net/http
Modern HTTP/1.1 client and routed server for Nyra — inspired by Go’s net/http, built on native TCP + selectable TLS backends.
import "stdlib/net/http/mod.ny"import "stdlib/net/http/mod.ny"Architecture
Application
│
stdlib/net/http (get / post / …)
│
stdlib/tls.ny (stable rt_tls_* ABI)
│
┌────┴────┬──────────┐
rustls native openssl
(default) (OS TLS) (optional)
│
TCP
HTTPS works out of the box with the bundled rustls client — no OpenSSL install required. Choose a backend in nyra.mod; nyra.lock pins the resolved choice under features.tls.
TLS backends — what Nyra supports today
rustls.
HTTPS client (get("https://…"), WebSocket TLS, SMTP STARTTLS client paths) ships as a bundled static library (libnyra_rt_tls.a). No OpenSSL install. System CA store. This is the production default when tls is omitted from nyra.mod. Prefer tls native when you want OS TLS (Secure Transport / SChannel).
# nyra.mod — optional; rustls is already the default
module example.local
tls rustls
// nyra.lock pins the resolved backend
{
"features": { "tls": "rustls" },
"require": []
}
| Backend | nyra.mod | Status today | When to use |
|---|---|---|---|
| rustls | tls rustls or omit |
Stable — default | All normal HTTPS apps. Bundled with Nyra (libnyra_rt_tls.a); no extra packages. |
| native | tls native |
Stable | OS TLS: macOS Secure Transport · Windows SChannel · Linux system OpenSSL (via native-tls / libnyra_rt_tls_native.a). Same HTTPS API as rustls. |
| openssl | tls openssl |
Optional / available | System OpenSSL client unit, or TLS server / PEM helpers. Requires OpenSSL headers + libs (-lssl -lcrypto). Soft-skips in CI when OpenSSL is missing. |
How to choose in practice: prefer rustls (default, portable). Use tls native when you want the platform TLS stack (enterprise CA / OS policy). Use openssl when you already depend on OpenSSL or need server helpers.
Resolve order: value in nyra.mod → else features.tls in nyra.lock → else rustls. If mod and lock disagree, Nyra reports a mismatch. Full nyra.mod syntax: packages → nyra.mod syntax.
CI covers all three backends in make test-conformance (pass/tls for rustls defaults; fixtures tls_native / tls_openssl for the others).
Application code is identical for every backend:
fn main() {
print(get("https://example.com/"))
}fn main() -> void {
print(get("https://example.com/"))
}On failure, tls_last_error() describes the real connect/handshake problem (network reset, cert error, …) — it does not ask you to install OpenSSL when using rustls or native on macOS/Windows.
Module layout
stdlib/net/http/
mod.ny — public entry (Router, listen_and_serve_handlers, client verbs)
types.ny — HttpRequest, HttpResponse, Server, Client, Router
client.ny — GET, POST, PUT, PATCH, DELETE, HEAD
server.ny — listen, keep-alive, dispatch, CORS helpers
request.ny — parse incoming requests
response.ny — status lines, JSON helpers
Routing and middleware helpers live in mod.ny / server.ny (no separate router.ny file).
HTTP client
Preferred (JS-like): one entry — fetch(url) returns HttpResponse. Put options on req(), then call the method with the URL (.get / .post / …). Body-only convenience remains get(url) -> string.
let resp = fetch("https://example.com/")
print(resp.status, resp.text())
let posted = req()
.header("Authorization", "Bearer tok")
.timeout(10000)
.json("{\"name\":\"Nyra\"}")
.post("https://example.com/api")
let form_post = req()
.form(form().append("a", "1").append("b", "2"))
.post(url)let resp = fetch("https://example.com/")
print(resp.status, resp.text())
let posted = req()
.header("Authorization", "Bearer tok")
.timeout(10000)
.json("{\"name\":\"Nyra\"}")
.post("https://example.com/api")
let form_post = req()
.form(form().append("a", "1").append("b", "2"))
.post(url)| API | Returns | Role |
|---|---|---|
fetch(url) | HttpResponse | Primary GET |
req().header(…).timeout(…).json(…).post(url) | HttpResponse | Options + verb (like fetch(url, {method, headers, body})) |
req().verb("PATCH").go(url) | HttpResponse | String method name + fire |
post_json / post_form / get_json | response / map | Short helpers (optional) |
get(url) | string | Body only |
fetch_with(url, init) | HttpResponse | Explicit init object |
post / put / patch / delete / head | HttpResponse | Legacy free verbs |
Long names like http_post_form still work as aliases — prefer the short path above.
RequestInit · headers · forms · abort · redirects
| API | Role |
|---|---|
RequestInit_new / _header / _authorization / _cookie | Custom request headers |
RequestInit_timeout / Client_with_timeout | Real connect/read timeout (sys_set_timeout_ms) |
RequestInit_redirect · REDIRECT_FOLLOW/ERROR/MANUAL | Redirect policy |
AbortController / AbortSignal | Cooperative cancel before/between hops |
CookieJar | Store Set-Cookie, emit Cookie |
FormData · URLSearchParams · urlencoded helpers | Form bodies |
HeaderMap_* · HttpResponse.headers | Request/response header maps |
HttpResponse_json · JSON_parse_object | Parse body / text to map |
Blob / ArrayBuffer / BodyStream | Binary-ish body helpers over bytes |
Client example
import "stdlib/net/http/mod.ny"
fn main() {
let client = Client_default()
let resp = client.do_post("https://example.com/api", "{\"name\":\"Nyra\"}")
if resp.status == 201 {
print(resp.body)
}
}import "stdlib/net/http/mod.ny"
fn main() -> void {
let client = Client_default()
let resp = client.do_post("https://example.com/api", "{\"name\":\"Nyra\"}")
if resp.status == 201 {
print(resp.body)
}
}HttpResponse
| Field / helper | Type | Description |
|---|---|---|
status | i32 | HTTP status code |
body | string | Response payload |
content_type | string | MIME type |
headers | HashMap_str_str | Parsed response headers |
HttpResponse_json(resp) | HashMap_str_str | Parse JSON object body |
HttpResponse_text(resp) | string | Body text |
HttpResponse_blob(resp) | Blob | Body as Blob |
HttpResponse_array_buffer(resp) | ArrayBuffer | Body as ArrayBuffer |
Router
Routes are keyed by METHOD:path (e.g. GET:/health). Register handlers with free functions:
let router = Router_new()
let r = Router_add_get(router, "/health", "{\"status\":\"ok\"}")
let r2 = Router_add_post(r, "/users", "{\"created\":true}")
let r3 = Router_add_put(r2, "/users/1", "{\"updated\":true}")let router = Router_new()
let r = Router_add_get(router, "/health", "{\"status\":\"ok\"}")
let r2 = Router_add_post(r, "/users", "{\"created\":true}")
let r3 = Router_add_put(r2, "/users/1", "{\"updated\":true}")| Function | Description |
|---|---|
Router_new() | Empty router, default 404 JSON |
Router_add_get(router, path, body) | Register GET handler body |
Router_add_post(router, path, body) | Register POST handler body |
Router_add_put(router, path, body) | Register PUT handler body |
Router_add_patch(router, path, body) | Register PATCH handler body |
Router_add_delete(router, path, body) | Register DELETE handler body |
HandleFunc(router, method, path, body) | Register by method constant |
Server
let server = Server_with_router("127.0.0.1", 8080, router)
let s2 = Server_enable_cors(server)
Server_listen(s2, router)let server = Server_with_router("127.0.0.1", 8080, router)
let s2 = Server_enable_cors(server)
Server_listen(s2, router)Handler dispatch
Register integer slots and pass a Nyra function fn(i32, RequestContext) -> HttpResponse. Use for dynamic logic instead of static response bodies.
fn health_slot(slot, ctx) {
return response_ok_json("{\"status\":\"ok\"}")
}
fn main() {
let router = Router_new()
let r = Router_add_slot_get(router, "/health", 0)
listen_and_serve_handlers("127.0.0.1", 8080, r, health_slot)
}fn health_slot(slot: i32, ctx: RequestContext) -> i32 {
return response_ok_json("{\"status\":\"ok\"}")
}
fn main() -> void {
let router = Router_new()
let r = Router_add_slot_get(router, "/health", 0)
listen_and_serve_handlers("127.0.0.1", 8080, r, health_slot)
}Arrow handler alternative (Extended): (ctx: RequestContext) => response_ok_json("{\"status\":\"ok\"}") when compiled with arrow support.
| Function | Description |
|---|---|
Router_add_slot_get(router, path, slot) | Map GET path to handler slot id |
Router_add_slot_post(router, path, slot) | Map POST path to slot |
Router_match_slot(router, ctx) | Resolve slot for request (used internally) |
listen_and_serve_handlers(host, port, router, dispatch) | Listen with custom handler function |
Server_handle_one_handler(...) | Single connection + handler dispatch |
| Function | Description |
|---|---|
Server_with_router(host, port, router) | Bind address + route table |
Server_enable_cors(server) | Attach CORS headers + OPTIONS preflight |
Server_listen(server, router) | Accept one connection, handle up to 100 keep-alive requests |
serve_once(host, port, body) | Quick one-shot / + /health server |
listen_and_serve(host, port, router) | Shorthand for listen |
Response helpers
| Function | Status |
|---|---|
response_ok_json(body) | 200 |
response_created_json(body) | 201 |
response_no_content() | 204 |
response_not_found() | 404 JSON error |
response_bad_request() | 400 |
response_unauthorized() | 401 |
response_internal_error() | 500 |
build_response(resp, keep_alive) | Raw HTTP/1.1 bytes |
Request lifecycle
- TCP accept on
host:port - Read raw HTTP/1.1 request (headers + body)
- Parse into
RequestContext(method, path, query, body) - Log request line (
middleware_log) - Match route in router → build
HttpResponse - Apply CORS headers if enabled
- Write response; repeat on keep-alive until close or 100 requests
Method constants
METHOD_GET, METHOD_POST, METHOD_PUT, METHOD_DELETE, METHOD_PATCH, METHOD_HEAD, METHOD_OPTIONS
Other networking (stdlib)
Beyond HTTP/1.1, the stdlib ships:
| Module | API |
|---|---|
stdlib/net/udp.ny | UdpSocket_bind, send, recv |
stdlib/net/websocket.ny | WebSocket_connect, send, recv (client, RFC 6455 text) |
stdlib/tls.ny | tls_connect, tls_listen, tls_accept (backends via tls in nyra.mod) |
stdlib/net/smtp.ny | Smtp_send |
stdlib/net/mail.ny | Mail_send, Mail_message |
stdlib/net/rpc.ny | JSON-RPC 2.0 helpers |
Full inventory: stdlib → Net, HTTP, WebSocket
Legacy imports
stdlib/http/mod.ny and stdlib/http.ny re-export this module. HttpServer wraps Server_listen for older examples.
Production APIs
Build services with handler slots (examples/net_http_smoke.ny), stdlib/db/*, and NyraPkg drivers (ny-postgres, ny-redis). See Standard library → net/http and NyraPkg.