silk.http.serve.

A fast HTTP/1.1 server: write one handler function, and serve it on every core.

When to use#

Use this module to answer HTTP requests with little code and high throughput. Write a handler that reads a Request and writes a Response, then call Server.serve, or Server.serveTls for HTTPS. For streaming bodies, upgrades or full control of the connection, use silk.http.server instead.

Details#

Server.serve runs the silk.net.tcp_server engine: one event loop for each worker thread, and no locks between workers. The server reads a whole request, with its body, before it calls the handler. Requests that come one after another on the same connection (pipelining) are answered in order.

The server parses HTTP/1.0 and HTTP/1.1 requests with a Content-Length body, a chunked body, or no body. It answers with HTTP/1.1 and adds the Date, Content-Length and, when it closes the connection, Connection: close header fields. A request with Expect: 100-continue gets 100 Continue before its body is read. A HEAD request gets the head of the response, without the body.

The connection closes after the response when the request asks for it, or when the request is HTTP/1.0 without Connection: keep-alive. A request that the server cannot read gets an error response, and then the connection closes:

StatusCause
400The request line, a header field or the body framing is malformed
413The body is larger than maxBodyBytes
431The head is larger than maxHeadBytes, or has more than maxHeaderFields fields
501The transfer coding is not chunked
505The version is not HTTP/1.0 or HTTP/1.1

Errors#

Server.serve fails with TcpServerError if a listener cannot be made, and with OutOfMemoryError if a worker cannot allocate. A failure of a handler only ends its request: the server answers 500 Internal Server Error and closes that connection.

Gotchas#

  • It needs Linux with GNU libc on x86_64 or aarch64, like silk.net.tcp_server.

  • The handler runs on the worker thread of the connection. A handler that blocks, for example on a slow file, stops every other connection of that worker until it returns.

  • Request and its texts borrow the input buffer. They are valid only during the handler.

Examples#

Answer every request#

silk
import silk.mem.allocator { Allocator, OutOfMemoryError }
import silk.core.effect { Effect }
import silk.http.serve { Handler, Request, Response, Server }

struct Hello {}

impl Handler for Hello {
  effect fn handle(self: &Self, request: &Request, response: &mut Response) -> () ! OutOfMemoryError ? &mut Allocator {
    if request.path() == "/" {
      run response.text("Hello, World!")
      return ()
    }
    response.status(404)
    run response.text("Not found")
  }
}

effect fn failed<E>(error: E) -> i32 { drop error return 1 }

pub fn main() -> i32 {
  let mut allocator = Allocator.systemAllocatorProvider()
  let served = Server.make(8080).serve(Hello {})
  return run Effect.catchAll(Effect.map(move served, fn(done: ()) -> i32 { return 0 }), failed)
    |> Effect.provideMut<Allocator>(&mut allocator)
}

Import as Server with import silk.http.serve { Server }.

Public declarations: 6.

Request#

silk
pub struct Request<'a>

One HTTP request, with its whole body.

Details#

The texts it returns borrow the input buffer of the connection, so they cost no copy. The method, the target and the header names are ASCII, as the parser checks. Header values may hold bytes that are not UTF-8: Request.header skips such a value, and Request.headerBytes returns it.

Implementation Request<'a>#

silk
impl<'a> Request<'a>

Method method#

silk
pub fn method(self: &Self) -> string<'a>

Returns the method, such as GET.

Method target#

silk
pub fn target(self: &Self) -> string<'a>

Returns the request target as it was sent, such as /search?q=silk.

Method path#

silk
pub fn path(self: &Self) -> string<'a>

Returns the path of the target: the part before ?. It is not percent-decoded.

Method query#

silk
pub fn query(self: &Self) -> string<'a>

Returns the query of the target: the part after ?, or an empty text when there is none. It is not percent-decoded.

Method queryParameter#

silk
pub fn queryParameter(self: &Self, name: string) -> Option<string<'a>>

Returns the value of the query parameter name, as it was sent, or None. The value is not percent-decoded, and + stays +.

Method minorVersion#

silk
pub fn minorVersion(self: &Self) -> u8

