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:
| Status | Cause |
|---|---|
| 400 | The request line, a header field or the body framing is malformed |
| 413 | The body is larger than maxBodyBytes |
| 431 | The head is larger than maxHeadBytes, or has more than maxHeaderFields fields |
| 501 | The transfer coding is not chunked |
| 505 | The 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.
Requestand its texts borrow the input buffer. They are valid only during the handler.
Examples#
Answer every request#
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#
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>#
impl<'a> Request<'a>Method method#
pub fn method(self: &Self) -> string<'a>Returns the method, such as GET.
Method target#
pub fn target(self: &Self) -> string<'a>Returns the request target as it was sent, such as /search?q=silk.
Method path#
pub fn path(self: &Self) -> string<'a>Returns the path of the target: the part before ?. It is not percent-decoded.
Method query#
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#
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#
pub fn minorVersion(self: &Self) -> u8Returns the minor version: 0 for HTTP/1.0, 1 for HTTP/1.1.
Method headerCount#
pub fn headerCount(self: &Self) -> usizeReturns the number of header fields.
Method header#
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#
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#
pub fn headerName(self: &Self, index: usize) -> string<'a>Returns the name of header field index, in the case it was sent.
Method headerValueBytes#
pub fn headerValueBytes(self: &Self, index: usize) -> &'a [u8]Returns the value bytes of header field index.
Method body#
pub fn body(self: &Self) -> &'a [u8]Returns the body. A chunked body is already decoded.
Method text#
pub fn text(self: &Self) -> Option<string<'a>>Returns the body as text, or None if it is not UTF-8.
Response#
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.headerskips a field whose name is not a token or whose value has a line break, and theContent-Length,Transfer-Encoding,ConnectionandDatefields, which the server writes itself.
Implementation Response<'a>#
impl<'a> Response<'a>Method status#
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#
pub fn statusCode(self: &Self) -> u16Returns the status code.
Method isSent#
pub fn isSent(self: &Self) -> boolReturns whether the body was sent.
Method closeConnection#
pub fn closeConnection(self: &mut Self) -> ()Makes the server close the connection after this response.
Method header#
pub effect fn header(self: &mut Self, name: string, value: string) -> () ! OutOfMemoryError ? &mut AllocatorAdds 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#
pub effect fn send(self: &mut Self, body: &[u8]) -> () ! OutOfMemoryError ? &mut AllocatorSends body with the status and the header fields that were set.
Errors#
OutOfMemoryError if the output buffer cannot grow.
Method text#
pub effect fn text(self: &mut Self, body: string) -> () ! OutOfMemoryError ? &mut AllocatorSends body as text/plain; charset=utf-8.
Errors#
OutOfMemoryError if the output buffer cannot grow.
Method html#
pub effect fn html(self: &mut Self, body: string) -> () ! OutOfMemoryError ? &mut AllocatorSends body as text/html; charset=utf-8.
Errors#
OutOfMemoryError if the output buffer cannot grow.
Method json#
pub effect fn json(self: &mut Self, body: string) -> () ! OutOfMemoryError ? &mut AllocatorSends body, which holds JSON text, as application/json.
Errors#
OutOfMemoryError if the output buffer cannot grow.
Handler#
pub interface HandlerThe 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#
effect fn handle(self: &Self, request: &Request, response: &mut Response) -> () ! OutOfMemoryError ? &mut AllocatorReads request and writes response. A failure makes the server answer 500 and close the
connection.
ServerOptions#
pub struct ServerOptionsThe settings of a Server: the address, the workers, and the limits of one request.
Field address#
pub address: IpAddressThe address to listen on. The default is 127.0.0.1.
Field port#
pub port: u16The TCP port to listen on.
Field workers#
pub workers: usizeThe number of worker threads. Zero, the default, means one for each processor.
Field maxHeadBytes#
pub maxHeadBytes: usizeThe largest request head: the request line and the header fields. The default is 16 KiB.
Field maxHeaderFields#
pub maxHeaderFields: usizeThe largest number of header fields. The default is 100.
Field maxBodyBytes#
pub maxBodyBytes: usizeThe largest request body, after chunked decoding. The default is 1 MiB.
Field idleTimeoutSeconds#
pub idleTimeoutSeconds: usizeThe time a connection can stay silent before it is closed, in seconds. The default is 60.
Implementation Copy for ServerOptions#
impl Copy for ServerOptionsServer#
pub struct ServerAn 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#
impl Copy for ServerServeError#
pub union ServeErrorThe failure of Server.serve.
InvalidHost#
InvalidHostThe host given to Server.host is not an IPv4 or IPv6 address.
Engine#
EngineThe engine failed: a listener, a thread or the event loop.
Implementation Server#
impl ServerAssociated function make#
pub fn make(port: u16) -> ServerReturns a server for port on 127.0.0.1, with one worker for each processor and the
default limits of ServerOptions.
Associated function fromOptions#
pub fn fromOptions(options: ServerOptions) -> ServerReturns a server with all its settings from options.
Method options#
pub fn options(self: &Self) -> ServerOptionsReturns the settings.
Method host#
pub fn host(self: Self, address: string) -> ServerReturns 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#
pub fn workers(self: Self, count: usize) -> ServerReturns a copy with count worker threads. Zero means one for each processor.
Method maxBodyBytes#
pub fn maxBodyBytes(self: Self, limit: usize) -> ServerReturns a copy that accepts request bodies up to limit bytes.
Method serve#
pub effect<'static> fn serve<H: 'static + Handler + Transferable + Concurrent>(self: Self, handler: H) -> () ! ServeError | OutOfMemoryError ? &mut AllocatorServes 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#
InvalidHostifServer.hostgot a text that is not an address.Engineif a listener, a thread or an event loop cannot be made, for example when the port is in use.OutOfMemoryErrorif a worker cannot allocate.
Method serveTls#
pub effect<'static> fn serveTls<H: 'static + Handler + Transferable + Concurrent>(
self: Self,
config: TlsServerConfig,
handler: H,
) -> () ! ServeError | OutOfMemoryError ? &mut AllocatorServes 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.