Hacker News new | ask | show | jobs
by austin-cheney 439 days ago
Every web server claims to be fast, so I wonder how they define that. As someone who has written their own supposedly fast web server I only want configuration simplicity. Most web servers are unnecessarily far too complicated.

In a web server here is what I am looking for:

* Fast. That is just a matter of streams and pipes. More on this later. That said the language the web server is written in largely irrelevant to its real world performance so long as it can execute low level streams and pipes.

* HTTP and WebSocket support. Ideally a web server will support both on the same port. It’s not challenging because you just have to examine the first incoming payload on the connection.

* Security. This does not have to be complicated. Let the server administrator define their own security rules and just execute those rules on incoming connections. For everything that fails just destroy the connection. Don’t send any response.

* Proxy/reverse proxy support. This is more simple than it sounds. It’s just a pipe to another local existing stream or piping to a new stream opened to a specified location. If authentication is required it can be the same authentication that sits behind the regular 403 HTTP response. The direction of the proxy is just a matter of who pipes to who.

* TLS with and without certificate trust. I HATE certificates with extreme anger, especially for localhost connections. A good web server will account for that anger.

* File system support. Reading from the file system for a specific resource by name should be a low level stream via file descriptor piped back to the response. If this specific file system resource is something internally required by the application, like a default homepage it should be read only once and then forever fetched from memory by variable name. Displaying file system resources, like a directory listing, doesn’t have to be slow or primitive or brittle.

5 comments

> Fast. That is just a matter of streams and pipes. More on this later. That said the language the web server is written in largely irrelevant to its real world performance so long as it can execute low level streams and pipes.

I'm no expert, but that doesn't sound right to me. Efficiently serving vast quantities of static data isn't trivial, Netflix famously use kernel-level optimisations. [0] If you're serious about handling a great many concurrent web-API requests, you'll need to watch your step with concerns like asynchrony. Some languages make that much easier than others. Plenty of work has gone into nginx's efficiency, for example, which is highly asynchronous but is written in C, a language that lacks features to aid with asynchronous programming.

If you aren't doing that kind of serious performance work, your solution presumably isn't performance-competitive with the ones that do. As you say, anyone can call their solution fast.

[0] [PDF] https://freebsdfoundation.org/wp-content/uploads/2020/10/net...

everyone is using kernel optimisations at that size. (because it's worth it, because there's a well known pattern of load, file/chunk sizes (so I/O in general) and metrics to shoot for)

nginx is a big state machine built around epoll, and there's not much to do with the raw kernel ABI anyway (of course using safer and more powerful tools helps with the general quality of the end result, but not really with speed). it took many years for the uring ABI to emerge (and even using it efficiently is not trivial).

Can you share a link to your web server please?

I'm finding it hard to make sense of your comment: I can't reconcile some of the stuff you're saying. My gut feeling is you're either ridiculously smart, so smart that defining and implementing a security rules engine for a web server is something genuinely trivial for you, and the world has a lot to learn from you. Or, you're really, really not aware of how much you don't know, so much so that you're going to end up doing something stupid/dangerous without realising.

Either way, an example of a supposedly fast web server written by you should clear it up pretty quickly.

Sorry, because this feels a little rude, and I don't mean it to be, but you're contradicting quite a lot of widely held common sense and best practise in a very blasé way, and I think that makes the burden of proof a little higher than normal.

Not OP, and also not a web server genius, but I read OP's comment as allowing server administers to write policy in OPA then just using https://github.com/microsoft/regorus/ to determine whether to allow or forbid the connection. The web server author can clearly document what is available in input/data to be checked against in the policy. Is it really more complicated than that?
I honestly don't know. I'm in the same place as you: not a web server expert. But I did spend a bunch of time in security a while ago, so maybe it's my own bias to be sceptical of anyone who casually suggests building and implementing their own boundary security solutions.

As well as that, the idea that the language any software is written in is largely irrelevant, especially in the context of performance, is not at all obvious or intuitive to me. I get that it would look that way if you reduce a web server down its core functionality. But that also is a common mistake in educated but inexperienced early career software engineers.

I don't know this stuff, but I know enough to know how well I don't know this stuff. I'm trying to work out if the stuff I'm reading is from someone who I should learn from, or if it's from someone with a lot of confidence but limited experience. It could be either, I'm sincerely on the fence, but a git repo of their web server would help clear it up for me personally.

> Is it really more complicated than that?

I can't say without really doing a thorough review. Even if regorus is 100% reliable rules engine, my understanding is it's a rules engine. I assume there's still a bunch of custom integration needed to manage and source the rules, feed them to the engine, and then implement the result effectively and safely across the web server. It can be done quickly and easily, but to consider everything and be confident it's done correctly and securely? I don't think that can be done trivially by the average human without some compromise.

a rules engine for a web server isnt difficult if it doesnt have to carry responsibility for the web app security itself. then its as the posted outlined really...

