Wednesday Mar 11, 2015

WebSocket vs REST

As controversial or potentially “flame starting” topic this might seem to be, don’t worry. I will approach this purely from pro-WebSocket view and the comparison with REST will be done on the sample, which heavily favours WebSocket ;)

Not that long ago, I had to explain one of my colleague where he should consider using WebSocket protocol and I realised, that lots of people don’t really know about it much. I even heard question whether WebSocket is successor of REST, like REST was/is to SOAP web services.. well, I’ll try to make this little bit clearer in this post.

For starters, WebSocket is NOT REST replacement. These are two technologies, which can coexist very nicely even in single application or webpage. Both are doing similar things and for some applications are even interchangeable. Bold statement, but it’s true. Both approaches have it’s own pros and cons, as with everything else..

Let’s go little back to the history of web services and remember why was WebSocket protocol even created – to allow bi-directional communication with clients, mainly represented by web pages. It was (and still is) possible to achieve the same with plain REST, but there are some issues with it. Let’s name two of them:

  • REST is always Request/Response “stateless” communication,
  • by the nature of the HTTP protocol, lots of information must be sent in each Request and response.

The first one implies simple fact – web server cannot send anything to the webpage without a Request. There are various workarounds (yes, workarounds. First real standard solution is WebSocket protocol) like long polling or JSONP, but they are solving only the communication from server to client, which implies that there needs to be the other channel from client to server. And we are getting to the second item in short list above – efficiency. When the application needs to communicate frequently with the server, the volume of HTTP traffic can be really big. Now try to compute the entropy (how much information is acquired due to observation of the Request/Response) and see how much redundant and unimportant bytes is sent with each HTTP communication. This is already addressed in HTTP/2, but anyway, the overhead still exists. I intentionally skip the HTTP/2 server push implementation – I plan to address that in another blogpost.

Enough with history lesson and plain “code-less” chatter. You might remember Shared collection sample introduced couple of moths ago in Tyrus workspace – the last modification of it was inspired by the discussion with that colleague – I wanted to compare REST and WebSocket implementation of the same thing.

Quick recapitulation – the sample exposes a map-like object in JavaScript and Java and synchronises their changes using WebSocket. Additionally you can register listeners on both sides, so you always know when anyone changes anything.. basically very simple and fragile implementation of coherence/hazelcast/yourFavouriteDistributedFramework.

As you most likely already expect, I implemented the same thing (or extended that implementation) to support REST based transport instead of WebSocket. I must admit I cheated a little – I used Server-Sent Events (SSE), which is not really RESTful, but this feature is implemented as part of Jersey, which is Reference Implementation of JAX-RS: The JavaTM API for RESTful Web Services and also my favourite REST framework (I used to be a contributor, so consider this as another very-impartial fact). SSE also has simple JavaScript API implemented in all modern browsers, so it was an easy decision for me – I have the channel from server to client covered and the other way will be just standard request using XMLHttpRequest (or an ActiveXObject when in M$ Internet Explorer).

Below is the simple scheme with mentioned protocols.

Screen Shot 2015-03-11 at 16.10.45

When you compile and deploy the sample, the standard behaviour is actually quite comparable (I’m on localhost, so that is not that much surprising), both maps receive and send updates and the experience is almost the same. So let’s look under the hood…

When you create or modify a map entry, browsers sends and event. In case of WebSocket, it is short (text) message containing Json “object” + few bytes (let’s say 8) of overhead – WebSocket message “header”. On the other hand, when you do the same action on REST version of the page, HTTP Request is sent (and Response received – it does not contain anything, just status, headers and no entity):

Screen Shot 2015-03-11 at 16.44.18

I don’t even want to know what is the overhead in this case…

