uWebSockets.js icon indicating copy to clipboard operation
uWebSockets.js copied to clipboard

Auto compression for HTTP and use zstd

Open uasan opened this issue 1 year ago • 10 comments

Hey.

I think this topic has already been raised, but once again I want to describe how important it is for real performance.

Most HTTP responses need to be compressed, now this has to be done on the Node side, this is not effective, and Chrome already supports the new compression method zstd: https://chromestatus.com/feature/6186023867908096 Safari and Firefox also plan to implement it.

It is ideal for fast (faster than gzip) stream compression, but Node does not have normal zstd streaming libraries. If uWS implements compression on its side and will check the client Accept-Encoding header, if its value contains zstd then compress zstd, if not then gzip.

This is something that will really speed up all existing servers that use uWS, thank you.

uasan avatar Mar 18 '24 12:03 uasan

Eventually something like this (at least gzip given that zlib which we already depend on, has gzip support) could be added but in what way is not decided (could be automatic like you propose, or just a helper layer, or completely separate). Automatic is kind of promoting inefficient use, as you could in many case pre-compress the file and store it compressed and uncompressed on disk and then serve it based on headers. Then you would have 0 cost server side. So making it in a way that promotes efficient use is key.

uNetworkingAB avatar Mar 20 '24 15:03 uNetworkingAB

Yes, of course, I had that an explicit method was needed, for example something like this: response.isCompresed(true | false) by default there should be no compression.

This method will make it possible not to use compression in Node.js, this will be very useful, over time zstd can be added later

uasan avatar Mar 20 '24 16:03 uasan

if not zstd, brotli can be used too for certain file types.

personally i see this more like a plugin / helper as it's dependent on variable things like

  • what files do u serve
  • how many cores do u use
  • how many servers do u use
  • if you'd serve from disk or from memory
  • if you'd focus on compression speed or compression ratio

some considerations

  • a helper function for serving static files might help, which got its own complexities too
    • use of cache-control and etag headers
    • choice of encoding algos to support
    • normalization and sorting of urls
    • serving the right status codes
    • having authentication and authorization hooks
    • some implementation that we use here: https://gist.github.com/joshxyzhimself/260b4d3b33cc59fd1f64cc0bcd8f83b2
    • hyperexpress has their own implementation at: https://github.com/kartikk221/hyper-express/blob/master/docs/LiveDirectory.md
  • if these helpers are to be made for nodejs, there's extra work for deno and bun too
  • at certain point you stop optimizing for this and just serve these static files from a proper cdn (like bunny.net which is hella cheap), and use uws where it excels the most which is dynamic http requests and websockets

refs

  • https://caniuse.com/zstd
  • https://caniuse.com/brotli

joshxyzhimself avatar Jun 16 '24 23:06 joshxyzhimself

I use a middleware like this:

app.get('/example.js', serveFile('src/example.js'));

app.get('/public/:file', serveDir('public'));

serveFile sets up a file watcher, runs brotliCompress from node:zlib on any file changes, stores the raw bytes and compressed bytes in ram, serves correct file according to request headers and sets Etag header. Might publish that code along with some other helpers

e3dio avatar Jun 17 '24 04:06 e3dio

I'm really looking forward to how this new feature can help me be more productive at work.

drag0n-app avatar Jul 02 '24 01:07 drag0n-app

I will describe my particular case, I need to export data from a database to CSV format via HTTP, the response is 70 MB of text, gzip stream compresses it to 14 MB, but it takes 2 minutes with 100% processor load, this is a performance disaster, so we really need the ability to compress in uWS

uasan avatar Nov 24 '24 11:11 uasan

This functionality is a very good idea for dynamic http responses.

But to be clear, it should not be used to serve static files. For static files you have to either store them on disk already compressed, or compress them and store them on memory at server startup. This way you can use higher-level compression and you don't have to compress for every request.

And then we have to test if it really gives any performance benefit, actually i do someting like this: response.setHeader("Content-Encoding", "gzip").end(gzipSync(htmlPage, {level: 1}));

It could be implemented by a custom endCompress() function that automatically choose the best algorithm and set response Content-Encoding header accordingly, something like: response.endCompress(htmlPage);

RatchetS2 avatar Mar 07 '25 10:03 RatchetS2

What I do myself is to just pre-compress everything in RAM when the root folder changes, and serve from pre-compressed RAM. That's really the way to go for HTTP, since it benefits from being state-less unlike WebSockets.

uNetworkingAB avatar May 18 '25 07:05 uNetworkingAB

You already have functions in Node.js to easily compress, and you just add a header. So really nothing to add in the library.

uNetworkingAB avatar May 18 '25 07:05 uNetworkingAB

@uNetworkingAB you are right, the zstd compression method appeared in the node, but there are 2 options, synchronous, which blocks the process but is not slow, or streaming, which does not block, but is slow, so I suggested adding this to your library (fast and non-blocking).

This only makes sense for dynamic content, for immutable statics, it is more profitable to compress in advance and give out compressed, I absolutely agree with this.

uasan avatar May 18 '25 18:05 uasan