Http
Http module: pure HTTP protocol types, constructors, and transforms.
This is Layer 1 of March's HTTP library. It contains no I/O — only data types and functions for building and inspecting HTTP requests and responses. Libraries that work with HTTP types depend on this alone.
Types
An HTTP request method. The nine standard methods are nullary constructors; Other(name) carries any extension method (PROPFIND, MKCOL, ...) verbatim. Render one for the wire with method_to_string.
The URL scheme a request targets: SchemeHttp (http://) or SchemeHttps (https://).
An HTTP status code. Status(n) wraps any integer, so non-standard codes such as Status(418) are representable. Read it with status_code, build the common ones with status_ok, status_not_found, ...; classify with is_success, is_redirect, is_client_error, ...
One header field: Header(name, value). Names keep the case they were created with; lookups (get_header, get_request_header) compare names case-insensitively. Header lists are plain List(Header) and may hold the same name more than once.
Why parse_url rejected a URL:
InvalidScheme(url): the URL starts with neitherhttp://norhttps://.MissingHost: nothing between the scheme and the first/,:or end.InvalidPort(host_and_port): the text after:is not an integer.MalformedUrl(url): reserved for other malformed input;parse_urldoes not currently produce it.
An HTTP request, generic over its body type: Request(method, scheme, host, port, path, query, headers, body).
port is None when the URL named none (use the scheme's default), query is the raw text after ? without the ?, and body is whatever the caller stores there: Unit for a request built by get/parse_url, a String or Bytes for one built by post. Prefer the accessors (method, host, path, ...) and the set_* transforms over matching on the constructor: its field order is an implementation detail.
An HTTP response, generic over its body type: Response(status, headers, body). Read it with response_status, response_status_code, response_headers, response_body, and get_header.
Functions
The request body: () for a request built by get/parse_url.
Examples
march> let req = Result.unwrap(Http.get("http://example.com:8080/a/b?x=1"))
march> Http.body(req)
()
march> match Http.post("http://example.com/api", "{}") do Ok(r) -> Http.body(r) | Err(_) -> "" end
"{}"A DELETE request for url, with a Unit body. Fails exactly when parse_url(url) does.
Examples
march> match Http.delete("http://example.com/items/1") do Ok(r) -> Http.method_to_string(Http.method(r)) | Err(_) -> "" end
"DELETE"
march> Http.delete("http://example.com:x/") == Err(InvalidPort("example.com:x"))
trueJoin (key, value) pairs into a query string k1=v1&k2=v2, in order.
Keys and values are inserted VERBATIM: nothing is percent-encoded, so a value containing &, =, #, a space or non-ASCII text produces a different query than intended. Use Uri.encode_query when the pairs are not already encoded.
Examples
march> Http.encode_query([("q", "march"), ("page", "2")])
"q=march&page=2"
march> Http.encode_query([])
""A GET request for url. The same as parse_url, named for symmetry with post, put, ...
Examples
march> match Http.get("http://example.com/x") do Ok(r) -> Http.method_to_string(Http.method(r)) | Err(_) -> "" end
"GET"
march> Http.get("example.com") == Err(InvalidScheme("example.com"))
trueThe value of the first response header named name, compared case-insensitively, or None. When a header repeats, only the first is returned; scan response_headers to see them all.
Examples
march> let resp = Response(Status(301), [Header("Location", "/new")], "moved")
march> Http.get_header(resp, "LOCATION")
Some("/new")
march> let resp = Response(Status(301), [Header("Location", "/new")], "moved")
march> Http.get_header(resp, "content-type")
None
march> Http.get_header(Response(Status(200), [Header("A", "1"), Header("a", "2")], ()), "a")
Some("1")The value of the first request header named name, compared case-insensitively, or None. set_header prepends, so after setting the same name twice this returns the most recent value.
Examples
march> let req = Result.unwrap(Http.get("http://example.com:8080/a/b?x=1"))
march> Http.get_request_header(Http.set_header(Http.set_header(req, "X-Id", "1"), "x-id", "2"), "X-ID")
Some("2")
march> let req = Result.unwrap(Http.get("http://example.com:8080/a/b?x=1"))
march> Http.get_request_header(req, "Accept")
NoneA HEAD request for url, with a Unit body. Fails exactly when parse_url(url) does.
Examples
march> match Http.head("http://example.com/") do Ok(r) -> Http.method_to_string(Http.method(r)) | Err(_) -> "" end
"HEAD"All request headers, most recently added first (set_header prepends). A request built by parse_url/get has none.
Examples
march> let req = Result.unwrap(Http.get("http://example.com:8080/a/b?x=1"))
march> Http.headers(req)
[]
march> let req = Result.unwrap(Http.get("http://example.com:8080/a/b?x=1"))
march> Http.headers(Http.set_header(Http.set_header(req, "Accept", "*/*"), "X-Id", "7"))
[Header("X-Id", "7"), Header("Accept", "*/*")]The host name, without scheme, port or path.
Examples
march> match Http.parse_url("https://api.example.com:8443/v1/users?page=2") do Ok(r) -> Http.host(r) | Err(_) -> "" end
"api.example.com"True for client-error 4xx statuses (caller's fault).
Examples
march> Http.is_client_error(Http.status_not_found())
true
march> Http.is_client_error(Http.status_server_error())
falseTrue for informational 1xx statuses.
Examples
march> Http.is_informational(Status(100))
true
march> Http.is_informational(Status(200))
falseTrue for redirect 3xx statuses.
Examples
march> Http.is_redirect(Http.status_moved())
true
march> Http.is_redirect(Http.status_ok())
falseTrue for server-error 5xx statuses (server's fault).
Examples
march> Http.is_server_error(Http.status_server_error())
true
march> Http.is_server_error(Http.status_not_found())
falseTrue for success 2xx statuses.
Examples
march> Http.is_success(Http.status_ok())
true
march> Http.is_success(Http.status_not_found())
falseThe request method.
Examples
march> match Http.parse_url("https://api.example.com:8443/v1/users?page=2") do Ok(r) -> Http.method_to_string(Http.method(r)) | Err(_) -> "" end
"GET"Render an HTTP method as its uppercase wire-format string. Other(s) returns its inner string verbatim — useful for non-standard extensions like MKCOL or PROPFIND (WebDAV) which an upstream parser may have wrapped in Other(...).
Examples
march> Http.method_to_string(Get)
"GET"
march> Http.method_to_string(Patch)
"PATCH"
march> Http.method_to_string(Other("MKCOL"))
"MKCOL"An OPTIONS request for url, with a Unit body. Fails exactly when parse_url(url) does.
Examples
march> match Http.options("http://example.com/") do Ok(r) -> Http.method_to_string(Http.method(r)) | Err(_) -> "" end
"OPTIONS"Parse an absolute http:// or https:// URL into a GET request with a Unit body and no headers.
The URL is split into host, optional :port, path (default "/") and the raw query after ?. Nothing is percent-decoded and nothing past the split is validated: a #fragment stays part of the path or query, user:pass@ is not recognised, and an IPv6 literal ([::1]) is not supported. The port must be an integer but is not range-checked.
Errors: InvalidScheme for any other scheme, MissingHost for an empty host, InvalidPort for a non-numeric port.
Examples
march> match Http.parse_url("https://api.example.com:8443/v1/users?page=2") do Ok(r) -> Http.host(r) | Err(_) -> "" end
"api.example.com"
march> match Http.parse_url("https://api.example.com:8443/v1/users?page=2") do Ok(r) -> Http.query(r) == Some("page=2") | Err(_) -> false end
true
march> Http.parse_url("ftp://example.com") == Err(InvalidScheme("ftp://example.com"))
trueA PATCH request for url carrying bdy. Fails exactly when parse_url(url) does.
Examples
march> match Http.patch("http://example.com/items/1", "data") do Ok(r) -> (Http.method_to_string(Http.method(r)), Http.body(r)) | Err(_) -> ("", "") end
("PATCH", "data")The path, starting with /; "/" when the URL had none. Excludes the query string.
Examples
march> match Http.parse_url("https://api.example.com:8443/v1/users?page=2") do Ok(r) -> Http.path(r) | Err(_) -> "" end
"/v1/users"The explicit port, or None when the URL named none (the scheme default, 80 or 443, applies).
Examples
march> let req = Result.unwrap(Http.get("http://example.com:8080/a/b?x=1"))
march> Http.port(req)
Some(8080)
march> match Http.get("https://example.com/") do Ok(r) -> Http.port(r) == None | Err(_) -> false end
trueA POST request for url carrying bdy. Fails exactly when parse_url(url) does. No Content-Type header is added; set one with set_header.
Examples
march> match Http.post("https://example.com/api", "{}") do Ok(r) -> (Http.method_to_string(Http.method(r)), Http.body(r), Http.headers(r)) | Err(_) -> ("", "", []) end
("POST", "{}", [])
march> match Http.post("https://:443/api", "{}") do Ok(_) -> "ok" | Err(MissingHost) -> "missing host" | Err(_) -> "other" end
"missing host"A PUT request for url carrying bdy. Fails exactly when parse_url(url) does.
Examples
march> match Http.put("http://example.com/items/1", "data") do Ok(r) -> (Http.method_to_string(Http.method(r)), Http.body(r)) | Err(_) -> ("", "") end
("PUT", "data")The raw query string after ? (without the ?), or None when the URL had none. Not decoded.
Examples
march> let req = Result.unwrap(Http.get("http://example.com:8080/a/b?x=1"))
march> Http.query(req)
Some("x=1")
march> match Http.get("http://example.com/a%20b?q=a%20b") do Ok(r) -> Http.query(r) == Some("q=a%20b") | Err(_) -> false end
trueThe response body.
Examples
march> let resp = Response(Status(301), [Header("Location", "/new")], "moved")
march> Http.response_body(resp)
"moved"All response headers, in the order they were stored.
Examples
march> let resp = Response(Status(301), [Header("Location", "/new")], "moved")
march> Http.response_headers(resp)
[Header("Location", "/new")]True when the response status is 3xx. Read the target with get_header(resp, "location").
Examples
march> let resp = Response(Status(301), [Header("Location", "/new")], "moved")
march> Http.response_is_redirect(resp)
true
march> Http.response_is_redirect(Response(Status(200), [], ()))
falseTrue when the response status is 2xx.
Examples
march> Http.response_is_success(Response(Status(204), [], ()))
true
march> let resp = Response(Status(301), [Header("Location", "/new")], "moved")
march> Http.response_is_success(resp)
falseThe response status.
Examples
march> let resp = Response(Status(301), [Header("Location", "/new")], "moved")
march> Http.response_status(resp)
Status(301)The response status as an integer, e.g. 200. Shorthand for status_code(response_status(resp)).
Examples
march> let resp = Response(Status(301), [Header("Location", "/new")], "moved")
march> Http.response_status_code(resp)
301The request scheme, SchemeHttp or SchemeHttps.
Examples
march> let req = Result.unwrap(Http.get("http://example.com:8080/a/b?x=1"))
march> Http.scheme(req)
SchemeHttp
march> match Http.get("https://example.com/") do Ok(r) -> Http.scheme(r) == SchemeHttps | Err(_) -> false end
trueReplace the body. The body type may change: a Request(Unit) from get becomes a Request(String) after set_body(req, "...").
Examples
march> let req = Result.unwrap(Http.get("http://example.com:8080/a/b?x=1"))
march> Http.body(Http.set_body(req, "payload"))
"payload"
march> let req = Result.unwrap(Http.get("http://example.com:8080/a/b?x=1"))
march> Http.body(Http.set_body(req, [1, 2, 3]))
[1, 2, 3]Prepend a header to req. The name and value are sanitized: NUL, CR, and LF bytes are removed to block CRLF-injection attacks even when the inputs come from untrusted sources (a name or value may become empty). An existing header of the same name is not removed; the new one shadows it for get_request_header.
Examples
march> let req = Result.unwrap(Http.get("http://example.com:8080/a/b?x=1"))
march> Http.get_request_header(Http.set_header(req, "Accept", "text/html"), "accept")
Some("text/html")
march> let req = Result.unwrap(Http.get("http://example.com:8080/a/b?x=1"))
march> Http.headers(Http.set_header(req, "X-Evil", "a\r\nSet-Cookie: x"))
[Header("X-Evil", "aSet-Cookie: x")]Replace the host name. The value is stored verbatim (not validated).
Examples
march> let req = Result.unwrap(Http.get("http://example.com:8080/a/b?x=1"))
march> Http.host(Http.set_host(req, "api.example.org"))
"api.example.org"Replace the request method, keeping every other field.
Examples
march> let req = Result.unwrap(Http.get("http://example.com:8080/a/b?x=1"))
march> Http.method_to_string(Http.method(Http.set_method(req, Delete)))
"DELETE"
march> let req = Result.unwrap(Http.get("http://example.com:8080/a/b?x=1"))
march> Http.method_to_string(Http.method(Http.set_method(req, Other("PROPFIND"))))
"PROPFIND"Replace the path. The query string is separate and is left unchanged; pass a path beginning with /.
Examples
march> let req = Result.unwrap(Http.get("http://example.com:8080/a/b?x=1"))
march> Http.path(Http.set_path(req, "/v2/items"))
"/v2/items"
march> let req = Result.unwrap(Http.get("http://example.com:8080/a/b?x=1"))
march> Http.query(Http.set_path(req, "/v2/items"))
Some("x=1")Set an explicit port (stored as Some(new_port)). The value is not range-checked.
Examples
march> let req = Result.unwrap(Http.get("http://example.com:8080/a/b?x=1"))
march> Http.port(Http.set_port(req, 443))
Some(443)
march> let req = Result.unwrap(Http.get("http://example.com:8080/a/b?x=1"))
march> Http.port(Http.set_port(req, 70000))
Some(70000)Replace the query string with encode_query(params); an empty list removes it (query becomes None). Like encode_query, this does not percent-encode.
Examples
march> let req = Result.unwrap(Http.get("http://example.com:8080/a/b?x=1"))
march> Http.query(Http.set_query(req, [("page", "3"), ("sort", "asc")]))
Some("page=3&sort=asc")
march> let req = Result.unwrap(Http.get("http://example.com:8080/a/b?x=1"))
march> Http.query(Http.set_query(req, []))
NoneReplace the request scheme. The port is left alone, so switching to SchemeHttps does not change an explicit :80.
Examples
march> let req = Result.unwrap(Http.get("http://example.com:8080/a/b?x=1"))
march> Http.scheme(Http.set_scheme(req, SchemeHttps))
SchemeHttps
march> let req = Result.unwrap(Http.get("http://example.com:8080/a/b?x=1"))
march> Http.port(Http.set_scheme(req, SchemeHttps))
Some(8080)Status 400 Bad Request.
Examples
march> Http.status_code(Http.status_bad_request())
400Extract the integer status code from a Status.
Examples
march> Http.status_code(Http.status_ok())
200
march> Http.status_code(Status(418))
418Status 201 Created.
Examples
march> Http.status_code(Http.status_created())
201Status 403 Forbidden.
Examples
march> Http.status_code(Http.status_forbidden())
403Status 302 Found.
Examples
march> Http.status_code(Http.status_found())
302Status 301 Moved Permanently.
Examples
march> Http.status_code(Http.status_moved())
301Status 204 No Content.
Examples
march> Http.status_code(Http.status_no_content())
204Status 404 Not Found.
Examples
march> Http.status_code(Http.status_not_found())
404Status 200 OK.
Examples
march> Http.status_code(Http.status_ok())
200Status 500 Internal Server Error.
Examples
march> Http.status_code(Http.status_server_error())
500