Top
Best
New

Posted by prophittcorey 5 hours ago

A Modern Web App in C(prophitt.me)
314 points | 130 comments
ramses0 4 hours ago|
My favorite trivia is that busybox has built-in cgi-bin support (and `rclone serve webdav` seems remiss to not mention as well). `https://elixir.bootlin.com/busybox/1.37.0/source/networking/...`

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).
rpdillon 3 hours ago|
Mirrors my experience, and it's funny you mention busybox: I just discovered busybox's ability to serve via `busybox httpd -p 9000 -h . -f -v` with a `cgi-bin` folder dropped in the current directory.

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.

MomsAVoxell 3 hours ago||
Instead of busybox+Lua, I just use TurboLua:

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 ..

rpdillon 2 hours ago||
This is exactly the sort of thing I'm really interested in. I'm particularly excited about various takes on Lua+batteries, have looked into designing my own (C wrapper with bindings to networking, JSON, templating, and DB libraries), but found Redbean to be almost ideal so never got past overall design to dig into code.

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!

MomsAVoxell 2 hours ago||
You’re welcome - we share interests, I too am interested in the Lua+batteries approach to life - I’ve used Lua very successfully in many commercial projects, so I’m quite convinced of its power in spite of the warts and thorns that many newbies in the ways of Lua tend to whine about.

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 ..

OleksandrC 4 hours ago||
For people who find interest in weird web applications, I have been working on a game project, in C++, where the client is compiled to WebAssembly with Emscripten, and the application server is also implemented in C++ (behind nginx).

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/

Transformanshen 5 minutes ago||
That's cool
nmstoker 47 minutes ago|||
Nice. I know your comment was on the technical points, but it definitely under sells the game!
hughw 3 hours ago||
It's beautiful
assimpleaspossi 2 hours ago||
I started a web dev company 22 years ago after developing an online menu ordering system for a restaurant I owned. I, then we, did pretty much the same thing as talked about in the posting but we used fastcgi. I started with C because I knew C. That was my real job. It ran so well, the franchise bought the software from me--and then devolved it to all the Microsoft stuff and had issues upon issues from then on out. That's another story.

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.

glouwbug 1 hour ago|
Feels so long ago the way you write about it, but 22 years ago was basically 2005
assimpleaspossi 1 hour ago||
It was February, 2004
unixholder 28 minutes ago||
The deeper you go into complex web technologies & frameworks, the more obvious it becomes that simplicity wins.
benlivengood 3 hours ago||
Golang also makes it possible to serve a web site or service from a single binary. You can even avoid linking libc if you stick to core Go (CGO_ENABLED=0), and it comes with all the http and tls libraries you need to replace the traditional web server (nginx, apache, httpd) as well.

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.

ncruces 43 minutes ago|
Shameless plug: if you value Go, and CGO_ENABLED=0, and SQLite, and are worried about C in a web server and RCEs, may I interest you in my SQLite driver?

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

creatonez 49 minutes ago||
Is the CGI process opening up the SQLite database for every request...? That would have the most insane contention ever.
zbentley 47 minutes ago|
Would it? The file handle doesn’t contend for a lock IIRC; just writes.
creatonez 45 minutes ago||
I mean sure, you could still win if almost no requests are opening the database in write mode
Levitating 5 hours ago||
CGI is great. Sadly nginx doesn't support it natively as opposed to lighttpd.

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...

synergy20 3 hours ago||
I just use lighttpd
Levitating 1 hour ago||
I used it for a long time but the configuration of nginx just allows for more stuff.

I have my own configuration template for nginx: https://git.levitati.ng/Levitating/nginx.conf

huijzer 3 hours ago|||
But the real question is, does Caddy support it?

The answer is: it does but via community plugins.

BirAdam 5 hours ago||
fastcgi wrap.
assimpleaspossi 2 hours ago||
Exactly what I used and wonder why the OP didn't. I'll comment at the top.
Levitating 1 hour ago||
Ease of configuration, I think? I don't remember my gripes with fcgiwrap exactly.

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.

Kuyawa 1 hour ago||
The time is right for AI to generate a basic C web framework just like Node Express with request, response, template, cookies, auth, db, websockets and some other libs

It would be blazing fast, for use by AI itself

Just for fun

nostrademons 38 minutes ago|
Why C instead of Rust if you're using AI anyway?

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?

ok123456 3 hours ago||
A Go binary with CGO=0 and all templates/documents embedded gives you a better overall tooling and deployment experience. Put it in a scratch container for complete isolation.
doublepg23 3 hours ago|
CGO=0 came in clutch at previous job when we were deploying Go apps compiled on builders that had a newer glibc than the actual prod server.
Panzerschrek 5 hours ago|
> There's no separate database server. You open the database file through SQLite's C API.

> 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?

citrin_ru 4 hours ago||
SQLite does allow write access from mulitple processes - file locks (fcntl) are used to ensure at any given moment only one of processes modifies the database (or WAL if WAL is enabled).
prophittcorey 4 hours ago|||
SQLite can handle multiple concurrent requests if it's set to WAL mode. Reading is all non-blocking/concurrent but writing is still synchronous. You should also set the "busy timeout" or else your blocked writes will timeout immediately.
tomjen3 50 minutes ago|||
In Wal mode you can have multiple readers. Writers block, but because there is no network traffic, the writers finish very quickly, so in practice you don't usually wait that long.
warkdarrior 4 hours ago|||
Users who send concurrent requests will be summarily banned and their data deleted. It's the only way to be sure.
huijzer 3 hours ago||
SQLite is a database. The main idea of a database is to support concurrent reads/writes although databases differ in how much concurrency they allow
td521534f12 3 hours ago|||
The answer is more complicated for SQLite: https://sqlite.org/isolation.html

> 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.

sophacles 2 hours ago|||
The main idea of a database is to support storing data in a structured and queryable way. Concurrency came later.
More comments...