Asynchronous API

Introduction

The Restlet API before version 2.0 was fully synchronous, blocking a thread during the whole call processing. The Restlet edition for GWT however had to work in an asynchronous way due to the constraints of AJAX. The goal is to move towards a unified API properly supporting asynchronicity.

Requirements

High priorities

Medium priorities

Analysis

Lifecycle of a HTTP call

On the client-side

  1. Send this request : call Uniform#handle(Request, Response)
  2. My request headers have been sent
  3. My request entity is about to be sent : ConnectorService#beforeSend(Representation)
  4. My request entity was fully sent : ConnectorService#afterSend(Representation)
  5. My request was fully sent, including its entity
  6. The response to my request has just arrived, at least its headers : Uniform#handle(Request, Response) returns
  7. The response was fully read : Representation fully processed
  8. My request was aborted by the server

On the server-side

  1. A request has just arrived, at least its headers : Uniform#handle(Request, Response) is called
  2. The request has been processed and the response is ready : Uniform#handle(Request, Response) returns
  3. My response headers have been sent
  4. My response entity is about to be sent : ConnectorService#beforeSend(Representation)
  5. My response entity was fully sent : ConnectorService#afterSend(Representation)
  6. My response was fully sent, including its entity
  7. The request was aborted by the client

Design

Restlet API changes

  • Request#onResponse : Uniform, called back when the response has been received, at least its headers
  • Message#onSent : Uniform, called back when the message is fully sent
  • Message#abort() method to prematurely end the sending of a request or a response
  • Response#autoCommitting : boolean, indicates if the response should be written with the current status
  • Response#commit() : triggers the sending of a provisional or final response (new separate Response instances)

Connector changes

Removal of HttpCall class

  • HttpCall represents a single request+response couple when we need support for several responses for a single request
  • HttpCall, HttpClientCall and HttpClientCall will be splitted into Connection, ClientConnection, ServerConnection and ConnectedRequest

New Connection class

  • Supports persistent connections : "Connection" header
  • Supports pipelining : ensuring same order for requests and responses, only indempotent methods in parallel
  • Supports provisional responses : 1xx status family, "Expect" header, SIP transactions
  • Support tunnels :  "CONNECT" method, transforming a proxy into a tunnel
  • Responsible for respecting the same order between requests and responses using concurrent queues
  • Properties : persistent (boolean), state (opening, opened, closing, closed), pipelining (boolean), requests (Queue<Request>), response (Queue<Response>), timeout (long)
  • Methods : close(), abort()

Server connector changes

  • New ServerConnection, subclass of Connection
  • Subclasses will be provided for various extensions (Grizzly, Jetty, Netty, Servlet, Simple)

ServerHelper
Click to enlarge

Client connector changes

  • New ClientConnection, subclass of Connection
  • Ability to process asynchronously
  • Ability to limit concurrent connections to a remote host
  • Subclasses will be provided for various extensions (Apache HTTP Client, Java.Net HttpURLConnection)

ClientHelper
Click to enlarge

Leveraging NIO

In Restlet 2.0, the new connectors above support asynchronous processing of the messages, but still relies on blocking IO at the network level. This results in one to two IO threads blocked on each connection. We can clearly do better by leveraging NIO. Here are the steps when reading a message:

  1. When the connection can read, call readMessage() which registers read interest with the selector
  2. When new content is available on the socket notify the connection -> onBytes(Buffer, int)
  3. When notified, the connection should check if a full line is available -> onLine(Buffer)
  4. When a new line is available, parse the action line or a header or the beginning of the entity -> onFirstLine(Buffer), onHeader(Parameter), onEmptyLine(), onEndReached()
  5. When an empty line is received or the end is reached the message is read -> onMessage(Message)

Here are the steps while writing a message:

  1. When the connection can write, call writeMessage(Message) which registers write interest with the selector
  2. When bytes can be written -> writeLine(Buffer)
  3. When bytes have been written -> onBytes(Buffer, int)
  4. When line has been fully written -> onLine(Buffer)
  5. When the message is fully written, call onMessage(Message)
Comments (3)