# Web libraries

> Pure-Langoost libraries for serving HTTP with Request/Response types, WebSockets, Server-Sent Events, cookies, and signed sessions.

# Web libraries

These are pure-Langoost libraries shipped in the `lib/` directory, written on top of the `net.*` primitives. You import them by name. They are distinct from the built-in `http.serve` (see [HTTP server](/docs/http-server)) — these give you Langoost-level types and handler functions you can read and extend.

Covered here: `http_server`, `websocket`, `sse`, `cookies`, and `session`.

## http_server

An HTTP/1.1 server. Each accepted connection is handled in its own thread.

### Types

```goost
type Request = {
    method:  string,
    path:    string,
    query:   string,
    version: string,
    headers: string[],    // raw "Key: Value" lines
    body:    string,
}

type Response = {
    status:  int,
    headers: string[],    // raw "Key: Value" lines
    body:    string,
}
```

### Functions

| Function | Signature | Description |
| --- | --- | --- |
| `serve` | `http_server.serve(port: int, handler)` | bind the port and dispatch `handler(req: Request)` per connection; blocks forever |
| `header` | `http_server.header(req, name: string) → string` | value of a header (case-insensitive), or `""` if missing |
| `statusText` | `http_server.statusText(code: int) → string` | IANA reason phrase for a status code |

The handler is called as `handler(req)` and may return either a `string` (sent as `200 OK` with an auto Content-Type) or a `Response` for full control. Content-Length and Content-Type are added automatically if you omit them.

### Example

```goost
import http_server
import { Response } from "http_server"

fn handle(req) {
    if req.path == "/" {
        return "Hello, World!"
    }
    if req.path == "/json" {
        return Response{status: 200, headers: [], body: "{\"ok\":true}"}
    }
    return Response{status: 404, headers: [], body: "Not Found"}
}

http_server.serve(8080, handle)
```

## websocket

A minimal RFC 6455 server-side WebSocket implementation. Use `websocket.serve` for end-to-end connections; it runs the handshake and hands your handler the raw connection.

### Types

```goost
type WSRequest = { path: string, headers: string[] }
```

### Functions

| Function | Signature | Description |
| --- | --- | --- |
| `serve` | `websocket.serve(port: int, handler)` | listen and call `handler(conn: int, path: string)` after the handshake completes |
| `recv` | `websocket.recv(conn: int) → string` | read one frame's payload; void on close/EOF (auto-replies to pings) |
| `send` | `websocket.send(conn: int, msg: string) → bool` | send a text frame |
| `sendBinary` | `websocket.sendBinary(conn: int, data: string) → bool` | send a binary frame |
| `close` | `websocket.close(conn: int)` | send a close frame and close the connection |
| `isUpgrade` | `websocket.isUpgrade(req) → bool` | true if the request is a valid WebSocket upgrade |
| `acceptKey` | `websocket.acceptKey(req) → string` | compute the `Sec-WebSocket-Accept` value, or `""` |
| `upgradeHeaders` | `websocket.upgradeHeaders(accept: string) → string[]` | response header lines for a 101 handshake |
| `handshake` | `websocket.handshake(conn: int, req) → bool` | perform the full server-side upgrade on `conn` |

### Example

```goost
import websocket

fn handle(conn, path) {
    println("ws connected: " + path)
    while true {
        let msg = websocket.recv(conn)
        if typeof(msg) == "void" { break }
        websocket.send(conn, "echo: " + msg)
    }
    websocket.close(conn)
}

websocket.serve(8090, handle)
```

## sse

Server-Sent Events: the connection stays open and the server streams text events to the browser's `EventSource`.

### Types

```goost
type SSERequest = { path: string, headers: string[] }
```

### Functions

| Function | Signature | Description |
| --- | --- | --- |
| `serve` | `sse.serve(port: int, handler)` | accept connections and call `handler(req, emit)`; returning from the handler closes the socket |
| `writeHeaders` | `sse.writeHeaders(conn: int) → bool` | write the `text/event-stream` 200 OK header block |
| `frame` | `sse.frame(event: string, data: string) → string` | build one event payload; `event=""` uses the default `message` |
| `frameWithID` | `sse.frameWithID(event: string, data: string, id: string) → string` | build an event with a `Last-Event-ID` hint |
| `retryHint` | `sse.retryHint(ms: int) → string` | build a reconnect-delay hint frame |

The handler is called as `handler(req, emit)` where `req` is an `SSERequest` and `emit` is `fn(event: string, data: string)` which writes one event to the client.

### Example

```goost
import sse
import thread

fn handle(req, emit) {
    let i = 0
    while i < 5 {
        emit("tick", "n=" + toString(i))
        thread.core.sleep(1000)
        i = i + 1
    }
}

sse.serve(8091, handle)
```

## cookies

Parse incoming cookies and build `Set-Cookie` headers. Import with
`import cookies`.

| Function | Signature | Description |
| --- | --- | --- |
| `parse` | `cookies.parse(req) → object` | Read the request's `Cookie` header into a `{name: value}` object |
| `set` | `cookies.set(name: string, value: string, opts) → string` | Build a `Set-Cookie:` header line |
| `unset` | `cookies.unset(name: string) → string` | Build a `Set-Cookie:` line that immediately expires the cookie |

`opts` is an object; recognized keys: `path` (string), `domain` (string),
`maxAge` (int), `expires` (string), `httpOnly` (bool), `secure` (bool),
`sameSite` (string).

```goost
import cookies

let jar = cookies.parse(req)
println(jar.theme)

let header = cookies.set("theme", "dark", {
    path: "/",
    maxAge: 86400,
    httpOnly: true,
    sameSite: "Lax",
})
// add `header` to your Response headers
```

## session

Stateless, HMAC-SHA256-signed session tokens — no server-side storage. Import
with `import session`.

| Function | Signature | Description |
| --- | --- | --- |
| `sign` | `session.sign(payload: string, secret: string) → string` | Return a signed token carrying `payload` |
| `verify` | `session.verify(token: string, secret: string) → string` | Return the original payload, or `nil` if the token is missing, malformed, or tampered |

`verify` is exception-safe: a bad token returns `nil` rather than throwing, so
it's safe to call on untrusted input.

```goost
import session
import cookies

let SECRET = "change-me"

// issue a session after login
let token = session.sign("user:42", SECRET)
let header = cookies.set("sid", token, {httpOnly: true, sameSite: "Strict"})

// verify on a later request
let who = session.verify(cookies.parse(req).sid, SECRET)
if who == nil {
    // not logged in
}
```