Developing request handlers

The com.yahoo.jdisc.service.RequestHandler interface defines a component that is capable of acting as a handler for a Request. This document explains how to implement and deploy a custom request handler.

All request processing in a JDisc application is done by request handlers. Implementations of the RequestHandler interface are bound to one or more URI patterns, and invoked by server providers or other request handlers. Upon receiving a request, the request handler must consume its content, process it, and then return a response back to the provided ResponseHandler. To implement a request handler, either implement the RequestHandler interface directly, or subclass the more convenient AbstractRequestHandler, the JDisc container also has several utility base classes available. Please note the following:

To install a request handler in a container, use the handler element in services.xml, e.g.:

<container id="default" version="1.0">
    <handler id="my.package.MyRequestHandler" bundle="MyBundleSymbolicName">
        <binding>http://*/*</binding>
    </handler>
    <nodes>
        <node hostalias="node1" />
    </nodes>
</container>

Notice that the binding element declared within a handler element implies a server-binding. To configure a client-binding for a request handler, use the clientBinding element instead.

Handling a request

A request handler is invoked by a call to its handleRequest(Request, ResponseHandler) method. Because JDisc enforces no specific threading model, this call may be performed by any thread -- even some critical IO thread that belongs to one of the configured server providers. Because of this you should never perform much work in the body of the handleRequest(Request, ResponseHandler) method. Instead, you should pass the necessary context to some known thread pool. The ThreadedRequestHandler provides an efficient implementation of such handover. Once invoked, a request handler must create and return a ContentChannel into which the caller can write the request's payload.

In no specific order, the request handler must now

Reading from a ContentChannel

The ContentChannel provides an asynchronous interface for ByteBuffer handover -- where handover implies that the caller will never reuse the buffer. The buffer may safely be stored and processed at some point in the future. A content channel is required to be thread-safe, but it is the caller that is expected to perform the necessary external synchronization to avoid out-of-order calls; a second call to write(ByteBuffer, CompletionHandler) will never be initiated before the first call has returned.

The optional (in the sense that it might be null) CompletionHandler provides support for asynchronous notification of the completion or failure of a call to a content channel. Notice that unless your implementation of the content channel throws an exception during a write(ByteBuffer, CompletionHandler) or close(CompletionHandler), it is required that you explicitly call the corresponding completion handler at some point in the future.

Instead of developing your own ContentChannel, please consider using BufferedContentChannel if you intend to forward the content without blocking or copying, or ReadableContentChannel if you intend to access the content yourself.

Dispatching a response

The ResponseHandler is the callback to be invoked once a request has been sufficiently processed. The response may be dispatched by any thread, and at any time -- even before the call to the request handler returns. The response handler is the second argument of the request handler's handleRequest(Request, ResponseHandler) method, and it is commonly a one-off object. Notice that unless the call to the request handler throws an exception, it is required that you call the response handler exactly once at some point in the future.

Once a response has been constructed, it is dispatched by calling responseHandler.handleResponse(Response). The returned ContentChannel must then be used to asynchronously write the content of the response into it. Notice that unless the call to the response handler threw an exception, it is required that you explicitly close the returned content channel at some point in the future.

Instead of explicitly managing the dispatch of a response yourself, please consider using the ResponseDispatch utility class to safely dispatch your response.

Writing to a ContentChannel

The ContentChannel provides an asynchronous interface for ByteBuffer handover -- where handover implies that you may never reuse a buffer that you have written to it. All content channels are thread-safe, but it is your responsibility to perform the necessary synchronization to avoid out-of-order calls to it; a second call to write(ByteBuffer, CompletionHandler) should never be initiated before the first call has returned.

The optional (in the sense you are allowed to pass null for it) CompletionHandler provides support for asynchronous notification of the completion or failure of a call to a content channel. Unless an exception is thrown in a call to write(ByteBuffer, CompletionHandler) or close(CompletionHandler), you are guaranteed to have your completion handler called at some point in the future. Notice that it is required that you explicitly close all content channels -- regardless of whether some call to it threw an exception or failed(Throwable) was invoked on some completion handler.

Dispatching a client request

If a request handler decides to create and dispatch a child-request, it is done through the same binding set mechanics that was used to resolve the current request handler. However, where server providers resolve their dispatch against server-bindings, request handlers will resolve their dispatch against client-bindings. Override this behavior by using the setServerRequest(boolean) method. Notice that the request maintains a reference to the CurrentContainer, and that you are required to release the local reference once the request has been dispatched, to allow the JDisc Container to cleanly shut down at some point in the future.

Once the child-request and a response handler has been constructed, it is dispatched by calling request.connect(ResponseHandler). The returned ContentChannel must then be used to asynchronously write the content of the request into it. Notice that unless the call to connect threw an exception, it is required that you explicitly close the returned content channel at some point in the future.

Instead of explicitly managing the dispatch of a request yourself, please consider using the RequestDispatch utility class to safely dispatch requests.

Handling a client response

As a client provider responds to a request, the response handler that was part of the request dispatch is invoked. Because JDisc enforces no specific threading model, this call may be performed by any thread -- even some critical IO thread that belongs to one of the configured client providers. Because of this you should never perform much work in the body of the handleResponse(Response) method. Also, because the response may be dispatched at any time, the response handler may be invoked even before the call to dispatch the request returns.

Once invoked, a response handler must create and return a ContentChannel into which the caller can write the response's payload. At some point in the future, this payload then needs be consumed and acted upon.

Request timeout

A request may or may not have an assigned timeout. Such a timeout is the total amount of time that a request handler may spend processing a request before the corresponding call to handleResponse(Response) is made. If no timeout is assigned to a request, there will be no timeout. Once the allocated time has expired, unless the response handler has already been called, the request handler's handleTimeout(Request, ResponseHandler) method is invoked.

The ResponseHandler passed to the timeout method is the same ResponseHandler that was initially passed to the handleRequest() method. There is a built-in guard against calling the ResponseHandler more than once, so you do not need additional synchronization to prevent a late response from calling the handler of a request that has already timed out. The handleTimeout() method is called by the timeout manager's own thread. Any exception thrown in handleTimeout() is ignored.

Notice that you are required to call the given response handler from within handleTimeout(Request, ResponseHandler). Failure to do so will prevent the JDisc Container from cleanly shutting down.