Repository navigation
Clients
A Client is a sender plus the defaults every request through it gets: a base address, default
headers, a timeout (100 seconds unless set), a response buffer limit (64 MiB), and a default version
and policy. Client.create wraps any sender, Client.over a transport, and Client.pooled builds a
long-lived client over a pooled transport of its own. Relative addresses resolve against the base
address the way links do, so a base ending in / keeps its last segment.
module Ex03Clients
import Std.Concurrent as Concurrent
import Std.Concurrent.Cancel as Cancel
import Std.Http as Http
import Std.Io as Io
import Std.Result as Result
import Std.Sync as Sync
import PuduLangHttpClient as HttpClient
import PuduLangHttpClient.Client as Client
import PuduLangHttpClient.Content as Content
import PuduLangHttpClient.Request as Request
import PuduLangHttpClient.Response as Response
import PuduLangHttpClient.Stub as Stub
fn slow(request: Request.Request, token: Cancel.Token) -> HttpClient.Outcome[Response.Response] {
match Cancel.pause(&token, 5000) {
case Ok(_) => Ok(Response.answer(&request, 200))
case Err(Cancel.Requested(reason)) => Err(HttpClient.Cancelled(reason))
case Err(Cancel.DeadlineExceeded) => Err(HttpClient.TimedOut(0))
}
}
export fn main() -> Int {
let stub = Stub.create()
Stub.on(&stub, Http.Get, "/v2/items", |request: Request.Request| Ok(Response.answer(&request, 200).withText(request.uri + " as " + show(Request.header(&request, "user-agent")))))
Stub.respond(&stub, Http.Post, "/v2/items", 201, "created")
let client = Client.create(Stub.send(&stub)).withBaseAddress("https://api.test/v2/").withDefaultHeader("user-agent", "ex03").withTimeout(2000)
let listed = Client.getText(&client, "items?page=2")
let posted = Result.map(Client.post(&client, "items", Content.plain("tea")), |r: Response.Response| r.status.code)
let pieces = Sync.cell(0)
let streamed = Client.stream(&client, "items", fn(piece: Bytes) -> Bool {
let _counted = Sync.set(&pieces, Result.unwrapOr(Sync.get(&pieces), 0) + piece.length())
true
})
let timedOut = Client.get(&Client.create(slow).withTimeout(50), "https://api.test/")
let pending = Client.create(slow)
let result = Sync.cell(Ok(0))
let worker = match Concurrent.start(fn() -> () { let _kept = Sync.set(&result, Result.map(Client.get(&pending, "https://api.test/"), |r: Response.Response| r.status.code)) }) {
case Ok(started) => started
case Err(problem) => panic(show(problem))
}
let _waited = Concurrent.sleep(30)
Client.cancelPending(&pending)
let _joined = Concurrent.join(&worker)
let _a = Io.writeLine(show(listed))
let _b = Io.writeLine(show(posted) + " streamed " + show(Result.unwrapOr(Sync.get(&pieces), 0)) + " bytes, ok: " + show(Result.isOk(&streamed)))
let _c = Io.writeLine(show(Result.map(timedOut, |r: Response.Response| r.status.code)) + " " + show(Result.unwrapOr(Sync.get(&result), Ok(0))))
if listed == Ok("https://api.test/v2/items?page=2 as Some(\"ex03\")") && posted == Ok(201) && Result.map(timedOut, |r: Response.Response| r.status.code) == Err(HttpClient.TimedOut(50)) { 0 } else { 1 }
}
Check it and run it:
pudu check src/Ex03Clients.pudu
pudu run src/Ex03Clients.puduOutput:
Ok("https://api.test/v2/items?page=2 as Some(\"ex03\")")
Ok(201) streamed 41 bytes, ok: true
Err(TimedOut(50)) Err(Cancelled("the client cancelled its pending requests"))
Client.send and sendWith (with a cancellation token) send any request; get, head, post,
put, patch, and delete answer the response whatever its status; getText and getBytes
answer the body of a success; stream hands the body to a receiver as it arrives. The client's own
timeout answers TimedOut(timeout), and cancelPending stops every request in flight while later
ones run.
Build a client at startup and pass the same value to every part of the program. Every copy sends
through the same pooled transport, so the parts share connections — the
SharedClient
program hands one client to a catalog and an order service and serves both over one connection.
Build it once: a function that builds a client each time builds a new pool each time. Give a
long-lived client withPooledConnectionLifetime so it still sees addresses change.
- Previous: Requests And Responses
- Next: Named And Typed Clients
- Reference Index