Proposal for Native Comet Support for Browsers

by Kris ZypJanuary 17th, 2008

What if I had commit privileges on all the major browsers, the competence to add functionality in all the code bases, and wanted to add native Comet support in a form that was easily accessible to developers? What if we could use a native API that didn’t require the hacks of frame-based streaming, and didn’t face the cross-site and streaming limitations of XMLHttpRequest? Could we access Comet in a simple JavaScript API that works in a manner that servers could easily deliver messages to? I believe we could, and we could not only have easier Comet interaction, but also significantly improve the performance characteristics of both Ajax and Comet interaction in the process. I will describe a native implementation that not only provides simple techniques for Comet, but also provides some very interesting performance enhancements for general Ajax requests.

The first question is what level in the communication stack should native Comet be implemented at? HTTP is not mandated: Comet could be implemented at the TCP level or UDP could even be used. However, eliminating HTTP renders a vast number of application servers obsolete in their ability to communicate with the browser. Most web applications are built around HTTP. Requiring a different TCP or UDP protocol would negate substantial efforts to create open-connection capabilities in existing application servers, and would require significant architectural changes. A native Comet implementation should use HTTP-based Comet. With widespread investment and adoption, HTTP should be leveraged as much as possible to provide the foundation for Comet.

A second question would be what type of JavaScript interface to use. I am suggesting simply using the existing and familiar XMLHttpRequest API with some simple additions to achieve a powerful range of Comet capabilities. Obviously a new API could be used, but the existing XMLHttpRequest is widely adopted and understood. The XMLHttpRequest API provides comprehensive control and access over HTTP messaging.

Server Streamed Messaging

HTTP provides a streaming mechanism, but browser limitations and lack of message partitioning conspire to make it a shaky foundation for Comet. If all browsers properly implemented the interactive mode of XMLHttpRequest, content could be streamed to deliver asynchronous data from the server. However, IE doesn’t support this, and the browsers that do support streaming simply create an ever increasing string of content, continually devouring memory and requiring ad-hoc message partitioning. One could provide server initiated messaging by simply allowing ad hoc HTTP responses to be delivered on the TCP/IP connection without any requirements on correlated HTTP requests. However, this is clearly not allowed by the HTTP specification. Firefox has implemented support for the multipart/x-mixed-replace content type, which provides streaming with clear message partitioning without unbounded strings. While this is effective, this content type is semantically simply incorrect for many Comet usages. Often Comet is subscribing to a series of events, not resources that replace each other. If chat messages are being sent from a server, they are not intended to supersede the prior messages, but rather add to them. The multipart/x-mixed-replace content type is great for streaming web cams, but it is simply not the ideal solution for Comet. HTML 5 proposes to use server-sent events with the application/x-dom-event-stream content type to send DOM events. This approach is certainly not without merit. However, JavaScript interaction with a server requires routing all messages through a DOM element, and servers must place the data in a special DOM event formatting style. Handling these types of server-sent events would be very awkward with the XMLHttpRequest object, requiring a number of altered or new interface points.

In my last article, I pointed out the technique of encapsulating HTTP messages into content. I originally suggested the use of the multipart/digest content type, but after further thought, I believe that the message/http content type is actually more semantically correct since it directly indicates that the content will contain HTTP message(s), and this content type is also more compact because it doesn’t require boundaries. A native implementation could easily handle response streams with the XMLHttpRequest object in the same manner as Firefox handles a response with the multipart/x-mixed-replace content type. The message/http content type would not only be semantically correct, but headers can be included in each message to indicate status codes, request correlation, caching information, and more. This can also provide the means for RESTful semantics for Comet messages. The message/http content type could easily be handled by the XMLHttpRequest in the same way as Firefox handles the multipart/x-mixed-replace content type. Each new HTTP response in the stream could trigger the onreadystatechange handler and the content and response headers could easily be accessed with the standard XMLHttpRequest API.

