HTML5 知识库

html5 server push

   阅读:4140次   评论:0条   更新时间:2010-09-19    

http://today.java.net/article/2010/03/31/html5-server-push-technologies-part-1
http://today.java.net/article/2010/04/26/html5-server-push-technologies-part-2

The upcoming HTML5 specification includes a lot of powerful and exiting features which turn web browsers into a fully capable rich internet application (RIA) client platform. This article takes a deeper look into two new HTML5 communication standards, Server-Sent Events and WebSockets. These new standards have the potential to become the dominant Server-push technologies for helping developers to write real-time web applications.

1. The evolution of the web
Since its beginning in the 1990s, the World Wide Web has grown rapidly and became more and more dynamic. While the first generation of web pages were static documents, later generations of web sites have been enriched by dynamic elements, followed ultimately by the development of highly-interactive browser-based rich internet applications.

The Hypertext Transfer Protocol (HTTP) was designed in the early days of the internet to transport information entities such as web pages or multimedia files between clients and servers. HTTP became a fundamental part of the World Wide Web initiative. The design of the HTTP protocol has been driven by the idea of a distributed, collaborative, hypermedia information system "to give universal access to a large universe of documents." The main design goal of the HTTP protocol was to minimize latency and network communication on the one hand, and to maximize scalability and independence of the components on the other hand.

The HTTP protocol implements a strict request-response model, which means that the client sends a HTTP request and waits until the HTTP response is received. The protocol has no support wherein the server initiates interaction with the client.

Listing 1. HTTP request on the wire

GET /html/rfc2616.html HTTP/1.1
Host: tools.ietf.org
User-Agent: xLightweb/2.11.3
For instance, a browser or other user agent will request a specific web page by performing an HTTP GET request such as shown in Listing 1. The server returns the requested information entity by a HTTP response such as shown in Listing 2. The response consists of the HTTP response header, and the HTTP response body, which are separated by a blank line. The GET request, in contrast, includes a HTTP header only.

Listing 2. HTTP response on the wire

HTTP/1.1 200 OK
Server: Apache/2.2.14 (Debian)
Date: Tue, 09 Feb 2010 05:00:01 GMT
Content-Length: 510825
Content-Type: text/html; charset=UTF-8
Last-Modified: Fri, 25 Dec 2009 05:49:54 GMT
ETag: "302e72-7cb69-47b871ff82480"
Accept-Ranges: bytes

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="en" xml:lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=us-ascii" />
    <meta name="robots" content="index,follow" />
    <meta name="creator" content="rfcmarkup version 1.53" />
    <link rel="icon" href="/images/rfc.png" type="image/png" />
    <link rel="shortcut icon" href="/images/rfc.png" type="image/png" />
    <title>RFC 2616 Hypertext Transfer Protocol -- HTTP/1.1</title> 
 
[...]

</small></small></span>
</body></html>
To create more interactive web applications, the AJAX approach has been established as a popular solution for dynamically pulling data requests from the server. By using AJAX the browser application performs HTTP requests based on the XMLHttpRequest API. The XMLHttpRequest API enables performing the HTTP request in the background in an asynchronous way without blocking the user interface. AJAX does not define new kinds of HTTP requests or anything else. It just performs a HTTP request in the background.

In the case of a Web mail application, for instance, the web application could periodically perform an "AJAX request" to ask the server if the mailbox content is changed. This polling approach causes an event latency which depends on the polling period. Increasing the polling rate reduces event latency. The downside of this is that frequent polling wastes system resources and scales poorly. Most polling calls return empty, with no new events having occurred.

While Ajax is a popular solution for dynamically pulling data requests from the server, it does nothing to help to push data to the client. Sure, a server push channel could be emulated by an AJAX polling approach as described above, but this would waste resources. Comet, also known as reverse Ajax, enhances the Ajax communication pattern by defining architecture for pushing data from the server to the client. For instance, the Comet pattern would allow pushing a 'new mail available' event from the mail server to the WebMail client immediately.

Like AJAX, Comet is build on the top of the existing HTTP protocol without modifying it. This is a little bit tricky because the HTTP protocol is not designed to send unrequested responses from the server to the client. A HTTP response always requires a previous HTTP request initiated by the client. The Comet approach breaks this limitation by maintaining long-lived HTTP connections and "faking" notifications.

In practice, two popular strategies has been established. With long-polling, the client sends a HTTP request, waiting for a server event. If an event occurs on the server-side, the server sends the response including the event data. After receiving the response containing the event data, the client will send a request again, waiting for the next event. There is always a pending request which allows the server to send a response at any time. With Http Streaming, the server keeps the response message open. In contrast to long polling the HTTP response message (body) will not be closed after sending an event to the client. If an event occurs on the server-side, the server will write this event to the open response message body. The HTTP response message body represents a unidirectional event stream to the client.

