I am trying to test if the server will return the string. However, I want to test it without actually connecting to the server. I am trying to get rid of the java.net.SocketException by mocking the server. What approach should I do?
The code below declares all of the necessary values for the server.
#RunWith(MockitoJUnitRunner.class)
public class testTransfer {
#Test
public void getServerMessage() {
//variables
String serverhostname = "serverA";
int portNo = 8888;
int versionNo = 9999;
String protocol = "tcp";
String serverInput = "input string";
String url = "http://localhost:8080/server/url";
Service service =new Service();
Call call = Mockito.mock(Call.class);
server_string result = Mockito.mock(server_string.class);
The code below calls the server and adds values inside the xml.
//try catch
try {
call=(Call)service.createCall();
QName qn = new QName("urn:ServerApiService", "server_string" );
call.registerTypeMapping(server_string.class, qn,
new org.apache.axis.encoding.ser.BeanSerializerFactory(server_string.class, qn),
new org.apache.axis.encoding.ser.BeanDeserializerFactory(server_string.class, qn));
call.setTargetEndpointAddress( new java.net.URL(url) );
call.addParameter("arg1", XMLType.XSD_STRING, ParameterMode.IN);
call.addParameter("arg2", XMLType.XSD_INT, ParameterMode.IN);
call.addParameter("arg3", XMLType.XSD_INT, ParameterMode.IN);
call.addParameter("arg4", XMLType.XSD_STRING, ParameterMode.IN);
call.addParameter("arg5", XMLType.XSD_STRING, ParameterMode.IN);
call.addParameter("arg6", qn, ParameterMode.OUT);
call.setOperationName( new QName(url, "getServerMessage") );
call.setReturnClass(server_string.class);
//result = (server_string) call.invoke( new Object[] {serverhostname,portNo,versionNo,protocol,serverInput} );
result = (server_string) Mockito.when(call.invoke( new Object[] {serverhostname,portNo,versionNo,protocol,serverInput} )).thenReturn(server_string.class);
System.out.println("Get server_string object from server ....");
// System.out.println("Value is "+result.value);
} catch (ServiceException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
However, I keep getting this
AxisFault
faultCode: {http://schemas.xmlsoap.org/soap/envelope/}Server.userException
faultSubcode:
faultString: java.net.SocketException: Connection reset
faultActor:
faultNode:
faultDetail:
{http://xml.apache.org/axis/}stackTrace:java.net.SocketException: Connection reset
I have used wiremock to good effect. It's a mocking framework specifically for this job.
You may also use JBoss and run the project. When mocking the server you need to use what #Bohemian said because it is much easier that way. However, the learning curve is high and is not recommended for emergency situations(i'm a newbie btw).
Related
I've been given the task to move our application from WebLogic 12.1.3 to Payara 4.1 and have run into an issue that I feel I'm more or less at the end of the line of troubleshooting.
We have an EJB (a Stateless bean) that has two methods, one that makes a call to the Google Maps Directions API and one that makes a call to the Google Maps Geocoding API, both using the same credentials and Googles client libraries for Java. Both methods work perfectly running on WebLogic, but after switching to Payara the one using the Directions API gives me an error. Here's the relevant part of the stacktrace:
java.io.IOException: Server Error: 403 Forbidden
at com.google.maps.internal.OkHttpPendingResult.parseResponse(OkHttpPendingResult.java:258)
at com.google.maps.internal.OkHttpPendingResult.await(OkHttpPendingResult.java:167)
at com.google.maps.PendingResultBase.await(PendingResultBase.java:56)
at com.somecompany.integration.GoogleDirectionsIntegration.getDirections(GoogleDirectionsIntegration.java:XXX)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
So the geocoding-method still works on both platforms, but when attempting the call to the Directions API I get a 403 back from Google indicating that my credentials are messed up, but the very same credentials work for the geocoding call. The code hasn't been changed in any way switching from one platform to the other.
What's even more confusing is that if I grab the actual URL of the call to Google from the logs and try it in my browser, i.e. paste "https://maps.googleapis.com/maps/api/directions/json?client=gme-company&mode=driving&arrival_time=1435825037&o
rigin=Somewhere&destination=Somewhere+else&alternat
ives=false&signature=nfre3XYZ2kmuDX8Qibce87ZFKQQ=" into Chrome, it works. I get a proper answer from Google. (btw, those aren't the actual credentials or origin and destination I'm using, they've been "anonymized" :-)). I've also checked that this URL (which is built by the client library) is the same running on both platforms as well as used the URL Signing Debugger on Google's developer pages, but to no avail. There should be nothing wrong with my credentials.
I'm really at the end of the line here, having spent days troubleshooting and searching online without finding a solution.
Not that it matters that much but I didn't write this code myself, and the person who did of course doesn't work here anymore :-P
Anyway, here's the code (somewhat anonymized):
#Stateless
public class GoogleDirectionsIntegration {
private static final Logger LOGGER = Logger.getLogger(GoogleDirectionsIntegration.class.getName());
private GeoApiContext context = null;
/**
* Initializer
*/
#PostConstruct
public void init() {
LOGGER.log(Level.INFO, "initiating {0}", this.getClass().getSimpleName());
this.context = new GeoApiContext().setEnterpriseCredentials("gme-company", "companyGoogleCryptographicSecret");
this.context.setReadTimeout(1, TimeUnit.SECONDS)
.setRetryTimeout(1, TimeUnit.SECONDS)
.setConnectTimeout(1, TimeUnit.SECONDS)
.setWriteTimeout(1, TimeUnit.SECONDS);
OkHttpRequestHandler okHttpRequestHandler = null;
OkHttpClient okHttpClient = null;
try {
Field requestField = this.context.getClass().getDeclaredField("requestHandler");
requestField.setAccessible(true);
okHttpRequestHandler = (OkHttpRequestHandler) requestField.get(this.context);
Field f = okHttpRequestHandler.getClass().getDeclaredField("client");
f.setAccessible(true);
okHttpClient = (OkHttpClient) f.get(okHttpRequestHandler);
} catch (NoSuchFieldException | SecurityException | IllegalAccessException | IllegalArgumentException e) {
throw new IllegalStateException("Failed to create SSL context", e);
}
SSLContext sslCtx = this.getSslContext();
if (sslCtx != null && okHttpClient != null) {
SSLSocketFactory sslSocketFactory = sslCtx.getSocketFactory();
okHttpClient.setSslSocketFactory(sslSocketFactory);
}
}
private SSLContext getSslContext() {
TrustManager[] tm = new TrustManager[] {
new CustomTrustManager()
};
SSLContext sslContext = null;
try {
sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(null, tm, new SecureRandom());
} catch (NoSuchAlgorithmException | KeyManagementException ex) {
throw new IllegalStateException("Failed to create SSL context", ex);
}
return sslContext;
}
public DirectionsRoute getDirections(final String origin, final String destination, final DistanceUnit distanceUnit,
#Nullable TransportMode mode, #NotNull Instant arrivalTime) throws NotFoundException {
TransportMode actualMode = mode == null ? TransportMode.CAR : mode;
DirectionsRoute[] directionsRoutes;
DirectionsApiRequest directionsApiRequest = DirectionsApi.getDirections(this.context, origin, destination);
directionsApiRequest.arrivalTime(new Instant(arrivalTime));
directionsApiRequest.alternatives(false);
directionsApiRequest.mode(this.toTravelMode(actualMode));
try {
DirectionsResult res = directionsApiRequest.await(); // THIS IS WHERE IT BREAKS!
directionsRoutes = res.routes;
} catch (Exception e) {
LOGGER.log(Level.WARNING, e.getMessage(), e);
throw new NotFoundException(e.getMessage());
}
if (directionsRoutes.length != 1) {
throw new NotFoundException("Failed to fetch valid directions");
}
return directionsRoutes[0];
}
public void getAddress(LatLng startLocation, Location location, boolean cacheOverride) throws Exception {
com.google.maps.model.LatLng gLatLng = new com.google.maps.model.LatLng(startLocation.getLat(), startLocation.getLng());
GeocodingApiRequest geocodingApiRequest = GeocodingApi.reverseGeocode(this.context, gLatLng);
GeocodingResult[] geocodingResults;
geocodingResults = geocodingApiRequest.await();
if (0 < geocodingResults.length) {
//.. Code that does stuff with the result..
} else {
LOGGER.log(Level.WARNING, "Received empty results from Google reverse geocode for [{0}].", startLocation);
}
}
}
So I solved it. The problem wasn't in the code but in the dependencies, or rather in a dependency to one of the dependencies - OkHttp. I simply changed the version and it works now.
I am trying to create a service provider in WSO2 Identity Server through Java program. The code block creating a service provider goes as follows.
public static OAuthKeySecret createOAuthServiceProvider(OAuthAppDetails oauthApp, String authType) {
OAuthKeySecret oauthKeySecret = new OAuthKeySecret();
try {
IdentityApplicationManagementServiceStub IAMStub = new IdentityApplicationManagementServiceStub(
null, oauthApp.getSERVER_URL() + "IdentityApplicationManagementService");
ServiceClient IAMClient = IAMStub._getServiceClient();
Authenticate.authenticate(IAMClient);
ServiceProvider serviceProvider = new ServiceProvider();
serviceProvider.setApplicationName(oauthApp.getAppName());
serviceProvider.setDescription(oauthApp.getAppDescription());
IAMStub.createApplication(serviceProvider);
System.out.println("Service Provider created");
} catch (Exception e) {
e.printStackTrace();
}
return oauthKeySecret;
}
When running the program I am getting the following error traced back to following line of code
IAMStub.createApplication(serviceProvider);
Complete trace
org.apache.axis2.AxisFault: Transport error: 302 Error: Found
at org.apache.axis2.transport.http.HTTPSender.handleResponse(HTTPSender.java:311)
at org.apache.axis2.transport.http.HTTPSender.sendViaPost(HTTPSender.java:194)
at org.apache.axis2.transport.http.HTTPSender.send(HTTPSender.java:75)
at org.apache.axis2.transport.http.CommonsHTTPTransportSender.writeMessageWithCommons(CommonsHTTPTransportSender.java:451)
at org.apache.axis2.transport.http.CommonsHTTPTransportSender.invoke(CommonsHTTPTransportSender.java:278)
at org.apache.axis2.engine.AxisEngine.send(AxisEngine.java:442)
at org.apache.axis2.description.OutInAxisOperationClient.send(OutInAxisOperation.java:430)
at org.apache.axis2.description.OutInAxisOperationClient.executeImpl(OutInAxisOperation.java:225)
at org.apache.axis2.client.OperationClient.execute(OperationClient.java:149)
at org.wso2.carbon.identity.application.mgt.stub.IdentityApplicationManagementServiceStub.createApplication(IdentityApplicationManagement
ServiceStub.java:601)
at com.xxxxx.identity.wso2.IdentityServerAdapter.IDManagementClient.createOAuthServiceProvider(IDManagementClient.java:52)
at com.xxxxx.identity.wso2.IdentityServerAdapter.IdentityServerRest$1.handle(IdentityServerRest.java:37)
at com.xxxxx.identity.wso2.IdentityServerAdapter.IdentityServerRest$1.handle(IdentityServerRest.java:19)
at spark.webserver.MatcherFilter.doFilter(MatcherFilter.java:138)
at spark.webserver.JettyHandler.doHandle(JettyHandler.java:54)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:179)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:136)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
at org.eclipse.jetty.server.Server.handle(Server.java:451)
at org.eclipse.jetty.server.HttpChannel.run(HttpChannel.java:252)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:266)
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.run(AbstractConnection.java:240)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:596)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:527)
at java.lang.Thread.run(Unknown Source)
Whats going on here? Please help me.
In Identity Server 5.1.0, "createApplication" operation works without any issue. Can you please post the full sample source code and Implementation of Authenticate.authenticate? Log the values of "oauthApp.getAppName()". Below code works for me.
try {
String authCookie = null;
ConfigurationContext ctx = ConfigurationContextFactory.createConfigurationContextFromFileSystem(null, null);
AuthenticationAdminStub authstub = new AuthenticationAdminStub(ctx, "https://localhost:9443/services`enter code here`/AuthenticationAdmin");
ServiceClient client = authstub._getServiceClient();
Options options = client.getOptions();`enter code here`
options.setManageSession(true);
options.setProperty(org.apache.axis2.transport.http.HTTPConstants.COOKIE_STRING, authCookie);
//set trust store properties required in SSL communication.
System.setProperty("javax.net.ssl.trustStore", RemoteUMSampleConstants.TRUST_STORE_PATH);
System.setProperty("javax.net.ssl.trustStorePassword", RemoteUMSampleConstants.TRUST_STORE_PASSWORD);
authstub.login("admin", "admin", "localhost");
authCookie = (String) authstub._getServiceClient().getServiceContext().getProperty(
HTTPConstants.COOKIE_STRING);
IdentityApplicationManagementServiceStub stub = new IdentityApplicationManagementServiceStub(
"https://localhost:9443/services/IdentityApplicationManagementService");
ServiceClient e = stub._getServiceClient();
Options option = e.getOptions();
option.setManageSession(true);
option.setProperty("Cookie", authCookie);
ServiceProvider serviceProvider = new ServiceProvider();
serviceProvider.setApplicationName("testName");
serviceProvider.setDescription("testDescription");
stub.createApplication(serviceProvider);
}catch (Exception e){
System.out.print(e);
}
I am getting the below exception before hitting a axis 2 webservice.
org.apache.axis2.AxisFault: org.apache.axis2.databinding.ADBException: Unexpected subelement underTimelyRenewal
I can't reproduce the same issue locally on tomcat or in DEV environment which runs in Weblogic. It justs happens only in 1 environment which runs on Weblogic 11g. This makes to think that I am missing some config in that environment, I am not sure what it is. Any help on this is highly appreciated.
Here is code that calls web service.
public com.ibs.accouting.employeeVerificationResponse getEmployeeVerificationRequest(
com.ibs.accounting.EmployeeVerificationRequest employeeVerificationRequest108)
throws java.rmi.RemoteException
{
org.apache.axis2.context.MessageContext _messageContext = null;
try{
org.apache.axis2.client.OperationClient _operationClient = _serviceClient.createClient(_operations[5].getName());
_operationClient.getOptions().setAction("http://ibs.com/accounting/WBLEmployeeVerificationRequest");
_operationClient.getOptions().setExceptionToBeThrownOnSOAPFault(true);
addPropertyToOperationClient(_operationClient,org.apache.axis2.description.WSDL2Constants.ATTR_WHTTP_QUERY_PARAMETER_SEPARATOR,"&");
// create a message context
_messageContext = new org.apache.axis2.context.MessageContext();
// create SOAP envelope with that payload
org.apache.axiom.soap.SOAPEnvelope env = null;
env = toEnvelope(getFactory(_operationClient.getOptions().getSoapVersionURI()),
employeeVerificationRequest108,
optimizeContent(new javax.xml.namespace.QName("http://ibs.com/accounting",
"getEmployeeVerificationRequest")));
//adding SOAP soap_headers
_serviceClient.addHeadersToEnvelope(env);
// set the message context with that soap envelope
_messageContext.setEnvelope(env);
// add the message contxt to the operation client
_operationClient.addMessageContext(_messageContext);
//execute the operation client
_operationClient.execute(true);
org.apache.axis2.context.MessageContext _returnMessageContext = _operationClient.getMessageContext(
org.apache.axis2.wsdl.WSDLConstants.MESSAGE_LABEL_IN_VALUE);
org.apache.axiom.soap.SOAPEnvelope _returnEnv = _returnMessageContext.getEnvelope();
java.lang.Object object = fromOM(
_returnEnv.getBody().getFirstElement() ,
com.ibs.accounting.EmployeeVerificationResponse.class,
getEnvelopeNamespaces(_returnEnv));
return (com.ibs.accounting.EmployeeVerificationResponse)object;
}catch(org.apache.axis2.AxisFault f){
org.apache.axiom.om.OMElement faultElt = f.getDetail();
if (faultElt!=null){
if (faultExceptionNameMap.containsKey(faultElt.getQName())){
//make the fault by reflection
try{
java.lang.String exceptionClassName = (java.lang.String)faultExceptionClassNameMap.get(faultElt.getQName());
java.lang.Class exceptionClass = java.lang.Class.forName(exceptionClassName);
java.lang.Exception ex=
(java.lang.Exception) exceptionClass.newInstance();
//message class
java.lang.String messageClassName = (java.lang.String)faultMessageMap.get(faultElt.getQName());
java.lang.Class messageClass = java.lang.Class.forName(messageClassName);
java.lang.Object messageObject = fromOM(faultElt,messageClass,null);
java.lang.reflect.Method m = exceptionClass.getMethod("setFaultMessage",
new java.lang.Class[]{messageClass});
m.invoke(ex,new java.lang.Object[]{messageObject});
throw new java.rmi.RemoteException(ex.getMessage(), ex);
}catch(java.lang.ClassCastException e){
// we cannot intantiate the class - throw the original Axis fault
throw f;
} catch (java.lang.ClassNotFoundException e) {
// we cannot intantiate the class - throw the original Axis fault
throw f;
}catch (java.lang.NoSuchMethodException e) {
// we cannot intantiate the class - throw the original Axis fault
throw f;
} catch (java.lang.reflect.InvocationTargetException e) {
// we cannot intantiate the class - throw the original Axis fault
throw f;
} catch (java.lang.IllegalAccessException e) {
// we cannot intantiate the class - throw the original Axis fault
throw f;
} catch (java.lang.InstantiationException e) {
// we cannot intantiate the class - throw the original Axis fault
throw f;
}
}else{
throw f;
}
}else{
throw f;
}
} finally {
_messageContext.getTransportOut().getSender().cleanup(_messageContext);
}
}
This error can be kind of misleading. AFter I modified the WSDL and added a new mandatory element, I created my client. Than this error appeared. The solution was, that I forgot to fill this element in one method of the my web service. If this error appears, also check if your mandatory elements are filled within the server.
That it works under one environment and not in an other can also mean, that a mandatory item is filled on one server (development server) and not under the other (productive server).
Hey, have been trying to work this out for last day or so but hitting brick wall. Trying to unit test this bit of code. But not sure if need to use EasyMock or not?? Seem few examples online but seem to be using older techniques.
public boolean verifyConnection(final String url) {
boolean result;
final int timeout = getConnectionTimeout();
if (timeout < 0) {
log.info("No need to verify connection to client. Supplied timeout = {}", timeout);
result = true;
} else {
try {
log.debug("URL: {} Timeout: {} ", url, timeout);
final URL targetUrl = new URL(url);
final HttpURLConnection connection = (HttpURLConnection) targetUrl.openConnection();
connection.setConnectTimeout(timeout);
connection.connect();
result = true;
} catch (ConnectException e) {
log.warn("Could not connect to client supplied url: " + url, e);
result = false;
} catch (MalformedURLException e) {
log.error("Malformed client supplied url: " + url, e);
result = false;
} catch (IOException e) {
log.warn("Could not connect to client supplied url: " + url, e);
result = false;
}
}
return result;
}
It just take's in a url checks its valid and returns T or F.
I have always observed that Mocking Can be avoided as much as possible because it can lead to difficult to maintain JUnit tests and defeat the whole purpose.
My suggestion would be to create a temporary server on your local machine from a JUnit itself.
At the beginning of JUnit you can create a server(not more than 10-15 lines of coding required) using Java sockets and then in your code pass the URL for the local server. This way you are reducing mocking and ensuring maximum code coverage.
Something like this -
public class SimpleServer extends Thread {
public void run() {
try {
serverSocket = new ServerSocket(port);
while (true) {
Socket s = serverSocket.accept();
}
}
catch (IOException e) {
e.printStackTrace();
}
finally {
serverSocket = null;
}
}
}
If you want to mock this method, I'd recommend passing in the URL rather than the String. Don't have your method create the URL it needs; let the client create the URL for you and pass it in. That way your test can substitute a mock if it needs to.
It's almost a dependency injection idea - your method should be given its dependencies and not create them on its own. The call to "new" is the dead giveaway.
It's not a drastic change. You could overload the method and have two signatures: one that accepts a URL string and another that accepts the URL itself. Have the first method create the URL and call the second. That way you can test it and still have the method with the String signature in your API for convenience.
Trying to set up mock implementation of the HttpURLConnection. Like
public class MockHttpURLConnection extends HttpURLConnection {'
then added method to class to override
' protected HttpURLConnection createHttpURLConnection(URL url)
throws IOException {
return (HttpURLConnection) url.openConnection();
}
So test looking something like this:
#Test
public void testGetContentOk() throws Exception
{
String url = "http://localhost";
MockHttpURLConnection mockConnection = new MockHttpURLConnection();
TestableWebClient client = new TestableWebClient();
client.setHttpURLConnection(mockConnection);
boolean result = client.verify(url);
assertEquals(true, result);
}
#Test
public void testDoesNotGetContentOk() throws Exception
{
String url = "http://1.2.3.4";
MockHttpURLConnection mockConnection = new MockHttpURLConnection();
TestableWebClient client = new TestableWebClient();
client.setHttpURLConnection(mockConnection);
boolean result = client.verify(url);
assertEquals(false, result);
}
/**
* An inner, private class that extends WebClient and allows us
* to override the createHttpURLConnection method.
*/
private class TestableWebClient extends WebClient1 {
private HttpURLConnection connection;
/**
* Setter method for the HttpURLConnection.
*
* #param connection
*/
public void setHttpURLConnection(HttpURLConnection connection)
{
this.connection = connection;
}
/**
* A method that we overwrite to create the URL connection.
*/
#Override
public HttpURLConnection createHttpURLConnection(URL url) throws IOException
{
return this.connection;
}
}
First part passed but is getting true for false dummy test, thanks for feedback back so far best site I have found for help. So let me know if think on right track
i try to passing numeric parameter to a web service that receive the value and return it back.
this is the snippet of the web method :
#WebMethod(operationName = "getNumber")
public Integer getNumber(#WebParam(name = "i")
Integer i) {
//TODO write your implementation code here:
System.out.println("number : "+i);
return i;
}
an this is the snippet of my client code :
Map results = FastMap.newInstance();
results.put("result", "success");
String endPoint = "http://localhost:8084/ProvideWS/MathWS";
URL endpoint=null;
try{
endpoint = new URL(endPoint);
}
catch (MalformedURLException e) {
org.ofbiz.base.util.Debug.log("Location not a valid URL "+e);
// TODO: handle exception
}
Service service = null;
Call call = null;
try{
service = new Service();
call = (Call)service.createCall();
call.setTargetEndpointAddress(endpoint);
String nameSpace = "http://ws/";
String serviceName = "getNumber";
call.setOperationName(new QName(nameSpace, serviceName));
call.addParameter("i",org.apache.axis.Constants.XSD_INTEGER , ParameterMode.IN);
call.setReturnType(org.apache.axis.Constants.XSD_INTEGER);
Object msg[] = new Object[]{new Integer(5)};
for (Object o : msg) {
org.ofbiz.base.util.Debug.log("object to be sent===== "+o.toString());
}
Object ret = call.invoke(msg);
results.put("result", "result : "+ ret.toString());
}
catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
org.ofbiz.base.util.Debug.log("exc when running soap client test : "+e);
results.put("result", "error : "+e);
}
return results;
the problem is the return value in the client always 0 (the server received the number as zero), the method i used to pass the parameter works fine when the paramater is String. I.ve tried to hard-coding the return value in server and the output in client is fine, so i thought it must be how the server retrieved the parameter is the problem.
do you have any idea why this is happen and how to solve this?
any help will be appreciated, thanks
I don't know what is causing your problem. But the first thing I would do would be to try to capture the actual request that is being sent to the server. That should give you some clues as to whether the root problem is on the client or server side.