Wednesday, May 6, 2015

HTTP/2: A Jump-Start for Java Developers Part-2

Listing 6. Establishing an HTTP/2 connection

// create a low-level Jetty HTTP/2 client
HTTP2Client lowLevelClient = new HTTP2Client();
lowLevelClient.start();

// create a new session the represents a (multiplexed) connection to the server
FuturePromise<Session> sessionFuture = new FuturePromise<>();
lowLevelClient.connect(new InetSocketAddress("myserver", 8043)), new Session.Listener.Adapter(),sessionFuture);
Session session = sessionFuture.get();

 

Streaming data in HTTP/2

Once the HTTP/2 connection has been established, endpoints can begin exchanging frames. Frames are always associated with a stream. A single HTTP/2 connection can contain multiple concurrently open streams. In the listing below a stream is opened to perform an HTTP request-response exchange. When the stream is opened a request HEADER frame is provided. In HTTP/2 the header data of such a request message will be transferred by using a HEADER frame.

Listing 7. An HTTP/2 request-response exchange

 

// build a request header frame
MetaData.Request metaData = new MetaData.Request("GET", HttpScheme.HTTP, new HostPortHttpField("myserver: 8043" + server.getLocalport()), "/", HttpVersion.HTTP_2, new HttpFields());
HeadersFrame headersFrame = new HeadersFrame(1, metaData, null, true);

// .. and perform the request-response exchange
session.newStream(headersFrame, new Promise.Adapter<Stream>(), new PrintingFramesHandler());

 

To handle the response data a response frame handler has to be assigned to the stream. The frame handler defines call-back methods to process the different frames types. The simplified example in Listing 8 specifies that the content of the responded HEADERS and DATA frames will be written to the console.

Listing 8. HTTP/2 response frame handler

 

// prints out the received frames. E.g.
// [1] HEADERS HTTP/2.0{s=200,h=2}
// [1]     server: Jetty(9.3.0.M2)
// [1]     date: Thu, 16 Apr 2015 15:02:00 GMT
// [1] DATA <html> <header> ...
//
class PrintingFramesHandler extends Stream.Listener.Adapter {

   // processes HEADER frames
   @Override
   public void onHeaders(Stream stream, HeadersFrame frame) {
      System.out.println("[" + stream.getId() + "] HEADERS " + frame.getMetaData().toString());
   }

   // processes HEADER frames
   @Override
   public void onData(Stream stream, DataFrame frame, Callback callback) {
      byte[] bytes = new byte[frame.getData().remaining()];
      frame.getData().get(bytes);
      System.out.println("[" + stream.getId() + "] DATA " + new String(bytes));      callback.succeeded();
   }

   // ...
}

 

The header frame structure, which is provided by the onHeaders(...) callback method, includes the decoded header data. In HTTP/2 header data is serialized by using HTTP/2 header compression.

HTTP/2 header compression

It is important to understand that HTTP/2 header compression is not like message-body gzip compression. On the contrary, it is a technique that ensures you will not re-send the same header twice. For every HTTP/2 connection the client and server will maintain a headers table containing the last response and request headers and their values, respectively. Upon the first request or response tall message headers will be sent. But for subsequent messages the endpoints will omit duplicate headers.

As an example, the request header shown in Listing 9 contains ~670 characters. The unencrypted HEADERS frame of the HTTP request requires ~500 bytes. By repeating the HTTP request with modified query parameters the HEADERS frame will consume ~60 bytes. Repeating the HTTP request without modifications will consume ~20 bytes. However, the concrete size depends on the header content and the current state of the HTTP/2 connection.

Listing 9. Example request header values

 

GET /mailboxes/5ca45b1fc92d3/mails?offset=0&amount=40 HTTP/2.0
referer: http://www.mail.com/premiumlogin/#.1258-header-premiumlogin1-1
accept-language: de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4
cookie: optimizelyEndUserId=oeu1411376552437r0.004748885752633214; ns_sample=65; SSID=BwAfHx0OAAQAAAAAfC5UTOoGAQB8LlQkAAAAAAAAAAAAXZAnVQAXHQQAAAEIAAAAXZAnVQEANwAAAA; SSRT=CJEnVQADAQ; SSLB=.0; um_cvt=UzHGLQpIBTMAABjAgjcAAAGX
host: www.mail.com
accept-encoding: gzip, deflate, sdch
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
user-agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36

 

