11 ms·
An HTTP server in a single .c file
- rualca 6y agoI was about to submit this very link after reading this HN submission: This blog is now hosted on a GPS/LTE modem (Apr 1, 2021) https://news.ycombinator.com/item?id=26669749 https://news.ycombinator.com/item?id=26669749 So darkhttp is a terribly simple HTTPS server that only supports GET and OPTION but is more than enough to serve static content.
- kragen 6y agodarkhttpd supports HEAD too!
- spzb 6y agoBetter link since submitted one appears down https://github.com/emikulic/darkhttpd https://github.com/emikulic/darkhttpd
- zoomablemind 6y agoalthttpd is another single-binary webserver [1]. It's from the creator of SQLite. Also single-binary Fossil SCM, by the same author, internally implements a webserver (which is multiplatform, by the way) [1]: https://sqlite.org/althttpd/doc/trunk/althttpd.md https://sqlite.org/althttpd/doc/trunk/althttpd.md
- silasdb 6y agoI'm using it as well and it is great. The well designed approach to virtual hosts and CGI is relly awesome. Zero configuration needed.
- indigodaddy 6y agoVery cool! It talks about using stunnel for https, but for ssl termination I could just as easily put caddy or haproxy in front of the standard (non-https) http althttpd right?
- lifthrasiir 6y agoOne of 2004 IOCCC winners (2004/hibachi [1]) was an 154 line long CGI and vhost capable HTTP server [2]. It is one of a few winning entries that ever came with its own ./configure, and violated so many guidelines but not a single rule, making judges pretty upset. Its obfuscation was pretty basic even at that time and it won solely out of the novelty factor. [1] https://github.com/ioccc-src/winner/blob/main/2004/hibachi.hint https://github.com/ioccc-src/winner/blob/main/2004/hibachi.h... [2] https://github.com/ioccc-src/winner/blob/main/2004/hibachi.tar.gz https://github.com/ioccc-src/winner/blob/main/2004/hibachi.t... (yes, it is also probably the only winning entry that its source code is packaged into a tarball.)
- 13of40 6y agoI wish I could dig it up now, but years ago I ran a QA team that needed to validate that our equivalent of man pages could be downloaded on the fly with a command. The guy who owned it came up with a test plan that included setting up and maintaining a web server just for this purpose, and was dragging the project on for weeks until I sat through lunch one day and wrote him a bare bones HTTP server in Powershell that we could just start locally and talk to at run-time. I'm pretty sure the first cut of it was only like 50 lines of code.
- majkinetor 6y agoToday you can do it in single line.
- aflag 6y agoWhat is the sort of guidelines it violated?
- lifthrasiir 6y agoIt wasn't obfuscated at all. The guideline doesn't explicitly dislike such entries, but as a side effect of not being obfuscated it implicitly violated several guidelines at once (e.g. it was quite "longer than [it] need[s] to be"). Indeed, today's nginx source code [1] would be probably slightly more obfuscated than this code modulo perhaps name changes (and that can be considered as a minimal effort to get the entry within the size limit). Its only obfuscation was reusing a buffer from getenv() pre-initialized (referred as to "getenv() == "putenv()" in the hint). [1] for example, https://github.com/nginx/nginx/blob/8365f73/src/http/ngx_http_parse.c#L108-L128 https://github.com/nginx/nginx/blob/8365f73/src/http/ngx_htt...
- imwally 6y agoI'm not being snarky, but is a "single .c file" a good metric? It could be a rather large single file, right?
- kzrdude 6y agoIt could be sqlite or a lua interpreter, yes.
- rualca 6y agoI agree, jamming a program in a single source file is not the best metric ever. To be fair, that's just a line in the project's Readme, which is far from the best and doesn't even provide a concise description of what the project does.
- avhon1 6y agoBeing in a single .c or .h file does make a project easy to incorporate into the source tree of other projects.
- Koshkin 6y agoHere's one, small enough to fit in a comment: #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> main() { int rc; struct sockaddr_in ssa, csa; int ss, cs; memset(&ssa, 0, sizeof ssa); ssa.sin_family = PF_INET; ssa.sin_port = htons(8080); ssa.sin_addr.s_addr = htonl(INADDR_ANY); ss = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); bind(ss, (struct sockaddr*)&ssa, sizeof ssa); listen(ss, 5); for (;;) { int ln; static char rq[1000]; memset(&csa, 0, sizeof csa); ln = sizeof(struct sockaddr); cs = accept(ss, (struct sockaddr*)&csa, &ln); memset(rq, 0, sizeof rq); recv(cs, rq, sizeof rq, 0); if (!strncmp(rq, "GET /", 5)) { char *b; FILE *f; char *fn; if (b = strchr(rq + 5, ' ')) *b = 0; else rq[sizeof rq - 1] = 0; if (fn = malloc(strlen(rq + 5) + 5)) { char *t, *type(); strcpy(fn, "www/"); strcat(fn, rq + 5); if ((t = type(fn)) && (f = fopen(fn, "rt"))) { int n; static char h[] = "HTTP/1.1 200 OK\nContent-Type: "; static char c[1000]; strcpy(c, h); strcat(c, t); strcat(c, "\n\n"); sendall(cs, c, strlen(c)); while ((n = fread(c, 1, sizeof c, f)) > 0) sendall(cs, c, n); fclose(f); } else { static char h[] = "HTTP/1.1 404 NotFound\nContent-Type: text/html\n\n404 Nice try!"; sendall(cs, h, sizeof h - 1); } free(fn); } } close(cs); } close(ss); return 0; } sendall(s, c, n) char *c; { int m; for (m = 0; (m += send(s, c + m, n - m, 0)) < n;); } char *type(fn) char *fn; { static char *s[] = { ".html", "text/html", ".asm", "text/plain", ".c", "text/plain", ".py", "text/plain" }; int i; for (i = 0; i < sizeof s / sizeof *s; i += 2) if (strstr(fn, s[i])) return s[i + 1]; return 0; }
- 6y ago
- _wldu 6y agoHere's an HTTP server in one line of bash: https://www.go350.com/posts/finding-a-hacked-server/#a-quick-fix https://www.go350.com/posts/finding-a-hacked-server/#a-quick...
- iod 6y agoHere is an HTTPS server in one line of posix shell and openssl¹ which also works as an alternative to `python -m SimpleHTTPServer`: openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-384 -sha512 -subj /CN=localhost -keyout - -out - -nodes -addext subjectAltName=IP:::,IP:::1,IP:0.0.0.0,IP:127.0.0.1|openssl s_server -accept 8443 -WWW -no_ssl3 -no_tls1 -no_tls1_1 -cert - I put this into https.sh marked executable and run anywhere. ¹ https://www.openssl.org/docs/man1.1.1/man1/openssl-s_server.html https://www.openssl.org/docs/man1.1.1/man1/openssl-s_server....
- SavantIdiot 6y agoSigh. Yes, ANYTHING can be one line... if you're allowed to call tens of thousands of lines of code compiled in a different library. It really defeats the purpose of showing off minimal coding skill if hundreds of other developers did all the heavy lifting over three decades.
- robotresearcher 6y ago# one line of Bash apachectl -f ./httpd.conf
- 1vuio0pswjnm7 6y agoSounds like you are describing Python, Go and whatever is the next programming language du jour. Folks "showing off minimal coding skill" proclaim "X in Y lines of code" for these "batteries included" languages all the time. Yet I never see any snarky comments pointing out the size, age or authors of the libraries they are using. All the parent comment did was demonstrate using the openssl binary, which is ubiquitous. The GP referred to using nc. Assuming the original from 1995, that is not 10s of 1000s of LOC and was not written over three decades. It is the work of one person. Yes, it is true. Everytime we use the OpenSSL library we are using 10s of 1000s of LOC written by a changing team of developers over several decades, a project with a long legacy of ad hoc development and mistakes. I doubt the parent is trying to take credit for any of that "heavy lifting".
- kragen 6y agodarkhttpd is cool, and it's pretty featureful for a small program, but it's also 2500 lines of code. If you liked it, you might also like my server httpdito: http://canonical.org/~kragen/sw/dev3/server.s http://canonical.org/~kragen/sw/dev3/server.s which has documentation in http://canonical.org/~kragen/sw/dev3/httpdito-readme http://canonical.org/~kragen/sw/dev3/httpdito-readme. It's also an HTTP server in a single source file, but it's i386 assembly, for Linux, that doesn't use libc. It's only about 700 lines of code, and the executable is up to 2060 bytes now that I've added CSS and PDF support to it. httpdito is, to my surprise, practically useful on occasion as an alternative to things like `python -m SimpleHTTPServer`. Unlike the server in Koshkin's excellent comment https://news.ycombinator.com/item?id=26672683 https://news.ycombinator.com/item?id=26672683 httpdito does send CRLFs as the HTTP standard demands. Unfortunately it also requires them in the request. Also—and this is a key point on which most software falls down—httpdito's documentation contains a section entitled "Are you insane?", which really is a question every programmer ought to answer in their documentation. It doesn't have most of the things darkhttpd has, though. It doesn't support IPv6, HEAD, directory listings, byte ranges, conditional requests, keepalive, name-based virtual hosting, sendfile, logging, chroot, or privsep, and doesn't run on Solaris, SPARC, or ARM. But if you're looking for features like "small memory footprint" and "no installation needed" and "no messing around with config files" it may actually be better than darkhttpd. Due in large part to improvements in modern Linux and its small memory footprint (normally 5 pages) it's efficient enough to saturate a gigabit connection.
- mysterydip 6y agoDo you have plans for any of those features, or is it complete for the use cases it is solving?
- kragen 6y agoNo, I didn't write it to solve use cases at all, but rather because I thought it would be awesome to write a web server in assembly. As the readme says, we all have moments where we do things we regret. Youthful† lapses in judgment. If you git clone http://canonical.org/~kragen/sw/dev3/.git http://canonical.org/~kragen/sw/dev3/.git you will see that I wrote most of it one weekend in 02013, removed the dependency on libc and added forking the next weekend, and tweaked it slightly over the next month and a half. Then in 02019 I added CSS and PDF to the mime-types list. So it's not, like, an ongoing project. It's just a program I wrote once, like most things in that directory. I did just fix the documentation, though, because it was still claiming it was under 2000 bytes. ______ † Technically 37 is not actually that youthful.
- Twirrim 6y agohttps://github.com/avleen/bashttpd https://github.com/avleen/bashttpd for a bash based one (that uses socat or netcat under the covers)
- deleted 6y ago[deleted]
- runawaybottle 6y agoWhat are the performance implications of something like this (and other similar ones posted here) for a really light server (let’s say a small chat server). Would getting down closer to the metal matter much?
- jeffbee 6y agoThe performance problems of HTTP servers are architectural, not usually about things like instructions per cycle. For example common topics are whether accepting connections will tend to starve serving established connections, whether events are distributed optimally among threads or CPUs, how the server behaves if a client sends one byte per frame, very slowly, or if the client receives the response very slowly, whether the process will run out of memory if a number of events happen suddenly, etc. A web server that's actually equipped to survive as a public service will be much, much longer than 2500 lines of code.
- kragen 6y agoWhile mostly I agree with your comment, I suspect both darkhttpd and httpdito (linked above) are actually equipped to survive as a public service. httpdito is about 700 lines of code (in assembly); it handles these issues as follows: 1. Accepting connections starving established connections: this is mostly a problem for event-driven servers. httpdito runs each connection in its own child process, has a compiled-in limit of 2048 concurrent connections. Whenever it has 2048 live children, it blocks in waitpid() until at least one of them dies. Modern Linux does a fair enough job of scheduling that this seems to be sufficient in practice to keep the parent from hogging the whole machine and preventing the children from making progress, even if most of your incoming connections just close without sending an HTTP request or any bytes at all. Darkhttpd might have a problem with this or not, I haven't tried, but it seems to rely on OS ulimits to limit its number of concurrent connections. My guess is that with the default ulimit -n 1024 darkhttpd won't have any problem with this at all. 2. Whether events are distributed optimally among threads or CPUs: the real issue here is not optimality but stability, and again, this is mostly an issue for event-driven servers (or servers made of thread pools, etc.) The essential property for production is usually not that you're using the machine to within 0.1% or 10% of its capacity, but that you're not wasting 99+% of its capacity uselessly thrashing—or, at the other extreme, responding so fairly that you provide some service to every client, but not enough to actually ever complete a whole request successfully. Modern Linux does a good enough job of this that httpdito's very stupid approach of forking a new child for each request works reasonably well. darkhttpd avoids the problem in a different way by not having threads and not using multiple CPUs, which is clearly not optimal but also totally avoids any related stability concerns. 3. How the server behaves if a client sends one byte per frame, very slowly: in overload conditions, which is when this matters "to survive as a public service", Linux's TCP stack does a reasonably efficient job of consolidating the bytes until a particular server child gets a chance to run. httpdito does a super stupid O(N²) thing in these cases where it looks for the CRLFCRLF at every position in the buffer after every received chunk (potentially a single frame), but this is ameliorated not only by Linux's TCP stack but also by the fact that the request buffer size is only 1024 bytes, so in the worst case we spend 4084 instructions on an incoming packet the last time we run this loop for a given client, and only half that (2042 instructions) on average. This is still fast enough to handle somewhere in excess of 4 million packets per core and probably faster than Linux's TCP stack. The flip side of that is that by sending data slowly in this way, or by not sending any data at all, a client can monopolize all 2048 concurrent connections, preventing other clients from connecting. This is a problem that cannot be solved in TCP/IP, only ameliorated. httpdito ameliorates it with a 32-second timeout, counted from when it accepts, so in the steady state it always accepts an average of at least 64 new connections per second, regardless of how fast or slowly the requests come in. I haven't tested darkhttpd under these conditions but it has a 30-second idle timeout and a 4000-byte max request length. It kind of looks like it does the same O(N²) thing, but I've only glanced at the code. I don't know how long it allows for the client to slowloris the initial requests in one byte at a time. 4. How the server behaves if the client receives the response very slowly: I think the above covers this. However, extremely large files or extremely slow connections will whack into the 32-second timeout. At 9600 baud, for example, responses of more than about 30 kilobytes will get truncated. There's an unavoidable tradeoff between handling slow connections successfully (which requires many memory buffers), handling fast connections efficiently (which requires large memory buffers), and withstanding DoS attacks (which requires, among other things, limiting memory use). 5. Whether the process will run out of memory if a number of events happen suddenly: httpdito does all of its within-process memory allocation at compile time, so this isn't a thing that can happen within a process. Its limit of 2048 children works out to about 16 mebibytes of RAM typically, so it's not a problem systemwide either, unless you're running it on a Rio Receiver or something. darkhttpd does allocate memory, but at first glance it seems to be limited to a couple of allocations of less than 4000 bytes per concurrent connection, so I don't see a problem here either. It's common for event-loop servers to handle overload events like these much more gracefully than multitasking servers; httpdito is kind of an outlier here because it uses about a thousandth the RAM of a normal multitasking server, and running on an OS whose task scheduling is less shitty than is typical, which allows it to get away with a lot. (The original slowloris README describes it as a DoS tool for threaded web servers.) By contrast, the usual overload problem event-loop systems have trouble with is where some event handler that usually takes a millisecond or a microsecond suddenly starts taking a second or a kilosecond, locking out everything else until it's done. Maybe you have an O(N³) algorithm in your HTTP content negotiation, but you never noticed because normal browsers send an Accept: header with five content types and so N³ means 125 iterations, and then some guy in Turkey with a screwed up browser configuration sends you an Accept: header with 1296 content-types and your whole server freezes for several seconds. Every three minutes. You could hypothetically make an event-loop system immune to this kind of thing by doing a rigorous WCET analysis on every single event handler, as if it were jet engine control firmware. But that's a huge pain in the ass for any program of more than about 1000 machine instructions, so I've never heard of anybody doing this with an event-loop-driven network server. I've been toying with ideas for solving this problem over the years, including approaches like BPF and software transactional memory. Some of my notes on this are at https://dercuano.github.io/topics/transactions.html https://dercuano.github.io/topics/transactions.html, while others are in Derctuo and Dernocua. That said, I haven't tested darkhttpd at all, and I'm not running any public services on httpdito. It wouldn't surprise me if either or both had some kind of stability bug under overload conditions. But it would surprise me very much if fixing those bugs required adding thousands of lines of code.
- nwsm 6y agoHug of death. A bit ironic!
- commandlinefan 6y agoWas it self-hosted?
- jart 6y agoDarkHTTPD appears to be self-hosted on Apache. It's also configured to force HTTPS too, even if the Upgrade-Insecure-Requests header isn't present. That's one of my pet peeves.
- the_arun 6y agoThis is very smart & creative. Without stealing any bit of thunder from OP, there are Free/Open CDNs to host entire Blog package. All you need is a device/machine to generate static content & push them to these free CDNs (eg GitHub pages or Hostry).
- kragen 6y agoIf you want to clone a git repo from your laptop onto your cellphone (or vice versa), or test your CSS for iPhone 6 compatibility while you're actively editing it on your laptop, it's probably a lot less clunky to run a web server locally.
- rkeene2 6y agoI wrote filed [0], another single file HTTP server. It's very fast ! I wrote it because I was hosting containers (using LXC) on a laptop which only had mass storage available via a USB 1.1 (12mbps) connected drive. I was serving video streams out to the various Roku devices in my house from this container, but other webservers would do things very slowly (such as opening the file to serve out, which would require doing a lot of slow I/O) causing periodic interruptions. filed doesn't do those things. It also supports Range requests (required to meaningfully serve media), and a few other things. If you want a static HTTP server, filed can work out quite well. [0] https://filed.rkeene.org/fossil/artifact/d39c0feefb3f9113?ln https://filed.rkeene.org/fossil/artifact/d39c0feefb3f9113?ln
- Ryan_Goosling 6y agodoesn't load
- WalterSobchak 6y agohttps://webcache.googleusercontent.com/search?q=cache:LfJkT-G9DR4J:https://unix4lyfe.org/darkhttpd/+&cd=1&hl=en&ct=clnk&gl=us https://webcache.googleusercontent.com/search?q=cache:LfJkT-...
- ActorNightly 6y agoThere is something even cooler that was posted last month. https://justine.lol/redbean/index.html https://justine.lol/redbean/index.html
- jart 6y agoAuthor of redbean here. Thanks for the mention!
- Thorrez 6y agoYep. darkhttpd is "Written in C - efficient and portable." Portable, psh. How about a single that binary runs on Linux, MacOS, Windows, FreeBSD, OpenBSD, and NetBSD and also contains all the files you want it to serve, and you can add files without recompiling.
- IncRnd 6y agoThe site was down for me, but the archive of the site can be accessed this way. https://archive.is/mXgB1 https://archive.is/mXgB1
- bullen 6y agoIn 7000 lines I add multi-threading, non-blocking IO, dynamic responses, chunking and hot-deployment: https://github.com/tinspin/rupy https://github.com/tinspin/rupy Used in a product by 300.000 end-customers at 90% approval rating without rebooting for 4 years.
- samtc 6y agothttpd (http://acme.com/software/thttpd/ http://acme.com/software/thttpd/), mini_httpd (http://acme.com/software/mini_httpd/ http://acme.com/software/mini_httpd/) and micro_httpd (http://acme.com/software/micro_httpd/ http://acme.com/software/micro_httpd/) from acme software worth taking a look at.
- secondcoming 6y agoWe use microhttpd in a few places. It's certainly lean but its API is odd and how to handle POST bodies is just weird.
- stephc_int13 6y agoVery small HTTP server is a common trick. What I'd like to see is a very small HTTPS server ^^
- rualca 6y agoThere's another comment showing how to use OpenSSL to setup a https web server by providing a magic cli command.
- stephc_int13 6y agoYeah, but using a lib for SSL is cheating, in this context, obviously.
- zokier 6y agoI wonder how small you could make TLS1.3 (or 1.2) implementation if you only supported bare minimum like single ciphersuite, no negotiation, no fancy features, no weird cert formats, but without skimping on the crypto parts?
- userbinator 6y agoAs an order-of-magnitude estimate, probably a few KB of binary.
- Galanwe 6y agoEvery one liner HTTP server is barely one `stunnel` away from becoming an HTTPS server ;}
- AshamedCaptain 6y agoSurprise, simple protocol with UNIXy roots is simple to implement on UNIX. Now try HTTP3/QUIC or any of the other "let's just break all layering and abstraction for performance sake" stuff.
- Matthias247 6y agoThe IETF version of QUIC isn't really that bad on layering. HTTP/3 is fully decoupled from the QUIC transport layer, and thereby e.g. stream multiplexing/flow-control is also fully separated from HTTP semantics (unlike in HTTP/2). Handshake/TLS is also mostly decoupled from QUIC, as are flow controllers (recovery). Compared to TCP is certainly has multiplexing as part of the main layer, and uses mandatory crypto. But that's more or less the point of QUIC.
- deathanatos 6y agoHTTP/1.1 is anything but simple. E.g., https://news.ycombinator.com/item?id=26343577 https://news.ycombinator.com/item?id=26343577 . This also doesn't correctly implement HTTP/1.1 (or /1.0). (It doesn't really claim to, either: the README really seems to hint that it is implementing a subset of HTTP that might work in a pinch. I don't think I'd put a production load on that, though.)
- ExcavateGrandMa 6y agoendless spinning circle... BTW, I was unable to access your network... Is that awful?
- deleted 6y ago[deleted]
- abrkn 6y agoBeautiful!
- textprotocol 6y ago~113 sloc. lua. ucspi-tcp text://protocol server. https://github.com/textprotocol/publictext/blob/main/publictext https://github.com/textprotocol/publictext/blob/main/publict...
- pikseladam 6y agoI'm not familiar with this concept. What you will do with a simple HTTP server? IOT applications?
- ben509 6y agoIt's often handy for browsing documentation you compiled locally.
- moiz_ahmed 6y agomakes you appreciate things like flask
- spaetzleesser 6y agoWhen I look at this code it becomes clear that after using design patterns, dependency injection and other "modern" stuff for too long I have totally lost the ability to write short, concise code :-(
- amelius 6y agoThat's possible because most of the server was already implemented in the OS kernel. The .c file only glues the components together.
- 1vuio0pswjnm7 6y agoNo one mentioned the httpd in busybox. There are of course web servers everywhere now, sometimes in places that we really do not need/want them.
- pmayrgundter 6y agoKudos! I've also found it nice to have a simple http server that I can build with simple tooling (just javac and run). My single file HTTP/1.0-ish java http server, 300 LOC: https://github.com/pablo-mayrgundter/http https://github.com/pablo-mayrgundter/http I use it for most of my local dev. Handles only GET requests, serving files below the directory in which the server is started. Does Content-Type inference based on file extensions (e.g. html, js, png, jpg). Currently disables any type of cacheing as I use it mostly for local development and want to avoid versioning mistakes. Also has fledgling SSL support that has occasionally worked. > ./http_load -p 10 -f 100000 test.url 100000 fetches, 10 max parallel, 1.024e+09 bytes, in 34.9176 seconds 10240 mean bytes/connection 2863.88 fetches/sec, 2.93262e+07 bytes/sec msecs/connect: 0.0995282 mean, 2.717 max, 0.037 min msecs/first-response: 3.18724 mean, 202.23 max, 0.315 min HTTP response codes: code 200 -- 100000
- known 6y agoYou can check https://rosettacode.org/wiki/Hello_world/Web_server https://rosettacode.org/wiki/Hello_world/Web_server
- johnisgood 6y agoI love rwasa: https://2ton.com.au/rwasa/ https://2ton.com.au/rwasa/ (flat assember (fasm)) > rwasa is our full-featured, high performance, scalable web server designed to compete with the likes of nginx. It has been built from the ground-up with no externel library dependencies entirely in x86_64 assembly language, and is the result of many years' experience with high volume web environments. In addition to all of the common things you'd expect a modern web server to do, we also include assembly language function hooks ready-made to facilitate Rapid Web Application Server (in Assembler) development. I love the fact that it uses flat assembler! It is worth checking out their other stuff: https://2ton.com.au/Products/ https://2ton.com.au/Products/ For example https://2ton.com.au/HeavyThing/ https://2ton.com.au/HeavyThing/ supports Curve25519/Ed25519, SHA3/Keccak, SHA512, SHA384, SHA256, SHA160, MD5 HMAC, PBKDF2, scrypt, HMAC_DRBG, and Poly1305. It is quite impressive.