Returns the minor version: 0 for HTTP/1.0, 1 for HTTP/1.1.

Method headerCount#

silk
pub fn headerCount(self: &Self) -> usize

Returns the number of header fields.

Method header#

silk
pub fn header(self: &Self, name: string) -> Option<string<'a>>

Returns the value of the first header field named name, compared without case, or None. It skips a value that is not UTF-8.

Method headerBytes#

silk
pub fn headerBytes(self: &Self, name: string) -> Option<&'a [u8]>

Returns the bytes of the value of the first header field named name, compared without case, or None. The value has no leading or trailing spaces.

Method headerName#

silk
pub fn headerName(self: &Self, index: usize) -> string<'a>

Returns the name of header field index, in the case it was sent.

Method headerValueBytes#

silk
pub fn headerValueBytes(self: &Self, index: usize) -> &'a [u8]

Returns the value bytes of header field index.

Method body#

silk
pub fn body(self: &Self) -> &'a [u8]

Returns the body. A chunked body is already decoded.

Method text#

silk
pub fn text(self: &Self) -> Option<string<'a>>

Returns the body as text, or None if it is not UTF-8.

Response#

silk
pub struct Response<'a>

The response to one Request.

Details#

Set the status with Response.status and add header fields with Response.header. Then write the body with Response.send, Response.text or Response.html. The server adds Date and Content-Length. A handler that returns without a body sends an empty one, with the status it set.

Gotchas#

  • The body can be sent once. Later sends and header fields are ignored.

  • Response.header skips a field whose name is not a token or whose value has a line break, and the Content-Length, Transfer-Encoding, Connection and Date fields, which the server writes itself.

Implementation Response<'a>#

silk
impl<'a> Response<'a>

Method status#

silk
pub fn status(self: &mut Self, code: u16) -> ()

Sets the status code. The default is 200. A code outside 100 to 999 is ignored.

Method statusCode#

silk
pub fn statusCode(self: &Self) -> u16

Returns the status code.

Method isSent#

silk
pub fn isSent(self: &Self) -> bool

Returns whether the body was sent.

Method closeConnection#

silk
pub fn closeConnection(self: &mut Self) -> ()

Makes the server close the connection after this response.

Method header#

silk
pub effect fn header(self: &mut Self, name: string, value: string) -> () ! OutOfMemoryError ? &mut Allocator

Adds the header field name: value.

Details#

It skips the field if the name is not a token, if the value holds a CR, an LF or a NUL, or if the name is one the server writes itself: Content-Length, Transfer-Encoding, Connection or Date.

Errors#

OutOfMemoryError if the field cannot be stored.

Method send#

silk
pub effect fn send(self: &mut Self, body: &[u8]) -> () ! OutOfMemoryError ? &mut Allocator

Sends body with the status and the header fields that were set.

Errors#

OutOfMemoryError if the output buffer cannot grow.

Method text#

silk
pub effect fn text(self: &mut Self, body: string) -> () ! OutOfMemoryError ? &mut Allocator

Sends body as text/plain; charset=utf-8.

Errors#

OutOfMemoryError if the output buffer cannot grow.

Method html#

silk
pub effect fn html(self: &mut Self, body: string) -> () ! OutOfMemoryError ? &mut Allocator

Sends body as text/html; charset=utf-8.

Errors#

OutOfMemoryError if the output buffer cannot grow.

Method json#

silk
pub effect fn json(self: &mut Self, body: string) -> () ! OutOfMemoryError ? &mut Allocator

Sends body, which holds JSON text, as application/json.

Errors#

OutOfMemoryError if the output buffer cannot grow.

Handler#

silk
pub interface Handler

The code that answers requests. Server.serve calls handle for each request.

Details#

One handler value serves every worker thread through a shared borrow, so the type must be Transferable and Concurrent. Keep state that requests change in a Mutex or in atomics.

Operation handle#

silk
effect fn handle(self: &Self, request: &Request, response: &mut Response) -> () ! OutOfMemoryError ? &mut Allocator

Reads request and writes response. A failure makes the server answer 500 and close the connection.

ServerOptions#

silk
pub struct ServerOptions

