Hacker News .hnnew | past | comments | ask | show | jobs | submitlogin
Show HN: Nano-web – a low latency one binary webserver designed for serving SPAs (github.com/radiosilence)
157 points by antihero on March 25, 2024 | hide | past | favorite | 115 comments
I'd found that whilst there's a lot of good options out there for webservers, I was looking for something that is a single deployable binary for usage with containers or unikernels and solves some of the problems that you get with other setups like nginx.

It uses a single compiled binary, it's going to have extremely low latency on account of caching all files in memory at runtime, and also the fun feature and really useful that's good for SPAs (e.g. Vite) or things like Astro is that you can inject configuration variables into it at runtime and access them from within your frontend code, so you don't have to rebuild any images for different environments as part of your CI.

Whilst I'm sure this problem has been solved time and time again, I could never get a solution to be quite right.

Also, serving things like Astro from S3 gets to be tricky because CloudFront doesn't support index pages in subdirectories so the routing breaks. This fixes this.

Use it or don't, I think it's a cool little project and it's been a while since I worked on and released something :)



> SPA_MODE when set to 1 404 request will return /public/index.html as a 200.

oof, as someone who frequently has to run DAST tools, returning a 200 for something that should be a 404 is the bane of my existence.

The tool will try to request sensitive files by trying something as simple as fetching hxxps://example.com/../../../etc/passwd. If you return a "200 OK", it will flag as a security issue, even if the resulting data isn't a passwd file.

I don't think you can convince me that returning a 200 OK and some default content for a URL that doesn't exist is ever the correct thing to do. If you want to use 301 or 302 to redirect them to the front page, that's probably fine, but the most correct thing to do is 404.


Tripping up hare-brained security scanning tools sounds like a perk tbh.

> I don't think you can convince me that [...] is ever the correct thing to do

It's just a matter of trade-offs.

Serving the app.js on a 404 can cause things like browser/caching issues that are confusing for the user, and 404 isn't necessarily correct since the app is both servable and recoverable from that url.

And I'm not sure 302-redirecting is any less meaningless than serving 200 on a not-found. The user doesn't need the redirect since the app is already capable of handling the not-found page in a user-friendly way. So does it somehow improve things for programmatic analysis of the app to make all 40X errors redirect to the homepage, and is that very compelling?


> Tripping up hare-brained security scanning tools sounds like a perk tbh.

Heh, not a bad criticism. Though TBH, I think all DAST tools are hare-brained. They get sold as automated penetration tests, but they really can't figure out SPAs at all, since they often only look at the immediate response to an HTTP request, when often, you'd need to see exactly what the JS code does with the response and how it gets rendered in the page. Maybe a PUT/POST request to upload data returns a blank 200 OK, but only a follow-up GET request will actually show the result.

They end up with more false negatives than false positives. At best, they'll catch the low-hanging fruits involving missing headers that often lead to Beg Bounties like Clickjacking or missing HSTS.

The only reason DAST still exists is because compliance frameworks still list it as a checkbox. Otherwise, I think they're pretty useless.


If I make a typo and get a 200 for some url that doesn't actually exist, my browser will now happily suggest that url when I'm typing something in the future.

Is there a benefit for doing so?


It's not a benefit but a side effect of client side routing

If your main routing logic is in your JS app your server can't know if a route exists or not unless you duplicate routing code across front end and backend


Well client side routing is fundamentally broken unless you do serve a 200 and the index


You only have to serve a 200 and the index for valid routes to have frontend routing working.

The complaint is serving a 200 for invalid routes, which breaks all kinds of things and violates web standards.


Indeed, for a cost-effective approach with a static host, why not generate placeholder HTML files for all valid routes at compile time? Alternatively, using Astro or a similar tool would be a wise choice :)


This isn’t really feasible, think about how dynamic routes can be? Can you generate a route for every ID of a thing?


I have actually seen this approach for SSG. You define a function to get all IDs, then the framework renders all of the pages using those.


Well you could plausibly do this with nano-web, simply disable SPA_MODE and generate your routes in this manner.


But if it's an SPA, how else would you handle this case?

How is the webserver supposed to know in advance which URLs the SPA is able to handle?


I'm confused here. What defines the "webserver" versus the "the SPA"?

