redbean
single-file distributable web server

redbean makes it possible to share web applications that run offline as a single-file αcτµαlly pδrταblε εxεcµταblε zip archive which contains your assets. All you need to do is download the redbean.com program below, change the filename to .zip, add your content in a zip tool like InfoZIP, and change the extension back to .com.

redbean can serve 1 million+ gzip encoded responses per second on a cheap personal computer. That performance is thanks to zip and gzip using the same compression format, which enables kernelspace copies. Another reason redbean goes fast is that it's a tiny static binary, which makes fork memory paging nearly free.

redbean is also easy to modify to suit your own needs. The program itself is written as a single .c file. It embeds the Lua programming language which lets you write dynamic pages.

download  

alternative builds

features

installation

curl https://justine.lol/redbean/redbean-latest.com >redbean.com
curl https://justine.lol/redbean/redbean-latest.com.dbg >redbean.com.dbg
chmod +x redbean.com
./redbean.com -v
bash -c './redbean.com -v'  # zsh/fish workaround (we upstreamed patches!)

usage

echo 'Write("<b>hello</b>")' >hello.lua
echo '<b>hello</b>' >hello.html
zip redbean.com hello.lua
zip redbean.com hello.html
./redbean.com -vv
curl -v http://127.0.0.1:8080/index.html

source build

git clone https://github.com/jart/cosmopolitan && cd cosmopolitan
make -j8 o//tool/net/redbean.com
o//tool/net/redbean.com -vv

details

Assets can be listed by running the following command:

unzip -vl redbean.com        # lists files

Which is also implemented as the default index page:

Assets can be added to the zip archive as follows:

zip redbean.com index.html           # adds file
zip -r redbean.com mirrored-website  # adds directory

By default, anything you add to the archive gets compressed. Sometimes you don't want that to happen. A good example is video files. The web browser will want to send HTTP range requests to seek in the video, in which case redbean requires that the asset be uncompressed.

zip -0 redbean.com video.mp4  # adds file without compression

You can run redbean interactively in your terminal as follows:

redbean.com -vv
CTRL-C                        # 1x: graceful shutdown
CTRL-C                        # 2x: forceful shutdown

The index.lua and index.html names are special since they're used to automatically figure out how to serve directories. Such files can appear in any directory, however the root directory is special. The default action for / is to show a listing page showing the contents of your zip central directory.

flags

-h help
-v verbosity [repeatable]
-d daemonize
-u uniprocess
-m log messages
-b log message bodies
-k encourage keep-alive
-z print port [useful with -p0 for automation]
-D DIR serve assets from local filesystem directory [repeatable]
-c INT cache seconds
-r /X=/Y redirect /X to /Y [repeatable]
-R /X=/Y rewrite /X to /Y [repeatable]
-l ADDR listen ip [default 0.0.0.0]
-p PORT listen port [default 8080]
-L PATH log file location
-P PATH pid file location
-U INT daemon set user id
-G INT daemon set group id
-B STR changes brand

lua server pages

Any files with the extension .lua will be dynamically served by redbean. Here's the simplest possible example:

Write('<b>Hello World</b>')

The request handler above should perform at 700,000 responses per second, without any sort of caching. In practice though, it ends up being closer to 300,000 responses per second, since once your response reaches 100 bytes redbean will do things like apply gzip compression automatically.

Here's an example of a more typical workflow for Lua Server Pages using the redbean API:

SetStatus(200)
SetHeader('Content-Type', 'text/plain; charset=utf-8')
Write('<p>Hello ')
Write(EscapeHtml(GetParam('name')))

We didn't need the first two lines in the previous example, because they're implied by redbean automatically if you don't set them. Responses are also buffered until the script finishes executing. That enables redbean to make HTTP as easy as possible. In the future, API capabilities will be expanded to make possible things like websockets.

redbean embeds the Lua standard library. You can use packages such as io to persist and share state across requests and connections. In the future, SQLite might be embedded too.

Your Lua interpreter begins its life in the main process at startup in the .init.lua, which is likely where you'll want to perform all your expensive one-time operations like importing modules. Then, as requests roll in, isolated processes are cloned from the blueprint you created.

