[HN Gopher] Show HN: Nano-web - a low latency one binary webserv...
       ___________________________________________________________________
        
       Show HN: Nano-web - a low latency one binary webserver designed for
       serving SPAs
        
       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 :)
        
       Author : antihero
       Score  : 142 points
       Date   : 2024-03-25 13:23 UTC (9 hours ago)
        
 (HTM) web link (github.com)
 (TXT) w3m dump (github.com)
        
       | EGreg wrote:
       | 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.
        
         | vundercind wrote:
         | 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.
        
           | EGreg wrote:
           | What about all those desktop computers on 24/7 in the world?
        
             | vundercind wrote:
             | 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.
        
       | i_have_to_speak wrote:
       | h2o (https://h2o.examp1e.net/index.html) is a full-fledged,
       | single-binary, open source web server. Check it out.
        
         | todotask wrote:
         | 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.
        
       | Brajeshwar wrote:
       | 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?
        
         | VWWHFSfQ wrote:
         | Yeah maybe I'm misunderstanding what this person is referring
         | to, but cloudfront definitely supports index pages in any
         | directory
        
           | antihero wrote:
           | 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.
        
             | Brajeshwar wrote:
             | 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-...
        
               | antihero wrote:
               | 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
        
         | lukevp wrote:
         | 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.
        
           | somethingAlex wrote:
           | 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/Cu
           | stom...
        
       | qwerty456127 wrote:
       | 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?
        
         | supriyo-biswas wrote:
         | "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
        
           | qwerty456127 wrote:
           | 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).
        
         | ricardobeat wrote:
         | 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/
        
           | busfahrer wrote:
           | Depending on the environment I'm in I either use `npx serve`
           | or `python3 -m http.server` (aka `python2 -m
           | SimpleHTTPServer`)
        
             | rmbyrro wrote:
             | Python http server is not intended for public exposure.
             | It's meant for local usage in testing / debugging contexts.
        
           | egeozcan wrote:
           | 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.
        
         | rkeene2 wrote:
         | 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/
        
           | cdaringe wrote:
           | actually readable C. kudos
        
             | rkeene2 wrote:
             | 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
        
           | xprueg wrote:
           | Should "test/plain" be "text/plain" "in mime.types?
        
             | rkeene2 wrote:
             | Yes, thank you, it is now fixed :-)
        
         | beklein wrote:
         | 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
        
           | eddd-ddde wrote:
           | TIL you can actually get a single binary to execute in
           | multiple operating systems.
           | 
           | Redbean in general seems great and really interesting.
        
         | kragen wrote:
         | 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
        
         | Alifatisk wrote:
         | Yeah, Vercel created a tool for such case called serve.
         | https://www.npmjs.com/package/serve
        
         | antihero wrote:
         | This is that. Unless you mean having a directory listing, in
         | which case, it is not.
        
         | PhilippGille wrote:
         | 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.
        
         | dclowd9901 wrote:
         | Not sure what single binary means but here's my take.
         | 
         | https://github.com/dclowd9901/posse
        
           | KomoD wrote:
           | Single binary meaning you only need a single binary to run
           | it, nothing else. Yours is not single binary nor zero config
        
         | Borg3 wrote:
         | 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.
        
         | adrianlv512 wrote:
         | 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
        
           | dns_snek wrote:
           | 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.
        
       | gwbas1c wrote:
       | > 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.
        
         | antihero wrote:
         | 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.
        
         | kijin wrote:
         | 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.
        
           | leptons wrote:
           | >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?
        
             | traverseda wrote:
             | 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.
        
             | treve wrote:
             | Perhaps the page existed in the past but now it doesn't. Or
             | at some point a link was misspelled.
        
             | shkkmo wrote:
             | > 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.
        
           | floydnoel wrote:
           | 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).
        
       | jazzjazz wrote:
       | 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
        
       | varbhat wrote:
       | 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`
        
       | Alifatisk wrote:
       | 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#...
        
         | antihero wrote:
         | 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).
        
           | Alifatisk wrote:
           | How has your experience with Nanos unikernel been so far
           | since you started? Have you noticed any benefits over Docker?
        
             | antihero wrote:
             | 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).
        
               | Alifatisk wrote:
               | > 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!
        
               | antihero wrote:
               | The joys of funemployment ;)
        
       | ngrilly wrote:
       | 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.
        
         | antihero wrote:
         | 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.
        
           | pvg wrote:
           | 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?
        
             | antihero wrote:
             | 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.
        
               | pvg wrote:
               | 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.
        
           | traverseda wrote:
           | 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.
        
             | antihero wrote:
             | This doesn't necessarily have to run on Linux.
        
         | andai wrote:
         | 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...)
        
           | binary132 wrote:
           | that sounds broken
        
           | MR4D wrote:
           | 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.
        
         | tempnow987 wrote:
         | 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.
        
           | ngrilly wrote:
           | 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.googleus
           | ercontent.com/media/sre.google/en//st...
           | 
           | sendfile(2): https://man7.org/linux/man-
           | pages/man2/sendfile.2.html
        
         | fabrice_d wrote:
         | https basically makes sendfile unusable for that use case.
        
         | dspillett wrote:
         | _> 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.
        
           | antihero wrote:
           | 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.
        
       | Sohcahtoa82 wrote:
       | > 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.
        
         | __jonas wrote:
         | 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?
        
           | phanimahesh wrote:
           | Handle a prefix maybe?
        
           | ch_sm wrote:
           | 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.
        
             | antihero wrote:
             | This is an opinionated approach that is out of scope really
             | I think. Though if it works for you, awesome!
        
               | shkkmo wrote:
               | 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.
        
               | __jonas wrote:
               | 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.
        
               | withinboredom wrote:
               | 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.
        
           | Sohcahtoa82 wrote:
           | 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?
        
             | __jonas wrote:
             | > 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.
        
               | fragmede wrote:
               | > they aren't the best solution in most cases with what's
               | available now
               | 
               | What's available now that superceds them?
        
               | __jonas wrote:
               | 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/
        
               | shkkmo wrote:
               | > 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.
        
               | Sohcahtoa82 wrote:
               | 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
        
               | __jonas wrote:
               | > 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://news.ycombinator.com/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.
        
               | antihero wrote:
               | 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.
        
             | MrJohz wrote:
             | 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.
        
         | hombre_fatal wrote:
         | 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?
        
           | eddd-ddde wrote:
           | 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?
        
             | shawabawa3 wrote:
             | 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
        
             | antihero wrote:
             | Well client side routing is fundamentally broken unless you
             | do serve a 200 and the index
        
               | shkkmo wrote:
               | 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.
        
               | egeozcan wrote:
               | 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 :)
        
               | antihero wrote:
               | This isn't really feasible, think about how dynamic
               | routes can be? Can you generate a route for every ID of a
               | thing?
        
               | eddd-ddde wrote:
               | 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.
        
           | Sohcahtoa82 wrote:
           | > 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.
        
         | antihero wrote:
         | 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.
        
       | synergy20 wrote:
       | 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?
        
       | debarshri wrote:
       | 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.
        
       | RiverCrochet wrote:
       | There is also Rapid Web Application Server in Assembly:
       | https://2ton.com.au/rwasa/
        
       | password4321 wrote:
       | 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.)?
        
         | antihero wrote:
         | 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.
        
       | lipanski wrote:
       | 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
        
         | Solvency wrote:
         | 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?
        
         | joekrill wrote:
         | 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?
        
       ___________________________________________________________________
       (page generated 2024-03-25 23:01 UTC)