The way I've implemented SPAs is to run nginx. All my JS and CSS is in /static/ and served up by nginx. "/" returns an index.html. "/api/*" gets forwarded to a back end of some sort, usually Python's Bottle because it's lightweight and simple to use. If the route doesn't exist, Bottle returns a 404. If I have an endpoint that includes an ID of some sort in the URL, like /api/getobject/12345, but you request an object that doesn't exist, then I still return a 404.

How are you implementing SPAs in a way that you can't return a 404?


A lot of SPAs do some level of client-side routing, so that if you open up the SPA and click on a link, rather than directing you to a different page, the app instead updates the URL with Javascript and renders the content of the new page. There are various benefits and drawbacks to this approach, but it's quite common, and you can find tools like React Router that are designed to support this client-side routing.

The problem is that when you only serve your app from `/`, and then the user navigates via the app to `/user/123`, the url gets updated, but if the user tries to reload the page, they'll end up on a 404 page because you're not serving the app from `/user/123`. There's a few ways to solve this, each again with tradeoffs, but one way is to have the server return 200 and the contents of the main page any time that the user requests an unknown route, under the assumption that the user is probably trying to navigate to a dynamic route handled by the frontend.


> How are you implementing SPAs in a way that you can't return a 404?

Not specifically me, but a lot of SPAs have their own routing. So you would go to

  https://my-spa.com/user/39820002/post/39811155
When you hit that route, the webserver returns /index.html with the JS bundle, the JS runs and matches the user ID and post ID from the URL and then maybe makes some API requests to fetch content of whatever, I mean the SPA doesn't have to be backed by an API anyways, could be purely static but still use a router.

In the React world, react-router is (was) pretty popular:

https://github.com/remix-run/react-router

That's a library that works exactly that way, relying on every route being passed to the JS bundle for handling, so the webserver needs to be configured to never return 404 and just return the index.html for every route, here is an example of what this looks like from the docs of Caddy, another popular webserver with a pretty easy to understand config format:

https://caddyserver.com/docs/caddyfile/patterns#single-page-...

It's a "Single Page App", you may have many pages being rendered by this app, but it's a single html page that is responsible for doing this.

Now – SPAs are not as popular as they used to be, and there is an argument to be made that they aren't the best solution in most cases with what's available now, regardless of that they do exist and this appears to be a web server specifically for them so having the option handle SPA routing as it's commonly done, does make sense.

And I think for stuff like internal management apps, admin panels etc. SPAs with their own routing are still a decent solution.


Ahhh...I see what you're saying.

So like, if you're on my-spa.com, and click a link to a post, the JS loads the post and updates the URL, but doesn't perform a full page load. So while you might be on https://my-spa.com/user/39820002/post/39811155, your browser likely never actually sent a GET request to /user/39820002/post/39811155. But this creates the issue that if you manually navigate to that URL, your browser WILL send a request for it.

In that case, yeah, I think the correct thing to do is to return the 200 OK with your usual index.html. When the JS makes the API requests, those requests should return a 404, and the JS will then render a page that says "That post doesn't exist" or something.

But in the case of a request that couldn't possibly exist, like a GET request for /etc/passwd, they should still be 404s. Any router should be able to do that.

EDIT: I think a better way to do this hold thing is to make the resource an anchor tag in the URL. Instead of https://my-spa.com/user/39820002/post/39811155, make it https://my-spa.com/#user/39820002/post/39811155


> So like, if you're on my-spa.com, and click a link to a post, the JS loads the post and updates the URL, but doesn't perform a full page load. So while you might be on https://my-spa.com/user/39820002/post/39811155, your browser likely never actually sent a GET request to /user/39820002/post/39811155. But this creates the issue that if you manually navigate to that URL, your browser WILL send a request for it.

Exactly, you got it. This is why SPA configurations for webservers exist, like the SPA_MODE on the one we are commenting on.

> EDIT: I think a better way to do this hold thing is to make the resource an anchor tag in the URL. Instead of https://my-spa.com/user/39820002/post/39811155, make it https://my-spa.com/#user/39820002/post/39811155

As someone else said:

https://hackernews.hn/item?id=39819120

Hash routes have their own problems, I don't think they are a great solution, but indeed SPA routers often support them, example from react router:

https://reactrouter.com/en/main/router-components/hash-route...

Note that they strongly suggest to not use hash routing unless you have no other choice.