The Comet approach allows implementing a real-time web. In contrast of the beginning of the web, many of today's web applications have to receive information as soon as it's been published on the server-side. For instance a web-based chat application will be successful only if the underlying architecture supports real-time communication.

2. HTML 5
The upcoming HTML 5 extends the HTML language to better support highly interactive web applications. HTML 5 turns web browsers into a fully capable rich internet application client platform, and is a direct competitor of other client platform environments such as Adobe Flash and Microsoft Silverlight.

For instance the HTML 5 standard includes technologies such as the WebWorkers API which allows running scripts in the background independently, the WebStorage API to store structured data on the client side or the new canvas element which supports dynamic scriptable rendering of 2D bitmap images.

2.1 Server-Sent Events
HTML5 also applies the Comet communication pattern by defining Server-Sent Events (SSE), in effect standardizing Comet for all standards-compliant web browsers. The Server-Sent Events specification "defines an API for opening an HTTP connection for receiving push notifications from a server." Server-Sent Events includes the new HTML element EventSource as well as a new mime type text/event-stream which defines an event framing format.

Listing 3. Example JavaScript using the EventSource interface

<html>
   <head>
     <script type='text/javascript'>
        var source = new EventSource('Events');
        source.onmessage = function (event) {
           ev = document.getElementById('events');
           ev.innerHTML += "<br>[in] " + event.data;
        };
     </script>
  </head>
  <body>
    <div id="events"></div>
  </body>
</html>
The EventSource represents the client-side end point to receive events. The client opens an event stream by creating an EventSource, which takes an event source URL as its constructor argument. The onmessage event handler will be called each time new data is received.

In general, browsers limit the connections per server. Under some circumstances, loading multiple pages that include an EventSource from the same domain can result in each EventSource creating a dedicated connection. Often the maximum number of connections is quickly exceeded in such situations. To handle the per-server connection limitation, a shared WebWorker, which shares a single EventSource object, can be used. Furthermore, by definition the browser-specific EventSource implementation is free to reuse an existing connection if the event source absolute URL is equal to the required one. In this case, sharing connections will be managed by the browser-specific EventSource implementation.

Figure 1 shows the HTTP request which will be sent by a browser if an event stream is opened. The Accept header indicates the required format, text/event-stream. Although the new mime type text/event is defined by the Server-Sent Events spec, the specification also allows using other formats for event framing. However, a valid Server-Sent Events implementation has to support the mime type text/event-stream at minimum.


REQUEST:
GET /Events HTTP/1.1
Host: myServer:8875
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; de-DE)
        AppleWebKit/532+ (KHTML, like Gecko) Version/4.0.4
        Safari/531.21.10
Accept-Encoding: gzip, deflate
Referer: http://myServer:8875/
Accept: text/event-stream
Last-Event-Id: 6
Accept-Language: de-DE
Cache-Control: no-cache
Connection: keep-alive  



RESPONSE:
HTTP/1.1 200 OK
Server: xLightweb/2.12-HTML5Preview6
Content-Type: text/event-stream
Expires: Fri, 01 Jan 1990 00:00:00 GMT
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Connection: close

: time stream
retry: 5000

id: 7
data: Thu Mar 11 07:31:30 CET 2010

id: 8
data: Thu Mar 11 07:31:35 CET 2010

[...]

Figure 1. An example event-stream
According to the mime type text/event-stream, an event consists of one or more comment lines and/or field lines on the wire level. The event is delimited by a blank line. A comment always starts with a colon (:). Fields, on the other hand, consist of a field name and field value separated by a colon.

Figure 1 also shows an example response. To avoid caching, the response header includes cache directives which disable caching of the response. Event streams should not be cached by definition.

The example response includes 3 events. The first event includes a comment and a retry field; the second and third event includes an id field and event data. The data field holds the event data, which is the current time in the example above. The second and third event also includes an id field to track progress through the event stream. The example server application writes a new event on the wire every 5 seconds. If the EventSource receives the event, the onmmessage handler will be called.

In contrast to the second and third event, the first event will not trigger the onmmessage handler. The first event does not contain data. It includes an comment field and a retry field for reconnecting purposes. The retry field defines the reconnect time in milliseconds. If such a field is received, the EventSource will update its associated reconnection time property with the received one. The reconnect time plays an important role in improving reliability in cases where network errors arise. If the event source instance detects that the connection is dropped, the connection will be re-established automatically after a delay equal to the reconnection time.

