Developing an http server using libmill
arafath-mk opened this issue · 20 comments
I would like to develop an http server using libmill. As a first step, I am planning to port an existing go based http server library using libmill. (https://github.com/valyala/fasthttp) Is it a good idea? Will I get more performance than Go? (I am expecting more performance since this web server will be in C)
I hope, the porting will be easy as there is 1:1 mapping of concurrency model.
And I would like to port this (new webserver) code to libdill once it is matured.
Excellent idea! I'm looking at developing a http/2 server using nghttp2 (so that the hardwork is done for me already). However, nghttp2 does not support http/1.1.
It should, in theory, be faster. My preliminary microbenchmark helloworld put a nghttp2 server (with single thread/1 stream) only 20% behind lwan which arguably is the fastest web server for raw-http/1.1 serving. This is with lwan's highly optimised http parsing implementation etc. whereas http2 is a more complicated protocol and nghttp2's implementation has been aiming for correctness rather than raw performance (as yet). See #155.
In terms of the performance from actual libmill
features that will be pitted against go
, namely co/goroutines, HEAD has an x86-64 assembler implementation of the context switching. To compare use this code:
https://github.com/jamel/go-perf-tests
On my Intel(R) Core(TM) i3-5010U CPU @ 2.10GHz
for go
:
$ ./ctxswitch 10
performed 10M context switches in 3.101000 seconds
duration of one context switch: 310 ns
context switches per second: 3.225806M
On my Intel(R) Core(TM) i3-5010U CPU @ 2.10GHz
for git libmill
:
$ ./ctxswitch 10
performed 10M context switches in 0.289000 seconds
duration of one context switch: 28 ns
context switches per second: 35.714283M
On an older Intel(R) Pentium(R) Dual CPU T2370 @ 1.73GHz
, libmill gets:
$ ./perf/ctxswitch 10
performed 10M context switches in 0.420000 seconds
duration of one context switch: 42 ns
context switches per second: 23.809525M
Which basically makes context switching costs almost negligible across x86-64 platforms! The implementation of the context switching may even be faster than lwan's due to inlining... but I haven't tested this.
Done!
The application server should work in all major browsers. Can nghttp2 handle requests from http/1.1 clients? What about support for SSL and WebSockets?
Do we need to focus on porting https://github.com/valyala/fasthttp or nghttp2 ?
The application server should work in all major browsers. Can nghttp2 handle requests from http/1.1 clients?
nghttp2 does not handle http/1.1 requests, but fasthttp doesn't handle http/2 requests either.
What about support for SSL and WebSockets?
Support for ssl/tls is ongoing: #152
This is necessary for HTTP/2 to function in browsers but you can still use some tools to test non-SSL/TLS http/2 web servers while under development.
I saw some of @sustrik 's repositories have websocket support... I don't know their status though. Leave that extension for another day as websockets don't actually have much relationship with the webserver itself and can be implemented independently.
Do we need to focus on porting https://github.com/valyala/fasthttp or nghttp2 ?
nghttp2 doesn't need porting, just needs a libmill backend and a new frontend unless you want to reimplement it from scratch (to better integrate http/1.1, http/2 and libmill under a cohesive API).
Don't worry about that for now, I'm currently working with nghttp2 for a basic web-server so better focus on fasthttp or some http/1.1 implementation first.
Use cases
In terms of use cases, HTTP/2 is well-supported in browsers now but there's not many implementations out there. HTTP/1.1 is still required for some older browsers and mobile browsers that don't get the love they need.
Eventual Architecture?
As you intend to port it to libdill, I think @sustrik had ideas of protocol layering and having a separate libdill-based library that implement protocol primitives. So something for a HTTP server would end up like a series of layers: dsocks (TCP/IP) <-> dsocks (TLS) <-> dnetproto (HTTP/1.1 or HTTP2).
In my mind, I envisioned a complete web server to involve a series of binaries (to follow the UNIX principle). Main points are:
- a proxy front-end for communicating with the backend web servers (translating between HTTP/2 and HTTP/1.1 if required).
nghttpx
is such an example fornghttp2
. - communication between frontend and backend is done using IPC (pipes, unix sockets, shared memory etc.).
- the backend servers can work as a limited frontend (e.g. you don't want to have proper proxying support for multiple backends but just want a simple webserver for an embedded system).
In terms of use cases, HTTP/2 is well-supported in browsers now but there's not many implementations out there. HTTP/1.1 is still required for some older browsers and mobile browsers that don't get the love they need.
Most of the modern JavaScript libraries/frameworks don't work in older browsers. So, now a days, web application developers focus on relatively new browsers only. So, it is a good idea to focus on HTTP/2.
I'm currently working with nghttp2 for a basic web-server
How can I help you now? Or do you have any other tasks in your mind for me?
I'm currently busy for the next 10 days or so but the initial tasks that need to be done for HTTP/2 are:
- Implement a TLS API into libmill. There's some basic SSL support in the repository already but it lacks ALPN (required) and SNI (desirable) extensions. I wrote a preliminary API design for: #152 (comment)
- Design an API for a HTTP/2 lib[md]ill-based library.
- Keep in mind composability: sustrik/libdill#8 (comment)
- The idea is that the API should be able to be able to expose a push interface (like wsock).
- This is different to nghttp2 but having a list of what needs to be exposed could be useful from nghttp2's header.
- Try and avoid kitchen-sink API design like
nghttp2
as we assume we have coroutines and basic I/O supplied by libmill and libdill. - This will bubble up a parameter in the push functions to have a
deadline
. - If possible avoid callbacks to fit in with libmill/libdill design.
Implement a TLS API into libmill. There's some basic SSL support in the repository already but it lacks ALPN (required) and SNI (desirable) extensions. I wrote a preliminary API design for: #152 (comment)
SSL, ALPN and SNI are new to me for implementing. So, I need to get familiarize to those at first.
Your help/guidance is appreciated.
Design an API for a HTTP/2 lib[md]ill-based library.
Here too, I need to get familiarize things at first.
SSL, ALPN and SNI are new to me for implementing. So, I need to get familiarize to those at first.
Your help/guidance is appreciated.
We were thinking that the best approach would be to port the code from libtls and link with openssl/libressl, whichever is available on the target system.
https://github.com/libressl-portable/openbsd/tree/master/src/lib/libtls
https://github.com/libressl-portable/openbsd/tree/master/src/lib/libtls
- How to build the current
libtls
in Linux ? - What is to be done next? (Do we need to use
libmill
APIs for I/O operations inlibtls
?)
How to build the current libtls in Linux ?
If your distribution has libressl, libtls is built along with it. For example see: https://aur.archlinux.org/cgit/aur.git/tree/PKGBUILD?h=libressl
What is to be done next? (Do we need to use libmill APIs for I/O operations in libtls ?)
Pretty much and matching the API in the similar way to the one I gave here: #152 (comment)
If your distribution uses OpenSSL, some of the headers will be different to libressl (distinguish using an #ifdef
). If you use the latest beta OpenSSL 1.1, it is NOT backwards compatible - again use appropriate #ifdef
statements if required. The priority would be OpenSSL 1.0.2 and libressl compatbility. ALPN support is unavailable prior to OpenSSL 1.0.2 so disable those features/ignore parameters when required.
P.S. There may be mistakes in the API, but the general idea is there.
I am lurking on IRC: ##libmill on freenode today if anyone wants a quick chat.
Yes, double hashes, as it's not an official channel.
Thanks. IRC will be helpful. I was busy today. See you there in coming days.
There's a node.js implementation;
https://github.com/molnarg/node-http2
Node.js also uses asynchronous I/O calls so in theory some of the design concepts from that implementation could be useful.
Normally Node.JS
modules have tones of dependencies with other modules (directly or indirectly). So, I think, it would be better to refer a C/C++
implementation.
Although perhaps implementing it directly from the RFC would be better, then we have a full understanding of the quirks, performance etc. https://tools.ietf.org/rfc/rfc7540.txt
I think, referring both RFC and a working C implementation would be better.
What about H2O
server? https://h2o.examp1e.net/
Meanwhile, I am familiarizing the afore mentioned technologies and doing some research on network and file access performance improvement.
My primary focus on Performance and Scalability.
Closing old issues.
Has anyone made some progress in implementing a libmill
+dsock
HTTP server / webframework?
I've been fooling with it, but haven't gotten too far. My poor, unoptimized, incorrect toy implementation isn't going as fast as I'd hoped. hahaha...
Anecdotal toy benchmarks are here: https://github.com/ubergarm/binks
Any pointers or more dsock
example code that handles requests, responses, and message body would be useful. Cheers!
The saga continues! Pretty sure my benchmarks were way off because wrk
was using KeepAlive and recycling TCP connections for some webframeworks I tested, thus not accurately reflecting libdill
+dsock
's abilities.
I scratched everything and will try some more.
Gonna earn my grey hairs trying to figure this stuff out... ;)