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

Thursday Feb 12, 2015

Tyrus 1.10

Tyrus 1.10 is a maintenance relase, so unfortunately no cool new features to highlight, but we were able to add some new examples – Shared collection and BTC Xchange.

Complete list of fixed issues is below. I would like to thank to java.net users “toto2″ and “gray” for their help with GLASSFISH-21213. There is one other issue worth mentioning and that is TYRUS-329. In 1.10 release, Tyrus (after added tests for Per-Message compression) becomes fully compliant with Autobahn Test Suite.

Let me shortly introduce the new samples which were added in this version, just to make this blog post justifiable :).

Shared Collection Sample

In this example I tried to explore one possibility of data sharing between JavaScript client and Java code. The sample name could be misleading, the only implemented collection is a Map, which is backed up by WebSocket connection going back to the server. Both sides do implement “standard” Map, with some added flavour of distributed structures – update listener.

If you want to try it, you can follow the instructions in README.html; the application should present itself with a simple JavaScript based interface to modifying the content of the map. Don’t modify the content right away, open it in another window and observe how fast are changes propagated to other window(s).

BTC Xchange Sample

Another sample which demonstrates simple-to-implement communication with JavaScript code. On the title page you can see the graph, which in this case represents current exchange rate BTC/USD and there are some buy and sell proposals which can be executed. The sample currently allow you to go to “red numbers”, but you can create few simple modifications and make a “game” from that – the sample can be also opened from multiple windows and you can try to compete with someone else; for example, you can make a goal of getting some amount of USD or BTC :)

Complete list of changes

  • [TYRUS-394]  – Proxy bypassed on Linux but not on Windows.
  • [TYRUS-391]  – Tyrus reconnect handler not reached on Android.
  • [TYRUS-393] – Incorrect handling of sending whole message during (unfinished) partial message.
  • [TYRUS-329] – Deflate extension fixes – autobahn test suite.
  • [GLASSFISH-21213] – Deadlock (HttpSession vs TyrusWebSocket).

Monday Aug 18, 2014

Tyrus 1.8

Another version of Tyrus, the reference implementation of JSR 356 – Java API for WebSocket is out! Complete list of fixes and features is below, but let me describe some of the new features in more detail. All information presented here is also available in Tyrus documentation.

What’s new?

First to mention is that JSR 356 Maintenance review Ballot is over and the change proposed for 1.1 release was accepted. More details about changes in the API can be found in this article. Important part is that Tyrus 1.8 implements this API, meaning you can use Lambda expressions and some features of Nashorn without the need for any workarounds.