As shown in Figure 1, the HTTP request to establish the connection can be enriched by the Last-Event-Id header. This header will be set if the EventSource's last event id property is set with a non-empty string. The EventSource's last event id property will be updated each time an event is received that contains an id, or it will be updated an empty string if no id is present. Because of this, an event stream can be re-established without repeating or missing any events. This guarantees message delivery if lastEventId handling is implemented on the server side.

Listing 4 shows an example HttpServer based on the Java HTTP library xLightweb (with HTML5 preview extension), which is maintained by the author.

Listing 4. Example Java-based server delivering text/event-stream

class ServerHandler implements IHttpRequestHandler {
  private final Timer timer = new Timer(false);
  public void onRequest(IHttpExchange exchange) throws IOException {
    String requestURI = exchange.getRequest().getRequestURI();
    if (requestURI.equals("/ServerSentEventExample")) {
      sendServerSendPage(exchange, requestURI);
    } else if (requestURI.equals("/Events")) {
      sendEventStream(exchange);
    } else {
      exchange.sendError(404);
    }
  }
  private void sendServerSendPage(IHttpExchange exchange,
          String uri) throws IOException {
    String page = "<html>\r\n " +
        " <head>\r\n" +
        "     <script type='text/javascript'>\r\n" +
        "        var source = new EventSource('Events');\r\n" +
        "        source.onmessage = function (event) {\r\n" +
        "          ev = document.getElementById('events');\r\n" +
        "          ev.innerHTML += \"<br>[in] \" + event.data;\r\n"+
        "        };\r\n" +
        "     </script>\r\n" +
        " </head>\r\n" +
        " <body>\r\n" +
        "    <div id=\"events\"></div>\r\n" +
        " </body>\r\n" +
        "</html>\r\n ";
    exchange.send(new HttpResponse(200, "text/html", page));
  }
  private void sendEventStream(final IHttpExchange exchange)
          throws IOException {
    // get the last id string
    final String idString = exchange.getRequest().getHeader(
            "Last-Event-Id", "0");
    // sending the response header
    final BodyDataSink sink = exchange.send(new
            HttpResponseHeader(200, "text/event-stream"));
    TimerTask tt = new TimerTask() {
       private int id = Integer.parseInt(idString);
       public void run() {
         try {
           Event event = new Event(new Date().toString(), ++id);
           sink.write(event.toString());
         } catch (IOException ioe) {
           cancel();
           sink.destroy();
         }
       };
    };
    Event event = new Event();
    event.setRetryMillis(5 * 1000);
    event.setComment("time stream");
    sink.write(event.toString());
    timer.schedule(tt, 3000, 3000);
  }
}
XHttpServer server = new XHttpServer(8875, new ServerHandler());
server.start();
The Server-Sent Events specification recommends sending a "keep-alive" comment event periodically, if no other data event is send. Proxy servers are known which drop an HTTP connection after a short period of inactivity. Such proxy servers close idling connections to avoid wasting connections to unresponsive HTTP servers. Sending a comment event deactivates this behaviour. Even though the EventSource will re-establish the connection automatically, sending comment events periodically avoids unnecessary reconnects.

Server-Sent Events are based on HTTP streaming. As described above, the response stays open and event data are written as they occur on the server side. Theoretically, HTTP streaming will cause trouble if network intermediaries such as HTTP proxies do not forward partial responses immediately. The current HTTP RFC (RFC 2616 Hypertext Transfer Protocol - HTTP/1.1) does not require that partial responses have to be forwarded immediately. However, a lot of popular, well-working web applications exists which are built on the HTTP Streaming approach. Furthermore, production-level intermediaries always avoid buffering large amounts of data to minimize their memory usage.

In contrast to other popular Comet protocols such as Bayeux or BOSH, Server-Sent Events support a unidirectional server-to-client channel only. The Bayeux protocol on the other side supports a bidirectional communication channel. Furthermore, Bayeux can use HTTP streaming as well as long polling. Like Bayeux, the BOSH protocol is a bidirectional protocol. BOSH is based on the long polling approach.

Although Server-Sent Events do have less functionality than Bayeux or BOSH, Server-Sent Events have the potential to be become the dominant protocol for use cases where a unidirectional server push channel is required only (which is the case in many instances). The Sever-Sent Events protocol is much simpler than Bayeux or BOSH. For instance, you are able to test the event stream by using telnet. No handshake protocols have to be implemented. Just send the HTTP GET request and get the event stream. Furthermore Server-Sent Events will be supported natively by all HTML5-compatible browsers. In contrast, Bayeux and BOSH protocol are implemented on the top of the browser language environment.

Still to come...
Part 2 of this article series will cover the WebSocket API and protocol, and present concluding remarks.

Resources
Server-Sent Events Specification - W3C Working Draft 22 December 2009
Asynchronous HTTP and Comet architectures - Gregor Roth
RESTful HTTP in practice - Gregor Roth
Best Practices for the Use of Long Polling and Streaming in Bidirectional HTTP

