Web server

On this page

MatchBox has two web runtimes. Both are implemented in the matchbox_server crate and run on the same VM as the CLI.

Webroot serverApp server
Start withmatchbox --servematchbox_server --app app.bxs
RoutingThe file path in the URLRoutes you register in a script
Pages.bxm and .bxs templates plus static filesHandlers, middleware, event.setView() templates
Configurationboxlang.json in the webrootCalls in the script (app.listen())
WebSocketsYes, through boxlang.jsonYes, through app.enableWebSockets()
ExampleWeb serverApp server, WebSocket counter

Webroot server

matchbox --serve --webroot ./site --port 8080
FlagDefaultMeaning
--webroot <dir>.Directory to serve.
--port <n>8080Listen port.
--host <addr>127.0.0.1Bind address. Use 0.0.0.0 to accept connections from other machines, for example inside a container.

matchbox --serve accepts only these three flags and reads boxlang.json from the webroot. The --config <file> flag exists only on the matchbox_server binary, which you build from the MatchBox source tree with cargo build --release -p matchbox_server.

On startup the server prints the address, the resolved webroot and whether rewrites are enabled. Files are read on every request, so edits appear without a restart.

What gets served

RequestResult
A .bxm or .bxs fileRendered as a template. Response is 200 with Content-Type: text/html and a MBX_SESSION_ID cookie.
Any other fileSent as is, with a content type guessed from the extension.
A directoryindex.bxm, then index.bxs, inside it.
A missing file404 Not Found, or the rewrite file when rewrites are on.
A path with a segment that starts with .403.
A path that climbs out of the webroot403.
A method other than GET, HEAD or POST405 with allow: GET,HEAD,POST.

HEAD returns the headers of the GET response with an empty body. A template that throws returns 500 with the error text.

Scopes in templates

ScopeContents
urlQuery-string parameters.
formFields of an application/x-www-form-urlencoded POST body. JSON bodies are not exposed.
cookieRequest cookies, plus MBX_SESSION_ID.
sessionA struct kept in server memory per MBX_SESSION_ID cookie.
cgirequest_method, script_name, path_info, server_name, server_port, http_host, content_type, content_length. It is not the process environment.

All values are strings. Session values are stored as strings between requests, so a number or struct you assign comes back as its string form on the next request. Sessions have no expiry and are lost when the process stops.

To read an environment variable use getSystemSetting( "NAME", "default" ). There is no env scope.

boxlang.json

Place boxlang.json in the webroot. All keys are optional.

{
  "rewrites": true,
  "rewriteFileName": "index.bxm",
  "websocket": {
    "uri": "/ws",
    "listenerClass": "Chat",
    "listenerState": { "greeting": "hello" },
    "handler": "WebSocket.bx"
  }
}
KeyDefaultMeaning
rewritesfalseWhen true, a request whose file does not exist is answered by rewriteFileName instead of a 404. The original path is available as cgi.path_info, and the query string stays in url. This applies to every missing file, including missing images and stylesheets.
rewriteFileNameindex.bxmFront-controller file, relative to the webroot. A value that contains .. or starts with / is replaced by the default.
websocketnoneEnables a WebSocket endpoint. See below.

If boxlang.json is not valid JSON, or a websocket block is missing one of uri, listenerClass or listenerState, the whole file is ignored without a warning and the defaults apply. Check the "Rewrites:" line printed at startup.

Give each directory an index template; directory requests do not fall back to the rewrite file.

WebSockets in the webroot server

The websocket block names a class and the file that defines it.

KeyMeaning
uriPath that accepts WebSocket upgrades.
listenerClassName of the class to instantiate.
listenerStateJSON object. Its keys are set as instance variables of the listener (variables.greeting). The constructor is not called.
handlerFile that defines the class. Default WebSocket.bx. It is looked up case-insensitively in the webroot directory itself, not in subdirectories.
class Chat {
    function onConnect( channel ) {
        channel.sendJson( { "welcome": variables.greeting, "id": channel.getId() } );
    }

    function onMessage( message, channel ) {
        channel.broadcastMessage( "echo:" & message );
    }

    function onClose( channel ) {}
}

If the handler file is missing or does not compile, the server starts without a WebSocket endpoint. An error thrown in onConnect or onMessage is printed on the server's stderr and closes that connection with code 1011.

WebSocket channel API

The same channel object is passed to listeners in both server modes.

MethodPurpose
sendMessage( text ), sendText( text )Send a text frame to this client.
sendJson( value )Serialize value as JSON and send it as text.
sendBytes( bytes )Send a binary frame.
broadcastMessage( text ), broadcastText( text )Text frame to every connected client, including this one.
broadcastJson( value ), broadcastBytes( bytes )The same for JSON and binary payloads.
close( [code], [reason] )Close the connection. Default code 1000.
getId()Server-generated UUID of this connection.
getPath()Path of the upgrade request, such as /ws.
getURL()Full URL of the upgrade request, including the query string.
getHTTPHeader( name, [default] )Header of the upgrade request. Names are matched case-insensitively. Returns null when missing and no default is given.

Listener hooks are onConnect( channel ), onMessage( message, channel ) and onClose( channel ). message is a string for text frames and bytes for binary frames. The channel has no properties: channel.id is null, use getId().

App server

The app server runs one script that builds a routed application with web.server(). It is started with the --app flag of the matchbox_server binary:

cargo run -p matchbox_server -- --app app.bxs

matchbox app.bxs cannot run such a script: the CLI does not install the web namespace, and web.server() fails with Method server not found on null. In --app mode the --port, --host, --webroot and --config flags are ignored; the address comes from app.listen().

import boxlang.web;

app = web.server();

app.get( "/hello/:name", function( event, rc, prc ) {
    event.renderJson( { "message": "Hello " & rc.name } );
} );

