Hi everyone, this is srv, a minimalist http server and file browser. I felt like lobsters readers would be interested in this kind of software.
I made it as a much lighter and faster alternative to python -m http.server
. Benchmarks can be found here.
If you look at the source code, I went to some pretty great lengths to really make things as fast and simple as possible. Golang made it really easy to write something like this, I like the language a lot.
Please open issues if you’d like to request features or report bugs. Though I probably won’t accept most features (depends on how much complexity they’d introduce), I’d be interested in hearing people’s use cases.
There is also https://unix4lyfe.org/darkhttpd/
Oh this is real nice, didn’t know about this one. It feels very similar to me in spirit. Reminder to self to match darkhttpd in logging info.
I have something like this called quickserv that I recently ported to Rust from Go. I use this to host a production-facing website at work and for my various eBook projects.
How does this compare to https://github.com/svenstaro/miniserve?
Thanks for mentioning miniserve :D I’m currently rewriting it to work on stable rust and to upgrade its version of actix-web.
I looked at that one while researching prior art, it seems good. More featureful than my personal liking though, for example, it lets you download a tarball which is nice in principle, but I’d just use the CLI for the rare occasion I’d personally need that, rather than embed all that extra code + complexity into srv.
For science, I used
ab -n 1000 -c 10 http://localhost:${PORT}/
against the latest miniserve from Homebrew and srv from GH Releases. I used default options for each serving up the content of my~/Downloads
directory.miniserve:
srv:
srv when I turned off console logging:
And then I tested with a ~130 MB file
ab -n 100 -c 10 http://127.0.0.1:${PORT}/Youre_Not_Alone.zip
:Cool!
Does miniserve not log to console? [Comparing them with default options shows miniserve as having better tail latency unless you disable console logging for srv.]
It doesn’t produce logs at all, IIRC.
It doesn’t produce logs at all, IIRC.
srv’s higher RPS is definitely due to the fact that I’m generating a very small amount of HTML. I bet if someone rewrote it in Rust it might get a tiny bit faster.
Certainly true. Look at the transfer rate, though, especially for the single file test.
Especially? It doesn’t matter for the rest because the size per response is much higher for miniserve.
Anyways, that’s pretty surprising. You have a pretty fast disk, I’m jealous. Maybe srv is using better syscalls? Or maybe you ran miniserv first, and that warmed up all the I/O cache.
Edit: Sorry for the wall of text haha, I’m rereading sniffSignatures and it seems pretty good actually. TIL mkv shares the same signature as webm, and opus audio shares ogg. If I find anything that’s missing then I’ll just contribute it to golang.
I forgot to add: does anyone have opinions on or a need for serving with the correct Content-Type mimetype? Python’s http server just sends everything as plain octet-stream, which means the browser won’t like, show an audio player or display the image.
I do get some for free from golang’s
DetectContentType
, which is a few calls up the http stack, but if you look atsniffSignatures
, it’s fairly incomplete. For example, there is novideo/x-matroska
.There’s 2 paths here: I could either resolve the file extension to the corresponding mimetype, or I could either swap out
DetectContentType
with something else that sniffs magic bytes… the most up-to-date is probably coreutilsfile
, where the functionality i’d be looking for is inlibmagic
. There seems to be prior art here: https://github.com/rakyll/magicmime.I prefer the latter, since I don’t really trust file extensions; plenty of files I’ve downloaded in the past are suffixed, for example, with png when in reality they’re JPEG images.
So definitely leaning towards exploring magicmime here, but I’m wondering if it’s really worth the added complexity. I honestly could even disable
DetectContentType
and hardcode an extension to mimetype mapping for some of the most popular media mimetypes.… I could also just contribute to golang’s
DetectContentType
itself.Purely anecdotal, but as a semi-frequent user of
-m http.server
I apparently have never run into a situation where I realized nor cared that it wasn’t sending a mimetype. So, if that’s your target, maybe it’s not really needed.Oh! I just tried
http.server
for some things, looks like it’s sending it now…I wrote the first minor version of srv quite a while ago and I could have sworn it didn’t send mimetypes. Or maybe I was using an older python version, I dunno.
I was curious so checked out the implementation:
At least as of 3.9
http.server
overrides and explicitly specifies types for a small number of compression formats (not sure why, wasn’t commented, and I didn’t dig into the git blame there) – but otherwise tries to lookup a type viamimetypes.guess_type()
which loads mappings from filename extension to type from a hodgepodge of different config files on posixy systems, or the registry on windows. If a file doesn’t have an extension, or the extension doesn’t have a mapping in either of those, it defaults toapplication/octet-stream
. Notably it doesn’t attempt to look for magic numbers or anything like that.Sure, since it’s mapping from extension to mimetypes using those databases. Quoting myself:
Those are two separate approaches to guessing the Content-Type, the latter being more correct.
One case I ran in to it is when developing an app which uses WASM locally, which needs to be served with
application/wasm
or it won’t work.It’s easily solved with Python’s http.serve, but it took me a bit to figure out since the browser message wasn’t too helpful.
Surprise! Go has that: https://golang.org/src/net/http/sniff.go#L197
Therefore srv does too!
FYI,
mimtypes.types_map
in the standard library is adict
mapping common file extensions to their MIME type values. On my system it already knows.js
and.wasm
and has the right mappings for them, so settingextensions_map
to that would probably do what you want:(or if yours is missing types you want, I guess
.copy()
the base one and update it for one-off use, or register the new types throughmimetypes.add_type()
)I am more of a “php -S localhost:3000” person myself. Any idea now srv compares to that?
Well, for starters, I did
php -S localhost:8000
, then requested http://localhost:8000/ and got a 404:So there’s that!
Never had that happen to me… Well, glad you have srv then!
I’m calling it right now, you’re now cursed to encounter it sometime in the future. :)
Even PHP’s built-in server has some whack design choices: https://bugs.php.net/bug.php?id=69655
That’s why everyone says “don’t use it in prod”! I’ll be honest with you, most of my dev happens in php so it’s just the easiest thing to grab. Might switch to OP’s solution once I get the chance to try it
Not entirely sure whether you understand the issue I linked. This is downright stupid in development too IMO. If anything the singular reason it works that way (performance) makes less sense in development.
I agree that I must not be understanding the implications of this, it sounded like a simple bug. But thanks for clarifying, I will switch servers for development, OP’s solution looks great!
Basically the php server freezes, drops connections, or returns 415 when it encounters unknown or malformed http methods. “unknown methods” includes some exotic but valid HTTP methods. I am not sure if this is still the case, but it did cost me a lot of time in debugging back then. Surely this matters less with static hosting where everything is GET
How well does it handle forking? One semi-common use I have is having multiple people try to test a website. Right now I use code like the below. Does this roughly do the same?
It is written in Go. There is no forking, concurrent requests are handled via concurrent goroutines.
I really like the idea of a simple server focused on one-off interactive runs. I typically use
caddy
, instead of Python’s built-in server.Where do you feel
srv
sits relative tocaddy
(especially since they’re both written in Go)?My only complaint of caddy is that it focuses a bit much on configurability for daemon mode rather than ease of use for interactive mode: caddy2 needs to be run as
caddy file-server --listen :2015
which is a bit more annoying than caddy1’scaddy browse
. Not entirely sure if this is something that is PR-worthy for caddy, or if it’s better to focus on a dedicated tool likesrv
.[Edit: Articulating this finally prompted me to open an issue] [Edit2: Aaaand closed as WONTFIX, nevermind.]
srv
is way, way simpler thancaddy
, but they’re trying to solve different problems. As you mentioned: a simple server focused on one-off interactive runs. Caddy’s like, a full-blown nginx competitor.You can quite literally just type
srv
:Does it comes free with the standard library of a language that does many other things or do I have to install it explicitly? :-)