RikuloRikulo

Request Handling

Request Handlers

A request handler is a function that processes a particular phase of HTTP requests. A request handler must have one argument typed HttpConnect. For example,

void currentTime(HttpConnect connect) {
  connect.response
    .write("<html><body>It is now ${new Date.now()}.</body></html>");
}

HttpConnect encapsulates all information of a HTTP connection, including the request (HttpRequest) and the response (HttpResponse).

The signature of the WebSocket handler is different. Please refer to WebSocket Handling for details.

Return void if completed immediately

If a request handler finishes immediately, it doesn't have to return anything. For example,

void serverInfo(HttpConnect connect) {
  final info = {"name": "Rikulo Stream", "version": connect.server.version};
  connect.response
    ..headers.contentType = contentTypes["json"]
    ..write(JSON.encode(info));
}

Tip: For sake of consistency, you can always return Future by use of new Future.value().

Return Future if handled asynchronously

If a request handler processes the request asynchronously, it must return an instance of Future to indicate when the processing will be completed. For example,

Future loadFile(HttpConnect connect)
=> new File("some_file").exists().then((exists) {
    if (exists)
      return connect.response.addStream(file.openRead());
    throw new Http404("some_file");
  });

It is for illustration. You generally don't have to load a file, since Stream server will handle it automatically.

The returned Future object can carry any type of objects, such as Future<bool>. The type is application specific (as long as your caller knows how to handle it). Stream server simply ignores it.

If you're using Completer, remember to wire the error. For example,

Future waitUntilReady(HttpConnect connect) {
  final completer = new Completer();
  doSomething(
    onDone: () => completer.complete(),
    onError: (error) => completer.completeError(error));
  return completer.future;
}

Tip: before using Completer, it is always a good idea to find if a method returns Future for saving the effort, such as StreamSubscription.asFuture and IOSink.done.

Provide additional named arguments

A request handler can have any number of named arguments too. They are usually used to pass data models for applying the MVC design pattern. For example,

Future userRetriever(HttpConnect connect) { //controller
  //1. prepare the model
  return loadUser(connect.request.uri.queryParameter["user"])
    .then((User user) {
      //2. pass the model to the view
      return userView(connect, user: user);
    });
}
Future userView(HttpConnect connect, {User user}) { //view
  //...display the user
}
Future<User> loadUser(String name) {
  //...access the user from database
}

For how to apply MVC, please refer to the MVC Design Pattern section.

For how to process a form submit, please refer to the Form Handling section.

Chain the request handlers

As shown above, chaining the request handlers is straightforward: there is no difference from chaining Future objects.

If you'd like to wrap HttpConnect to, say, collect the output to a buffer for further processing, you can use HttpConnect.stringBuffer or HttpConnect.buffer.

Future foo(HttpConnect connect) {
  final buffer = new StringBuffer();
  return another(new HttpConnect(connect, buffer)).then(() {
    //...process buffer and write the result to connect.response
  })
}

Chain to another URI

To chain to another URI, you can use HttpConnect.include or HttpConnect.forward:

Future foo(HttpConnect connect) {
  return connect.include("/webapp/header").then((_) {
    connect.response.write("<p>This is the body</p>");
    return connect.include("/webapp/footer");
  });
}

For information, please refer to Request Forwarding and Inclusion and Templating - Composite View Pattern.

Request Routing

To map a URI to a request handler, you can specify a URI mapping when instantiating StreamServer. For example,

void main() {
  new StreamServer(uriMapping: {
    "/server-info": (connect) {
      connect.response.write("<html><body>${getServerInfo()}.</body></html>");
    },
    "/order/.*": placeOrder,
    "/user/.*": user
  }).start();
}

As shown, the URI is a regular expression staring with '/'. Furthermore, you can specify the HTTP method for RESTful services, name the matched group and so on. For more information, please refer to the Request Routing section.