Gregor Roth works as a software architect at United Internet group, a leading European Internet Service Provider to which GMX, 1&1, and Web.de belong. His areas of interest include software and system architecture, enterprise architecture management, object-oriented design, distributed computing, and development methodologies.
» Login or register to post comments 18208 visits Printer-friendly version ShareThis
Related Topics >> Ajax      Web Applications      Web Services and XML      Featured Article     
Comments
Comments are listed in date ascending order (oldest first)

The upcoming HTML5 specification includes a lot of powerful and exiting features which turn web browsers into a fully capable ich internet application (RIA) client platform. Part 1 of this article series presented an overview of the history of the web, and investigated the new HTML5 Server-Sent Events communication standard.

2.2 WebSockets
The upcoming HTML5 standard also includes WebSockets. WebSockets enables establishing a bidirectional communication channel. In contrast to Server-Sent Events, the WebSocket protocol is not build on top of HTTP. However, the WebSocket protocol defines the HTTP handshake behaviour to switch an existing HTTP connection to a lower level WebSocket connection. WebSockets does not try to simulate a server push channel over HTTP. It just defines a framing protocol on top of TCP. In this way WebSockets enables two-way communication natively.

Like the Server-Sent Events specification, WebSockets specifies an API as well as a wire protocol. The WebSockets API specification includes a new HTML element, WebSocket.

Listing 5. Example JavaScript using the WebSocket interface

<html>
   <head>
     <script type='text/javascript'>
        var ws = new WebSocket('ws://localhost:8876/Channel', 'mySubprotocol.example.org');
        ws.onmessage = function (message) {
          var messages = document.getElementById('messages');
          messages.innerHTML += "<br>[in] " + message.data;
        };
       
        sendmsg = function() {
          var message = document.getElementById('message_to_send').value
          document.getElementById('message_to_send').value = ''
          ws.send(message);
          var messages = document.getElementById('messages');
          messages.innerHTML += "<br>[out] " + message;
        };
     </script>
  </head>
  <body>
     <form>
       <input type="text" id="message_to_send" name="msg"/>
       <input type="button" name="btn" id="sendMsg" value="Send" onclick="javascript:sendmsg();">
       <div id="messages"></div>
     </form>
  </body>
</html>
A WebSocket will be established by creating a new WebSocket instance. The constructor takes one or two arguments. The WebSocketURL argument specifies the URL to connect. A WebSocketURL starts with the new scheme type ws for a plain WebSocket connection or wss for secured WebSocket connection. Optionally, a second parameter protocol can be set which defines the sub-protocol to be used (over the WebSocket protocol). As with the EventSource element, an onmessage handler can be assigned to a WebSocket, which will be called each time a message is received. Data will be sent by calling the send() method.

If a new WebSocket is created, first the underlying user agent will establish an ordinary HTTP(S) connection to the host of the URL. Based on this new HTTP connection, an HTTP upgrade will be performed. The HTTP specification defines the upgrade header field to do this. The upgrade header is intended to provide a simple mechanism for transition from HTTP protocol to other, incompatible protocols. This capability of the HTTP protocol is used by the WebSocket specification to switch the newly created HTTP connection to a WebSocket connection. By adding the optional WebSocket-Protocol header, a specific sub-protocol is requested.


REQUEST:
GET /Channel HTTP/1.1
Upgrade: WebSocket
Connection: Upgrade
Host: myServer:8876
Origin: http://myServer:8876
WebSocket-Protocol: mySubprotocol.example.org


RESPONSE:
HTTP/1.1 101 Web Socket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
WebSocket-Origin: http://myServer:8876
WebSocket-Location: ws://myServer:8876/Channel
WebSocket-Protocol: mySubprotocol.example.org
Figure 2. WebSocket upgrade handshake
After receiving the response HTTP header, data will be transmitted according to the WebSocket protocol. This means at this point only WebSocket frames will be transferred over the wire. A frame can be sent at each time in each direction. The WebSocket protocol defines two types of frames: a text frame and a binary frame. Each text frame starts with a 0x00 byte and ends with a 0xFF byte. The text will be transferred UTF8-encoded between the start and the end byte. A text frame requires only 2 additional bytes for packaging purposes. Figure 3 shows a text frame for the string "GetDate" and the string "Sat Mar 13 14:00:25 CET 2010".


Text frame of "GetDate":
0x00 0x47 0x65 0x74 0x44 0x61 0x74 0x65 0xFF