app.listen( 8090 );

The script is compiled once at startup and is not reloaded when the file changes. Its top level runs at startup and then again on every request, in a fresh VM, so variables set at the top of the script do not carry over between requests. Keep shared state in session, a file or a database.

Application object

CallPurpose
app.get/post/put/patch/delete( path, [middleware...], handler )Register a route.
app.use( middleware )Add middleware that runs for every request.
app.group( prefix )Returns a group with the same methods, a path prefix and its own use(). Groups nest.
app.middleware.buildStaticFiles( mount, dir )Middleware that serves files from dir (relative to the script) under mount. Missing files fall through to routing.
app.webhook( builder, handler ), app.buildWebhook()Signed webhook endpoint. See below.
app.enableWebSockets( uri, listener )Attach a listener object. Its state is snapshotted when you register it.
app.listen( port ) or app.listen( { host, port } )Where to listen. Default 127.0.0.1:8080.

Routes:

  • A path segment that starts with : captures that segment. There are no wildcards or regular expressions.
  • A route matches only when the number of segments is equal. The first registered match wins.
  • Only get, post, put, patch and delete can be registered. Other methods get 404.
  • An unmatched request returns 404 with the text Not Found.

Middleware has the signature function( event, rc, prc, next ) and continues the chain with next.run(). Handlers have the signature function( event, rc, prc ).

Request collections

NameContents
rcQuery parameters, then form fields, then top-level keys of a JSON body, then route parameters. A later source overwrites an earlier one with the same name.
prcPrivate collection for passing data between middleware and handlers.
sessionPer-session struct, keyed by the MBX_SESSION_ID cookie that the server sets. The store is in memory, round-trips through JSON (only JSON types survive), and has no expiry.

JSON bodies are parsed when Content-Type is JSON. The server does not limit the request body size.

The event object

MethodReturns or does
getCollection(), getPrivateCollection(), getSession()The rc, prc and session structs.
valueExists( key ), privateValueExists( key ), sessionExists( key )Whether the key exists.
getValue( key, [default] ), getPrivateValue( key, [default] ), getSessionValue( key, [default] )Read a value.
setValue( key, value, [private=false] )Set a value in rc, or in prc when the third argument is true.
paramValue( key, default ), paramPrivateValue( key, default ), paramSessionValue( key, default )Set the value only when the key is missing. setSessionValue is an alias of paramSessionValue.
getOnly( keys ), getExcept( keys )A struct holding only, or everything but, the named keys of rc.
getRouteParams()Struct of the :name captures.
getCurrentRoute()The matched pattern, such as /hello/:name.
getHTTPMethod(), getRequestMethod()The request method.
getPath( [withQuery=true] )Request path, with the raw query string unless false.
getURL( [withQuery=true] )Full request URL, or only the path when false.
getHTTPHeader( name, [default] )Request header. Names are case-insensitive.
getHTTPCookie( name, [default] ), getCookieRequest cookie.
isJSON(), isAjax()Whether the request has a JSON content type, or X-Requested-With: XMLHttpRequest.
getHTTPContent( [asJson=false] )Raw body as a string, or parsed JSON when true. Invalid JSON gives a 400.
setHTTPHeader( name, value )Add a response header.
setHTTPCookie( name, value, [path="/"], [httpOnly=false] ), setCookieAdd a Set-Cookie header.
setHTTPStatus( code )Set the response status.
clearSession(), invalidateSession()Empty the session struct.
renderText( text ), renderHTML( html ), renderJSON( value )Set the body and content type.
renderData( "json" \| "html" \| "text" \| "plain", data )Same, with the format as an argument.
setView( path, [viewArgs] ), renderTemplate( path, [viewArgs] )Render a .bxm template (relative to the script) as HTML. The template sees event, rc, prc, session and viewArgs.

renderJSON writes numbers as floating point, so 1 is serialized as 1.0.

Errors

If a handler throws, the response status comes from the start of the error message:

Message containsStatus
Bad Request:400
Unauthorized:401
Conflict:409
anything else500

The body is the error text, or {"error": "..."} when the request sends Accept: application/json.

Webhooks

app.webhook(
    app.buildWebhook()
        .path( "/webhooks/stripe" )
        .secret( getSystemSetting( "STRIPE_SECRET" ) )
        .signatureHeader( "stripe-signature" )
        .prefix( "sha256=" )
        .timestampHeader( "stripe-timestamp" )
        .toleranceSeconds( 300 )
        .replayHeader( "stripe-event-id" )
        .replayTtlSeconds( 300 ),
    function( event, rc, prc ) {
        event.renderText( "ok" );
    }
);

A webhook is a POST route. The server computes an HMAC-SHA256 of the raw request body with the secret and compares its lowercase hex form with the signature header, after removing prefix if the header starts with it. A bare hex signature is also accepted.

Builder methodRule
path, secret, signatureHeaderRequired.
prefixOptional text stripped from the header value.
timestampHeader and toleranceSecondsSet both or neither. The header must hold Unix seconds within the tolerance of the server clock. The timestamp is checked, not signed.
replayHeaderDelivery id header. A repeated id for the same path is rejected with 409 for the TTL.
replayTtlSecondsRequires replayHeader. Default 3600. The replay store is in memory.

A missing or wrong signature, a missing or stale timestamp, or a missing delivery id returns 401.

WebSockets in the app server

listener = new ClickCounterSocket();
listener.configure();
app.enableWebSockets( "/ws", listener );

The listener and the channel API are the same as in the webroot server, except that the listener is an instance you create and configure yourself. Its state is captured when enableWebSockets() is called and restored inside the WebSocket runtime.

Edit this page Download Markdown Last updated Oct 27, 2026, 1:45:34 PM Report an issue