Almost all other features are related to client side support, which was significantly improved in this release. Firstly – I have to admit, that Tyrus client contained security issue – SSL Hostname verification was not performed when connecting to “wss” endpoints. This was fixed as part of TYRUS-339 and resulted in some changes in the client configuration API. Now you can control whether HostnameVerification should be performed (SslEngineConfigurator#setHostnameVerificationEnabled(boolean)) or even set your own HostnameVerifier (please use carefully): #setHostnameVerifier(…). Detailed description can be found in Host verification chapter.

Another related enhancement is support for Http Basic and Digest authentication schemes. Tyrus client now enables users to provide credentials and underlying implementation will take care of everything else. Our implementation is strictly non pre-emptive, so the login information is sent always as a response to 401 Http Status Code. If the Basic and Digest are not good enough and there is a need to use some custom scheme or something which is not yet supported in Tyrus, custom Authenticator can be registered and the authentication part of the handshake process will be handled by it. Please seeClient HTTP Authentication chapter in the user guide for more details.

There are other features, like fine-grain threadpool configuration for JDK client container, build-in Http redirect support and some reshuffling related to unifying the location of client configuration classes and properties definition – every property should be now part of ClientProperties class. All new features are described in the user guide – in chapterTyrus proprietary configuration.

Update – Tyrus 1.8.1

There was another slightly late reported issue related to running in environments with SecurityManager enabled, so this version fixes that. Another noteworthy fixes are TYRUS-355 and TYRUS-361; the first one is about incorrect thread factory used for shared container timeout, which resulted in JVM waiting for that thread and not exiting as it should. The other issue enables relative URIs in Location header when using redirect feature.

Links

Complete list of changes:

Bug

  • [TYRUS-333] – Multiple endpoints on one client
  • [TYRUS-334] – When connection is closed by a peer, periodic heartbeat pong is not stopped
  • [TYRUS-336] – ReaderBuffer.getNextChars() keeps blocking a server thread after client has closed the session
  • [TYRUS-338] – JDK client SSL filter needs better synchronization during handshake phase
  • [TYRUS-339] – SSL hostname verification is missing
  • [TYRUS-340] – Test PathParamTest are not stable with JDK client
  • [TYRUS-341] – A control frame inside a stream of continuation frames is treated as the part of the stream
  • [TYRUS-343] – ControlFrameInDataStreamTest does not pass on GF
  • [TYRUS-345] – NPE is thrown, when shared container timeout property in JDK client is not set
  • [TYRUS-346] – IllegalStateException is thrown, when using proxy in JDK client
  • [TYRUS-347] – Introduce better synchronization in JDK client thread pool
  • [TYRUS-348] – When a client and server close connection simultaneously, JDK client throws NPE
  • [TYRUS-356] – Tyrus cannot determine the connection port for a wss URL
  • [TYRUS-357] – Exception thrown in MessageHandler#OnMessage is not caught in @OnError method
  • [TYRUS-359] – Client based on Java 7 Asynchronous IO makes application unexitable

Improvement

  • [TYRUS-328] – JDK 1.7 AIO Client container – threads – (setting threadpool, limits, …)
  • [TYRUS-332] – Consolidate shared client properties into one file.
  • [TYRUS-337] – Create an SSL version of Basic Servlet test

New Feature

  • [TYRUS-228] – Add client support for HTTP Basic/Digest

Task

  • [TYRUS-330] – create/run tests/servlet/basic via wss
  • [TYRUS-335] – [clustering] – introduce RemoteSession and expose them via separate method (not include remote sessions in the getOpenSessions())
  • [TYRUS-344] – Introduce Client support for HTTP Redirect

Monday Jun 16, 2014

Tyrus 1.7

This release cycle was shorter comparing to previous ones and the main purpose was to integrate and align with upper stack projects. Despite that fact there are some new features and bugfixes:

What’s new?

Ondřej added nice feature which allows to limit opened WebSocket sessions. And it is configurable, currently supported scopes of limitations are “per endpoint”, “per client remote address” and “per application”. This should allow effective control of server-side resources. For now, the values are static and constant, but if there will be a demand for creating more advanced session limit support, like dynamic load-balancing among set of endpoints etc, we will provide that as well. Feel free to file an enhancement request if you are interested!

Petr improved monitoring support which was added in Tyrus 1.6 – now there is a possibility to get the information about errors reported to the endpoints. Errors are collected per Exception type thrown, so you might get number of decoding issues, IOExceptions and other custom Throwables used in the endpoint implementation.

Other than that, there were just bugfixes. Two of them are worth mentioning: Tyrus now supports close codes 1012 and 1013 (SERVICE RESTART and TRY AGAIN LATER). Both of these can be returned only from server endpoint. When client side will try to send those, they will be replaced with close code 1000.

And the other bugfix is about SSL/TLS support in Java 7 AIO based client container – the issue manifested when bigger messages were sent from the client to server. The handling was not correct in this case and it could happen that the message was not completely sent.

Complete list of changes

TYRUS-313 Limiting opened connections
TYRUS-319 Bug call EJB from WebSocket
TYRUS-320 When put under heavy load by multiple clients, server throws an exception.
TYRUS-321 tyrus-client incorrectly reports HTTP staus 500 for all non-101 responses
TYRUS-322 Some of session close-codes are not supported
TYRUS-324 Session.close() should close the connection without close reason.
TYRUS-325 Server do not close session properly if non-instantiable endpoint class is provided
TYRUS-326 Expose monitoring statistics about number of @OnError method invocations
TYRUS-327 JDK client container cannot handle “big” messages over SSL/TLS

Link

If you have any feedback, suggestions or improvement requests, please send us a note tousers@tyrus.java.net or create new issue at our JIRA instance.

Friday May 16, 2014

Tyrus 1.6

I’m pleased to announce that Tyrus 1.6 was released this week and there are some nice features and bugfixes in this release, so let me introduce most important ones.

What’s new?

First bigger addition is JDK Client Transport. We used exclusively Grizzly framework for handling transport layer on the client side and now we provide an option to switch to JDK 1.7 based transport. Main advantage of it is reduced size and number of jar files. The default transport is still based on Grizzly, so if you want to try JDK 1.7 AIO, you need to enable it explicitly. See JDK 7 client chapter in Tyrus documentation for more details. This feature was implemented by Petr Janouch, new Tyrus team member.

Another important feature is monitoring server side resource utilisation and exposing this info as statistics via JMX beans. Currently available statistics are mostly about number and type of received and send messages, also there is higher level part which provides list of deployed endpoints and number of concurrent connected clients. If you want to try this out, see JMX Monitoring chapter in Tyrus user guide for configuration details. This feature was also contributed by Petr.

Next in line is change related to an issue in JSR 356. Current version of Session interface does allow use of Lambda expressions when registering a message handler, but it does not work as expected. Problem is, that it just cannot work. The API forces implementation to get the generic type information during runtime, which is not always available (in Java). Most usecases do work fine, but one doesn’t – when the anonymous class implementation is replaced by Lambda expression, type information is lost. Tyrus now implements proposed solution, but to be able to access mentioned methods, you need to cast Session to TyrusSession. Then you need to provide type of the message handler (java.lang.Class) and the handler itself – since the type info is now separated, there is no need to get it from the message handler instance , thus it will work with any representation. We are working on porting this fix to the WebSocket API, stay tuned for more details.

Last and in this case maybe least feature is WSADL. Don’t worry if you don’t know what that is – nobody does :). WSADL is XML (for now) descriptor of deployed application, currently providing only set of deployed endpoints (mainly because other information, like registered message handlers, is not that easy to get before client connects to the endpoint and can vary per Session). WSADL stands for WebSocket Application Descriptor Language and it is supposed to be the same as WADL is for RESTful webservices and WSDL for SOAP webservices. In case you want to test this feature, enableWSADL_SUPPORT (or see the test) and GET /contextPath/application.wsadl on your deployed Tyrus application.

