std.http_server

HTTP listeners, handlers, streaming bodies, and multipart input.

Importuse std.http_server

Types

struct Request#

Incoming request handled by a std.http_server.

method: string

HTTP method, uppercase.

path: string

Request target, query string included.

headers: string

The request header block, one Name: value per line.

body: string

The whole request body; empty for a streaming accept.

_handle: int

Connection handle the server answers on; not meant to be touched directly.

_body_reader: BodyReader

Bounded reader for a streaming body; not meant to be touched directly.

struct MultipartPart#

One multipart/form-data part with a bounded streaming body.

name: string

The part's form field name.

filename: string

The uploaded filename, or empty for a non-file part.

headers: string

The part's own header block, one Name: value per line.

_reader: MultipartReader

Reader the part body is pulled from; not meant to be touched directly.

_generation: int

Guards against reading a part after the reader has moved on.

struct BodyReader#

A bounded reader for a streaming HTTP request body.

An opaque handle: only std.http_server can produce one, and its contents are not reachable from moon source.

_handle: NativeHandle

The native resource.

struct ResponseSink#

An HTTP response stream; write chunks and close it exactly once.

An opaque handle: only std.http_server can produce one, and its contents are not reachable from moon source.

_handle: NativeHandle

The native resource.

struct MultipartReader#

A single-pass multipart/form-data reader.

An opaque handle: only std.http_server can produce one, and its contents are not reachable from moon source.

_handle: NativeHandle

The native resource.

Functions

fn text(status: int, body: string) -> Response#

Creates a plain-text HTTP response.

  • status: HTTP status code to send.
  • body: Body text, sent as text/plain.
use std.http.Response

fn main() {
  let plain: Response = std.http_server.text(200, "moon")
  let json = std.http_server.json(200, "{\"ok\":true}")
  let html = std.http_server.html(200, "<h1>moon</h1>")
  let custom = std.http_server.response(201, "X-Mode: tour", "created")
  println([plain.status, json.status, html.status, custom.status])
  println(std.http_server.not_found().status)
  println(std.http_server.bad_request().status)
  println(std.http_server.server_error().status)
  println(std.http_server.redirect(302, "/tour").headers)
}
fn json(status: int, body: string) -> Response#

Creates a JSON HTTP response.

  • status: HTTP status code to send.
  • body: Already-serialized JSON text.
fn json_value(status: int, value: Json) -> Response#

A Json tree answered directly, so a route never hand-stringifies its own body. Pair with json.to_value<T> for a typed response.

  • status: HTTP status code to send.
  • value: Tree serialized into the body.
fn html(status: int, body: string) -> Response#

Creates an HTML HTTP response.

  • status: HTTP status code to send.
  • body: Body markup, sent as text/html.
fn ok_response(body: string) -> Response#

Creates a 200 plain-text response.

  • body: Body text, sent as text/plain.
fn not_found() -> Response#

Creates a 404 response.

fn bad_request() -> Response#

Creates a 400 response.

fn server_error() -> Response#

Creates a 500 response.

fn redirect(status: int, location: string) -> Response#

Creates a redirect response with a Location header.

  • status: Redirect status, such as 302 or 308.
  • location: Value of the Location header.
fn response(status: int, headers: string, body: string) -> Response#

Creates an HTTP response from explicit fields.

  • status: HTTP status code to send.
  • headers: Header block, one Name: value per line.
  • body: Body sent verbatim.
extern fn accept(listener: int) -> Request#

Accepts and reads the next client request.

extern fn accept_stream(listener: int) -> Request#

Accepts a request without buffering its body.

fn detached_request(method: string, target: string, headers: string, body: string) -> Request#

A Request that never came off a socket, for exercising routing and handlers without a listener. There is no connection behind it (_handle is -1), so anything that needs the socket — draining a body, streaming a response, serve_file — will not work; the body passed here is all there is.

  • method: HTTP method, uppercase.
  • target: Raw request target, query string included.
  • headers: Header block, one Name: value per line.
  • body: The body the handler will see.
let request = std.http_server.detached_request(
  "POST",
  "/notes?id=1",
  "Content-Type: application/json\nX-Trace: abc",
  "{}",
)
println(request.method)
println(request.path)
println(std.http_server.get_header(request, "x-trace"))
println(std.http_server.is_connected(request))
println(std.http_server.with_body(request, "replacement").body)
fn with_body(req: Request, body: string) -> Request#

The same request with body in place of whatever it carried.

An accept_stream server hands routes a Request whose body is still on the socket; something has to read it and hand the handler a request that has it. The connection handle and body reader ride along unchanged, so the result is still the same connection — this is a re-body, not a new request. Both of those are private fields of Request, which is why the clone lives here rather than in the framework that needs it.

  • req: The request to re-body.
  • body: The body the handler should see.
fn is_connected(req: Request) -> bool#

Whether req arrived over a socket this process still holds.

A request accepted from a listener carries a live descriptor; one built by detached_request carries none. Anything reaching for the connection — draining a body, streaming a response, serving a file — has to ask first.

  • req: The request to test for a live descriptor.
