I feel like I knew at one point why it got so thoroughly supplanted by nginx but I don't remember now why that happened.
I feel like I knew at one point why it got so thoroughly supplanted by nginx but I don't remember now why that happened.
This was 2006 and nginx was the only realistic alternative on the market. It worked beautifully since day 1. It saved my startup. Next year we got acquired by Google.
I only got 1 crash with nginx and it was partially my fault, I had an "expires 30y" on some images, and a morning on feb 2008 I came to the office and the whole site was down. After a very quick gdb session under panic I realized it was trying to get a weekday name on an array with a negative index. Nginx was adding 30 years to the current date and that was over 2038 and it overflowed. Igor fixed that issue in hours, and he graciously explained that I could have used "expires max"
Nginx has powered all my startups since then (Freepik, Flaticon, Slidesgo, Besoccer).
This guy has added more real value to the economy than most unicorns. A true hero.
From the bottom of my heart, thank you.
This classic 2007 tutorial starts by pointing out that NGINX parses the HTTP verb by looking at the second letter first, so that if it's O it knows to check for POST or COPY!
https://web.archive.org/web/20070505051653/http://www.riceon...
I think that if you want to support all verbs, you face at least 3 'ambiguities' whether you first check the first, second, or third character of the string. (It must be at most the third, as the shortest verbs are 3 characters long.)
First checking the first character is ambiguous between POST, PUT and PATCH. First checking the second character is ambiguous between HEAD, DELETE, and GET. First checking the third character is ambiguous between GET, PUT, OPTIONS, and PATCH. [0]
edit As danachow points out, the verbs are not all used with the same frequency. If real-world performance is the goal we'd presumably want to optimise for the GET case, which presumably means first checking the first character, as the 'G' is unique to GET.
[0] https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods
https://github.com/nginx/nginx/blob/7587778a33bea0ce6f203a8c...
There are several points to note: This trick only works if the architecture supports unaligned memory access. Macros are used instead of functions, so you don't have to rely on the compiler to do inlining. The shifts and ors should get optimized out by the compiler. I once tried something similar in C#, but sadly the JIT doesn't do that optimization.
Presumably then it's relying on undefined behaviour.
https://stackoverflow.com/a/47622343/
https://stackoverflow.com/a/28895321/
https://blog.quarkslab.com/unaligned-accesses-in-cc-what-why...
That method does look better. My idea assumes that the method has already been checked to see if it is a valid method, which has its own cost. Otherwise, POSABC would be parsed as POST. Their method does that more or less on the fly.
if (m[0] == 'G') {
r->method = NGX_HTTP_GET;
break;
} else if (m[0] == 'P') {
switch(m[1]) { ... }
} else if ...
this should be fast enough.'Should' implies uncertainty; 'fast enough' implies there's no speed constraints / requirements. This particular bit of code - checking what request type is coming in - will be executed trillions of times across millions of servers across decades; it's the kind of thing you want to be as fast and secure as possible, so it's worth taking away these insecurities and guesses about performance.
Hopefully the actual code does a full parse after guessing what to try by the character test heuristic.
Something I don't see many people talk about, but I've setup web instances with both read and write clusters similar to db's, and have the nicety to getting more umph from the read cluster by making sure it only hits read db nodes and disabling any framework stuff for tracking changes on a model.
I expect anything that talks HTTP to reject requests with invalid HTTP methods. HTTP is a well-known standard and servers do not need to accomodate for sloppy, wrong, or malicious implementations.
First, the async model was literally years ahead of Apache. It leaned heavily on interfaces like epoll to manage large connection pools with a small number of processes, while Apache still used a thread or process per connection.
Second, it removed exactly the right features - those with minimal benefit and high performance impact. The classic example is .htaccess, which adds (at least) one stat to every single request, but in practice was only needed for the horrible multi-tenant LAMP reseller setups of the day - everyone else was fine with static centralized configuration.
The Architecture of Open Source Applications - volume II - nginx:
Here's there I ended up: https://github.com/nginx/nginx/blob/363505e806feebb7ceb1f9ed...
PS, I'm not totally sure, but they definitely use the count of letters as an optimization, and it seems they increment the bits associated with each type, so the order of the bits behind each NGX_HTTP_GET etc seems to matter...!
https://github.com/nginx/nginx/blob/67d160bf25e02ba6679bb6c3...
Someday I will understand :)
ngx_http_v2_parse_method iterates through all the tests, starting with the first test (GET).
They compare request method string length to test string length then on matching lengths compare each character in the request method string to the test string.
Fully matching strings set the request method numerical value from the test value and returns OK.
Any non-matching characters GOTO the next test.
After that it does a sanity check on the request method string characters (A to Z or _ or -) and returns OK or DECLINED as appropriate.
For the macros defining the HTTP method numerical values I think they're set up that way as bitmasks for bitwise operations.
For example, they do things like[1]
if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD)))
Here HTTP request method value is bitwise AND against (0x00000002 OR 0x00000004)Any non-zero value here would be true and any zero value is false. So if the request method bit value AND matches either GET or HEAD bits then this conditional is false.
[1] https://github.com/nginx/nginx/blob/a64190933e06758d50eea926...
But there indeed came a time (maybe 2010ish?) where nginx took the lead with gread strides and most people (even the die-hard fans) mostly moved to nginx, that's about when it was clear that it would probably win and stay for the forseeable future. Back in those circles at least (see some other comments for the PHP FPM story) Apache was only kept for setups with lots of different other dependencies, like mod_svn or webdav, if you "only" needed a webserver to front PHP it was nginx.
I also remember many people holding out on adopting Apache 2.2 for a looong time.
With Apache, at their current max load, the systems would just completely fall over, but with lighttpd, at that same load, the system breathed a little hard. I could push lighttpd 10x more before it fell over.
Then we started looking at replacing the haproxy solution, and I looked at nginx. I tried it out. I also tried out replacing lighttpd with nginx. And no matter how hard I pushed nginx, I couldn’t get the damn thing to breathe hard. I couldn’t even push the load average up over 1.0.
We went back to lighttpd and haproxy because those tools gave us better monitoring and logging, but we always held nginx in our back pocket in reserve, in case we needed another 10x beyond what lighttpd+haproxy could do.
And yes, I did get invited to Edinburgh to do a nice little talk on the subject.
They had their ups and downs over the years, but ihiji did end up getting acquired by Control4, and the founders are now off doing other weird and cool things.
https://serverfault.com/questions/330413/lighttpds-memory-le...
We were serving dynamic content via FastCGI, IIRC.
This was a long time ago and I'm pretty hazy on the details, but I'm pretty sure I remember finding memory leak bug discussions on the lighttpd website around that time, and no clear answers on how to avoid it.
The proper workaround was to send an X-Sendfile header to instruct lighttpd to fetch the file itself, instead of serving the content directly from the backend. It was also more efficient, but it required changes to backend code that made it less portable. I don't know if the bug was ever fixed as lighttpd development had slowed to a crawl and nginx arrived just in time to take away all the market share.
The introduction of PHP-FPM around the same time was another factor that favored nginx, because lighttpd typically integrated with PHP using a more fragile setup called fcgi-wrapper. PHP-FPM was much nicer to work with, and nginx could even load-balance across FPM pools. A lot of WordPress sites switched to nginx and never looked back.
Back then people were getting into using VPS services like linode, and memory usage mattered a lot.