A Modern Web App in C
prophitt.me
prophitt.me
I think the conclusion that CGI made sense to die then but not now is probably correct, but for a different reason.
The main reason CGI died in large part is because it wasn't fast enough. Not the only reason for sure, but this is the general consensus. Spawning cgi children was slow, etc. Solutions were built to do in-process CGI, or separate CGI daemons. This even happened per-language with in-process CGI support for Perl, etc.
In the end, as people wanted more speed, they moved away from CGI to various other ways of doing things (client side, etc).
This aspect probably no longer makes sense - i'd be shocked if you couldn't make CGI "fast enough" these days without anywhere near as many hoops. To the degree hoops are necessary, they are probably saner to implement.
That doesn't mean folks should move back to CGI of course - but as i said, it is probably accurate that the main reason it died probably does not hold up anymore.
Irony is that it's most useful for hobby projects. In production I'd not use it.
That is one way many agencies have reduced their delivery teams, a bunch of cloud products tied together, and infrastructure is someone's else fault.
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 ..
I am offended on behalf of all for whom that is more RAM than their first computer had :)
Seemed like a lot when I only had 2MB…
512 megs is not a lot for an embedded Linux project these days, but it can do a lot. Especially if you use Lua. ;)
local SSEHandler = class("SSEHandler", turbo.web.RequestHandler)
function SSEHandler:get()
self:set_async(true)
self:set_header("Content-Type", "text/event-stream")
self:set_header("Cache-Control", "no-cache")
self:set_chunked_write()
-- first event
self:write("data: hello\n\n")
self:flush()
-- later events via IOLoop
local ioloop = turbo.ioloop.instance()
local count = 0
local timer
timer = ioloop:add_timeout(1000, function()
count = count + 1
self:write(string.format("data: tick %d\n\n", count))
self:flush()
if count < 10 then
timer = ioloop:add_timeout(1000, timer) -- reschedule
else
self:finish()
end
end)
endThis 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/
It's something I've been picking at in my free time but these posts from the last week regarding game decompiling old games and rebuilding the as web apps have inspired me to pick it up again. It's interesting to me how many people were critical of the performance of these games and that recent Sim City clone definitely had my laptop fans whirring but I find that my game doesn't have any of these performance issues. Performance-wise WebAssembly and Emscripten seem remarkable, and I haven't even added SIMD or multicore functionality yet.
One of the nice features about building it this way is that the desktop version can easily interface with 3d model / image generators which I intend to in the desktop unit-editor to generate assets for the game.
The pipeline that I'm envisioning is a 3d model generator that pushes the models into Blender to generate different perspective stills of them and then feed that to a stable diffusion image generator that paints them and a unit icons and such, and a voice model that generates their "ready to serve" peon type sounds when you click on them. I'm picturing a system that I can leave over night unattended to generate a bunch of units that I can look at in the morning to add to the game if I like them.
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
Might be useful to test your own implementation against.
But for the point I was making, it is in fact not an alternative. In my version each SQLite connection is confined to a sandbox, and a C memory bug cannot corrupt the Go runtime, call stack or heap. In their version, a C pointer becomes an unsafe.Pointer that can address all Go memory.
That's a bold claim not supported by facts. The following one had not had any RCE for 27 years, since its inception, and counting. I am certain there are more C web servers written securely.
I also don't exclude the possibility of formally verified C code like seL4 having no RCEs, etc.
Some of that is protecting your app by using pre-existing components that nothing to do with your app or business: Load balancing, DDOS protection, ect, ect. There's no need to write this kind of stuff from scratch unless you're in some kind of performance-sensitive niche or otherwise are large enough to justify getting into these weeds.
> You need to write your own utilities to handle things like:
>
> Cookie parsing and creation.
> Query parameter parsing and validation.
> Form data parsing and validation.
> Escaping input for display.
This is a short list of the most common places where developers screw up and write vulnerable code. I'd definitely want to lean on well-tested, mature libraries for this sort of thing.Otherwise, the general idea is appealing. I always liked writing in plain C, and I felt I understood what was going on. In modern web frameworks so much of it feels magic. Just learn the incantations and cast the right spells.
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
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.
The answer is: it does but via community plugins.
IIRC the spec itself says it was built for C applications.
mod-perl was a godsend, of course.
For example, it wasn't uncommon to have C extensions for Perl scripts dropped into CGI directory.
But the very first CGI scripts I wrote were in C, late 95 or early 96. Apache on Slackware Linux.
I know very little of web technology, just a faint memory from when I took Odin Project years ago.But surely there are hackers out there that can give us good justification why we currently need such a long list of stack?
Why do people use : TypeScript + React + Next.js + Tailwind CSS + shadcn/ui + PostgreSQL
I honestly dont know.
Are these still needed in 2026
Maybe in just some use cases such as high interactivity + needing to scale ?
Also it's the code version of this old phrase: ("I Had More Time, I Would Have Written a Shorter Letter") https://quoteinvestigator.com/2012/04/28/shorter-letter/
As long as the tokens keep flowing we'll all be fine. If they stop flowing we're going to be boned with this mountain of low level software we're creating that will need to be maintained. Let's not think about that.
And unless you're writing in assembly or raw machine code, "low level" isn't quite the right terminology. There's nothing more inherently complex about maintaining code in Rust, Go, or even C than in TypeScript or JS.
Given the option, I will always go to a Java or .NET framework.
Using anything other than that, the decision wasn't done by me, rather my employer or customer.
The only reason I would not expose C or C++ directly into the Internet, is as Gen X I have been there, and don't miss the possible security issues, which I also failed to match 100% even though we already used tools like Purify, BoundsChecker and Insure++.
Need has never been the correct word.
You can get quite far with boring enterprise stack.
Me Raw php .
Many people here have machines that can handle all of AOL , yahoo, msn of 2001 combined in their closet .
I have been going backwards in time as an experiment . I use mature extremely documented tools . Has to be at least 20 years old
SPA's and their frameworks still have their use cases. I don't think you can really get full "simplicity" when you have a lot of people working on the same code that goes into enterprise production. I do think that a lot of the more complicated stacks mainly exist because "that's how we've always done it". Which is frankly also a fair consideration if you're not in a position where changing it would generate a lot of value.
I find that it always comes down to people. The fewer people you are, the easier it is to make rapid changes. With a lot of people, and maybe people with very different backgrounds and skill-sets things changes. Change management isn't easy, and often the actual tech stack will matter a lot less than building a good team around it. You obviously still want to work toward changes and approvements, but switching a 100 people from their safe and overly complicated tech stack to something else will take a while. You'll also have to be damn sure it's worth it.
I think the author of the article has the right idea, although they do not use AI. But I think with AI now, people are anyway stepping out of what they would normally dare do, and so one person can accomplish more than before. So it makes sense to simplify again, although again I think for most people this will mean use AI.
Implementing an HTTP server from scratch in C is quite simple, clients are harder though, so I would keep using libcurl.
HTTPS can be tricky though, so you probably want to delegate this part to a reverse proxy.
Implementing an HTTP server is easy, but implementing a fast, feature-complete and secure one is complex.
What you gain by rolling your own instead of using a battle tested solution is a reduced attack surface. For example, an off-the-shelf HTTP server may be tricked into revealing the content of a file the user should not have access to, but if your custom server doesn't read files at all (you don't even make a open()) because everything is in RAM, or hardcoded in the executable itself, then such a vulnerability cannot exist. You may also decide to allocate most of your memory statically, because you know in advance how much you will need, and doing that, eliminate an entire class of potential vulnerabilities. An off-the-shelf HTTP server doesn't have this luxury.
And with one less dependency, you are also less vulnerable to supply chain attacks.
When I start to dig into how complex async is to debug I'm really starting to come back around to regular forking.
> 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.
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
I am not sure I like the idea that the binary will be called by nginx on each request. I prefer the backend binary to have state. A 256 MiB virtual instance is enough for any software shared between friends, family or even a small community.
SQLite backups are easy. I really like how I am building web apps these days. Been with PHP for 7 years and then Python for 10+ years. Single binaries are awesome. I am looking at multi-server backends as well, still 256 - 512 MiB per instance but with a shared Redis. Still with SQLite. Should easy enable medium sized business software.
> All too many times have I:
> * Fought vim to highlight Go's html/template syntax.
> * Fought helix to detect Go templates without breaking html detection.
> * Fought rustrover to recognize jinja templates for a project using askama.
> Using C means there's no second language for your editor to half-detect, no mixed-syntax where highlighting falls apart, no plugin dance to get the two languages to cooperate.
None of that has to do with C. You can apply the exact same approach in Go, or in any other language for that matter.
In other words, you are the supply chain. You're responsible for the bugs you create, but at least they are your bugs not caused by upstream's upstream's upstream's maintainer installing an infected JS library.
Shall we call this "vertically-integrated programming" then? :-)
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;
}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?
Lean yet complete standard library is what should be preferred instead of that supply chain, especially in AI era that makes half of them obsolete and redundant.
Cargo is a danger to society. People need to stop using these unsafe and dangerous technology.
The typical C project has far fewer dependencies than the typical Rust project. Those dependencies are usually individually larger and either maintained by OS maintainers or there is a copy of the code that is built for the project and is readily available for audit. Those copies can't change without direct action.
Its less trouble to setup static analysis, address sanitizer, and similar tools for a C project than it would be to manage local copies of thousands of dependencies for a Rust project.
The sole advantage of Rust over C or C++ is that the borrow-checker provides a set of guardrails for the AI so that certain classes of bugs become impossible to generate, and then the compiler error will feed back to the AI so that it knows it's gone off the rails. This is a common failure mode for AI: frequently it does what you tell it to, but then its attention mechanism isn't on things like security or robustness and so it unknowingly introduces a crash or memory safety violation. The sole advantage over Java is that it generates faster code, which is normally something you have to prompt the AI to do. All the stuff about programmer ergonomics or ecosystem health is meaningless, because there are no programmers.
I also disagree that even if you vibe code in natural language (English or any other), platform selection still matters and it’s naturally a preference and experience related reflection of a vibe coder who positions himself as architect, product owner and analyst more than a single platform choice zealot.
In such case selection of modern C/C++ is equally revenant as religiously pitching about a single rust feature as differentiator here. It simply reflects the ability to control author’s vision of the outcome and ability to control it.
Better and safer than C. Much nicer for the programmer than Rust.
But the website itself is awful. I mean, it's impossible on desktop or mobile to tell what buttons are active or disabled. There is basically no UI at all to tell you what you're dealing with or what you get if you unlock it by signing up.
And I'd say I appreciate the raw simplicity of the dark mode interface, except it also looks like it's LLM-generated, even if it's not.
So, I'm not sure what the gains were here, really. I like the hand-written approach, but wish it had some sort of personality. And not to be a debbie downer, but the purpose of this site is also to entice some long tail of small game developers to pay to feature their games with influencers on youtube, twitch, etc. which, as a business model, fine, parasitic but fine, but maybe the most depressing thing I've seen all year even in the middle of the death of most of the industry. On top of that, you're making people pay to sell dead games to dead-eyed streamers on a site that looks like a fucking mortuary. At a glance, there are even more influencers dying to get paid on this site than there are shitty AI slop games going out now; and I don't even know who's playing them; so zayt gezunt to everyone involved.
The best thing about this is that it was written in C, without AI. Ostensibly. Everything else about it makes me want to shoot myself.
My art director critique: make some fucking bright pink rollovers on the buttons or something. At least. Add a stupid pink polar bear as a mascot. Don't make me feel like I'm selling my soul to satan by signing up for it.
edit I'm still confused by which assets were compiled into the C .exe. Are there assets?
A few years ago one would have thought that it would not be worth the pain to develop it in C, now with AI that is no longer a concern as well, and we're so glad we did not compromise.
I only use it in C++ but I'm glad to have memory safe constructs and other modern primitives to keep it somewhat verifiable by me.
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
Eh? What's the point of a `fn` keyword if I can't `grep -r "fn foo"`?
That may be a me problem.
Large portions of the standard lib depend on heap allocations. C is a much safer target for environments with limited resources.
I frequently do that myself on simple C++ programs just for compile time reasons alone, where I'll use stdio.h instead of std::iostream or std::format.
Depends on the metric.
1. If the metric is "How many features can I have", then, sure, C++ wins - it has everything.
2. If the metric is "how few footguns are there", then C++ loses by a mile - there's too many ways that the different features from #1 above can interact with each other to make it viable to keep track of all the footguns when writing code.
You may not value having fewer footguns and/or lower cognitive burden, but many do.
[0] - the other one: https://news.ycombinator.com/item?id=50051573
You can do that in other languages but you need to use aditionnal dependencies (modules or bindings).
¹ The exception is the Nouveau driver, but without it X was also perfectly stable for me.
I took the concept of "slots" in requests, from it: https://github.com/okws/okws
But more to your point, I think what makes this httpd scheme especially attractive (for C users especially) is that you can't try to "optimize" anything. There is no temptation to lock/mutex/cache yourself into more problems. Its 1 process that then dies. Even 'free' becomes optional in some cases.
If the latter, then it’ll only contend on simultaneous writes, which mutex anyway.
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.
Not sure why but it probably had something to do with Microsoft and the browser wars.
Is it time to fix web development?
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'd wager a CGI written in C uses less memory and CPU than an equivalent application in any other language or framework. I'd wager this design has lower latency meaning higher throughput and therefore higher capacity, too.
Or perhaps you mean developer efficiency? Well, with the right tools you're going to catch memory issues so they're gone. It's a tiny language you can keep in your head, so that's not an issue - I'd argue you need an LLM to write good C (if you know what you're doing), than to write good Java or Python.
I sure do love a modern language and environment (I spent years as a Rails dev, and I love me a bit of Elixir), but to argue that C is not efficient is kind of wild.
Except I had no idea malloc existed and the whole thing was static arrays...
Most of my initial foray in programming was on microcontrollers, of which dynamic memory allocation came late.
Atmel ATMega didn’t support it at all. Rabbits supported allocation but not freeing, so, static memory with extra steps. It wasn’t until I was using cortex’s that the idea of using malloc was even entertained.
Even still today, I pretty much stuck with static global with reference passing or allocate everything at initialisation time. It’s a surprisingly robust strategy.
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 :)
Give it a shot; I bet you'd like it a lot.
Um, any and all string handling in C is pretty miserable.
These modern languages have both easy-to-use "owning" string types and a "string slice" type that can be passed around efficiently. Unlike C, in these languages a string and a slice are largely interchangeable, which makes them easier to use throughout not just your own codebase, but third-party libraries too.
For example, the Rust `httparse` crate `Request` type returns parsed fields as string slices, not strings, which means it doesn't allocate anything on the heap: https://docs.rs/httparse/latest/httparse/struct.Request.html
The last update seems > 5 years ago.
Maybe not all projects benefit from the full package but some things are now so commoditized that you should have them everywhere (and for heavier project add readiness/liveness probes, configuration management, live documentation, connection pooling, background task scheduling, dev tools like profiling and tracing)
Speaking as someone who hand-rolled their server stack as a C++ framework for years, there is nothing in there that can't be done relatively easily. Some things certainly get easier even, the actual change in paradigm here is not that it's a hand written web app server in C, it's that it runs in its own server container which you have to manage. That's the actual tradeoff. You can't just deploy to Vercel.
One could simply put a Node.js server directly on the line. And then it is crystal clear who calls the business logic. I mean, there are reasons why we use Nginx in front of it. Distribution, filtering, rate limiting, etc..
BTW, the cgi-bin competitor back in the days was JavaScript too with the Netscape Communication/Enterprise Server that supported server-side dynamic JavaScript in 1997! Funny enough. :-)
which also makes me wonder if it wouldn't have been more programmer-friendlier had it been written in Perl?
1: How is the database backed up? Some kind of cron job to an external server? Stored on some kind of network drive with automatic versioned recovery?
2: How will scaling be handled?
3: How is disaster recovery handled?
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.
LMAO, I'm sold.
To give you more context. I recently closed a security gap in a customer's web server that most likely resulted in a data leak. That web server stored (of course) database backups and other personal data. The directory listing was visible and accessible from the open internet, because - note that - the .htaccess file where the access was disabled had INVALID END OF LINE CHARACTERS according to Apache (and it wasn't me who prepared it in the first place). Therefore - note - the server IGNORED that file and its default setting was OPEN ACCESS and enabled directory index. It's wrong on so many fronts. The Apache's philosophy is not "I cannot parse the config file, therefore I disable access to the directory tree until administrator notices and fixes it". It was not logged in the server logs as "invalid EOL detected". Apache after over 30 years of development cannot parse all 3 end of line sequences - the process which is TRIVIAL to the point of being ridiculous. Deciding not to include CRLF or bare CR is not about performance. It's just the decision of its makers, there's no justification these days. It violates "be liberal in what you accept and conservative in what you send". In 2026, when the cybercriminals are everywhere, just waiting for the data, scraping it all over, the server's default should be "close everything until it's explicitly open" and "if the config is broken, disable access and/or fail".
You didn't.
I'd also take issue with the "poor performance" slur. It's never going to beat out lighttpd for pure static speed but all the "poor" benchmarks I have see for it are entirely due to low quality (or no) configuration. Configure it properly and it flies.
Linux is C.
Windows is C and C++.
macOS is C, ObjC, and C++.
clearly AI slop
(I won't even get started on the issues of creating new software in a language that is unsafe by design, and where any performance advantage will be dwarfed by the cost of forking. Whatever floats your boat, dude.)
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 mean, the article should not be taken seriously on any technical level. It's ok just for fun.
"Make no mistakes."
Or a comfy scripting language with a great FFI as X intended.
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.
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).
Take whatever is pushing the site over the wire (nginx, Apache, lighthttpd) and use its public APIs to implement your web application.
You can make your nginx.conf list the routes along with the C functions for each route, which nginx looks up in your shared library.
You can host numerous web apps just by updating adding an .so and updating the nginx conf.
This is also structurally more efficient your hand rolled web server which in all likelihood has to reverse proxy via nginx anyway.
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.
You can write a modern web application in C. I shipped one. One binary, a SQLite file, three dependencies and the entire thing fits in your head.
> I'm a minimalist at heart. I use classic Vim with zero plugins. That means no LSP, no autocomplete, no code formatters, and no AI.
yet the author gives us an AI-written blog post :p