{"id":13594855,"url":"https://github.com/pkulchenko/fullmoon","last_synced_at":"2025-04-12T21:28:29.025Z","repository":{"id":37429111,"uuid":"434136215","full_name":"pkulchenko/fullmoon","owner":"pkulchenko","description":"Fast and minimalistic Redbean-based Lua web framework in one file.","archived":false,"fork":false,"pushed_at":"2024-08-05T06:53:30.000Z","size":356,"stargazers_count":718,"open_issues_count":8,"forks_count":32,"subscribers_count":17,"default_branch":"master","last_synced_at":"2025-04-04T01:07:38.734Z","etag":null,"topics":["cosmopolitan","lua","webframework","webserver"],"latest_commit_sha":null,"homepage":"","language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pkulchenko.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-12-02T08:15:51.000Z","updated_at":"2025-03-27T21:34:08.000Z","dependencies_parsed_at":"2024-04-20T23:39:49.704Z","dependency_job_id":"1089a678-193f-48d6-bfdd-986640316084","html_url":"https://github.com/pkulchenko/fullmoon","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pkulchenko%2Ffullmoon","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pkulchenko%2Ffullmoon/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pkulchenko%2Ffullmoon/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pkulchenko%2Ffullmoon/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pkulchenko","download_url":"https://codeload.github.com/pkulchenko/fullmoon/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248633966,"owners_count":21136955,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cosmopolitan","lua","webframework","webserver"],"created_at":"2024-08-01T16:01:40.000Z","updated_at":"2025-04-12T21:28:28.992Z","avatar_url":"https://github.com/pkulchenko.png","language":"Lua","readme":"# Fullmoon\n\nFullmoon is a [fast](#benchmark) and minimalistic web framework\nbased on [Redbean](https://redbean.dev/)\n-- a portable, single-file distributable web server.\n\nEverything needed for development and distribution comes in a single file with\nno external dependencies and after packaging with Redbean runs on Windows,\nLinux, or macOS. The following is a complete example of a Fullmoon application:\n\n```lua\nlocal fm = require \"fullmoon\"\nfm.setTemplate(\"hello\", \"Hello, {%\u0026 name %}\")\nfm.setRoute(\"/hello/:name\", function(r)\n    return fm.serveContent(\"hello\", {name = r.params.name})\n  end)\nfm.run()\n```\n\nAfter it is [packaged with Redbean](#installation), it can be launched\nusing `./redbean.com`, which starts a server that returns \"Hello, world\"\nto an HTTP(S) request sent to http://localhost:8080/hello/world.\n\n## Contents\n\n- [Why Fullmoon](#why-fullmoon)\n  - [What Redbean provides](#what-redbean-provides)\n  - [What Fullmoon adds](#what-fullmoon-adds)\n- [Installation](#installation)\n- [Usage](#usage)\n- [Quick reference](#quick-reference)\n- [Examples](#examples)\n  - [Showcase example](#showcase-example)\n  - [TechEmpower benchmark example](#techempower-benchmark-example)\n  - [htmx board example](#htmx-board-example)\n  - [htmx SSE example](#htmx-sse-example)\n- [Documentation](#documentation)\n  - [Routes](#routes)\n    - [Basic routes](#basic-routes)\n    - [Routes with parameters](#routes-with-parameters)\n    - [Optional parameters](#optional-parameters)\n    - [Splat parameters](#splat-parameters)\n    - [Custom parameters](#custom-parameters)\n    - [Query and Form parameters](#query-and-form-parameters)\n    - [Multipart parameters](#multipart-parameters)\n    - [Multiple routes](#multiple-routes)\n    - [Named routes](#named-routes)\n    - [External routes](#external-routes)\n    - [Internal routes](#internal-routes)\n  - [Conditions](#conditions)\n    - [Handling of HTTP methods](#handling-of-http-methods)\n    - [Conditional routes](#conditional-routes)\n    - [Custom validators](#custom-validators)\n    - [Responding on failed conditions](#responding-on-failed-conditions)\n    - [Form validation](#form-validation)\n  - [Actions](#actions)\n    - [Throwing errors](#throwing-errors)\n  - [Requests](#requests)\n    - [Headers](#headers)\n    - [Cookies](#cookies)\n    - [Session](#session)\n    - [Utility functions](#utility-functions)\n  - [Templates](#templates)\n    - [Passing parameters to templates](#passing-parameters-to-templates)\n    - [Handling undefined values in templates](#handling-undefined-values-in-templates)\n    - [Including templates in other templates](#including-templates-in-other-templates)\n    - [Using layouts and blocks](#using-layouts-and-blocks)\n    - [Loading templates](#loading-templates)\n    - [Serving template output](#serving-template-output)\n    - [Special templates](#special-templates)\n  - [Schedules](#schedules)\n  - [Responses](#responses)\n    - [Serving response](#serving-response)\n    - [Serving redirect](#serving-redirect)\n    - [Serving static asset](#serving-static-asset)\n    - [Serving error](#serving-error)\n    - [Serving directory index](#serving-directory-index)\n    - [Serving path (internal redirect)](#serving-path-(internal-redirect))\n  - [Database management](#database-management)\n  - [Running application](#running-application)\n    - [Cookie options](#cookie-options)\n    - [Session options](#session-options)\n  - [Logging](#logging)\n- [Benchmark](#benchmark)\n  - [3-rd party benchmarks](#3-rd-party-benchmarks)\n- [Status](#status)\n- [Author](#author)\n- [License](#license)\n\n## Why Fullmoon\n\nRedbean is a single-file distributable cross-platform web server with\nunique and powerful qualities. While there are several Lua-based\nweb frameworks ([Lapis](https://leafo.net/lapis/),\n[Lor](https://github.com/sumory/lor),\n[Sailor](https://github.com/sailorproject/sailor),\n[Pegasus](https://github.com/EvandroLG/pegasus.lua), and others),\nnone of them integrates with Redbean (although there is an experimental\nframework [anpan](https://git.sr.ht/~shakna/anpan)).\n\nFullmoon is a lightweight and minimalistic web framework that is\nwritten from the perspective of showcasing all the capabilities that\nRedbean provides by extending and augmenting them in the simplest and\nthe most efficient way. It runs fast and comes with batteries included\n(routes, templates, JSON generation and more).\n\nFullmoon follows the Lua philosophy and provides a minimal set of tools\nto combine as needed and use as the basis to build upon.\n\n### What Redbean provides\n\n- Single file deployment and distribution (Linux, Windows, and macOS)\n- Integrated SSL support (using MbedTLS) with SSL virtual hosting\n- Integrated crypto hashing (SHA1/SHA224/256/384/512/BLAKE2B256)\n- Cross-platform `fork`, `socket`, shared memory, and more\n- Efficient serving of static and gzip encoded assets\n- Integrated password-hashing (using Argon2)\n- pledge/unveil sandboxing (where supported)\n- unix.* module for Unix system interfaces\n- HTTP/HTTPS client for external requests\n- JSON and Lua serialization and parsing\n- Ships with Lua 5.4 and SQLite 3.40\n\n### What Fullmoon adds\n\n- Small package (~1700 LOC) with no external dependencies\n- Simple and flexible routing with [parameters](#routes-with-parameters)\n  and [custom filters](#conditions)\n- [Template engine](#templates) with JSON support and efficient memory utilization\n- Optimized execution with pre-compiled routes and lazy loaded methods\n- Response streaming and Server-Sent Events support\n- [Cookie](#cookies)/[header](#headers)/[session](#session) generation and processing\n- [Multipart](#multipart-parameters) message processing for file uploads\n- Parametrized URL [rewrites](#internal-routes) and re-routing\n- [Form validation](#form-validation) with a variety of checks\n- [Cron syntax](#schedules) for scheduling Lua functions\n- DB management with schema migrations\n- Custom 404 and other status pages\n- Basic support to run CGI scripts\n- Access to all Redbean features\n\n## Installation\n\n### Step 1: Get the latest Redbean (v2.0+)\n\nDownload a copy of Redbean by running the following commands (skip the second\none if running these commands on Windows):\n\n```sh\ncurl -o redbean.com https://redbean.dev/redbean-2.2.com\nchmod +x redbean.com\n```\n\nThe latest version number can be retrieved with the following request:\n\n```sh\ncurl https://redbean.dev/latest.txt\n```\n\nAnother option is to build Redbean from source by following instructions for\nthe [source build](https://redbean.dev/#source).\n\n### Step 2: Prepare Fullmoon code\n\n- Copy `fullmoon.lua` to `.lua/` directory\n- Save the application code to a file named `.init.lua` (for example, the Lua\n  code shown in the [description](#fullmoon)).\n\nAnother option is to place the application code into a separate file\n(for example, `.lua/myapp.lua`) and add `require \"myapp\"` to `.init.lua`.\nThis is how [all included examples](#examples) are presented.\n\n### Step 3: Package Fullmoon code with Redbean\n\n```sh\nzip redbean.com .init.lua .lua/fullmoon.lua\n```\n\nIf the application code is stored in a separate Lua file, as described above,\nmake sure to place it inside the `.lua/` directory and zip that file as well.\n\n### Step 4: Run the server\n\n```sh\n./redbean.com\n```\n\nIf this command is executed on Linux and throws an error about not finding\ninterpreter, it should be fixed by running the following command (although note\nthat it may not survive a system restart):\n\n```sh\nsudo sh -c \"echo ':APE:M::MZqFpD::/bin/sh:' \u003e/proc/sys/fs/binfmt_misc/register\"\n```\n\nIf this command produces puzzling errors on WSL or WINE when using Redbean 2.x,\nthey may be fixed by disabling binfmt_misc:\n\n```sh\nsudo sh -c 'echo -1 \u003e/proc/sys/fs/binfmt_misc/status'\n```\n\n### Step 5: Check the result\n\nLaunch a browser pointing at http://localhost:8080/hello/world and it should\nreturn \"Hello, world\" (assuming the application is using the code shown in the\n[introduction](#fullmoon) or the one in the [usage](#usage) section).\n\n## Usage\n\nThe simplest example needs to (1) load the module, (2) configure one route,\nand (3) run the application:\n\n```lua\nlocal fm = require \"fullmoon\" -- (1)\nfm.setRoute(\"/hello\", function(r) return \"Hello, world\" end) -- (2)\nfm.run() -- (3)\n```\n\nThis application responds to any request for `/hello` URL with returning\n\"Hello, world\" content (and the 200 status code) and responds with the\n404 status code for all other requests.\n\n## Quick reference\n\n- `setRoute(route[, action])`: registers a route.\n  If `route` is a string, then it is used as a route [expression](#basic-routes)\n  to compare the request path against. If it is a table, then its\n  elements are strings that are used as [routes](#multiple-routes) and\n  its hash values are [conditions](#conditional-routes) that the routes\n  are checked against.\n  If the second parameter is a [function](#actions), then it is executed\n  if all conditions are satisfied. If it is a string, then it is used as\n  a route expression and the request is processed as if it is sent at\n  the specified route (acts as an [internal redirect](#internal-routes)).\n  If any condition is not satisfied, then the next route is checked. The\n  route expression can have multiple [parameters](#routes-with-parameters)\n  and [optional parts](#optional-parameters). The action handler accepts\n  a [request table](#requests) that provides access to request and route\n  parameters, as well as [headers](#headers), [cookies](#cookies), and\n  [session](#session).\n\n- `setTemplate(name, template[, parameters])`: registers a template\n  with the specified name or a [set of templates](#loading-templates)\n  from a directory.\n  If `template` is a string, then it's compiled into a template handler.\n  If it is a function, it is stored and called when rendering of the\n  template is requested. If it's a table, then its first element is a\n  template or a function and the rest are used as options. For example,\n  specifying `ContentType` as one of the options sets the `Content-Type`\n  header for the generated content. Several templates (`500`, `json`,\n  and others) are [provided by default](#special-templates) and can be\n  overwritten. `parameters` is a table with template parameters stored as\n  name/value pairs (referenced as variables in the template).\n\n- `serveResponse(status[, headers][, body])`: sends an HTTP response\n  using provided `status`, `headers`, and `body` values.\n  `headers` is an optional table populated with HTTP header name/value\n  pairs. If provided, this set of headers *removes all other headers*\n  set earlier during handling of the same request. Header names are\n  *case-insensitive*, but provided aliases for header names with dashes\n  are *case-sensitive*: `{ContentType = \"foo\"}` is an alternative form\n  for `{[\"Content-Type\"] = \"foo\"}`. `body` is an optional string.\n\n- `serveContent(name, parameters)`: renders a template using provided\n  parameters.\n  `name` is a string that names the template (as set by a `setTemplate`\n  call) and `parameters` is a table with template parameters stored as\n  name/value pairs (referenced as variables in the template).\n\n- `run([options])`: runs the server using configured routes.\n  By default the server listens on localhost and port 8080. These values\n  can be changed by setting `addr` and `port` values in the\n  [`options` table](#running-application).\n\n## Examples\n\nRunning examples requires including a `require` statement in the `.init.lua`\nfile, which loads the module with each example code, so for the showcase\nexample implemented in `showcase.lua`, `.init.lua` includes the following:\n\n```lua\n-- this is the content of .init.lua\nrequire \"showcase\"\n-- this loads `showcase` module from `.lua/showcase.lua` file,\n-- which also loads its `fullmoon` dependency from `.lua/fullmoon.lua`\n```\n\n### Showcase example\n\nThe [showcase example](examples/showcase.lua) demonstrates several Fullmoon features:\n- serving static assets (using `serveAsset`)\n- setting http to https redirect\n- setting 404 template\n- configuring internal redirect\n- configuring external redirect (using `serveRedirect`)\n- filtering for loopback ip client addresses\n- filtering based on parameter values using regex\n- serving json\n\nThe following files need to be added to redbean executable/archive:\n\n\u003cpre\u003e\n.init.lua -- require \"showcase\"\n.lua/fullmoon.lua\n.lua/showcase.lua\n\u003c/pre\u003e\n\n### TechEmpower benchmark example\n\nThe [TechEmpower example](examples/techbench.lua) implements various test types\nfor the [web framework benchmarks](https://www.techempower.com/benchmarks/)\nusing Fullmoon and an in-memory sqlite database.\n\nThis example demonstrates several Fullmoon/redbean features:\n- routing for various endpoints\n- serving text and json content\n- filtering for specific HTTP methods\n- using templates with embedded Lua code\n- using select/insert statements with included SQLite engine\n- executing prepared SQL statements\n\nThe following files need to be added to redbean executable/archive:\n\n\u003cpre\u003e\n.init.lua -- require \"techbench\"\n.lua/fullmoon.lua\n.lua/techbench.lua\n\u003c/pre\u003e\n\n### htmx board example\n\nThe [htmx board example](examples/htmxboard/htmxboard.lua) demonstrates\na simple application that generates HTML fragments delivered to the client\nusing [htmx library](https://htmx.org/).\n\nThis example demonstrates several Fullmoon/redbean features:\n- handling of GET, POST, PUT, and DELETE HTTP methods\n- serving of dynamic HTML fragments and static assets\n- processing of required and optional parameters\n- loading of templates from a directory\n- using 10+ templates of two different types\n- including templates into other templates and passing parameters to templates\n- serving of internal state for debugging purposes as a local-only resource\n- using \"fallthrough\" routes to imitate \"before\" hook\n- using internal redirects\n\nThe following files need to be added to redbean executable/archive:\n\n\u003cpre\u003e\n.init.lua -- require \"htmxboard\"\n.lua/fullmoon.lua\n.lua/htmxboard.lua\nassets/styles.css\ntmpl/* -- all files from examples/htmxboard/tmpl directory\n\u003c/pre\u003e\n\nNote 1: since all the data is stored in memory, **this example is executed\nin the uniprocess mode.**\n\nNote 2: this examples retrieves htmx, hyperscript, and sortable libraries from\nexternal resources, but these libraries can be also stored as local assets,\nthus providing a completely self-sufficient portable distribution package.\n\n### htmx SSE example\n\nThe [htmx SSE example](examples/htmxsse.lua) demonstrates a way to generate\nserver-sent events (SSE) that can be streamed to a client (which shows\nresults using [htmx library](https://htmx.org/) and its SSE extension).\n\nThis example demonstrates several Fullmoon/redbean features:\n- usage of \"sse\" template to generate SSE content\n- streaming of responses (using `streamContent`)\n- logging of messages\n\nThe following files need to be added to redbean executable/archive:\n\n\u003cpre\u003e\n.init.lua -- require \"htmxsse\"\n.lua/fullmoon.lua\n.lua/htmxsse.lua\n\u003c/pre\u003e\n\n## Documentation\n\nEach Fullmoon application follows the same basic flow with five main\ncomponents:\n\n- [configures and runs](#running-application) a redbean server, which\n- filters each request based on specified [conditions](#conditions), and\n- [routes](#routes) it to an [action handler](#actions), that\n- generates content (using provided [template engine](#templates)), and\n- serves a [response](#responses).\n\nLet's look at each of the components starting from the request routing.\n\n### Routes\n\nFullmoon handles each HTTP request using the same process:\n\n- takes the path URL and matches it against each route URL in the order\n  in which routes are registered\n- verifies conditions for those routes that match\n- calls a specified action handler (passing a request table) for those\n  routes that satisfy all conditions\n- serves the response if anything other than `false` or `nil` returned\n  from the action handler (and continues the process otherwise)\n\nIn general, route definitions bind request URLs (and a set of conditions)\nto action handlers (which are regular Lua function). All conditions are\nchecked in a random order for each URL that matches the route definition.\nAs soon as any condition fails, the route processing is aborted and the\nnext route is checked *with one exception*: any condition can set the\n[`otherwise` value](#responding-on-failed-conditions), which triggers a\nresponse with the specified status code.\n\nIf no route matches the request, then the default 404 processing is\ntriggered, which can be customized by registering a custom 404 template\n(`fm.setTemplate(\"404\", \"My 404 page...\")`).\n\n#### Basic routes\n\nEach route takes a path that matches exactly, so the route `\"/hello\"`\nmatches requests for `/hello` and doesn't match `/hell`, `/hello-world`,\nor `/hello/world`. The route below responds with \"Hello, World!\" for all\nrequests directed at the `/hello` path and returns 404 for all other\nrequests.\n\n```lua\nfm.setRoute(\"/hello\", function(r) return \"Hello, World!\" end)\n```\n\nTo match a path where `/hello` is only a part of it,\n[optional parameters](#optional-parameters) and [splat](#splat-parameters)\ncan be used.\n\n#### Routes with parameters\n\nIn addition to fixed routes, any path may include placeholders for\nparameters, which are identified by a `:` followed immediately by\nthe parameter name:\n\n```lua\nfm.setRoute(\"/hello/:name\",\n  function(r) return \"Hello, \"..(r.params.name) end)\n```\n\nEach parameter matches one or more characters except `/`, so the route\n`\"/hello/:name\"` matches `/hello/alice`, `/hello/bob`, `/hello/123` and\ndoes not match `/hello/bob/and/alice` (because of the non-matched\nforward slashes) or `/hello/` (because the length of the to-be-matched\nfragment is zero).\n\nParameter names can only include alphanumeric characters and `_`.\n\nParameters can be accessed using the request table and its `params`\ntable, such that `r.params.name` can be used to get the value of the\n`name` parameter from the earlier example.\n\n#### Optional parameters\n\nAny specified route fragment or parameter can be declared as optional by\nwrapping it into parentheses:\n\n```lua\nfm.setRoute(\"/hello(/:name)\",\n  function(r) return \"Hello, \"..(r.params.name or \"World!\") end)\n```\n\nIn the example above, both `/hello` and `/hello/Bob` are accepted,\nbut not `/hello/`, as the trailing slash is part of the optional\nfragment and `:name` still expects one or more characters.\n\nAny unmatched optional parameter gets `false` as its value, so in the\ncase above \"Hello, World!\" gets returned for the `/hello` request URL.\n\nMore than one optional parameter can be specified and optional\nfragments can be nested, so both `\"/posts(/:pid/comments(/:cid))\"` and\n`\"/posts(/:pid)/comments(/:cid)\"` are valid route values.\n\n#### Splat parameters\n\nThere is another kind of parameter called splat that is written as `*`\nand matches zero or more characters, *including* a forward slash (`/`).\nThe splat is also stored in the `params` table under the `splat` name.\nFor example, the route `\"/download/*\"` matches `/download/my/file.zip`\nand the splat gets the value of `my/file.zip`. If multiple splats are\nneeded in the same route, then splats can be assigned names similar to\nother parameters: `/download/*path/*fname.zip` (although the same result\ncan be achieved using `/download/*path/:fname.zip`, as the first splat\ncaptures all path parts except the filename).\n\nAll parameters (including the splat) can appear in any part of the path\nand can be surrounded by other text, which needs to be matched exactly.\nThis means that the route `\"/download/*/:name.:ext\"` matches\n`/download/my/path/file.zip` and `params.name` gets `file`,\n`params.ext` gets `zip` and `params.splat` gets `my/path` values.\n\nAnother reason to use splat is to allow multiple routes with the same\npath to be registered in the system. The current implementation\noverwrites routes with the same name and to avoid that a named splat can\nbe used to create unique paths. For example,\n\n```lua\nfm.setRoute(\"/*dosomething1\", function(r) return \"something 1\" end)\nfm.setRoute(\"/*dosomething2\", function(r) return \"something 2\" end)\n```\n\nThis can be used in situations when there is a set of conditions that\nneeds to be checked in the action handler and while it may be possible\nto combine both routes into one, sometimes it's cleaner to keep them\nseparate.\n\n#### Custom parameters\n\nThe default value for the parameters is all characters (except `/`) of\nlength one or more. To specify a different set of valid characters, it\ncan be added at the end of the variable name; for example, using\n`:id[%d]` instead of `:id` changes the parameter to match only digits.\n\n```lua\nfm.setRoute(\"/hello(/:id[%d])\",\n  function(r) return \"Hello, \"..(r.params.id or \"World!\") end)\n```\n\nThe following Lua character classes are supported: `%w`, `%d`, `%a`,\n`%l`, `%u`, and `%x`; any punctuation character (including `%` and `]`)\ncan also be escaped with `%`. Negative classes (written in Lua as `%W`)\nare *not supported*, but not-in-set syntax is supported, so `[^%d]`\nmatches a parameter that doesn't include any digits.\n\nNote that the number of repetitions can't be changed (so `:id[%d]*`\nis not a valid way to accept zero-or-more digits), as only sets are\nallowed and the values still accept one or more characters. If more\nflexibility in describing acceptable formats is needed, then [custom\nvalidators](#custom-validators) can be used to extend the matching logic.\n\n#### Query and Form parameters\n\nQuery and form parameters can be accessed in the same way as the path\nparameters using the `params` table in the `request` table that is\npassed to each action handler. Note that if there is a conflict between\nparameter and query/form names, then **parameter names take precedence**.\n\nThere is one special case that may result in a table returned instead of\na string value: if the query/form parameter name ends in `[]`, then all\nmatching results (one or more) are returned as a table. For example,\nfor a query string `a[]=10\u0026a[]\u0026a[]=12\u0026a[]=` the value of `params[\"a[]\"]`\nis `{10, false, 12, \"\"}`.\n\nAs writing these parameter names may require several brackets, `params.a`\ncan be used as a shortcut for `params[\"a[]\"]` with both forms returning\nthe same table.\n\n#### Multipart parameters\n\nMultipart parameters are also processed when requested and can be\naccessed in the same way as the rest of the parameters using the `params`\ntable. For example, parameters with names `simple` and `more` can be\nretrieved from a message with `multipart/form-data` content type using\n`params.simple` and `params.more`.\n\nAs some of the multipart content may include additional headers and\nparameters within those headers, they can be accessed with `multipart`\nfield of the `params` table:\n\n```lua\nfm.setRoute({\"/hello\", simple = \"value\"}, function(r)\n    return \"Show \"..r.params.simple..\" \"..r.params.multipart.more.data)\n  end)\n```\n\nThe `multipart` table includes all the parts of the multipart message\n(so it can be iterated over using `ipairs`), but it also allows access\nusing parameter names (`params.multipart.more`). Each of the elements is\nalso a table that includes the following fields:\n\n- data: the main field with the content. It contains a **string** with\n  the content or a **table** in the case of recursive multipart messages.\n- headers: a table with headers (as keys, **all lowercase**) and their\n  content as values. This table is always present, but may be empty.\n- name: the name of the parameter (if specified); `nil` if not.\n- filename: the filename of the parameter (if specified); `nil` if not.\n\nThis multipart processing consumes any multipart sub-types and handles\nrecursive multipart messages. It also inserts a part with `Content-ID`\nvalue matching the `start` parameter into the first position.\n\n#### Multiple routes\n\nDespite all earlier examples showing a single route, it's rarely the\ncase in real applications; when multiple routes are present, they are\nalways **evaluated in the order in which they are registered**.\n\nOne `setRoute` call can also set multiple routes when they have the same\nset of conditions and share the same action handler:\n\n```lua\nfm.setRoute({\"/route1\", \"/route2\"}, handler)\n```\n\nThis is equivalent to two calls setting each route individually:\n\n```lua\nfm.setRoute(\"/route1\", handler)\nfm.setRoute(\"/route2\", handler)\n```\n\nGiven that routes are evaluated in the order in which they are set, more\nselective routes need to be set first, otherwise they may not get a\nchance to be evaluated:\n\n```lua\nfm.setRoute(\"/user/bob\", handlerBob)\nfm.setRoute(\"/user/:name\", handlerName)\n```\n\nIf the routes are set in the opposite order, `/user/bob` may never be\nchecked as long as the `\"/user/:name\"` action handler returns some\nnon-`false` result.\n\nAs described earlier, if none of the routes match, a response with a 404\nstatus code is returned. There may be cases when this is *not* desirable;\nfor example, when the application includes Lua scripts to handle requests\nthat are not explicitly registered as routes. In those cases, a catch-all\nroute can be added that implements the default redbean processing (the name\nof the splat parameter is only used to disambiguate this route against\nother `/*` routes that may be used elsewhere):\n\n```lua\nfm.setRoute(\"/*catchall\", fm.servePath)\n```\n\n#### Named routes\n\nEach route can be provided with an optional name, which is useful in\nreferencing that route when its URL needs to be generated based on\nspecific parameter values. Provided `makePath` function accepts either\na route name or a route URL itself as well as the parameter table and\nreturns a path with populated parameter placeholders:\n\n```lua\nfm.setRoute(\"/user/:name\", handlerName)\nfm.setRoute({\"/post/:id\", routeName = \"post\"}, handlerPost)\n\nfm.makePath(\"/user/:name\", {name = \"Bob\"}) --\u003e /user/Bob\nfm.makePath(\"/post/:id\", {id = 123}) --\u003e /post/123\nfm.makePath(\"post\", {id = 123}) --\u003e /post/123, same as the previous one\n```\n\nIf two routes use the same name, then the name is associated with the\none that was registered last, but both routes are still present.\n\nThe route name can also be used with external/static routes that are\nonly used for URL generation.\n\n#### External routes\n\nIf the route is only used for path generation, then it doesn't even need\nto have a route handler:\n\n```lua\nfm.setRoute({\"https://youtu.be/:videoid\", routeName = \"youtube\"})\nfm.makePath(\"youtube\", {videoid = \"abc\"}) --\u003e https://youtu.be/abc\n```\n\nA route without any action handler is skipped during the route matching\nprocess.\n\n#### Internal routes\n\nInternal routes allow redirecting of one set of URLs to a different one.\nThe target URL can point to a static resource or a `.lua` script. For example,\nif requests for one location need to be redirected to another, the following\nconfiguration redirects requests for any resources under `/blog/` URL to those\nunder `/new-blog/` URL as long as the target resource exists:\n\n```lua\nfm.setRoute(\"/blog/*\", \"/new-blog/*\")\n```\n\nThis route accepts a request for `/blog/post1` and serves `/new-blog/post1`\nas its response, as long as `/new-blog/post1` asset exists.\n**If the asset doesn't exist, then the next route is checked.** Similarly,\nusing `fm.setRoute(\"/static/*\", \"/*\")` causes requests for `/static/help.txt`\nto be served resource `/help.txt`.\n\nBoth URLs can include parameters that are filled in if resolved:\n\n```lua\nfm.setRoute(\"/blog/:file\", \"/new-blog/:file.html\") --\u003c\u003c-- serve \"nice\" URLs\nfm.setRoute(\"/new-blog/:file.html\", fm.serveAsset) --\u003c\u003c-- serve original URLs\n```\n\nThis example resolves \"nice\" URLs serving their \"html\" versions. Note that this\n**doesn't trigger the client-side redirect by returning the `3xx` status code**,\nbut instead handles the re-routing internally.\nAlso note that **the second rule is needed to serve the \"original\" URLs,**\nas they are not handled by the first rule, because if the request is for\n`/blog/mylink.html`, then the redirected URL is `/new-blog/mylink.html.html`,\nwhich is not likely exist, so the route is skipped and the next one is checked.\nIf handling of path separators is required as well, then `*path` can be used\ninstead of `:file`, as `*` allows path separators.\n\n### Conditions\n\nIf an application needs to execute different functions depending on\nspecific values of request attributes (for example, a method), this\nlibrary provides two main options: (1) check for the attribute value an\naction handler (for example, using `request.method == \"GET\"` check) and\n(2) add a condition that filters out requests such that only requests\nusing the specified attribute value reach the action handler. This\nsection describes the second option in more detail.\n\n#### Handling of HTTP methods\n\nEach registered route by default responds to all HTTP methods (GET, PUT,\nPOST, etc.), but it's possible to configure each route to only respond\nto specific HTTP methods:\n\n```lua\nfm.setRoute(fm.GET\"/hello(/:name)\",\n  function(r) return \"Hello, \"..(r.params.name or \"World!\") end)\n```\n\nIn this case, the syntax `fm.GET\"/hello(/:name)\"` configures the route\nto only accept `GET` requests. This syntax is equivalent to passing a\ntable with the route and any additional filtering conditions:\n\n```lua\nfm.setRoute({\"/hello(/:name)\", method = \"GET\"},\n  function(r) return \"Hello, \"..(r.params.name or \"World!\") end)\n```\n\nIf more than one method needs to be specified, then a table with a list\nof methods can be passed instead of one string value:\n\n```lua\nfm.setRoute({\"/hello(/:name)\", method = {\"GET\", \"POST\"}},\n  function(r) return \"Hello, \"..(r.params.name or \"World!\") end)\n```\n\nEvery route that allows a `GET` request also (implicitly) allows a\n`HEAD` request and that request is handled by returning all headers\nwithout sending the body itself. If for some reason this implicit\nhandling is not desirable, then adding `HEAD = false` to the method\ntable disables it (as in `method = {\"GET\", \"POST\", HEAD = false}`).\n\nNote that requests with non-matching methods don't get rejected, but\nrather [fall through](#actions) to be checked by other routes and\ntrigger the 404 status code returned if they don't get matched (with\none [exception](#responding-on-failed-conditions)).\n\n#### Conditional routes\n\nIn addition to `method`, other conditions can be applied using `host`,\n`clientAddr`, `serverAddr`, `scheme`, request headers, and parameters.\nFor example, specifying `name = \"Bob\"` as one of the conditions ensures\nthe value of the `name` parameter to be \"Bob\" for the action handler to\nbe called.\n\nAny request header can be checked using the header name as the key, so\n`ContentType = \"multipart/form-data\"` is satisfied if the value of the\n`Content-Type` header is `multipart/form-data`. Note that the header\nvalue may include other elements (a boundary or a charset as part of\nthe `Content-Type` value) and only the actual media type is compared.\n\nSince names for headers, parameters and properties can overlap, they are\nchecked in the following order:\n- request headers that consist of multiple words, like `ContentType`,\n- request parameters,\n- request properties (`method`, `port`, `host`, etc.), and\n- request headers again.\n\n`Host` header is also checked first (despite being a single word), so\nreferencing `Host` filters based on the header `Host`, while referencing\n`host` filters based on the property `host`.\n\n#### Custom validators\n\nString values are not the only values that can be used in conditional\nroutes. If more than one value is acceptable, passing a table allows to\nprovide a list of acceptable values. For example, if `Bob` and `Alice`\nare acceptable values, then `name = {Bob = true, Alice = true}`\nexpresses this as a condition.\n\nTwo special values passed in a table allow to apply a *regex* or a\n*pattern* validation:\n\n- `regex`: accepts a string that has a regular expression. For example,\n  `name = {regex = \"^(Bob|Alice)$\"}` has the same result as the hash\n  check shown earlier in this section\n- `pattern`: accepts a string with a Lua pattern expression. For example,\n  `name = {pattern = \"^%u%l+$\"}` accepts values that start with an\n  uppercase character followed by one or more lowercase characters.\n\nThese two checks can be combined with the table existence check:\n`name = {Bob = true, regex = \"^Alice$\"}` accepts both `Bob` and `Alice`\nvalues. If the first table-existence check fails, then the results of\nthe `regex` or `pattern` expression is returned.\n\nThe last type of a custom validator is a function. The provided function\nreceives the value to validate and its result is evaluated as `false` or\n`true`. For example, passing `id = tonumber` ensures that the `id` value\nis a number. As another example, `clientAddr = fm.isLoopbackIp` ensures\nthat the client address is a loopback ip address.\n\n```lua\nfm.setRoute({\"/local-only\", clientAddr = fm.isLoopbackIp},\n  function(r) return \"Local content\" end)\n```\n\nAs the validator function can be generated dynamically, this works too:\n\n```lua\nlocal function isLessThan(n)\n  return function(l) return tonumber(l) \u003c n end\nend\nfm.setRoute(fm.POST{\"/upload\", ContentLength = isLessThan(100000)},\n  function(r) ...handle the upload... end)\n```\n\nIt's important to keep in mind that the validator function actually\nreturns a function that is called during a request to apply the check.\nIn the previous example, the returned function accepts a header value\nand compares it with the limit passed during its creation.\n\n#### Responding on failed conditions\n\nIn some cases, failing to satisfy a condition is a sufficient reason to\nreturn some response back to the client without checking other routes.\nIn a case like this, setting `otherwise` value to a number or a function\nreturns either a response with the specified status or the result of the\nfunction:\n\n```lua\nlocal function isLessThan(n)\n  return function(l) return tonumber(l) \u003c n end\nend\nfm.setRoute(fm.POST{\"/upload\",\n    ContentLength = isLessThan(100000), otherwise = 413\n  }, function(r) ...handle the upload... end)\n```\n\nIn this example the routing engine matches the route and then validates\nthe two conditions comparing the method value with `POST` and the value\nof the `Content-Length` header with the result of the `isLessThan`\nfunction. If *one of the conditions* doesn't match, the status code\nspecified by the `otherwise` value is returned with the rest of the\nresponse.\n\nIf the `otherwise` condition needs to *only* apply to the `ContentLength`\ncheck, then the `otherwise` value along with the validator function can\nbe moved to a table associated with the `ContentLength` check:\n\n```lua\nfm.setRoute(fm.POST{\"/upload\",\n    ContentLength = {isLessThan(100000), otherwise = 413}\n  }, function(r) ...handle the upload... end)\n```\n\nThe difference between the last two examples is that in this example\nonly the `ContentLength` check failure triggers the 413 response (and\nall other methods fall through to other routes), while in the previous\none both `method` and `ContentLength` check failures trigger the same\n413 response.\n\nNote that when the checked value is `nil`, the check against a table is\ndeemed to be valid and the route is accepted. For example, a check for an\noptional parameter made against a string (`name = \"Bo\"`) fails if the\nvalue of `params.name` is `nil`, but passes if the same check is made\nagainst a table (`name = {Bo=true, Mo=true}`), including regex/pattern checks.\nIf this is not desirable, then a custom validator function can explicitly\ncheck for the expected value.\n\nConsider the following example:\n\n```lua\nfm.setRoute({\"/hello(/:name)\",\n    method = {\"GET\", \"POST\", otherwise = 405}},\n  function(r) return \"Hello, \"..(r.params.name or \"World!\") end)\n```\n\nIn this case, if this endpoint is accessed with the `PUT` method, then\ninstead of checking other routes (because the `method` condition is not\nsatisfied), the 405 status code is returned, as configured with the\nspecified `otherwise` value. [As documented elsewhere](#handling-of-http-methods),\nthis route accepts a `HEAD` request too (even when not listed), as a\n`GET` request is accepted.\n\nWhen the 405 (Bad method) status code is returned and the `Allow` header\nis not set, it is set to the list of methods allowed by the route. In\nthe case above it is set to `GET, POST, HEAD, OPTIONS` values, as those\nare the methods allowed by this configuration. If the `otherwise` value\nis a function (rather than a number), then returning a proper result and\nsetting the `Allow` header is the responsibility of this function.\n\nThe `otherwise` value can also be set to a function, which provides more\nflexibility than just setting a status code. For example, setting\n`otherwise = fm.serveResponse(413, \"Payload Too Large\")` triggers a\nresponse with the specified status code and message.\n\n#### Form validation\n\nHandling form validation often requires specifying a set of conditions\nfor the same parameter and a custom error message that may need to be\nreturned when the conditions are not satisfied and these are provided\nby special validators returned by `makeValidator` function:\n\n```lua\nlocal validator = fm.makeValidator{\n  {\"name\", minlen = 5, maxlen = 64, msg = \"Invalid %s format\"},\n  {\"password\", minlen = 5, maxlen = 128, msg = \"Invalid %s format\"},\n}\nfm.setRoute(fm.POST{\"/signin\", _ = validator}, function(r)\n    -- do something useful with name and password\n    return fm.serveRedirect(307, \"/\")\n  end)\n```\n\nIn this example, the validator is configured to check two parameters --\n\"name\" and \"password\" -- for their min and max lengths and return a\nmessage when one of the parameters fails the check.\n\nSince the failing check causes the route to be skipped, providing the\n`otherwise` value allows the error to be returned as part of the\nresponse:\n\n```lua\nlocal validator = fm.makeValidator{\n  {\"name\", minlen = 5, maxlen = 64, msg = \"Invalid %s format\"},\n  {\"password\", minlen = 5, maxlen = 128, msg = \"Invalid %s format\"},\n  otherwise = function(error)\n    return fm.serveContent(\"signin\", {error = error})\n  end,\n}\n```\n\nIn this case the `otherwise` handler receives the error message (or a table\nwith messages if requested by passing the `all` option covered below) that\ncan be then provided as a template parameter and returned to the client.\n\nAnother option is to call the validator function directly in an action\nhandler and return its results:\n\n```lua\nlocal validator = fm.makeValidator{\n  {\"name\", minlen = 5, maxlen = 64, msg = \"Invalid %s format\"},\n  {\"password\", minlen = 5, maxlen = 128, msg = \"Invalid %s format\"},\n}\nfm.setRoute(fm.POST{\"/signin\"}, function(r)\n    local valid, error = validator(r.params)\n    if valid then\n      return fm.serveRedirect(\"/\") -- status code is optional\n    else\n      return fm.serveContent(\"signin\", {error = error})\n    end\n  end)\n```\n\nIn this example the validator is called directly and is passed a table\n(`r.params`) with all parameter values to allow the validator function\nto check the values against the specified rules.\n\nThe validator function then returns `true` to signal success or\n`nil, error` to signal a failure to check one of the rules. This allows\nthe validator call to be wrapped into an `assert` if the script needs\nto return an error right away:\n\n```lua\nassert(validator(r.params))  -- throw an error if validation fails\nreturn fm.serveRedirect(307, \"/\")  -- return redirect in other cases\n```\n\nThe following validator checks are available:\n- `minlen`: (integer) checks minimal length of a string.\n- `maxlen`: (integer) checks maximal length of a string.\n- `test`: (function) calls a function that is passed one parameter\n  and is expected to return `true` or `nil | false [, error]`.\n- `oneof`: (`value | { table of values to be compared against }`)\n  checks if the parameter matches one of the provided values.\n- `pattern`: (string) checks if the parameter matches a Lua pattern\n  expression.\n\nIn addition to the checks, the rules may include options:\n- `optional`: (bool) makes a parameter optional when it's `nil`.\n  All the parameters are required by default, so this option allows\n  the rules to be skipped when the parameter is not provided.\n  All the rules are still applied if parameter is not nil.\n- `msg`: (string) adds a customer message for this if one of its\n  checks fails, which overwrites messages from individual checks.\n  The message may include a placeholder (`%s`), which is going to\n  be replaced by a parameter name.\n\nThe validator itself also accepts several options that modify how\nthe generated errors are returned or handled:\n- `otherwise`: (function) sets an error handler that is called\n  when one of the checks fails. The function receives the error(s)\n  triggered by the checks.\n- `all`: (bool) configures the validator to return all errors\n  instead of just the first one. By default only one (first) error\n  is returned as a string, so if all errors are requested, they\n  are returned as a table with each error being a separate item.\n- `key`: (bool) configures the validator to return error(s) as\n  values in a hash table (instead of element) where the keys are\n  parameter names. This is useful to pass the table with errors to\n  a template that can then display `errors.name` and\n  `errors.password` error messages next to their input fields.\n\n### Actions\n\nAn action handler receives all incoming HTTP requests filtered for a\nparticular route. Each of the examples shown so far includes an action\nhandler, which is passed as a second parameter to the `setRoute` method.\n\n**Multiple action handlers can be executed in the course of handling one\nrequest and as soon as one handler returns a result that is evaluated as\na non-`false` value, the route handling process ends.** Returning `false`\nor `nil` from an action handler continues the processing, which allows\nimplementing some common processing that applies to multiple routes\n(similar to what is done using \"before\" filters in other frameworks):\n\n```lua\nlocal uroute = \"/user/:id\"\nfm.setRoute({uroute..\"/*\", method = {\"GET\", \"POST\", otherwise = 405}},\n    function(r)\n      -- retrieve user information based on r.params.id\n      -- and store in r.user (as one of the options);\n      -- return error if user is not found\n      return false -- continue handling\n  end)\nfm.setRoute(fm.GET(uroute..\"/view\"), function(r) ... end)\nfm.setRoute(fm.GET(uroute..\"/edit\"), function(r) ... end)\nfm.setRoute(fm.POST(uroute..\"/edit\"), function(r) ... end)\n```\n\nIn this example, the first route can generate three outcomes:\n\n- if the route is not matched, then other routes set later are checked.\n- if the route is matched, but the condition (the `method` check) is not\n  matched, then the 405 status code is returned.\n- if the route is matched and the action handler is executed, it either\n  retrieves the user and returns `false`, which continues processing\n  with other routes, or fails to retrieve the user and returns an error.\n\nIn general, an action handler can return any of the following values:\n\n- `true`: this stops any further processing, sets the headers that have\n  been specified so far, and returns the generated or set response body.\n- `false` or `nil`: this stops the processing of the current route and\n  proceeds to the next one.\n- a string value: this sends a response with 200 as the status code and\n  the returned string as its body. The `Content-Type` is set based on\n  the body content (using a primitive heuristic) if not set explicitly.\n- a function value (most likely as a call to one of `serve*` methods):\n  this executes the requested method and returns an empty string or\n  `true` to signal the end of the processing.\n- any other returned value is ignored and interpreted as if `true` is\n  returned (and a warning is logged).\n\n#### Throwing errors\n\nNormally any processing that results in a Lua error is returned to the\nclient as a server error response (with the 500 status code). To assist\nwith local debugging, the error message includes a stack trace, but only\nif the request is sent from a loopback or private IP (or if redbean is\nlaunched with the `-E` command line option).\n\nIt may be desirable to return a specific response through multiple\nlayers of function calls, in which case the error may be triggered with\na function value instead of a string value. For example, executing\n`error(fm.serve404)` results in returning the 404 status code, which is\nsimilar to using `return fm.serve404`, but can be executed in a function\ncalled from an action handler (and *only* from inside an action handler).\n\nHere is a more complex example that returns the 404 status code if no\nrecord is fetched (assuming there is a table `test` with a field `id`):\n\n```\nlocal function AnyOr404(res, err)\n  if not res then error(err) end\n  -- serve 404 when no record is returned\n  if res == db.NONE then error(fm.serve404) end\n  return res, err\nend\nfm.setRoute(\"/\", function(r)\n    local row = AnyOr404(dbm:fetchOne(\"SELECT id FROM test\"))\n    return row.id\n  end)\n```\n\nThis example uses the `serve404` function, but any other [serve*](#responses)\nmethod can also be used.\n\n### Requests\n\nEach [action handler](#actions) accepts a request table that includes\nthe following attributes:\n\n- `method`: request HTTP method (GET, POST, and others).\n- `host`: request host (if provided) or the bind address.\n- `serverAddr`: address to which listening server socket is bound.\n- `remoteAddr`: client ip4 address encoded as a number. This takes into\n  consideration reverse proxy scenarios. Use `formatIp` function to\n  convert to a string representing the address.\n- `scheme`: request URL scheme (if any).\n- `path`: request URL path that is guaranteed to begin with `/`.\n- `authority`: request URL with scheme, host, and port present.\n- `url`: request URL as an ASCII string with illegal characters percent\n  encoded.\n- `body`: request message body (if present) or an empty string.\n- `date`: request date as a Unix timestamp.\n- `time`: current time as a Unix timestamp with 0.0001s precision.\n\nThe request table also has several [utility functions](utility-functions),\nas well as [headers](#headers), [cookies](#cookies), and [session](#session)\ntables that allow retrieving request headers, cookies, and session and\nsetting of headers and cookies that are included with the response.\n\nThe same request table is given as a parameter to all (matched) action\nhandlers, so it can be used as a mechanism to pass values between those\naction handlers, as any value assigned as a field in one handler is\navailable in all other action handlers.\n\n#### Headers\n\nThe `headers` table provides access to the request headers. For example,\n`r.headers[\"Content-Type\"]` returns the value of the `Content-Type`\nheader. This form of header access is case-insensitive. A shorter form\nis also available (`r.headers.ContentType`), but only for registered\nheaders and *is* case-sensitive with the capitalization preserved.\n\nThe request headers can also be set using the same syntax. For example,\n`r.headers.MyHeader = \"value\"` sets `MyHeader: value` response header.\nAs the headers are set at the end of the action handler processing,\nheaders set earlier can also be removed by assigning a `nil` value.\n\nRepeatable headers can also be assigned with values separated by commas:\n`r.headers.Allow = \"GET, POST\"`.\n\n#### Cookies\n\nThe `cookies` table provides access to the request cookies. For example,\n`r.cookies.token` returns the value of the `token` cookie.\n\nThe cookies can also be set using the same syntax. For example,\n`r.cookies.token = \"new value\"` sets `token` cookie to `new value`.\nIf the cookie needs to have its attributes set as well, then the value\nand the attributes need to be passed as a table:\n`r.cookies.token = {\"new value\", secure = true, httponly = true}`.\n\nThe following cookie attributes are supported:\n- `expires`: sets the maximum lifetime of the cookie as an HTTP-date\n  timestamp. Can be specified as a date in the RFC1123 (string) format\n  or as a UNIX timestamp (number of seconds).\n- `maxage`: sets number of seconds until the cookie expires. A zero or\n  negative number expires the cookie immediately. If both `expires`\n  and `maxage` are set, `maxage` has precedence.\n- `domain`: sets the host to which the cookie is going to be sent.\n- `path`: sets the path that must be present in the request URL, or\n  the client is not going to send the Cookie header.\n- `secure`: (bool) requests the cookie to be only send to the\n  server when a request is made with the https: scheme.\n- `httponly`: (bool) forbids JavaScript from accessing the cookie.\n- `samesite`: (`Strict`, `Lax`, or `None`) controls whether a cookie is\n  sent with cross-origin requests, providing some protection against\n  cross-site request forgery attacks.\n\nNote that `httponly` and `samesite=\"Strict\"` are set by default;\na different set of defaults can be provided using `cookieOptions`\npassed to the [run method](#running-application). Any attributes set\nwith a table **overwrite the default**, so if `Secure` needs to\nbe enabled, make sure to also pass `httponly` and `samesite` options.\n\nTo delete a cookie, set its value to `false`: for example,\n`r.cookies.token = false` deletes the value of the `token` cookie.\n\n#### Session\n\nThe `session` table provides access to the session table that can\nbe used to set or retrieve session values. For example,\n`r.session.counter` returns the `counter` value set previously.\nThe session values can also be set using the same syntax. For example,\n`r.session.counter = 2` sets the `counter` value to `2`.\n\nThe session allows storing of nested values and other Lua values.\nIf the session needs to be removed, it can be set to an empty table\nor a `nil` value. Each session is signed with an application secret,\nwhich is assigned a random string by default and can be changed by\n[setting session options](#session-options).\n\n#### Utility functions\n\nThe following functions are available as both request functions (as\nfields in the request table) and as library functions:\n\n- `makePath(route[, parameters])`: creates a path from either a route\n  name or a path string by populating its parameters using values from\n  the parameters table (when provided).\n  The path doesn't need to be just a path component of a URL and can be\n  a full URL as well. [Optional parts](#optional-parameters) are removed\n  if they include parameters that are not provided.\n- `makeUrl([url,] options)`: creates a URL using the provided value and\n  a set of URL parameters provided in the `options` table: scheme, user,\n  pass, host, port, path, and fragment.\n  The `url` parameter is optional; the current request URL is used if\n  `url` is not specified. Any of the options can be provided or removed\n  (using `false` as the value). For example, `makeUrl({scheme=\"https\"})`\n  sets the scheme for the current URL to `https`.\n- `escapeHtml(string)`: escapes HTML entities (`\u0026\u003e\u003c\"'`) by replacing them\n  with their HTML entity counterparts (`\u0026amp;\u0026gt;\u0026lt;\u0026quot;\u0026#39;`).\n- `escapePath(path)`: applies URL encoding (`%XX`) escaping path unsafe\n  characters (anything other than `-.~_@:!$\u0026'()*+,;=0-9A-Za-z/`).\n- `formatHttpDateTime(seconds)`: converts UNIX timestamp (in seconds) to\n  an RFC1123 string (`Mon, 21 Feb 2022 15:37:13 GMT`).\n\n### Templates\n\nTemplates provide a simple and convenient way to return a predefined and\nparametrized content instead of generating it piece by piece.\n\nThe included template engine supports mixing an arbitrary text with Lua\nstatements/expressions wrapped into `{% %}` tags. All the code in templates\nuses a regular Lua syntax, so there is no new syntax to learn. There are\nthree ways to include some Lua code:\n- `{% statement %}`: used for Lua *statements*.\n  For example, `{% if true then %}Hello{% end %}` renders `Hello`.\n- `{%\u0026 expression %}`: used for Lua *expressions* rendered as HTML-safe text.\n  For example, `{%\u0026 '2 \u0026 2' %}` renders `2 \u0026amp; 2`.\n- `{%= expression %}`: used for Lua *expressions* rendered as-is (without escaping).\n  For example, `{%= 2 + 2 %}` renders `4`.\n  Be careful, as HTML is not escaped with `{%= }`, this should be used carefully\n  due to the potential for XSS attacks.\n\nThe template engine provides two main functions to use with templates:\n- `setTemplate(name, text[, parameters])`: registers a template with the\n  provided name and text (and uses `parameters` as its default parameters).\n  There are special cases where `name` or `text` parameters may not be\n  strings, with some of those cases covered in\n  the [Loading templates](#loading-templates) section.\n  `parameters` is a table with template parameters as\n  name/value pairs (referenced as variables in the template).\n- `render(name, parameters)`: renders a registered template using the\n  `parameters` table to set values in the template (with key/value in the\n  table assigned to name/value in the template).\n\nThere is only one template with a given name, so registering a template\nwith an existing name replaces this previously registered template. This\nis probably rarely needed, but can be used to overwrite default templates.\n\nHere is an example that renders `Hello, World!` to the output buffer:\n\n```lua\nfm.setTemplate(\"hello\", \"Hello, {%\u0026 title %}!\")\nfm.render(\"hello\", {title = \"World\"})\n```\n\nRendering statements using the expression syntax or expressions using\nthe statement syntax is a syntax error that is reported when the template\nis registered. Function calls can be used with either syntax.\n\nAny template error (syntax or run-time) includes a template name and a line\nnumber within the template. For example, calling\n`fm.setTemplate(\"hello\", \"Hello, {%\u0026 if title then end %}!\")` results in\nthrowing `hello:1: unexpected symbol near 'if'` error (as it inserts a Lua\nstatement using the expression syntax).\n\nTemplates can also be loaded from a file or a directory using the same\n`setTemplate` function, which is described later in\nthe [Loading templates](#loading-templates) section.\n\nThere are several aspects worth noting, as they may differ from how\ntemplates are processed in other frameworks:\n- Templates *render directly to the output buffer*. This is done primarily\n  for simplicity and performance reasons to delegate the output management\n  to redbean. This means that template rendering doesn't return the output,\n  although there are alternative ways to access it if needed.\n- Templates only have access to a *restricted environment*. Every value\n  a template is using needs to be explicitly registered or passed as a\n  parameter to be accessible (although there are several\n  [utility functions](#utility-functions) available).\n- Each template is parsed during registration and is converted to function\n  that gets executed when a template is rendered. This allows to handle\n  all the parsing and related processing once (during the initialization)\n  and then call generated functions during rendering.\n- As all *templates are converted to functions*, it is also possible to\n  pass a function directly (instead of a template), which provides a\n  convenient extension mechanism that reuses the rest of the library. For\n  example, [`json` and `sse` templates](#special-templates) are\n  implemented using this approach.\n- There is *no whitespace control or escaping* provided (mostly for\n  simplicity, as the same effect can be achieved with some reformatting).\n\n#### Passing parameters to templates\n\nEach template accepts parameters that then can be used in its rendering logic.\nParameters can be passed in two ways: (1) when the template is registered and\n(2) when the template is rendered. Passing parameters during registration\nallows to set default values that are used if no parameter is provided\nduring rendering. For example,\n\n```lua\nfm.setTemplate(\"hello\", \"Hello, {%\u0026 title %}!\", {title = \"World\"})\nfm.render(\"hello\") -- renders `Hello, World!`\nfm.render(\"hello\", {title = \"All\"}) -- renders `Hello, All!`\n```\n\n`nil` or `false` values are rendered as empty strings without throwing any\nerror, but any operation on a `nil` value is likely to result in a Lua\nerror. For example, doing `{%\u0026 title .. '!' %}` (without `title` set)\nresults in `attempt to concatenate a nil value (global 'title')` error.\n\nThere is no constraint on what values can be passed to a template, so any\nLua value can be passed and then used inside a template.\n\nIn addition to the values that can be passed to templates, there are two\nspecial tables that provide *access to cross-template values*:\n- `vars`: provides access to values registered with `setTemplateVar`, and\n- `block`: provides access to template fragments that can be [overwritten\n  by other templates](#using-layouts-and-blocks).\n\nAny value registered with `setTemplateVar` becomes *accessible from any\ntemplate* through the `vars` table. In the following example, the\n`vars.title` value is set by the earlier `setTemplateVar('title', 'World')`\ncall:\n\n```lua\nfm.setTemplateVar('title', 'World')\nfm.setTemplate(\"hello\", \"Hello, {%\u0026 vars.title %}!\")\nfm.render(\"hello\") -- renders `Hello, World!`\n```\n\n#### Handling undefined values in templates\n\nWhile undefined values are rendered as empty string by default (which may be\nconvenient in most cases), there are still situations when it is preferrable\nto not allow undefined values to be silently handled. In this a special\ntemplate variable (`if-nil`) can be set to handle those cases to throw\nan error or to log a message. For example, the following code throws an\nerror, as the `missing` value is undefined, which triggers `if-nil` handler:\n\n```lua\nfm.setTemplateVar('if-nil', function() error\"missing value\" end)\nfm.setTemplate(\"hello\", \"Hello, {%\u0026 vars.missing %}!\")\nfm.render(\"hello\") -- throws \"missing value\" error\n```\n\n#### Including templates in other templates\n\nTemplates can be also rendered from other templates by using the `render`\nfunction, which is available in every template:\n\n```lua\nfm.setTemplate(\"hello\", \"Hello, {%\u0026 title %}!\")\nfm.setTemplate(\"header\", \"\u003ch1\u003e{% render('hello', {title = title}) %}\u003c/h1\u003e\")\n---------------------------------└──────────────────────────────┘----------\nfm.render(\"header\", {title = 'World'}) -- renders `\u003ch1\u003eHello, World!\u003c/h1\u003e`\n```\n\nThere are no limits on how templates can be rendered from other templates,\nbut no checks for loops are made either, so having circular references in\ntemplate rendering (when a template A renders a template B, which in turn\nrenders A again) is going to cause a Lua error.\n\nIt's worth noting that the `render` function doesn't return the value of\nthe template it renders, but instead puts it directly into the output\nbuffer.\n\n#### Using layouts and blocks\n\nThis ability to render templates from other templates allows producing\nlayouts of any complexity. There are two ways to go about it:\n- to use dynamic template selection or\n- to use blocks.\n\n##### Dynamic template selection\n\nTo dynamically choose the template to use at render time, the template\nname itself can be passed as a parameter:\n\n```lua\nfm.setTemplate(\"hello\", \"Hello, {%\u0026 title %}!\")\nfm.setTemplate(\"bye\", \"Bye, {%\u0026 title %}!\")\nfm.setTemplate(\"header\", \"\u003ch1\u003e{% render(content, {title = title}) %}\u003c/h1\u003e\")\nfm.render(\"header\", {title = 'World', content = 'hello'})\n```\n\nThis example renders either `\u003ch1\u003eHello, World!\u003c/h1\u003e` or\n`\u003ch1\u003eBye, World!\u003c/h1\u003e` depending on the value of the `content` parameter.\n\n##### Blocks\n\nUsing blocks allows defining template fragments that can (optionally) be\noverwritten from other templates (usually called \"child\" or \"inherited\"\ntemplates). The following example demonstrates this approach:\n\n```lua\nfm.setTemplate(\"header\", [[\n  \u003ch1\u003e\n    {% function block.greet() %} -- define a (default) block\n      Hi\n    {% end %}\n    {% block.greet() %}, -- render the block\n    {%\u0026 title %}!\n  \u003c/h1\u003e\n]])\nfm.setTemplate(\"hello\", [[\n  {% function block.greet() %} -- overwrite the `header` block (if any)\n    Hello\n  {% end %}\n  {% render('header', {title=title}) %}!\n]])\nfm.setTemplate(\"bye\", [[\n  {% function block.greet() %} -- overwrite the `header` block (if any)\n    Bye\n  {% end %}\n  {% render('header', {title=title}) %}!\n]])\n\n-- normally only one of the three `render` calls is needed,\n-- so all three are shown for illustrative purposes only\nfm.render(\"hello\", {title = 'World'})  -- renders \u003ch1\u003eHello, World!\u003c/h1\u003e\nfm.render(\"bye\", {title = 'World'})    -- renders `\u003ch1\u003eBye, World!\u003c/h1\u003e`\nfm.render(\"header\", {title = 'World'}) -- renders `\u003ch1\u003eHi, World!\u003c/h1\u003e`\n```\n\nIn this example the `header` template becomes the \"layout\" and defines the\n`greet` block with `Hi` as its content. The block is defined as a function\nin the `block` table with the content it needs to produce. It's followed by\na call to the `block.greet` function to include its content in the template.\n\nThis is important to emphasize, as *in addition to defining a block, it\nalso needs to be called from the base/layout template* at the point where\nit is expected to be rendered.\n\nThe `hello` template also defines `block.greet` function with a different\ncontent and then renders the `header` template. When the `header` template\nis rendered, it uses the content of the `block.greet` function as defined in\nthe `hello` template. In this way, the child template \"redefines\" the `greet`\nblock with its own content, inserting it into the appropriate place into\nthe parent template.\n\nIt works the same way for the `bye` and `header` templates. There is\nnothing special about these \"block\" functions other than the fact that\nthey are defined in the `block` table.\n\nThis concepts is useful for template composition at any depth. For example,\nlet's define a modal template with a header and a footer with action\nbuttons:\n\n```lua\nfm.setTemplate(\"modal\", [[\n  \u003cdiv class=\"modal\"\u003e\n    \u003cdiv class=\"modal-title\"\u003e\n      {% function block.modal_title() %}\n        Details\n      {% end %}\n      {% block.modal_title() %}\n    \u003c/div\u003e\n    \u003cdiv class=\"modal-content\"\u003e\n      {% block.modal_content() %}\n    \u003c/div\u003e\n    \u003cdiv class=\"modal-actions\"\u003e\n      {% function block.modal_actions() %}\n        \u003cbutton\u003eCancel\u003c/button\u003e\n        \u003cbutton\u003eSave\u003c/button\u003e\n      {% end %}\n      {% block.modal_actions() %}\n    \u003c/div\u003e\n  \u003c/div\u003e\n]])\n```\n\nNow, in a template that renders the modal, the blocks can be overwritten\nto customize the content:\n\n```lua\nfm.setTemplate(\"page\", [[\n  {% function block.modal_title() %}\n    Insert photo\n  {% end %}\n  {% function block.modal_content() %}\n    \u003cdiv class=\"photo-dropzone\"\u003eUpload photo here\u003c/div\u003e\n  {% end %}\n\n  {% render('modal') %}\n]])\n```\n\nThis enables easily building composable layouts and components, such as\nheaders and footers, cards, modals, or anything else that requires the\nability to dynamically customize sections in other templates.\n\nHere is an example to illustrate how nested blocks work together:\n\n```lua\n-- base/layout template\n{% function block.greet() %} -- 1. defines default \"greet\" block\n  Hi\n{% end %}\n{% block.greet() %}          -- 2. calls \"greet\" block\n\n-- child template\n{% function block.greet() %} -- 3. defines \"greet\" block\n  Hello\n{% end %}\n{% render('base') %}         -- 4. renders \"base\" template\n\n-- grandchild template\n{% function block.greet() %} -- 5. defines \"greet\" block\n  Bye\n{% end %}\n{% render('child') %}        -- 6. renders \"child\" template\n```\n\nIn this example the \"child\" template \"extends\" the base template and any\n`block.greet` content defined in the child template is rendered inside\nthe \"base\" template (when and where the `block.greet()` function is\ncalled). The default `block.greet` block doesn't need to be defined in\nthe base template, but when it is present (step 1), it sets the content\nto be rendered (step 2) if the block is not overwritten in a child\ntemplate and needs to be defined *before* `block.greet` function is called.\n\nSimilarly, `block.greet` in the child template needs to be defined\n*before* (step 3) the base template is rendered (step 4) to have\na desired effect.\n\nIf one of the templates in the current render tree doesn't define the\nblock, then the later defined block is going to be used. For example,\nif the grandchild template doesn't define the block in step 5, then\nthe `greet` block from the child template is going to be used when the\ngrandchild template is rendered.\n\nIf none of the `block.greet` functions is defined, then `block.greet()`\nfails (in the `base` template). *To make the block optional*, just\ncheck the function before calling. For example,\n`block.greet and block.greet()`.\n\nIn those cases where the \"overwritten\" block may still need to be rendered,\nit's possible to reference that block directly from the template that\ndefines it, as shown in the following example:\n\n```lua\nfm.setTemplate(\"header\", [[\n  \u003ch1\u003e\n    {% function block.greet() %}\n      Hi\n    {% end %}\n    {% block.greet() %}, {%\u0026 title %}!\n  \u003c/h1\u003e\n]])\nfm.setTemplate(\"bye\", [[\n {% block.header.greet() %},\n  {% function block.greet() %}\n    Bye\n  {% end %}\n  {% render('header', {title=title}) %}!\n]])\nfm.render(\"bye\", {title = 'World'}) -- renders `\u003ch1\u003eHi, Bye, World!\u003c/h1\u003e`\n```\n\nIn this case, `{% block.header.greet() %}` in the `bye` template renders\nthe `greet` block from the `header` template. This only works with the\ntemplates that are currently being rendered and is intended to simulate the\n\"super\" reference (albeit with explicit template references). The general syntax\nof this call is `block.\u003ctemplatename\u003e.\u003cblockname\u003e()`.\n\nAs blocks are simply regular Lua functions, there are no restrictions\non how blocks can be nested into other blocks or how blocks are defined\nrelative to template fragments or other Lua statements included in\nthe templates.\n\n#### Loading templates\n\nIn addition to registering templates from a string, the templates can be\nloaded and registered from a file or a directory using the same\n`setTemplate` function, but passing a table with the directory and a list\nof mappings from file extensions to template types to load. For example,\ncalling `fm.setTemplate({\"/views/\", tmpl = \"fmt\"})` loads all `*.tmpl`\nfiles from the `/views/` directory (and its subdirectories) and\nregisters each of them as the `fmt` template, which is the default\ntemplate type. Only those files that match the extension are loaded\nand multiple extension mappings can be specified in one call.\n\nEach loaded template gets its name based on the full path starting\nfrom the specified directory: the file `/views/hello.tmpl` is registered\nas a template with the name \"hello\" (without the extension), whereas the\nfile `/views/greet/bye.tmpl` is registered as a template with the name\n\"greet/bye\" (and this is the exact name to use to load the template).\n\nThere are two caveats worth mentioning, both related to the directory\nprocessing. The first one is related to the trailing slash in the\ndirectory name passed to `setTemplate`. It's recommended to provide\none, as the specified value is used as a prefix, so if `/view` is\nspecified, it's going to match both `/view/` and `/views/` directories\n(if present), which may or may not be the intended result.\n\nThe second caveat is related to how external directories are used during\ntemplate search. Since redbean allows access to external directories when\nconfigured using the `-D` option or `directory` option\n(see [Running application](#running-application) for details), there may\nbe multiple locations for the same template available. The search for the\ntemplate follows these steps:\n- the internal (zip archive) is used to get the list of files matching\n  a certain prefix (as specified in a `setTemplate` call);\n- the external directories are checked (in the order in which they\n  are specified) to load the file;\n- the internal (zip archive) directory is checked to load the file.\n\nThis allows to have a working copy of a template to be modified and\nprocessed from the file system (assuming the `-D` option is used) during\ndevelopment without modifying its copy in the archive.\n\n#### Serving template output\n\nEven though using `fm.render` is sufficient to get a template rendered,\nfor consistency with other [serve*](#responses) functions, the library\nprovides the [`serveContent` function](#serving-content), which is\nsimilar to `fm.render`, but allows the action handler to complete after\nserving the content:\n\n```lua\nfm.setTemplate(\"hello\", \"Hello, {%\u0026 name %}\")\nfm.setRoute(\"/hello/:name\", function(r)\n    return fm.serveContent(\"hello\", {name = r.params.name})\n  end)\n```\n\nThere is also one subtle difference between `render` and `serveContent`\nmethods that comes into play when *serving static templates*. It may be\ntempting to directly render a static template in response to a route\nwith something like this:\n\n```lua\nfm.setTemplate(\"hello\", \"Hello, World!\")\n-- option 1:\nfm.setRoute(\"/hello\", fm.render(\"hello\"))\n-------------------------└─────┘-------- not going to work\n-- option 2:\nfm.setRoute(\"/hello\", fm.serveContent(\"hello\"))\n-------------------------└───────────┘-- works as expected\n```\n\nThe first approach is not going to work, as the call to `fm.render` is\ngoing to be made when `setRoute` is called (and the route is only being\nset up) and not when a request is being handled. When the `serveContent`\nmethod is using (the second option), it's implemented in a way that delays\nthe processing until the request is handled, thus avoiding the issue.\nIf the template content depends on some values in the request, then the\n`serverContent` call has to be wrapped into a function to accept and pass\nthose variables (as shown in the earlier `/hello/:name` route example).\n\n#### Special templates\n\n### Schedules\n\nMost of the time, the library configuration is focused on handling of\nincoming requests, but in some cases it may be desirable to trigger\nand handle internal events. The library supports job scheduling using\ncron syntax, with configured jobs executed at the scheduled time (as\nlong as the redbean instance is running). A new schedule can be\nregistered using the `setSchedule` method:\n\n```lua\n--------------- ┌─────────── minute (0-59)\n--------------- │ ┌───────── hour (0-23)\n--------------- │ │ ┌─────── day of the month (1-31)\n--------------- │ │ │ ┌───── month (1-12 or Jan-Dec)\n--------------- │ │ │ │ ┌─── day of the week (0-6 or Sun-Mon)\n--------------- │ │ │ │ │ --\n--------------- │ │ │ │ │ --\nfm.setSchedule(\"* * * * *\", function() fm.logInfo(\"every minute\") end)\n```\n\nAll the standard and some non-standard cron expressions are supported:\n- `*`: describes any values in the allowed range.\n- `,`: uses to form a list of items, for example, `1,2,3`.\n- `-`: creates an (inclusive) range; for example, `1-3` is equivalent\n  to `1,2,3`. Open ranges are allowed as well, so `-3` is equivalent to\n  `1-3` for months and `0-3` for minutes and hours.\n- `/`: describes a step for ranges. It selects a subset of the values\n  in the range, using the step value; for example, `2-9/3` is equivalent\n  to `2,5,8` (it starts with 2, then adds a step value to get 5 and 8).\n\nNon-numeric values are supported for months (`Jan-Dec`) and days of week\n(`Sun-Mon`) in any capitalization. Using `7` for `Sun` is supported too.\n\nBy default all functions are executed in a separate (forked) process.\nIf the execution within the same process is needed, then `setSchedule`\ncan be passed a third parameter (a table) to set `sameProc` value\nas one of the options: `{sameProc = true}`.\n\nSome of the caveats to be aware of:\n- using schedules relies on `OnServerHeartbeat` hook, so a version of\n  Redbean that provides that (v2.0.16+) should be used.\n- all schedule entries are interpreted as specified in GMT.\n- day-of-month and day-of-week are combined with an `and` (instead of an\n  `or`), so when *both* are specified, the job is executed when both are\n  satisfied (and not when both or either are specified). In other words,\n  `* * 13 * Fri` is only valid on Friday the 13th and not on any Friday.\n  If the `or` behavior is needed, then the schedule can be split into\n  two to handle each condition separately.\n- each function is executed in a process forked from the main process;\n  as noted above, set `sameProc = true` option to avoid forking.\n- some schedules can be executed twice if redbean instance is restarted\n  within the same minute, as the implementation is stateless.\n- day-of-week makes `Sun` available on both ends (as 0 or 7), so it's\n  better to use closed ranges in this case to avoid ambiguity.\n- all parsing errors (on incorrect formats or expressions) are reported\n  as fatal errors, but incorrect ranges are silently corrected into\n  proper ones, so using `6-100` for months is corrected to `6-12`.\n\n### Responses\n\nEach action handler generates some sort of response to send back to the\nclient. In addition to [strings](#actions), the application can return\nthe following results:\n- general responses (`serveResponse`),\n- templates (`serveContent`),\n- redirects (`serveRedirect`),\n- static assets (`serveAsset`),\n- errors (`serveError`),\n- directory index (`serveIndex`), and\n- internal redirects/resources (`servePath`).\n\nEach of these methods can be used as the return value from an action\nhandler. `serveAsset`, `servePath`, and `serveIndex` methods can also\nbe used as action handlers directly:\n\n```lua\nfm.setRoute(\"/static/*\", fm.serveAsset)\nfm.setRoute(\"/blog/\", fm.serveIndex(\"/new-blog/\"))\n```\n\nThe first route configures all existing assets to be served from\n`/static/*` location; the second route configures `/blog/` URL to return\nthe index (`index.lua` or `index.html` resource) from `/new-blog/`\ndirectory.\n\n#### Serving response\n\n`serveResponse(status[, headers][, body])`: sends an HTTP response using\nprovided `status`, `headers`, and `body` values.\n`headers` is an optional table populated with HTTP header name/value\npairs. If provided, this set of headers *removes all other headers* set\nearlier during the handling of the same request. Similar to the headers set\nusing the `request.headers` field, the names are *case-insensitive*, but\nprovided aliases for header names with dashes are *case-sensitive*:\n`{ContentType = \"foo\"}` is an alternative form for\n`{[\"Content-Type\"] = \"foo\"}`. `body` is an optional string.\n\nConsider the following example:\n\n```lua\nreturn fm.serveResponse(413, \"Payload Too Large\")\n```\n\nThis returns the 413 status code and sets the body of the returned\nmessage to `Payload Too Large` (with the header table not specified).\n\nIf only the status code needs to be set, the library provides a short\nform using the `serve###` syntax:\n\n```lua\nreturn fm.serve413\n```\n\nIt can also be used as the action handler itself:\n\n```lua\nfm.setRoute(fm.PUT\"/status\", fm.serve402)\n```\n\n#### Serving content\n\n`serveContent(name, parameters)` renders a template using provided\nparameters. `name` is a string that names the template (as set by a\n`setTemplate` call) and `parameters` is a table with template parameters\n(referenced as variables in the template).\n\n#### Serving redirect\n\n#### Serving static asset\n\n#### Serving error\n\n#### Serving directory index\n\n#### Serving path (internal redirect)\n\n### Database management\n\nFullmoon's function `makeStorage` is a way to connect to, and use a `SQLite3`\ndatabase. `makeStorage` returns a _database management_ table which contains\na rich set of functions to use with the connected database.\n\n### Running application\n\nThe `run` method executes the configured application. By default the server\nis launched listening on localhost and port 8080. Both of these\nvalues can be changed by passing `addr` and `port` options:\n\n```lua\nfm.run({addr = \"localhost\", port = 8080})\n```\n\nThe following options are supported; the default values are shown in\nparentheses and options marked with `mult` can set multiple values by\npassing a table:\n\n- `addr`: sets the address to listen on (mult)\n- `brand`: sets the `Server` header value (`\"redbean/v# fullmoon/v#\"`)\n- `cache`: configures `Cache-Control` and `Expires` headers (in seconds)\n  for all static assets served. A negative value disables the headers.\n  Zero value means no cache.\n- `certificate`: sets the TLS certificate value (mult)\n- `directory`: sets local directory to serve assets from in addition to\n  serving them from the archive within the executable itself (mult)\n- `headers`: sets default headers added to each response by passing a\n  table with HTTP header name/value pairs\n- `logMessages`: enables logging of response headers\n- `logBodies`: enables logging of request bodies (POST/PUT/etc.)\n- `logPath`: sets the log file path on the local file system\n- `pidPath`: sets the pid file path on the local file system\n- `port`: sets the port number to listen on (8080)\n- `privateKey`: sets the TLS private key value (mult)\n- `sslTicketLifetime`: sets the duration (in seconds) of the ssl ticket\n  (86400)\n- `trustedIp`: configures IP address to trust (mult).\n  This option accepts two values (IP and CIDR values), so they need to\n  be passed as a table within a table specifying multiple parameters:\n  `trustedIp = {{ParseIp(\"103.31.4.0\"), 22}, {ParseIp(\"104.16.0.0\"), 13}}`\n- `tokenBucket`: enables DDOS protection.\n  This option accepts zero to 5 values (passed as a table within a table);\n  an empty table can be passed to use default values: `tokenBucket = {{}}`\n\nEach option can accept a simple value (`port = 80`), a list of values\n(`port = {8080, 8081}`) or a list of parameters. Since both the list of\nvalues and the list of parameters are passed as tables, the list of values\ntakes precedence, so if a list of parameters needs to be passed to an option\n(like `trustedIp`), it has to be wrapped into a table:\n`trustedIp = {{ParseIp(\"103.31.4.0\"), 22}}`.\nIf only one parameter needs to be passed, then both\n`trustedIp = {ParseIp(\"103.31.4.0\")}` and `trustedIp = ParseIp(\"103.31.4.0\")`\ncan work.\n\nThe `key` and `certificate` string values can be populated using the\n`getAsset` method that can access both assets packaged within the\nwebserver archive and those stored in the file system.\n\nThere are also default cookie and session options that can be assigned\nusing `cookieOptions` and `sessionOptions` tables described below.\n\n#### Cookie options\n\n`cookieOptions` sets default options for all [cookie values](#cookies)\nassigned using `request.cookie.name = value` syntax (`{httponly=true,\nsamesite=\"Strict\"}`). It is still possible to overwrite default values\nusing table assignment: `request.cookie.name = {value, secure=false}`.\n\n#### Session options\n\n`sessionOptions` sets default options for the [session](#session) value\nassigned using `request.session.attribute = value` syntax\n(`{name=\"fullmoon_session\", hash=\"SHA256\", secret=true, format=\"lua\"}`).\nIf the `secret` value is set to `true`, then a random key is assigned\n*each time the server is started*; if *verbose* logging is enabled (by either\nadding `-v` option for Redbean or by using `fm.setLogLevel(fm.kLogVerbose)`\ncall), then a message is logged explaining how to apply the current random\nvalue to make it permanent.\n\nSetting this value to `false` or an empty string applies hashing without a\nsecret key.\n\n### Logging\n\n## Benchmark\n\nThe results shown are from runs in the same environment and on the same\nhardware as the [published redbean benchmark](https://redbean.dev/#benchmark)\n(thanks to [@jart](https://github.com/jart/) for executing the tests!).\nEven though these tests are using pre-1.5 version of redbean and 0.10 version\nof Fullmoon, the current versions of redbean/Fullmoon are expected to deliver\nsimilar performance.\n\nThe tests are using exactly the same code that is shown in the\n[introduction](#fullmoon) with one small change: using `{%= name %}` instead of\n`{%\u0026 name %}` in the template, which skips HTML escaping.\nThis code demonstrates routing, parameter handling and template processing.\n\n\u003cpre\u003e\n$ wrk -t 12 -c 120 http://127.0.0.1:8080/user/paul\nRunning 10s test @ http://127.0.0.1:8080/user/paul\n  12 threads and 120 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency   312.06us    4.39ms 207.16ms   99.85%\n    Req/Sec    32.48k     6.69k   71.37k    82.25%\n  3913229 requests in 10.10s, 783.71MB read\nRequests/sec: \u003cstrong\u003e387477.76\u003c/strong\u003e\nTransfer/sec:     77.60MB\n\u003c/pre\u003e\n\nThe following test is using the same configuration, but redbean is compiled\nwith `MODE=optlinux` option:\n\n\u003cpre\u003e\n$ wrk -t 12 -c 120 http://127.0.0.1:8080/user/paul\nRunning 10s test @ http://127.0.0.1:8080/user/paul\n  12 threads and 120 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency   346.31us    5.13ms 207.31ms   99.81%\n    Req/Sec    36.18k     6.70k   90.47k    80.92%\n  4359909 requests in 10.10s, 0.85GB read\nRequests/sec: \u003cstrong\u003e431684.80\u003c/strong\u003e\nTransfer/sec:     86.45MB\n\u003c/pre\u003e\n\nThe following two tests demonstrate the latency of the request handling by\nFullmoon and by redbean serving a static asset (no concurrency):\n\n\u003cpre\u003e\n$ wrk -t 1 -c 1 http://127.0.0.1:8080/user/paul\nRunning 10s test @ http://127.0.0.1:8080/user/paul\n  1 threads and 1 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency    \u003cstrong\u003e15.75us    7.64us 272.00us\u003c/strong\u003e   93.32%\n    Req/Sec    65.54k   589.15    66.58k    74.26%\n  658897 requests in 10.10s, 131.96MB read\nRequests/sec:  65241.45\nTransfer/sec:     13.07MB\n\u003c/pre\u003e\n\nThe following are the results from redbean itself on static compressed assets:\n\n\u003cpre\u003e\n$ wrk -H 'Accept-Encoding: gzip' -t 1 -c 1 htt://10.10.10.124:8080/tool/net/demo/index.html\nRunning 10s test @ htt://10.10.10.124:8080/tool/net/demo/index.html\n  1 threads and 1 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency     \u003cstrong\u003e7.40us    1.95us 252.00us\u003c/strong\u003e   97.05%\n    Req/Sec   129.66k     3.20k  135.98k    64.36%\n  1302424 requests in 10.10s, 1.01GB read\nRequests/sec: 128963.75\nTransfer/sec:    102.70MB\n\u003c/pre\u003e\n\n### 3-rd party benchmarks\n\n[Berwyn Hoyt](https://berwyn.hashnode.dev/) included Redbean results in his\n[lua server benchmark](https://github.com/berwynhoyt/lua-server-benchmark) results,\nwhich shows redbean outperforming a comparable nginx/openresty implementation.\n\n## Status\n\nHighly experimental with everything being subject to change.\n\nThe core components are more stable and have been rarely updated since v0.3.\nUsually, the documented interfaces are much more stable than undocumented\nones. Those commits that modified some of the interfaces are marked with\n`COMPAT` label, so can be easily identified to review for any compatibility\nissues.\n\nSome of the obsolete methods are still present (with a warning logged when\nused) to be removed later.\n\n## Author\n\nPaul Kulchenko (paul@zerobrane.com)\n\n## License\n\nSee [LICENSE](LICENSE).\n","funding_links":[],"categories":["Lua","Fullmoon"],"sub_categories":["Blog Posts"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpkulchenko%2Ffullmoon","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpkulchenko%2Ffullmoon","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpkulchenko%2Ffullmoon/lists"}