I agree that returning 200 to every route isn't great for some scenarios, but realistically, SPAs exist, they usually work like this and they are deployed like this, can't really change that now. I would just not build an SPA with client side routing at this point in time since there are solutions that I personally prefer, other people may think differently though.


Yeah I mean SPAs still make lots of sense for web based applications, of which there are many. They make bugger all sense for websites, of course. I wouldn’t say they are declining in popularity, more like declining in overuse.

Also it is nice to be able to cleanly implement SPAs in such a way that the server itself is doing as little work as possible for cost and scale.


> they aren't the best solution in most cases with what's available now

What's available now that superceds them?


Well I suppose that's subjective, I probably put my bias in there but I would say:

For mostly static or content driven sites, there are really nice static site generators now. I find Astro[1] really pleasant to use, offering the convenience of SPA framework tooling, while allowing me to build sites that run without the need for any client-side JavaScript but the option to into client-side JS as needed.

But also NextJS has a similar static export function, and of course there are many other great SSGs outside the JavaScript world like Hugo.

For dynamic web applications like the example from my comment I would just do server side rendering using your language and framework of choice, Phoenix, Rails, Next, Django, whatever.

In both cases the issue with 404 routes is avoided.

[1] https://astro.build/


> What's available now that superceds them?

The mostly same stuff as before, but slightly better. SPAs were a fad that got very popular, but they were generally a poor solution in most cases and as the fad faded, they stopped being as overused.

One of the reasons why SPAs are bad solution is because they interfere with how the web works in significant ways (browser history, caching, server response statuses) and solving those issues without degrading user experience is hard.

When a SPA was actually useful, it was worth spending the time to solve those issues.

The overuse of SPAs means that most of them did not take the time to solve these issues properly so most SPAs sucked (and still do). This is why I don't understand releasing a SPA server in 2024 that doesn't even try to provide tools to solve those issues.


I typically keep all my SPA routes/route names in a single JSON, and then reference those from the App, instead of repeating the routes everywhere. I can then pass those to the server to let it know what the SPA can handle.


This is an opinionated approach that is out of scope really I think. Though if it works for you, awesome!


I'm not sure what is 'opinionated' about this approach. It seems like a pretty standard and reasonable way of correctly serving an SPA in a way that doesn't violate web standards.


So this would mean you would have to dynamically generate your webservers configuration at the build time of your SPA?

And then how are you handling routes with params inside? In your SPA they would probably be configured like

  /user/:id/post/:post
Now you would have to translate that to something the webserver config understands, probably wildcard / glob pattern / regex stuff and splice that into the web server config?

I mean it's possible, sure, but that's a lot of overhead and room for error just to deploy a simple SPA, it definitely is an opinionated approach.


I don’t know. Computers are pretty damn good at transforming text. It sounds pretty simple really, it’s probably less than a week to build something rock solid.


I'd say not handling 404's correctly and simply returning a 200 OK is pretty opinionated, too.


Handle a prefix maybe?


But how could this possibly be correct if the route is correct within the context of the client side code which uses history API to route, which the server knows nothing about? Else you’re going back to the days of hash routes which nobody likes.


If this is the nano version then let me share the pico alternatives with you:

* BusyBox httpd [1]

* thttpd [2]

* Lwan Web Server [3]

* gatling [4]

I built and maintain a super tiny Docker image (~158KB) for serving static files [5]. My current implementation is based on Busybox httpd and scratch but I also tried out thttpd in the past, which worked well but came with a high memory footprint in some cases. thttpd was used to serve big projects back in the day but it doesn't seem maintained any more. The other two projects were on my list of things to try out. All these projects should compile to binaries <500KB.

By comparison nano-web builds a 18.5MB image on my machine. In my experience you can't go below 5-6MB with Alpine (scratch is the way to go if you can compile statically) and you can't go below ~500KB with Golang or Rust, at least not for serving http requests (C or ASM is probably the way to go).

Note that I'm not saying that small is always better, it probably depends on your use case and there are reasons for using nginx for a static website as well.

[1] https://www.busybox.net/ [2] https://www.acme.com/software/thttpd/ [3] https://lwan.ws/ [4] https://www.fefe.de/gatling/ [5] https://github.com/lipanski/docker-static-website


OP says of nano-web:

> you can inject configuration variables into it at runtime and access them from within your frontend code