Another advantage to encapsulating HTTP responses in a content stream is that gzip can be applied to compress the full inner HTTP responses. Normally gzip does not compress headers (since the headers negotiate the gzip), but with encapsulated HTTP responses, the outer response can negotiate and handle the gzip and the inner HTTP responses will be fully compressed (headers and content). HTTP headers are highly compressible when multiple responses are delivered in the same stream, meaning that this delivery mechanism can be highly efficient and compact on the wire. Each HTTP response may only have several bytes of overhead per message.

Client-Delivered Streaming

While there are several ways for JavaScript clients to consume server-delivered streaming data in existing browsers, there is simply no mechanism for client-delivered streaming. Once a browser sends request data to a server, it is basically impossible for JavaScript to coerce the browser to send any more data on that connection until a response is received and finished. The one exception is through HTTP pipelining, however HTTP pipelining is very inflexible because it requires responses to be received in the same order as requests. In a true asynchronous messaging system, a client should be able to deliver messages without placing constraints on the order of messages received from the server. The inability of a client to send data on an open HTTP connection is particularly problematic for Comet clients. A Comet connection may stay open for an extended period of the time, and the whole time that connection is essentially limited to one-way communication (the client is mute on that connection until the HTTP conversation has finished). Once request has been sent on an open connection, if the client needs to send additional data to the server (such as a request to subscribe to an additional resource) a new connection must be used. Creating new TCP connections is slow, and with the browser’s two connection limit, requests may be queued. Lifting the two-connection limit on browsers may seem attractive, but connections utilize valuable resources on the server, and using existing TCP connections is more efficient. By utilizing chunked encoding in HTTP requests, a native Comet implementation could also add support for client-delivered streaming. This ability could be accessed by adding a sendChunk method to the XMLHttpRequest API. Calling sendChunk instead of the normal send method would indicate a request transfer encoding of chunked, and it would send a chunk of data in the content stream and leave the content stream open for future chunks to be sent. The connection could always be terminated with the standard send method.

Once again, the message/http content type can be utilized to encapsulate multiple HTTP messages in this chunked content stream. This provides a standards-based way to partition messages in the client sent content stream, and follows the well understood HTTP protocol for sending messages. This capability can be achieved by manually constructing HTTP request messages and calling sendChunk. However, to simplify usage, an addHttpChunk method could be added to the XMLHttpRequest API. The addHttpChunk would take a single argument: an inner XMLHttpRequest object to be delivered inside the outer XMLHttpRequest object’s content stream. When a user calls the XMLHttpRequest.addHTTPChunk method, the provided inner XMLHttpRequest object would be associated with the outer XMLHttpRequest object. When a the send method was called on the inner XMLHttpRequest object, the HTTP message would be delivered as a data chunk in the containing message/http content stream. Multiple messages (HTTP requests) could then be delivered in a single request stream and a single connection. Responses could be correlated to requests by the Content-Location header, and the onreadystatechange handler could be called for inner XMLHttpRequests when the containing HTTP response content stream received a correlated HTTP response. Below is an example of the usage:


var cometXHR = new XMLHttpRequest;
cometXHR.open('POST','comet',true);

var xhr1 = new XMLHttpRequest;
cometXHR.addHttpChunk(xhr1);
xhr1.onreadystatechange=function(){...}
xhr.open('GET','/ticker1',true);
xhr.send();

var xhr2 = new XMLHttpRequest;
cometXHR.addHttpChunk(xhr2);
xhr2.onreadystatechange=function(){...}
xhr.open('GET','/ticker2',true);
xhr.send();

This would asynchronously send and receive embedded HTTP messages utilizing a single HTTP connection.

The ability to stream HTTP requests over a single connection does not solely benefit Comet applications. Standard pull-based Ajax application can benefit from this capability as well. In existing browsers, when multiple Ajax calls are made, each request must either create a new connection (unless there is an unused keep-alive connection available) or wait until a response is received on an existing connection. With request streaming, successive Ajax requests can be continuously delivered to the server without unnecessary delay and without creating new connections.

