Everything the post mentions is 100% true and well-reasoned, especially with the inclusion of pledge/chroot/etc. for dropping privileges.
There's two diametrically opposed deployment mechanisms:
1) stack towers of glop on top of each other until you're running a simple `echo "hello world"` on top of a docker container inside a vm in a proxmox server as part of a k8s cluster under a hypervisor running on remote compute...
2) just run `bash -c 'echo "hello world"'` and front it with CGI (stateless, single binary, yada yada yada...)
Take a peek at https://github.com/Domain/bash.cgi ... not that i'm suggesting that 'bash over busybox using cgi-bin' is the "proper architecture", it's more trying to illustrate that the amount of code that holds you back from being nearly truly "frameworkless" is tiny and easy to reason about.
For some of the internal tooling I'm doing for work, I've been tending towards:
some-cmd [--json|--markdown] ...yet-more-args...
When you use the process and shell environment itself as a repl, you start trending towards `[--cgi-bin]` would "just" be another output type, and the ability (nay: foresight!) to ensure that your commands are independent, reproducible, and only depend on their input arguments leads you directly towards being runnable in both "hostile" (ie: firecracker vm / lambda-style) or very "comfortable" environments (ie: shell, debuggers, unit/integration tests).I already write Python services using Bottle that function both as a standalone server and as a CGI script, and I've been updating them to work with busybox's more constrained CGI environment (basically being able to handle the lack of .htaccess rewrite rules).
I definitely see a chance for simple deployment tools to make a resurgence as we create more one-off/disposable/personal software, and the low system requirements for something like busybox + lua stack are extremely minimal, which fits well with the current DDR4-is-good-enough zeitgeist. I'm running them on an old Raspberry Pi 2B and it's plenty performant for the ~10 people that access it regularly.
https://turbo.readthedocs.io/en/latest/
Event driven, fast, all the networking basics in place including http, mustache templates and encryption, light and small executable, very nice for embedded applications, signals and fd handling built-in, very easy to use…
Have used it very successfully for million-dollar projects, including some scientific applications running on limited-resource systems (512 megs of RAM) ..
Was very, very easy to add C functions to interface with the web front-end, libffi is great for this purpose, but its also very easy to extend as a standard, well-written Lua/C application ..
Turbo looks fantastic, though! It's precisely the niche I was looking to have filled, so I'll build a prototype or two in it to see what I think. Very much appreciate the pointer!
TurboLua is particularly great because it really integrates about all of the I/O mechanisms you could want in a network-centric application, plus it has a pretty nice runtime loop which is easy to hook into and coordinate properly with signal handlers and fd sets and things. Can’t beat it as a very quick way to do embedded stuff too - very easy to do gpio, signal handling, etc.
Good luck, if you have time to share your thoughts after an initial first-pass, I’d be happy to hear your perspective ..
This is from pre-AI era also. The game is Civ-like. Custom engine from scratch (including both the 3D stuff, and all the web handling also).
Take a look: https://statebuilder.com/
I continued to make sites for other venues that needed online ordering; such as theatre companies and restaurants. All in C, fastcgi, FreeBSD, postgresql and h2o-server cause it always did what we wanted without bending to someone else's rules. It was fast and easy to make changes while changes weren't made for us or without our consent. We did very well for ourselves.
I love C and wrote some CGIs in C in the past but I don't love concurrent programming in C. While forking and execing can use shared read-only mmap on .text segments, the .bss and .data will need initialization and copying; there's unavoidable startup overhead that a threaded process avoids, and raw pthreads is kind of painful. If all you need to do is serve a low traffic site, CGI is fine.
My biggest worry would be how often the SQLite DB gets backed up because there's no way a C web server will be free of RCEs in today's world.
https://github.com/ncruces/go-sqlite3
I just wrote up the security goals of the tool I use to translate C to Go: https://github.com/ncruces/wasm2go/blob/main/SECURITY.md
I obviously can't make any promises, as it's just a personal pet project, but those are the goals, and they do catch memory corruption bugs in practice: https://sqlite.org/bugs/forumpost/543f94984b
I like this project for adding CGI to nginx: https://github.com/pjincz/nginx-cgi
There's also methods of starting Rack applications directly form nginx via passenger[1]. Rack is the CGI like interface for ruby webapps: https://github.com/rack/rack It easily allows for writing applications and middleware with a few lines of ruby.
[1]: https://www.phusionpassenger.com/documentation/Users%20guide...
I have my own configuration template for nginx: https://git.levitati.ng/Levitating/nginx.conf
The answer is: it does but via community plugins.
I have used it and I've even made PR for it which led to a response from the lighttpd maintainer: https://github.com/gnosek/fcgiwrap/pull/62
Essentially I wanted fgciwrap to be able to accept arbitrary response bodies without headers, so I could use it with programs that weren't actually made for cgi. Lighttpd supports this but apparently that's an happy accident.
It would be blazing fast, for use by AI itself
Just for fun
C introduces the possibility of memory-safety issues, which are deadly in server software that's exposed to the Internet. And AI, from what I've seen, is not all that great at writing software with zero memory bugs.
Rust closes the memory safety holes, at the cost of making the programmer think through ownership and architecture much more carefully. Those are exactly the skills that AI is good at. So if you want something fast and are writing it with AI anyway, why not have the AI generate Rust code?
If the latter, then it’ll only contend on simultaneous writes, which mutex anyway.
> Every request spawns a fresh process.
How does it work when multiple concurrent requests are possible? Does SQLite allow simultaneous write access from multiple processes?
> There can only be a single writer at a time to an SQLite database. There can be multiple database connections open at the same time, and all of those database connections can write to the database file, but they have to take turns.
However, it can also be configured in WAL mode where the single writer is applying WALs while the applications append to the log.
IIRC the spec itself says it was built for C applications.
mod-perl was a godsend, of course.
But the very first CGI scripts I wrote were in C, late 95 or early 96. Apache on Slackware Linux.
Their example is:
#include <err.h> /* err(3) */
#include <stdlib.h> /* EXIT_xxxx */
#include <stdio.h> /* puts(3) */
#include <unistd.h> /* pledge(2) */
int main(void)
{
if (pledge("stdio", NULL) == -1)
err(EXIT_FAILURE, "pledge");
puts("Status: 200 OK\r");
puts("Content-Type: text/html\r");
puts("\r");
puts("Hello, world!\n");
return EXIT_SUCCESS;
}A typical setup today looks like:
nginx -> puma/unicorn/gunicorn/etc -> your code
With cgi that middle layer is gone. It's just:
nginx -> your code
The layer reduction is nice when it comes to things like debugging. You don't have to guess if the middle server is removing a header, failing to forward a header, etc.
It's obvious who does that with cgi.
Early PHP (3 and 4) were the thin layer over the C libraries and were pretty fast and easy. it came about right when CGI was deprecated and it solved CGI's problems
For any greenfield project, pick up C++ in lieu of C if you want a C language.
There is no runtime penalty for using C++ if you stick to plain structs and don't use virtual functions, but you get better static type safety, lvalue references, namespaces, etc. even if you don't use the OO features.
Even for virtual functions, the overhead is minimal that it is just noise.
C compilers themselves are written in C++ for the last 15 years. Oh, the irony!
So people obsess over C because you’re hard limited by the upper feature bound. And that’s not so bad because C gets feature updates too - microscopic but effective feature updates; see C29 for the poor man’s RAII: defer
Large portions of the standard lib depend on heap allocations. C is a much safer target for environments with limited resources.
That may be a me problem.
[0] - the other one: https://news.ycombinator.com/item?id=50051573
I took the concept of "slots" in requests, from it: https://github.com/okws/okws
It’s for intranet business app so although it’s not yet production ready yet, definitively winning against many other modern approaches in web development in my consideration, and quite refreshing POC project.
I'm actually also rewriting a header-only server (std c++ and sockets) I wrote a few years back to take advantage of C++26 reflections and similar to give it something similar in capability to AspNetCore+EFCore.
I just got C FFI working in my LLVM based language last night so I'll need to take a look at playing around with BCHS :)
Um, any and all string handling in C is pretty miserable.
I mean, the article should not be taken seriously on any technical level. It's ok just for fun.
It’s a cool project to test yourself but most of the benefits outlined would also apply to Go, Rust, etc, and then you also get memory safety thrown into the mix as well.
It has headers like AI blogposts, and features a lot of parallel structure like AI blogposts ("there's no <blank>. you <blank>"), but if you actually read it none of it reads like slop. To me anyway.
A far better (and harder approach) is to actually write the whole stack yourself. Meaning the actual web application and web server are packaged from a single binary. You of course have a myriad options in how to actually implement this (the server loop can run as a separate coroutine/thread/process/etc), but overall the performance and expressiveness flexibility is limitless. On top of that you can actually get blazing fast performance compared to classical approaches.
I'd also advise everyone to avoid CGI at all costs. It's overhead is massive (new process every time, more or less) and _itself_ is a half measure since implementing a proper code hot loading/module system for servers is too much of a hassle (and standardizing it is hard). Nowadays you want to inline your server and then handle HTTP generation separately (however you'd like).
"Make no mistakes."
Or a comfy scripting language with a great FFI as X intended.
I can't read any of the text on that page other than the headings. Is that what makes it modern? I assume the stack mentioned in the blog post is vibe-slop as well.
It was 100% human slopped :)
Is that the stack you are referring to, or something else?
I shy away from frameworks unless I am building something that I expect a large team to work on, or I specifically need multiple complex features that don't make sense to maintain.
I've written a few applications for my own personal use or targeted at friends/family recently that have no third party dependencies other than Bootstrap, which I still like more than tailwind for anything that doesn't need to look like a marketing page.
Linux is C.
Windows is C and C++.
macOS is C, ObjC, and C++.
I think that's why FastCGI exists. It is the equivalent of PHP's "Swoole" or "FrankenPHP" or "amphp" or Laravel's "octane", etc. Each process is long-lived and tries to clean up its state as much as possible between requests. Forking a new process can take around 8ms (https://news.ycombinator.com/item?id=19005586)
When I was building https://github.com/Qbix/webserver in PHP, I was faced with forking a new process per request. I implemented fork-per-request at first, similar to PHP-FPM and then implemented the "FastCGI" approach of long-lived processes that clean up after each request. (Modern PHP has introspection so the engine can know what to clean up, before calling the php files to handle things.) However, some things are not really possible to completely reset in PHP, and you have to be careful with database connection pools, etc. Typically those are left open even by preforking processes like php-fpm.
Anyway, I ended up flipping it: use the "FastCGI" approach as a default, and if the original PHP code doesn't work (rare), then for that app, you can choose the regular CGI approach of forking a process per request. That way you get maximum speed by default, and manually opt into safety (this is an exception to my usual priorities).
PHP is actually quite fast. But I was also interested in transpiling it to C. So I built the transpiler at: https://ulanguage.org/playground ... used Claude to transform about 1000 PHP functions to their native equivalents, but if you use any of the others, it would hook into the Zend runtime and link with it. (There are some nuances with zvals and objects, and resources in previous PHP versions, but I managed to overcome them.) By the way, it also works with Node and Python FFI the same way.
You can basically now take PHP, Python or Javascript/Typescript code, and speed up the CPU-bound parts about 10-50x. A lot of that is because of string and JSON handling. Many web apps use strings and JSON a lot. So underneath, I had the compiler use "ropes" for the strings, and avoid concatenation. It also parallelizes the JSON encode and decode into code that can run on multiple cores at once, i.e. large JSON payloads are encoded and decoded in parallel. Those are just huge speedups.
Anyway... all of this is mostly focused on speed optimization, so you can solve the literally C100K problem (forget C10K) today, on a regular box. If you don't need speed or FastCGI, your approach is fast enough for the vast majority of applications.
PS: By the way, if you fork after your program already loaded the framework and initialized the configuration, then you can run a lot more workers, literally 10x-100x more of them, because Linux and Mac will share all the loaded memory between processes, and do Copy-On-Write only on dirty pages. And when your request is done, and the process exits, that custom memory is efficiently cleaned up by the OS. It is this feature that allowed me to finally get the best of both worlds... run all existing PHP code, that makes blocking I/O requests all over the place to the database, filesystem, network etc. while at the same time being able to handle 100,000 requests in flight at the same time! Swoole and FrankenPHP can't do that.
if not, you could have sent patches and have the best of both worlds
My goal for speed was to run all existing unmodified PHP code out there, but handle 10x as many requests and throughput
Well, and also a ton of other features too
Visited this website out of curiosity, and... Please do not use Courier New as a monospace font, not even as part of a fallback. Its stem width for body text is often less than a pixel, which looks awful.