March Docs

HttpServer

HttpServer module: server-side HTTP types and pipeline runner.

Built on top of Http (for Method, Header types) and WebSocket modules. Provides the Conn type, pipeline composition, and server startup.

Usage: HttpServer.new(4000) |> HttpServer.max_connections(500) |> HttpServer.idle_timeout(30) |> HttpServer.plug(logger) |> HttpServer.plug(router) |> HttpServer.listen()

Types

typeUpgradeUpgrade = NoUpgrade | WebSocketUpgrade(WsSocket -> Unit)#

Upgrade type for WebSocket connections

typeConnConn = Conn(#

The Conn type: holds everything about a request-response cycle. Modeled on Plug.Conn from Elixir. The method field is an Atom (e.g. :get, :post) — interned i64 hash, allocation-free and safe for pattern matching in compiled mode.

typeServerServer = Server(#

Server config type

Functions

fnassignassign(conn, key, value)#

Store a key/value pair in the conn's assigns (a per-request store plugs use to pass data along the pipeline). Later assignments to the same key shadow earlier ones for get_assign; all are kept in assigns.

fnassignsassigns(conn)#

All assigns as a List((String, String)), most recent first (see assign).

fnbind_hostbind_host(host : String) : Unit#

Bind later servers to host, a dotted IPv4 address such as "127.0.0.1", instead of every interface. Call it before listen or spawn_n; it panics on anything that isn't an IPv4 address.

fnconn_upgradeconn_upgrade(conn)#

The conn's upgrade request: NoUpgrade (the default), or WebSocketUpgrade(handler) to hand the socket to a WebSocket handler after the pipeline.

fnfdfd(conn)#

The underlying TCP socket file descriptor of the conn.

fnget_assignget_assign(conn, key)#

Look up a value stored with assign: Some(value) for the most recent assignment to key, or None.

Examples

let conn = HttpServer.assign(conn, "user", "ann")
HttpServer.get_assign(conn, "user")   -- Some("ann")
fnget_req_headerget_req_header(conn, name)#

Look up a request header by name, case-insensitively. Returns Some(value) for the first match in req_headers order, or None.

Examples

match HttpServer.get_req_header(conn, "Authorization") do
  Some(v) -> check_token(conn, v)
  None -> HttpServer.text(conn, 401, "unauthorized")
end
fnget_resp_headerget_resp_header(conn, name)#

Look up a response header set so far, case-insensitively. Because put_resp_header prepends, this returns the most recently put value.

fnhalthalt(conn)#

Mark the conn halted without setting a response, so remaining plugs are skipped. The status stays whatever it was (0 if never set). A later send_resp on the halted conn panics.

fnhaltedhalted(conn)#

True once the conn has been responded to or halted; run_pipeline then skips the remaining plugs.

fnhtmlhtml(conn, resp_status, body)#

Respond with an HTML body: adds content-type: text/html; charset=utf-8 and calls send_resp. Panics if the conn is already halted.

fnidle_timeoutidle_timeout(server, secs)#

Set the idle keep-alive timeout in seconds (default 60).

fnjsonjson(conn, resp_status, body)#

Respond with a JSON body (already-encoded String): adds content-type: application/json and calls send_resp. Panics if the conn is already halted.

fnlistenlisten(server)#

Bind the configured port and serve requests forever with the plug pipeline (via the runtime's http_server_listen). Each request becomes a Conn; the pipeline's result is written back as the response, or handed to the WebSocket handler if conn_upgrade is a WebSocketUpgrade. Does not return in normal operation. Requires IO.NetListen.

fnmax_connectionsmax_connections(server, n)#

Set the maximum number of concurrent connections (default 1000).

fnmethodmethod(conn)#

Return the request method atom (:get, :post, …) of a Conn.

fnmethod_to_stringmethod_to_string(m)#

Convert a request-method atom to its uppercase string name, suitable for logging, response bodies, or rebuilding a request line. Returns "UNKNOWN" for any atom that is not one of the recognized HTTP verbs. Atoms use lowercase by convention: :get, :post, :put, :patch, :delete, :head, :options, :trace, :connect.

Examples

march> HttpServer.method_to_string(:get)
"GET"
march> HttpServer.method_to_string(:patch)
"PATCH"
march> HttpServer.method_to_string(:not_a_verb)
"UNKNOWN"
fnnewnew(port)#

A server config listening on port with no plugs, max_connections

  1. Nothing is bound until listen or

spawn_n.

Examples

HttpServer.new(4000)
|> HttpServer.plug(fn conn -> HttpServer.text(conn, 200, "ok"))
|> HttpServer.listen()
fnpathpath(conn)#

The raw request path without the query string, e.g. /users/42.

fnpath_infopath_info(conn)#

The request path split on / with empty segments removed: /users/42/ gives a two-element list of users and 42.

fnpeer_addrpeer_addr(conn)#

Return the numeric IP address of the connected peer (the socket's remote address), e.g. "203.0.113.7" or "2001:db8::1".

This is the transport-level address — it cannot be spoofed by request headers, unlike X-Forwarded-For. Behind a reverse proxy it is the proxy's address, not the end client's.

Returns "" when no live socket backs the conn (e.g. test conns).

fnplugplug(server, p)#

Append a plug (Conn -> Conn) to the end of the server's pipeline. Plugs run in the order they were added.

fnput_resp_headerput_resp_header(conn, name, value)#

Add a response header. The header is prepended and existing headers with the same name are NOT replaced, so putting a name twice sends it twice.

fnquery_stringquery_string(conn)#

The raw query string after the ?, e.g. page=1&limit=10; empty when the URL has none.

fnredirectredirect(conn, url)#

Respond 302 Found with a location: url header and an empty body. Panics if the conn is already halted.

fnreq_bodyreq_body(conn)#

The request body (the Content-Length bytes) as a String; empty when there is none.

fnreq_headersreq_headers(conn)#

All request headers as a List(Header). The runtime builds the list by prepending, so it is in reverse wire order.

fnresp_bodyresp_body(conn)#

The response body set so far; empty until a response is sent.

fnresp_headersresp_headers(conn)#

The response headers set so far, most recently put first.

fnrun_pipelinerun_pipeline(conn, plugs)#

Run a list of plugs (Conn -> Conn functions) in order, threading the conn through. Before each plug the conn is checked: once it is halted the remaining plugs are skipped and the halted conn is returned.

Examples

HttpServer.run_pipeline(conn, [logger, auth, router])
fnsend_respsend_resp(conn, resp_status, body)#

Set the response status and body and mark the conn halted. This is the primary way to respond; later plugs in the pipeline are skipped. The response is written by the runtime after the pipeline returns.

Panics if the conn is already halted (by send_resp, a helper such as text/json, or halt): a double response is always a handler bug, and silently overwriting the first reply would mask it.

Examples

conn
|> HttpServer.put_resp_header("cache-control", "no-store")
|> HttpServer.send_resp(200, "ok")
fnspawn_nspawn_n(server, n)#

Start the server in a forked subprocess that handles exactly n requests and then exits. Returns a handle (the child PID) for wait_for. A readiness pipe makes the call return only after the child has bound the port, so the caller can connect immediately. Intended for tests.

Examples

let h = HttpServer.new(18080)
  |> HttpServer.plug(fn conn -> HttpServer.text(conn, 200, "pong"))
  |> HttpServer.spawn_n(1)
let resp = HttpClient.get(HttpClient.new_client(), "http://127.0.0.1:18080/")
HttpServer.wait_for(h)
fnstatusstatus(conn)#

The response status set so far: 0 until send_resp or a helper sets it. A conn returned with status 0 is sent as 200.

fntexttext(conn, resp_status, body)#

Respond with a plain-text body: adds content-type: text/plain; charset=utf-8 and calls send_resp. Panics if the conn is already halted.

Examples

fn hello(conn) do
  HttpServer.text(conn, 200, "hello, " ++ HttpServer.path(conn))
end
fnwait_forwait_for(handle)#

Block until the server subprocess started by spawn_n (given its handle) exits.