Complete list of changes

TYRUS-301 Custom String encoder is not used
TYRUS-305 Add support for multiple client container to TestContainer
TYRUS-311 Session timeout on client does not work when set in onOpen method
TYRUS-312 TyrusFuture.get(long,TimeUnit) does not honor Future.get(long,TimeUnit) contract
TYRUS-293 Automatic heartbeat PING
TYRUS-259 Should produce a warning during deployment when OnMessage#maxMessageSize is larger than the value of org.glassfish.tyrus.servlet.incoming-buffer-size
TYRUS-308 JDK Client transport – SSL support
TYRUS-309 JDK Client transport – Proxy support
TYRUS-318 Writer returned from BasicRemote.getSendWriter() throws NPE when flush is called more than once.
TYRUS-314 Create WADL-like descriptor per deployed app
TYRUS-317 Allow server configuration using WebSocketContainer or WebSocketAddOn
TYRUS-302 Java 8 Lambda
TYRUS-214 Expose monitoring API/statistics
TYRUS-299 Missing tyrus-container-grizzly-server in the release package WebSocket RI archive
TYRUS-233 Provide client transport based on plain JDK
TYRUS-310 When max idle timeout is reset to 0, every received message or sent ping or pong causes an empty task to be scheduled and executed

Links

If you have any questions or comments or if you want to discuss anything related to Tyrus, easiest way to contact us is via users@tyrus.java.net mailing list.

Thursday Mar 06, 2014

Tyrus 1.5

New version of Tyrus was released today, so let’s do a quick summary of what is new compared to previous version and so on.

What’s new?

Maven archetype was (finally) added, so if you want to generate simple application, test it and maybe start playing with the code and modifying it to something more complex, you can. All you need is maven and little space on your hard drive. Then you can execute following command:

1
2
3
4
mvn archetype:generate -DarchetypeArtifactId=tyrus-archetype-echo \
-DarchetypeGroupId=org.glassfish.tyrus.archetypes -DinteractiveMode=false \
-DgroupId=com.example -DartifactId=echo -Dpackage=com.example \
-DarchetypeVersion=1.5

and the project should be created.

What’s fixed?

  • Close reason for dropped connection is now 1006, as it should be.
  • Standalone Server is not leaking daemon threads any more.
  • Java SE 8 runtime issues.
  • “Host” header parsing issue which caused failed handshakes when using IPv6 localhost address ([::1]).
  •  Internal InputStream implementation now correctly returns ‘-1′ to InputStream#read() only when end of input is reached. Thanks Raghu for contributing the fix!

Java SE 8 related issue is worth a short explanation, since the change introduced by this fix goes beyond SE 8. Original problem was about some bug in Java SE 7 which somehow corrected Tyrus behaviour, so it was not noticed by our tests – bridge methods are not returned from Class.getMethods() call there. This was fixed in Java SE 8 and it caused some troubles in Tyrus implementation, because we just did not expect them to be returned.

The other part of this issue was correcting Tyrus in terms of handling inherited methods. JSR 356 describes how these should be handled in little bit cryptic way, but it basically states that annotations are not inherited, which is already defined in Java language specification. Unfortunately, Tyrus prior this version was considering annotated methods (@OnOpen, @OnMessage, …) only from the very same class as the registered one, so inherited methods were always ignored. This is now changed, so you can have something like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class BaseEndpoint {
@OnOpen
public void onOpen(Session session) throws IOException {
// do something.
}
}
@ServerEndpoint("/echo")
public class EchoEndpoint extends BaseEndpoint {
@OnMessage
public void echo(Session session, String message) throws IOException {
// do something else.
}
}