Is there a way to do that with BusyBox httpd or the others?


Let's say you have a personal portfolio website. Single index.html file. Written entirely in vanilla HTML/CSS/JS with zero dependencies whatsoever. Project "pages" are fullscreen slideshows of high-rez images (3200x2000) etc. So basically a handcrafted static site.

Would buying a virtual server and running any one of these mini servers to run your site make it more performant than throwing it on a regular host like Dreamhost?

Like, does the simplicity in the server itself offer ANY speed/performance/latency advantages for a site that is ultimately showcasing big imagery for a folio?


> SPA mode to service 404s as index (200) to support client side routing.

Be careful: SPAs can handle 404s client-side.

All you need to do is continue to return the 404 result code and include the same exact content that you normally do.

Then your SPA recognizes that the route isn't set up and displays the error page.


Yep, returning the wrong HTTP status code is going to confuse a lot of search engines, chat app link previews, and other automated consumers of your SPA, even if you subsequently display the correct error page.


>returning the wrong HTTP status code is going to confuse a lot of search engines

How is the search engine going to find a page that doesn't exist, isn't in a sitemap, isn't linked to in any nav anchors, in order to index it?


I will submit it to google to be indexed myself, purely out of spite for the fact that you're willfully and with malice aforethought returning 200 when you know you should be returning a 404.


> How is the search engine going to find a page that doesn't exist, isn't in a sitemap, isn't linked to in any nav anchors, in order to index it?

If links are never going to be used, there is no point in doing the effort of client side routing.

If links are going to used, the server needs to respond with the correct status or you break the assumptions upon which the tools people use to access the web are based.


The article is about serving SPAs. In SPAs the "pages" are not really pages, they are program state. Just because you think /whatever shouldn't go to the homepage doesn't mean your way is the best way for an SPA to work. Maybe I'd rather display the current sale instead of a generic "404" error. Nothing wrong with that. If you're scraping my site or something, then too bad for you. The SPA server is under no obligation to serve any page with any specific response code so long as it is valid HTTP.


> Just because you think /whatever shouldn't go to the homepage doesn't mean your way is the best way for an SPA to work. Maybe I'd rather display the current sale instead of a generic "404" error. Nothing wrong with that. I

Return a non-200 response doesn't mean you can't return a page. You can indeed display the current sale in the content returned with a 404 response and most sites do. A SPA absolutely can return tue full index.html content for every end point, but it shouldn't lie with it's status codes in the process.

> The SPA server is under no obligation to serve any page with any specific response code so long as it is valid HTTP.

You are also under no obligation to close your html tags or accessibility guidelines. However, ignoring specs like this can break things for your users in ways you can't always predict, especially for more marginal use cases.

If you're gonna to write a SPA, please, I beg to you, actually take the time to do it properly and release tools that enable that. Otherwise you are directly contributing to making the internet worse.


>Otherwise you are directly contributing to making the internet worse.

I don't think it really matters as much as you say it matters. It isn't "making the internet worse", it's only affecting the specific site's indexing, so don't act like this happening is bleeding out to other sites and affecting everyone. Calm down, "the internet" isn't getting worse from this practice.

>A SPA absolutely can return tue full index.html content for every end point, but it shouldn't lie with it's status codes in the process.

That's your opinion. I'm fine with it returning a 200. The thing about SPAs is "the server" doesn't always know if a route is valid or invalid. Hosting an SPA on S3 is one example of that. It responds to requests made to unfound resources with the index page, and a 200 status. It does not know if /account or /config does or does not exist in the front-end routing, the server is dumb and can only serve assets that it knows exist. So it returns the content for the index page and the front-end then decides if the route is valid or not, because the front end state changes with the URL, and there could be thousands or billions of different routes, especially if data is included in the route. There is no way to set up the server to know every route that might happen when it encounters something like /account/verify/8ab2c09f43e7b2a094839ab3cef329

In this case the back end serves index.html with status 200 and the front-end sorts out what to do.

And I'm fine with it, this doesn't "make the internet worse", only the SPA site is affected (if it really has any effect at all).

You can serve pages on your site however you want to, I don't really care what your site does.


Perhaps the page existed in the past but now it doesn't. Or at some point a link was misspelled.


