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"

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

Default you should use: 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": []
}
Backendnyra.modStatus todayWhen 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/"))
}

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)
APIReturnsRole
fetch(url)HttpResponsePrimary GET
req().header(…).timeout(…).json(…).post(url)HttpResponseOptions + verb (like fetch(url, {method, headers, body}))
req().verb("PATCH").go(url)HttpResponseString method name + fire
post_json / post_form / get_jsonresponse / mapShort helpers (optional)
get(url)stringBody only
fetch_with(url, init)HttpResponseExplicit init object
post / put / patch / delete / headHttpResponseLegacy free verbs

Long names like http_post_form still work as aliases — prefer the short path above.

RequestInit · headers · forms · abort · redirects

APIRole
RequestInit_new / _header / _authorization / _cookieCustom request headers
RequestInit_timeout / Client_with_timeoutReal connect/read timeout (sys_set_timeout_ms)
RequestInit_redirect · REDIRECT_FOLLOW/ERROR/MANUALRedirect policy
AbortController / AbortSignalCooperative cancel before/between hops
CookieJarStore Set-Cookie, emit Cookie
FormData · URLSearchParams · urlencoded helpersForm bodies
HeaderMap_* · HttpResponse.headersRequest/response header maps
HttpResponse_json · JSON_parse_objectParse body / text to map
Blob / ArrayBuffer / BodyStreamBinary-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)
    }
}

HttpResponse

Field / helperTypeDescription
statusi32HTTP status code
bodystringResponse payload
content_typestringMIME type
headersHashMap_str_strParsed response headers
HttpResponse_json(resp)HashMap_str_strParse JSON object body
HttpResponse_text(resp)stringBody text
HttpResponse_blob(resp)BlobBody as Blob
HttpResponse_array_buffer(resp)ArrayBufferBody 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}")
FunctionDescription
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)

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)
}

Arrow handler alternative (Extended): (ctx: RequestContext) => response_ok_json("{\"status\":\"ok\"}") when compiled with arrow support.

FunctionDescription
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
FunctionDescription
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

FunctionStatus
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

  1. TCP accept on host:port
  2. Read raw HTTP/1.1 request (headers + body)
  3. Parse into RequestContext (method, path, query, body)
  4. Log request line (middleware_log)
  5. Match route in router → build HttpResponse
  6. Apply CORS headers if enabled
  7. 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:

ModuleAPI
stdlib/net/udp.nyUdpSocket_bind, send, recv
stdlib/net/websocket.nyWebSocket_connect, send, recv (client, RFC 6455 text)
stdlib/tls.nytls_connect, tls_listen, tls_accept (backends via tls in nyra.mod)
stdlib/net/smtp.nySmtp_send
stdlib/net/mail.nyMail_send, Mail_message
stdlib/net/rpc.nyJSON-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.