Text frame of "Sat Mar 13 14:00:25 CET 2010":
0x00 0x53 0x61 0x74 0x20 0x4D 0x61 0x72 0x20 0x31
0x33 0x20 0x31 0x34 0x3A 0x30 0x30 0x3A 0x32 0x35
0x20 0x43 0x45 0x54 0x20 0x32 0x30 0x31 0x30 0xFF
Figure 3. Text frame example
Binary data can be transferred by using a binary frame. A binary frame starts with 0x80. In contrast to the text frame the binary frame does not use a terminator. The start byte of a binary frame is followed by the length bytes. The number of length bytes is given by the required bytes to encode the length. Figure 4 shows the binary frame for a small number of bytes to transfer which requires one length byte as well as a larger binary frame which requires two length bytes.


binary frame of 0x00 0x44:
0x80 0x02 0x00 0x44

binary frame of 0x30 0x31 0x32 0x33  0x34  0x35 […] 0x39 (1000 bytes):
0x80 0x87 0x68 0x30 0x31 0x32 0x33 0x34 0x35 […] 0x39
Figure 4. Binary frame example
Because JavaScript cannot operate with binary data represented as a byte array, the binary frame type is limited to be used for languages other than JavaScript. In addition the binary frame and text frame, new frames types can be introduced by future releases of the WebSocket protocol specification. WebSocket's framing is designed to support new frame types. A connection can be closed at any time. No extra end-of-connection byte or frame exists.

The overhead involved managing a WebSocket is very minimal. Comet protocols such as Bayeux and BOSH, for instance, uses some "hacks" to break the HTTP Request-Response barrier. This forces such protocols to implement a complex session and connection management. Due the fact that WebSockets is not implemented on the top of HTTP it will not run into trouble caused by HTTP protocol limitations.

On the other hand WebSockets, does almost nothing for reliability. It does not include reconnect handling or support guaranteed message delivery like Server-Sent Event does. Further more, as a non-HTTP based protocol, WebSocket cannot make use of the built-in reliability features of HTTP. For instance HTTP supports auto-retry execution strategies in case of network errors. Based on the fact that a GET method can be executed at any time without side effects, a GET method will be re-executed by browsers and modern HttpClients automatically, if a network error occurs. A GET method must not change the server-side resource state by definition and is therefore safe.

This means reliability has to be implemented in the application (sub-protocol level) when using WebSockets. The same is true for the "keep alive" message approach to avoid a proxy server dropping the connection after a small period of inactivity. Additionally, sharing a WebSocket between different pages often causes trouble. In contrast to Server-Sent Events, a WebSocket also includes an upstream channel which is difficult to share. For instance, concurrent writes and reads have to be synchronized, which is not a simple task. This general challenge also affects bidirectional Comet protocols such as Bayeux or BOSH. When using WebSockets, the per-server connection limitation has to be considered carefully.

As with the origin policy used by web browsers to restrict browser-side programming languages from contacting the server, a WebSocket server will only be contacted if the web page is loaded from the same domain. This is not true for stand-alone WebSocket clients such as shown in Listing 6. Such clients contact the WebSocket server in a direct way without same origin policy limitations.

Listing 6. Example Java Client


class MyWebSocketHandler implements IWebSocketHandler {

  public void onConnect(IWebSocketConnection wsCon) throws IOException {
               
  }

  public void onMessage(IWebSocketConnection wsCon) throws IOException {
    IWebMessage msg = wsCon.readMessage();
    System.out.println(msg.toString());
  }
           
  public void onDisconnect(IWebSocketConnection wsCon) throws IOException {
               
  }
}

       
MyWebSocketHandler hdl = new MyWebSocketHandler ();
IWebSocketConnection wsCon = httpClient.openWebSocketConnection(
    "ws://myServer:8876/WebSocketsExample",
    "mySubprotocol.example.org", hdl);
       
wsCon.writeMessage(new TextMessage("GetDate"));
// ...
Listing 7 shows an example WebSocket Server implementation. The server handler implements two interfaces: the IHttpRequestHandler handles ordinary HTTP requests and the IWebSocketHandler handles WebSocket connections. In the case of a standard HTTP request (without the upgrade request) the IHttpRequestHandler's onRequest() method will be called. If the client opens a WebSocket, the server will handle the HTTP upgrade and call the IWebSocketHandler's onConnect() method. Each time a WebSocket message is received, the IWebSocketHandler's onMessage() method is called.

Within the onConnect() method some preconditions can be checked. For instance if a required sub-protocol is not supported, the example server will return an error status. Furthermore the origin header will be checked. As is the case with the referer header, the origin header will be set by the browser automatically. The origin header is defined by the HTTP Origin Header RFC, which is in draft. In contrast to the referer header, the origin header includes the domain name of the page's source only. Each time embedded code makes a request, the browser adds the origin header which contains the origin page's domain. Web Servers can block requests that send invalid origin headers.