easy enough to solve with Prerender. when using it I include some head tags to allow the Prerender server to send codes like 404 or 301. It's worked well for SEO compatibility, I even wrote a small React router project to handle this situation automatically (as well as other TSEO considerations like trailing vs non-trailing slashes).


I could certainly add this as a configuration option! Generally an SPA could do this by simply not having that route and handle accordingly but if it's something people would be interesting it is a quick change. Perhaps make a ticket/PR and I can look into adding it :)

That said, thinking about it - the server has no idea whether a client side route is actually correct.

You could have https://example.com/potato/tomato which will not exist on the server of course, but will be handled by client side JS on /index.html and could be a correct route within the app, so it is safer to 200 it.

SPAs aren't really generally made for SEO also. With something like Astro which is SEO friendly, it could actually return a 200 as expected and a 404 if not.


By the way, is there a really minimal, really fast, reasonably secure, zero-cofig single-binary web server which would only support HTTP GET and just expose all the static content in a given directory over it?


"caddy file-server"[1] and sirv-cli[2] should have you covered, but in case you're looking for something more niche and less powerful, you could look at busybox httpd[3].

I wrote something similar long ago in < 400 lines of Go (with most of it being templates and mimetype information) that just supports GET, HEAD and gzip, in case you're interested[4].

[1] https://caddyserver.com/docs/quick-starts/static-files

[2] https://www.npmjs.com/package/sirv-cli

[3] https://openwrt.org/docs/guide-user/services/webserver/http....

[4] https://github.com/supriyo-biswas/gohttpd