both methods – onOpen and onMessage will be considered as part of EchoEndpoint class. Please note that annotations are still not inherited, so if you for example declare BaseEndpoint class as abstract with onOpen method annotated with @OnOpen, overriding method won’t be called by Tyrus unless you “re-add” @OnOpen annotation to new method:

1
2
3
4
5
6
7
8
9
10
11
12
13
public abstract class BaseEndpoint {
public abstract void onOpen(Session session) throws IOException;
}
@ServerEndpoint("/echo")
public class EchoEndpoint extends BaseEndpoint {
@OnOpen // this has to be here!
@Override
public void onOpen(Session session) throws IOException {
// do something.
}
}

Feel free to ask here or send us a note to users@tyrus.java.net if you have any questions related to this release or anything related to our WebSocket implementation.

Links

Wednesday Feb 12, 2014

Oracle Toplink team is hiring!

Oracle Toplink team is looking for Java Developers! If you want to became part of the team which is working on Toplink, EclipseLink, JAX-B, JAX-WS and related projects and products, this is the time to update your CV and share it with us. This job position is based in Prague, Czech Republic.

Formal job offer can be found on linked.in.

If you are interested and have any questions, feel free to contact me directly via this blog or via pavel.bucek at oracle.com.

Friday Jan 24, 2014

Updating Tyrus in Glassfish

This article is inspired by similar one about Jersey and will provide similar information. Thanks to Michal for creating such comprehensive instructions.

Fortunately, Tyrus does not depend on HK2 so the task here is lot easier. To be absolutely honest, I did expect some issues with Grizzly dependency in Tyrus client, but changes are backwards compatible (applies to Tyrus 1.4), so you can update Tyrus to any version of released Glassfish very easily.

Which version of Tyrus am I using?

You can get this from tyrus-core.jar manifest:

1
2
$ unzip -p $GLASSFISH_HOME/glassfish/modules/tyrus-core.jar META-INF/MANIFEST.MF | grep Bundle-Version
Bundle-Version: 1.0.0

This means you are using Tyrus 1.0. I strongly recommend to upgrade. Latest version now is Tyrus 1.4 and the output will look like:

1
2
$ unzip -p $GLASSFISH_HOME/glassfish/modules/tyrus-core.jar META-INF/MANIFEST.MF | grep Bundle-Version
Bundle-Version: 1.4.0

Glassfish distributions

Table below contains overview of current Glassfish 4.x builds:

Glassfish version Download link Tyrus version
4.0 (Java EE 7 RI) [download] 1.0
4.0.1 b01 [download] 1.0
4.0.1 b02 [download] 1.2.1
4.0.1 b03 [download] 1.2.1
4.0.1 latest nightly [download] latest

Updating to Tyrus 1.4

1
2
3
4
5
6
7
8
9
10
11
$ rm $GLASSFISH_HOME/glassfish/modules/tyrus-*jar
$ unzip -j ./websocket-ri-archive-1.4.zip "websocket-ri-archive-1.4/lib/*" -d $GLASSFISH_HOME/glassfish/modules/
Archive:  ./websocket-ri-archive-1.4.zip
inflating: [path]/modules/tyrus-client-1.4.jar
inflating: [path]/modules/tyrus-container-glassfish-cdi-1.4.jar
inflating: [path]/modules/tyrus-container-grizzly-client-1.4.jar
inflating: [path]/modules/tyrus-container-servlet-1.4.jar
inflating: [path]/modules/tyrus-core-1.4.jar
inflating: [path]/modules/tyrus-server-1.4.jar
inflating: [path]/modules/tyrus-spi-1.4.jar

And that’s it. Remember to restart Glassfish instance after replacing Tyrus jar files.

Note

As of now (1/24/2013) latest nightly build of Glassfish contains Tyrus 1.3.3. Next nightly should contain latest Tyrus release – version 1.4.

Links

Thursday Jan 16, 2014

Tyrus 1.4

I’m pleased to announce that Tyrus 1.4 was released today. It contains all features mentioned recently on my blog: Extensions support (including compression extensions implementation), shared client container and many other improvements and bug fixes. Binaries will be available on maven central repository soon and Tyrus 1.4 will be integrated into Glassfish trunk.

As always, feel free to contribute! You can file an enhancement or a bug or just ask if there is something unclear in the documentation.

