Lwan is a
high-performance & scalable web server.
View it on GitHub
Requests per second vs. number of concurrent connections?
|
Hello, World! (C) |
|
100B file |
|
Hello, World! (LuaJIT) |
|
32KiB file |
Lwan was until recently just a personal research effort that focused
mostly on building a solid infrastructure for a lightweight and
speedy web server.
With its low disk and memory footprints, it's
suitable to be used from embedded devices to robust servers.
Both static and dynamic content can be served, as it can also
be used as a library. Dynamic content can be generated by
code written in either C or Lua.
Its simple architecture and tiny source code guarantees the
entire code base can be easily
grokked.
Connections are handled individually by coroutines, which are
transparently and efficiently juggled by a per-CPU cooperative
scheduler, giving the illusion of blocking I/O to handlers.
Resource management is also greatly simplified with
coroutines: whenever a client connection is closed, memory is automatically
reclaimed, files are automatically closed, references are automatically
decremented. This provides more predictability than most garbage
collectors, helping keep latencies low while being almost as easy to
use.
Lwan is a work of art. Every time I read through it, I am almost always
awe-struck.@neurodrone
What is deadlier than bullet points?
- Low memory footprint (~500KiB for 10k idle connections)
- Small disk footprint: x86-64 executable has 110KiB (~52KiB if packed with UPX)
- Minimal memory allocations & copies
- Minimal system calls
- Hand-written HTTP request parser
- Static file serving uses the most efficient way according to file size
- Mostly wait-free multi-threaded design
- One thread accepts connections, one I/O thread per logical CPU handles them
- Coroutines makes asynchronous I/O a breeze in C
- Supports Linux, FreeBSD and macOS
- Purpose-built I/O loop
- Efficient, Guava-inspired loading cache. Used for:
- Directory listing
- File information (size, last modified date, MIME type, etc)
- Compressed file contents
- Compiled Lua scripts
- Request rewriting support based on Lua pattern matching syntax
- Lua scripts can be executed to control the rewriting behavior as well
- Diminute codebase with roughly 10000 lines of C99 code
- Efficient Mustache templating engine
- Used internally for directory listing & error messages (can
be loaded from a file)
- Available for user-built handlers
- Easy to use API to create web applications or extend the web server
- Example: Freegeoip reimplementation,
which performs better than the Go version and is roughly the same
amount of lines of code. The live version has been up for
years, and serves millions of requests per day, with a RSS of
roughtly 5MiB.
- Handlers can be written in C and Lua
- Supports rebimboca da parafuseta
- No-nonsense configuration file syntax
- Supports a subset of HTTP/1.0 and HTTP/1.1
- Support for keep-alive connections
- Support for pipelined requests
- PROXY protocol support
- Useful for TLS terminators such as Hitch
- systemd socket activation
- IPv6 ready
- Buildbots checks every commit
- Code is statically analyzed by Clang Static Analyzer
- Debug & Release builds
- Various platforms:
- Arch Linux, always up-to-date
- FreeBSD 10 and 11, courtesy of @koobs
- macOS Sierra
- Test suite written in Python tests the server as a black box
- Test suite is executed with both Undefined Behavior and Address sanitizers
Nice work with Lwan! I haven't looked that carefully yet but so far I like what I saw. You definitely have the right
ideas.
@thinkingfish
How to build, setup, and run
The
README.md
file in the repository will list build dependencies, commands, and set up
information.
API example
Lwan isn't just a simple static file server: it can be used as a library
to build web services on top of it. In fact, the static file server isn't a
special case: it just uses the same APIs that are available when Lwan is
used as a library.
The Hello, World! handler, shown below, was used to generate the
above chart. While the API is simple, Lwan isn't a framework, so not
everything is built-in; some features will actually require changes to the
internals, as they're very tightly-coupled.
#include "lwan.h"
LWAN_HANDLER(hello_world)
{
static const char message[] = "Hello, World!";
response->mime_type = "text/plain";
lwan_strbuf_set_static(response->buffer, message, sizeof(message) - 1);
return HTTP_OK;
}
int
main(void)
{
const struct lwan_url_map default_map[] = {
{ .prefix = "/", .handler = LWAN_HANDLER_REF(hello_world) },
{ .prefix = NULL }
};
struct lwan l;
lwan_init(&l);
lwan_set_url_map(&l, default_map);
lwan_main_loop(&l);
lwan_shutdown(&l);
return 0;
}
Lua is also available to write handlers, but support is still
pretty rough and not everything available to C handlers is available
to Lua scripts.
However, it's enough to run some frameworks, such as Sailor. To whet your appetite, an
improvement of the program above could be easily written in Lua like
so:
function handle_get_hello(req)
local name = req:query_param[[name]]
if name then
req:set_response("Hello, " .. name .. "!")
else
req:set_response("Hello, World!")
end
end
Lwan infers what method and endpoint by the function name, so there's no
need to specify them with something akin to an lwan_url_map_t
array. In this case, issuing a GET /hello will be sufficient to run
this handler.
An in-depth view of the C API can be seen in this
article.
FAQ
Is it secure?
How is it possible to write a
“hand-crafted HTTP parser” in 2014, and not have at least an H2 for
security?@gnat
We're not security experts, by any means. Having said that, here are some facts:
- Lwan has a 0.0 defect
density as reported by Coverity. No top CWE 25
defects were found by this tool.
- Every commit is
checked with Clang Static
Analyzer. As far as we can tell, only false positives remain in the
code; feel free to check for yourself.
- It produces no warnings when executed on Valgrind Memcheck (with debug builds:
release builds disable some things that Memcheck relies on to keep sanity when
coroutines switches stacks.)
- No warnings are
produced while building Lwan, even though options to enable extra warnings are always passed.
- It has been also fuzzed with Sulley,
libFuzzer, and afl. One bug has been found with libFuzzer and it has been
fixed.
- All tests are performed automatically, with both Address
Sanitizer and Undefined Behavior Sanitizer; coverage is still on
the low side, though.
So, to answer the question: we have no idea. We were dead
serious when we said we're not security experts. But care is being taken to
at least find the most mundane sources of vulnerabilities and fix them
before they hit the source tree.
There's nothing stopping you from
exploiting Lwan, however. We would specially appreciate a public writeup
about the bug; we'll even pay you a drink if we ever meet in person.
It's interesting how some of the tricks you used for performance also made
the code less "risky". For example all the parsing is just setting
pointers, no allocation of new buffers, so there isn't much opportunity for
memory corruption, and coro_defer makes use-after-frees pretty much
impossible.
immerse
A web server is rarely the bottleneck. Why not just use Nginx or
Apache?
If you're asking this question, then you probably should.
These web servers are well known, stable, compatible with almost everything
under the sun. People depend on them. Breaking their behavior will most
likely make some
system administrators
very upset. On the other hand, few (if any) people use this server, so it
is possible to innovate without guilt.
Where are the benchmarks?
Lwan is in the 10th round of
TechEmpower's web framework
benchmarks. This independent work tests a large number of frameworks and
platforms against a set of tests common to web applications, such as
JSON
serialization,
database queries and
templating.
For Round 10, Lwan has taken the crown. But we expect the other
top contenders won't leave this a settled matter. TechEmpower
Blog
Could you share some details on how exactly Lwan is this
fast?
A good place to start are these
blog posts. The code
is fairly small as well, so lots of things can be inferred from reading
it. Also, there's a
rather
lengthy post that explains the life of a HTTP request from Lwan's
viewpoint, which gives lots of insights.
What's the license?
GNU GPLv2+, plus a few
other bits and pieces licensed under other permissive stuff. Refer to each
file for their respective credits and legalese. It might move to LGPLv2+
(with a static linking exception) in the future, though.
What does Lwan mean?
Nobody knows. Tell us when you find
out! Someone suggested a recursive
bacronym, "Lwan Will
Annihilate Node.js", which is funny because this is very unlikely to ever
happen.
Four years is a long time for a project this small!
This
isn't a question, but, yes, it is a long time. Keep in mind that this has
been written exclusively on the spare time of a single developer, who works
full-time on a real job. Also consider that this was exclusively a research
project: the goal was not to write a web server, but to learn while finding
novel ways to implement certain things.
Is there a stable release?
There's just one release: the
current. This might or might not change in the future.
Is anyone using this server?
Please refer to
the GitHub
repository for a list of Lwan servers spotted in the wild.
Does it support HTTP2, or at least TLS?
Not yet. As for TLS,
Lwan supports the PROXY protocol, so a TLS terminator proxy such as Hitch
can be used.
This web page looks like an advertisement for snake oil.
That's
not a question. And, yes, it's on purpose. It's a parody of the web page
for a similarly-named web server. In fact, there are a few easter eggs in
this web page.
Brought to you by
@lafp. Powered by
Lwan.