Thanks, I'll take a look. I also care about it being reasonably secure so I don't write it myself (I'm far from a pro in avoiding vulnerabilities) and also prefer to decrease the attack surface by giving up all non-essential features.

gzip is unnecessary because 99% of the traffic I would serve is going to be pre-compressed (like zip and jpeg files).


I can recommend redbean[1], it's basically a webserver and your static files in a zip file that is executable on basically any computer. It's also super small, includes TLS and reasonably fast.

[1] https://redbean.dev


TIL you can actually get a single binary to execute in multiple operating systems.

Redbean in general seems great and really interesting.


Caddy [1] is a single binary. It is not minimal, but the size difference is barely noticeable.

serve also comes to mind. If you have node installed, `npx serve .` does exactly that.

There are a few go projects that fit your description, none of them very popular, probably because they end up being a 100-line wrapper around http frameworks.

[1] https://caddyserver.com/


> It is not minimal, but the size difference is barely noticeable.

Minimality is not about size, it's about reducing the attack surface.

> There are a few go projects that fit your description, none of them very popular, probably because they end up being a 100-line wrapper around http frameworks.

Indeed. Everyone can write their own but dependencies will make its attack surface enormous and performance will probably be mediocre at best.


Caddy is amazing for simple static hosting AND more, if one needs it. With sqlite, postgres, D3.js and FFmpeg, it's up there in my software love list.


Depending on the environment I'm in I either use `npx serve` or `python3 -m http.server` (aka `python2 -m SimpleHTTPServer`)


Python http server is not intended for public exposure. It's meant for local usage in testing / debugging contexts.


I wrote filed[0] for this. It is VERY fast in terms of low-latency (it doesn't even open the file its serving if it can help it) -- although it doesn't generate a dynamic directory listing, so it may not meet your needs if you want any dynamic content like that.

[0] https://filed.rkeene.org/


Should "test/plain" be "text/plain" "in mime.types?


Yes, thank you, it is now fixed :-)


actually readable C. kudos


Thanks, I've come a long way since my first C programs in the 90s, "sadly" lost to the sands of time. Earliest I can find is [0].

[0] https://rkeene.org/viewer/devel/vmi586/main.c.htm


Yeah, Vercel created a tool for such case called serve. https://www.npmjs.com/package/serve


Not sure if it meets all of your requirements, but what about using Crystal HTTP::Server[1] and building a fully statically linked[2] binary and deploying via a Docker scratch image[3] using a Multi-stage build[4].

Here's[5] an example I put together that I have running on fly.io[6]. I'm doing a redirect for root path, but it could easily be changed to serve up the contents of index.html.

I use a similar deployment for a small site I created[7] to generate UUIDs. Kemal[8] also looks like a good alternative.

----

Update:

Here's[9] an example also running on fly.io[10] using Kemal[8]

[1] https://crystal-lang.org/api/1.11.2/HTTP/Server.html

[2] https://crystal-lang.org/reference/1.11/guides/static_linkin...

[3] https://docs.docker.com/build/building/multi-stage/

[4] https://hub.docker.com/_/scratch/

[5] https://github.com/adrianlv512/minimal-server

[6] https://minimal-server.fly.dev

[7] https://uuid-generator.org/

[8] https://kemalcr.com/

[9] https://github.com/adrianlv512/minimal-server-kemal

[10] https://minimal-server-kemal.fly.dev


I don't think anyone looking to just serve static files wants to touch a programming language, deal with any (additional) build steps, or run Docker. Especially the parent who expressed "minimal" as a requirement.



I created this some years ago: https://github.com/philippgille/serve

It's written in Go, has zero third party dependencies, supports optional basic auth, and optional TLS cert generation (self signed) for local HTTPS.

Not updated in a while though.

And if you drop the requirement for minimalism I'd second the recommendation for Caddy instead.


This is that. Unless you mean having a directory listing, in which case, it is not.


You want adhoc HTTP server or daemon? I wrote my adhoc HTTP server for serving files from my desktop right away. If interested, leave comment, I can make it public.


Not sure what single binary means but here’s my take.

https://github.com/dclowd9901/posse


Single binary meaning you only need a single binary to run it, nothing else. Yours is not single binary nor zero config


Helpful, thanks


you might want to write your own; i hacked httpdito together for this purpose one weekend. you won't find anything that beats it on 'really minimal' by more than 2 kibibytes :)

readme: http://canonical.org/~kragen/sw/dev3/httpdito-readme

source (single file): http://canonical.org/~kragen/sw/dev3/server.s

it might be reasonable to configure its port and mime types map on the command line instead of in the source, but at the moment it's arguably a bit too zero-configuration

it runs on linux, can handle about 20000 hits per second, has about 20 kilobytes of memory footprint when idle, and needs another 4 kilobytes per concurrently open connection (though the kernel needs several times that)

20000 hits per second is not really fast in the sense that other servers on the same hardware and kernel can handle more than twice that, but it can saturate a gigabit pipe if your files average 7 kilobytes or more


Considering the latency between the user web browser and the server, does the “extremely low latency on account of caching all files in memory” really matter? The files should already be cached by the OS and using sendfile lets the OS copy the file directly to the network.


I just figured any savings where savings can be made is a good thing, especially if there are zero downsides, plus it means very low CPU usage as it's literally just pulling from RAM without any CPU usage.

It also precaches gzipped and brotli'd versions of sensible files so that it can accept-encoding for a variety of browsers.


Linux already caches files in ram, you're probably just doing something that your OS is already doing, but worse.

If you're really concerned you can MMAP the file instead, and do a read every once in a while to keep it cached.

There are a lot of cases where this kind of functionality could cause big problems, and very few where it has more than a marginal improvement over what your OS is already doing. Just read the file every once in a while to keep it in memory cache.


This doesn’t necessarily have to run on Linux.


Couldn't the extra memory copies end up being an actual downside? I.e. the sort of thing that inspired sendfile(2) usage in some servers?


I’m not so sure, if you’re serving massive files this is not perhaps the option you want. If your SPA is so big you can’t handle a container that has its app code and assets in RAM perhaps it’s also too large.

I see it as a pragmatic trade off between speed and RAM. Even free tier EC2 instance should have enough RAM to handle pretty much any SPA even if the text content is duplicated (compressed) especially seeing as with a unikernel you have no RAM used up by Linux processes.


I think the argument is the same for files that fit in memory. This sort of thing is fairly well-trodden (if unsettled, to mix up the metaphors) ground, for yet another take there's varnish and Poul-Henning Kamp's strident writings about it.


For anyone else wanting to read about it: https://varnish-cache.org/docs/trunk/phk/notes.html


> serving things like Astro from S3

Given that point, I got the impression that the comparison was against loading resources from S3. The difference between that and serving from memory (even between that and serving from uncached traditional hard-drive) is going to be significant.

EDIT: cursory research suggests S3 latency (https://stackoverflow.com/a/861539/114292, being the most recent reference that turned up) is smaller than it was last time I remember needing to look into it, but still of the order of 10+ms.


Yeah I see this as a viable alternative to serving from S3 (I mean, which is still a great option I recommend provided you aren’t exposing S3 directly and set up S3 properly locked down) but the original niche was having an SPA web interface that runs on a k8s cluster as part of wider service where you want a tiny minimal and fast image that can be configurable when the pod is spun up. I just then expanded it to be a bit not of a general solution to have fun with it.


We deploy little apps internally. Users have low latency on the network - even home users we steer to use ISPs we know have low latency. I've found that speed can make a difference in user like for an app. Even going out to google etc I'm currently at 5-6 ms.

Some things we might have 1000+ checkboxs to work through on a checklist (cut down by some initial tailoring steps). Even things with a small wait on a click etc are frustrating. If you can drive using a keyboard shortcut and get very immediate response you can really move through things.


The difference of latency between serving a file cached in user-space RAM (what nano-web is doing) and serving a file from the kernel buffer cache (also in RAM) using sendfile should be totally negligible compared to the latency of your network, even if that network is "low latency".

Latency Numbers Everyone Should Know: https://static.googleusercontent.com/media/sre.google/en//st...

sendfile(2): https://man7.org/linux/man-pages/man2/sendfile.2.html


Latency between me and my cloud provider is about 10x lower than the latency of Python's http.server on localhost.

(It's negated by the SSL handshake, but that's another discussion...)


Is that wall-clock measurement or are you using a tool like Apache Benchmark to see the latency?

I ask because those numbers seem really off.


The load time in Chrome Dev Tools


that sounds broken


Once you add a CDN into the picture, it almost doesn't matter what you use to serve static assets for your web app.

Plus, like you said, 10-20ms extra on top of the actual latency from the server is not going to make any difference to the end user.

There are use cases where you probably need something like Nginx or whatever to handle lots of requests without a CDN but these are rare.


I agree on the CDN.

Reading assets from files instead of a RAM cache won’t add “10-20ms”. This is measured in microseconds, not milliseconds. Most servers nowadays are using SSDs or eNVM. And as I wrote earlier, the files are likely already cached in RAM by the OS anyway :)


> Reading assets from files instead of a RAM cache won’t add “10-20ms”.

I know. I was just using a hypothetical worst case scenario to exemplify that it doesn't matter.


https basically makes sendfile unusable for that use case.


Can you elaborate "CloudFront doesn't support index pages in subdirectories" and how does it not work?

I'm not much into them these days but if I remember correctly in the 4-5 years prior, they just works! In-fact, CloudFront was easier to serve static web assets than serving directly from S3.

Or have I misunderstood what you meant?


In a SPA, you want to change routes in the URL so you don’t break people’s expectations about being able to share deep links to specific pages, but those sites don’t really exist. Eg if your site is www.example.com and you have a SPA on there, and one of the pages is www.example.com/products, you want to still serve index.html from the root even though the /products path is not a real directory on your system. If you don’t serve for this URL then it’ll work fine locally but if someone hard-refreshes or shares the link, it won’t load.


If you are using Cludfront with an S3 origin you can turn on static website hosting and specify a 404 fallback page[0] - which would then just be your index file. It will render your client code and let you show a 404 if it is indeed one.

The problem is that essentially every time a user goes to the site it will be a 404 status code since they are probably not typing in example.com/index but this has pragmatically not been an issue for a wholly authenticated, private, B2B SaaS app. The marketing website is a separate subdomain.

For a public site this is probably worse than returning 200s that should be 404s occasionally, though.

[0]: https://docs.aws.amazon.com/AmazonS3/latest/userguide/Custom...


Yeah maybe I'm misunderstanding what this person is referring to, but cloudfront definitely supports index pages in any directory


When was this added? I recall several years ago having to do this with edge lambdas.

IE the it would happily resolve / to /index.html would be fine, but if you wanted to do /my-dir and have it resolve to /my-dir/index.html it would 404.


Now that I'm curious because I remember facing no such issues. Cloudfront was my way of testing all of these scenarios to see what happens when the URL endpoint changes, etc. and then move to Akamai to scale up.

Did some quick searches and I think there seems to be some kinks indeed and of course the solutions. Here are a few;

- https://stackoverflow.com/questions/59634922/how-do-i-serve-...

- https://aws.amazon.com/blogs/networking-and-content-delivery...

Update/Edit: I think the setting to use correct S3 Endpoint makes a difference (which was indeed always my default way when I did).

https://stackoverflow.com/questions/31017105/how-do-you-set-...


Yeah, I ended up writing a whole thing for it as part of my static website CDK construct.

Here's the request edge lambda:

https://github.com/radiosilence/cdk-static-web/blob/master/s...

Here's the response edge lambda:

https://github.com/radiosilence/cdk-static-web/blob/master/s...

It's been a while since I've updated that project though.

With nano-web for my current site (https://blit.cc) I am just spinning up a free-tier unikernel instance on EC2 and using it as the CF origin. Possibly even more cost effective than S3, and far simpler.

Response times for the "waiting" portion are usually around 5-10ms with CF and if I am going direct to the origin it's around 2-5ms


Neat idea! We did a blog post around just using nginx for something similar. Which is to say NGINX serving SPA. https://medium.com/disney-streaming/api-driven-spa-with-ngin...

There is even a sample template repos. https://medium.com/disney-streaming/api-driven-spa-with-ngin...

Looking forward to diving into nano-web


There is also Rapid Web Application Server in Assembly: https://2ton.com.au/rwasa/


The interesting bit is that the project is using a unikernel instead of Docker, I hope this becomes a trend.

https://github.com/radiosilence/nano-web?tab=readme-ov-file#...


It is set up to use both.

Currently working on automating releasing the unikernel OPS package (https://repo.ops.city/v2/packages/radiosilence/show) but I had to submit a patch to the OPS project that needs to be released first (or I can make a custom image to use with GHA to build it with a version from master).


How has your experience with Nanos unikernel been so far since you started? Have you noticed any benefits over Docker?


I mean, I think it's an interesting approach to and kind of nice to be going back to more PaaS primitives. I haven't used it in production, it does seem extremely quick and kind of simple and nice.

Either way, I figure the more projects that support it natively, the bigger the ecosystem grows and the more data we'll see based on real world usage.

The tooling seems pretty good, though the documentation could do with some improvement (I intend to do more discovery and PR some documentation improvements when I have time).


> Either way, I figure the more projects that support it natively, the bigger the ecosystem grows and the more data we'll see based on real world usage.

I like your approach towards this, I'm thinking the same. I appreciate the response you provided, I've been planning on playing around with it for a while when I get the time. Your comment made me even more interested.

It went quite quick from seeing your comment on the post about Nanos to your own project integrating it!


The joys of funemployment ;)


I wrote a similar tool long time ago.

https://github.com/varbhat/serv

* Serve the SPA (fallback to index.html): `serv -spa`

* Serve the directory and all it's contents: : `serv -dir=/path/to/my/dir`


h2o (https://h2o.examp1e.net/index.html) is a full-fledged, single-binary, open source web server. Check it out.


The last time I used it for serving multiple WordPress sites and realise having lengthy YAML can be hard to maintain, and I read that mruby can impacts the performance. Their alternative, picohttpparser that V langauge uses on Techempower benchmark is fast but not useful for real world.


Are people using nanovms in production? We love to understand the landscape? We are considering it lately. Would love to learn some of the upsides and downsides.


Are there bigger latency wins supporting HTTP/2 or HTTP/3?

Is HTTP/2 or HTTP/3 even useful behind a service mesh (istio/envoy etc.)?


If you want TLS, HTTP/3, quic etc I’d suggest that’s the job of whatever is terminating the connection not the core server.


I like the idea!

Have you seen redbean, which is a universal binary and serves static files from a zipfile? How does this compare to that?

Honestly, I think that the current Web can easily use libp2p from IPFS and serve files from any browser peer to peer, with relays on the internet.

I think the Web lacks two major things:

1) Address resolution only uses federated DNS and you need extensions to use, say, self-sovereign Web3 domains which can’t be stolen

2) There is NO WAY to load files by their content hash like onion links in Tor. If there was, we’d have a much more secure Web.


Federation’s a non-starter when most devices people use are battery powered and don’t like to hold connections open when idle. Like, it works, but it won’t work well enough unless most devices are just going through gateways, which largely defeats the point.


What about all those desktop computers on 24/7 in the world?


As a share of web clients, those are a rounding error. Phones dominate, with tablets, laptops, and desktops that haven’t been configured to never sleep, following.


how about civetweb? or lighttpd? both are well known and field tested, light weight and as portable as you need. what are the different features needed for serving SPA comparing to a normal http server?




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: