Extend HTTP server routing and JSON exchange helpers

This commit is contained in:
sergeych committed 2026-04-26 13:42:14 +03:00
1 parent b969edd30a
commit ca4a0d4b12
8 files changed
+981 -116

No files matched your search

+210
View File
@@ -0,0 +1,210 @@
### lyng.io.http.server — Minimal HTTP/1.1 and WebSocket server
This module provides a small server-side HTTP API for Lyng scripts. It is implemented in `lyngio` on top of the existing TCP layer and is intended for embedded tools, local services, test fixtures, and lightweight app backends.
It supports:
- HTTP/1.1 request parsing
- keep-alive
- exact-path routing
- regex routing
- path-template routing with named parameters
- websocket upgrade and server-side sessions
It does not aim to replace a full reverse proxy. Typical deployment is behind nginx, Caddy, or another frontend that handles TLS and public-facing edge concerns.
> **Security note:** this module uses the same `NetAccessPolicy` capability model as raw TCP sockets. If scripts are allowed to listen on TCP, they can host an HTTP server.
---
#### Install the module into a Lyng session
Kotlin bootstrap example:
```kotlin
import net.sergeych.lyng.EvalSession
import net.sergeych.lyng.Scope
import net.sergeych.lyng.io.http.server.createHttpServerModule
import net.sergeych.lyngio.net.security.PermitAllNetAccessPolicy
suspend fun bootstrapHttpServer() {
val session = EvalSession()
val scope: Scope = session.getScope()
createHttpServerModule(PermitAllNetAccessPolicy, scope)
session.eval("import lyng.io.http.server")
}
```
---
#### Basic exact route
```lyng
import lyng.io.http.server
val server = HttpServer()
server.get("/hello") { ex ->
ex.setHeader("Content-Type", "text/plain")
ex.respondText(200, "hello")
}
server.listen(8080, "127.0.0.1")
```
---
#### Regex route
Regex routes match the whole request path, not a substring.
```lyng
server.get("^/users/([0-9]+)/posts/([0-9]+)$".re) { ex ->
val m = ex.routeMatch!!
ex.respondText(200, "user=" + m[1] + ", post=" + m[2])
}
```
---
#### Path-template route
Path templates are sugar on top of regex routes. Template parameters are exposed as decoded `routeParams`.
```lyng
server.getPath("/users/{userId}/posts/{postId}") { ex ->
ex.respondText(
200,
ex.routeParams["userId"] + ":" + ex.routeParams["postId"]
)
}
```
Template rules:
- template must start with `/`
- a segment is either literal text or `{name}`
- parameter names must be valid identifiers
- parameter values match one path segment only
- parameter values use path decoding rules:
- valid percent-encoding is decoded
- `+` stays `+`
- malformed `%` stays literal
---
#### Request and exchange data
`ServerRequest` exposes parsed HTTP request data:
- `method: String`
- `target: String`
- `path: String`
- `pathParts: List<String>`
- `queryString: String?`
- `query: Map<String, String>`
- `headers: HttpHeaders`
- `body: Buffer`
`ServerExchange` exposes routing context and response controls:
- `request: ServerRequest`
- `routeMatch: RegexMatch?`
- `routeParams: Map<String, String>`
- `jsonBody<T>()`
- `respond(...)`
- `respondText(...)`
- `respondJson(body, status = 200)`
- `setHeader(...)`
- `addHeader(...)`
- `acceptWebSocket(...)`
For exact routes, `routeMatch` is `null` and `routeParams` is empty.
For regex routes, `routeMatch` is set and `routeParams` is empty.
For path-template routes, both `routeMatch` and `routeParams` are set.
---
#### JSON request/response helpers
For ordinary HTTP JSON APIs, `ServerExchange` includes two helpers:
- `jsonBody<T>()` decodes the request body with typed `Json.decodeAs(...)`
- `respondJson(body, status = 200)` sets JSON content type and responds with plain `toJsonString()`
Example:
```lyng
import lyng.io.http.server
closed class CreateUserRequest(name: String, age: Int)
closed class CreateUserResponse(id: Int, name: String, age: Int)
val server = HttpServer()
server.postPath("/api/users") { ex ->
val req = ex.jsonBody<CreateUserRequest>()
if (req.name.isBlank()) {
ex.respondJson({ error: "name must not be empty" }, 400)
return
}
ex.respondJson(CreateUserResponse(101, req.name, req.age), 201)
}
server.listen(8080, "127.0.0.1")
```
These helpers intentionally use ordinary JSON projection for HTTP interop, not canonical `Json.encode(...)`.
---
#### Route precedence
Dispatch order is:
1. exact method route
2. exact `any` route
3. regex method route, registration order
4. regex `any` route, registration order
5. fallback
This means exact routes stay fast and always win over template or regex routes for the same path.
---
#### WebSocket routes
You can route websocket upgrades by exact path, regex, or path template:
```lyng
server.ws("/chat") { ws, ex ->
ws.sendText("hello")
ws.close()
}
server.wsPath("/ws/{room}") { ws, ex ->
ws.sendText("room=" + ex.routeParams["room"])
ws.close()
}
```
---
#### API surface
`HttpServer` route registration methods:
- `get(path: String|Regex, handler)`
- `getPath(pathTemplate: String, handler)`
- `post(path: String|Regex, handler)`
- `postPath(pathTemplate: String, handler)`
- `put(path: String|Regex, handler)`
- `putPath(pathTemplate: String, handler)`
- `delete(path: String|Regex, handler)`
- `deletePath(pathTemplate: String, handler)`
- `any(path: String|Regex, handler)`
- `anyPath(pathTemplate: String, handler)`
- `ws(path: String|Regex, handler)`
- `wsPath(pathTemplate: String, handler)`
- `fallback(handler)`
- `listen(port, host = null, backlog = 128)`
+2
View File
@@ -17,6 +17,7 @@
- **[lyng.io.process](lyng.io.process.md):** External process execution and shell commands. Provides `Process`, `RunningProcess`, and `Platform` information.
- **[lyng.io.console](lyng.io.console.md):** Rich console/TTY access. Provides `Console` capability detection, geometry, output, and iterable events.
- **[lyng.io.http](lyng.io.http.md):** HTTP/HTTPS client access. Provides `Http`, `HttpRequest`, `HttpResponse`, and `HttpHeaders`.
- **[lyng.io.http.server](lyng.io.http.server.md):** Minimal HTTP/1.1 and WebSocket server. Provides `HttpServer`, `ServerRequest`, `ServerExchange`, and `ServerWebSocket`.
- **[lyng.io.ws](lyng.io.ws.md):** WebSocket client access. Provides `Ws`, `WsSession`, and `WsMessage`.
- **[lyng.io.net](lyng.io.net.md):** Transport networking. Provides `Net`, `TcpSocket`, `TcpServer`, `UdpSocket`, and `SocketAddress`.
- **Shared networking type packages:** `lyng.io.http.types`, `lyng.io.ws.types`, and `lyng.io.net.types` expose reusable value types such as `HttpHeaders`, `WsMessage`, `IpVersion`, `SocketAddress`, and `Datagram` when host code wants type-only imports without installing the corresponding capability object module.
@@ -120,6 +121,7 @@ For more details, see the specific module documentation:
- [Process Security Details](lyng.io.process.md#security-policy)
- [Console Module Details](lyng.io.console.md)
- [HTTP Module Details](lyng.io.http.md)
- [HTTP Server Module Details](lyng.io.http.server.md)
- [Transport Networking Details](lyng.io.net.md)
- [WebSocket Module Details](lyng.io.ws.md)