http - an HTTP/1.1 client
Import with import "http.j" as http;. An HTTP/1.1 client over the net
system library: build a request (method, URL, headers, body), send it, and read
the response back into a Response (status, headers, body). http:// connects
in the clear; https:// connects with TLS (net.connectTLS). Because it uses
net, this module needs the default jennifer binary.
On
jennifer-tiny: “needs the defaultjenniferbinary” refers to the stock tiny build, which ships without a network driver - not a TinyGo limitation. Ajennifer-tinyrebuilt with a network stack runs this module too; see the note onnetand TinyGo.
import "http.j" as http;
def r as http.Response init http.get("http://example.com/", {});
io.printf("status %d\n%s\n", $r.status, $r.body);
def sent as http.Response init http.post("https://api.example.com/items",
"application/json", "{\"name\":\"ada\"}", {"Authorization": "Bearer xyz"});
Runnable: examples/modules/http_demo.j.
Surface
headers is a map of string to string (pass {} for none); a body is a
string ("" for none).
| Call / type | Notes |
|---|---|
http.Response | status (int), statusText, headers (lowercased keys), body. |
http.request(method, url, headers, body) | The general request (default idle timeout); returns a Response. |
http.requestWith(method, url, headers, body, timeoutMs) | As request, with an explicit per-read idle timeout (0 = none). |
http.get(url, headers) | GET. |
http.post(url, contentType, body, headers) | POST; sets Content-Type. |
http.put(url, contentType, body, headers) | PUT; sets Content-Type. |
http.patch(url, contentType, body, headers) | PATCH (partial update); sets Content-Type. |
http.delete(url, headers) | DELETE. |
http.head(url, headers) | HEAD (status + headers, no body). |
http.options(url, headers) | OPTIONS (capability probe; read the Allow header). |
http.header(resp, name) | Read a response header case-insensitively, or "" if absent. |
The shortcuts are thin wrappers over request, which is method-agnostic -
it sends whatever method string you pass. So a method without a shortcut still
works: http.request("TRACE", url, {}, "") and the like. The one method that is
not supported is CONNECT: it is the HTTP tunneling primitive (after a
200 the socket becomes a raw bidirectional tunnel), which needs a connection
hand-off this request/response-then-close client does not do.
URLs and headers
A URL is parsed into scheme / host / port / path: http:// defaults to port 80,
https:// to 443, an explicit :port overrides, and the path (with any query
string) defaults to /. The Host header is set automatically (with the port
when non-default), along with Connection: close and a default User-Agent
(overridable by supplying your own).
Response header names are lowercased (HTTP header names are
case-insensitive), so $r.headers["content-type"] works regardless of how the
server cased it; http.header($r, "Content-Type") does the case-folding for you.
Response body and framing
The client reads the whole response (it sends Connection: close, so the server
closes when done) and decodes the body, handling both framings:
- Content-Length - the body is taken as exactly that many bytes.
- Transfer-Encoding: chunked - the chunks are decoded and concatenated.
The body is returned as text (UTF-8). A JSON / HTML / XML body round-trips exactly (the whole body is decoded as one unit, so it is byte-exact); a binary body (an image, a gzip stream) is not decodable to a string and raises an error
- a
bytesbody accessor is a planned follow-on.
Timeouts
Every request carries a per-read idle timeout (default 30 s): the deadline is
re-armed before each read, so a server that accepts the connection and then
stalls (or a hung endpoint) fails with a catchable read timed out error instead
of blocking the caller forever. This is the difference between a slow dependency
degrading one request and one exhausting your process on a pool of hung
connections. Pass a different value (in milliseconds) with http.requestWith; a
0 disables the timeout for that request (e.g. a long streaming download):
try {
def r as http.Response init http.requestWith("GET", url, {}, "", 5000); # 5 s
} catch (e) {
io.printf("request timed out or failed\n");
}
The timeout bounds each read, not the whole transfer, so a large but steady download is fine while a stalled one is cut off.
Errors
A malformed response, a body that is not valid UTF-8, or a network failure
raises a positioned error (a thrown Error for a malformed response, kind
"http"; a read timed out error on an idle-timeout); wrap a request in try /
catch to handle a down or slow server. A non-2xx status is not an error - a
404 or 500 comes back as a normal Response with that status, for the caller
to branch on.
Out of scope
- Redirects are returned, not followed. A 3xx comes back with its
Locationheader; follow it yourself (auto-follow with a hop limit is a later add). - One request per connection. No keep-alive, no connection pool.
- No cookie jar, no automatic decompression, no multipart builder. Set the headers and body you need directly.
- Text bodies. Binary responses need the planned
bytesaccessor.
See also
- net.md - the transport (and its TLS)
httpbuilds on. - json.md - encode / decode JSON request and response bodies.
- modules/index.md - the module catalog and import rules.