11

We have a Node/express web app that is serving static assets in addition to normal content, via express.static(). There is an nginx server in front of it that is currently configured to gzip these static asset requests, if the user agent is up for it.

However, though nginx is doing the gzip as expected, it is dropping the Content-Length header from the origin, and setting Transfer-Encoding: chunked instead. This breaks caching on our CDN.

Below are the responses for a typical static asset request (a JS file in this case), from the node backend, and from nginx:

Request:

curl -s -D - 'http://my_node_app/res/my_js.js' -H 'Accept-Encoding: gzip, deflate, sdch' -H 'Connection: keep-alive' --compressed -o /dev/null

Response Headers from Node:

HTTP/1.1 200 OK
Accept-Ranges: bytes
Date: Wed, 07 Jan 2015 02:24:55 GMT
Cache-Control: public, max-age=0
Last-Modified: Wed, 07 Jan 2015 01:12:05 GMT
Content-Type: application/javascript
Content-Length: 37386   // <--- The expected header
Connection: keep-alive

Response Headers from nginx:

HTTP/1.1 200 OK
Server: nginx
Date: Wed, 07 Jan 2015 02:24:55 GMT
Content-Type: application/javascript
Transfer-Encoding: chunked  // <--- The problematic header
Connection: keep-alive
Vary: Accept-Encoding
Cache-Control: public, max-age=0
Last-Modified: Wed, 07 Jan 2015 01:12:05 GMT
Content-Encoding: gzip

Our current nginx configuration for the static assets location looks like below:

nginx config:

# cache file paths that start with /res/
location /res/ {
    limit_except GET HEAD { }

    # http://nginx.com/resources/admin-guide/caching/
    # http://nginx.org/en/docs/http/ngx_http_proxy_module.html

    proxy_buffers 8 128k;
    #proxy_buffer_size 256k;
    #proxy_busy_buffers_size 256k;

    # The cache depends on proxy buffers, and will not work if proxy_buffering is set to off.
    proxy_buffering     on;
    proxy_http_version  1.1;
    proxy_set_header  Connection "";
    proxy_connect_timeout  2s;
    proxy_read_timeout  5s;
    proxy_pass          http://node_backend;

    chunked_transfer_encoding off;

    proxy_cache         my_app;
    proxy_cache_valid   15m;
    proxy_cache_key     $uri$is_args$args;
}

As can be seen from the above config, even though we've explicitly set chunked_transfer_encoding off for such paths as per the nginx docs, have proxy_buffering on, and have a big enough proxy_buffers size, the response is still being chunked.

What are we missing here?

--Edit 1: version info--

$ nginx -v
nginx version: nginx/1.6.1

$ node -v
v0.10.30

--Edit 2: nginx gzip config--

# http://nginx.org/en/docs/http/ngx_http_gzip_module.html
gzip on;
gzip_buffers 32 4k;
gzip_comp_level 1;
gzip_min_length 1000;
#gzip_http_version 1.0;
gzip_types application/javascript text/css
gzip_proxied any;
gzip_vary on;
kodeninja
  • 339
  • 1
  • 7
  • 23
  • 1
    Where are you telling nginx to gzip? It's doing a streaming gzip, for which chunked is the only sensible option. Instead you should have the static assets in raw and gzipped form and configure nginx to serve the gziped assets when requested. In other words pre gzip instead of gzipping on the fly. – generalhenry Jan 07 '15 at 22:08
  • @generalhenry I added the `gzip` config as well. You're right that the assets are being zipped on the fly. I also saw the `gzip_static` module that helps do what you've described. So, I guess in our case, we'll either have to host the raw+gzipped assets on `nginx` itself, configure `gzip_static` and then it'll do the right thing. Or, have them pre-built during our asset build phase in `Node`, build some smarts on top of `express.static()` to serve one or the other based on the current request, and turn off `gzip` on `nginx`. – kodeninja Jan 07 '15 at 22:47
  • Yeah gzip_proxied any; looks to be the culprit (disclaimer, I'm not at all an expert on nginx, I just think in terms of streams) – generalhenry Jan 07 '15 at 23:09
  • 1
    Hello, Any success to this? We are also facing the same issue. – jigneshbrahmkhatri Sep 24 '15 at 10:11
  • I have same issue http://serverfault.com/questions/745153/images-failed-to-load-resource-the-network-connection-was-lost and I also tried off `gzip` on `nginx` and `chunked_transfer_encoding off` and set header in node.js `res.header('transfer-encoding', ''); res.header('Content-Length', 0);` all these not work... – user1575921 Dec 24 '15 at 16:27

1 Answers1

2

You are correct, let me elaborate.

The headers are the first thing that need to get sent. However since you are using streaming compression, the final size is uncertain. You only know the size of the uncompressed asset, and sending a Content-Length too large would also be incorrect.

Thus, there are two options:

  1. transfer encoding chunked
  2. Completely Compress the asset before sending any data, so the compressed size is known

Currently, you're experiencing the first case, and it sounds like you really need the second. The easiest way to get the second case is to turn on gzip_static as @kodeninja said in the comments.

EnabrenTane
  • 7,428
  • 2
  • 26
  • 44