Release notes

Bugs

  • [TYRUS-136] – org.glassfish.tyrus.server.Server does not stop correctly when DeploymentException is thrown
  • [TYRUS-263] – SSL via HTTP PROXY – wss://echo.websocket.org Handshake error. “Response code was not 101: 200″
  • [TYRUS-269] – Parallel connection to ServerEndpoint with URI template mix up response to client instances …
  • [TYRUS-270] – The title of file README.html of Draw sample is mis-typed from “Draw Sample” to “Chat Sample”
  • [TYRUS-271] – “?null” is added to every request without query params
  • [TYRUS-272] – TyrusServerConfiguration incompatible with jetty’s jsr 356 implementation.
  • [TYRUS-273] – EJB component provider needs to provide method which will be invoked.
  • [TYRUS-275] – Tyrus client has bug causing limitation of # of open web sockets
  • [TYRUS-276] – Session accepts messages after idle timeout (@OnMessage is triggered)
  • [TYRUS-277] – session.setMaxIdleTimeout(0) does not reset/cancel the timeout
  • [TYRUS-280] – Extension parsed does not allow parameter without value
  • [TYRUS-281] – Client does CDI/EJB lookup
  • [TYRUS-282] – Session.setMaxIdleTimeout(..) does not work as expected for negative values
  • [TYRUS-288] – WebSocket connections are automatically closed after 30 idle seconds in standalone mode

Improvements

  • [TYRUS-56] – content root directory configuration in Server class for static content
  • [TYRUS-160] – Tyrus tests won’t compile with JDK 1.6
  • [TYRUS-183] – UTF8 validation logic should be separated from frame parsing
  • [TYRUS-265] – Improve GrizzlyWriter implementation
  • [TYRUS-266] – Support for WebSocket extensions
  • [TYRUS-268] – In-memory transport for testing / performance analysis

New Features

  • [TYRUS-193] – Support Tyrus with different transports
  • [TYRUS-283] – CompressionExtension (permessage-compression)
  • [TYRUS-286] – Shared client container improvement (stop when there is no open session)
  • [TYRUS-287] – Cannot create non-daemon threads with Tyrus server standalone mode

Tasks

  • [TYRUS-246] – Investigate and fix issues related to ServletTest#testWebSocketBroadcast
  • [TYRUS-264] – Client SPI

Wednesday Jan 08, 2014

WebSocket Extensions in Tyrus

There is always room for another experimental feature :-) This one is maybe little less experimental than broadcast support, but please implement presented APIs with one important fact in your mind – it can change any time.

What is WebSocket Extension?

You can think of WebSocket Extension as a filter, which processes all incoming and outgoing frames. Frame is the smallest unit in WebSocket protocol which can be transferred on the wire – it contains some some metadata (frame type, opcode, payload length, etc.) and of course payload itself.

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|     Extended payload length continued, if payload len == 127  |
+ - - - - - - - - - - - - - - - +-------------------------------+
|                               |Masking-key, if MASK set to 1  |
+-------------------------------+-------------------------------+
| Masking-key (continued)       |          Payload Data         |
+-------------------------------- - - - - - - - - - - - - - - - +
:                     Payload Data continued ...                :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|                     Payload Data continued ...                |
+---------------------------------------------------------------+

Figure taken from RFC 6455

If you are interested about some more details related to WebSocket protocol specification, please see linked RFC document (RFC 6455).

What can be achieved by WebSocket Extension?

Almost everything. You can change every single bit of incoming or outgoing frame, including control frames (close, ping and pong). There are some RFC drafts trying to standardise extensions like per message compression (used to be per frame compression) and multiplexing extension (now expired).

Tyrus already has support for per message compression extension and exposes interfaces which allow users to write custom extensions with completely different functionality.

When should I consider implementing WebSocket Extensions?

This is maybe the most important question. WebSocket Extensions can do almost everything, but you should not use them for use cases achievable by other means. Why? Majority of WebSocket use cases are about communication with browsers and javascript client cannot really influence which exception is going to be used. Browser must support your particular extension (by default or it can be enabled by some custom module).

You can easily use custom extension when using Tyrus java client, so if there is no browser interaction in your application, it should be easier to distribute your extensions to involved parties and you might lift the threshold when deciding whether something will be done by extension or by application logic.

Java API for WebSocket and Extensions

API currently contains following extension representation (javadoc removed):

1
2
3
4
5
6
7
8
9
10
public interface Extension {
String getName();
List getParameters();
interface Parameter {
String getName();
String getValue();
}
}

