Repository navigation
The Transport
The transport is the primary handler: it keeps connections per origin and reuses them, follows
redirects, keeps cookies, decompresses gzip, goes through proxies, and bounds what a response may
cost. Its options are built one decision at a time from Transport.defaults().
module Ex06Transport
import Std.Compress.Gzip as Gzip
import Std.Concurrent as Concurrent
import Std.Http as Http
import Std.Http.Server as Server
import Std.Http.Server.Reply as Reply
import Std.Http.Server.Route as Route
import Std.Io as Io
import Std.Result as Result
import PuduLangHttpClient as HttpClient
import PuduLangHttpClient.Client as Client
import PuduLangHttpClient.Cookies as Cookies
import PuduLangHttpClient.Response as Response
import PuduLangHttpClient.Transport as Transport
fn serve() -> (Server.Running, Str) {
let running = match Server.start("127.0.0.1", 0) {
case Ok(found) => found
case Err(problem) => panic(show(problem))
}
let server = Server.server(&Route.routing(&[
Route.get("/old", |_request: Route.Request| Http.Response{status: Http.status(301), headers: [("location", "/new")], body: "", binaryBody: None}),
Route.get("/new", |_request: Route.Request| Http.Response{..Reply.text(200, "moved here"), headers: [("set-cookie", "visited=yes; Path=/")]}),
Route.get("/report", |_request: Route.Request| Http.Response{..Reply.bytes(200, "text/plain", &Gzip.compressText("a long report", &Gzip.config())), headers: [("content-type", "text/plain"), ("content-encoding", "gzip")]}),
Route.get("/big", |_request: Route.Request| Reply.text(200, "0123456789"))
]))
let _serving = Concurrent.start(fn() -> () { let _ran = Server.run(&server, &running, 0) })
(running, "http://127.0.0.1:" + show(Server.portOf(&running)) + "/")
}
export fn main() -> Int {
let (running, base) = serve()
let jar = Cookies.jar()
let options = Transport.defaults()
.withMaxConnectionsPerServer(4)
.withPooledConnectionLifetime(300000)
.withPooledConnectionIdleTimeout(30000)
.withConnectTimeout(2000)
.withMaxRedirects(5)
.withDecompression()
.withCookies(jar)
.withMaxResponseHeadersLength(16384)
let transport = Transport.create(options)
let client = Client.over(&transport).withBaseAddress(base)
let moved = Result.map(Client.get(&client, "old"), |r: Response.Response| (r.status.code, r.request.uri))
let report = Client.getText(&client, "report")
let limited = Client.getText(&client.withMaxResponseContentBufferSize(4), "big")
let publicOnly = Client.over(&Transport.create(Transport.defaults().withAddressPolicy(Transport.PublicOnly([])))).withBaseAddress(base)
let refused = Client.getText(&publicOnly, "new")
let stats = Transport.statistics(&transport)
let _a = Io.writeLine(show(moved) + " " + show(report))
let _b = Io.writeLine(show(limited) + " " + show(Cookies.header(&jar, base + "new")))
let _c = Io.writeLine(show(stats) + " " + show(Result.isErr(&refused)))
Transport.close(&transport)
let _stopped = Server.stop(&running)
if report == Ok("a long report") && limited == Err(HttpClient.ContentTooLarge(4)) && stats.opened == 1 && Cookies.header(&jar, base) == Some("visited=yes") { 0 } else { 1 }
}
Check it and run it:
pudu check src/Ex06Transport.pudu
pudu run src/Ex06Transport.puduOutput:
Ok((200, "http://127.0.0.1:49581/new")) Ok("a long report")
Err(ContentTooLarge(4)) Some("visited=yes")
Statistics{opened: 1, reused: 3, closed: 1, active: 0, idle: 0} true
| Option | Default | What it does |
|---|---|---|
withPooledConnectionLifetime |
forever | closes a pooled connection once it has lived this long |
withPooledConnectionIdleTimeout |
one minute | closes a pooled connection unused this long |
withMaxConnectionsPerServer |
no limit | requests wait for a free connection, bounded by their deadline |
withConnectTimeout |
the request's deadline | gives up on opening a connection |
withMaxRedirects, withoutRedirects
|
50 | follows 300–308 redirects; https is never followed to http and credentials never cross origins |
withDecompression |
off | asks for gzip and decompresses buffered bodies within the buffer limit |
usingCookies, withCookies
|
off | stores and sends cookies in the transport's own jar or a shared one |
withProxy |
none | sends plain requests through the proxy and tunnels secured ones with CONNECT
|
withAddressPolicy |
any address |
PublicOnly(permitted) refuses private and loopback hosts, on every hop |
withMaxResponseHeadersLength |
64 KiB | refuses a longer head |
withMaxResponseContentLength |
64 MiB | refuses a longer buffered body when the request sets no limit |
Transport.statistics counts connections opened, reused, closed, in use, and idle, and
Transport.close closes idle connections and stops keeping new ones. A pooled connection that the
server closed while idle is replaced and the request sent once more. Cookies.save and
Cookies.restore keep a jar's persistent cookies across runs; Proxy.at(address) with
withCredentials, bypassing, and bypassingLocal describes a proxy. The transport speaks
HTTP/1.0 and 1.1.
- Previous: Handlers And Pipelines
- Next: Lifetimes And Events
- Reference Index