Listing 7. Example Java WebSocket Server


class ServerHandler implements IHttpRequestHandler, IWebSocketHandler {
           
           
  // IHttpRequestHandler method
  public void onRequest(IHttpExchange exchange) throws IOException {
  String requestURI = exchange.getRequest().getRequestURI();
               
  if (requestURI.equals("/WebSocketsExample")) {
    sendWebSocketPage(exchange, requestURI);
                   
  } else {
    exchange.sendError(404);
  }
}
           
           
  private void sendWebSocketPage(IHttpExchange exchange, String uri)
          throws IOException {
    String page = "<html>\r\n " +
                  "  <head>\r\n" +
                  "     <script type='text/javascript'>\r\n" +
                  "        var ws = new WebSocket('ws://" +
                           exchange.getRequest().getHost() + "/Channel',
                           'mySubprotocol.example.org');\r\n" +
                  "        ws.onmessage = function (message) {\r\n" +
                  "          var messages = document.getElementById('messages');\r\n" +
                  "          messages.innerHTML += \"<br>[in] \" +
                             message.data;\r\n"+
                  "        };\r\n" +
                  "        \r\n" +
                  "        sendmsg = function() {\r\n" +
                  "          var message = document.getElementById
                             ('message_to_send').value\r\n" +
                  "          document.getElementById('message_to_send').value = ''\r\n" +
                  "          ws.send(message);\r\n" +
                  "          var messages = document.getElementById('messages');\r\n" +
                  "          messages.innerHTML += \"<br>[out] \" + message;\r\n"+
                  "        };\r\n" +
                  "     </script>\r\n" +
                  "  </head>\r\n" +
                  "  <body>\r\n" +
                  "     <form>\r\n" +
                  "       <input type=\"text\" id=\"message_to_send\"
                          name=\"msg\"/>\r\n" +
                  "       <input type=\"button\" name=\"btn\" id=\"sendMsg\"
                          value=\"Send\" onclick=\"javascript:sendmsg();\">\r\n" +
                  "       <div id=\"messages\"></div>\r\n" +
                  "     </form>\r\n" +
                  "  </body>\r\n" +
                  "</html>\r\n ";
               
    exchange.send(new HttpResponse(200, "text/html", page));
  }

           
           
  // IWebSocketHandler method
  public void onConnect(IWebSocketConnection webStream) throws IOException, BadMessageException {
    IHttpRequestHeader header = webStream.getUpgradeRequestHeader();

    // check origin header
    String origin = header.getHeader("Origin");
    if (!isAllowed(origin)) {
      throw new BadMessageException(403);
    }
               
    // check the subprotocol 
    String subprotocol = header.getHeader("WebSocket-Protocol", "");
    if (!subprotocol.equalsIgnoreCase("mySubprotocol.example.org")) {
      throw new BadMessageException(403);
    }
  }

  private boolean isAllowed(String origin) {
    // check the origin
    // ...
    return true;
  }
           
           
  // IWebSocketHandler
  public void onMessage(IWebSocketConnection webStream) throws IOException {
    WebSocketMessage msg = webStream.readMessage();
    if (msg.toString().equalsIgnoreCase("GetDate")) {
      webStream.writeMessage(new TextMessage(new Date().toString()));
    } else {
      webStream.writeMessage(new TextMessage(
              "unknown command (supported: GetDate)"));
    }
  }
           
  // IWebSocketHandler
  public void onDisconnect(IWebSocketConnection webStream)
          throws IOException {  }

}
       