and the specification (JSR 356) limits extension definition only for handshake purposes. To sum that up, users can only declare Extension with static parameters (no chance to set parameters based on request extension parameters) and that’s it. These extensions don’t have any processing part, so the work must be done somewhere else. As you might already suspect, this is not ideal state. Usability of extensions specified like this is very limited, it is basically just a marker class which has some influence on handshake headers. You can get list of negotiated extensions in the runtime (Session.getNegotiatedExtensions()) but there is no way how you could access frame fields other than payload itself.

Proposed Extension API

I have to repeat warning already presented at the start of this blog post – anything mentioned below might be changed without notice. There are some TODO items which will most likely require some modification of presented API, not to mention that RFC drafts of WebSocket Extensions are not final yet. There might be even bigger modification needed – for example, multiplexing draft specifies different frame representation, use of RSV bits is not standardised etc. So please take following as a usable proof of concept and feel free to use them in agile projects.

Firstly, we need to create frame representation.

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
32
public class Frame {
public boolean isFin() { .. }
public boolean isRsv1() { .. }
public boolean isRsv2() { .. }
public boolean isRsv3() { .. }
public boolean isMask() { .. }
public byte getOpcode() { .. }
public long getPayloadLength() { .. }
public int getMaskingKey() { .. }
public byte[] getPayloadData() { .. }
public boolean isControlFrame() { .. }
public static Builder builder() { .. }
public static Builder builder(Frame frame) { .. }
public final static class Builder {
public Builder() { .. }
public Builder(Frame frame) { .. }
public Frame build() { .. }
public Builder fin(boolean fin) { .. }
public Builder rsv1(boolean rsv1) { .. }
public Builder rsv2(boolean rsv2) { .. }
public Builder rsv3(boolean rsv3) { .. }
public Builder mask(boolean mask) { .. }
public Builder opcode(byte opcode) { .. }
public Builder payloadLength(long payloadLength) { .. }
public Builder maskingKey(int maskingKey) { .. }
public Builder payloadData(byte[] payloadData) { .. }
}
}

This is pretty much straightforward copy of frame definition mentioned earlier. Frame is designed as immutable, so you cannot change it in any way. One method might be recognised as mutable – getPayloadData() – returns modifiable byte array, but it is always new copy, so the original frame instance remains intact. There is also a Frame.Builder for constructing new Frame instances, notice it can copy existing frame, so creating a new frame with let’s say RSV1 bit set to “1″ is as easy as:

1
Frame newFrame = Frame.builder(originalFrame).rsv1(true).build();

Note that there is only one convenience method: isControlFrame. Other information about frame type etc needs to be evaluated directly from opcode, simply because there might not be enough information to get the correct outcome or the information itself would not be very useful. For example: opcode 0×00 means continuation frame, but you don’t have any chance to get the information about actual type (text or binary) without intercepting data from previous frames. Consider Frame class as as raw as possible representation.isControlFrame can be also gathered from opcode, but it is at least always deterministic and it will be used by most of extension implementations. It is not usual to modify control frames as it might end with half closed connections or unanswered ping messages.

New Extension representation needs to be able to handle extension parameter negotiation and actual processing of incoming and outgoing frames. It also should be compatible with existing javax.websocket.Extension class, since we wan’t to re-use existing registration API and be able to return new extension instance included in response fromSession.getNegotiatedExtensions():List<Extension> call. Consider following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface ExtendedExtension extends Extension {
Frame processIncoming(ExtensionContext context, Frame frame);
Frame processOutgoing(ExtensionContext context, Frame frame);
List onExtensionNegotiation(ExtensionContext context, List requestedParameters);
void onHandshakeResponse(ExtensionContext context, List responseParameters);
void destroy(ExtensionContext context);
interface ExtensionContext {
Map<String, Object> getProperties();
}
}

ExtendedExtension is capable of processing frames and influence parameter values during the handshake. Extension is used on both client and server side and since the negotiation is only place where this fact applies, we needed to somehow differentiate these sides. On server side, only onExtensionNegotiation(..) method is invoked and client side hasonHandshakeResponse(..). Server side method is a must, client side could be somehow solved by implementing ClientEndpointConfig.Configurator#afterResponse(..) or calling Session.getNegotiatedExtenions(), but it won’t be as easy to get this information back to extension instance and even if it was, it won’t be very elegant. Also, you might suggest replacing processIncoming and processOutgoing methods by just oneprocess(Frame) method. That is also possible, but then you might have to assume current direction from frame instance or somehow from ExtenionContext, which is generally not a bad idea, but it resulted it slightly less readable code.