HTTP message headers are massively redundant, so header compression is a very efficient way to reduce the overhead of additional requests. The overhead of a request-response exchange collapses to a very small size. In HTTP/2 a request-response exchange becomes cheap. Common network-optimization strategies such as avoiding request-response exchanges or combining multiple single requests into a batch request are not crucial in HTTP/2.

HTTP/2 multiplexing

 

Multiplexing is another browsing optimization in HTTP/2. In HTTP/2 each HTTP request-response exchange is associated with its own stream. Streams are largely independent of each other, so a blocked or stalled request or response does not prevent progress on other streams. Multiple requests and responses can be in flight simultaneously, and stream data can be interleaved and prioritized. The prioritization can be assigned for a new stream by including prioritization information in the HEADERS frame that opens the stream. The stream priority setting acts as advice for the peer and is relative to other streams in the connection.
Streams resolve HTTP/1.1's limitations with regard to parallel connections. In HTTP/2, thanks to Web ams, web developers can load Web dded web page resources in parallel. It isn't unusual Web ee a web page command 10 to 100 simultaneous streams for this purpose.

Impact on domain sharding, image sprites, and resource inlining

 

Multiplexing renders several browsing optimizations developed for HTTP/1.1 unnecessary in HTTP/2. Domain sharding, a popular technique to work around the maximum -connections-per-domain limitation in HTTP/1.1, is one example. Domain sharding works by splitting embedded page elements across multiple domains, which adds significant complexity to your infrastructure on the other side. HTTP/2 multiplexing makes domain sharding obsolete. Image sprites and resource inlining are two adWeb onal web page optimizations that are rendered obsolete by HTTP/2, as I will discuss below.

HTTP/2 push

 

HTTP/2 features push support that enables developers to load contained or linked resources in a very efficient way. HTTP/2 push allows a server to proactively send resources to the client's cache for future use. The server can start sending these as soon as a stream has been established, without waiting for the client to request them. For instance, resources such as contained images can be pushed to the client in parallel by returning the rWeb sted web page. As a result, browsing optimizations such as image sprites or resource inlining are no longer useful.

It is important to note that HTTP/2 push is not intended to replace server-sent events or WebSockets, which were introduced with HTML5. These HTML5 server-push technologies break away from HTTP's strict request-response semantics, which means that the client sends an HTTP request and waits until the HTTP response has been received. Server-sent events and WebSockets allow the server to send events or data at any time without a preceding HTTP request.

HTTP/2 push is different because it is still based on request-response semantics. But HTTP/2 push allows the server to respond with data for more queries than the client has requested. A push will be initiated by the server by sending a PUSH_PROMISE frame. A PUSH_PROMISE frame includes the associated HTTP request message data for the pushed HTTP response message. For instance a PUSH_PROMISE frame includes the request URI or request method. A PUSH_PROMISE frame is followed by HEADER and DATA frames to transfer the HTTP response message to push.

In Listing 10 the PrintingFramesHandler implements the callback method to process PUSH_PROMISE frames received from the server. The server then opens a new stream to push the data.

Listing 10. Handling push-promise frames

 

// prints out the received frames incl. push promise frames. E.g.
// [2] PUSH_PROMISE GET{u=http://myserver:8043/pictures/logo.jpg,HTT
// [1] HEADERS HTTP/2.0{s=200,h=4}
// [1]     server: Jetty(9.3.0.M2)
// [1]     date: Sat, 18 Apr 2015 05:47:00 GMT
// [1]     set-cookie: JSESSIONID=136ro5bx61vz611x5900d5fc3n;Path=/
// [1]     expires: Thu, 01 Jan 1970 00:00:00 GMT
// [2] HEADERS HTTP/2.0{s=200,h=1}
// [2]     date: Sat, 18 Apr 2015 05:47:00 GMT
// [2] DATA &brvbar;&brvbar;&brvbar;&brvbar; ?JFIF   d d  &brvbar;&brvbar; ?Ducky  ?   P  &brvbar;...
// [1] DATA <html> <header> ...
//
class PrintingFramesHandler extends Stream.Listener.Adapter {
   // ...

