### 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 ```