LEVIATHAN v962456e · 962456eee1

Standard Library

class HttpServer

A web server that listens on a port and answers each request with a handler function.

since 0.1.0-alpha.1linux

Overview

Create it with a port number, check ok(), and call handle with a function that receives an HttpRequest and returns an HttpResponse. Connections are kept open for further requests unless either side sends Connection: close. If the handler throws, the client receives a 500 Internal Server Error and the connection is closed, while the server keeps serving other clients. A response made with HttpResponse::ofStream is sent in chunks as your code writes it.

Give a certificate and a private key to the second form of the constructor to serve HTTPS; the handler code stays the same. The server runs on the event loop: the program keeps running while the server is listening, until you call stop.

Description

HttpServer(port) listens on a port; handle(handler) installs the function that answers requests and starts serving. The handler takes an HttpRequest and returns an HttpResponse:

server.handle((req) => HttpResponse(200, "hello"));

ok() tells you whether the port could be opened, and stop() stops listening.

The server takes care of the protocol around your handler.

  • Framing. A request is read incrementally: the head, then the body as declared by Content-Length. Your handler is called once the whole request has arrived.
  • Keep-alive. When the request did not ask for Connection: close, the connection is kept for the next request, up to 100 requests per connection.
  • Errors. If the handler throws, the client gets a 500 response with Connection: close and the server keeps serving other clients.
  • Streaming. A handler may return HttpResponse::ofStream(status, headers, writer). The server sends the head, then calls writer with a ChunkedSink that can keep writing from a timer or a subscription until it calls end(). A finite stream keeps the connection alive only after its last chunk has been sent.
  • HTTPS. HttpServer(port, certPath, keyPath) serves over TLS with exactly the same handler surface. A client that fails the handshake is dropped and the server carries on. See lang.tls.

A handler is an ordinary function, so you can test it without a network by building a request from text and calling it:

Testing a handler without a server

HttpResponse handle(HttpRequest req) {
    if (req.path == "/hello") {
        return HttpResponse(200, "hello, " + req.method);
    }
    return HttpResponse(404, "no such page");
}

HttpRequest home = HttpRequest();
home.parse("GET /hello HTTP/1.1\r\nHost: test\r\n\r\n");
HttpResponse a = handle(home);
console.writeln("${a.status} ${a.reason()} ${a.body}");

HttpRequest other = HttpRequest();
other.parse("GET /missing HTTP/1.1\r\nHost: test\r\n\r\n");
HttpResponse b = handle(other);
console.writeln("${b.status} ${b.reason()} ${b.body}");
200 OK hello, GET
404 Not Found no such page

Rules

  • A server needs the event loop: it keeps the program alive until stop() is called.
  • The handler must return a response. Return HttpResponse(404, ...) for pages that do not exist; there is no automatic routing.
  • There is no pipelining. If a client sends more bytes while a response is in progress, the connection is closed.
  • A response to a HEAD request, or with a 1xx, 204 or 304 status, has no body. For a streaming response, only the head is sent and the writer is not called.
  • A streaming response to a request that is not HTTP/1.1 is replaced by 505 and the connection is closed.
  • The server computes Content-Length, Connection and Transfer-Encoding; headers with these names that your handler sets are ignored.
  • Bodies are text.

Examples

A server with a plain route and a streaming route that sends three events:

HttpServer server = HttpServer(8080);
if (!server.ok()) {
    console.writeln("port 8080 is not available");
} else {
    server.handle((req) => {
        if (req.path == "/events") {
            HeaderMap h = HeaderMap();
            h.set("Content-Type", "text/event-stream");
            return HttpResponse::ofStream(200, h, (sink) => {
                Timer ticker = std::every(1000);
                ticker.subscribe((n) => {
                    sink.write("data: tick ${n}\n\n");
                    if (n == 3) { ticker.cancel(); sink.end(); }
                });
                sink.onClose(() => ticker.cancel());
            });
        }
        return HttpResponse(200, "hello").withHeader("Content-Type", "text/plain");
    });
}
not run — needs the network

Notes

Not implemented: request pipelining.

Examples

A small server

HttpServer server = HttpServer(8080);
if (server.ok()) {
    server.handle((req) => {
        if (req.path == "/hello") {
            return HttpResponse(200, "Hello from ${req.method}");
        }
        return HttpResponse(404, "not found");
    });
}
not run — binds a network port

A streaming response

HttpServer server = HttpServer(8080);
server.handle((req) => HttpResponse::ofStream(200, HeaderMap(), (sink) => {
    sink.write("first part\n");
    sink.write("second part\n");
    sink.end();
}));

Constructors

new

new(int port)

Starts listening for plain HTTP connections on a port.

The port is bound immediately, so call ok() to find out whether that worked. No requests are answered until handle is called.

Parameters

port
The port number to listen on.
new(int port, string cert, string key)

Starts listening for HTTPS connections on a port.

It behaves like HttpServer(port), but every connection first completes a secure handshake using the certificate and key. A client whose handshake fails is dropped and the server carries on.

Parameters

port
The port number to listen on.
cert
The path of the certificate file.
key
The path of the private key file.

Methods

handle

handle((HttpRequest) => HttpResponse h) -> void

Starts answering requests with a handler function.

The handler is called once for each complete request and must return the response to send. Calling handle again replaces the handler for connections accepted afterwards.

Parameters

h
The function that turns a request into a response.

See also: stop

ok

ok() -> bool

Tells whether the server is listening.

Returns

true when the port was bound, false when it could not be (for example because it is already in use).

stop

stop() -> void

Stops listening for new connections.

Calling it more than once is harmless. Once the server has stopped, a program with nothing else to wait for can finish.

See also: handle

See also

  • HttpRequest — An HTTP request as seen by a server: the request line, the headers and the body.
  • HttpResponse — An HTTP response: a status code, headers and a body.
  • HttpClient — A simple HTTP client that sends one request per connection and delivers the response to a callback.
  • TcpListener — A listening socket that delivers each incoming connection as a TcpStream.
  • ChunkedSink — The writer for a streaming HTTP response body.