Last but not least is ExtensionContext itself and related lifecycle method. OriginalExtension from javax.websocket is singleton and ExtendedExtension must obey this fact. But it does not meet some requirements we stated previously, like per connection parameter negotiation and of course processing itself will most likely have some connection state. Lifecycle of ExtensionContext is defined as follows: ExtensionContextinstance is created right before onExtensionNegotiation (server side) oronHandshakeResponse (client side) and destroyed after destroy method invocation. Obviously, processIncoming or processOutgoing cannot be called before ExtensionContextis created or after is destroyed. You can think of handshake related methods as @OnOpenand destroy as @OnClose.

For those more familiar with WebSocket protocol: process*(ExtensionContext, Frame) is always invoked with unmasked frame, you don’t need to care about it. On the other side, payload is as it was received from the wire, before any validation (UTF-8 check for text messages). This fact is particularly important when you are modifying text message content, you need to make sure it is properly encoded in relation to other messages, because encoding/decoding process is stateful – remainder after UTF-8 coding is used as input to coding process for next message. If you want just test this feature and save yourself some headaches, don’t modify text message content or try binary messages instead.

Code sample

Let’s say we want to create extension which will encrypt and decrypt first byte of every binary message. Assume we have a key (one byte) and our symmetrical cipher will be XOR. (Just for simplicity (a XOR key XOR key) = a, so encrypt() and decrypt() functions are the same).

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class CryptoExtension implements ExtendedExtension {
@Override
public Frame processIncoming(ExtensionContext context, Frame frame) {
return lameCrypt(context, frame);
}
@Override
public Frame processOutgoing(ExtensionContext context, Frame frame) {
return lameCrypt(context, frame);
}
private Frame lameCrypt(ExtensionContext context, Frame frame) {
if(!frame.isControlFrame() && (frame.getOpcode() == 0x02)) {
final byte[] payloadData = frame.getPayloadData();
payloadData[0] ^= (Byte)(context.getProperties().get("key"));
return Frame.builder(frame).payloadData(payloadData).build();
} else {
return frame;
}
}
@Override
public List onExtensionNegotiation(ExtensionContext context,
List requestedParameters) {
init(context);
// no params.
return null;
}
@Override
public void onHandshakeResponse(ExtensionContext context,
List responseParameters) {
init(context);
}
private void init(ExtensionContext context) {
context.getProperties().put("key", (byte)0x55);
}
@Override
public void destroy(ExtensionContext context) {
context.getProperties().clear();
}
@Override
public String getName() {
return "lame-crypto-extension";
}
@Override
public List getParameters() {
// no params.
return null;
}
}

You can see that ExtendedExtension is slightly more complicated that original Extension so the implementation has to be also not as straightforward.. on the other hand, it does something. Sample code above shows possible simplification mentioned earlier (one process method will be enough), but please take this as just sample implementation. Real world case is usually more complicated.

Now when we have our CryptoExtension implemented, we want to use it. There is nothing new compared to standard WebSocket Java API, feel free to skip this part if you are already familiar with it. Only programmatic version will be demonstrated. It is possible to do it for annotated version as well, but it is little bit more complicated on the server side and I want to keep the code as compact as possible.

Client registration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ArrayList extensions = new ArrayList();
extensions.add(new CryptoExtension());
final ClientEndpointConfig clientConfiguration =
ClientEndpointConfig.Builder.create()
.extensions(extensions).build();
WebSocketContainer client = ContainerProvider.getWebSocketContainer();
final Session session = client.connectToServer(new Endpoint() {
@Override
public void onOpen(Session session, EndpointConfig config) {
// ...
}
}, clientConfiguration, URI.create(/* ... */));

Server registration:

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
public class CryptoExtensionApplicationConfig implements ServerApplicationConfig {
@Override
public Set getEndpointConfigs(Set<Class<? extends Endpoint>> endpointClasses) {
Set endpointConfigs = new HashSet();
endpointConfigs.add(
ServerEndpointConfig.Builder.create(EchoEndpoint.class, "/echo")
.extensions(Arrays.asList(new CryptoExtension())).build()
);
return endpointConfigs;
}
@Override
public Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> scanned) {
// all scanned endpoints will be used.
return scanned;
}
}
public class EchoEndpoint extends Endpoint {
@Override
public void onOpen(Session session, EndpointConfig config) {
// ...
}
}

CryptoExtensionApplicationConfig will be found by servlets scanning mechanism and automatically used for application configuration, no need to add anything (or even have)web.xml.

Per Message Deflate Extension

