June 16th, 2022 @ justine's web page
redbean is a webserver in a zip executable that runs on six operating systems. The basic idea is if you want to build a web app that runs anywhere, then you download the redbean.com file, put your .html and .lua files inside it using the zip command, and then you've got a hermetic app you can deploy and share. I introduced this web server about a year ago on Hacker News, where it became the third most upvoted hobby project of all time.
Over the last year, we've turned redbean into more than a hobby project. It's grown to become a 1.9mb file that self-hosts a Lua + SQLite development stack. There's builtin MbedTLS support. It does sandboxing. It has argon2 password hashing. It can geolocate IPs with MaxMind. It has a readline-like REPL. You can use it as a Lua shebang interpreter. It has an easy-mode API and a Fullmoon web framework for high-level development. It also has a hard-mode API that provides direct access to Cosmopolitan Libc Unix system calls. You can use Unix on Windows, or even from JavaScript too, since redbean is great for spinning up a web GUI in Chrome via localhost. You can also use redbean as a production web server on the public-facing internet. I stand by that statement since I eat my own dogfood. redbean hosts all my websites now, including this one (justine.lol). There's no need for a proxy like nginx; redbean is vertically integrated.
Your redbean supports x86-64 Linux, MacOS, FreeBSD, NetBSD, or OpenBSD. Visit redbean.dev/2.0.html to download the release binary. redbean is permissively licensed under the ISC license. The source code is available on GitHub. Instructions for building redbean from source on Linux are available at redbean.dev.
curl https://redbean.dev/redbean-demo-2.0.19.com >redbean.com chmod +x redbean.com ./redbean.com -v
PowerShell users can use:
wget -O redbean.com https://redbean.dev/redbean-demo-2.0.19.com ./redbean.com -v
redbean 2.0 uses the new
APE Loader which lets your
redbean execute without having to self-modify its header. What happens
instead is the ape
command will mmap()
your
redbean into memory. It's just as fast. If APE isn't installed on a
system, then the shell script header will extract it automatically.
There's shebang support and binfmt_misc support on Linux too. These
changes will have an enabling impact for distros and build systems, who
had difficulties packaging and distributing APE software. For users who
want the original behavior, an --assimilate
flag is
introduced that will turn your redbean into the platform-local ELF or
Mach-O format.
In addition to helping distributors, the redbean 2.0 release helps
self-distributors too. You can now place a
.args
file in your redbean that specifies the default CLI arguments. This
can help make it easier to white-label redbean, especially if it's being
used as an alternative to the standard Lua interpreter command.
redbean 2.0 introduces a Read Eval Print Loop or REPL for short. It's built on the bestline library, since it provides near parity with GNU Readline in terms of features, except it's licensed MIT instead of LGPL, so there's no dynamic linking requirement. redbean can't dynamically link things, since then it wouldn't be a single file. I put a lot of work into creating bestline, a linenoise fork, for that very reason. Here's a short screencast of the redbean repl being used.
Since the video goes by quickly, here's an explanation of what happened.
The video starts by running
redbean-demo.com -Zv
in the terminal. The -v
flag increases the logging verbosity. The -Z
flag enables
system call tracing, so you can monitor all the powerful things you're
doing with the new UNIX module. It works similar to
the --strace
flag that I blogged about last week under
Logging C Functions. Once you see
the >:
prompt, your redbean REPL is ready to receive
commands.
>: Benchmark(unix.clock_gettime) 125 389 594 1
You can call most of the redbean Lua APIs from this shell. If you've
defined global variables and functions in the zip file
.init.lua
then you can call those functions too. The
example shown above in the video is of microbenchmarking. In the video
you'll notice unix.clock_gettime()
takes 125 nanoseconds to run. It's helpful to be able to run one-off
live experiments like this, since when I made that video for the
sponsors-only pre-release, it helped me realize I could use the Linux
vDSO to make unix.clock_gettime()
10x faster!
>: Benchmark(unix.clock_gettime) 17 53 88 1
So 17 nanoseconds is now the performance you can expect in 2.0. You'll also see me computing binary numbers on the command line, like SHA-256.
>: Sha256('hello') ",\xf2M\xba_\xb0\xa3\x0e&\xe8;*\xc5\xb9\xe2\x9e\x1b\x16\x1e\\x1f\xa7B^s\x043b\x93\x8b\x98$"
redbean embraces and extends Lua in many ways. For example, the normal
Lua command line will print ,�M�_��&�;*Ź�\x1f�B^s3b���$
for the
binary value above. I figured if a REPL's input is code, then its output
must be code too, since that's how LISP does things. Anything
your redbean REPL outputs, can usually be copy and pasted back into your
scripts.
The next thing you'll see in the video is the new tab completion
feature. Like bash, you can press <tab><tab>
to
see a listing of all available global functions and objects. If you
press unix.<tab><tab>
then you'll see all the
objects and functions available in the unix module.
Users of GNU Emacs will be delighted to hear that your redbean REPL
supports nearly all the common GNU-style keyboard chording shortcuts,
including CTRL-R
for reverse search. See the
keyboard reference for
further details.
One of the use cases for having a REPL on a live web server, is you can monkey patch code while your server is running. redbean is a forking web server. That means the main process behaves like a master template from which worker processes are cloned. Therefore, anything you change in the REPL will propagate lazily into client connections, as new ones roll in, without impacting the connections currently active.
redbean 2.0 introduces optional system call logging. The last thing
you'll notice in the REPL video above (but can't actually see) is I fire
off a request to redbean from curl. Since we passed -Z
we
get a nice system call trace. This logging can all be happening
seamlessly while you're typing on the REPL.
SYS 15987 7'977 close(4) → 0 SYS 15987 14'055 close(5) → 0 SYS 15987 191'846 read(6, [u"GET /tool/net/demo/index.html HTTP/1.1♪◙"...], 65'536) → 137 I2022-06-13T16:37:34+000400:tool/net/redbean.c:5798:redbean-demo:15987] (req) received 127.0.0.1:57542 HTTP11 GET http://127.0.0.1:8080/tool/net/demo/index.html "" "curl/7.79.1" SYS 15987 308'231 sigaction(SIGINT, {.sa_handler=0x41da5e, .sa_flags=0x80000000, .sa_mask={}}, [{.sa_handler=0x419305, .sa_flags=0x4000000, .sa_mask={}}]) → 0 V2022-06-13T16:37:34+000063:tool/net/redbean.c:6004:redbean-demo:15987] (rsp) 200 OK SYS 15987 341'459 sigaction(SIGINT, {.sa_handler=0x419305, .sa_flags=0x4000000, .sa_mask={}}, [NULL]) → 0 SYS 15987 352'506 writev(6, {{u"HTTP/1.1 200 OK♪◙Content-Type: text/html"..., 306}, {u"▼ï◘ ♥", 10}, {u"àRMN▌0►▐τ¶So╪└ïTuüP^╢E]s☺█↓↕âc╗€q≤┬Ü♂!⌡]"..., 511}, {u"╠↑♦φü♥ ", 8}}, 4) → 835 SYS 15987 679'711 read(6, [u""], 65'536) → 0 SYS 15987 683'584 _Exit(0) >:
Here we see the redbean worker process closing the server socket file descriptors. The nicest thing about using fork() as the basis of a web server, is it creates a very safe level of isolation between clients. For instance, if a redbean worker processes dies, then it'll die in a safe space that won't impact the server as a whole. redbean will also do things like wipe SSL keys from memory after fork(), so a compromised worker won't be able to read them. We also see some sigaction() overhead in the trace from the video. That's only needed since redbean is running in a mode where the REPL is active.
Once the worker process has established itself, the thing that makes redbean so lightning fast is that it only needs a single system call to serve each response message. That system call is writev(), which helps us avoid having to copy buffers. In fact, it's an even nicer paradigm than sendfile() since you'll notice the writev() call has two buffers. The first is the headers we had to generate. The second is compressed zip executable content, which is a zero copy operation that happens in kernelspace, because we used mmap() to load it into memory.
When I spoke about redbean at SpeakEasy JS [YouTube Video] last year, I loved bragging about how redbean, a supposedly slow forking web server, benchmarked at a million qps on my PC when nginx could only do half that. Is it possible to improve upon perfection? It turns out, I ran wrk again for our 2.0 release, and redbean did 1.1 million qps.
# Note: Benchmarked on an Intel® Core™ i9-9900 CPU # Note: Use redbean-demo.com -s $ wrk --latency -t 10000 -c 10000 -H 'Accept-Encoding: gzip' \ http://127.0.0.1:8080/tool/net/demo/index.html Running 10s test @ http://127.0.0.1:8080/tool/net/demo/index.html 10000 threads and 10000 connections Thread Stats Avg Stdev Max +/- Stdev Latency 10.44ms 46.76ms 1.76s 98.41% Req/Sec 189.08 259.45 39.10k 98.67% Latency Distribution 50% 5.68ms 75% 6.87ms 90% 8.77ms 99% 197.91ms 4327728 requests in 3.72s, 3.37GB read Socket errors: connect 0, read 5, write 0, timeout 2 Requests/sec: 1163062.91 Transfer/sec: 0.90GB
redbean 2.0 introduces a new unix module implemented in tool/net/lunix.c. I love the unix operating system and I hope you will too. In fact, I love it so much, that I wrote Lua wrappers for all of the following system interfaces.
exit, fork, read, write, open, close, stat, fstat, lseek, access, pipe, dup, poll, execve, environ, link, unlink, symlink, mkdir, makedirs, chdir, rmdir, sigaction, setitimer, nanosleep, clock_gettime, gmtime, localtime, opendir, fdopendir, fsync, fdatasync, getpid, getppid, getsockname, getsockopt, getuid, kill, listen, major, minor, pledge, raise, readlink, accept, realpath, recv, bind, recvfrom, rename, chmod, chown, send, chroot, sendto, setgid, commandv, setpgid, connect, setpgrp, setresgid, setresuid, setrlimit, , setsid, fcntl, setsockopt, setuid, shutdown, sigprocmask, sigsuspend, ftruncate, siocgifconf, getcwd, socket, getegid, socketpair, geteuid, getgid, strsignal, gethostname, getpeername, sync, getpgid, syslog, getpgrp, truncate, umask, getrlimit, wait, getrusage, getsid
We've put a lot of work into documenting these functions and making sure they work on all supported platforms, including Windows, where we've sought to model the Linux kernel behaviors as accurately as possible. Our goal has been making sure doing anything from Lua will be possible rather than impossible. As such, this module abstracts very little and is nearly identical to the APIs provided for the C language.
fd = assert(unix.open("hello.txt")) st = assert(unix.fstat(fd)) Log(kLogInfo, 'hello.txt is %d bytes in size' % {st:size()}) unix.close(fd)
In many cases, Lua makes using the C syscall functions much easier. For
example, you'll notice we didn't need to pass unix.O_RDONLY
to open()
since Lua function calls can have default
parameters. You'll also notice the object oriented access to
the struct stat
.
The unix module implements this with a user data object metatable. That
means it goes 10x faster than if we were to construct a table, because
Cosmopolitan's stat has 16 fields! Thanks to Lua udata objects, we can
return those without needing to copy them.
Write('<ul>') for name, kind, ino, off in assert(unix.opendir('/etc')) do if kind == unix.DT_REG then Write('<li>%s' % {EscapeHtml(name)}) end end Write('</ul>')
The dirstream interface is particularly nice, since it's one of the few
system call interfaces that the C language explicitly defines as
object-oriented. What opendir()
does is return an object
that can be __call
'd to receive each directory entry, and
as such, Lua lets it integrate neatly and automatically with all its
lovely language features.
Thanks to the system call interface designed at UC Berkeley, redbean's new unix module means that redbean can now be much more than just an HTTP server. For example, you can fork() off a daemon:
if assert(unix.fork()) > 0 then return end unix.close(GetClientFd()) unix.setsid() if assert(unix.fork()) > 0 then unix.exit(0) end unix.close(1) assert(unix.open('/var/log/daemon.log', unix.O_WRONLY | unix.O_CREAT | unix.O_TRUNC, 0600)) unix.dup(1, 2) assert(unix.setgid(1000)) assert(unix.setuid(1000))
That listens for client connections on another port:
sock = assert(unix.socket()) -- create ipv4 tcp socket assert(unix.bind(sock)) -- all interfaces ephemeral port ip, port = assert(unix.getsockname(sock)) print("listening on ip", FormatIp(ip), "port", port) assert(unix.listen(sock)) while true do client, clientip, clientport = assert(unix.accept(sock)) print("got client ip", FormatIp(clientip), "port", clientport) unix.close(client) end
That port can be any one of your choosing: smtp, irc, name it.
There's also a few demo scripts included in the redbean-demo.com binary release file. You can also read the example code on GitHub. The unix-webserver.lua demo is particularly nice, since it shows how you can create a web server within your web server.
Let's say you're downloading Lua extensions for redbean off the web and you don't want them poking around in /etc like in the example above. redbean provides a sandboxing solution for this, that's available on Linux and OpenBSD. It works by exposing the OpenBSD unix.pledge() system call, which we've polyfilled for Linux using SECCOMP BPF.
function OnWorkerStart() unix.pledge('stdio') end
If you do something like that, to reduce privileges on forked workers,
then unix.opendir()
will return unix.EPERM
rather than exposing your /etc
folder. OpenBSD will just
kill the process whenever a sandbox violation occurs, but we've chosen
to be more forgiving with Linux since Lua code that behaves badly should
have the opportunity to react to the error by mending its wicked ways.
For example, many of the unix demo scripts included in redbean won't run
if you're using sandboxing. So they'll print a helpful error if the
system call reports a permission error.
Another important security feature is
unix.setrlimit()
which has a usage example in the
binarytrees.lua
benchmark game. The form takes an exponential parameter for how complex
the game should be. If that number is high, like 25 rather than 18, then
the Lua script will allocate gigabytes of memory and use a ton of CPU.
The setrlimit()
function lets you place a limit on how many
resources a connection is allowed to use, before it either receives a
notification signal or gets killed.
redbean 2.0 introduces support for reading MaxMind's free GeoLite2 IP and ASN databases. It works locally in a self-hosted way, but you have to sign up on their website to download the necessary data files. Once you have them, your redbean will gain the superpower of knowing where everyone lives.
geodb = maxmind.open('/usr/local/share/maxmind/GeoLite2-City.mmdb') geo = geodb:lookup(GetRemoteAddr()) if geo then Write('hello citizen of %s!' % {geo:get('country', 'names', 'en')}) end
This can be an invaluable tool for defending your redbean from the bad guys on the web. For example, let's say you want to have a comments section on your blog. One thing you could do to reduce abuse, while remaining completely open, is to simply use MaxMind to check that the visitor is using their home internet connection.
if geo:get('location', 'accuracy_radius') >= 100 then SetStatus(403) Write('you can only post comments from your home internet connection') return end
IPs that come from a data center usually have an accuracy of 1000km. So if the accuracy is less than 100, then the visitor is much less likely to be a robot or a bad guy concealing their identity. This is by no means a perfect criterion for fighting abuse, but it can keep a lot of unsavory traffic at bay in a scrappy way that doesn't require FAANG accounts. It works because the list of bad guys who've hacked a fleet of Comcast home routers is much shorter than the list of bad guys who just rent cheap virtual servers from the usual suspects in the cloud.
asndb = maxmind.open('/usr/local/share/maxmind/GeoLite2-ASN.mmdb') as = asndb:lookup(GetRemoteAddr()) asname = as:get('autonomous_system_organization')
Logging the autonomous system name will let you know who the usual suspects are. When bad guys send unreasonable amounts of traffic, they love doing it using a fleet of seemingly unrelated IPs from countries around the world. But one of their weaknesses is that, like most devs, they're usually wedded to just one platform. Knowing what the platform is can be an invaluable tool in drawing the dots and filing complaints accordingly.
redbean 2.0 introduces enhancements to the Lua language that are intended to help C/C++ and Python developers feel more comfortable.
"hello %s" % {"world"}
instead of
string.format("hello %s", "world")
.
0644 == 420
is the case in redbean, whereas in upstream Lua
0644 == 644
would be the case. There's also a new
oct function.
0b1010 == 10
is the case in redbean, whereas in upstream Lua
0b1010
would result in an error. There's also a new
bin function.
"\e"
is the same as "\x1b"
.
redbean 2.0 introduces the following native functions:
EncodeJson, EncodeLua, Compress, Uncompress, GetMonospaceWidth, ProgramMaxPayloadSize, ProgramSslRequired, ProgramSslClientVerify, MeasureEntropy, Decimate, Benchmark, Rdtsc, Lemur64, Rand64, Rdrand, Rdseed, GetCpuCount, GetCpuCore, GetCpuNode, oct, hex, bin
redbean 2.0 adds support for modern password hashing. See argon2.hash_encoded and argon2.verify.
redbean now defines the
arg global the same way
as the upstream lua
command interpreter.
The argv
global is now deprecated.
This release includes many upstream fixes from Cosmopolitan Libc. The quality of the Windows platform support has improved considerably. For example, fork() now works well enough on Windows that this release enables it by default. Many other bugs on Windows that would cause redbean to become unresponsive to CTRL-C interrupts have also now been resolved.
Funding for the development of redbean was crowdsourced from Justine Tunney's GitHub sponsors and Patreon subscribers. Your support is what makes projects like redbean possible. Thank you.