special paths

/
redbean will generate a zip central directory listing for this page, and this page only, but only if there isn't an /index.lua or /index.html file defined.
/tool/net/.init.lua
This script is run once in the main process at startup. This lets you modify the state of the Lua interpreter before connection processes are forked off. For example, it's a good idea to do expensive one-time computations here. You can also use this file to call the ProgramFOO() functions below. The init module load happens after redbean's arguments and zip assets have been parsed, but before calling functions like socket() and fork(). Note that this path is a hidden file so that it can't be unintentionally run by the network client.
/tool/net/.reload.lua
This script is run from the main process when SIGHUP is received. This only applies to redbean when running in daemon mode. Any changes that are made to the Lua interpreter state will be inherited by future forked connection processes. Note that this path is a hidden file so that it can't be unintentionally run by the network client.
/tool/net/redbean.png
If it exists, it'll be used as the / listing page icon, embedded as a base64 URI.

globals

argv: array[str]
Array of command line arguments, excluding those parsed by getopt() in the C code, which stops parsing at the first non-hyphenated arg. In some cases you can use the magic -- argument to delimit C from Lua arguments.

constants

kLogDebug
Integer for debug logging level. See Log.
kLogVerbose
Integer for verbose logging level, which is less than kLogDebug. See Log.
kLogInfo
Integer for info logging level, which is less than kLogVerbose. See Log.
kLogWarn
Integer for warn logging level, which is less than kLogVerbose. See Log.
kLogError
Integer for error logging level, which is less than kLogWarn. See Log.
kLogFatal
Integer for fatal logging level, which is less than kLogError. See Log.

functions