By combining the client-delivered streaming capabilities with the handling and partitioning of server-delivered streaming, a client could truly have asynchronous two-way communication with a server. A client could asynchronously send messages and receive messages from the server on the same HTTP/TCP connection.

Resource Subscription

A very simple resource subscription technique could also be included in the XMLHttpRequest API. Without any API additions, one could use my proposed When-Modified-After header. A client could trigger resource retrieval and subscription by simply including a When-Modified-After header with a beginning-of-the-epoch time (Wed, 1 Jan 1970 00:00:00 GMT) and indicating multipart response parsing (with the multipart property). The server could then return a response with a message/http content stream, and then immediately return the resource in a nested HTTP response in the entity content stream. The server could then keep the connection open and send any further modifications in the entity content stream as additional HTTP responses. Resource update HTTP responses could be a 200 with the entire new resource representation, 206 with an updated range, or 410 to indicate the resource was deleted. The XMLHttpRequest object could then call the onreadystatechange handler with each resource update, just as when the first HTTP response arrived. The XMLHttpRequest API could be augmented with a subscribe property, which when set to true could automatically include the When-Modified-After header. Developers would then have a very simple method for sending subscription requests using a familiar API.

Cached Resource until Updated

With a native modification to the browser in combination with the When-Modified-After header, an implementation could provide an interesting alternate behavior, which I believe is much more powerful than the simple approach above. When a XMLHttpRequest is made with the subscribe property set to true, and a cached copy of the resource is available, the browser could immediately return the cached copy and send a request to the server with the When-Modified-After header set to the date of the cached copy of the resource. The client would then immediately have access to the resource data without even needing to wait for a request and response. If the server has a newer copy of the resource than the cached copy, the resource would be immediately delivered to the client and the client would receive the update as soon as the HTTP response was received by the client. The fact that the client chose to subscribe to the resource indicates that the client code is prepared to handle updates. This behavior takes advantage of this fact to allow immediate access to cached data while waiting for a server copy of the data, which is treated as an update. Furthermore, if the cached copy is fresh, the server does not need to send a 304 response, as it can simply block the connection and wait for a future modification. In the meantime the client can happily continue with the cached fresh copy of the resource data, while listening for any updates from the server. This can provide a substantial improvement in performance of incrementally rendering data in applications, especially in the face of poor or broken network connections. This provides a capability reminiscent of that suggested by the stale-while-revalidated and stale-if-error headers used by Yahoo. A client can act like an offline client, immediately showing cached resource data until network response demonstrates otherwise. This provides a much more rapid and seamless approach to handle network uncertainty than waiting for a network problem before resorting to cached/offline resource handling.

Bi-Directional Asynchronous HTTP Connection

Flexible Low or High Level Interaction

This API provides flexibility and means for developers to interact with Comet at various different levels. Developers could easily use this API to create their own low-level-customized high-speed Comet protocol since all the capabilities of HTTP can be controlled and accessed: method, request and response headers, request and response entities, and status codes. Bayeux could easily be implemented with this API. Developers could use the sendChunk method to create their own custom stream of messages, and directly parse responseText on incremental updates to do their own message partitioning. The API also allows developers to work at a higher level, simply issuing resource subscription requests with the subscribe property and delivering and receiving messages as HTTP messages. An important characteristic of HTTP/REST that is preserved by this API is that it does not force any inter-connection statefulness. By using the When-Modified-After header, subscriptions can be issued without requiring the server to maintain a session to remember subscriptions. An HTTP user agent should not force stateful interconnection session handling. Subscriptions are all local to an HTTP connection. When the connection is broken, a server may discard any subscription information related to that connection. This is a very important concept for utilizing shared-nothing (or at least shared-little) principles to easily create simple scalable servers. Developers are still free to create Comet communications that utilize stateful sessions when desired.