The settings of a Server: the address, the workers, and the limits of one request.

Field address#

silk
pub address: IpAddress

The address to listen on. The default is 127.0.0.1.

Field port#

silk
pub port: u16

The TCP port to listen on.

Field workers#

silk
pub workers: usize

The number of worker threads. Zero, the default, means one for each processor.

Field maxHeadBytes#

silk
pub maxHeadBytes: usize

The largest request head: the request line and the header fields. The default is 16 KiB.

Field maxHeaderFields#

silk
pub maxHeaderFields: usize

The largest number of header fields. The default is 100.

Field maxBodyBytes#

silk
pub maxBodyBytes: usize

The largest request body, after chunked decoding. The default is 1 MiB.

Field idleTimeoutSeconds#

silk
pub idleTimeoutSeconds: usize

The time a connection can stay silent before it is closed, in seconds. The default is 60.

Implementation Copy for ServerOptions#

silk
impl Copy for ServerOptions

Server#

silk
pub struct Server

An HTTP server, with the settings of ServerOptions.

Details#

Server.make gives the defaults for a port. The other functions return a copy with one setting changed, so calls chain: Server.make(8080).host("0.0.0.0").workers(4).

Implementation Copy for Server#

silk
impl Copy for Server

ServeError#

silk
pub union ServeError

The failure of Server.serve.

InvalidHost#

silk
InvalidHost

The host given to Server.host is not an IPv4 or IPv6 address.

Engine#

silk
Engine

The engine failed: a listener, a thread or the event loop.

Implementation Server#

silk
impl Server

Associated function make#

silk
pub fn make(port: u16) -> Server

Returns a server for port on 127.0.0.1, with one worker for each processor and the default limits of ServerOptions.

Associated function fromOptions#

silk
pub fn fromOptions(options: ServerOptions) -> Server

Returns a server with all its settings from options.

Method options#

silk
pub fn options(self: &Self) -> ServerOptions

Returns the settings.

Method host#

silk
pub fn host(self: Self, address: string) -> Server

Returns a copy that listens on address, an IPv4 or IPv6 address such as 0.0.0.0 or ::1. If it is not an address, Server.serve fails with InvalidHost.

Method workers#

silk
pub fn workers(self: Self, count: usize) -> Server

Returns a copy with count worker threads. Zero means one for each processor.

Method maxBodyBytes#

silk
pub fn maxBodyBytes(self: Self, limit: usize) -> Server

Returns a copy that accepts request bodies up to limit bytes.

Method serve#

silk
pub effect<'static> fn serve<H: 'static + Handler + Transferable + Concurrent>(self: Self, handler: H) -> () ! ServeError | OutOfMemoryError ? &mut Allocator

Serves handler on every worker, until a worker fails.

Details#

The handler gets each request and writes its response. One handler value serves every worker through a shared borrow, so it must be Transferable and Concurrent: a function, or a closure that captures only such values, such as an Arc of a Mutex.

Errors#

  • InvalidHost if Server.host got a text that is not an address.

  • Engine if a listener, a thread or an event loop cannot be made, for example when the port is in use.

  • OutOfMemoryError if a worker cannot allocate.

Method serveTls#

silk
pub effect<'static> fn serveTls<H: 'static + Handler + Transferable + Concurrent>(
    self: Self,
    config: TlsServerConfig,
    handler: H,
  ) -> () ! ServeError | OutOfMemoryError ? &mut Allocator

Serves handler over HTTPS with the certificate and key of config, until a worker fails.

Details#

Each connection runs TLS 1.3 with silk.tls.server (through silk.tls.server_protocol), then HTTP/1.1 over it, as Server.serve does. Load config with TlsServerConfig.fromPem.

Errors#

The same as Server.serve.

Category: HTTP.

Profiles: aarch64-apple-darwin, aarch64-unknown-linux-gnu, aarch64-unknown-linux-gnu-no-libc, x86_64-unknown-linux-gnu, x86_64-unknown-linux-gnu-no-libc, wasm32-unknown-unknown, wasm32-wasip1, wasm32-wasip2, x86_64-pc-windows-msvc, wasm64-unknown-unknown.

View the source of this page