DecodeBase64(ascii:str) → binary:str
Turns ASCII into binary, in a permissive way that ignores characters outside the base64 alphabet, such as whitespace. See decodebase64.c.
EncodeBase64(binary:str) → ascii:str
Turns binary into ASCII. This can be used to create HTML data: URIs that do things like embed a PNG file in a web page. See encodebase64.c.
EscapeFragment(str) → str
Escapes URI #fragment. The allowed characters are -/?.~_@:!$&'()*+,;=0-9A-Za-z and everything else gets %XX encoded. Please note that '& can still break HTML and that '() can still break CSS URLs. This function is charset agnostic and will not canonicalize overlong encodings. It is assumed that a UTF-8 string will be supplied. See escapeurlfragment.c.
EscapeHtml(str) → str
Escapes HTML entities: The set of entities is &><"' which become &amp;&gt;&lt;&quot;&#39;. This function is charset agnostic and will not canonicalize overlong encodings. It is assumed that a UTF-8 string will be supplied. See escapehtml.c.
EscapeLiteral(str) → str
Escapes JavaScript or JSON string literal content. The caller is responsible for adding the surrounding quotation marks. This implementation \uxxxx sequences for all non-ASCII sequences. HTML entities are also encoded, so the output doesn't need EscapeHtml. This function assumes UTF-8 input. Overlong encodings are canonicalized. Invalid input sequences are assumed to be ISO-8859-1. The output is UTF-16 since that's what JavaScript uses. For example, some individual codepoints such as emoji characters will encode as multiple \uxxxx sequences. Ints that are impossible to encode as UTF-16 are substituted with the \xFFFD replacement character. See escapejsstringliteral.c.
EscapeParam(str) → str
Escapes URI parameter name or value. The allowed characters are -.*_0-9A-Za-z and everything else gets %XX encoded. This function is charset agnostic and will not canonicalize overlong encodings. It is assumed that a UTF-8 string will be supplied. See escapeurlparam.c.
EscapePath(str) → str
Escapes URI path. This is the same as EscapeSegment except slash is allowed. The allowed characters are -.~_@:!$&'()*+,;=0-9A-Za-z/ and everything else gets %XX encoded. Please note that '& can still break HTML, so the output may need EscapeHtml too. Also note that '() can still break CSS URLs. This function is charset agnostic and will not canonicalize overlong encodings. It is assumed that a UTF-8 string will be supplied. See escapeurlpath.c.
EscapeSegment(str) → str
Escapes URI path segment. This is the same as EscapePath except slash isn't allowed. The allowed characters are -.~_@:!$&'()*+,;=0-9A-Za-z and everything else gets %XX encoded. Please note that '& can still break HTML, so the output may need EscapeHtml too. Also note that '() can still break CSS URLs. This function is charset agnostic and will not canonicalize overlong encodings. It is assumed that a UTF-8 string will be supplied. See escapeurlpathsegment.c.
FormatHttpDateTime(seconds:int) → rfc1123:str
Converts UNIX timestamp to an RFC1123 string that looks like this: Mon, 29 Mar 2021 15:37:13 GMT. See formathttpdatetime.c.
GetClientAddr() → str
Returns client socket address, e.g. "1.2.3.4:31337"
GetDate() → seconds:int
Returns date associated with request that's used to generate the Date header, which is now, give or take a second. The returned value is a UNIX timestamp.
GetHeader(name:str) → value:str
Returns HTTP header. name is case-insensitive. The header value is returned as a canonical UTF-8 string, with leading and trailing whitespace trimmed, which was decoded from ISO-8859-1. The returned value might contain separators, C0, C1, and even NUL characters. It is the responsibility of the caller to impose restrictions on validity if they're desired, since each specific header has its own rules for serialization. In the event that the client suplies raw UTF-8 in the HTTP message headers, the original UTF-8 sequence can be losslessly restored by counter-intuitively recoding the returned string back to Latin1.
GetHeaders() → table[name:str, value:str]
Returns HTTP headers as dictionary mapping header key strings to their UTF-8 decoded values. The ordering of headers from the request message is not preserved. Headers are not multimapped, therefore if a field name occurs twice in a message, the latter will overwrite the prior. Standard header names (e.g. Content-Type) will always have their casing normalized to what the RFC uses. Non-standard headers might have whatever casing the user specifies. This function is suboptimally designed and therefore likely to change in the future. Please consider using GetHeader instead if possible.
GetLogLevel() → int
Returns logger verbosity level. Likely return values are kLogDebug > kLogVerbose > kLogInfo > kLogWarn > kLogError > kLogFatal.
GetMethod() → str
Returns HTTP method as normalized uppercase str. Currently the only methods supported are GET, HEAD, POST, PUT, DELETE, and OPTIONS. Everything else is rejected by the transport layer, using either a 401 or 501 Not Implemented response.
GetParam(name:str) → value:str
Returns first value associated with name. name is handled in a case-sensitive manner. This function checks Request-URI parameters first. Then it checks application/x-www-form-urlencoded from the message body, if it exists, which is common for HTML forms sending POST requests. If a parameter is supplied matching name that has no value, e.g. foo in ?foo&bar=value, then the returned value will be nil, whereas for ?foo=&bar=value it would be "". To differentiate between no-equal and absent, use the HasParam function. The returned value is decoded from ISO-8859-1 (only in the case of Request-URI) and we assume that percent-encoded characters were supplied by the client as UTF-8 sequences, which are returned exactly as the client supplied them, and may therefore may contain overlong sequences, control codes, NUL characters, and even numbers which have been banned by the IETF. It is the responsibility of the caller to impose further restrictions on validity, if they're desired.
GetParams() → array[array[str]]
Returns name=value parameters from Request-URI and application/x-www-form-urlencoded message body in the order they were received. This may contain duplicates. The inner array will have either one or two items, depending on whether or not the equals sign was used.
GetPath() → str
Returns the Request-URI path. This is guaranteed to begin with "/". It is further guaranteed that no "//" or "/." exists in the path. The returned value is returned as a UTF-8 string which was decoded from ISO-8859-1. We assume that percent-encoded characters were supplied by the client as UTF-8 sequences, which are returned exactly as the client supplied them, and may therefore may contain overlong sequences, control codes, NUL characters, and even numbers which have been banned by the IETF. redbean takes those things into consideration when performing path safety checks. It is the responsibility of the caller to impose further restrictions on validity, if they're desired.
GetPayload() → str
Returns the request message payload, or empty string if there isn't one.
GetServerAddr() → str
Returns address to which listening server socket is bound, e.g. "1.2.3.4:31337". If -p 0 was supplied as the listening port, then the port in this string will be whatever number the operating system assigned.
GetUri() → str
Returns the Request-URI as a UTF-8 string, that hasn't been percent decoded. For the parsed Request-URI see GetPath and GetParam.
GetVersion() → int
Returns the request HTTP protocol version, which can be 9 for HTTP/0.9, 100 for HTTP/1.0, or 101 for HTTP/1.1.
GetZipPaths() → array[str]
Returns paths of all assets in the zip central directory, prefixed by a slash.
HasParam(name:str) → bool
Returns true if parameter with name was supplied in either the Request-URI or an application/x-www-form-urlencoded message body.
HidePath(prefix:str)
Programs redbean / listing page to not display any paths beginning with prefix. This function should only be called from .init.lua.
LoadAsset(path:str) → str
Returns contents of file as string. The asset may be sourced from either the zip (decompressed) or the local filesystem if the -D flag was used. If slurping large file into memory is a concern, then consider using ServeAsset which can serve directly off disk.
Log(level:int, message:str)
Emits message string to log, if level is less than or equal to GetLogLevel. If redbean is running in interactive mode, then this will log to the console. If redbean is running as a daemon or the -L LOGFILE flag is passed, then this will log to the file. Reasonable values for level are kLogDebug > kLogVerbose > kLogInfo > kLogWarn > kLogError > kLogFatal. The logger emits timestamps in the local timezone with microsecond precision. If log entries are emitted more frequently than once per second, then the log entry will display a delta timestamp, showing how much time has elapsed since the previous log entry. This behavior is useful for quickly measuring how long various portions of your code take to execute.
ParseHttpDateTime(rfc1123:str) → seconds:int
Converts RFC1123 string that looks like this: Mon, 29 Mar 2021 15:37:13 GMT to a UNIX timestamp. See parsehttpdatetime.c.
ProgramBrand(str)
Changes HTTP Server header, as well as the <h1> title on the / listing page. The brand string needs to be a UTF-8 value that's encodable as ISO-8859-1. If the brand is changed to something other than redbean, then the promotional links will be removed from the listing page too. This function should only be called from .init.lua.
ProgramCache(seconds:int)
Configures Cache-Control and Expires header generation for static asset serving. A negative value will disable the headers. Zero means don't cache. Greater than zero asks public proxies and browsers to cache for a given number of seconds. This should only be called from .init.lua.
ProgramPort(int)
Hard-codes the port number on which to listen, which can be any number in the range 1..65535, or alternatively 0 to ask the operating system to choose a port, which may be revealed later on by GetServerAddr or the -z flag to stdout.
ProgramRedirect(code:int, src:str, location:str)
Configures fallback routing for paths which would otherwise return 404 Not Found. If code is 0 then the path is rewritten internally as an accelerated redirect. If code is 301, 302, 307, or 308 then a redirect response will be sent to the client. This should only be called from .init.lua.
ServeAsset(path:str)
Instructs redbean to serve static asset at path. This function causes what would normally happen outside a dynamic handler to happen. The asset can be sourced from either the zip or local filesystem if -D is used. This function is mutually exclusive with SetStatus and ServeError.
ServeError(code:int[, reason:str])
Instructs redbean to serve a boilerplate error page. This takes care of logging the error, setting the reason phrase, and adding a payload. This function is mutually exclusive with SetStatus and ServeAsset.
SetHeader(name:str, value:str)
Appends HTTP header to response header buffer. name is case-insensitive and restricted to non-space ASCII. value is a UTF-8 string that must be encodable as ISO-8859-1. Leading and trailing whitespace is trimmed automatically. Overlong characters are canonicalized. C0 and C1 control codes are forbidden, with the exception of tab. This function automatically calls SetStatus(200, "OK") if a status has not yet been set. The header buffer is independent of the payload buffer. Neither are written to the wire until the Lua Server Page has finished executing. This function disallows the setting of certain headers such as Date and Content-Range which are abstracted by the transport layer. In such cases, consider calling ServeAsset.
SetLogLevel(level:int)
Sets logger verbosity. Reasonable values for level are kLogDebug > kLogVerbose > kLogInfo > kLogWarn > kLogError > kLogFatal.
SetStatus(code:int[, reason:str])
Starts an HTTP response, specifying the parameters on its first line. reason is optional since redbean can fill in the appropriate text for well-known magic numbers, e.g. 200, 404, etc. This method will reset the response and is therefore mutually exclusive with ServeAsset and ServeError. If a status setting function isn't called, then the default behavior is to send 200 OK.
Write(data:str)
Appends data to HTTP response payload buffer. This is buffered independently of headers.
bsf(x:int) → int
Returns position of first bit set. Passing 0 will raise an error. Same as the Intel x86 instruction BSF.
bsr(x:int) → int
Returns binary logarithm of 𝑥. Passing 0 will raise an error. Same as the Intel x86 instruction BSR.
crc32(initial:int, data:str) → int
Computes Phil Katz CRC-32 used by zip/zlib/gzip/etc.
crc32c(initial:int, data:str) → int
Computes 32-bit Castagnoli Cyclic Redundancy Check.
popcnt(x:int) → int
Returns number of bits set in integer.

