Hcs.Server
hcs · API reference
HTTP server runtime.
Use this module to bind sockets and serve a request handler. It supports HTTP/1.1, HTTP/2, TLS configuration, WebSocket upgrade handling, streaming responses, request/idle/read/write timeouts, and bounded request-body buffering.
Most applications call Server.run with a handler produced by Endpoint.to_handler. Lower-level applications can pass a handler directly.
Unified HTTP Server - High-performance server supporting HTTP/1.1, HTTP/2, and WebSocket.
This module consolidates all server functionality into a single, high-performance implementation. Protocol detection overhead is optional and controlled via the protocol configuration.
Protocol Modes
Http1_only: Fastest path. No protocol detection, direct HTTP/1.1 handling.Http2_only: HTTP/2 only. h2c (cleartext) or h2 (over TLS).Auto: Auto-detect protocol via connection preface peek (h2c) or ALPN (TLS).Auto_websocket: Auto-detect with WebSocket upgrade support.
Performance Features
- GC tuning for high-throughput scenarios
- Cached Date headers (1-second resolution)
- Pre-built responses for zero-allocation hot paths
- Zero-copy bigstring responses
- Streaming response support
- Multi-domain parallelism via Eio
Example
(* Fastest HTTP/1.1 server *)
let handler req = Server.respond "Hello, World!" in
Server.run ~sw ~net ~clock handler
(* Multi-protocol with auto-detection *)
let config = Server.{ default_config with protocol = Auto } in
Server.run ~sw ~net ~clock ~config handlerProtocol Configuration
type protocol =
| Http1_only (* Fastest: No protocol detection, direct HTTP/1.1 handling. With TLS: HTTP/1.1 over TLS (no ALPN negotiation). *)
| Http2_only (* HTTP/2 only. Without TLS: h2c (HTTP/2 cleartext). With TLS: h2 with ALPN advertising only "h2". *)
| Auto (* Auto-detect protocol. Without TLS: Peek for "PRI " preface (h2c), fallback to HTTP/1.1. With TLS: ALPN negotiation. *)
| Auto_websocket (* Auto-detect with WebSocket support. Same as Auto but also handles WebSocket upgrade requests in HTTP/1.1 mode. *)Protocol mode controls detection overhead and supported protocols.
GC Tuning
module Gc_tune : sig ... endmodule Prebuilt = Response.PrebuiltTypes
type config = {
host : string; (* Bind address. Default: "0.0.0.0" *)
port : int; (* Listen port. Default: 8080 *)
backlog : int; (* Listen backlog. Default: 4096 *)
max_connections : int; (* Max concurrent connections. Default: 100000 *)
domain_count : int; (* Number of domains (CPUs) to use. Default: 1 *)
protocol : protocol; (* Protocol mode. Default: Http1_only *)
read_timeout : float; (* Read timeout in seconds. Default: 0.0, disabled. *)
write_timeout : float; (* Write timeout in seconds. Default: 0.0, disabled. *)
idle_timeout : float; (* Idle connection timeout in seconds. Default: 0.0, disabled. *)
request_timeout : float; (* Request processing timeout in seconds. Default: 0.0, disabled. *)
max_header_size : int; (* Max size in bytes of the request head (request line + headers). Passed to the codec parser as max_request_head_size (HTTP/1.1) / max_header_list_size (HTTP/2). Default: 32768. *)
max_body_size : int64 option; (* Max body size. None = unlimited. Default: None *)
request_body_buffer_limit : int option; (* Cap (in bytes) on how much of a request body is buffered in memory for whole-body access (Request.body). A body larger than this is streamed past without being retained: the handler still sees the exact Request.body_length, but Request.body reads empty. None = buffer the whole body (the default; preserves classic behavior). Set this to bound memory for endpoints that only count/stream large uploads. *)
tcp_nodelay : bool; (* Set TCP_NODELAY on connections. Default: true *)
reuse_addr : bool; (* Set SO_REUSEADDR on listener. Default: true *)
reuse_port : bool; (* Set SO_REUSEPORT on listener. Default: true *)
tls : Tls_config.Server.t option; (* TLS config. None = plain HTTP *)
gc_tuning : Gc_tune.config option; (* GC tuning config. Some = apply tuning. Default: Some Gc_tune.default *)
}Server configuration.
val default_config : configDefault configuration optimized for HTTP/1.1 performance.
val auto_config : configConfiguration for auto-detection mode.
val websocket_config : configConfiguration for WebSocket support.
Config Builders
val with_port : int -> config -> configval with_host : string -> config -> configval with_backlog : int -> config -> configval with_max_connections : int -> config -> configval with_domain_count : int -> config -> configval with_protocol : protocol -> config -> configval with_read_timeout : float -> config -> configval with_write_timeout : float -> config -> configval with_idle_timeout : float -> config -> configval with_request_timeout : float -> config -> configval with_max_header_size : int -> config -> configval with_max_body_size : int64 -> config -> configval with_request_body_buffer_limit : int -> config -> configval with_tcp_nodelay : bool -> config -> configval with_tls : Tls_config.Server.t -> config -> configval with_gc_tuning : Gc_tune.config -> config -> configval without_gc_tuning : config -> configRequest/Response Types
type protocol_version =
| HTTP_1_1
| HTTP_2Protocol version indicator.
exception Body_too_largetype header_src =
| Headers_http of Http_core.Headers.tRaw request headers, kept in their protocol-native form so the assoc list is only materialized if a handler actually asks for it.
type body_reader = {
read : unit -> string; (* Read the whole body as a string (memoized; reads on first call). *)
read_stream : unit -> Cstruct.t option; (* Read the body in chunks; returns None at end of input. *)
length : unit -> int; (* Body length in octets, without materializing it as a string. *)
close : unit -> unit; (* Discard the body without reading it. *)
}On-demand body access. The body is not read from the socket until one of these is called, so a handler that ignores it pays nothing.
type request = {
meth : Method.t; (* HTTP method (parsed eagerly — cheap). *)
target : string; (* Request target, path + query (eager). *)
version : protocol_version; (* Protocol version (eager). *)
header_src : header_src; (* Raw headers; parsed on demand via Request. *)
overlay : (string * string) list; (* Headers injected by plugs; checked before header_src. Usually empty. *)
body_reader : body_reader; (* On-demand body. *)
assigns : Assigns.t; (* Type-safe application data attached by plugs. *)
}Request type exposed to handlers. Only meth/target/version are parsed up front; headers, query params, and the body are read on demand through the Request accessors.
val empty_body_reader : body_readerA body reader for requests with no body (GET/HEAD, or already drained).
val body_reader_of_string : string -> body_readerWrap an already-materialized body string as a body_reader. Used where the body was read eagerly (the H1 bodied path, the H2 path, or plugs that transform it). read_stream yields the whole body as a single chunk then None, so streaming consumers (e.g. multipart) still work.
val body_reader_of_bigstring : Http_core.Bigstring.t -> body_readerWrap an owned bigstring request body (the http codec delivers bodies as Http_core.Bigstring.t). read_stream yields a zero-copy Cstruct view; read materializes a string only on demand (lazily, once) — so a handler that only needs the length or reads it as a Cstruct never pays the copy.
val request_of :
?meth:Method.t ->
?target:string ->
?version:protocol_version ->
?headers:(string * string) list ->
?body:string ->
unit ->
requestBuild a synthetic request with materialized headers/body. For tests and callers that construct a request by hand (the server builds requests lazily from the wire, not via this).
type response = Response.ttype handler = request -> responsetype ws_handler = Websocket.t -> unittype ws_config = {
origin_policy : Websocket.origin_policy;
max_payload_size : int;
}val default_ws_config : ws_configval respond :
?status:Status.t ->
?headers:(string * string) list ->
string ->
Response.tval respond_bigstring :
?status:Status.t ->
?headers:(string * string) list ->
Bigstringaf.t ->
Response.tval respond_prebuilt : Response.Prebuilt.t -> Response.tval respond_empty :
?status:Status.t ->
?headers:(string * string) list ->
unit ->
Response.tval respond_text : ?status:Status.t -> string -> Response.tval respond_html : ?status:Status.t -> string -> Response.tmodule Clen : sig ... endval respond_json : ?status:Status.t -> string -> Response.tInternal: Socket Helpers
val set_tcp_nodelay : 'a Eio.Resource.t -> unitval shutdown_flow :
[> Eio.Flow.two_way_ty ] Eio.Flow.two_way ->
Eio.Flow.shutdown_command ->
unitInternal: http codec ↔ hcs conversions
The server side runs on the http library codecs (Http_core/Http1/ Http2), which use a typed Http_core.Method.t / Http_core.Status.t / Http_core.Headers.t. The public hcs request/response keep the polymorphic-variant Method.t / Status.t (so routers, plugs and existing handlers are unchanged), so we translate at the boundary.
val meth_to_h1 : Http_core.Method.t -> Method.tval status_to_core : Status.t -> Http_core.Status.tval with_deadline :
[> float Eio.Time.clock_ty ] Eio.Time.clock ->
float ->
(unit -> 'a) ->
'aInternal: Protocol Detection
val h2_preface_prefix : stringHTTP/2 connection preface starts with "PRI "
val h2_preface_prefix_len : intval peek_bytes :
clock:[> float Eio.Time.clock_ty ] Eio.Time.clock ->
timeout:float ->
[> Eio.Flow.source_ty ] Eio.Flow.source ->
int ->
(string, [> `Eof | `Exn of exn ]) resultval is_h2_preface : string -> boolmodule Conn_buffers : sig ... endPer-connection scratch, reused across connections. buf/cs are the read buffer: the http codecs read from a bigstring (Http_core.Bigstring.t), and Eio reads the socket into a bigarray-backed Cstruct over that buffer, so the socket→codec hop is zero-copy. wscratch is the write scratch: small Bytes iovecs (frame heads, Fixed string bodies) are blitted into it for the vectored write instead of mallocing a fresh Cstruct each. Lock-free Treiber stack via Kcas.
type timeouts = {
read : float;
write : float;
idle : float;
request : float;
}val timeouts_of_config : config -> timeoutsval request_timeout_response : Response.tval run_handler_timed :
clock:[> float Eio.Time.clock_ty ] Eio.Time.clock ->
request_timeout:float ->
('a -> Response.t) ->
'a ->
Response.tval write_iovecs :
clock:[> float Eio.Time.clock_ty ] Eio.Time.clock ->
write_timeout:float ->
[> Eio.Flow.sink_ty ] Eio.Flow.sink ->
Conn_buffers.t ->
Http_core.Iovec.t list ->
intmodule Driver : sig ... endSingle-loop driver for a sans-I/O http server connection over an Eio flow.
Internal: HTTP/1.1 Connection Handler
module H1_handler : sig ... endmodule H2_handler : sig ... endInternal: TLS Connection Handler
module Tls_handler : sig ... endInternal: Connection Handler
val handle_connection :
sw:'a ->
clock:[> float Eio.Time.clock_ty ] Eio.Time.clock ->
config:config ->
handler:(request -> Response.t) ->
ws_handler:(Websocket.t -> unit) ->
[> Eio.Flow.two_way_ty ] Eio.Flow.sink ->
unitPublic API
val resolve_bind_addr :
net:[> [> `Generic ] Eio.Net.ty ] Eio.Net.t ->
config ->
[> `Tcp of string * int ]val run :
sw:Eio.Switch.t ->
net:[> [> `Generic ] Eio.Net.ty ] Eio.Net.t ->
clock:[> float Eio.Time.clock_ty ] Eio.Time.clock ->
?config:config ->
?ws_handler:(Websocket.t -> unit) ->
(request -> Response.t) ->
'aRun an HTTP server.
parameter sw Switch for resource management parameter net Eio network capability parameter clock Eio (wall) clock, used to enforce the connection timeouts parameter config Server configuration (default: default_config) parameter ws_handler WebSocket handler (required for Auto_websocket mode) parameter handler Request handler
val run_parallel :
sw:Eio.Switch.t ->
net:[> [> `Generic ] Eio.Net.ty ] Eio.Net.t ->
domain_mgr:[> Eio.Domain_manager.ty ] Eio.Domain_manager.t ->
clock:[> float Eio.Time.clock_ty ] Eio.Time.clock ->
?config:config ->
?ws_handler:(Websocket.t -> unit) ->
(request -> Response.t) ->
'aRun an HTTP server with multi-domain parallelism.
parameter sw Switch for resource management parameter net Eio network capability parameter domain_mgr Eio domain manager parameter clock Eio (wall) clock, used to enforce the connection timeouts parameter config Server configuration (default: default_config) parameter ws_handler WebSocket handler (required for Auto_websocket mode) parameter handler Request handler