You might say that it does not matter, since we are not connected using 33.6 kbps modems.. well, that’s true, but every byte introduces some delay, additional handling in the network and even app servers – they have to read/write that byte, even when it won’t be used. And that’s not everything related to the resources utilisation – imagine, that every such Request created new TCP connection to the server (which does not need to be true when HTTP keep-alive is used). Since I’m still on localhost, I wanted to push the bar little higher and wrote simple performance test: method, which will create 10k updates of single map entry (you can execute it by clicking on [PerfTest] button on the sample page – both Rest and WebSocket version have it). Starting with WebSocket – it does what is expected and I can measure how long it takes to have some comparison with REST.. but.. the problem is that the REST version does not even finish. I did not dig into that that much, but seems like every browser has some kind of limit for JavaScript requests. I was usually able to achieve something around 3-5k Requests, but after that, browser “run out of resources”, most likely to protect itself or the target site from potentially DDoS-y case.

Conclusion? If you need truly bi-directional communication, with high frequency of shorter messages (it is far easier to handle those in JavaScript than big ones), you should consider using WebSocket. On the other side, if you have some application, which already works well and uses REST efficiently, you don’t need to change that! REST is still the best solution for lots of use cases, you should look at these two technologies/protocols as complements, not as competitors.

And please don’t forget, that this was not by any means an attempt for unbiased comparison :-) Any comments/feedback is appreciated!

Links

Friday Feb 11, 2011

Replacing client used in Jersey Test Framework

There was an interesting question on mailing list - Is it possible to replace default Jersey client in JerseyTest? (JerseyTest is a class from Jersey Test Framework, basically superclass of all "Jersey enabled" tests).

Answer is.. not really. It just wasn't made to support this option. Until now :)

There was method getClient(), but it couldn't be easily overriden (it was private static) and it actually does more that create Client, so it isn't really method you want to replace. Long story short.. I created overridable ClientFactory which is (surprisingly) responsible for creating new Clients..

Let's say we want to just replace default Jersey Client implementation by Apache:

public class MainTest extends JerseyTest {
    ...
    @Override
    protected ClientFactory getClientFactory() {
        return new ClientFactory() {
            @Override
            public Client create(ClientConfig clientConfig) {
                return ApacheHttpClient.create(clientConfig);
            }
        };
    }
}

This overrides default client implementation BUT still allows container override this and use its client (this is used for example in InMemoryTestContainer). If you want to have total control and replace even this concept, you can do it by overriding getClient() method:

    @Override
    protected Client getClient(TestContainer tc, AppDescriptor ad) {
        return ApacheHttpClient.create(ad.getClientConfig());
    }

but I don't recommend this approach, former one should be sufficient in most cases.

This functionality is present in 1.6-SNAPSHOT and 1.6-ea03 (and newer Jersey versions).

Friday Feb 04, 2011

Jersey Client - Making requests with HttpURLConnection hack

HttpURLConnection provided in JDK has serious limitation - you can't do requests with arbitrary chosen method - actually it limit methods to some subsed defined in javadoc, which is NOT extendable in any way.. which might cause some problems. For example you can't do PATCH request because it wasn't in HTTP spec in the time of writing that class. And if you would want to use it for making WebDav client, you are totally lost, it just can't be done.

 Well, until now ;)

One Jersey user - Markus Karg came with a workaround. Method name can be "injected" to one field in HttpURLConnection via injection and it will be used to create request. We don't want to have this enabled by default, so if you want to use it, you need to set property URLConnectionClientHandler.PROPERTY_HTTP_URL_CONNECTION_SET_METHOD_WORKAROUND to true.

        DefaultClientConfig config = new DefaultClientConfig();
        config.getProperties().put(URLConnectionClientHandler.PROPERTY_HTTP_URL_CONNECTION_SET_METHOD_WORKAROUND, true);
        Client c = Client.create(config);

        WebResource r = c.resource(getUri().path("test/entity").build());

        ClientResponse cr = r.method("GOTOSLEEP", ClientResponse.class);

will produce:

Feb 4, 2011 11:25:44 AM com.sun.jersey.api.client.filter.LoggingFilter log
INFO: 1 \* Client out-bound request
1 > GOTOSLEEP http://localhost:9997/test/test

and you can cleary see that method "GOTOSLEEP" was used and HttpURLConnection don't complain about anything. This workaround has some limitation - you can't put an entity to the request. And additionally, it \*probably\* wont work on some containers and maybe in future versions of JDK (containers sometimes have their own implementation of HttpURLConnection).