extern fn body_reader(request: Request) -> BodyReader#

Returns the request's bounded body reader.

use std.http_server.Request

fn consume(request: Request) {
  let body = std.http_server.body_reader(request)
  println(std.http_server.read_body_line(body))
  println(std.http_server.read_body(body, 64 * 1024))
}

fn main() {}
extern fn read_body(reader: BodyReader, count: int) -> string#

Reads at most count decoded request-body bytes.

  • reader: Body reader for the request.
  • count: Upper bound on the bytes returned.
extern fn read_body_line(reader: BodyReader) -> string#

Reads one decoded request-body line; empty is EOF or an empty line.

  • reader: Body reader for the request.
extern fn respond_stream(request: Request, status: int, headers: string) -> ResponseSink#

Starts a Content-Length or chunk-framed response stream.

  • request: Request being answered.
  • status: HTTP status code to send.
  • headers: Header block; a Content-Length here picks length framing over chunking.
extern fn write_stream(sink: ResponseSink, chunk: string) -> bool#

Writes one response chunk; false means the client disconnected.

use std.http_server.Request

fn stream(request: Request) {
  let sink = std.http_server.respond_stream(
    request,
    200,
    "Content-Type: text/plain",
  )
  std.http_server.write_stream(sink, "first\n")
  std.http_server.write_stream(sink, "second\n")
  std.http_server.finish_stream(sink)
}

fn main() {}
extern fn finish_stream(sink: ResponseSink)#

Finishes framing and closes a response stream.

extern fn serve_file(request: Request, path: string, content_type: string) -> bool#

Serves a file with HEAD, single Range, If-Range, 206, and 416 handling.

  • request: Request being answered.
  • path: File to serve from disk.
  • content_type: Value of the Content-Type header.
extern fn multipart(request: Request) -> MultipartReader#

Creates a bounded multipart/form-data reader for a streaming request.

  • request: Streaming request whose body is multipart/form-data.
extern fn next_part(reader: MultipartReader) -> Optional<MultipartPart>#

Advances to the next multipart part.

  • reader: Reader returned by multipart; the previous part becomes unreadable.
use std.http_server.Request

fn read_upload(request: Request) {
  let reader = std.http_server.multipart(request)
  let mut reading = true
  while reading {
    match std.http_server.next_part(reader) {
      some(part) => {
        println(part.name + ":" + part.filename)
        println(std.http_server.read_part(part, 64 * 1024))
      },
      none => { reading = false },
    }
  }
}

fn main() {}
extern fn read_part(part: MultipartPart, count: int) -> string#

Reads at most count bytes from the current multipart part.

  • part: Part most recently returned by next_part.
  • count: Upper bound on the bytes returned.
extern fn listen(host: string, port: int) -> int#

Opens a TCP HTTP listener.

  • host: Address to bind, such as "127.0.0.1" or "0.0.0.0".
  • port: TCP port to bind; 0 asks the system to pick one.
extern fn listen_tls(host: string, port: int, cert_path: string, key_path: string) -> int#

Opens an HTTPS listener with a certificate and key.

  • host: Address to bind.
  • port: TCP port to bind.
  • cert_path: PEM certificate chain to present.
  • key_path: PEM private key for that certificate.
extern fn set_idle_timeout(listener: int, ms: int) -> bool#

How long a keep-alive connection may sit idle on this listener before the server closes it, in milliseconds; 0 disables the timeout.

The default is 60 s, which is nginx's keepalive_timeout. Without one, a client that connects, sends a request and walks away holds a descriptor for as long as the process runs — the pool is bounded by count (63) and evicts the oldest under pressure, which is an eviction policy and not a timeout.

  • listener: Listener handle returned by listen or listen_tls.
  • ms: Idle budget in milliseconds; 0 disables the timeout.
extern fn respond(request: Request, response: Response) -> bool#

Writes a response for an accepted request.

  • request: Request being answered.
  • response: Status, headers, and body to send.
extern fn close(listener: int) -> bool#

Closes the resource.

extern fn serve(host: string, port: int, handler: fn(Request) -> Response) -> bool#

Runs a synchronous HTTP server loop.

  • host: Address to bind.
  • port: TCP port to bind.
  • handler: Called with each accepted request; its response is sent back.
use std.http.Response
use std.http_server.Request

fn handle(request: Request) -> Response {
  if request.path == "/health" {
    std.http_server.ok_response("ok")
  } else {
    std.http_server.not_found()
  }
}

fn main() {
  std.http_server.serve("127.0.0.1", 8080, handle)
}
extern fn serve_tls(host: string, port: int, cert_path: string, key_path: string, handler: fn(Request) -> Response) -> bool#

Runs a synchronous HTTPS server loop.

  • host: Address to bind.
  • port: TCP port to bind.
  • cert_path: PEM certificate chain to present.
  • key_path: PEM private key for that certificate.
  • handler: Called with each accepted request; its response is sent back.
fn get_header(request: Request, name: string) -> string#

Returns a request header value case-insensitively, or an empty string.

  • request: Request to read the header block of.
  • name: Header name, matched case-insensitively.