Skip to content
Kavachकवच

HTTP: Sessions

● Intermediate2 min

http.open keeps one connection open for many requests, instead of connecting again for each.

use std.http
use std.net

fn api(r: Request) -> Response {
    http.text(200, "{r.method} {r.path}")
}

fn main() ! Error {
    const listener = try net.listen("127.0.0.1:0")
    const _server = spawn http.serve(listener, api)
    const session = try http.open("http://{listener.address()}")
    for path in ["/users/1", "/users/2"] {
        const reply = try session.get(path)
        print(reply.body.text() ?? "")
    }
    const made = try session.post("/users", "Asha".bytes())
    print(made.body.text() ?? "")
    session.close()
}

The program ran and printed:

kavach run session.kav

GET /users/1GET /users/2POST /users
session.kav: Kavach code, then what running it prints

Requests on a session go one after another, each waiting for its answer. A session is for one task at a time: give each task its own, so no task reads another's answer.

One HTTP/2 connection for many tasks

A service that speaks HTTP/2 itself, as gRPC does, connects once with h2.connect from std.http.h2. Several tasks can share the H2Client it gives: each request is on a stream of its own, all open at once, and each answer goes to the task that asked, in whatever order the server sends them.

use std.http
use std.http.h2
use std.net

fn hello(r: Request) -> Response {
    http.text(200, "{r.method} {r.path}")
}

fn ask(c: H2Client, path: String) -> String ! H2Error uses Net {
    const reply = try c.request([
        Header(name: ":method", value: "GET"),
        Header(name: ":scheme", value: "http"),
        Header(name: ":authority", value: "localhost"),
        Header(name: ":path", value: path),
    ], Bytes([]))
    reply.body.text() ?? "?"
}

fn main() ! Error {
    const listener = try net.listen("127.0.0.1:0")
    const _server = spawn http.serve(listener, hello)
    const c = try h2.connect(try net.connect(listener.address()))
    print(try ask(c, "/orders"))
    const tasks = [spawn ask(c, "/stock"), spawn ask(c, "/prices")]
    for t in tasks {
        print(try t.wait())
    }
    print("going away: {c.going_away()}")
    c.close()
    print(ask(c, "/late") catch e => "{e}")
}

The program ran and printed:

kavach run h2-client.kav

GET /ordersGET /stockGET /pricesgoing away: falseProtocol(reason: "the client was closed")
h2-client.kav: Kavach code, then what running it prints
FunctionGives
h2.connect(conn) -> H2Client ! H2ErrorStarts a connection that takes many requests, so a service connects once instead of once per call
c.request(headers, body) -> H2Message ! H2ErrorSends one request on c, on a stream of its own, and reads its response. Requests from several tasks are open at once: their sends take turns, in the order they asked, and each answer goes to the task that asked, in whatever order the server sends them. A stream the server resets fails only its own request
c.going_away() -> BoolWhether the server said it is going away (GOAWAY) or the connection broke: a pool replaces such a client with a new one
c.close()Closes the connection once the request being answered is done; a request after it fails with Protocol. A client is also closed when its last holder lets it go, as its connection is (14.10)
  • A pool replaces a client whose going_away() is true: the server sent GOAWAY, or the connection broke.
  • After close(), a request fails with Protocol.
  • A stream the server resets fails only its own request.