`
waveeee
  • 浏览: 52451 次
  • 来自: 上海
社区版块
存档分类
最新评论

html5 server push

阅读更多
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
分享到:
评论

相关推荐

    java中的Servlet实现Server_Push技术的聊天室!

    本文将深入探讨如何利用Java中的Servlet实现Server Push技术来创建一个聊天室应用。 ### Servlet与Server Push技术 Servlet是Java平台上的服务器端应用程序接口,它主要用于扩展应用程序服务器的能力,处理来自...

    serverpush聊天室

    "ServerPush聊天室"是一种基于DWR(Direct Web Remoting)技术实现的实时通信系统,主要特点是能够实现实时的点对点聊天功能。在这个系统中,服务器主动将信息推送至客户端,而不是传统的客户端发起请求获取数据的...

    Server Push入门介绍

    ### Server Push 技术入门详解 #### 一、Server Push 技术概述 **Server Push**,即服务器推送技术,是一种让服务器能够主动向客户端发送数据的技术。与传统的客户端发起请求(Client Pull)的方式不同,Server ...

    服务器推送技术Server Push详解

    ### 服务器推送技术(Server Push)详解 #### 一、引言 随着互联网技术的发展,用户对实时信息的需求越来越高。传统的客户端请求模式(Client Pull)已无法满足这种需求,因此服务器推送技术(Server Push)应运而生。...

    web聊天 serverpush servlet实现

    WebSocket是HTML5引入的一种新的网络协议,它提供了全双工的通信渠道,允许服务器和客户端双向通信。WebSocket API简化了服务器推送的实现,Servlet 3.1及以上版本支持WebSocket,通过`@ServerEndpoint`注解创建...

    serverpush做的webRoom

    【标题】"serverpush做的webRoom"涉及到的技术和概念主要集中在服务器推送(Server Push)以及Web实时通信领域,尤其在构建一个基于DWR(Direct Web Remoting)的在线聊天室应用上。首先,我们来详细了解这些关键点...

    asp.net server push长连接 源码示例

    ASP.NET Server Push是一种技术,它允许服务器主动向客户端发送数据,而无需客户端发起新的请求。在传统的HTTP协议中,客户端需要先发起请求,服务器才能响应数据。然而,Server Push技术打破了这种模式,使得实时性...

    Laravel开发-laravel-http2serverpush

    本文将深入探讨 Laravel 开发中的 HTTP/2 服务器推送技术,并介绍如何在 Laravel5 中使用 `laravel-HTTP2ServerPush` 这个中间件来实现这一功能。 首先,让我们理解 HTTP/2 服务器推送的基本概念。在 HTTP/1.x 协议...

    pushstate-server:与HTML5 Pushstate一起使用的静态文件服务器

    与HTML5 Pushstate一起使用的静态文件服务器。 例如,路由/some/pushstate/route将返回index.html文件。 但是,/ /some/static/path/logo.png static/ /some/static/path/logo.png logo.png将返回logo.png静态文件...

    Android Server Push - Urban Airship

    **Android Server Push - Urban Airship** 在Android应用开发中,Server Push技术是一种常见的实时通信方式,它允许服务器主动向客户端推送消息,而无需客户端持续轮询。Urban Airship是一款流行的推送服务提供商,...

    wappush.rar_WAPpush_java push_push_wap push_wap push java

    5. **WAP Push消息格式**: WAP Push消息遵循WSP(WAP Session Protocol)和WTP(WAP Tunnel Protocol)协议,通常包含一个PUSH-IND(Push Indication)报文,用于通知设备有新的推送内容。消息中可能包括推送内容...

    python serverpush

    9. **WebSocket**:虽然本案例标签只提到了“serverpush”,但另一种常见的服务器推送技术是WebSocket,它提供全双工通信,适合双向实时通信。Django也有一些库,如Django Channels,可以帮助实现WebSocket服务。 ...

    服务器推送技术资料 server push

    服务器推送技术,也称为Server Push,是Web开发中一种创新的通信模式,旨在解决传统HTTP协议下服务器无法主动向客户端发送信息的问题。该技术源于Ajax技术的广泛应用,它改变了Web应用仅能通过用户触发请求获取数据...

    一个完整的用ajax反转 server push(服务器主动向页面推送数据)技术实现的web聊天室源码

    在Web开发中,服务器主动向客户端推送数据的技术被称为Server Push,它是实时Web应用程序的重要组成部分,尤其是在构建聊天室、实时通知系统或在线游戏等场景中。本项目是一个利用Ajax反向推送(Comet技术)实现的...

    ajax pushserver

    【标题】"Ajax PushServer" 是一个基于C#开发的实时数据推送服务示例,它利用Ajax技术实现Web端的即时通信。在传统的HTTP协议中,客户端与服务器之间的交互是基于请求-响应模型的,而Ajax Push(也称为Comet技术)...

    服务端推技术 - Server-side Push 多示例

    2. **WebSocket**:WebSocket是HTML5引入的一种新的协议,它提供了一种全双工的通信方式,允许服务器和客户端双向传输数据。WebSocket建立连接后,可以在任意时刻由任一端发起数据传输,大大降低了延迟,并且可以...

    laravel-HTTP2ServerPush:用于Laravel 5的HTTP2 SeverPush中间件

    只需通过AddHttp2ServerPush中间件路由您的请求,它将自动创建并附加为CSS,JS和Image资产实施Server Push所需的Link标头。 要求 Laravel 5 PHP 7 安装 您可以通过composer安装该软件包: $ composer require ...

    详解HTTP/2ServerPush——进一步提升页面加载速度

    ServerPush允许服务器在接收到浏览器请求主资源(如HTML页面)时,预测并主动推送相关联的资源(如CSS或JavaScript文件)给浏览器,而无需等待浏览器发出额外的请求。这一机制提高了页面加载的速度,因为它减少了...

    Node.js-history-server一个用于单页应用的HTTP服务器使用HTML5historyAPI实现

    为了解决这个问题,HTML5引入了History API,Node.js中的`history-server`项目就是利用这个API构建的一个HTTP服务器,专门服务于SPA。 **HTML5 History API** HTML5 History API是专门为单页应用设计的,它允许...

    PHP实现HTML5的Serversentevents用于实时从server推送信息到client比Websocket更简单代替AJAX

    A simple and efficient library implemented HTML5's server-sent events by PHP, is used to real-time push events from server to client, and easier than Websocket, instead of AJAX request.

Global site tag (gtag.js) - Google Analytics