This functionality is available in current Jersey trunk (1.6-SNAPSHOT) and in promoted build 1.6-ea02 (which will be released later today).

Wednesday Jan 26, 2011

Jersey - Apache HTTP Client 4.1 (experimental) integration available

What is supported? All basic functionality (simple requests with or without entity) and Jersey related things like filters (logging, auth, ...). Additionally, some of Apache HTTP Client features are supported by default - like cookies processing.

What needs to be done:

  • Apache style auth support
  • Proxy configuration settings
  • Caching and other features...

How can you give it a try? Simply.. put another dependency to your maven project:

        <dependency>
            <groupId>com.sun.jersey.experimental</groupId>
            <artifactId>jersey-apache-client4</artifactId>
            <version>1.6-SNAPSHOT</version>
        </dependency>

And create your client like:

        Client c = new com.sun.jersey.client.apache.ApacheHttpClient4();

        WebResource wr = c.resource("http://localhost:9998/helloworld-webapp");

        String s = wr.path("helloworld").get(String.class);

        System.out.println(s);

and use it like you are used to..

Comments/remarks/suggestions are welcomed!

Wednesday Jan 12, 2011

Returning XML representation of List from Jersey

One user asked about wrapping element that Jersey uses when serves List<T> recently and I recalled I didn't write a blog post about one change I made in this area so.. here we are..

 Jersey supports returning JSR222 (JAXB) annotated classes and even List of them. Let's say we have following class:

@XmlRootElement
public class A {
        public String s;
        public int i;
}

and some corresponding method which returns List<A>

    @GET
    @Path("list")
    public List<A> getList() {
        ArrayList<A> list = new ArrayList<A>();

        return list;

    }

 when we deploy this and do a GET on /list, we'll see something like

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<as><a><s>first</s><i>1</i></a><a><s>second</s><i>2</i></a></as>

which is fine and valid for most cases, but you might need something else. For example, you want to rename <a> to something meaningful, let's say "contract". XmlRootElement annotation has "name" property which is designed for these cases, so lets add it to our class A annotation (@XmlRootElement(name="contract") public class A { ... } ) and make that request again..

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<as><contract><s>first</s><i>1</i></contract><contract><s>second</s><i>2</i></contract></as>

Looks better, <a> has changed to <contract> and we might be satisfied.. oh wait, what about <as>? It haven't changed! Firstly, <as> was just plural for <a> and it stayed same when XmlRootElements name property was specified.. oops. This was a bug in Jersey and we couldn't fix it without breaking backwards compatibility, so fix for this is enabled only when FEATURE_XMLROOTELEMENT_PROCESSING is enabled (set to true). Lets do that and see how output changes..

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<contracts><contract><s>first</s><i>1</i></contract><contract><s>second</s><i>2</i></contract></contracts>

Ha, we are almost there. Or.. depends what you want, I guess most of users is satisfied and doesn't need to read further. But there is always someone who wants something "extra".. like specify wrapping element. That is needed for some cases (like B2B communication) and Jersey needs to be able to handle this usecase as well. Unfortunately its not as clean as other solutions. We need to implement our own MessageBodyWriter and for those not familiar with JAX-RS API - it basically allows you to specify how the output will be presented for which mediatype.

Your MessageBodyWriter needs to have @javax.ws.rs.ext.Provider  annotation (or be added as provider to ResourceConfig) and implement MessageBodyWriter interface.. I create such class which is sufficient for basic cases, you might want to improve it by creating support for other charsets than UTF-8 or gathering Marshaller from other source that creating new instance.. but at least its a working startpoint..

@Provider
public class ListAProvider implements MessageBodyWriter<List> {

  private String myWrapElemName = "wrapper";
  private Marshaller m;

  public ListAProvider() throws Exception{
    JAXBContext context = JAXBContext.newInstance(A.class);
    m = context.createMarshaller();
    m.setProperty(Marshaller.JAXB_FRAGMENT, true);
  }

  @Override
  public long getSize(List as, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
    return -1;
  }