operations

You can have redbean run as a daemon by doing the following:

redbean.com -vv -d -L redbean.log -P redbean.pid
kill -TERM $(cat redbean.pid) # 1x: graceful shutdown
kill -TERM $(cat redbean.pid) # 2x: forceful shutdown

It's possible to modify global interpreter state later on in the server's lifecycle. When running in daemon mode, using kill -HUP $(pidof redbean.com) will instruct redbean to run the code in .reload.lua from the main process, will will be lazily propagated to client connections.

redbean will grow to whatever number of processes your system limits and tcp stack configuration allow. Once functions like fork() start to fail, redbean will enter meltdown mode where it begins interrupting processes so that idle connections can close as quickly as possible, while sending 503 Service Unavailable responses from the main process until congestion subsides. This means that redbean can be a good citizen in large systems where things like quotas and intelligent load balancing apply.

redbean works best right now for use cases that involve a lot of small messages. For example, pipelining is supported which basically gives us HTTP/2-like performance using just HTTP/1.1. On the other hand, redbean currently doesn't have APIs for handling things like large file uploads. There's a 64kb limit on request message size, so that processes can stay tiny. redbean also won't service hidden files, directory traversalals, or unreasonably fragmented messages. Those few restrictions aside, redbean generally aims to follow Postel's maxim in the sense that it's liberal in what it accepts but conservative in what it sends.