The When-Modified-After header is also advantageous in unreliable connection situations. If an application temporarily goes offline or if an underlying TCP connection is broken, the When-Modified-After header allows a client to easily resume a subscription from the last time of an update. To resume a subscription, the client simply sends a request with the When-Modified-After header set to the time of the last update. The server can simply compare the resource date with the When-Modified-After header date to determine if a resource needs to be sent. It does not need to keep individual event queues for each client in order to resume a connection.

Compatibility with Existing Capabilities

Even if my fantasy world of having commit privileges to all the browser code bases really did exist, adoption does not happen overnight. We would still be faced with dealing with existing browser implementations. However, this proposal for native Comet implementation using HTTP standards does not exclude current browsers from participation in the suggested Comet communication. Current browsers would not realize the performance benefits of client-delivered streaming, consistent server-delivered streaming handling, and cached until updated responses. However, all of the suggested communication techniques including message/http requests and responses and the When-Modified-After header are fully accessible with the existing capabilities of XMLHttpRequest. This means servers could easily deliver Comet communication without requiring separate protocols for newer implementations and existing implementations. Comet communication can remain consistent, with native implementations driving simple APIs and enjoying major performance benefits. Existing implementations can gracefully degrade, using JavaScript to emulate APIs, manually batching requests, and aborting and recreating HTTP connections in order to send new subscription requests and resume connections in browsers that do not support XMLHttpRequest interactive mode (IE). I am hoping to actually create a JavaScript implementation of this approach and make it available.

A native implementation that augmented these XMLHttpRequest capabilities would also be backwards compatible in behavior since all the new behaviors require opt-in (through sendChunk, addHTTPChunk, and subscribe).

Conclusion

With only a few simple XMLHttpRequest API additions, Comet communication could be realized with a native implementation that supports fast and efficient standards-based two-way asynchronous communication, with true server-delivered HTTP streaming and simple subscription requests, with the added benefit of client-delivered streaming and cached resource until updated capability. Developers could create Comet applications with a standard API without hacks. Communication could be standards-based, allowing current servers to easily implement the necessary features to handle communication.

Notes

To understand the When-Modified-After header, it is worth comparing it to the If-Modified-Since header. The behavior is similar. If the server’s resource is newer than the header’s date, the server immediately returns the server’s copy. When the resource matches or is older than the header’s date, the If-Modified-Since header says that the server should return a 304 Not Modified, whereas the When-Modified-After header says the server should wait until a modification occurs. The When-Modified-After header also suggests that when possible (when the client declares that it Accepts message/http with content negotiation) the connection should stay open indefinitely so as to receive multiple update responses.

