Eigenstate : libhttp

Summary

The http package implements code for a simple HTTP client and server. There

pkg http =
    type status = int
    type session
    type server

    /*
     * The full list of status codes is enumerated at the
     * bottom of this page.
     */

    type url = struct
        schema  : schema
        port    : uint16
        host    : byte[:]
        path    : byte[:]
        params  : (byte[:], byte[:])[:]
    ;;

    type req = struct
        url : url#
        hdrs    : (byte[:], byte[:])[:]
        err : std.option(err)
        method  : method
    ;;

    type resp = struct
        status  : status        /* http status */
        hdrs    : (byte[:], byte[:])[:] /* list of headers */
        len : std.size      /* length of response */
        err : std.option(err)   /* error, if present */
        reason  : byte[:]       /* reason text for error */
        body    : byte[:]       /* body of response or error */
    ;;

    type err = union
        `Ehttp int  /* http error */
        `Eunsupp    /* unsupported feature */
        `Econn      /* connection lost */
        `Ehdr       /* invalid header */
        `Eproto     /* protocol error */
        `Eshort     /* truncated response */
        `Egarbled   /* syntax error */
        `Eenc       /* encoding error */
        `Ewat       /* unknown error */
    ;;

    type schema = union
        `Http
        `Https
    ;;

    type method = union
        `Get
        `Head
        `Put
        `Post
        `Delete
        `Trace
        `Options
    ;;

    type encoding = union
        `Length
        `Chunked
        `Compress
        `Deflate
        `Gzip
    ;;
;;

These types are shared between both the client and server APIs. Currently, there are two major features missing: HTTPS, and

type status = int

This is the HTTP status response. All the valid types are listed later down this page.

type session

This represents a single session with a server or client. Multiple requests can reuse the same connection, and all requests on a connection are sent in order.

type server

This is the HTTP server. It implements a simple, multithreaded HTTP server. It currently uses a simple thread per connection model.

Client API

pkg http =
    /* convenience api */
    const get   : (s : session#, r : byte[:] -> std.result(byte[:], err))
    const head  : (s : session#, r : byte[:] -> std.result(byte[:], err))
    const put   : (s : session#, r : byte[:], data : byte[:] -> std.result(byte[:], err))
    const post  : (s : session#, r : byte[:], data : byte[:] -> std.result(byte[:], err))
    const delete    : (s : session#, r : byte[:] -> std.result(byte[:], err))
    const options   : (s : session#, r : byte[:] -> std.result(byte[:], err))
    const trace : (s : session#, r : byte[:] -> std.result(byte[:], err))

    /* request based versions */
    const getreq    : (s : session#, r : req# -> std.result(resp#, err))
    const headreq   : (s : session#, r : req# -> std.result(resp#, err))
    const putreq    : (s : session#, r : req#, data : byte[:] -> std.result(resp#, err))
    const postreq   : (s : session#, r : req#, data : byte[:] -> std.result(resp#, err))
    const deletereq : (s : session#, r : req# -> std.result(resp#, err))
    const optionsreq    : (s : session#, r : req# -> std.result(resp#, err))
    const tracereq  : (s : session#, r : req# -> std.result(resp#, err))

    const freeresp  : (r : resp# -> void)
;;

Server API

pkg http =
    const announce  : (ds : byte[:] -> std.result(server#, err))
    const shutdown  : (srv : server# -> void)
    const serve : (srv : server#, fn : (srv : server#, s : session#, req : req# -> void) -> void)
    const respond   : (srv : server#, sess : session#, resp : resp# -> void)
;;

The server is an plain, mutlithreaded server. Multiple requests can be processed in parallel.

const announce  : (ds : byte[:] -> std.result(server#, err))

Announce creates the server on the dialstring ds. This server does not begin to accept connections until the serve call is hit.

const shutdown  : (srv : server# -> void)

Shutdown waits for all in-flight requests to be closed, then releases all of the associated resources.

const serve : (srv : server#, fn : (srv : server#, s : session#, req : req# -> void) -> void)

Serve starts handling requests. For every request, it calls the callback function fn. The callback is responsible for routing the requests to the appropriate resources, and generating the response.

Status Codes

pkg http =
    const Continue          : status = 100 /* RFC 7231, 6.2.1*/
    const SwitchingProtocols    : status = 101 /* RFC 7231, 6.2.2*/
    const Processing        : status = 102 /* RFC 2518, 10.1*/

    const Ok            : status = 200 /* RFC 7231, 6.3.1*/
    const Created           : status = 201 /* RFC 7231, 6.3.2*/
    const Accepted          : status = 202 /* RFC 7231, 6.3.3*/
    const NonAuthoritativeInfo  : status = 203 /* RFC 7231, 6.3.4*/
    const NoContent         : status = 204 /* RFC 7231, 6.3.5*/
    const ResetContent      : status = 205 /* RFC 7231, 6.3.6*/
    const PartialContent        : status = 206 /* RFC 7233, 4.1*/
    const Multi         : status = 207 /* RFC 4918, 11.1*/
    const AlreadyReported       : status = 208 /* RFC 5842, 7.1*/
    const IMUsed            : status = 226 /* RFC 3229, 10.4.1*/

    const MultipleChoices       : status = 300 /* RFC 7231, 6.4.1*/
    const MovedPermanently      : status = 301 /* RFC 7231, 6.4.2*/
    const Found         : status = 302 /* RFC 7231, 6.4.3*/
    const SeeOther          : status = 303 /* RFC 7231, 6.4.4*/
    const NotModified       : status = 304 /* RFC 7232, 4.1*/
    const UseProxy          : status = 305 /* RFC 7231, 6.4.5*/
    const TemporaryRedirect     : status = 307 /* RFC 7231, 6.4.7*/
    const PermanentRedirect     : status = 308 /* RFC 7538, 3*/

    const BadRequest        : status = 400 /* RFC 7231, 6.5.1*/
    const Unauthorized      : status = 401 /* RFC 7235, 3.1*/
    const PaymentRequired       : status = 402 /* RFC 7231, 6.5.2*/
    const Forbidden         : status = 403 /* RFC 7231, 6.5.3*/
    const NotFound          : status = 404 /* RFC 7231, 6.5.4*/
    const MethodNotAllowed      : status = 405 /* RFC 7231, 6.5.5*/
    const NotAcceptable     : status = 406 /* RFC 7231, 6.5.6*/
    const ProxyAuthRequired     : status = 407 /* RFC 7235, 3.2*/
    const RequestTimeout        : status = 408 /* RFC 7231, 6.5.7*/
    const Conflict          : status = 409 /* RFC 7231, 6.5.8*/
    const Gone          : status = 410 /* RFC 7231, 6.5.9*/
    const LengthRequired        : status = 411 /* RFC 7231, 6.5.10*/
    const PreconditionFailed    : status = 412 /* RFC 7232, 4.2*/
    const RequestEntityTooLarge : status = 413 /* RFC 7231, 6.5.11*/
    const RequestURITooLong     : status = 414 /* RFC 7231, 6.5.12*/
    const UnsupportedMediaType  : status = 415 /* RFC 7231, 6.5.13*/
    const RangeNotSatisfiable   : status = 416 /* RFC 7233, 4.4*/
    const ExpectationFailed     : status = 417 /* RFC 7231, 6.5.14*/
    const Teapot            : status = 418 /* RFC 7168, 2.3.3*/
    const UnprocessableEntity   : status = 422 /* RFC 4918, 11.2*/
    const Locked            : status = 423 /* RFC 4918, 11.3*/
    const FailedDependency      : status = 424 /* RFC 4918, 11.4*/
    const UpgradeRequired       : status = 426 /* RFC 7231, 6.5.15*/
    const PreconditionRequired  : status = 428 /* RFC 6585, 3*/
    const TooManyRequests       : status = 429 /* RFC 6585, 4*/
    const HeaderFieldsTooLarge  : status = 431 /* RFC 6585, 5*/
    const UnavailableForLegal   : status = 451 /* RFC 7725, 3*/

    const InternalServerError   : status = 500 /* RFC 7231, 6.6.1*/
    const NotImplemented        : status = 501 /* RFC 7231, 6.6.2*/
    const BadGateway        : status = 502 /* RFC 7231, 6.6.3*/
    const ServiceUnavailable    : status = 503 /* RFC 7231, 6.6.4*/
    const GatewayTimeout        : status = 504 /* RFC 7231, 6.6.5*/
    const VersionNotSupported   : status = 505 /* RFC 7231, 6.6.6*/
    const VariantAlsoNegotiates : status = 506 /* RFC 2295, 8.1*/
    const InsufficientStorage   : status = 507 /* RFC 4918, 11.5*/
    const LoopDetected      : status = 508 /* RFC 5842, 7.2*/
    const NotExtended       : status = 510 /* RFC 2774, 7*/
    const NetworkAuthRequired   : status = 511 /* RFC 6585, 6*/
;;

These are standard HTTP codes. For detailed meanings, see the relevant RFCs.

Example Client

const main = {args
    var u, s, r

    cmd = std.optparse(args, &[
        .argdesc = "url...",
    ])
    for url : cmd.args
    u = std.try(http.parseurl(url))
    s = std.try(http.mksession(u.schema, u.host, u.port))
    r = std.try(http.get(s, u))
    std.put("{}\n", r.body)
    http.freeresp(r)
}

Example Server

A simple server that serves the contents of a directory to localhost. This program doens't attempt to sanitize paths, so it's not recommended to put it directly on the internet.

use std
use http

const main = {
    var srv, cmd

    match http.announce("tcp!localhost!8080")
    | `std.Ok s:    srv = s
    | `std.Err e:   std.fatal("unable to announce: {}\n", e)
    ;;

    http.serve(srv, handler)
}

const handler = {srv, sess, req
    std.put("Reading path {}\n", req.url.path)
    match req.url.path
    | "/ping":  respond(srv, sess, 200, "pong")
    | fspath:   showfile(srv, sess, req.url.path)
    ;;
}

const showfile = {srv, sess, path
    var eb : byte[128]
    var p

    p = std.pathcat(".", path)
    match std.slurp(p)
    | `std.Ok buf:
        respond(srv, sess, 200, buf)
        std.slfree(buf)
    | `std.Err e:
        respond(srv, sess, 404, std.bfmt(eb[:], "error reading {}: {}\n", p, e))
    ;;
    std.slfree(p)
}


const respond = {srv, sess, status, body
    var resp

    resp = std.mk([
        .status=status,
        .hdrs = [][:],
        .len = 0,
        .err = `std.None,
        .reason = "",
        .body = body,
        .enc = `http.Length
    ])
    http.respond(srv, sess, resp)
    std.free(resp)
}

Status Codes

const Continue          : status = 100 /* RFC 7231, 6.2.1*/
const SwitchingProtocols    : status = 101 /* RFC 7231, 6.2.2*/
const Processing        : status = 102 /* RFC 2518, 10.1*/

const Ok            : status = 200 /* RFC 7231, 6.3.1*/
const Created           : status = 201 /* RFC 7231, 6.3.2*/
const Accepted          : status = 202 /* RFC 7231, 6.3.3*/
const NonAuthoritativeInfo  : status = 203 /* RFC 7231, 6.3.4*/
const NoContent         : status = 204 /* RFC 7231, 6.3.5*/
const ResetContent      : status = 205 /* RFC 7231, 6.3.6*/
const PartialContent        : status = 206 /* RFC 7233, 4.1*/
const Multi         : status = 207 /* RFC 4918, 11.1*/
const AlreadyReported       : status = 208 /* RFC 5842, 7.1*/
const IMUsed            : status = 226 /* RFC 3229, 10.4.1*/

const MultipleChoices       : status = 300 /* RFC 7231, 6.4.1*/
const MovedPermanently      : status = 301 /* RFC 7231, 6.4.2*/
const Found         : status = 302 /* RFC 7231, 6.4.3*/
const SeeOther          : status = 303 /* RFC 7231, 6.4.4*/
const NotModified       : status = 304 /* RFC 7232, 4.1*/
const UseProxy          : status = 305 /* RFC 7231, 6.4.5*/
const TemporaryRedirect     : status = 307 /* RFC 7231, 6.4.7*/
const PermanentRedirect     : status = 308 /* RFC 7538, 3*/

const BadRequest        : status = 400 /* RFC 7231, 6.5.1*/
const Unauthorized      : status = 401 /* RFC 7235, 3.1*/
const PaymentRequired       : status = 402 /* RFC 7231, 6.5.2*/
const Forbidden         : status = 403 /* RFC 7231, 6.5.3*/
const NotFound          : status = 404 /* RFC 7231, 6.5.4*/
const MethodNotAllowed      : status = 405 /* RFC 7231, 6.5.5*/
const NotAcceptable     : status = 406 /* RFC 7231, 6.5.6*/
const ProxyAuthRequired     : status = 407 /* RFC 7235, 3.2*/
const RequestTimeout        : status = 408 /* RFC 7231, 6.5.7*/
const Conflict          : status = 409 /* RFC 7231, 6.5.8*/
const Gone          : status = 410 /* RFC 7231, 6.5.9*/
const LengthRequired        : status = 411 /* RFC 7231, 6.5.10*/
const PreconditionFailed    : status = 412 /* RFC 7232, 4.2*/
const RequestEntityTooLarge : status = 413 /* RFC 7231, 6.5.11*/
const RequestURITooLong     : status = 414 /* RFC 7231, 6.5.12*/
const UnsupportedMediaType  : status = 415 /* RFC 7231, 6.5.13*/
const RangeNotSatisfiable   : status = 416 /* RFC 7233, 4.4*/
const ExpectationFailed     : status = 417 /* RFC 7231, 6.5.14*/
const Teapot            : status = 418 /* RFC 7168, 2.3.3*/
const UnprocessableEntity   : status = 422 /* RFC 4918, 11.2*/
const Locked            : status = 423 /* RFC 4918, 11.3*/
const FailedDependency      : status = 424 /* RFC 4918, 11.4*/
const UpgradeRequired       : status = 426 /* RFC 7231, 6.5.15*/
const PreconditionRequired  : status = 428 /* RFC 6585, 3*/
const TooManyRequests       : status = 429 /* RFC 6585, 4*/
const HeaderFieldsTooLarge  : status = 431 /* RFC 6585, 5*/
const UnavailableForLegal   : status = 451 /* RFC 7725, 3*/

const InternalServerError   : status = 500 /* RFC 7231, 6.6.1*/
const NotImplemented        : status = 501 /* RFC 7231, 6.6.2*/
const BadGateway        : status = 502 /* RFC 7231, 6.6.3*/
const ServiceUnavailable    : status = 503 /* RFC 7231, 6.6.4*/
const GatewayTimeout        : status = 504 /* RFC 7231, 6.6.5*/
const VersionNotSupported   : status = 505 /* RFC 7231, 6.6.6*/
const VariantAlsoNegotiates : status = 506 /* RFC 2295, 8.1*/
const InsufficientStorage   : status = 507 /* RFC 4918, 11.5*/
const LoopDetected      : status = 508 /* RFC 5842, 7.2*/
const NotExtended       : status = 510 /* RFC 2774, 7*/
const NetworkAuthRequired   : status = 511 /* RFC 6585, 6*/