  @Override
  public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {

    if(mediaType.getSubtype().endsWith("xml") &&
          List.class.isAssignableFrom(type) &&
          genericType instanceof ParameterizedType) {
      if ((((ParameterizedType)genericType).getActualTypeArguments()[0]).equals(A.class)) {
        return true;
      }
    }

    return false;
  }

  @Override
  public void writeTo(List list, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException {

    Charset c = Charset.forName("UTF-8");
    String cName = c.name();

    entityStream.write(String.format("<?xml version=\\"1.0\\" encoding=\\"%s\\" standalone=\\"yes\\"?>", cName).getBytes(cName));

    entityStream.write(String.format("<%s>", myWrapElemName).getBytes(cName));

    for (Object o : list)
      try {
        m.marshal(o, entityStream);
      } catch(JAXBException exp) {}

    entityStream.write(String.format("</%s>", myWrapElemName).getBytes(cName));

  }
}

when you add this to your Jersey enabled application and make same request as we did earlier, you should get:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<wrapper><contract><s>first</s><i>1</i></contract><contract><s>second</s><i>2</i></contract></wrapper>

and we are done for this article, hope somebody will find it useful.

Tuesday May 05, 2009

Jersey Client - Using ContainerListener

I would like to introduce a possibility to monitor connections made within jersey client api. Monitoring in this case means have knowledge about amount of transferred bytes.

This functionality is provided (as many others) as a client filter, concretely ConnectionListenerFilter. To create ConnectionListenerFilterinstance, you have to implement OnStartConnectionListener. It might look as follows:

class ListenerFactory implements OnStartConnectionListener {
     public ContainerListener onStart(ClientRequest cr) {
         return new Listener();
    }
}

Looks simple (and it is). This factory creates ContainerListener instance based on ClientRequest. Important note: ClientRequest instance here may be used only for getting values. Any attempt to change anything will produce UnsupportedOperationException.

Ok. Now we need ContainerListener implementation. This class is defined as:

public abstract class ContainerListener {

    public void onSent(long delta, long bytes) {};

    public void onReceiveStart(long totalBytes) {};

    public void onReceived(long delta, long bytes) {};

    public void onFinish() {};
}

Parameters should be easy to understand, delta is always difference to previous call, bytes is sum of sent/received bytes so far and totalBytes is value from http header (if present, otherwise is set to -1).

Every request has a simple "lifecycle":

1) Send request
2) Receive response

Method onSent is called during step one only when request has some entity. When request is sent and response has no entity, onFinish() is called and that's it. More interesting scenario is when response contains some entity - onReceiveStart is called after sending a request and onReceived during data receiving. Method onFinish is called after whole entity is read.

You will probably have some nice GUI progress bar but I'm going to do only plain text log for now to keep it simple.

class Listener extends ContainerListener {
     @Override
     public void onSent(long delta, long bytes) {
         System.out.println("onSent: delta: " + delta + " bytes: " + bytes);
     }

     @Override
     public void onReceiveStart(long totalBytes) {
         System.out.println("onReceiveStart: " + totalBytes);
     }

     @Override
     public void onReceived(long delta, long bytes) {
         System.out.println("onReceived: delta: " + delta + " bytes: " + bytes);
     }

     @Override
     public void onFinish() {
         System.out.println("onFinish");
     }
}

And we're almost done! Last thing to do is register our ListenerFactory as a client filter:

    Client c = Client.create();

    c.addFilter(new ConnectionListenerFilter(new ListenerFactory()));

and actually do a request:

    WebResource r = c.resource("http://download.java.net/maven/2/com/sun/jersey/samples/jersey-samples/1.1.0-ea/jersey-samples-1.1.0-ea-project.zip");
    r.get(String.class);

Output should look like this:

onReceiveStart: 615534
onReceived: delta: 3677 bytes: 3677
onReceived: delta: 2608 bytes: 6285
...
onReceived: delta: 2608 bytes: 613949
onReceived: delta: 1585 bytes: 615534
onFinish

You can experiment with some requests containing entity (so you will see that onSent method is called during sending).

For more information about project Jersey visit http://jersey.dev.java.net

About

Pavel Bucek-Oracle

Search

Categories
Archives
« July 2015
SunMonTueWedThuFriSat
   
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
 
       
Today