### Compile and Run Basic WebSockets Example
Source: https://github.com/guzba/mummy/blob/master/README.md
Command to compile and run the basic WebSocket example using the Nim compiler.
```bash
nim c --threads:on --mm:orc -r examples/basic_websockets.nim
```
--------------------------------
### Basic Router Usage
Source: https://context7.com/guzba/mummy/llms.txt
Demonstrates how to define routes for common HTTP methods (GET, POST) and paths, including named path parameters and query parameters.
```APIDOC
## `Router` — Route HTTP requests by method and path
`Router` is a value type from `mummy/routers` that maps HTTP method + path combinations to handler procs. Routes are matched in the order they are added. Named path parameters use the `@name` syntax; single-segment wildcards use `*`; multi-segment wildcards use `**`. A `Router` converts implicitly to a `RequestHandler` via the `convertToHandler` converter.
```nim
import mummy, mummy/routers
proc listItems(request: Request) =
request.respond(200, @[("Content-Type", "text/plain")], "List of items")
proc getItem(request: Request) =
let id = request.pathParams["id"]
request.respond(200, @[("Content-Type", "text/plain")], "Item: " & id)
proc createItem(request: Request) =
request.respond(201, @[("Content-Type", "text/plain")], "Created: " & request.body)
proc searchItems(request: Request) =
let q = request.queryParams["q"]
request.respond(200, @[("Content-Type", "text/plain")], "Search: " & q)
var router: Router
# Convenience methods for common HTTP verbs
router.get("/items", listItems)
router.get("/items/@id", getItem)
router.post("/items", createItem)
router.get("/search", searchItems)
# Also available: router.put, router.delete, router.patch, router.head, router.options
# Generic: router.addRoute("CUSTOM_METHOD", "/path", handler)
let server = newServer(router)
server.serve(Port(8080))
# curl http://localhost:8080/items/42 -> "Item: 42"
# curl http://localhost:8080/search?q=hello -> "Search: hello"
# curl -X POST -d "thing" http://localhost:8080/items -> "Created: thing"
```
```
--------------------------------
### Bind and start serving with a custom address
Source: https://context7.com/guzba/mummy/llms.txt
Binds the server to a specified port and address, then enters the main I/O loop. This call blocks until `server.close()` is invoked. Use '0.0.0.0' to listen on all network interfaces.
```nim
import mummy
proc handler(request: Request) =
request.respond(200, @[("Content-Type", "text/plain")], "OK")
let server = newServer(handler)
# Listen on all interfaces for external access
server.serve(Port(8080), address = "0.0.0.0")
```
--------------------------------
### Example WebSocket Server in Nim
Source: https://github.com/guzba/mummy/blob/master/README.md
Sets up a basic WebSocket server with handlers for HTTP requests and WebSocket events. Requires the 'mummy' and 'mummy/routers' modules. The server listens on port 8080.
```nim
import mummy, mummy/routers
proc indexHandler(request: Request) =
var headers: HttpHeaders
headers["Content-Type"] = "text/html"
request.respond(200, headers, """
""")
proc upgradeHandler(request: Request) =
let websocket = request.upgradeToWebSocket()
websocket.send("Hello world from WebSocket!")
proc websocketHandler(
websocket: WebSocket,
event: WebSocketEvent,
message: Message
) =
case event:
of OpenEvent:
discard
of MessageEvent:
echo message.kind, ": ", message.data
of ErrorEvent:
discard
of CloseEvent:
discard
var router: Router
router.get("/", indexHandler)
router.get("/ws", upgradeHandler)
let server = newServer(router, websocketHandler)
echo "Serving on http://localhost:8080"
server.serve(Port(8080))
```
--------------------------------
### Create a new Mummy HTTP/WebSocket server
Source: https://context7.com/guzba/mummy/llms.txt
Initializes a new Mummy server with a request handler and an optional WebSocket handler. Configure worker threads, buffer sizes, and TCP settings. Use `server.serve()` to start listening.
```nim
import mummy, mummy/routers
proc requestHandler(request: Request) =
var headers: HttpHeaders
headers["Content-Type"] = "text/plain"
request.respond(200, headers, "Hello, World!")
proc wsHandler(websocket: WebSocket, event: WebSocketEvent, message: Message) =
case event:
of OpenEvent: echo "Client connected: ", $websocket
of MessageEvent: websocket.send("Echo: " & message.data)
of ErrorEvent: echo "Error on: ", $websocket
of CloseEvent: echo "Client disconnected: ", $websocket
let server = newServer(
requestHandler,
websocketHandler = wsHandler,
workerThreads = 20, # default: max(countProcessors() * 10, 1)
maxHeadersLen = 8 * 1024, # default: 8 KB
maxBodyLen = 1024 * 1024, # default: 1 MB
maxMessageLen = 64 * 1024,# default: 64 KB
tcpNoDelay = true
)
echo "Serving on http://localhost:8080"
server.serve(Port(8080)) # blocks until server.close() is called
```
--------------------------------
### Define HTTP Routes with Mummy Router
Source: https://context7.com/guzba/mummy/llms.txt
Sets up a router to handle GET, POST requests with path parameters and query parameters. Imports `mummy` and `mummy/routers`. The router implicitly converts to a `RequestHandler`.
```nim
import mummy, mummy/routers
proc listItems(request: Request) =
request.respond(200, @[("Content-Type", "text/plain")], "List of items")
proc getItem(request: Request) =
let id = request.pathParams["id"]
request.respond(200, @[("Content-Type", "text/plain")], "Item: " & id)
proc createItem(request: Request) =
request.respond(201, @[("Content-Type", "text/plain")], "Created: " & request.body)
proc searchItems(request: Request) =
let q = request.queryParams["q"]
request.respond(200, @[("Content-Type", "text/plain")], "Search: " & q)
var router: Router
# Convenience methods for common HTTP verbs
router.get("/items", listItems)
router.get("/items/@id", getItem)
router.post("/items", createItem)
router.get("/search", searchItems)
# Also available: router.put, router.delete, router.patch, router.head, router.options
# Generic: router.addRoute("CUSTOM_METHOD", "/path", handler)
let server = newServer(router)
server.serve(Port(8080))
```
--------------------------------
### newServer
Source: https://context7.com/guzba/mummy/llms.txt
Creates and initializes a new Mummy server. It takes a request handler and an optional WebSocket handler, along with configuration options for worker threads and message size limits. The server is not started by this call; `serve()` must be called separately.
```APIDOC
## `newServer` — Create an HTTP/WebSocket server
Creates and initializes a new Mummy server. The `handler` parameter is required and will be called from worker threads for every incoming HTTP request. The optional `websocketHandler` handles WebSocket lifecycle events. Server configuration limits (headers, body, message sizes) and the worker thread count can be tuned here. This call does not start listening; use `server.serve()` to begin accepting connections.
```nim
import mummy, mummy/routers
proc requestHandler(request: Request) =
var headers: HttpHeaders
headers["Content-Type"] = "text/plain"
request.respond(200, headers, "Hello, World!")
proc wsHandler(websocket: WebSocket, event: WebSocketEvent, message: Message) =
case event:
of OpenEvent: echo "Client connected: ", $websocket
of MessageEvent: websocket.send("Echo: " & message.data)
of ErrorEvent: echo "Error on: ", $websocket
of CloseEvent: echo "Client disconnected: ", $websocket
let server = newServer(
requestHandler,
websocketHandler = wsHandler,
workerThreads = 20, # default: max(countProcessors() * 10, 1)
maxHeadersLen = 8 * 1024, # default: 8 KB
maxBodyLen = 1024 * 1024, # default: 1 MB
maxMessageLen = 64 * 1024,# default: 64 KB
tcpNoDelay = true
)
echo "Serving on http://localhost:8080"
server.serve(Port(8080)) # blocks until server.close() is called
```
```
--------------------------------
### Respond to HTTP requests with different methods and bodies
Source: https://context7.com/guzba/mummy/llms.txt
Handles GET and POST requests, responding with JSON or plain text based on the method and body content. Returns a 400 for empty POST bodies and 405 for unsupported methods. Mummy automatically handles Content-Length and compression.
```nim
import mummy, mummy/routers
proc handler(request: Request) =
case request.httpMethod:
of "GET":
var headers: HttpHeaders
headers["Content-Type"] = "application/json"
request.respond(200, headers, """{"status": "ok", "message": "Hello"}""")
of "POST":
if request.body.len == 0:
request.respond(400, @[("Content-Type", "text/plain")], "Empty body")
else:
request.respond(201, @[("Content-Type", "text/plain")], "Created")
else:
request.respond(405) # Method Not Allowed, no body needed
var router: Router
router.get("/api/status", handler)
router.post("/api/items", handler)
let server = newServer(router)
server.serve(Port(8080))
# curl http://localhost:8080/api/status
# {"status": "ok", "message": "Hello"}
```
--------------------------------
### Upgrade HTTP to WebSocket with `request.upgradeToWebSocket`
Source: https://context7.com/guzba/mummy/llms.txt
Upgrades an HTTP GET request to a WebSocket connection. Subsequent WebSocket events are handled by the provided `websocketHandler`. Raises `MummyError` for invalid upgrade headers.
```nim
import mummy, mummy/routers
proc upgradeHandler(request: Request) =
let ws = request.upgradeToWebSocket()
# Can send immediately after upgrading
ws.send("Welcome! You are connected.")
proc wsHandler(websocket: WebSocket, event: WebSocketEvent, message: Message) =
case event:
of OpenEvent:
echo "New WebSocket connection"
of MessageEvent:
echo "Received (", message.kind, "): ", message.data
websocket.send("Echo: " & message.data)
of ErrorEvent:
echo "WebSocket error"
of CloseEvent:
echo "WebSocket closed"
var router: Router
router.get("/ws", upgradeHandler)
let server = newServer(router, wsHandler)
server.serve(Port(8080))
# JavaScript client: const ws = new WebSocket("ws://localhost:8080/ws");
```
--------------------------------
### Wait for server readiness with `server.waitUntilReady`
Source: https://context7.com/guzba/mummy/llms.txt
Blocks the calling thread until the server is ready to accept connections or a timeout occurs. Useful in tests for synchronizing with a server started on a background thread. Returns immediately if the server is already serving.
```nim
import mummy, std/httpclient
proc handler(request: Request) =
request.respond(200, @[("Content-Type", "text/plain")], "Ready!")
let server = newServer(handler)
var serverThread: Thread[Server]
proc runServer(s: Server) = s.serve(Port(9090))
createThread(serverThread, runServer, server)
server.waitUntilReady(timeout = 5.0) # Raises MummyError if timeout exceeded
let client = newHttpClient()
echo client.getContent("http://localhost:9090/")
# Ready!
server.close()
```
--------------------------------
### server.waitUntilReady
Source: https://context7.com/guzba/mummy/llms.txt
Blocks the calling thread until the server is ready to accept connections, or until the timeout (in seconds) elapses. Useful in tests when the server is started on a background thread and you need to wait before sending requests. Returns immediately if the server is already serving.
```APIDOC
## `server.waitUntilReady` — Wait for server readiness
Blocks the calling thread until the server is ready to accept connections, or until the timeout (in seconds) elapses. Useful in tests when the server is started on a background thread and you need to wait before sending requests. Returns immediately if the server is already serving.
```nim
import mummy, std/httpclient
proc handler(request: Request) =
request.respond(200, @[("Content-Type", "text/plain")], "Ready!")
let server = newServer(handler)
var serverThread: Thread[Server]
proc runServer(s: Server) = s.serve(Port(9090))
createThread(serverThread, runServer, server)
server.waitUntilReady(timeout = 5.0) # Raises MummyError if timeout exceeded
let client = newHttpClient()
echo client.getContent("http://localhost:9090/")
# Ready!
server.close()
```
```
--------------------------------
### request.upgradeToWebSocket
Source: https://context7.com/guzba/mummy/llms.txt
Upgrades an HTTP GET request to a WebSocket connection. Returns a WebSocket handle that can be used immediately to call send(). After the upgrade, all subsequent WebSocket lifecycle events (Open, Message, Error, Close) for this connection are dispatched to the websocketHandler provided to newServer. Raises MummyError if the request does not have valid WebSocket upgrade headers.
```APIDOC
## `request.upgradeToWebSocket` — Upgrade HTTP to WebSocket
Upgrades an HTTP GET request to a WebSocket connection. Returns a `WebSocket` handle that can be used immediately to call `send()`. After the upgrade, all subsequent WebSocket lifecycle events (Open, Message, Error, Close) for this connection are dispatched to the `websocketHandler` provided to `newServer`. Raises `MummyError` if the request does not have valid WebSocket upgrade headers.
```nim
import mummy, mummy/routers
proc upgradeHandler(request: Request) =
let ws = request.upgradeToWebSocket()
# Can send immediately after upgrading
ws.send("Welcome! You are connected.")
proc wsHandler(websocket: WebSocket, event: WebSocketEvent, message: Message) =
case event:
of OpenEvent:
echo "New WebSocket connection"
of MessageEvent:
echo "Received (", message.kind, "): ", message.data
websocket.send("Echo: " & message.data)
of ErrorEvent:
echo "WebSocket error"
of CloseEvent:
echo "WebSocket closed"
var router: Router
router.get("/ws", upgradeHandler)
let server = newServer(router, wsHandler)
server.serve(Port(8080))
# JavaScript client: const ws = new WebSocket("ws://localhost:8080/ws");
```
```
--------------------------------
### Benchmark AsyncHttpServer
Source: https://github.com/guzba/mummy/blob/master/README.md
Command to compile and run the benchmark test for the AsyncHttpServer.
```bash
nim c --mm:orc --threads:off -d:release -r tests/wrk_asynchttpserver.nim
```
--------------------------------
### Benchmark Go Server
Source: https://github.com/guzba/mummy/blob/master/README.md
Command to run the benchmark test for a Go server.
```bash
go run tests/wrk_go.go
```
--------------------------------
### Basic HTTP Server with Router in Nim
Source: https://github.com/guzba/mummy/blob/master/README.md
Sets up a simple HTTP server that responds with 'Hello, World!' to requests on the root path. Requires the `mummy` and `mummy/routers` modules. The server is configured to listen on port 8080.
```nim
import mummy, mummy/routers
proc indexHandler(request: Request) =
var headers: HttpHeaders
headers["Content-Type"] = "text/plain"
request.respond(200, headers, "Hello, World!")
var router: Router
router.get("/", indexHandler)
let server = newServer(router)
echo "Serving on http://localhost:8080"
server.serve(Port(8080))
```
--------------------------------
### Benchmark Prologue
Source: https://github.com/guzba/mummy/blob/master/README.md
Command to compile and run the benchmark test for Prologue.
```bash
nim c --mm:orc --threads:off -d:release -r tests/wrk_prologue.nim
```
--------------------------------
### Benchmark HttpBeast
Source: https://github.com/guzba/mummy/blob/master/README.md
Command to compile and run the benchmark test for HttpBeast.
```bash
nim c --mm:orc --threads:on -d:release -r tests/wrk_httpbeast.nim
```
--------------------------------
### Build a WebSocket Chat Server with Broadcast Pattern
Source: https://context7.com/guzba/mummy/llms.txt
This code sets up a WebSocket chat server. It uses a global lock and a set to manage connected clients for broadcasting messages. Compile with `nim c --threads:on --mm:orc -r yourserver.nim`.
```nim
import mummy, mummy/routers, std/locks, std/sets
var
lock: Lock
clients: HashSet[WebSocket]
initLock(lock)
proc indexHandler(request: Request) =
var headers: HttpHeaders
headers["Content-Type"] = "text/html"
request.respond(200, headers, """
""")
proc upgradeHandler(request: Request) =
let ws = request.upgradeToWebSocket()
{.gcsafe.}:
withLock lock:
clients.incl(ws)
proc websocketHandler(ws: WebSocket, event: WebSocketEvent, msg: Message) =
case event:
of OpenEvent:
ws.send("Welcome to the chat!")
of MessageEvent:
{.gcsafe.}:
withLock lock:
for client in clients:
client.send(msg.data)
of ErrorEvent: discard
of CloseEvent:
{.gcsafe.}:
withLock lock:
clients.excl(ws)
var router: Router
router.get("/", indexHandler)
router.get("/chat", upgradeHandler)
let server = newServer(router, websocketHandler)
echo "Chat server on http://localhost:8080"
server.serve(Port(8080))
# Open multiple browser tabs to http://localhost:8080 and chat between them
```
--------------------------------
### Benchmark NodeJS Server
Source: https://github.com/guzba/mummy/blob/master/README.md
Command to run the benchmark test for a NodeJS server.
```bash
node tests/wrk_node.js
```
--------------------------------
### server.serve
Source: https://context7.com/guzba/mummy/llms.txt
Binds the server to a specified port and address, then enters the main I/O select loop. This method blocks the calling thread until `server.close()` is invoked. It can listen on all interfaces by using `"0.0.0.0"`.
```APIDOC
## `server.serve` — Bind and start serving
Binds the server to a port and address and enters the main I/O select loop. This call blocks the calling thread until `server.close()` is called from another thread. Use `"0.0.0.0"` to listen on all interfaces; the default address is `"localhost"`.
```nim
import mummy
proc handler(request: Request) =
request.respond(200, @[("Content-Type", "text/plain")], "OK")
let server = newServer(handler)
# Listen on all interfaces for external access
server.serve(Port(8080), address = "0.0.0.0")
```
```
--------------------------------
### Benchmark Mummy Server
Source: https://github.com/guzba/mummy/blob/master/README.md
Command to compile and run the benchmark test for the Mummy server.
```bash
nim c --mm:orc --threads:on -d:release -r tests/wrk_mummy.nim
```
--------------------------------
### Implement HTTP Redirects with `request.respond`
Source: https://context7.com/guzba/mummy/llms.txt
Use `request.respond` with 3xx status codes and a `Location` header to issue HTTP redirects. Temporary redirects use 302, and permanent redirects use 301.
```nim
import mummy, mummy/routers
proc oldEndpoint(request: Request) =
# 302 Temporary Redirect
request.respond(302, @[("Location", "/new-endpoint")])
proc permanentRedirect(request: Request) =
# 301 Permanent Redirect
request.respond(301, @[("Location", "https://example.com/new")])
proc newEndpoint(request: Request) =
request.respond(200, @[("Content-Type", "text/plain")], "You reached the new endpoint!")
var router: Router
router.get("/old", oldEndpoint)
router.get("/gone", permanentRedirect)
router.get("/new-endpoint", newEndpoint)
let server = newServer(router)
server.serve(Port(8080))
# curl -L http://localhost:8080/old -> "You reached the new endpoint!"
```
--------------------------------
### Benchmark Jester
Source: https://github.com/guzba/mummy/blob/master/README.md
Command to compile and run the benchmark test for Jester.
```bash
nim c --mm:orc --threads:off -d:release -r tests/wrk_jester.nim
```
--------------------------------
### Stop the server cleanly with `server.close`
Source: https://context7.com/guzba/mummy/llms.txt
Initiates a clean server shutdown, allowing in-flight requests to finish but dispatching no new handlers. This can be called from any thread, and the `serve()` call will return after shutdown is complete.
```nim
import mummy, std/os
proc handler(request: Request) =
request.respond(200, @[("Content-Type", "text/plain")], "Running")
let server = newServer(handler)
# Start a background thread to shut the server down after 30 seconds
var t: Thread[Server]
proc shutdownAfterDelay(s: Server) =
sleep(30_000)
echo "Shutting down..."
s.close()
createThread(t, shutdownAfterDelay, server)
echo "Serving on http://localhost:8080"
server.serve(Port(8080)) # Returns after server.close() is called
echo "Server stopped."
```
--------------------------------
### Configure Router Wildcard Routes
Source: https://context7.com/guzba/mummy/llms.txt
Utilizes single-segment (`*`) and multi-segment (`**`) wildcards for flexible URL matching. Supports partial wildcards like `*.json`. Imports `mummy` and `mummy/routers`.
```nim
import mummy, mummy/routers
proc staticHandler(request: Request) =
request.respond(200, @[("Content-Type", "text/plain")], "Static file: " & request.path)
proc apiCatchAll(request: Request) =
request.respond(404, @[("Content-Type", "text/plain")], "API endpoint not found: " & request.path)
proc jsonHandler(request: Request) =
request.respond(200, @[("Content-Type", "application/json")], "{}")
var router: Router
router.get("/static/**", staticHandler) # matches /static/a/b/c/file.js
router.get("/api/**", apiCatchAll) # matches any /api/... path
router.get("/*.json", jsonHandler) # matches /data.json, /config.json, etc.
let server = newServer(router)
server.serve(Port(8080))
```
--------------------------------
### Handling Redirects with `request.respond`
Source: https://context7.com/guzba/mummy/llms.txt
Demonstrates how to issue HTTP redirects using 3xx status codes and the `Location` header. Supports both temporary (302) and permanent (301) redirects.
```APIDOC
## `request.respond` with redirects — 3xx responses
Issue HTTP redirects by responding with a 3xx status code and a `Location` header. Temporary redirects use `302`; permanent redirects use `301`.
### Example Usage:
```nim
import mummy, mummy/routers
proc oldEndpoint(request: Request) =
# 302 Temporary Redirect
request.respond(302, @[("Location", "/new-endpoint")])
proc permanentRedirect(request: Request) =
# 301 Permanent Redirect
request.respond(301, @[("Location", "https://example.com/new")])
proc newEndpoint(request: Request) =
request.respond(200, @[("Content-Type", "text/plain")], "You reached the new endpoint!")
var router: Router
router.get("/old", oldEndpoint)
router.get("/gone", permanentRedirect)
router.get("/new-endpoint", newEndpoint)
let server = newServer(router)
server.serve(Port(8080))
# curl -L http://localhost:8080/old -> "You reached the new endpoint!"
```
```
--------------------------------
### Implement Custom Router Error Handlers
Source: https://context7.com/guzba/mummy/llms.txt
Overrides default error responses for 404 (notFoundHandler), 405 (methodNotAllowedHandler), and exceptions (errorHandler). Imports `mummy` and `mummy/routers`. Error handlers should respond with appropriate status codes and content types.
```nim
import mummy, mummy/routers
proc custom404(request: Request) =
let body = "{"error": "not_found", "path": "$#"}" % request.path
var headers: HttpHeaders
headers["Content-Type"] = "application/json"
if request.httpMethod == "HEAD":
headers["Content-Length"] = $body.len
request.respond(404, headers)
else:
request.respond(404, headers, body)
proc custom405(request: Request) =
request.respond(405, @[("Content-Type", "application/json")],
"{"error": "method_not_allowed"}")
proc customError(request: Request, e: ref Exception) =
echo "Handler error: ", e.msg
request.respond(500, @[("Content-Type", "application/json")],
"{"error": "internal_server_error"}")
proc indexHandler(request: Request) =
request.respond(200, @[("Content-Type", "text/plain")], "OK")
var router: Router
router.notFoundHandler = custom404
router.methodNotAllowedHandler = custom405
router.errorHandler = customError
router.get("/", indexHandler)
let server = newServer(router)
server.serve(Port(8080))
```
--------------------------------
### Router Wildcard Routes
Source: https://context7.com/guzba/mummy/llms.txt
Explains how to use single-segment (`*`) and multi-segment (`**`) wildcards in routes for flexible URL matching without capturing values.
```APIDOC
## Router wildcard routes — `*` and `**` path patterns
Routes support single-segment (`*`) and multi-segment (`**`) wildcards as well as partial wildcards like `*.json` or `page_*`. Wildcards allow flexible URL matching without capturing values into `pathParams`.
```nim
import mummy, mummy/routers
proc staticHandler(request: Request) =
request.respond(200, @[("Content-Type", "text/plain")], "Static file: " & request.path)
proc apiCatchAll(request: Request) =
request.respond(404, @[("Content-Type", "text/plain")], "API endpoint not found: " & request.path)
proc jsonHandler(request: Request) =
request.respond(200, @[("Content-Type", "application/json")], "{}")
var router: Router
router.get("/static/**", staticHandler) # matches /static/a/b/c/file.js
router.get("/api/**", apiCatchAll) # matches any /api/... path
router.get("/*.json", jsonHandler) # matches /data.json, /config.json, etc.
let server = newServer(router)
server.serve(Port(8080))
# curl http://localhost:8080/static/css/style.css -> "Static file: /static/css/style.css"
# curl http://localhost:8080/data.json -> "{}"
```
```
--------------------------------
### Default Console Logging with `echoLogger`
Source: https://context7.com/guzba/mummy/llms.txt
Uses the default `echoLogger` from `mummy/common` to echo all log messages to stdout. This is the default `LogHandler` unless explicitly overridden.
```nim
import mummy, mummy/common
# echoLogger is the default; this is equivalent to not passing logHandler at all
let server = newServer(
proc(request: Request) = request.respond(200),
logHandler = echoLogger
)
server.serve(Port(8080))
```
--------------------------------
### Thread-Safe File Logging with `newFileLogger`
Source: https://context7.com/guzba/mummy/llms.txt
Implements thread-safe file logging using `FileLogger`. Logs are queued and written asynchronously, with error-level logs flushing synchronously. Requires a `logHandler` proc passed to `newServer`.
```nim
import mummy, mummy/fileloggers, std/os
let logger = newFileLogger("server.log") # Opens/creates server.log for appending
proc logHandler(level: LogLevel, args: varargs[string]) =
# Filter: only log Info and above to the file
if level >= InfoLevel:
logger.log(level, args)
# Always echo to stdout for debugging
if args.len == 1: echo args[0]
proc handler(request: Request) =
logger.debug("Request: ", request.httpMethod, " ", request.uri) # queued async write
if request.uri == "/error":
logger.error("Simulated error on /error") # synchronous flush
request.respond(500, @[("Content-Type", "text/plain")], "Error logged")
else:
logger.info("Handled: ", request.uri)
request.respond(200, @[("Content-Type", "text/plain")], "OK")
let server = newServer(handler, logHandler = logHandler)
echo "Logging to server.log"
server.serve(Port(8080))
# Clean shutdown — flushes all queued log lines before returning
logger.close()
# curl http://localhost:8080/ -> OK (logged as Info)
# curl http://localhost:8080/error -> Error logged (synchronous flush)
```
--------------------------------
### Thread-safe File Logging with `newFileLogger` / `FileLogger`
Source: https://context7.com/guzba/mummy/llms.txt
Introduces `FileLogger` for thread-safe, asynchronous file logging. Log lines are queued and written by a dedicated thread, with error-level logs flushing synchronously. A `logHandler` can be provided to `newServer` for filtering and dispatching logs.
```APIDOC
## `newFileLogger` / `FileLogger` — Thread-safe file logging
`FileLogger` from `mummy/fileloggers` is a thread-safe logger that queues log lines from any number of worker threads and writes them asynchronously to a file using a dedicated internal writer thread. Error-level logs bypass the queue and flush synchronously. Pass a `logHandler` proc to `newServer` to wire up filtering and dispatch to the logger.
### Example Usage:
```nim
import mummy, mummy/fileloggers, std/os
let logger = newFileLogger("server.log") # Opens/creates server.log for appending
proc logHandler(level: LogLevel, args: varargs[string]) =
# Filter: only log Info and above to the file
if level >= InfoLevel:
logger.log(level, args)
# Always echo to stdout for debugging
if args.len == 1: echo args[0]
proc handler(request: Request) =
logger.debug("Request: ", request.httpMethod, " ", request.uri) # queued async write
if request.uri == "/error":
logger.error("Simulated error on /error") # synchronous flush
request.respond(500, @[("Content-Type", "text/plain")], "Error logged")
else:
logger.info("Handled: ", request.uri)
request.respond(200, @[("Content-Type", "text/plain")], "OK")
let server = newServer(handler, logHandler = logHandler)
echo "Logging to server.log"
server.serve(Port(8080))
# Clean shutdown — flushes all queued log lines before returning
logger.close()
# curl http://localhost:8080/ -> OK (logged as Info)
# curl http://localhost:8080/error -> Error logged (synchronous flush)
```
```
--------------------------------
### Run Fuzzer for Socket Reading
Source: https://github.com/guzba/mummy/blob/master/README.md
Command to compile and run the fuzzer for Mummy's socket reading and parsing code.
```bash
nim c -r tests/fuzz_recv.nim
```
--------------------------------
### Custom Handler Middleware
Source: https://context7.com/guzba/mummy/llms.txt
Demonstrates how to create custom middleware for request handling by wrapping RequestHandler procedures. This pattern is useful for implementing cross-cutting concerns like authentication, rate limiting, or parameter injection.
```APIDOC
## Custom handler middleware — wrapping `RequestHandler`
Because `RequestHandler` is just `proc(request: Request) {.gcsafe.}`, middleware is implemented by defining a custom proc type and a `toHandler` wrapper. This pattern supports authentication, rate limiting, parameter injection, or any cross-cutting concern without framework magic.
```nim
import mummy, mummy/routers
type AuthHandler = proc(request: Request, userId: string) {.gcsafe.}
proc toHandler(wrapped: AuthHandler): RequestHandler =
return proc(request: Request) =
let token = request.headers["Authorization"]
if token == "Bearer secret-token":
wrapped(request, "user-42")
else:
request.respond(401, @[("Content-Type", "application/json")],
"""{"error": "unauthorized"}""")
proc profileHandler(request: Request, userId: string) =
request.respond(200, @[("Content-Type", "application/json")],
"""{"userId": "$#", "name": "Alice"}""" % userId)
proc settingsHandler(request: Request, userId: string) =
request.respond(200, @[("Content-Type", "application/json")],
"""{"userId": "$#", "theme": "dark"}""" % userId)
var router: Router
router.get("/profile", profileHandler.toHandler())
router.get("/settings", settingsHandler.toHandler())
let server = newServer(router)
server.serve(Port(8080))
# curl -H "Authorization: Bearer secret-token" http://localhost:8080/profile
# {"userId": "user-42", "name": "Alice"}
# curl http://localhost:8080/profile
# {"error": "unauthorized"}
```
```
--------------------------------
### Compile and Run Mummy Server in Nim
Source: https://github.com/guzba/mummy/blob/master/README.md
Command to compile and run a Mummy server application using Nim. It enables multi-threading and specifies the ORC memory manager. The `-r` flag indicates that the compiled executable should be run immediately after compilation.
```bash
nim c --threads:on --mm:orc -r examples/basic_router.nim
```
--------------------------------
### Custom Handler Middleware with `toHandler`
Source: https://context7.com/guzba/mummy/llms.txt
Implement custom middleware by defining a proc type and a `toHandler` wrapper. This pattern is useful for authentication, rate limiting, or parameter injection without framework magic.
```nim
import mummy, mummy/routers
type AuthHandler = proc(request: Request, userId: string) {.gcsafe.}
proc toHandler(wrapped: AuthHandler): RequestHandler =
return proc(request: Request) =
let token = request.headers["Authorization"]
if token == "Bearer secret-token":
wrapped(request, "user-42")
else:
request.respond(401, @[("Content-Type", "application/json")],
"{\"error\": \"unauthorized\"}")
proc profileHandler(request: Request, userId: string) =
request.respond(200, @[("Content-Type", "application/json")],
"{\"userId\": \"$#\", \"name\": \"Alice\"}" % userId)
proc settingsHandler(request: Request, userId: string) =
request.respond(200, @[("Content-Type", "application/json")],
"{\"userId\": \"$#\", \"theme\": \"dark\"}" % userId)
var router: Router
router.get("/profile", profileHandler.toHandler())
router.get("/settings", settingsHandler.toHandler())
let server = newServer(router)
server.serve(Port(8080))
```
--------------------------------
### server.close
Source: https://context7.com/guzba/mummy/llms.txt
Stops the server cleanly. In-flight request handler calls are allowed to finish. No additional handler calls are dispatched even if tasks are already queued. Can be called from any thread. The serve() call on the main thread returns after the shutdown completes.
```APIDOC
## `server.close` — Stop the server cleanly
Initiates a clean shutdown of the server. In-flight request handler calls are allowed to finish. No additional handler calls are dispatched even if tasks are already queued. Can be called from any thread. The `serve()` call on the main thread returns after the shutdown completes.
```nim
import mummy, std/os
proc handler(request: Request) =
request.respond(200, @[("Content-Type", "text/plain")], "Running")
let server = newServer(handler)
# Start a background thread to shut the server down after 30 seconds
var t: Thread[Server]
proc shutdownAfterDelay(s: Server) =
sleep(30_000)
echo "Shutting down..."
s.close()
createThread(t, shutdownAfterDelay, server)
echo "Serving on http://localhost:8080"
server.serve(Port(8080)) # Returns after server.close() is called
echo "Server stopped."
```
```
--------------------------------
### Close a WebSocket connection with `websocket.close`
Source: https://context7.com/guzba/mummy/llms.txt
Initiates the WebSocket closing handshake by enqueuing a close frame. Any pending messages are sent before the close frame. A `CloseEvent` is dispatched after the handshake completes.
```nim
import mummy, mummy/routers
proc upgradeHandler(request: Request) =
let ws = request.upgradeToWebSocket()
ws.send("Goodbye!")
ws.close() # Close frame is queued after the "Goodbye!" message
proc wsHandler(websocket: WebSocket, event: WebSocketEvent, message: Message) =
case event:
of MessageEvent:
if message.data == "bye":
websocket.send("Closing connection...")
websocket.close()
of CloseEvent:
echo "Connection closed gracefully"
else: discard
var router: Router
router.get("/ws", upgradeHandler)
let server = newServer(router, wsHandler)
server.serve(Port(8080))
```
--------------------------------
### Send WebSocket messages with `websocket.send`
Source: https://context7.com/guzba/mummy/llms.txt
Enqueues a message to be sent over a WebSocket connection. Supports TextMessage, BinaryMessage, Ping, and Pong frames. This procedure is non-blocking and thread-safe.
```nim
import mummy, mummy/routers, std/locks, std/sets
var
lock: Lock
clients: HashSet[WebSocket]
initLock(lock)
proc upgradeHandler(request: Request) =
let ws = request.upgradeToWebSocket()
{.gcsafe.}:
withLock lock:
clients.incl(ws)
proc wsHandler(websocket: WebSocket, event: WebSocketEvent, message: Message) =
case event:
of MessageEvent:
# Broadcast text to all connected clients
{.gcsafe.}:
withLock lock:
for client in clients:
client.send(message.data, TextMessage)
of CloseEvent:
{.gcsafe.}:
withLock lock:
clients.excl(websocket)
else: discard
var router: Router
router.get("/chat", upgradeHandler)
let server = newServer(router, wsHandler)
server.serve(Port(8080))
```
--------------------------------
### Custom Router Error Handlers
Source: https://context7.com/guzba/mummy/llms.txt
Details how to set custom handlers for 404 Not Found, 405 Method Not Allowed, and general error conditions to override default responses.
```APIDOC
## Custom router error handlers — `notFoundHandler`, `methodNotAllowedHandler`, `errorHandler`
The `Router` exposes three handler slots for custom error responses: `notFoundHandler` (404), `methodNotAllowedHandler` (405), and `errorHandler` (called when a route handler raises an exception). Assigning these overrides the default HTML responses.
```nim
import mummy, mummy/routers
proc custom404(request: Request) =
let body = "{"error": "not_found", "path": "$#"}" % request.path
var headers: HttpHeaders
headers["Content-Type"] = "application/json"
if request.httpMethod == "HEAD":
headers["Content-Length"] = $body.len
request.respond(404, headers)
else:
request.respond(404, headers, body)
proc custom405(request: Request) =
request.respond(405, @[("Content-Type", "application/json")],
"{"error": "method_not_allowed"}")
proc customError(request: Request, e: ref Exception) =
echo "Handler error: ", e.msg
request.respond(500, @[("Content-Type", "application/json")],
"{"error": "internal_server_error"}")
proc indexHandler(request: Request) =
request.respond(200, @[("Content-Type", "text/plain")], "OK")
var router: Router
router.notFoundHandler = custom404
router.methodNotAllowedHandler = custom405
router.errorHandler = customError
router.get("/", indexHandler)
let server = newServer(router)
server.serve(Port(8080))
# curl http://localhost:8080/missing -> {"error": "not_found", "path": "/missing"}
```
```
--------------------------------
### Accessing Request Data
Source: https://context7.com/guzba/mummy/llms.txt
Explains how to access various fields of the `Request` object, including HTTP metadata, headers, query parameters, path parameters, and the request body.
```APIDOC
## `request` fields — Accessing request data
The `Request` object (a `ptr RequestObj`) exposes all parsed request data as public fields, all populated before the handler is called.
```nim
import mummy, mummy/routers
proc inspectHandler(request: Request) =
# HTTP metadata
echo "Method: ", request.httpMethod # "GET", "POST", etc.
echo "URI: ", request.uri # Raw URI string, e.g. "/search?q=nim"
echo "Path: ", request.path # Decoded path, e.g. "/search"
echo "Version: ", request.httpVersion # Http10 or Http11
echo "Remote: ", request.remoteAddress
# Headers (case-insensitive key lookup)
echo "Host: ", request.headers["Host"]
echo "Accept: ", request.headers["Accept"]
for (key, value) in request.headers:
echo key, ": ", value
# Query parameters e.g. /search?q=nim&page=2
echo "q=", request.queryParams["q"]
echo "page=", request.queryParams["page"]
# Named path params (set by router for routes like /items/@id)
echo "id=", request.pathParams["id"]
# Request body
echo "Body length: ", request.body.len
echo "Body: ", request.body
request.respond(200)
var router: Router
router.get("/items/@id", inspectHandler)
let server = newServer(router)
server.serve(Port(8080))
# curl "http://localhost:8080/items/99?q=nim&page=2"
```
```
--------------------------------
### request.respond
Source: https://context7.com/guzba/mummy/llms.txt
Sends an HTTP response for a given request. Supports custom status codes, headers, and an optional body. Mummy automatically handles `Content-Length` and gzip/deflate compression for clients that support it.
```APIDOC
## `request.respond` — Send an HTTP response
Sends an HTTP response for the given request. `statusCode` is any valid HTTP status integer. `headers` can be an `HttpHeaders` object or a seq of `(string, string)` tuples. `body` is an optional string. Mummy automatically adds `Content-Length` and applies gzip/deflate compression when the client supports it and the body exceeds 860 bytes.
```nim
import mummy, mummy/routers
proc handler(request: Request) =
case request.httpMethod:
of "GET":
var headers: HttpHeaders
headers["Content-Type"] = "application/json"
request.respond(200, headers, """{"status": "ok", "message": "Hello"}"""
of "POST":
if request.body.len == 0:
request.respond(400, @[("Content-Type", "text/plain")], "Empty body")
else:
request.respond(201, @[("Content-Type", "text/plain")], "Created")
else:
request.respond(405) # Method Not Allowed, no body needed
var router: Router
router.get("/api/status", handler)
router.post("/api/items", handler)
let server = newServer(router)
server.serve(Port(8080))
# curl http://localhost:8080/api/status
# {"status": "ok", "message": "Hello"}
```
```
--------------------------------
### Parse `multipart/form-data` Uploads with `decodeMultipart`
Source: https://context7.com/guzba/mummy/llms.txt
Parses a `multipart/form-data` request body into `MultipartEntry` values. Each entry contains name, optional filename, headers, and data as index pairs into `request.body`. Raises `MummyError` on invalid input.
```nim
import mummy, mummy/routers, mummy/multipart
proc uploadHandler(request: Request) =
try:
let entries = request.decodeMultipart()
for entry in entries:
if entry.filename.isSome:
# File upload
let fname = entry.filename.get
if entry.data.isSome:
let (start, last) = entry.data.get
let fileData = request.body[start .. last]
echo "File upload: ", fname, " (", fileData.len, " bytes)"
# Save: writeFile("/uploads/" & fname, fileData)
else:
echo "Empty file upload: ", fname
else:
# Form field
if entry.data.isSome:
let (start, last) = entry.data.get
let value = request.body[start .. last]
echo "Field ", entry.name, " = ", value
else:
echo "Field ", entry.name, " is empty"
request.respond(200, @[("Content-Type", "text/plain")], "Upload received")
except MummyError as e:
request.respond(400, @[("Content-Type", "text/plain")], "Bad request: " & e.msg)
var router: Router
router.post("/upload", uploadHandler)
let server = newServer(router)
server.serve(Port(8080))
# curl -F name="Alice" -F file=@/tmp/photo.jpg http://localhost:8080/upload
```