XHttpServer server = new XHttpServer(8876, new ServerHandler());
server.start();class ServerHandler implements IHttpRequestHandler,
        IWebSocketHandler {
           
           
  // IHttpRequestHandler method
  public void onRequest(IHttpExchange exchange) throws IOException {
  String requestURI = exchange.getRequest().getRequestURI();
               
  if (requestURI.equals("/WebSocketsExample")) {
    sendWebSocketPage(exchange, requestURI);
                   
  } else {
    exchange.sendError(404);
  }
}
           
           
  private void sendWebSocketPage(IHttpExchange exchange, String uri) throws
          IOException {
    String page = "<html>\r\n " +
                  "  <head>\r\n" +
                  "     <script type='text/javascript'>\r\n" +
                  "        var ws = new WebSocket('ws://" +
                           exchange.getRequest().getHost() + "/Channel',
                           'mySubprotocol.example.org');\r\n" +
                  "        ws.onmessage = function (message) {\r\n" +
                  "          var messages = document.getElementById('messages');\r\n" +
                  "          messages.innerHTML += \"<br>[in] \" +
                             message.data;\r\n"+
                  "        };\r\n" +
                  "        \r\n" +
                  "        sendmsg = function() {\r\n" +
                  "          var message = document.getElementById(
                                   'message_to_send').value\r\n" +
                  "          document.getElementById('message_to_send').value = ''\r\n" +
                  "          ws.send(message);\r\n" +
                  "          var messages = document.getElementById('messages');\r\n" +
                  "          messages.innerHTML += \"<br>[out] \" + message;\r\n"+
                  "        };\r\n" +
                  "     </script>\r\n" +
                  "  </head>\r\n" +
                  "  <body>\r\n" +
                  "     <form>\r\n" +
                  "       <input type=\"text\" id=\"message_to_send\"
                          name=\"msg\"/>\r\n" +
                  "       <input type=\"button\" name=\"btn\" id=\"sendMsg\"
                          value=\"Send\" onclick=\"javascript:sendmsg();\">\r\n" +
                  "       <div id=\"messages\"></div>\r\n" +
                  "     </form>\r\n" +
                  "  </body>\r\n" +
                  "</html>\r\n ";
               
    exchange.send(new HttpResponse(200, "text/html", page));
  }

           
           
  // IWebSocketHandler method
  public void onConnect(IWebSocketConnection webStream) throws IOException, BadMessageException {
    IHttpRequestHeader header = webStream.getUpgradeRequestHeader();

    // check origin header
    String origin = header.getHeader("Origin");
    if (!isAllowed(origin)) {
      throw new BadMessageException(403);
    }
               
    // check the subprotocol 
    String subprotocol = header.getHeader("WebSocket-Protocol", "");
    if (!subprotocol.equalsIgnoreCase("mySubprotocol.example.org")) {
      throw new BadMessageException(403);
    }
  }

  private boolean isAllowed(String origin) {
    // check the origin
    // ...
    return true;
  }
           
           
  // IWebSocketHandler
  public void onMessage(IWebSocketConnection webStream) throws IOException {
    WebSocketMessage msg = webStream.readMessage();
    if (msg.toString().equalsIgnoreCase("GetDate")) {
      webStream.writeMessage(new TextMessage(new Date().toString()));
    } else {
      webStream.writeMessage(new TextMessage("unknown command (supported: GetDate)"));
    }
  }
           
  // IWebSocketHandler
  public void onDisconnect(IWebSocketConnection webStream) throws IOException {  }

}
       
XHttpServer server = new XHttpServer(8876, new ServerHandler());
server.start();
As shown in Listing 7, the origin header is checked against an internal whitelist to reject unwanted requests. This technique avoids the situation where an attacker copies a java script fragment from a publicly available page and embeds this code fragment into his page. In this case the browser would set the origin header with the domain of the attacker’s page and the upgrade request could be rejected. This technique helps to defend against Cross-Site Request Forgery attacks. The origin header specification is independent of the WebSocket protocol specification. However, the WebSocket protocol defines a WebSocket-Origin header which has to be included in the WebSocket upgrade response.

Due the fact that a WebSocket connection will be established over an HTTP connection, the WebSocket protocol also works with HTTP proxy servers. When using a visible proxy server, the browser always communicates with the proxy server, which forwards the HTTP requests and responses. If the browser is configured to use an HTTP proxy and a WebSocket is opened, first the browser opens a tunnel to the proxy server. By sending an HTTP/1.1 connect request, as shown in Figure 5, the browser asks the HTTP proxy to make a TCP connection to a dedicated (WebSocket) server. Once this connection has been established, the role of the HTTP proxy is “downsized” to act as a simple TCP proxy to the WebSocket server. Using this proxied connection, the browser sends the WebSocket upgrade request to WebSocket server.


1. REQUEST:
CONNECT myServer:8876 HTTP/1.1
Host: myServer:8876
User-Agent: xLightweb/2.12-HTML5Preview6
Proxy-Connection: keep-alive


1. RESPONSE:
HTTP/1.1 200 Connection established
Proxy-agent: myProxy


2. REQUEST:
GET /Channel HTTP/1.1
Upgrade: WebSocket
Connection: Upgrade
Host: myServer:8876
Origin: http://myServer:8876
WebSocket-Protocol: mySubprotocol.example.org


2. RESPONSE:
HTTP/1.1 101 Web Socket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
WebSocket-Origin: http://myServer:8876
WebSocket-Location: ws://myServer:8876/Channel
WebSocket-Protocol: mySubprotocol.example.org
Figure 5. WebSocket upgrade handshake based on a tunnel
Even though a browser does not explicitly configure an HTTP proxy, transparent HTTP proxies can be passed through invisibly by calling the WebSocket server. This depends on the current network infrastructure. Under some circumstances, such transparent HTTP proxies cause trouble for WebSockets. The Connection and Upgrade header are hop-by-hop headers by definition. The HTTP specification says that hop-by-hop headers have to be removed by an intermediary if a request is forwarded to the next hop. In the case of the WebSocket upgrade request, a transparent HTTP proxy will remove the Connection: upgrade header, which will result in the WebSocket server receiving a corrupt WebSocket upgrade request. Today, most HTTP proxies are not familiar with the WebSocket protocol.

