Author: admin

  • HTTP/2.0 Expressions of interest

    The IETF HTTPbis Working Group recently called for expressions of interest in the development of the HTTP/2.0 protocol, with SPDY being one of the candidates to use as a basis.

    As a HTTP server and an early implementer of the SPDY protocol, the Jetty project certainly has an interest in HTTP/2.0 and this blog contains the text of our response below.  However it is also very interest to have a read all of the expressions of interest received from industry heavy hitters like:

    Reading through these and the thousands of replies, it is clear that there is significant interest and some momentum towards replacing HTTP/1.1, but that the solution is not quite as simple as s/SPDY/HTTP/2.0/.

    There is a lot of heat around the suggestion of mandatory encryption (even though no proposal actually has mandatory encryption), and it looks like there is a big divide in the community.

    I also think that many of the concerns of the intermediaries (F5, haproxy, squid) are not being well addressed.  This is a mistake often made in previous protocol iterations and we would be well served by taking the time to listen and understand their concerns.  Even simple features such as easy access to host headers for quick routing may have significant benefits.

    The Jetty Expression of Interest in HTTP/2.0

    (see also the original post and responses)

    I’m the project leader of the Jetty project (http://eclipse.org/jetty) and am making this initial response on behalf of the project and not for eclipse as a whole (although we will solicit further feedback from other projects within eclipse). I work for Webtide|Intalio who sell support services around Jetty.

    Jetty is an open source server and client written in java that supports the 3.0 Servlet API, HTTP/1.1, Websocket 1.0 and SPDY v3. We have a reasonable market share of java servers (>10% < 30%) and are deployed on everything from tiny embedded servers to very large deployments with over 100k connections per server.

    The Jetty project is very interested in the development and standardisation of HTTP/2.0 and intend to be contributors to the WG and early implementers. We are well acquainted with the limitations of HTTP/1.1 and have a desire to see the problems of pipelining and multiple connections (>2) resolved.

    The Jetty project SPDY effort is lead by Simone Bordet and it has implemented SPDY v3 with flow control and push. This is available in the main releases of our Jetty-7 and jetty-8 servers (we also have a java SPDY client). The project has also provided an extension to the JVM to implement the TLS NPN extension needed by SPDY, and we understand that several other java SPDY implementations are using this.

    We chose SPDY to implement rather than any other HTTP/2.0 proposal mainly because of the support available in deployed browsers, so that we can achieve real world feedback. However, we were also encouraged in our adoption of SPDY by the open, methodical and congenial approach of the SPDY project at Google (not always our experience with projects at Google or elsewhere).

    We definitely see the potential of SPDY and it is already being used by some sites. However we still lack the feedback from widespread deployment (it is early days) or from large deployments. We are actively seeking significant sites who are interested in working with us to deploy SPDY.

    There are several key features of SPDY that we see as promising:

    Header compression greatly improves data density. In our use of Ajax and Comet over HTTP/1.1 we have often hit scalability limits due to network saturation with very poor data density of small massages in large HTTP framing. While websocket is doing a lot to resolve this, we are hoping that SPDY will provide improvement without the need to redevelop applications.

    Multiplexing of multiple streams over a single connection is also a good development. Reducing the number of connections that the server must handle is key to scalability, specially as modern HTTP browsers are now exceeding the 2 connection limit. The ability to send out of order responses is good and we also suspect that receiving messages from a single client over a single connection may help reduce some of the non deterministic behaviours that can develop as multiple connections from the same client set cookies or update session state. It will also avoid the issue of load balancers directing connections from the same client to different nodes in a cluster. We recognise the additional cost of multiplexing (extra copies and flow control), but currently believe that it is worth the effort.

    We see the potential of server push for content, but are struggling with the lack of meta data knowledge available to know what to push and when. We are currently working on strategies that use the referrer header to identify associated resources that can be pushed together. We also check for if-modified-since headers as an indication that associated content may already be cached and thus a push is not required. We see the challenge of push as not being the protocol to send the content, but in working out the standards for meta data, cache control etc so that we know what to push and when.

    We have not yet implemented websocket over SPDY, but do intend to do so if it is supported by the browsers. We see a lot of similarities in the base framing of these two protocols and would hope that eventually only one would need to be well supported.

    We are bit ambivalent about the use of NPN and TLS only connections. There is a question to be asked regarding if we should be sending any web content in the clear, and how intermediaries should be able to (or not) filter/inspect/mutate content. However, I personally feel that this is essentially a non technical issue and we should not use a protocol to push any particular agenda. The RTT argument for not supporting in the clear connections is weak as there are several easy technical solutions available. Furthermore, the lack of support for NPN is a barrier to adoption (albeit one that we have broken down for some JVMs at least). Debugging over TLS is and will always be difficult. We would like to HTTP/2.0 support standardised non encrypted connections (at least from TLS offload to server). If a higher level debate determines that web deployments only accept TLS connections, then we are fine with that non technical determination.

    I repeat that we selected SPDY to implement because it’s availability in the browsers and not as the result of a technical review against other alternatives. However we are generally pleased with the direction and results obtained so far and look forward to gaining more experience and feedback as it is more widely deployed.

    However we do recognise that much of the “goodness” of SPDY can be provided by the other proposals. I’m particularly interested in the HTTP/speed/mobility’s use of websockets as it’s framing layer (as that addressed the concern I raised above). But we currently do not have any plans to implement the alternatives, mainly because of resource limitations and lack of browser support. So currently we are advocates of the SPDY approach in the starship troopers sense: ie we support SPDY until it is dead or we find something better. Of course Jetty is an open platform and we would really welcome and assist any contributors who would like to build on our websocket support to implement HTTP/SM.

    We believe that there is high demand for a significant improvement over HTTP/1.1 and that the environment is ripe for a rapid rollout of an alternative/improved protocol and expect that HTTP/1.1 can quickly be replaced. Because of this, we have begun development of jetty-9 which replaces the HTTP protocol centric architecture of jetty-7/8 with something that is much better suited to multiple protocols and multiplexed HTTP semantics. SPDY, Websocket and HTTP/1.1 are true peers in Jetty-9 rather than the newer protocols being implemented as HTTP facades. We believe that jetty-9 will be the ideal platform on which to develop and deploy HTTP/2.0 and we invite anybody with an interest to come contribute to the project.

  • JMiniX JMX console in Jetty

    Jetty has long had a rich set of JMX mbeans that give very detailed status, configuration and control over the server and applications, which can now simply be accessed with the JMiniX web console:

    The usability of JMX has been somewhat let down due to a lack of quality JMX management consoles.  JConsole and JVirtualVM do give good access to MBeans, but they rely on a RMI connection which can be tricky to setup to a remote machine.   JMiniX avoids the RMI by allowing access to the MBeans via a servlet you can add to your webapplication.

    The instructions were straight forward to follow and the steps were simply:

    1. Add dependency to your pom
    2. Add a repository to your pom (bummer – needs restlet.org which is not in maven central – if it was I’d consider adding JMiniX to our released test webapp)
    3. Define the servlet in your web.xml
    4. Build and run!

    You can see by the screen shot above that the console gives a nice rendering of the available mbeans from the JVM and Jetty (and cometd if running). Attributes can be viewed and updated, and operations can be called – all the normal stuff.   It only gives direct mbean access and does not provide any higher level management functions, but this is not a big problem if the mbeans are well designed and self documented.

    Also if you wanted to develop more advanced management functions, then the restful nature of JMiniX should make this fairly straight forward.  For example attributes can be retrieved with simple requests like:

    http://localhost:8080/jmx/servers/0/domains/org.eclipse.jetty.server/
    mbeans/type=server,id=0/attributes/startupTime/

    That returns JSON like:

    {"value":"1339059648877","label":"startupTime"}

    JMiniX looks like a great tool to improve the management of your servers and applications and to leverage the value already built into the Jetty JMX mbeans.

    We had been working on a similar effort for restful access to JMX, but JMiniX is more advanced.  It does lack some of the features that we had been working on like aggregate access to repeated attributes, but considering the state of JMiniX, we may consider contributing those features to that project instead.

  • Truth in Benchmarking!

    One of my pet peeves is misleading benchmarks, as discussed in my Lies, Damned Lies and Benchmarks blog.  Recently there has been a bit of interest in Vert.x, some of it resulting from apparently good benchmark results against node.js. The author gave a disclaimer that the tests were non-rigorous and just for fun, but they have already lead some people to ask if Jetty can scale like Vert.x.

    I know absolutely nothing about Vert.x, but I do know that their benchmark is next to useless to demonstrate any kind of scalability of a server.  So I’d like to analyse their benchmarks and compare them to how we benchmark jetty/cometd to try to give some understanding about how benchmarks should be designed and interpreted.

    The benchmark

    The vert.x benchmark uses 6 clients, each with 10 connections, each with up to 2000 pipelines HTTP requests for a trivial 200 OK or tiny static file. The tests were run for a minute and the average request rate was taken. So lets break this down:

    6 Clients of 10 connections!

    However you look at this (6 users each with a browser with 10 connections, or 60 individual users), 6 or 60 users does not represent any significant scalability.  We benchmark jetty/comet with 10,000 to 200,000 connections and have production sites that run with similar numbers.

    Testing 60 connections does not tell you anything about scalability. So why do so many benchmarks get performed on low numbers of connections?  It’s because it is really really hard to generate realistic load for hundreds of thousands of connections.  To do so, we use the jetty asynchronous HTTP client, which has been designed specifically for this purpose, and we still need to use multiple load generating machines to achieve high numbers of connections.

    2000 pipelined requests!

    Really? HTTP pipelining is not turned on by default in most web browsers, and even if it was, I cannot think of any realistic application that would be generate 2000 requests in a pipeline. Why is this important?  Because with pipelined requests a server that does:

    byte[] buffer = new byte[8192];
    socket.getInputStream().read(buffer);

    will read many requests into that buffer in a single read.  A trivial HTTP request is a few 10s of bytes (and I’m guessing they didn’t send any of the verbose complex headers that real browsers do), so the vert.x benchmark would be reading 30 or more requests on each read.  Thus this benchmark is not really testing any IO performance, but simply how fast they can iterate over a buffer and parse simple requests. At best it is telling you about the latency in their parsing and request handling.

    Handling reads is not the hard part of scaling IO.  It is handling the idle pauses between the reads that is difficult.  It is these idle periods that almost all real load profiles have that requires the server to carefully allocate resources so that idle connections do not consume resources that could be better used by non idle connections.    2000 connections each with 6 pipelined requests would be more realistic, or better yet 20000 connections with 6 requests that are sent with 10ms delays between them.

    Trivial 200 OK or Tiny static resource

    Creating a scalable server for non trivial applications is all about trying to ensure that maximal resources are applied to performing real business logic in preparing dynamic responses.   If all the responses are trivial or static, then the server is free to be more wasteful.  Worse still for realistic benchmarks, a trivial response generation can probably be in-lined by the hotspot compiler is a way that no real application ever could be.

    Run for a minute

    A minute is insufficient time for a JVM to achieve steady state.  For the first few minutes of a run the Hotspot JIT compiler will be using CPU to analyse and compile code. A trivial application might be able to be hotspot compiled in a minute, but any reasonably complex server/application is going to take much longer.  Try watching your application with jvisualvm and watch the perm generation continue to grow for many minutes while more and more classes are compiled. Only after the JVM has warmed up your application and CPU is no longer being used to compile, can any meaningful results be obtained.

    The other big killer of performance are full garbage collections that can stop the entire VM for many seconds.  Running fast for 60 seconds does not do you much good if a second later you pause for 10s while collecting the garbage from those fast 60 seconds.

    Benchmark result need to be reported for steady state over longer periods of time and you need to consider GC performance.  The jetty/cometd benchmark tools specifically measures and reports both JIT and GC actions during the benchmark runs and we can perform many benchmark runs in the same JVM.  Below is example output showing that for a 30s run some JIT was still performed, so the VM is not fully warmed up yet:

    Statistics Started at Mon Jun 21 15:50:58 UTC 2010
    Operative System: Linux 2.6.32-305-ec2 amd64
    JVM : Sun Microsystems Inc. Java HotSpot(TM) 64-Bit Server
    VM runtime 16.3-b01 1.6.0_20-b02
    Processors: 2
    System Memory: 93.82409% used of 7.5002174 GiB
    Used Heap Size: 2453.7236 MiB
    Max Heap Size: 5895.0 MiB
    Young Generation Heap Size: 2823.0 MiB
    - - - - - - - - - - - - - - - - - - - -
    Testing 2500 clients in 100 rooms
    Sending 3000 batches of 1x50B messages every 8000µs
    - - - - - - - - - - - - - - - - - - - -
    Statistics Ended at Mon Jun 21 15:51:29 UTC 2010
    Elapsed time: 30164 ms
            Time in JIT compilation: 12 ms
            Time in Young Generation GC: 0 ms (0 collections)
            Time in Old Generation GC: 0 ms (0 collections)
    Garbage Generated in Young Generation: 1848.7974 MiB
    Garbage Generated in Survivor Generation: 0.0 MiB
    Garbage Generated in Old Generation: 0.0 MiB
    Average CPU Load: 109.96191/200

    Conclusion

    I’m sure the vert.x guys had every good intent when doing their micro-benchmark, and it may well be that vert.x scales really well.  However I wish that when developers consider benchmarking servers, that instead of thinking: “let’s send a lot of requests at it”, that their first thought was “let’s open a lot of connections at it”.  Better yet, a benchmark (micro or otherwise) should be modelled on some real application and the load that it might generate.

    The jetty/cometd benchmark is of a real chat application, that really works and has real features like member lists, private messages etc.  Thus the results that we achieve in benchmarks are able to be reproduced by real applications in production.

     
     
     
     
     

  • Jetty-SPDY is joining the revolution!

    There is a revolution quietly happening on the web and if you blink you might miss it. The revolution is in the speed and latency with which some browsers can load some web pages, and what used to take 100’s of ms is now often reduced to 10’s.  The revolution is Google’s  SPDY protocol which I predict will soon replace HTTP as the primary protocol of the web, and  Jetty-SPDY is joining this revolution.

    SPDY is a fundamental rethink of how HTTP is transported over the internet, based on careful analysis of the interaction between TCP/IP, Browsers and web page design .  It does not entirely replace HTTP (it still uses HTTP GET’s and POST’s), but makes HTTP semantics available over a much more efficient wire protocol. It also opens up the possibility of new semantics that can be used on the web (eg server push/hint).  Improved latency, throughput and efficiency will improve user experience and facilitate better and cheaper services in environments like the mobile web.

    When is the revolution?

    So when is SPDY going to be available?  It already is!!! The SPDY protocol is deployed in the current Chrome browsers and on the Amazon Kindle, and it is optionally supported by firefox 11.  Thus it is already on 25% of clients and will soon be over 50%. On the server side, Google supports SPDY on all their primary services and Twitter switched on SPDY support this month.  As the webs most popular browsers and servers are talking SPDY, this is a significant shift in the way data is moved on the web.   Since Jetty 7.6.2/8.1.2, SPDY is supported in  Jetty and you can start using it without any changes to your web application!

    Is it a revolution or a coup?

    By deploying SPDY on it’s popular browser and web services, Google has used it’s market share to make a fundamental shift in the web (but not as we know it)!  and there are some rumblings that this may be an abuse of Google’s market power.  I’ve not been shy in the past of pointing out google’s failings to engage with the community in good faith, but in this case I think they have done an excellent job.  The SPDY protocol has been an open project for over two years and they have published specs and actively solicited feedback and participation.  More over, they are intending to take the protocol to the IETF for standardisation and have already submitted a draft to the httpbis working group.   Openly developing the protocol to the point of wide deployment is a good fit with the IETF’s approach of “rough consensus and working code“.

    Note also that Google are not tying any functionality to SPDY, so it is not as if they are saying that we must use their new protocol or else we can’t access their services.  We are free to disable or block SPDY on our own networks and the browsers will happily fallback to normal HTTP.  Currently SPDY is a totally transparent upgrade to the user.

    Is there a problem?

    So why would anybody be upset about Google making the web run faster?  One of the most significant changes in the SPDY protocol, is that all traffic is encrypted with TLS. For most users, this can be considered a significant security enhancement, as they will no longer need to consider if a page/form is secure enough for the transaction they are conducting.

    However, if you are the administrator of a firewall that is enforcing some kind of content filtering policy, then having all traffic be opaque to your filters will make it impossible to check content (which may be great if you are a dissident in a rogue state, but not so great if you are responsible for a primary school network).  Similarly, caching proxies will no longer be able to cache shareable content as it will also be opaque to them, which may reduce some of the latency/throughput benefits of SPDY.

    Mike Belshe, who has lead the development of SPDY, points out that SPDY does not prevent proxies, it just prevents implicit (aka transparent) proxies.  Since SPDY traffic is encrypted, the browser and any intermediaries must negotiate a session to pass TLS traffic, so the browser will need to give it’s consent before a proxy can see or modify any content.  This is probably workable for the primary school use-case, but no so much for the rouge state.

    Policy or Necessity?

    There is nothing intrinsic about the SPDY protocol that requires TLS, and there are versions of it that operate in the clear.  I believe it was a policy rather than a technical decision to required TLS only. There are some technical justification by the argument that it reduces round trips needed to negotiate a SPDY and/or HTTP connection,  but I don’t see that encryption is the only answer to those problems.  Thus I suspect that there is also a little bit of an agenda in the decision and it will probably be the most contentious aspect of SPDY going forward.  It will be interesting to see if the TLS-only policy survives the IETF process, but then I might be hard to argue for a policy change that benefits rogue states and less personal privacy.

    Other than rouge states, another victim of the TLS-only policy is eas of debugging, as highlighted by Mike’s blog, where he is having trouble working out how the kindle uses SPDY because all the traffic is encrypted.  As a developer/debugger of a HTTP server, I cannot over stress how important it is to be able to see a TCP dump of a problematic session.  This argument is one of the reasons why the IETF has historically favoured clear text protocols.  It remains to be seen if this argument will continue to prevail or if we will have to rely on better tools and browser/servers coughing up TLS sessions keys in order to debug?

    In Summary

    Google and the other contributors to the SPDY project have done great work to develop a protocol that promises to take the web a significant step forward and to open up the prospects for many new semantics and developments.  While they have done this some what unilaterally, it has been done openly and with out any evidence of any intent other than to improve user experience/privacy and to reduce server costs.

    SPDY is a great development for the web and the Jetty team is please to be a part of it.

  • Jetty WebSocket Client API updated

    With the release of Jetty 7.5.0 and the latest draft 13 of the WebSocket protocol, the API for the client has be re-factored a little since my last blog on WebSocket: Server, Client and Load Test.

    WebSocketClientFactory

    When creating many instances of the java WebSocketClient, there is much that can be shared between multiple instances: buffer pools, thread pools and NIO selectors.  Thus the client API has been updated to use a factory pattern, where the factory can hold the configuration and instances of the common infrastructure:

    WebSocketClientFactory factory = new WebSocketClientFactory();
    factory.setBufferSize(4096);
    factory.start();

    WebSocketClient

    Once the WebSocketClientFactory is started, WebSocketClient instances can be created and configured:

    WebSocketClient client = factory.newWebSocketClient();
    client.setMaxIdleTime(30000);
    client.setMaxTextMessageSize(1024);
    client.setProtocol("chat");

    The WebSocketClient does not need to be started and the configuration set is copied to the connection instances as they are opened.

    WebSocketClient.open(…)

    A websocket connection can be created from a WebSocketClient by calling open and passing the URI and the websocket instance that will handle the call backs (eg onOpen, onMessage etc.):

    Future future = client.open(uri,mywebsocket);
    WebSocket.Connection connection = future.get(10,TimeUnit.SECONDS);

    The open call returns a Future to the WebSocket.Connection.  Like the NIO.2 API in JDK7, calling get with a timeout imposes a connect time on the connection attempt and the connection will be aborted if the get times out.   If the connection is successful, the connection returned by the get is the same object passed to the WebSocket.onOpen(Connection) callback, so it may be access and used in either way.

    WebSocket.Connection

    The connection instance accessed via the onOpen callback or Future.get() is used to send messages and also to configure the connection:

    connection.setMaxIdleTime(10000);
    connection.setMaxTextMessageSize(2*1024);
    connection.setMaxBinaryMessageSize(64*1024);

    The  maximum message sizes are used to control how large messages can grow when they are being aggregated from multiple websocket frames.  Small max message sizes protect a server against DOS attack.

  • NoSql Sessions with Jetty7 and Jetty8

    When Jetty 7.5.0 is released we will have officially started to dabble in the area of distributed session handling and storage. To start this out we have created a set of abstract classes around the general concept of NoSQL support, and have prepared an initial implementation using MongoDB. We will also be working on Ehcache and perhaps Cassandra implementations over time to round out the offering, but it is overall a pretty exciting time for these sorts of things.

    NoSQL sessions are a good idea for a number of usage scenarios, but as with NoSQL solutions in general, it is not a one-size-fits-all technology. The Jetty NoSQL session implementation should be good for scenarios that require decentralization, highly parallel work loads, and scalability, while also supporting session migration from one machine to the next for load balancing purposes. While we are initially releasing with just the MongoDB session manager, it is important to make clear that all the different distributed NoSQLish solutions out there have there own positives and negatives that you need to balance when choosing a storage medium. This is an interesting and diverse area of development, and since there is little standardization at the moment it is not a simple matter of exporting data from one system to the next if you want to change back ends.

    Before jumping in and embracing this solution for your session management, ask yourself some questions:

    • Do I require a lot of write behavior on my session objects?

    When you’re dealing with anything that touches the network to perform an action, you have an entirely different set of issues than if you can keep all your logic on one machine.  The hash session manager is the fastest solution for this use profile, but the JDBC session manager is not a bad solution if you need to operate with the network.  That in mind, there is an optimization in the NoSQL session managers where tight write loops should queue up a bit before an actual write to the back end MongoDB server occurs.  In general, if you have a session profile that involves a lot of writes all the time, you might want to shy away from this approach.

    • Am I bouncing sessions across lots of machines all the time?

    If you are, then you might be better off to get rid of sessions entirely and be more RESTful, but a networked session manager is going to be difficult to scale to this approach and be consistent.  By consistent I mean writing data into your session on one node and having that same data present within a session on another node.  If you’re looking at using MongoDB to increase the number of sessions you’re able to support, it is vitally important to remember that the network is not an inexhaustable resource, and keeping sessions localized is good practice, especially if you want consistent behavior. But if you want non-sticky sessions or mostly sticky sessions that can scale, this sort of NoSQL session manager is certainly an option, especially for lightweight, mostly read sessions.

    • Do I want to scale to crazy amounts of sessions that are relatively small and largely contain write-once read-often data?

    Great! Use this!  You are the people we had in mind when we developed the distributed session handling.

    On the topic of configuring the new session managers, it is much like other traditional ones: add them to the context.xml or set up with the regular jetty.xml route. There are, however, a couple of important options to keep in mind for the session ID manager.

    • scavengeDelay–How often will a scavenge operation occur looking for sessions to invalidate?
    • scavengePeriod–How much time after a scavenge has completed should you wait before doing it again?
    • purge (Boolean)–Do you want to purge (delete) sessions that are invalid from the session store completely?
    • purgeDelay–How often do you want to perform this purge operation?
    • purgeInvalidAge–How old should an invalid session be before it is eligible to be purged?
    • purgeValidAge–How old should a valid session be before it is eligible to be marked invalid and purged? Should this occur at all?

    A guide for detailed configuration can be found on our wiki at on the Session Clustering with MongoDB page.

    The new MongoDB session manager and session ID manager are located in the jetty-nosql module.  Since we plan to have multiple offerings we have made the mongodb dependency optional, so if you’re planning to use embedded Jetty, make sure you declare a hard dependency in Maven. You can also download the mongodb jar file and place it into a lib/mongodb directory within the jetty distribution itself; then you must add mongodb to the OPTIONS  on the cli or in the start.ini file you’re starting Jetty with.

    There were a number of different ways to go in implementing session ID management. While we are wholly tolerant of a user request being moved from one server to another, we chose to keep normal session operations localized to the machine where the session originates.  If the request bounces from one machine to another, the latest known session is loaded. If it is saved and then bounces back, Jetty notices the change in the version of the session and reloads, but these operations are heavy weight: they require pulling back all data of a session across the network, as opposed to a field or two of MongoDB goodness.  One side effect of this approach is the scavenge operation executes only on the known session IDs of a given node. In this scenario, if your happy cluster of Jetty instances has a problem and one of them crashes (not our fault!), there is potential for previously valid session IDs to remain in your MongoDB session store, never to be seen again, but also never cleaned up. That is where purge comes in: the purge process can perform a passive sweep through the MongoDB cluster to delete really old, valid sessions.  You can also delete the invalid sessions that are over a week old, or a month old, or whatever you like. If you have hoarding instincts, you can turn purge off (it’s true by default), and your MongoDB cluster will grow… and grow.

    We have also added some additional JMX support to the MongoDB session manager. When you enable JMX, you can access all the normal session statistics, but you also have the option to force execution of the purge and scavenge operations on a single node, or purge fully, which executes the purge logic for everything in the MongoDB store.  In this mode you can disable purge on your nodes and schedule the actions for when you are comfortable they will not cause issues on the network.  For tips on configuring JMX support for jetty see our tutorial on JMX.

    Lastly I’ll just mention that MongoDB is really a treat to work with. I love how easy it is to print the data being returned from MongoDB, and it’s in happy JSON.  It has a rich query language that allowed us to easily craft queries for the exact information we were looking for, reducing the footprint on the network the session work imposes.

     

  • Websocket Example: Server, Client and LoadTest

    The websocket protocol specification is approaching final and the Jetty implementation and API have been tracking the draft and is ready when the spec and browsers are available.   More over, Jetty release 7.5.0 now includes a capable websocket java client that can be used for non browser applications or load testing. It is fully asynchronous and can create thousands of connections simultaneously.

    This blog uses the classic chat example to introduce a websocket server, client and load test.

    The project

    The websocket example has been created as a maven project with groupid com.example.  The entire project can be downloaded from here.   The pom.xml defines a dependency on org.eclipse.jetty:jetty-websocket-7.5.0.RC1 (you should update to 7.5.0 when the final release is available), which provides the websocket API and transitively the jetty implementation.  There is also a dependency on org.eclipse.jetty:jetty-servlet which provides the ability to create an embedded servlet container to run the server example.

    While the project implements a Servlet, it is not in a typical webapp layout, as I wanted to provide both client and server in the same project.    Instead of a webapp, this project uses embedded jetty in a simple Main class to provide the server and the static content is served from the classpath from src/resources/com/example/docroot.

    Typically developers will want to build a war file containing a webapp, but I leave it as an exercise for the reader to put the servlet and static content described here into a webapp format.

    The Servlet

    The Websocket connection starts with a HTTP handshake.  Thus the websocket API in jetty also initiated by the handling of a HTTP request (typically) by a Servlet.  The advantage of this approach is that it means that websocket connections are terminated in the same rich application space provided by HTTP servers, thus a websocket enabled web application can be developed in a single environment rather than by collaboration between a HTTP server and a separate websocket server.

    We create the ChatServlet with an init() method that instantiates and configures a WebSocketFactory instance:

    public class ChatServlet extends HttpServlet
    {
      private WebSocketFactory _wsFactory;
      private final Set _members = new CopyOnWriteArraySet();
      @Override
      public void init() throws ServletException
      {
        // Create and configure WS factory
        _wsFactory=new WebSocketFactory(new WebSocketFactory.Acceptor()
        {
          public boolean checkOrigin(HttpServletRequest request, String origin)
          {
            // Allow all origins
            return true;
          }
          public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol)
          {
             if ("chat".equals(protocol))
               return new ChatWebSocket();
             return null;
          }
        });
        _wsFactory.setBufferSize(4096);
        _wsFactory.setMaxIdleTime(60000);
      }
      ...

    The WebSocketFactory is instantiated by passing it an Acceptor instance, which in this case is an anonymous instance. The Acceptor must implement two methods: checkOrigin, which in this case accepts all; and doWebSocketConnect, which must accept a WebSocket connection by creating and returning an instance of the WebSocket interface to handle incoming messages.  In this case, an instance of the nested ChatWebSocket class is created if the protocol is “chat”.   The other WebSocketFactory fields have been initialised with hard coded buffers size and timeout, but typically these would be configurable from servlet init parameters.

    The servlet handles get requests by passing them to the WebSocketFactory to be accepted or not:

      ...
      protected void doGet(HttpServletRequest request,
                           HttpServletResponse response)
        throws IOException
      {
        if (_wsFactory.acceptWebSocket(request,response))
          return;
        response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
                           "Websocket only");
      }
      ...

    All that is left for the Servlet, is the ChatWebSocket itself.   This is just a POJO that receives callbacks for events.  For this example we have implemented the WebSocket.OnTextMessage interface to restrict the call backs to only connection management and full messages:

      private class ChatWebSocket implements WebSocket.OnTextMessage
      {
        Connection _connection;
        public void onOpen(Connection connection)
        {
          _connection=connection;
          _members.add(this);
        }
        public void onClose(int closeCode, String message)
        {
          _members.remove(this);
        }
        public void onMessage(String data)
        {
          for (ChatWebSocket member : _members)
          {
            try
            {
              member._connection.sendMessage(data);
            }
            catch(IOException e)
            {
              e.printStackTrace();
            }
          }
        }
      }

    The handling of the onOpen callback is to add the ChatWebSocket to the set of all members (and remembering the Connection object for subsequent sends).  The onClose handling simply removes the member from the set.   The onMessage handling iterates through all the members and sends the received message to them (and prints any resulting exceptions).

     

    The Server

    To run the servlet, there is a simple Main method that creates an embedded Jetty server with a ServletHandler for the chat servlet, as ResourceHandler for the static content needed by the browser client and a DefaultHandler to generate errors for all other requests:

    public class Main
    {
      public static void main(String[] arg) throws Exception
      {
        int port=arg.length>1?Integer.parseInt(arg[1]):8080;
        Server server = new Server(port);
        ServletHandler servletHandler = new ServletHandler();
        servletHandler.addServletWithMapping(ChatServlet.class,"/chat/*");
        ResourceHandler resourceHandler = new ResourceHandler();
        resourceHandler.setBaseResource(Resource.newClassPathResource("com/example/docroot/"));
        DefaultHandler defaultHandler = new DefaultHandler();
        HandlerList handlers = new HandlerList();
        handlers.setHandlers(new Handler[] {servletHandler,resourceHandler,defaultHandler});
        server.setHandler(handlers);
        server.start();
        server.join();
      }
    }

    The server can be run from an IDE or via maven using the following command line:

    mvn
    mvn -Pserver exec:exec

    The Browser Client

    The HTML for the chat room simply imports some CSS and the javascript before creating a few simple divs to contain the chat text, the join dialog and the joined dialog:

    <html>
     <head>
     <title>WebSocket Chat Example</title>
     <script type='text/javascript' src="chat.js"></script>
     <link rel="stylesheet" type="text/css" href="chat.css" />
     </head>
     <body>
      <div id='chat'></div>
      <div id='input'>
       <div id='join' >
        Username:&nbsp;<input id='username' type='text'/>
        <input id='joinB' class='button' type='submit' name='join' value='Join'/>
       </div>
       <div id='joined' class='hidden'>
        Chat:&nbsp;<input id='phrase' type='text'/>
        <input id='sendB' class='button' type='submit' name='join' value='Send'/>
       </div>
      </div>
      <script type='text/javascript'>init();</script>
     </body>
    </html>

    The javascript create a room object with methods to handle the various operations of a chat room.  The first operation is to join the chat room, which is triggered by entering a user name.  This creates a new WebSocket object pointing to the /chat URL path on the same server the HTML was loaded from:

    var room = {
      join : function(name) {
        this._username = name;
        var location = document.location.toString()
          .replace('http://', 'ws://')
          .replace('https://', 'wss://')+ "chat";
        this._ws = new WebSocket(location, "chat");
        this._ws.onopen = this.onopen;
        this._ws.onmessage = this.onmessage;
        this._ws.onclose = this.onclose;
      },
      onopen : function() {
        $('join').className = 'hidden';
        $('joined').className = '';
        $('phrase').focus();
        room.send(room._username, 'has joined!');
      },
      ...

    The javascript websocket object is initialised with call backs for onopen, onclose and onmessage. The onopen callback is handled above by switching the join div to the joined div and sending a “has joined” message.

    Sending is implemented by creating a string of username:message and sending that via the WebSocket instance:

      ...
      send : function(user, message) {
        user = user.replace(':', '_');
        if (this._ws)
          this._ws.send(user + ':' + message);
      },
      ...

    If the chat room receives a message, the onmessage callback is called, which sanitises the message, parses out the username and appends the text to the chat div:

      ...
      onmessage : function(m) {
        if (m.data) {
          var c = m.data.indexOf(':');
          var from = m.data.substring(0, c)
            .replace('<','<')
            .replace('>','>');
          var text = m.data.substring(c + 1)
            .replace('<', '<')
            .replace('>', '>');
          var chat = $('chat');
          var spanFrom = document.createElement('span');
          spanFrom.className = 'from';
          spanFrom.innerHTML = from + ': ';
          var spanText = document.createElement('span');
          spanText.className = 'text';
          spanText.innerHTML = text;
          var lineBreak = document.createElement('br');
          chat.appendChild(spanFrom);
          chat.appendChild(spanText);
          chat.appendChild(lineBreak);
          chat.scrollTop = chat.scrollHeight - chat.clientHeight;
        }
      },
      ...

    Finally, the onclose handling empties the chat div and switches back to the join div so that a new username may be entered:

      ...
      onclose : function(m) {
        this._ws = null;
        $('join').className = '';
        $('joined').className = 'hidden';
        $('username').focus();
        $('chat').innerHTML = '';
      }
    };

    With this simple client being served from the server, you can now point your websocket capable browsers at http://localhost:8080 and interact with the chat room. Of course this example glosses over a lot of detail and complications a real chat application would need, so I suggest you read my blog is websocket chat simpler to learn what else needs to be handled.

    The Load Test Client

    The jetty websocket java client is an excellent tool for both functional and load testing of a websocket based service.  It  uses the same endpoint API as the server side and for this example we create a simple implementation of the OnTextMessage interface that keeps track of the all the open connection and counts the number of messages sent and received:

    public class ChatLoadClient implements WebSocket.OnTextMessage
    {
      private static final AtomicLong sent = new AtomicLong(0);
      private static final AtomicLong received = new AtomicLong(0);
      private static final Set<ChatLoadClient> members = new CopyOnWriteArraySet<ChatLoadClient>();
      private final String name;
      private final Connection connection;
      public ChatLoadClient(String username,WebSocketClient client,String host, int port)
      throws Exception
      {
        name=username;
        connection=client.open(new URI("ws://"+host+":"+port+"/chat"),this).get();
      }
      public void send(String message) throws IOException
      {
        connection.sendMessage(name+":"+message);
      }
      public void onOpen(Connection connection)
      {
        members.add(this);
      }
      public void onClose(int closeCode, String message)
      {
        members.remove(this);
      }
      public void onMessage(String data)
      {
        received.incrementAndGet();
      }
      public void disconnect() throws IOException
      {
        connection.disconnect();
      }

    The Websocket is initialized by calling open on the WebSocketClient instance passed to the constructor.  The WebSocketClient instance is shared by multiple connections and contains the thread pool and other common resources for the client.

    This load test example comes with a main method that creates a WebSocketClient from command line options and then creates a number of ChatLoadClient instances:

    public static void main(String... arg) throws Exception
    {
      String host=arg.length>0?arg[0]:"localhost";
      int port=arg.length>1?Integer.parseInt(arg[1]):8080;
      int clients=arg.length>2?Integer.parseInt(arg[2]):1000;
      int mesgs=arg.length>3?Integer.parseInt(arg[3]):1000;
      WebSocketClient client = new WebSocketClient();
      client.setBufferSize(4096);
      client.setMaxIdleTime(30000);
      client.setProtocol("chat");
      client.start();
      // Create client serially
      ChatLoadClient[] chat = new ChatLoadClient[clients];
      for (int i=0;i<chat.length;i++)
        chat[i]=new ChatLoadClient("user"+i,client,host,port);
      ...

    Once the connections are opened, the main method loops around picking a random client to speak in the chat room

      ...
      // Send messages
      Random random = new Random();
      for (int i=0;i<mesgs;i++)
      {
        ChatLoadClient c = chat[random.nextInt(chat.length)];
        String msg = "Hello random "+random.nextLong();
        c.send(msg);
      }
      ...

    Once all the messages have been sent and all the replies have been received, the connections are closed:

      ...
      // close all connections
      for (int i=0;i<chat.length;i++)
        chat[i].disconnect();

    The project is setup so that the load client can be run with the following maven command:

    mvn -Pclient exec:exec

    And the resulting output should look something like:

    Opened 1000 of 1000 connections to localhost:8080 in 1109ms
    Sent/Received 10000/10000000 messages in 15394ms: 649603msg/s
    Closed 1000 connections to localhost:8080 in 45ms

    Yes that is 649603 messages per second!!!!!!!!!!! This is a pretty simple easy test, but it is still scheduling 1000 local sockets plus generating and parsing all the websocket frames. Real applications on real networks are unlikely to achieve close to this level, but the indications are good for the capability of high throughput and stand by for more rigorous bench marks shortly.

     

     

     

  • Prelim Cometd WebSocket Benchmarks

    I have done some very rough preliminary benchmarks on the latest cometd-2.4.0-SNAPSHOT with the latest Jetty-7.5.0-SNAPSHOT and the results are rather impressive.  The features that these two releases have added are:

    • Optimised Jetty NIO with latest JVMs and JITs considered.
    • Latest websocket draft implemented and optimised.
    • Websocket client implemented.
    • Jackson JSON parser/generator used for cometd
    • Websocket cometd transport for the server improved.
    • Websocket cometd transport for the bayeux client implemented.

    The benchmarks that I’ve done have all been on my notebook using the localhost network, which is not the most realistic of environments, but it still does tell us a lot about the raw performance of the cometd/jetty.  Specifically:

    • Both the server and the client are running on the same machine, so they are effectively sharing the 8 CPUs available.   The client typically takes 3x more CPU than the server (for the same load), so this is kind of like running the server on a dual core and the client on a 6 core machine.
    • The local network has very high throughput which would only be matched by gigabit networks.  It also has practically no latency, which is unlike any real network.  The long polling transport is more dependent on good network latency than the websocket transport, so the true comparison between these transports will need testing on a real network.

    The Test

    The cometd load test is a simulated chat application.  For this test I tried long-polling and websocket transports for 100, 1000 and 10,000 clients that were each logged into 10 randomly selected chat rooms from a total of 100 rooms.   The messages sent were all 50 characters long and were published in batches of 10 messages at once, each to randomly selected rooms.  There was a pause between batches that was adjusted to find a good throughput that didn’t have bad latency.  However little effort was put into finding the optimal settings to maximise throughput.

    The runs were all done on JVM’s that had been warmed up, but the runs were moderately short (approx 30s), so steady state was not guaranteed and the margin of error on these numbers will be pretty high.  However, I also did a long run test at one setting just to make sure that steady state can be achieved.

    The Results

    The bubble chart above plots messages per second against number of clients for both long-polling and websocket transports.   The size of the bubble is the maximal latency of the test, with the smallest bubble being 109ms and the largest is 646ms.  Observations from the results are:

    • Regardless of transport we achieved 100’s of 1000’s messages per second!  These are great numbers and show that we can cycle the cometd infrastructure at high rates.
    • The long-polling throughput is probably a over reported because there are many messages being queued into each HTTP response.   The most HTTP responses I saw was 22,000 responses per second, so for many application it will be the HTTP rate that limits the throughput rather than the cometd rate.  However the websocket throughput did not benefit from any such batching.
    • The maximal latency for all websocket measurements was significantly better than long polling, with all websocket messages being delivered in < 200ms and the average was < 1ms.
    • The websocket throughput increased with connections, which probably indicates that at low numbers of connections we were not generating a maximal load.

    A Long Run

    The throughput tests above need to be redone on a real network and longer runs. However I did do one long run ( 3 hours) of 1,000,013,657 messages at 93,856/sec. T results suggest no immediate problems with long runs. Neither the client nor the server needed to do a old generation collection and all young generation collections took on average only 12ms.

    The output from the client is below:

    Statistics Started at Fri Aug 19 15:44:48 EST 2011
    Operative System: Linux 2.6.38-10-generic amd64
    JVM : Sun Microsystems Inc. Java HotSpot(TM) 64-Bit Server VM runtime 17.1-b03 1.6.0_22-b04
    Processors: 8
    System Memory: 55.35461% used of 7.747429 GiB
    Used Heap Size: 215.7406 MiB
    Max Heap Size: 1984.0 MiB
    Young Generation Heap Size: 448.0 MiB
    - - - - - - - - - - - - - - - - - - - -
    Testing 1000 clients in 100 rooms, 10 rooms/client
    Sending 1000000 batches of 10x50 bytes messages every 10000 µs
    - - - - - - - - - - - - - - - - - - - -
    Statistics Ended at Fri Aug 19 18:42:23 EST 2011
    Elapsed time: 10654717 ms
    	Time in JIT compilation: 57 ms
    	Time in Young Generation GC: 118473 ms (8354 collections)
    	Time in Old Generation GC: 0 ms (0 collections)
    Garbage Generated in Young Generation: 2576746.8 MiB
    Garbage Generated in Survivor Generation: 336.53125 MiB
    Garbage Generated in Old Generation: 532.35156 MiB
    Average CPU Load: 433.23907/800
    ----------------------------------------
    Outgoing: Elapsed = 10654716 ms | Rate = 938 msg/s = 93 req/s =   0.4 Mbs
    All messages arrived 1000013657/1000013657
    Messages - Success/Expected = 1000013657/1000013657
    Incoming - Elapsed = 10654716 ms | Rate = 93856 msg/s = 90101 resp/s(96.00%) =  35.8 Mbs
    Thread Pool - Queue Max = 972 | Latency avg/max = 3/62 ms
    Messages - Wall Latency Min/Ave/Max = 0/8/135 ms

    Note that the client was using 433/800 of the available CPU, while you can see that the server (below) was using only 170/800.  This suggests that the server has plenty of spare capacity if it were given the entire machine.

    Statistics Started at Fri Aug 19 15:44:47 EST 2011
    Operative System: Linux 2.6.38-10-generic amd64
    JVM : Sun Microsystems Inc. Java HotSpot(TM) 64-Bit Server VM runtime 17.1-b03 1.6.0_22-b04
    Processors: 8
    System Memory: 55.27913% used of 7.747429 GiB
    Used Heap Size: 82.58406 MiB
    Max Heap Size: 2016.0 MiB
    Young Generation Heap Size: 224.0 MiB
    - - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - - - - -
    Statistics Ended at Fri Aug 19 18:42:23 EST 2011
    Elapsed time: 10655706 ms
    	Time in JIT compilation: 187 ms
    	Time in Young Generation GC: 140973 ms (12073 collections)
    	Time in Old Generation GC: 0 ms (0 collections)
    Garbage Generated in Young Generation: 1652646.0 MiB
    Garbage Generated in Survivor Generation: 767.625 MiB
    Garbage Generated in Old Generation: 1472.6484 MiB
    Average CPU Load: 170.20532/800

    Conclusion

    These results are preliminary, but excellent none the less!   The final releases of jetty 7.5.0 and cometd 2.4.0 will be out within a week or two and we will be working to bring you some more rigorous benchmarks with those releases.

     

     

     

  • Jetty Overlayed WebApp Deployer

    The Jetty Overlay Deployer allows multiple WAR files to be overlayed so that a web application can be customised, configured and deployed without the need to unpack, modify and repack the WAR file. This has the benefits of:

    • The WAR file may be kept immutable, even signed, so that it is clear which version has been deployed.
    • All modifications made to customise/configure the web application are kept in a separate wars and thus are easily identifiable for review and migration to new versions.
    • A parameterised template overlay can be created that contains common customisations and configuration that apply to many instances of the web application (eg for multi-tenant deployment).
    • Because the layered deployment clearly identifies the common and instance specific components, then Jetty is able to share classloaders and static resource caches for the template, greatly reducing the memory footprint of multiple instances

    This blog is a tutorial of how to configure Jetty to use the Overlay deployer, and how to deploy multiple instances of the JTrac web application.

    Overview

    The customisation, configuration and deployment a web application bundled as a WAR file frequently includes some or all of:

    • Editing the WEB-INF/web.xml file to set init parameters, add filters/servlets or to configure JNDI resources.
    • Editing other application specific configuration files in WEB-INF/*
    • Editing container specific configuration files in WEB-INF/* (eg jetty-web.xml or jboss-web.xml)
    • Adding/modifying static content such as images and css to style/theme the webapplication
    • Adding jars to the container classpath for Datasource and other resources
    • Modifying the container configuration to provide JNDI resources

    The result is that the customisations and configurations are blended into both the container and the WAR file. If either the container or the base WAR file are upgraded to a new version, it can be a very difficult and error prone task to identify all the changes that have been made and to reapply them to a new version.

    Overlays

    To solve the problems highlighted above, jetty 7.4 introduces WAR overlays (which are a concept borrowed from the maven war plugin). An overlay is basically just another WAR files, whose contents are merged on top of the original WAR so that files may be added or replaced.

    However,  jetty overlays also allow mixin fragments of web.xml, so the configuration can be modified without being replaced

    Jtrac Overlay Example

    The jtrac issue tracking webapplication is a good example of a typical web application, as it uses the usual suspects of libs: spring, hibernate, dom4j, commons-*, wicket, etc. So I’ve used it as the basis of this example.

    The files for this demonstration are available in overlays-demo.tar.gz. This could be expanded on top of the jetty distribution, but for this tutorial we will expand it to /tmp and install the components step by step:

    cd /tmp
    wget http://webtide.intalio.com/wp-content/uploads/2011/05/overlays-demo.tar.gz
    tar xfvz overlays-demo.tar.gz
    export OVERLAYS=/tmp/overlays

    Configuring Jetty for Overlays

    Overlays support is included in jetty distributions from 7.4.1-SNAPSHOT onwards, so you can download a distribution from oss.sonatype.org or maven central(once 7.4.1 is released) and unpack into a directory.    The start.ini file then needs to be edited so that it includes the overlay option and configuration file.   The resulting file should look like:

    OPTIONS=Server,jsp,jmx,resources,websocket,ext,overlay
    etc/jetty.xml
    etc/jetty-deploy.xml
    etc/jetty-overlay.xml

    The smarts of this are in etc/jetty-deploy.xml files, which installs the OverlayedAppProvider into the DeploymentManager. Jetty can then be started normally:

    java -jar start.jar

    Jetty will now be listening on port 8080, but with no webapp deployed.   The rest of the tutorial should be conducted in another window with the JETTY_HOME environment set to the jetty distribution directory.

    Installing the WebApp

    The WAR file for this demo can be downloaded and deployed using the following commands, which essentially downloads and extracts the WAR file to the $JETTY_HOME/overlays/webapps directory

    cd /tmp
    wget -O jtrac.zip http://sourceforge.net/projects/j-trac/files/jtrac/2.1.0/jtrac-2.1.0.zip/download
    jar xfv jtrac.zip jtrac/jtrac.war
    mv jtrac/jtrac.war $JETTY_HOME/overlays/webapps

    When you have run these commands (or equivalent), you will see in the jetty server window a message saying that the OverlayedAppProvider has extracted and loaded the war file:

    2011-05-06 10:31:54.678:INFO:OverlayedAppProvider:Extract jar:file:/tmp/jetty-distribution-7.4.1-SNAPSHOT/overlays/webapps/jtrac-2.1.0.war!/ to /tmp/jtrac-2.1.0_236811420856825222.extract
    2011-05-06 10:31:55.235:INFO:OverlayedAppProvider:loaded jtrac-2.1.0@1304641914666

    Unlike the normal webapps dir, loading a war file from the overlays/webapp dir does not deploy the webapplication.  It simply makes it available to be used as the basis for templates and overlays.

    Installing a Template Overlay

    A template overlay is a WAR structured directory/archive that contains just the files that have been added or modified to customize/configure the webapplication for all instances that will be deployed.

    The demo template can be installed from the downloaded files with the command:

    mv $OVERLAYS/jtracTemplate=jtrac-2.1.0 $JETTY_HOME/overlays/templates/

    In the Jetty server window, you should see the template loaded with a message like:

    2011-05-06 11:00:08.716:INFO:OverlayedAppProvider:loaded jtracTemplate=jtrac-2.1.0@1304643608715

    The contents of the loaded template is as follows:

    templates/jtracTemplate=jtrac-2.1.0
    └── WEB-INF
        ├── classes
        │   └── jtrac-init.properties
        ├── log4j.properties
        ├── overlay.xml
        ├── template.xml
        └── web-overlay.xml

    The name of the template directory (or it could be a war) uses the ‘=’ character in jtracTemplate=jtrac-2.1.0 to separates the name of the template from the name of the WAR file in webapps that it applies to.  If  = is a problem, then -- may also be used.

    WEB-INF/classes/jtrac-init.properties – replaces the jtrac properties file with an empty file, as the properties contained within it are configured elsewhere

    WEB-INF/log4j.properties – configures the logging for all instances of the template.

    WEB-INF/overlay.xml – a Jetty XML formatted IoC file that is used to inject/configure the ContextHandler for each instances. In this case it just sets up the context path:

    
    
    
      /
    

    WEB-INF/template.xml – a Jetty XML formatted IoC file that is used to inject/configure the resource cache and classloader that is shared by all instances of the template. It is run only once per load of the template:

    
    
    
      
        true
        10000000
        1000
        64000000
      
    

    WEB-INF/web-overlay.xml – a web.xml fragment that is overlayed on top of the web.xml from the base WAR file, that can set init parameters and add/modify filters and servlets. In this it sets the application home and springs rootKey:

    
    
      
        jtrac.home
        /tmp/jtrac-${overlay.instance.classifier}
      
      
        webAppRootKey
        jtrac-${overlay.instance.classifier}
      
      
    

    Note the use of parameterisation of values such as ${overlays.instance.classifier}, as this allows the configuration to be made in the template and not customised for each instance.

    Without the overlayed deployer, all the configurations above would still need to have been made, but rather that being in a single clear structure they would have been either in the servers common directory, the servers webdefaults.xml (aka server.xml), or baked into the WAR file of each application instance using copied/modified files from the original. The overlayed deployer allows us to make all these changes in one structure, more over it allows some of the configuration to be parameterised to facilitate easy multi-tenant deployment.

    Installing an Instance Overlay

    Now that we have installed a template, we can install one or more instance overlays, which deploy the actual web applications:

    mv /tmp/overlays/instances/jtracTemplate=blue $JETTY_HOME/overlays/instances/
    mv /tmp/overlays/instances/jtracTemplate=red $JETTY_HOME/overlays/instances/
    mv /tmp/overlays/instances/jtracTemplate=blue $JETTY_HOME/overlays/instances/
    

    As each instance is moved into place, you will see the jetty server window react and deploy that instance. Within each instance, there is the structure:

    instances/jtracTemplate=red/
    ├── WEB-INF
    │   └── overlay.xml
    ├── favicon.ico
    └── resources
        └── jtrac.css
    

    WEB-INF/overlay.xml – a Jetty XML format IoC file that injects/configures the context for the instance. In this case it sets up a virtual host for the instance:

    
    
    
      
        
          127.0.0.2
          red.myVirtualDomain.com
        
      
    

    favicon.ico – replaces the icon in the base war with one themed for the instance colour.

    resources/jtrac.css – replaces the style sheet from the base war with one themed for the instance colour

    The deployed instances can now be viewed by pointing your browser at http://127.0.0.1:8080, http://127.0.0.2:8080 and http://127.0.0.3:8080. The default username/password for jtrac is admin/admin.

    Things to know and notice

    • Each instance is themed with images and styles sheets from the instance overlay.
    • Each instance is running with it’s own application directory (eg. /tmp/jtrac-red), that is set templates web-overlay.xml.
    • The instances are distinguished by virtual host that is set in the instance overlay.xml
    • The static content from the base war and template are shared between all instances. Specifically there is a shared ResourceCache so only a single instance of each static content is loaded into memory.
    • The classloader at the base war and template level is shared between all instances, so that only a single instance of common classes is loaded into memory. Classes with non shared statics can be configured to load in the instances classloader.
    • All overlays are hot deployed and dependencies tracked. If an XML is touched in an instance, it is redeployed. If an XML is touched in a template, then all instances using it are redeployed. If a WAR file is touched, then all templates and all instances dependant on it are redeployed.
    • New versions can easily be deployed. Eg when jtrac-2.2.0.war becomes available, it can just be dropped into overlays/webapps and then rename jtracTemplate=jtrac-2.1.0 to jtracTemplate=jtrac-2.2.0
    • There is a fuller version of this demo in overlays-demo-jndi.tar.gz, that uses JNDI (needs options=jndi,annotations and jetty-plus.xml in start.ini) and shows how extra jars can be added in the overlays.
  • Jetty with Spring XML

    Since the very beginning, Jetty has been IOC friendly and thus has been able to be configured with spring.  But the injecting and assembling the jetty container is not the only need that Jetty has for configuration and there are several other configuration files (eg contexts/yourapp.xml,  jetty-web.xml,  jetty-env.xml) that have needed to be in the Jetty XML configuration format.

    With the release of Jetty-7.4, the jetty-spring module has been enhanced with and XmlConfiguration Provider, so now anywhere there is a jetty xml file can be replaced with a spring XML file, so that an all spring configuration is now possible. [ But note that there is no plan to use spring as the default configuration mechanism.  For one, the 2.9MB size of the spring jar is too large for Jetty’s foot print aspirations (currently only 1.5MB for everything) ].

    Starting with spring Jetty

    First you will need a download of jetty-hightide, that includes the spring module:

    wget --user-agent=other http://repo2.maven.org/maven2/org/mortbay/jetty/jetty-hightide/7.4.0.v20110414/jetty-hightide-7.4.0.v20110414.tar.gz
    tar xfz jetty-hightide-7.4.0.v20110414.tar.gz
    jetty-hightide-7.4.0.v20110414/

    You then need to augment this with a spring jar and commons logging:

    cd lib/spring
    wget --user-agent=other http://repo2.maven.org/maven2/org/springframework/spring/2.5.6/spring-2.5.6.jar
    wget --user-agent=other http://repo2.maven.org/maven2/commons-logging/commons-logging/1.1.1/commons-logging-1.1.1.jar
    cd ../..

    and then add spring to the Jetty options by editing start.ini and adding “spring” to the OPTIONS set there:

    OPTIONS=Server,jsp,jmx,resources,websocket,ext,jta,plus,jdbc,annotations,spring

    and that’s it! Jetty is now ready to be configured with spring

    Example Jetty XML

    We can now replace the main etc/jetty.xml file with a spring version as follows:

    
    
    
      
      
        
          
            
            
          
        
        
          
            
              
            
          
        
        
          
            
              
                 
                 
              
            
          
        
        
        
        
        
        
        
      
    
    

    Note that Server bean is given the name (or alias) of “Main” to identify it as the primary bean configured by this file. This equates to the Configure element of the Jetty XML format. Note also that both the Server and Contexts ids are used by subsequent config files (eg etc/jetty-deploy) to reference the beans created here and that the ID space is shared between the configuration formats. Thus you can mix and match configuration formats.

    Example Context XML

    As another example, you can replace the contexts/test.xml file with a spring version as follows:

    
    
    
      
        
        
          
            
              
              
            
          
          
        
        
        
        
        
        
        
        
        
        
          
            www.myVirtualDomain.com
            localhost
            127.0.0.1
          
        
      
    
    

    Note that unlike jetty XML, spring does not have a GET element that allows a bean to be obtained from another bean and then configured. So the structure of this context file is somewhat different to the corresponding jetty xml file.

    Running Spring Jetty

    Running spring jetty is now exactly as for normal jetty:

    java -jar start.jar

    This uses the start.ini file and the lib directory to construct a classpath and to execute the configuration files specified (including the jetty.xml we have converted to spring). Use java -jar start.jar --help to learn more about the jetty start mechanism.

    Of course, with spring, you can also start jetty by running spring directly and using a more spring-like mechanism for aggregating multiple configuration files.

    Conclusion

    While spring and jetty XML are roughly equivalent, they each have their idiosyncrasies. The Jetty API has been developed with the jetty XML format in mind, so if you examine the full suite of Jetty XML files, you will see Getters and methods calls used to configure the server. These can be done in spring (AFAIN using helper classes), but it is a little more clunky than jetty XML. This can be improved over time by a) having spring config files written by somebody more spring literate than me; b) improving the API to be more spring friendly; c) adapting the style of configuration aggregation to be more spring-like. I’m receptive to all three and would welcome spring users to collaborate with to improve the all spring configuration of jetty.