How do I send an HTTP request from EK9 and handle the response?
← Web Services · Ref: Q2241
Use HTTPClient. It holds connections, so a try owns it and closes it: 'try' then '-> client <- HTTPClient()' (or 'HTTPClient(config)'). Build an immutable HTTPClientRequest from an HTTPMethod - one of GET, POST, PUT, DELETE, HEAD, PATCH, OPTIONS, written exactly - and a URL, never text: the URL comes from the environment through Networking().urlFromEnv, or from run-time text through Networking().urlOf, and is derived with its withers. 'withHeader(headerName, headerValue)' and 'withBody(content)' return a new request; a header the client sets itself (Host, Content-Length, Connection, Expect, Upgrade, Transfer-Encoding) or one holding a line break is refused and gives an UNSET request. 'client.send(request)' blocks - cheap on virtual threads, so there is no async form - and returns Result of (HTTPClientResponse, HTTPError). Any HTTP status, 404 and 500 included, is a response: read 'status()', 'header(headerName)' (any case; names are lower case in 'headers()'), and the body as Bytes, decoded only when you say how - 'bodyText("UTF-8")' or 'bodyJson()', a Result of (JSON, String). Only a transport failure, a URL the policy refuses or a protocol error is an HTTPError: 'category()' is an HTTPErrorCategory (DNS_RESOLUTION, CONNECT_TIMEOUT, READ_TIMEOUT, TLS_FAILURE, CONNECTION_REFUSED, TOO_MANY_REDIRECTS, PROTOCOL_ERROR, REFUSED_BY_POLICY) you compare with '==', and nothing is thrown. HTTPClientConfig sets the connect and request timeouts (10 and 30 seconds by default), an explicit proxy, and whether redirects are followed - they are NOT by default. The client speaks HTTP/1.1 and always verifies TLS. HTTPClient is a gated type: a package under capability enforcement declares 'org.ek9.lang::HTTPClient'.
Example
#!ek9 defines module qa.webservices.httpclient defines service Orders for :/orders open operator += for :/ -> content as String :=: CONTENT <- response as HTTPResponse: (received: content) with trait of HTTPResponse override status() as pure <- rtn as Integer: 201 override content() <- rtn as Bytes: Bytes().withUtf8(`{"received":${received}}`) override contentType() as pure <- rtn as String: "application/json" default operator ? defines function placeOrder() -> port as Integer stdout <- Stdout() base <- Networking().urlOf(`http://127.0.0.1:${port}/orders`) post <- HTTPClientRequest(HTTPMethod("POST"), base) json <- post.withHeader("Content-Type", "application/json") order <- json.withBody(Bytes().withUtf8(`{"item":"tea","quantity":2}`)) try -> client <- HTTPClient(HTTPClientConfig().withRequestTimeout(PT5S)) result <- client.send(order) if result.isOk() response <- result.ok() stdout.println(`status ${response.status()}`) stdout.println(`content-type ${response.header("content-type")}`) parsed <- response.bodyJson() if parsed.isOk() stdout.println(`json ${parsed.ok()}`) else if result.isError() stdout.println(`not sent: ${result.error()}`) //The client sets Host itself, so a request that tries to is unset. redirected <- order.withHeader("Host", "elsewhere.example") stdout.println(`request with a Host header set: ${redirected?}`) defines application OrdersApp register Orders() defines program SendAnHttpRequest() with application of OrdersApp properties <- NetworkProperties(0, true) try -> httpServer <- HTTP(properties) caller <- (properties) is HTTPReadyHandler as function placeOrder(properties.port) server.stop() httpServer.serveUntilComplete(caller)
Common mistakes
E08275 — A URL written into the source is an endpoint hard-coded into the program, so urlOf refuses text the compiler can know. Here the port is only known at run time; an endpoint you call belongs in the environment, read with Networking().urlFromEnv. See ek9 -h E08275 for details.
Incorrect:
base <- Networking().urlOf("http://127.0.0.1:8080/orders")
Correct:
base <- Networking().urlOf(`http://127.0.0.1:${port}/orders`)
Other ways to ask this
- How do I call a REST API from EK9?
- How do I POST JSON with HTTPClient in EK9?
- Does EK9 have an HTTP client?
- How does EK9 report a failed HTTP request?
- Why does HTTPClient.send return a Result in EK9?
Coming from another language?
Java: java.net.http.HttpClient throws IOException, HttpTimeoutException and SSLHandshakeException, so a forgotten catch is a crash; its builder follows redirects and reads proxy settings from system properties. Python: requests raises ConnectionError/Timeout and decodes text with a guessed encoding. Go: http.Get returns (resp, err) but nothing forces the err check. In EK9 send returns a Result the compiler makes you check, the body is decoded only with an explicit encoding, redirects and proxies happen only when configured, and the URL is a URL from the environment or run-time data - never a literal.
Keywords: HTTPClient, HTTPClientConfig, HTTPClientRequest, HTTPClientResponse, HTTPError, HTTPErrorCategory, HTTPMethod, POST JSON, REST client, bodyJson, http request, send