The original goal of whole extension support was to implement Permessage extension as defined in draft-ietf-hybi-permessage-compression-15 and we were able to achieve that goal. Well, not completely, current implementation ignores parameters. But it seems like it does not matter much, it was tested with Chrome and it works fine. Also it passes newest version of Autobahn test suite, which includes tests for this extension.

  1. PerMessageDeflateExtension.java (compatible with draft-ietf-hybi-permessage-compression-15, autobahn test suite)
  2. XWebKitDeflateExtension.java (compatible with Chrome and Firefox – same as previous, just different extension name)
  3. PerMessageDeflateTest.java

TODO

There are some things which needs to be improved or specified to make this reliable and suitable for real world use. It might not seem as big feature, but it enables lots of use cases not defined in original specification and some of them are clashing little bit, so for now, I kept everything as it was when you are not using extended extensions.

Everything mentioned in this article is already available in Tyrus 1.4-SNAPSHOT and will be part of 1.4 release.

  • Extension / Frame Validation
  • Frame representation – frame types
  • Frame representation – payload representation
  • Frame representation – masking – remove? (current state: container responsibility)
  • Exception handling – processIncoming and processOutgoing methods (current state: exceptions are logged)
  • Exception handling – onExtensionNegotiation
  • Possibility to reject negotiation extension in onExtensionNegotiation (based on extension params)
  • Extension ordering (current state: handshake response header order)
  • Extension resource validation (two extensions using same RSV bit(s) cannot be negotiated)
  • PerMessageDeflate – parameters
  • MultiplexingExtension – implement when (if) ready

Conclusion

There is still lots of decisions to be made and things to do, but it seems like we can implement usable extensions which are supported by newer versions of browsers and containers. PerMessageDeflate extension is nice example of handy feature which can save significant resources.

Links

Wednesday Dec 18, 2013

Want to work on Tyrus? We are hiring!

Tyrus project is looking for another contributor! If you want to became part of the team which is working on WebSocket API for Java (JSR 356) Reference implementation and related projects and products, this is the time to update your CV and share it with us. This job position is based in Prague, Czech Republic.

Formal job offer can be found on linked in.

If you are interested and have any questions, feel free to contact me directly via this blog or via pavel.bucek at oracle.com.

Friday Dec 13, 2013

Tyrus container SPI

This blog post will be little more technical and insightful into Tyrus internals and won’t be as useful for common users which are just trying to use it for creating their web socket endpoint. I will describe how you can run Tyrus client or server side on virtually any container with a little help. We will start with Client side – it is a little bit simpler I think – then go through Server side and at the end I will present simple in-memory container which is already present in Tyrus workspace and is part of latest release (1.3.3).

Let me start with little background. Tyrus originally started as something built on top of project Grizzly, which serves as NIO layer in Glassfish. Grizzly had (still has) its own API which supports creating web socket endpoints and does even have client implementation. Then JSR 356 was started, Tyrus (originally called “websocket-sdk“) was created and it was just a simple adaptation layer on top of Grizzly container. Then it was decided that this JSR will  be part of Java EE 7. This brought much more visibility to this project and also some more requirements. Tyrus was required to run on any Java EE 7 compliant container, utilising Servlet 3.1 API (upgrade mechanism, non-blocking reads and writes). So another layer was created, this time slightly more low level, because Tyrus was required to handle reading and writing, something which was previously handled by Grizzly. The decision was made, Tyrus adopted some Grizzly code and the Container SPI started to form. Tyrus still kept Grizzly container support and it was able to work with Servet 3.1 API as well. (Just for the sake of completeness – this is about server side. Client part always exclusively used Grizzly as container).

Then another requirement came – it was decided that Tyrus will be integrated into WebLogic server (it will be part of upcoming 12.1.3 release). Initial integration used lots of Tyrus internals which was kind of scary, because future integrations might require significant amount of time, especially when considering some not trivial features we plan for Tyrus (Extension support, subprotocols, etc). WebLogic 12.1.3 does not contain Grizzly nor Servlet 3.1 implementation, so we needed to have something which will be more stable than any other Tyrus internal class.

Small note – this is final version of the SPI for now, it does not mean that it won’t change. We might need to extend it to support some new features or fix bugs. Also there is no discovery mechanism in place (yet), so implementors of these SPI will have direct dependency on some Tyrus internal class(es). These internal classes (TyrusWebSocketEngine, etc.) may be changed, but there is higher resistance mode for these, so it shouldn’t be done that often and if we can achieve backwards compatibility, we will. Discovery mechanism will most likely be introduced sometime later..

Enough with this history lesson, let’s get to the code part.

Client SPI

Client side container is basically only about implementing ClientContainer. Method openClientSocket will be called when ClientManager needs to connect to server endpoint and following sequence of calls is then expected:

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