Using secured WebSockets can avoid this effect. In creating a secured WebSocket connection, the browser opens an SSL connection to the WebSocket server. In this case intermediaries will not be able to interpret or modify data.

Conclusion
With WebSockets, writing highly interactive real-time web applications becomes a simple task. The WebSocket API is very easy to understand and to use. The underlying WebSocket protocol is high efficient: there is a minimal overhead involved in managing a WebSocket. Due the fact that the WebSocket protocol runs on the top of TCP, the WebSocket protocol does not have to deal with "hacks" as do popular Comet protocols like Bayeux or BOSH. Simulating a bidirectional channel over HTTP leads to complex and less efficient protocols. Especially if only a small amount of data will be transferred, such as tiny notification events, the overhead of the classic Comet protocols is very high. This is not true for WebSockets.

Furthermore, WebSockets fit well into the existing Web infrastructure. For instance WebSockets, use the same ports that standard HTTP connections use. To establish a new WebSocket connection, the WebSocket protocol makes use of the connection management capabilities of the HTTP protocol. WebSockets support highly efficient bi-directional communication by using the existing Web infrastructure without adding new requirements or components.

On the other hand, WebSockets do less for reliability. This has to be done on the application (sub-protocol) level. In contrast to Server-Sent events, the WebSocket protocol does not include reconnect handling or guarantee message delivery. The current WebSocket protocol represents a low-level communication channel only.

In contrast to WebSockets, the Server-Sent Events protocol includes powerful features to reconnect and synchronize messages. High reliability is a built-in feature of Server-Sent Events. Furthermore, as with WebSockets, the overhead involved in managing a Server-Sent Event stream is very low. However, Server-Sent Events support a unidirectional server push channel only. By creating a Server-Sent Event, a server-to-client server-push event stream will be opened. Often Server-Sent Events will satisfy the requirements of a server-push situation, but this depends on the concrete use cases.

What do WebSockets and Server-Sent Events mean for popular Comet protocols such as Bayeux and BOSH? The HTML5 communication standards have the potential to substitute for the classic Comet protocols and become the dominant server-push technology, at least for new applications. On the other side, for instance, the cometd community started implementing cometd 2.0 which will support the WebSockets protocol as a new transport type. cometd is the most popular Bayeux implementation.

Resources
Server-Sent Events Specification - W3C Working Draft 22 December 2009
The WebSocket API - W3C Editor's Draft 10 January 2010
The WebSocket Protocol - RFC Draft 75
Best Practices for the Use of Long Polling and Streaming in Bidirectional HTTP
How to improve Websocket - Greg Wilkins
The HTTP Origin Header - RFC Draft 00
How HTML5 Web Sockets Interact With Proxy Servers - Peter Lubbers
Asynchronous HTTP and Comet architectures - Gregor Roth
RESTful HTTP in practice - Gregor Roth

Gregor Roth works as a software architect at United Internet group, a leading European Internet Service Provider to which GMX, 1&1, and Web.de belong. His areas of interest include software and system architecture, enterprise architecture management, object-oriented design, distributed computing, and development methodologies.
» Login or register to post comments 14610 visits Printer-friendly version ShareThis
Related Topics >> Web Applications      Featured Article     
Comments
Comments are listed in date ascending order (oldest first)
Please consider that the
Submitted by grro on Mon, 2010-04-26 23:59.
Please consider that the WebSocket protocol has been updated. The article is based on draft75 (February 2010). Meanwhile draft 76 (April 2010) has been released (http://www.whatwg.org/specs/web-socket-protocol/).

The updated draft does not longer allow binary frames. Further more a close frame has been introduced which consist of a 0xFF byte followed by a 0x00 byte.

Additionally, a header prefix Sec- has been introduced for the WebSocket protocol headers. For instance the header WebSocket-Protocol has been replaced by the header Sec-WebSocket-Protocol.
Further more two new request header Sec-WebSocket-Key1 and Sec-WebSocket-Key2 have been introduced as well as an eight byte field which has to be sent after the request header fields. This makes the WebSocket protocol more secure against attackers.

Thanks to Simon for his feedback
Login or register to post comments
评论 共 0 条 请登录后发表评论

发表评论

您还没有登录,请您登录后再发表评论

Global site tag (gtag.js) - Google Analytics