redbean is designed to be an unsecure webserver. That means redbean is neither secure nor insecure. How much security you want to bolt on is entirely up to you. For example, HTTPS support can be trivially added by having TCP connections flow through a program like stunnel. redbean can also be compiled fully hardened with ASAN memory safety, since the Cosmopolitan C library has the only open source implementation of the Address Sanitizer runtime that's designed for production use.

benchmark

# Note: Benchmarked on an Intel® Core™ i9-9900 CPU
$ wrk -H 'Accept-Encoding: gzip' -t 12 -c 120 \
  http://127.0.0.1:8080/tool/net/redbean.html
Running 10s test @ http://127.0.0.1:8080/tool/net/redbean.html
  12 threads and 120 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    18.27ms  131.81ms   1.71s    97.60%
    Req/Sec    85.17k    10.73k  144.05k    82.75%
  10221627 requests in 10.10s, 7.53GB read
  Socket errors: connect 0, read 0, write 0, timeout 13
Requests/sec: 1012088.67
Transfer/sec:    763.48MB

Thanks for using redbean! If you like what you've seen and want to encourage more, then consider becoming a GitHub sponsor. PayPal donations are also accepted at jtunney@gmail.com.

see also

redbean hacker news thread (1,998 upvotes)
αcτµαlly pδrταblε εxεcµταblε (671 upvotes)
justine's web page