> draft

Don't give me an http response; I'll give you one.

Zhong Yu, 2014-07-15

In many server-side http frameworks, for every request, the framework creates a response object and passes it to the application; the application pushes response information (status, headers, body bytes) to the response object, e.g.

void handle(request, response){
    response.setHeader(...)
    response.body.write(data)
    ...

We'll call this the push style in this article.

In contrast, the pull style is such that the applications generates the response object and returns it to the framework; the framework pulls response information from the response object. The response body is a byte source instead of a sink.

Bayou Framework (http://bayou.io) adopts the pull style; the HttpHandler interface has a single abstract method

Async<HttpResponse> handle(HttpRequest)

Both HttpRequest and HttpResponse here are read-only.

Advantages of the pull style

The pull style is more friendly to consumers of responses - it's easy to inspect a response, transform it, pass it around, stored it somewhere and use/reuse it later. Consumers of responses include the framework, unit tests, libraries, and parent handlers. A parent handler may contain one or more child handlers forming a complex logic flow. See this example in the pull style

    response = cache.get(request.uri());
    if(response!=null)
        return response;

    response = handlerA.handle(request);
    if(response.statusCode()!=404)
        return response;

    response = handlerB.handle(request);
    if(response.statusCode()==200){
        response = cacheInMemory(response);
        cache.put(request.uri(), response);
    }
    return response;

These things can be achieved in the push style, but it'll be awkward, and require more efforts by the application. The problem is that, the response object in the push style does not really represent the response concept in the typical OO sense; it's more of a container, an output parameter, of the real thing; yet the real thing is nameless in the push style.

The pull style is more friendly to laziness - the consumer decides when to read the response body; if the body is not needed, no action is taken and no resource is invoked in vain. The application code usually does not need to do anything for laziness. Laziness is important for http, where a high percentage of requests are conditional requests (e.g. If-Modified-Since) to which responses are often body-less.

Another form of laziness is 206 Partial response (to a Range request), useful in resuming a download, or jumping to a random video position. This is also easier in the pull style; all it takes is that the response body supports the skip(n) operation.

APIs are more symmetric in the pull style - the response interface is very much like the request interface; and, the same request and response interfaces can be used on the client side as well as on the server side.

Another minor but charming advantage is that in the pull style the compiler guarantees that one and only one response is returned. In the push style, however, it's difficult to enforce that statically - it's easy to forget to populate the response in some code branch, as well as it's easy to populate the response twice by mistake - the problems will only surface at runtime.

The pull style is also more friendly to functional programming, if that's your preference.

Response cookies

Sometimes we want to set a response cookie in an early stage of a request-response cycle. This seems easier in the push style - since a mutable response is present from the beginning, we can add a cookie to the response at any time, as long as the response head is not "committed".

In the pull style, the response object is yet to be created, so we'll need to stage the cookie somewhere temporarily, to be incorporated into the response later. This is inconvenient.

Bayou provides a better abstraction, CookieJar, which models client cookies as a mutable map-like data structure. It gives the illusion that we are directly reading and modifying the client's cookie storage.

Response body

If the response body is based on an existing data source (for example, a file on disk, byte buffers in memory), the pull/source style is obviously the better choice.

If the response body consists of dynamic content generated by an imperative program, the push/sink style is better - but, only in the threaded/blocking mode, where the thread executing the program automatically pauses/resumes on body.write(data) when the output buffer is full/free. This advantage of the push style does not apply in the async/non-blocking mode, where write(data) must not block, therefore the same program would have to run to completion without pause, and the output buffer would have to be unbounded and accept the entire body content.

An optimization technique can be applied here in which the write(data) method simply queues the data object as is; at the end of the program, we get a list of data objects; if most of these objects are shared, for example, constant strings, or byte arrays from caches, the total memory consumed by multiple pending responses won't be too much. The list of data objects can then be serialized on demand to network bytes.

It's worth mentioning Bayou's Html Builder here - we can build an html tree in an imperative program, then flatten it to a list of CharSequences; the list is then wrapped as a byte source, as the body of a response. If most of these CharSequences are shared, particularly the big ones, (or if they have internal laziness), the amortized memory footprint of html responses would be considerably small.

If this optimization technique is not applicable in an application that also requires async mode, we'll have to break the program into smaller pieces, and arrange to run only one piece at a time. The complexity of doing this would be about the same in both push and pull styles. However, the pull style feels more natural here - the source maintains an internal state machine that advances on each read(), and each read() calculates a part of the body based on the current state.

Conclusion

All things considered, the pull style seems to be a better choice for an http framework, especially in an async/non-blocking environment. To coin a catch-phrase, don't response me, I'll response you.

Contact: bayou-io@googlegroups.com