15 Responses to “Proposal for Native Comet Support for Browsers”

  1. Boris Bokowski Says:

    Maybe I am just naive, but wouldn’t it be a huge step forward if we could at least get the major browsers to implement JSONRequest as proposed by Douglas Crockford?

    When-Modified-After sounds great, but you forgot to explain how this relates to the maximum of two persistent connections per browser as implemented by Firefox and IE. Would When-Modified-After connections be exempt?

  2. Kris Zyp Says:

    Boris,
    The When-Modified-After capability relates to connections because it creates a possibly lengthy time span between a (subscription) request and the response. Also multiple responses are possible from the server (for multiple update events). Most of the article is about how to send and receive asynchronous messages across a single HTTP connection so that a multiple requests can be sent and you can wait for multiple responses without using multiple connections. The ideas in this article make it possible to do multiple long-standing request/responses on a single connection.

  3. Kris Zyp Says:

    Also, in regards to JSONRequest, I would love to see it implemented (and I thought I heard it was coming in FF3), for it’s cross site data access capabilities. However, JSONRequest does very little for Comet, other than to make it easier to engage in cross site Comet. The JSONRequest proposal includes a section about duplex, but if you look at it, it is basically the same technique as long-polling with XHR.

  4. Nick Vidal Says:

    How about native XMPP support?

    Check out SamePlace for some cool applications that could spring from this. SamePlace is an add-on for the Firefox browser that adds XMPP support.

  5. Brad Neuberg Says:

    So who’s going to add this to Google Gears first? http://gears.google.com

    Best,
    Brad

  6. Tom Davis Says:

    Rather than send a conventional XHR object as an argument to a new XHR method addHttpChunk, which has the awkward properties of being a lowlevel term which many programmers won’t understand, and of requiring XHR objects to check if they happen to have been sent as such an argument, and of providing for a very buggy situation if someone calls xhr1.addHttpChunk( xhr2 ), it might be better and cleaner to simply add a new persistent connection object, and create a new XHR property ‘tube’ which could be set to the persistent connection, and would use that on a send. So you would have something like

    var comet = new persistentConnection( ‘http://example.com’ );

    var xhr1 = new XMLHttpRequest();
    xhr1.tube = comet;

    xhr1.send();

  7. Kris Zyp Says:

    Tom,
    That is not a bad idea. I actually went back and forth a few times when writing the article about whether to go with a syntax more similar to what you are suggesting. I preferred keeping a single more expanded XHR construct, where you could potentially be able to send your own defined chunks of data, and then one of the type of chunks you can send is browser built HTTP messages. However, it is not a strong preference, your syntax has merit.

  8. GregWilkins Says:

    Kris,

    I think your proposals have some merit as reasonable HTTP extensions, however I think they fall short of being a solution to the issue of lack of comet support in browsers.

    Your proposals are very much directed at how to improve packaging and transport of data over HTTP and how to move some common semantics currently implemented in a protocol like Bayeux back into the space of URLs and HTTP headers.

    While these might be valuable additions to the transport space, I don’t think they address the really big issues that browser support would help us address. The big issue of course being the two connection limit and how there is zero support to share these connections between widgets, libraries, frames, tabs or windows. Without browser support for sharing connections, improvements in transport are only going to result in cleaner comet code, but not more functional code. It will only reduce the “hackiness” feeling of comet (which is not a bad thing in of itself).

    However, in two aspects I see your proposals as steps in the wrong direction. By maintaining a request/response paradigm with access to HTTP headers, the task of sharing connections is made more difficult. Only by abstracting our comet frameworks away from transport detail will we be able to effectively share the scarce resources of connections.

    Similarly your proposal to use URL space to identify resources and When-Modified-After header are not good steps to assist with this abstract. These proposals make perfect sense in a pure HTTP view (specially a RESTful http view). But as much as I would like the R in URL to have real application meaning, I believe that the need to support sharing connections means that we have to step away from URL space and header control of transport.

    So if you did have commit access to all the browsers, then I would advocate spending your time implementing: secure inter-frame messaging; accessible connection meta data and manipulations; control over pipelining of requests over connections.

    I see the real issue that we need support from browsers

  9. GregWilkins Says:

    Nick Vidal,
    XMPP is no silver bullet. It’s HTTP transport (BOSH) is just the same long polling that many comet frameworks are based on. It still gives not support to solve the 2 connection issue and sharing of connections between widgets, frameworks, frames, tabs and windows.

    The thing I do like about XMPP is that it is a step towards messaging - but then XMPP does have a lot of chat baggage in it to be a general protocol. BOSH is almost generic enough except that it’s address space looks to be targeted at proxy servers. So while I think there is great potential to learn/collaborate/integrate from/with/to XMPP(BOSH), they are not solutions to the big problems we have.

  10. Kris Zyp Says:

    Greg,
    I think you are right, this proposal does not adequately address the problems of connections utilization from different frames, tabs, and windows. However, I think this proposal does provide the correct foundation for addressing the problem. Solving the problem requires either increasing connection limits (which I think is a bad solution, increasing connections is slow and even harder on servers), or finding a way to deliver multiple bi-directional channels over a single (or two) HTTP connections. That is what this proposal is all about. The shortcoming of inability to share connections across tabs/windows should be addressed by being able to authoritatively tunnel all request to given host through a single connection. The proposal provides the means of transport, just not the right API. The container connections need to be able to automatically tunnel other XHR requests without their manual attachment, which is certainly a reasonable API alteration.
    I don’t disagree that creating Comet abstractions is useful, but I don’t think the browser should limit Comet communication by creating unnecessarily high-level Comet abstractions. If the browser provides the framework to bi-directionally stream and handle messages over HTTP connections as the proposal describes, frameworks can create abstractions with different protocols on top of that. The proposal provides sugar for messages that are in the form of HTTP requests and responses to facilitate a well understood form messaging that is highly interoperable. The normal impedance mismatch Comet event dispatching and standard HTTP request/response is due to the limitation of one-to-one request/responses in naked HTTP conversations, which does not fit well with the ad-hoc multi-to-multi messaging in Comet. Once again, this proposal addresses this concern by allowing multiple requests and multiple responses over a single connection with correlation through the Content-Location header which frees us to have multi-to-multi messaging. Once past this issue, the actual semantics and meta-data capabilities of HTTP itself are not problematic for Comet, that are actually very well suited for Comet messaging.
    At the foundational level, this proposal is really about the same things you are advocating: connection manipulation and pipelining control, and doing so over an HTTP connection that will continue to work in firewalled environments.
    And as far as secure inter-frame messaging, I agree, although I think it can be done orthogonally to Comet if the connections sharing problem can be addressed, and a little beyond the scope of an XHR addition, but valuable nonetheless.

  11. Comet Daily » Blog Archive » Comet and HTTP Pipelining Says:

    [...] I had suggested a possible technique for duplex communication in HTTP based on request content streaming in XHR [...]

  12. Comet Daily » Blog Archive » Colliding Comets: Battle of the Bayeux Part 6 Says:

    [...] the entire point will likely be moot as browser vendors get wise to Comet and provide us with ways around the 2-connection limit at an HTTP level. When that happens, multi-frame concerns are just legacy features. The point here isn’t that [...]

  13. Stu Says:

    Good article. Though I think application/http might be the more appropriate media type to use. I think MS’ Astoria labs is using a combination of multipart/mixed and application/http to batch up requests, for example.

    Greg:
    “Only by abstracting our comet frameworks away from transport detail will we be able to effectively share the scarce resources of connections.”

    If your goal is to place JavaScript at the center of your network architecture, then this might make sense, but I think this falls into the same trap that SOAP fell into when it placed XML at the center of its network architecture. Transport independence is a bug, not a feature. Doing Internet-scale eventing is not going to be easy without taking the lessons of HTTP to heart.

    “But as much as I would like the R in URL to have real application meaning, I believe that the need to support sharing connections means that we have to step away from URL space and header control of transport.”

    To me, this basically basically we need to give up the foundation of the Web (URLs) because we need to workaround a missing feature in HTTP or browsers. It’s a dangerous path, and while it may have localized, smaller-scale successes, I’m doubt it will bring about the sort of architecture that’s healthy for the Web’s evolution into an event-driven architecture. It lacks social scalability that we get from URLs and linking.

    The more Web-aligned goal, in my opinion, would be be to enhance HTTP with WATCH/NOTIFY semantics, as has been discussed (for example) in Rohit Khare’s ARRESTED thesis. And, barring that taking forever to get standardized, we can find ways to approximate those semantics with GET/POST and multiparts, like what Kris has shown here. I don’t think connection sharing and developer conveniance is enough of a reason to veer away from such as successful architecture.

  14. Kris Zyp Says:

    Stu, thanks for the comment. Indeed you are correct, it looks like application/http is the most correct content type. Thanks for the info!

  15. Comet Daily » Blog Archive » ARREST: Extending REST for Subscriptions Says:

    [...] the comments of my proposal for native Comet support in browsers, Stu Charlton pointed me to an excellent paper on extending [...]


Copyright 2015 Comet Daily, LLC. All Rights Reserved