|

SPDY Sample App

SPDY is a project looking to boast web performance by utilizing the old good HTTP. Established in 2009, SPDY is still considered an experimental protocol supported by a limited number of browsers. The project is fairly well documented and there are examples highlighting the core concepts. When playing with SPDY features, I realized I needed to separate business logic from protocol details. My other concern was efficient data transfer using the JSON format. Today, I would like to demonstrate an application of basic communication flow between SPDY client and server.

The application is a simple trading client. Think of a portfolio grid being constantly updated with latest prices. A good example can be seen hereMy demo is far less advanced and lacks the web front-end, all the output is printed to the console. I hope you will find it interesting nevertheless.

The application I am going to talk about outputs nothing but this:

2012-06-10 14:41:17.282:INFO:oejs.Server:jetty-8.1.3.v20120416
2012-06-10 14:41:17.486:INFO:oejs.AbstractConnector:Started SPDYServerConnector@0.0.0.0:8181
2012-06-10 14:41:17.509:INFO:oejs.SPDYServerConnector:SPDY support is experimental. Please report feedback at jetty-dev@eclipse.org
2012-06-10 14:41:17,775 INFO  Received request for /favourite
2012-06-10 14:41:17,775 INFO  Received request for /prices
2012-06-10 14:41:17,815 INFO  $21.61: Bar Plc.
2012-06-10 14:41:17,822 INFO  Most traded: Bar Plc.
2012-06-10 14:41:17,822 INFO  $10.19: Foo Consulting
2012-06-10 14:41:17,823 INFO  $25.13: Acme Ltd.
2012-06-10 14:41:18,744 INFO  Received request for /prices
2012-06-10 14:41:18,747 INFO  $24.50: Bar Plc.
2012-06-10 14:41:18,747 INFO  $35.66: Foo Consulting
2012-06-10 14:41:18,747 INFO  $29.64: Acme Ltd.
2012-06-10 14:41:19,744 INFO  Received request for /prices
...

The excerpt reveals most of the details. First, the example runs on Jetty. As of version 8, Jetty comes shipped with SPDY client / server support. Anything I am going to say about SPDY applies solely to Jetty. Another choice in the Java world, as far as I know, could be Netty.

Secondly, it is obvious there is a notion of a concept not dissimilar from the existing web frameworks. The server listens on certain urls to cater for a variety of client needs. Thus the /prices request asks for latest prices in the given asset portfolio, whereas the /favourite one fetches the name of the momentarily most traded asset. Also notice, that price requests are more frequent that their favourite counterparts.

That’s it for theory, let’s take a look under the hood of the minimalistic trading system wanna-be. The server side is relatively straightforward. Here are the important steps.

To start off, instantiate the server and make it capture client requests:

import org.eclipse.jetty.server.Server;
  import org.eclipse.jetty.server.Connector;
  import org.eclipse.jetty.spdy.SPDYServerConnector;
  import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
  ..
  Server server = new Server();

  Connector connector = new SPDYServerConnector(getSessionListener());
  connector.setPort(8181);
  server.addConnector(connector);
  ..
  private ServerSessionFrameListener getSessionListener() {
    // Handle client requests
  }
  ..

Next, in the client handling part, inspect the url and determine what to do:

import org.eclipse.jetty.spdy.api.Headers;
import org.eclipse.jetty.spdy.api.Stream;
import org.eclipse.jetty.spdy.api.StreamFrameListener;
import org.eclipse.jetty.spdy.api.DataInfo;
  ..
  private ServerSessionFrameListener getSessionListener() {
    Headers headers = synInfo.getHeaders();
    final String url = headers.get("url").value();

    return new StreamFrameListener.Adapter() {
      @Override
      public void onData(Stream stream, DataInfo dataInfo) {
        if ("/prices".equals(url)) {..}
      }
    };
  }
  ..

Once done, send a response back to the client. The data in this particular case is of a non-primitive type. Therefore, it has to be converted into a byte array.

..
  if ("/prices".equals(url)) {
    // Let's assume that data have been populated and serialized
    byte[] priceMap = ..
    stream.data(new BytesDataInfo(priceMap, false));
  }
..

I relied on the Jackson Processor to help out with flattening of objects into JSON. Since I was striving for high efficiency I made use of the Streaming API. Conversion applies both on the server and the client side and is bi-directional: objects to JSON and JSON back to objects. Details can be found in the attached source code.

That wraps up the server side. Client’s main task is to establish a connection to the server:

import java.net.InetSocketAddress;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.spdy.SPDYClient;
import org.eclipse.jetty.spdy.api.Headers;
import org.eclipse.jetty.spdy.api.SPDY;
import org.eclipse.jetty.spdy.api.Session;
..
  private static SPDYClient.Factory clientFactory;
  ..
  // Create a client instance
  SPDYClient client = getClientFactory().newSPDYClient(SPDY.V2); 

  // Obtain a session instance
  Session session = client.connect(
        new InetSocketAddress("localhost", 8181), null)
       .get(30, TimeUnit.SECONDS);

  // Sends SYN_STREAM to the server, adding headers
  Headers headers = new Headers();
  headers.put("url", "/prices");

  // configure the polling
  ..

  private static SPDYClient.Factory getClientFactory()
                                            throws Exception {
    if (clientFactory == null) {
      clientFactory = new SPDYClient.Factory();
      clientFactory.start();
    }
    return clientFactory;
  }
  ..

Next, the client needs to be configured to poll for data updates.

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.jetty.spdy.api.Stream;
import org.eclipse.jetty.spdy.api.Stream;
import org.eclipse.jetty.spdy.api.StreamFrameListener;
..
  // 5 seconds is allowed for the server response before it times out
  Stream stream = session.syn(new SynInfo(headers, false),
                  getStreamListener()).get(5, TimeUnit.SECONDS);

  // Data updates are checked every single second.
  // To make it work the client implements java.lang.Runnable
  ScheduledExecutorService executor =
    Executors.newSingleThreadScheduledExecutor();
  executor.scheduleAtFixedRate(this, 0, 1, TimeUnit.SECONDS);
  ..
  public StreamFrameListener getStreamListener() {
    // Capture server responses and invoke business logic
  }
  ..

Last missing bit is how to go about various polling intervals. In our case, the major concern are latest prices whereas knowing which asset is the most popular one is far less urgent. Well, I have simply created two SPDY clients and redesigned my code so that protocol details were kept aside. The trading client as such remains very simple and straightforward:

..
  // SPDY Client #1: checks for prices
  private PriceHandler priceHandler = new PriceHandler();

  // SPDY Client #2: checks for the most traded asset
  private MostTradedHandler favoriteHandler = new MostTradedHandler();
  ..
  // Subscribe to changes in an asset portfolio
  public void subscribe(String... assets) {
    // every single second, check for price changes in the portfolio
    priceHandler.register(1, assets);
    // every third second, obtain the favourite asset
    favoriteHandler.register(3, assets);
  }

To conclude, I covered bare minimum from the options SPDY has on offer. To achieve the ultimate goal of low latency, SPDY supports a range of additional features: stream prioritization, multiplexed streams, server hint etc.

Download Source Code

Similar Posts