   // processes PUSH_PROMISE frames
   @Override
   public Listener onPush(Stream stream, PushPromiseFrame frame) {
      System.out.println("[" + stream.getId() + "] PUSH_PROMISE " + frame.getMetaData().toString());
      return this;
   }

 

In Listing 11 I have used Jetty's push support to generate PUSH_PROMISE frames on the server-side. Jetty's http2-server module provides a PushBuilder to initiate a push promise. The resource addressed by the URI path /pictures/logo.jpg will be pushed to the server if the /myrichpage.html page is requested.

Listing 11. Initiating an HTTP/2 push

 

class MyServlet extends HttpServlet {

   protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      Request jettyRequest = (Request) req;

      if (jettyRequest.getRequestURI().equals("/myrichpage.html") && jettyRequest.isPushSupported()) {
         jettyRequest.getPushBuilder()
                     .path("/pictures/logo.jpg")
                     .push();
      }

      // ...;
   }

 

Server push in Servlet 4.0

 

A standard interface to support server push will be part of the upcoming Servlet 4.0 (JSR 369) release. It may differ from Jetty's PushBuilder. Developers working in Servlet 4.0 may also be able to get the streamId for a given HttpServletRequest and HttpServletResponse. Developers should be able to get and set message priority, which is mapped into the underlying HTTP/2 stream priority. With the exception of HTTP/2 push, it is expected that the Servlet API update will see minor changes only. For instance frame handling or header compression could be done under the hood without the need to change the Servlet APIWeb isting web applications shouldn't have to be changed in order to support HTTP/2.

HTTP/2 in Jetty and other projects

 

In the examples above Jetty's new low-level HTTP/2 client has been used to provide a deeper look into HTTP/2's framing protocol. However, in most cases developers need a high-level client. For this you can use the new HTTP/2 client as a "transport" of Jetty's classic client. Jetty's classic client supports an API to plug-in different transport implementations. The current default is HTTP/1.1 compatible:

Listing 12. Jetty HttpClient

 

// create a low-level Jetty HTTP/2 client
HTTP2Client lowLevelClient = new HTTP2Client();
lowLevelClient.start();

// create a high-level Jetty client
HttpClient client = new HttpClient(new HttpClientTransportOverHTTP2(lowLevelClient), null);
client.start();

// request-response exchange
ContentResponse response = client.GET("http://localhost:" + server.getLocalport());

The Jetty project is an early adopter of the new HTTP/2 specification. Netty is another library that supports HTTP/2. Java 9 will also include an HttpClient that supports both HTTP 1.1 and HTTP/2 (JEP 110). It is expected that the Java 9 HttpClient will make use of new Java language features such as lambda expressions.

Many other popular HTTP frameworks and libraries are in the planning stages of implementing HTTP/2. The Apache HttpClient project plans to implement experimental and incomplete HTTP/2 support for the next HttpClient 5.0 only.

In conclusion

 

HTTP/2 is a huge step towardWeb ing the web faster and more responsive, and it has already been adopted byWeb e major web browsers. The current version of Chrome supports HTTP/2 by default, and so does the current version of Firefox. More browserWeb d other web components will follow.

While HTTP/2 completely renovates core elements of HTTP, it hasn't changed the protocol's high level syntax. This is good news for developers because it means that you should be able to support HTTP/2 without changing your application code. All you need to do is update and/or replace your proxy and server infrastructure. That said, as you adopt HTTP/2 it will likely benefit you to re-think some of your classic HTTP workarounds, such as domain sharding, resource inlining, and image sprites.

Although the Java Servlet 4.0 specification is a work in progress, you can leverage certain HTTP/2 features now by using proprietary web-container extensions or features of pre-connected HTTP/2 proxies. The HTTP/2 proxy nghttpx is one example. It supports HTTP/2 push by looking into the response-header link fields which includes the attribute rel=preload; such resources will then be pushed to the front-end client. Once again, we are only at the beginning. Many more implementations are yet to come.

The bottom line is: HTTP/2 is here, and it is here to stay. Make use of it. Make the Web faster.

More about this topic

 

Source :- This story, "HTTP/2: A jump-start for Java developers" was originally published by JavaWorld.

No comments:

Post a Comment