that being said, its common for new servers to have old vulns, not many coders will go over cve reports of apache and nginx and test their own code against old vulns in those.

i do find a lot of claims about performance or security are oftend unsupported as with this server. it just says it on the readme but provides no additional contex or proof or anything to back up those claims.

my thought is that original commenter gott riggered by that, perhaps rightfully, and points out thia fact more than anything. if you want to claim high performance or security, back it up with proof.

the simple fact its in rust doesnt make it more secure. and using async in rust doesnt imply good performance. it could in both cases. wheres the proof.?

Regarding the server performance, there are benchmark results on the Ferron's website.
Security is not that simple. Duplicate HTTP headers, utf-8 in HTTP headers, case insensitivity etc has resulted in countless security vulnerabilities over the years. Do you reject suspicious requests? Do you process the request but filter out invalid headers? What about suspicious headers being returned from the app being served? You have to make choices here and if you choose unwisely bad things happen. The spec is of little help here, because web browsers and other web servers (and downstream app servers) don't adhere to the specs being sometimes too lenient and in other times too restrictive in what they do.

Just take a look at https://www.rfc-editor.org/rfc/rfc9110#section-5.5 to get an idea of how any choice made by a web server can blow up in your face.

Most of these things are much harder to get right that you make it sound. Perhaps proxying most so. It is a legitimately hard problem. Look at something like Varnish, which is likely one of the better proxies out there. It took many years to get good.

I never had to write a proxy and am grateful for it. You have to really understand the whole network stack, window sizes and the effects of buffering, what to do about in flight requests, and so on. Just sending stuff from the file system is comparatively easier where you have things such as sendfile, provided you get the security implications of file paths right.

In Rust all web frameworks are fast because they all use the same stack tokio + hyper.
By the way, Ferron web server also uses Tokio and Hyper.
Fun experiment, try and benchmark a simple file download on a simple web server using rocket.rs (which also uses tokio and hyper, and has a built in fileserver type, so just a few lines of code) and compare it to a vanilla nginx config.

Here’s the rocket.rs source I used:

    #[rocket::main]
    async fn main() -> Result<(), rocket::Error> {
        rocket::build()
            .mount("/", rocket::fs::FileServer::from("/tmp/test"))
            .ignite()
            .await?
            .launch()
            .await?;
        Ok(())
    }
And do `mkdir /tmp/test && dd if=/dev/urandom of=/tmp/test/bigfile bs=1G count1` to create a 1G file, and run `time curl -o /dev/null localhost:8000/bigfile`.

My nginx config:

    worker_processes auto;
    master_process off;
    pid /dev/null;

    events {}

    http {
        sendfile on;
        access_log /dev/stdout;
        error_log /dev/stderr;
        server {
            listen 8089;
            server_name localhost;
            root /tmp/test;

            location / {
                try_files $uri $uri/ =404;
            }
        }
    }
launched with `nginx -c "$(pwd)"/nginx.conf -g "daemon off;"`

The results for a 1GB file for me on an nvme ssd, averaged over 100 runs:

    nginx: 150ms
    rocket: 4 seconds
Or roughly 25x slower. Release mode makes no difference.

You can definitely write slow code in rust if you’re naive about reading/writing between channels a few kilobytes at a time, which is what rocket does, vs using sendfile(2), like nginx does.

Edit: These numbers were from a few months ago... I tried it again by just pasting the above into a new project with `cargo init` and adding rocket and tokio to my deps, and it's now 2.3s in debug and 1.2s in release mode. It may have improved since a few months ago, but it's still 10x slower.

This doesn't count because hyper doesn't support sendfile. Maybe hyper would outperform nginx in other scenarios.
Disabling sendfile in nginx makes it take 170ms instead of 150ms on my system.

Because nginx knows to tune the buffer sizes properly, which goes a long way.

Using strace reveals what’s happening, rocket is reading and writing to file descriptors 4 kilobytes at a time, using 2 syscalls every chunk. nginx uses far, far fewer of them. (And with sendfile enabled, only one for the whole download.)

Also, there’s no reason rocket can’t use sendfile too. It’s basically the theoretically fastest way to perform this operation, and IMO rocket ought to use it by default if it’s available on the OS.

> Because nginx knows to tune the buffer sizes properly, which goes a long way.

Interesting. Do you know which algorithm nginx uses to determine the proper buffer size?

Enabling lto, using codegen-units=1, and using the minimum required feature flags might improve performance a bit.
No, it won’t. Rocket is issuing 2 syscalls for every 4 kilobytes, which is what is killing the performance.
Hold my beer while I trivially write a slow web server on tokio + hyper.
I could write one without Tokio, I'd just poll the sockets at 100 Hz
Why not two birds with one stone: do the same in tokio + hyper, and disprove the ridiculous claim!
Holding :>