2026-01-25 01:47:47 +00:00
<!DOCTYPE html>
<!-- retoor <retoor@molodetz.nl> -->
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< title > web - Wren-CLI Manual</ title >
< link rel = "stylesheet" href = "../css/style.css" >
</ head >
< body >
< button class = "mobile-menu-toggle" > Menu</ button >
< div class = "container" >
< aside class = "sidebar" >
< div class = "sidebar-header" >
< h1 >< a href = "../index.html" > Wren-CLI</ a ></ h1 >
< div class = "version" > v0.4.0</ div >
</ div >
< nav class = "sidebar-nav" >
< div class = "section" >
< span class = "section-title" > Getting Started</ span >
< ul >
< li >< a href = "../getting-started/index.html" > Overview</ a ></ li >
< li >< a href = "../getting-started/installation.html" > Installation</ a ></ li >
< li >< a href = "../getting-started/first-script.html" > First Script</ a ></ li >
< li >< a href = "../getting-started/repl.html" > Using the REPL</ a ></ li >
</ ul >
</ div >
< div class = "section" >
< span class = "section-title" > Language</ span >
< ul >
< li >< a href = "../language/index.html" > Syntax Overview</ a ></ li >
< li >< a href = "../language/classes.html" > Classes</ a ></ li >
< li >< a href = "../language/methods.html" > Methods</ a ></ li >
< li >< a href = "../language/control-flow.html" > Control Flow</ a ></ li >
< li >< a href = "../language/fibers.html" > Fibers</ a ></ li >
< li >< a href = "../language/modules.html" > Modules</ a ></ li >
</ ul >
</ div >
< div class = "section" >
< span class = "section-title" > API Reference</ span >
< ul >
< li >< a href = "index.html" > Overview</ a ></ li >
2026-01-25 03:58:39 +00:00
< li >< a href = "string.html" > String</ a ></ li >
< li >< a href = "number.html" > Num</ a ></ li >
2026-01-25 01:47:47 +00:00
< li >< a href = "argparse.html" > argparse</ a ></ li >
2026-01-25 12:09:28 +00:00
< li >< a href = "base64.html" > base64</ a ></ li >
< li >< a href = "crypto.html" > crypto</ a ></ li >
2026-01-25 01:47:47 +00:00
< li >< a href = "dataset.html" > dataset</ a ></ li >
2026-01-25 12:09:28 +00:00
< li >< a href = "datetime.html" > datetime</ a ></ li >
< li >< a href = "dns.html" > dns</ a ></ li >
< li >< a href = "env.html" > env</ a ></ li >
< li >< a href = "fswatch.html" > fswatch</ a ></ li >
< li >< a href = "html.html" > html</ a ></ li >
< li >< a href = "http.html" > http</ a ></ li >
< li >< a href = "io.html" > io</ a ></ li >
< li >< a href = "jinja.html" > jinja</ a ></ li >
< li >< a href = "json.html" > json</ a ></ li >
2026-01-25 01:47:47 +00:00
< li >< a href = "markdown.html" > markdown</ a ></ li >
2026-01-25 12:09:28 +00:00
< li >< a href = "math.html" > math</ a ></ li >
< li >< a href = "net.html" > net</ a ></ li >
< li >< a href = "os.html" > os</ a ></ li >
< li >< a href = "pathlib.html" > pathlib</ a ></ li >
< li >< a href = "regex.html" > regex</ a ></ li >
< li >< a href = "scheduler.html" > scheduler</ a ></ li >
< li >< a href = "signal.html" > signal</ a ></ li >
< li >< a href = "sqlite.html" > sqlite</ a ></ li >
< li >< a href = "subprocess.html" > subprocess</ a ></ li >
< li >< a href = "sysinfo.html" > sysinfo</ a ></ li >
< li >< a href = "tempfile.html" > tempfile</ a ></ li >
< li >< a href = "timer.html" > timer</ a ></ li >
< li >< a href = "tls.html" > tls</ a ></ li >
< li >< a href = "udp.html" > udp</ a ></ li >
< li >< a href = "uuid.html" > uuid</ a ></ li >
< li >< a href = "wdantic.html" > wdantic</ a ></ li >
2026-01-25 01:47:47 +00:00
< li >< a href = "web.html" class = "active" > web</ a ></ li >
2026-01-25 12:09:28 +00:00
< li >< a href = "websocket.html" > websocket</ a ></ li >
2026-01-25 01:47:47 +00:00
</ ul >
</ div >
< div class = "section" >
< span class = "section-title" > Tutorials</ span >
< ul >
< li >< a href = "../tutorials/index.html" > Tutorial List</ a ></ li >
2026-01-25 12:09:28 +00:00
< li >< a href = "../tutorials/http-client.html" > HTTP Client</ a ></ li >
< li >< a href = "../tutorials/websocket-chat.html" > WebSocket Chat</ a ></ li >
< li >< a href = "../tutorials/database-app.html" > Database App</ a ></ li >
< li >< a href = "../tutorials/template-rendering.html" > Templates</ a ></ li >
< li >< a href = "../tutorials/cli-tool.html" > CLI Tool</ a ></ li >
2026-01-25 18:02:02 +00:00
< li >< a href = "../tutorials/web-server.html" > Web Server</ a ></ li >
2026-01-25 01:47:47 +00:00
</ ul >
</ div >
< div class = "section" >
< span class = "section-title" > How-To Guides</ span >
< ul >
< li >< a href = "../howto/index.html" > How-To List</ a ></ li >
2026-01-25 12:09:28 +00:00
< li >< a href = "../howto/http-requests.html" > HTTP Requests</ a ></ li >
< li >< a href = "../howto/json-parsing.html" > JSON Parsing</ a ></ li >
< li >< a href = "../howto/regex-patterns.html" > Regex Patterns</ a ></ li >
< li >< a href = "../howto/file-operations.html" > File Operations</ a ></ li >
< li >< a href = "../howto/async-operations.html" > Async Operations</ a ></ li >
< li >< a href = "../howto/error-handling.html" > Error Handling</ a ></ li >
2026-01-25 01:47:47 +00:00
</ ul >
</ div >
2026-01-25 18:02:02 +00:00
< div class = "section" >
< span class = "section-title" > Contributing</ span >
< ul >
< li >< a href = "../contributing/index.html" > Overview</ a ></ li >
< li >< a href = "../contributing/module-overview.html" > Module Architecture</ a ></ li >
< li >< a href = "../contributing/pure-wren-module.html" > Pure-Wren Modules</ a ></ li >
< li >< a href = "../contributing/c-backed-module.html" > C-Backed Modules</ a ></ li >
< li >< a href = "../contributing/foreign-classes.html" > Foreign Classes</ a ></ li >
< li >< a href = "../contributing/async-patterns.html" > Async Patterns</ a ></ li >
< li >< a href = "../contributing/testing.html" > Writing Tests</ a ></ li >
< li >< a href = "../contributing/documentation.html" > Documentation</ a ></ li >
</ ul >
</ div >
2026-01-25 01:47:47 +00:00
</ nav >
</ aside >
< main class = "content" >
< nav class = "breadcrumb" >
< a href = "../index.html" > Home</ a >
< span class = "separator" > /</ span >
< a href = "index.html" > API Reference</ a >
< span class = "separator" > /</ span >
< span > web</ span >
</ nav >
< article >
< h1 > web</ h1 >
< p > The < code > web</ code > module provides a web framework for building HTTP servers and a client for making HTTP requests. It includes routing, middleware, sessions, static file serving, and class-based views.</ p >
2026-01-25 18:02:02 +00:00
< pre >< code > import "web" for Application, Router, Request, Response, View, Session, Client, WebSocketResponse</ code ></ pre >
2026-01-25 01:47:47 +00:00
< h2 > Application Class</ h2 >
< div class = "class-header" >
< h3 > Application</ h3 >
< p > HTTP server with routing and middleware</ p >
</ div >
< h3 > Constructor</ h3 >
< div class = "method-signature" >
< span class = "method-name" > Application.new</ span > () → < span class = "type" > Application</ span >
</ div >
< p > Creates a new web application.</ p >
< h3 > Properties</ h3 >
< div class = "method-signature" >
< span class = "method-name" > router</ span > → < span class = "type" > Router</ span >
</ div >
< p > Access to the underlying router.</ p >
< div class = "method-signature" >
< span class = "method-name" > sessionStore</ span > → < span class = "type" > SessionStore</ span >
</ div >
< p > Access to the session storage.</ p >
< h3 > Routing Methods</ h3 >
< div class = "method-signature" >
< span class = "method-name" > get</ span > (< span class = "param" > path</ span > , < span class = "param" > handler</ span > ) → < span class = "type" > Application</ span >
</ div >
< div class = "method-signature" >
< span class = "method-name" > post</ span > (< span class = "param" > path</ span > , < span class = "param" > handler</ span > ) → < span class = "type" > Application</ span >
</ div >
< div class = "method-signature" >
< span class = "method-name" > put</ span > (< span class = "param" > path</ span > , < span class = "param" > handler</ span > ) → < span class = "type" > Application</ span >
</ div >
< div class = "method-signature" >
< span class = "method-name" > delete</ span > (< span class = "param" > path</ span > , < span class = "param" > handler</ span > ) → < span class = "type" > Application</ span >
</ div >
< div class = "method-signature" >
< span class = "method-name" > patch</ span > (< span class = "param" > path</ span > , < span class = "param" > handler</ span > ) → < span class = "type" > Application</ span >
</ div >
< p > Register route handlers. Handler receives < code > Request</ code > and returns < code > Response</ code > .</ p >
< ul class = "param-list" >
< li >< span class = "param-name" > path</ span > < span class = "param-type" > (String)</ span > - URL path with optional parameters (< code > :param</ code > ) or wildcards (< code > *</ code > )</ li >
< li >< span class = "param-name" > handler</ span > < span class = "param-type" > (Fn)</ span > - Function receiving Request, returning Response</ li >
</ ul >
< div class = "method-signature" >
< span class = "method-name" > addView</ span > (< span class = "param" > path</ span > , < span class = "param" > viewClass</ span > ) → < span class = "type" > Application</ span >
</ div >
< p > Registers a class-based view for all HTTP methods.</ p >
< div class = "method-signature" >
< span class = "method-name" > static_</ span > (< span class = "param" > prefix</ span > , < span class = "param" > directory</ span > ) → < span class = "type" > Application</ span >
</ div >
< p > Serves static files from a directory.</ p >
< pre >< code > app.static_("/assets", "./public")</ code ></ pre >
2026-01-25 18:02:02 +00:00
< div class = "method-signature" >
< span class = "method-name" > websocket</ span > (< span class = "param" > path</ span > , < span class = "param" > handler</ span > ) → < span class = "type" > Application</ span >
</ div >
< p > Registers a WebSocket handler for the given path. Handler receives Request and should use WebSocketResponse to handle the connection.</ p >
< pre >< code > app.websocket("/ws", Fn.new { |req|
var ws = WebSocketResponse.new()
ws.prepare(req)
while (ws.isOpen) {
var msg = ws.receive()
if (msg == null || msg.isClose) break
if (msg.isText) ws.send("Echo: " + msg.text)
}
return ws
})</ code ></ pre >
2026-01-25 01:47:47 +00:00
< div class = "method-signature" >
< span class = "method-name" > use</ span > (< span class = "param" > middleware</ span > ) → < span class = "type" > Application</ span >
</ div >
< p > Adds middleware function. Middleware receives Request, returns Response or null to continue.</ p >
< div class = "method-signature" >
< span class = "method-name" > run</ span > (< span class = "param" > host</ span > , < span class = "param" > port</ span > )
</ div >
< p > Starts the server (blocking).</ p >
< h2 > Request Class</ h2 >
< div class = "class-header" >
< h3 > Request</ h3 >
< p > Incoming HTTP request</ p >
</ div >
< h3 > Properties</ h3 >
< table >
< tr >
< th > Property</ th >
< th > Type</ th >
< th > Description</ th >
</ tr >
< tr >
< td >< code > method</ code ></ td >
< td > String</ td >
< td > HTTP method (GET, POST, etc.)</ td >
</ tr >
< tr >
< td >< code > path</ code ></ td >
< td > String</ td >
< td > Request path without query string</ td >
</ tr >
< tr >
< td >< code > query</ code ></ td >
< td > Map</ td >
< td > Parsed query parameters</ td >
</ tr >
< tr >
< td >< code > headers</ code ></ td >
< td > Map</ td >
< td > Request headers</ td >
</ tr >
< tr >
< td >< code > body</ code ></ td >
< td > String</ td >
< td > Raw request body</ td >
</ tr >
< tr >
< td >< code > params</ code ></ td >
< td > Map</ td >
< td > Route parameters from URL</ td >
</ tr >
< tr >
< td >< code > cookies</ code ></ td >
< td > Map</ td >
< td > Parsed cookies</ td >
</ tr >
< tr >
< td >< code > session</ code ></ td >
< td > Session</ td >
< td > Session data</ td >
</ tr >
</ table >
< h3 > Methods</ h3 >
< div class = "method-signature" >
< span class = "method-name" > header</ span > (< span class = "param" > name</ span > ) → < span class = "type" > String|null</ span >
</ div >
< p > Gets header value (case-insensitive).</ p >
< div class = "method-signature" >
< span class = "method-name" > json</ span > → < span class = "type" > Map|List</ span >
</ div >
< p > Parses body as JSON (cached).</ p >
< div class = "method-signature" >
< span class = "method-name" > form</ span > → < span class = "type" > Map</ span >
</ div >
< p > Parses body as form data (cached).</ p >
< h2 > Response Class</ h2 >
< div class = "class-header" >
< h3 > Response</ h3 >
< p > HTTP response builder</ p >
</ div >
< h3 > Static Factory Methods</ h3 >
< div class = "method-signature" >
< span class = "method-name" > Response.text</ span > (< span class = "param" > content</ span > ) → < span class = "type" > Response</ span >
</ div >
< p > Creates plain text response.</ p >
< div class = "method-signature" >
< span class = "method-name" > Response.html</ span > (< span class = "param" > content</ span > ) → < span class = "type" > Response</ span >
</ div >
< p > Creates HTML response.</ p >
< div class = "method-signature" >
< span class = "method-name" > Response.json</ span > (< span class = "param" > data</ span > ) → < span class = "type" > Response</ span >
</ div >
< p > Creates JSON response (automatically stringifies).</ p >
< div class = "method-signature" >
< span class = "method-name" > Response.redirect</ span > (< span class = "param" > url</ span > ) → < span class = "type" > Response</ span >
</ div >
< div class = "method-signature" >
< span class = "method-name" > Response.redirect</ span > (< span class = "param" > url</ span > , < span class = "param" > status</ span > ) → < span class = "type" > Response</ span >
</ div >
< p > Creates redirect response (default 302).</ p >
< div class = "method-signature" >
< span class = "method-name" > Response.file</ span > (< span class = "param" > path</ span > ) → < span class = "type" > Response</ span >
</ div >
< div class = "method-signature" >
< span class = "method-name" > Response.file</ span > (< span class = "param" > path</ span > , < span class = "param" > contentType</ span > ) → < span class = "type" > Response</ span >
</ div >
< p > Serves a file. Auto-detects content type if not specified.</ p >
< h3 > Instance Methods</ h3 >
< div class = "method-signature" >
< span class = "method-name" > header</ span > (< span class = "param" > name</ span > , < span class = "param" > value</ span > ) → < span class = "type" > Response</ span >
</ div >
< p > Sets a response header.</ p >
< div class = "method-signature" >
< span class = "method-name" > cookie</ span > (< span class = "param" > name</ span > , < span class = "param" > value</ span > ) → < span class = "type" > Response</ span >
</ div >
< div class = "method-signature" >
< span class = "method-name" > cookie</ span > (< span class = "param" > name</ span > , < span class = "param" > value</ span > , < span class = "param" > options</ span > ) → < span class = "type" > Response</ span >
</ div >
< p > Sets a cookie. Options: < code > path</ code > , < code > maxAge</ code > , < code > httpOnly</ code > , < code > secure</ code > .</ p >
< h3 > Properties</ h3 >
< div class = "method-signature" >
< span class = "method-name" > status</ span > → < span class = "type" > Num</ span >
</ div >
< p > Gets or sets HTTP status code.</ p >
< div class = "method-signature" >
< span class = "method-name" > body</ span > → < span class = "type" > String</ span >
</ div >
< p > Gets or sets response body.</ p >
< h2 > View Class</ h2 >
< div class = "class-header" >
< h3 > View</ h3 >
< p > Base class for class-based views</ p >
</ div >
< p > Subclass and override methods for each HTTP method:</ p >
< pre >< code > class UserView is View {
get(request) {
return Response.json({"users": []})
}
post(request) {
var data = request.json
return Response.json({"created": data})
}
2026-01-25 18:02:02 +00:00
websocket(request) {
var ws = WebSocketResponse.new()
ws.prepare(request)
ws.iterate(Fn.new { |msg|
if (msg.isText) ws.send("Echo: " + msg.text)
})
return ws
}
2026-01-25 01:47:47 +00:00
}</ code ></ pre >
2026-01-25 18:02:02 +00:00
< h2 > WebSocketResponse Class</ h2 >
< div class = "class-header" >
< h3 > WebSocketResponse</ h3 >
< p > Server-side WebSocket connection handler (aiohttp-style API)</ p >
</ div >
< p > The < code > WebSocketResponse</ code > class provides a server-side WebSocket handler following the aiohttp-style API pattern. It handles the WebSocket handshake, frame encoding/decoding, and provides convenient methods for sending and receiving messages.</ p >
< h3 > Constructor</ h3 >
< div class = "method-signature" >
< span class = "method-name" > WebSocketResponse.new</ span > () → < span class = "type" > WebSocketResponse</ span >
</ div >
< p > Creates a new WebSocket response handler. Call < code > prepare(request)</ code > to complete the handshake before sending or receiving messages.</ p >
< h3 > Properties</ h3 >
< table >
< tr >
< th > Property</ th >
< th > Type</ th >
< th > Description</ th >
</ tr >
< tr >
< td >< code > isPrepared</ code ></ td >
< td > Bool</ td >
< td > True if the WebSocket handshake has been performed</ td >
</ tr >
< tr >
< td >< code > isOpen</ code ></ td >
< td > Bool</ td >
< td > True if the connection is open and ready for messages</ td >
</ tr >
</ table >
< h3 > Connection Methods</ h3 >
< div class = "method-signature" >
< span class = "method-name" > prepare</ span > (< span class = "param" > request</ span > ) → < span class = "type" > WebSocketResponse</ span >
</ div >
< p > Performs the WebSocket handshake using the request's socket. Must be called before sending or receiving messages. Returns < code > this</ code > for method chaining.</ p >
< ul class = "param-list" >
< li >< span class = "param-name" > request</ span > < span class = "param-type" > (Request)</ span > - The HTTP request containing the WebSocket upgrade headers</ li >
</ ul >
< pre >< code > var ws = WebSocketResponse.new()
ws.prepare(request)</ code ></ pre >
< h3 > Sending Methods</ h3 >
< div class = "method-signature" >
< span class = "method-name" > send</ span > (< span class = "param" > text</ span > )
</ div >
< div class = "method-signature" >
< span class = "method-name" > sendText</ span > (< span class = "param" > text</ span > )
</ div >
< p > Sends a text message to the client. Both methods are equivalent.</ p >
< ul class = "param-list" >
< li >< span class = "param-name" > text</ span > < span class = "param-type" > (String)</ span > - Text message to send</ li >
</ ul >
< pre >< code > ws.send("Hello, client!")
ws.sendText("Another message")</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > sendJson</ span > (< span class = "param" > data</ span > )
</ div >
< p > Serializes the data to JSON and sends it as a text message. Convenience method for JSON-based protocols.</ p >
< ul class = "param-list" >
< li >< span class = "param-name" > data</ span > < span class = "param-type" > (Map|List)</ span > - Data to serialize and send</ li >
</ ul >
< pre >< code > ws.sendJson({"type": "update", "value": 42})
ws.sendJson(["item1", "item2", "item3"])</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > sendBinary</ span > (< span class = "param" > bytes</ span > )
</ div >
< p > Sends a binary message to the client.</ p >
< ul class = "param-list" >
< li >< span class = "param-name" > bytes</ span > < span class = "param-type" > (List)</ span > - List of bytes to send</ li >
</ ul >
< pre >< code > ws.sendBinary([0x01, 0x02, 0x03, 0x04])
var imageData = File.readBytes("image.png")
ws.sendBinary(imageData)</ code ></ pre >
< h3 > Receiving Methods</ h3 >
< div class = "method-signature" >
< span class = "method-name" > receive</ span > () → < span class = "type" > WebSocketMessage|null</ span >
</ div >
< p > Receives the next message from the client. Blocks until a message arrives. Returns a < code > WebSocketMessage</ code > object, or < code > null</ code > if the connection closed unexpectedly. Ping frames are automatically answered with pong.</ p >
< pre >< code > var msg = ws.receive()
if (msg != null && msg.isText) {
System.print("Got: %(msg.text)")
}</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > receiveJson</ span > () → < span class = "type" > Map|List|WebSocketMessage|null</ span >
</ div >
< p > Receives a text message and parses it as JSON. Returns the parsed JSON data, the close frame if the connection was closed, or < code > null</ code > if the connection closed unexpectedly. Aborts if a binary frame is received.</ p >
< pre >< code > var data = ws.receiveJson()
if (data is Map && data.containsKey("type")) {
if (data["type"] == "message") {
System.print("Message: %(data["text"])")
}
}</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > receiveText</ span > () → < span class = "type" > String|null</ span >
</ div >
< p > Receives a message and returns only the text content. Returns < code > null</ code > if the message is not a text frame, if the connection was closed, or if the connection closed unexpectedly.</ p >
< pre >< code > var text = ws.receiveText()
if (text != null) {
System.print("Received: %(text)")
}</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > receiveBinary</ span > () → < span class = "type" > List|null</ span >
</ div >
< p > Receives a message and returns only the binary content. Returns < code > null</ code > if the message is not a binary frame, if the connection was closed, or if the connection closed unexpectedly.</ p >
< pre >< code > var bytes = ws.receiveBinary()
if (bytes != null) {
System.print("Received %(bytes.count) bytes")
}</ code ></ pre >
< div class = "method-signature" >
< span class = "method-name" > iterate</ span > (< span class = "param" > callback</ span > )
</ div >
< p > Helper method that loops while the connection is open, calling the callback for each received message (excluding close frames). Simplifies the common receive loop pattern.</ p >
< ul class = "param-list" >
< li >< span class = "param-name" > callback</ span > < span class = "param-type" > (Fn)</ span > - Function to call with each WebSocketMessage</ li >
</ ul >
< pre >< code > ws.iterate(Fn.new { |msg|
if (msg.isText) {
ws.send("Echo: " + msg.text)
}
})</ code ></ pre >
< h3 > Control Methods</ h3 >
< div class = "method-signature" >
< span class = "method-name" > ping</ span > ()
</ div >
< div class = "method-signature" >
< span class = "method-name" > ping</ span > (< span class = "param" > data</ span > )
</ div >
< p > Sends a ping frame to check if the client is still connected. Payload must be 125 bytes or less.</ p >
< ul class = "param-list" >
< li >< span class = "param-name" > data</ span > < span class = "param-type" > (String|List)</ span > - Optional payload data</ li >
</ ul >
< div class = "method-signature" >
< span class = "method-name" > pong</ span > (< span class = "param" > data</ span > )
</ div >
< p > Sends a pong frame. Usually automatic in response to ping, but can be sent manually.</ p >
< div class = "method-signature" >
< span class = "method-name" > close</ span > ()
</ div >
< div class = "method-signature" >
< span class = "method-name" > close</ span > (< span class = "param" > code</ span > , < span class = "param" > reason</ span > )
</ div >
< p > Closes the WebSocket connection. Default code is 1000 (normal closure).</ p >
< ul class = "param-list" >
< li >< span class = "param-name" > code</ span > < span class = "param-type" > (Num)</ span > - Close status code (1000-4999)</ li >
< li >< span class = "param-name" > reason</ span > < span class = "param-type" > (String)</ span > - Human-readable close reason</ li >
</ ul >
< pre >< code > ws.close()
ws.close(1000, "Goodbye")</ code ></ pre >
< h3 > WebSocketMessage Reference</ h3 >
< p > The < code > receive()</ code > method returns a < code > WebSocketMessage</ code > object with the following properties:</ p >
< table >
< tr >
< th > Property</ th >
< th > Type</ th >
< th > Description</ th >
</ tr >
< tr >
< td >< code > opcode</ code ></ td >
< td > Num</ td >
< td > Frame opcode (1=text, 2=binary, 8=close, 9=ping, 10=pong)</ td >
</ tr >
< tr >
< td >< code > fin</ code ></ td >
< td > Bool</ td >
< td > True if this is the final fragment</ td >
</ tr >
< tr >
< td >< code > isText</ code ></ td >
< td > Bool</ td >
< td > True if this is a text frame</ td >
</ tr >
< tr >
< td >< code > isBinary</ code ></ td >
< td > Bool</ td >
< td > True if this is a binary frame</ td >
</ tr >
< tr >
< td >< code > isClose</ code ></ td >
< td > Bool</ td >
< td > True if this is a close frame</ td >
</ tr >
< tr >
< td >< code > isPing</ code ></ td >
< td > Bool</ td >
< td > True if this is a ping frame</ td >
</ tr >
< tr >
< td >< code > isPong</ code ></ td >
< td > Bool</ td >
< td > True if this is a pong frame</ td >
</ tr >
< tr >
< td >< code > text</ code ></ td >
< td > String</ td >
< td > Payload as string (for text frames)</ td >
</ tr >
< tr >
< td >< code > bytes</ code ></ td >
< td > List</ td >
< td > Payload as byte list</ td >
</ tr >
< tr >
< td >< code > closeCode</ code ></ td >
< td > Num</ td >
< td > Close status code (for close frames)</ td >
</ tr >
< tr >
< td >< code > closeReason</ code ></ td >
< td > String</ td >
< td > Close reason text (for close frames)</ td >
</ tr >
</ table >
< h3 > Close Codes Reference</ h3 >
< table >
< tr >
< th > Code</ th >
< th > Meaning</ th >
</ tr >
< tr >
< td > 1000</ td >
< td > Normal closure</ td >
</ tr >
< tr >
< td > 1001</ td >
< td > Going away (server shutdown)</ td >
</ tr >
< tr >
< td > 1002</ td >
< td > Protocol error</ td >
</ tr >
< tr >
< td > 1003</ td >
< td > Unsupported data type</ td >
</ tr >
< tr >
< td > 1005</ td >
< td > No status received</ td >
</ tr >
< tr >
< td > 1006</ td >
< td > Abnormal closure</ td >
</ tr >
< tr >
< td > 1011</ td >
< td > Server error</ td >
</ tr >
< tr >
< td > 4000-4999</ td >
< td > Application-specific codes</ td >
</ tr >
</ table >
< div class = "admonition note" >
< div class = "admonition-title" > Note</ div >
< p > Ping frames received from clients are automatically answered with pong frames. You typically do not need to handle ping/pong manually unless implementing custom keepalive logic.</ p >
</ div >
2026-01-25 01:47:47 +00:00
< h2 > Session Class</ h2 >
< div class = "class-header" >
< h3 > Session</ h3 >
< p > Session data storage</ p >
</ div >
< h3 > Properties and Methods</ h3 >
< div class = "method-signature" >
< span class = "method-name" > id</ span > → < span class = "type" > String</ span >
</ div >
< p > The session ID.</ p >
< div class = "method-signature" >
< span class = "method-name" > [key]</ span > → < span class = "type" > any</ span >
</ div >
< p > Gets session value.</ p >
< div class = "method-signature" >
< span class = "method-name" > [key]=</ span > (< span class = "param" > value</ span > )
</ div >
< p > Sets session value.</ p >
< div class = "method-signature" >
< span class = "method-name" > remove</ span > (< span class = "param" > key</ span > )
</ div >
< p > Removes a session key.</ p >
< h2 > Client Class</ h2 >
< div class = "class-header" >
< h3 > Client</ h3 >
< p > HTTP client for making requests</ p >
</ div >
< h3 > Static Methods</ h3 >
< div class = "method-signature" >
< span class = "method-name" > Client.get</ span > (< span class = "param" > url</ span > ) → < span class = "type" > Map</ span >
</ div >
< div class = "method-signature" >
< span class = "method-name" > Client.get</ span > (< span class = "param" > url</ span > , < span class = "param" > options</ span > ) → < span class = "type" > Map</ span >
</ div >
< p > Makes GET request.</ p >
< div class = "method-signature" >
< span class = "method-name" > Client.post</ span > (< span class = "param" > url</ span > ) → < span class = "type" > Map</ span >
</ div >
< div class = "method-signature" >
< span class = "method-name" > Client.post</ span > (< span class = "param" > url</ span > , < span class = "param" > options</ span > ) → < span class = "type" > Map</ span >
</ div >
< p > Makes POST request.</ p >
< div class = "method-signature" >
< span class = "method-name" > Client.put</ span > (< span class = "param" > url</ span > , < span class = "param" > options</ span > ) → < span class = "type" > Map</ span >
</ div >
< p > Makes PUT request.</ p >
< div class = "method-signature" >
< span class = "method-name" > Client.delete</ span > (< span class = "param" > url</ span > , < span class = "param" > options</ span > ) → < span class = "type" > Map</ span >
</ div >
< p > Makes DELETE request.</ p >
< h3 > Client Options</ h3 >
< table >
< tr >
< th > Option</ th >
< th > Type</ th >
< th > Description</ th >
</ tr >
< tr >
< td >< code > headers</ code ></ td >
< td > Map</ td >
< td > Request headers</ td >
</ tr >
< tr >
< td >< code > body</ code ></ td >
< td > String</ td >
< td > Request body</ td >
</ tr >
< tr >
< td >< code > json</ code ></ td >
< td > Map/List</ td >
< td > JSON body (auto-stringifies and sets Content-Type)</ td >
</ tr >
</ table >
< h3 > Response Format</ h3 >
< pre >< code > {
"status": 200,
"headers": {"Content-Type": "application/json"},
"body": "{...}"
}</ code ></ pre >
< h2 > Examples</ h2 >
< h3 > Basic Server</ h3 >
< pre >< code > import "web" for Application, Response
var app = Application.new()
app.get("/", Fn.new { |req|
return Response.html("< h1> Hello World< /h1> ")
})
app.get("/api/users", Fn.new { |req|
return Response.json({"users": ["Alice", "Bob"]})
})
app.run("0.0.0.0", 8080)</ code ></ pre >
< h3 > Route Parameters</ h3 >
< pre >< code > import "web" for Application, Response
var app = Application.new()
app.get("/users/:id", Fn.new { |req|
var userId = req.params["id"]
return Response.json({"id": userId})
})
app.get("/files/*", Fn.new { |req|
return Response.text("Wildcard route")
})
app.run("0.0.0.0", 8080)</ code ></ pre >
< h3 > Class-Based Views</ h3 >
< pre >< code > import "web" for Application, Response, View
class ArticleView is View {
get(request) {
return Response.json({"articles": []})
}
post(request) {
var data = request.json
return Response.json({"created": data})
}
}
var app = Application.new()
app.addView("/articles", ArticleView)
app.run("0.0.0.0", 8080)</ code ></ pre >
< h3 > Sessions</ h3 >
< pre >< code > import "web" for Application, Response
var app = Application.new()
app.get("/login", Fn.new { |req|
req.session["user"] = "Alice"
return Response.text("Logged in")
})
app.get("/profile", Fn.new { |req|
var user = req.session["user"]
if (user == null) {
return Response.redirect("/login")
}
return Response.text("Hello, %(user)")
})
app.run("0.0.0.0", 8080)</ code ></ pre >
< h3 > Middleware</ h3 >
< pre >< code > import "web" for Application, Response
var app = Application.new()
app.use(Fn.new { |req|
System.print("%(req.method) %(req.path)")
return null
})
app.use(Fn.new { |req|
if (req.path.startsWith("/admin") && !req.session["isAdmin"]) {
return Response.redirect("/login")
}
return null
})
app.get("/", Fn.new { |req|
return Response.text("Home")
})
app.run("0.0.0.0", 8080)</ code ></ pre >
< h3 > HTTP Client</ h3 >
< pre >< code > import "web" for Client
import "json" for Json
var response = Client.get("https://api.example.com/data")
System.print("Status: %(response["status"])")
System.print("Body: %(response["body"])")
var postResponse = Client.post("https://api.example.com/users", {
"json": {"name": "Alice", "email": "alice@example.com"}
})
var data = Json.parse(postResponse["body"])</ code ></ pre >
2026-01-25 18:02:02 +00:00
< h3 > Concurrent HTTP Requests</ h3 >
< p > Use the < code > async</ code > keyword to create futures and < code > await</ code > to wait for results. Each request runs in its own fiber and executes in parallel while waiting for I/O.</ p >
< pre >< code > import "web" for Client
import "scheduler" for Scheduler, Future
var urls = [
"https://api.example.com/users",
"https://api.example.com/posts",
"https://api.example.com/comments"
]
var futures = []
for (url in urls) {
futures.add(async { Client.get(url) })
}
var responses = []
for (future in futures) {
responses.add(await future)
}
for (i in 0...urls.count) {
System.print("%(urls[i]): %(responses[i]["status"])")
}</ code ></ pre >
< h3 > Parallel Batch Requests</ h3 >
< p > For batch operations, create all futures first, then await them. The requests execute concurrently.</ p >
< pre >< code > import "web" for Client
import "scheduler" for Scheduler, Future
import "json" for Json
var fetchUrl = async { |url| Client.get(url) }
class BatchClient {
static getAll(urls) {
var futures = []
for (url in urls) {
futures.add(async { Client.get(url) })
}
var results = []
for (f in futures) {
results.add(await f)
}
return results
}
static postAll(requests) {
var futures = []
for (req in requests) {
futures.add(async { Client.post(req["url"], req["options"]) })
}
var results = []
for (f in futures) {
results.add(await f)
}
return results
}
}
var urls = [
"https://api.example.com/endpoint1",
"https://api.example.com/endpoint2",
"https://api.example.com/endpoint3"
]
var results = BatchClient.getAll(urls)
for (result in results) {
System.print("Status: %(result["status"])")
}</ code ></ pre >
< h3 > Parameterized Async Functions</ h3 >
< p > Create reusable async functions with parameters using < code > async { |args| ... }</ code > . There are two ways to invoke these functions:</ p >
< ul >
< li >< strong >< code > await fn(args)</ code ></ strong > — Direct call, waits immediately (sequential execution)</ li >
< li >< strong >< code > fn.call(args)</ code ></ strong > — Returns a Future, starts execution without waiting (concurrent execution)</ li >
</ ul >
< pre >< code > import "web" for Client
import "scheduler" for Scheduler, Future
import "json" for Json
var fetchJson = async { |url|
var response = Client.get(url)
return Json.parse(response["body"])
}
var postJson = async { |url, data|
var response = Client.post(url, {"json": data})
return Json.parse(response["body"])
}
// DIRECT CALLING (preferred for sequential operations)
// Waits for each request to complete before continuing
var user = await fetchJson("https://api.example.com/user/1")
System.print(user["name"])
// CONCURRENT EXECUTION (use .call() to start without waiting)
// Both requests run at the same time
var f1 = fetchJson.call("https://api.example.com/posts")
var f2 = fetchJson.call("https://api.example.com/comments")
// Now wait for results
var posts = await f1
var comments = await f2</ code ></ pre >
< div class = "admonition tip" >
< div class = "admonition-title" > Direct Calling vs .call()</ div >
< p > Use < code > await fn(args)</ code > when you want sequential execution. Use < code > fn.call(args)</ code > when you want to start multiple operations concurrently, then < code > await</ code > their results later.</ p >
</ div >
< h3 > WebSocket Echo Server</ h3 >
< pre >< code > import "web" for Application, Response, WebSocketResponse
var app = Application.new()
app.get("/", Fn.new { |req|
return Response.html("< script> var ws=new WebSocket('ws://localhost:8080/ws');ws.onmessage=e=> console.log(e.data);ws.onopen=()=> ws.send('hello');< /script> ")
})
app.websocket("/ws", Fn.new { |req|
var ws = WebSocketResponse.new()
ws.prepare(req)
while (ws.isOpen) {
var msg = ws.receive()
if (msg == null || msg.isClose) break
if (msg.isText) {
ws.send("Echo: " + msg.text)
}
}
ws.close()
return ws
})
app.run("0.0.0.0", 8080)</ code ></ pre >
< h3 > WebSocket JSON API</ h3 >
< pre >< code > import "web" for Application, Response, WebSocketResponse
var app = Application.new()
app.websocket("/api", Fn.new { |req|
var ws = WebSocketResponse.new()
ws.prepare(req)
ws.sendJson({"type": "welcome", "message": "Connected to API"})
while (ws.isOpen) {
var data = ws.receiveJson()
if (data == null) break
if (data is Map && data["type"] == "ping") {
ws.sendJson({"type": "pong", "timestamp": data["timestamp"]})
} else if (data is Map && data["type"] == "echo") {
ws.sendJson({"type": "echo", "data": data["data"]})
}
}
return ws
})
app.run("0.0.0.0", 8080)</ code ></ pre >
< h3 > WebSocket Binary Data Transfer</ h3 >
< pre >< code > import "web" for Application, Response, WebSocketResponse
import "io" for File
var app = Application.new()
app.websocket("/binary", Fn.new { |req|
var ws = WebSocketResponse.new()
ws.prepare(req)
ws.send("Ready to receive binary data")
while (ws.isOpen) {
var msg = ws.receive()
if (msg == null || msg.isClose) break
if (msg.isBinary) {
System.print("Received %(msg.bytes.count) bytes")
ws.sendJson({"received": msg.bytes.count, "status": "ok"})
} else if (msg.isText) {
if (msg.text == "send-file") {
var data = File.readBytes("example.bin")
ws.sendBinary(data)
}
}
}
return ws
})
app.run("0.0.0.0", 8080)</ code ></ pre >
< h3 > WebSocket in Class-Based View</ h3 >
< pre >< code > import "web" for Application, Response, View, WebSocketResponse
class ChatView is View {
get(request) {
return Response.html("< h1> Chat Room< /h1>< p> Connect via WebSocket< /p> ")
}
websocket(request) {
var ws = WebSocketResponse.new()
ws.prepare(request)
ws.sendJson({"type": "connected", "room": "general"})
ws.iterate(Fn.new { |msg|
if (msg.isText) {
var data = Json.parse(msg.text)
if (data["type"] == "message") {
ws.sendJson({
"type": "broadcast",
"from": data["from"],
"text": data["text"]
})
}
}
})
return ws
}
}
var app = Application.new()
app.addView("/chat", ChatView)
app.run("0.0.0.0", 8080)</ code ></ pre >
< div class = "admonition warning" >
< div class = "admonition-title" > WebSocket Error Handling</ div >
< p > Always check for < code > null</ code > or close frames when receiving messages. The connection can close at any time due to network issues or client disconnection. Wrap WebSocket handling in proper error handling to prevent server crashes.</ p >
</ div >
2026-01-25 01:47:47 +00:00
< div class = "admonition note" >
< div class = "admonition-title" > Note</ div >
< p > Sessions are stored in memory by default. Data is lost when the server restarts. For production, consider persisting session data to a database.</ p >
</ div >
</ article >
< footer class = "page-footer" >
2026-01-25 18:02:02 +00:00
< a href = "wdantic.html" class = "prev" > wdantic</ a >
< a href = "websocket.html" class = "next" > websocket</ a >
2026-01-25 01:47:47 +00:00
</ footer >
</ main >
</ div >
< script src = "../js/main.js" ></ script >
</ body >
</ html >