Zero bytes responses – cache or not to cache?

Post Written by
Ivan Dabić
Last modified on August 15th, 2020 at 12:27 pm

Should 0 bytes responses be cached or not (on middle points like CDN or other proxies) is a problem that can be addressed by two entities:

  1. RFC
  2. Client’s request

For client’s request to be met it’s assumed that #1 is honored. What RFC says for 0 bytes caching, basically, is that if a file (or other asset/object) is requested to be delivered as a full object and server delivers it incompletely unless incomplete delivery has been requested server must mark it as partial content. Otherwise this delivery is treated as broken delivery - corrupted delivery. Below is a sequence from RFC 7234 that defines the rule for object caching relative to content delivery: A cache MAY complete a stored incomplete response by making a subsequent range request ([RFC7233]) and combining the successful response with the stored entry, as defined in Section 3.3. A cache MUST NOT use an incomplete response to answer requests unless the response has been made complete or the request is partial and specifies a range that is wholly within the incomplete response. A cache MUST NOT send a partial response to a client without explicitly marking it as such using the 206 (Partial Content) status code. Workaround (with snippet located at: https://gist.github.com/caquino/5506639) is manipulating variable based on the upstream content length provided and it is then used in proxy_no_cache to prevent caching of a response without healthy content:

map $upstream_http_content_length $flag_cache_empty {
        default         0;
        0               1;
}

server {
        ....
        location / {
                ...
                proxy_no_cache $flag_cache_empty;
                proxy_cache_bypass $flag_cache_empty;
                ...
        }
}

Test

Below is a test case with single vps box with apache (port 8080) and nginx (port 80 -> caching from 8080). Apache is delivering intentionally created 0 byte file which is proxied through nginx. Plan is to catch Content-Length on nginx side and perform proxy_no_cache with appropriate value (which, as explained, depends on Content-Length header): apache conf:

<VirtualHost *:8080> 
 ServerAdmin webmaster@localhost
 DocumentRoot /var/www/html
 ErrorLog ${APACHE_LOG_DIR}/error.log
 CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

nginx conf block

map $upstream_http_content_length $flag_cache_empty {
 default         0;
 0               1;
}


server {
 listen 80 default_server;
 listen https://www.linkedin.com/redir/invalid-link-page?url=%5B%3A%3A%5D:80 default_server ipv6only=on;
 
 root /usr/share/nginx/html;
 index index.html index.htm;
 
 # Make site accessible from http://localhost/
 server_name localhost;
 
proxy_cache idabic;
 
 location / {
 
proxy_pass http://localhost:8080;
proxy_cache_valid 200 1d;
proxy_cache_min_uses 2;
gzip on;
gzip_min_length 100;
gzip_types text/plain text/xml application/xml text/css text/javascript application/javascript application/x-javascript text/x-component application/json application/xhtml+xml application/rss+xml application/atom+xml application/vnd.ms-fontobject image/svg+xml application/x-font-ttf font/opentype application/octet-stream;
gzip_comp_level 1;
gzip_disable "MSIE [1-6]\.";
 
add_header Ivan-Cache $upstream_cache_status;
add_header CL $upstream_http_content_length;
add_header CL0 $flag_cache_empty;
proxy_no_cache $flag_cache_empty;
 
proxy_cache_key $scheme$http_host$uri$is_args$args;
 }

cURL test:

~# curl -I http://localhost/empty_file.js
HTTP/1.1 200 OK
Server: nginx/1.4.6 (Ubuntu)
Date: Sat, 03 Oct 2015 15:09:42 GMT
Content-Type: application/javascript
Content-Length: 0
Connection: keep-alive
Last-Modified: Sat, 03 Oct 2015 15:02:26 GMT
ETag: "0-52134917e9217"
Accept-Ranges: bytes
Ivan-Cache: MISS
CL: 0
CL0: 1

wget:

~# wget -O /dev/null -d http://localhost/empty_file.js
DEBUG output created by Wget 1.15 on linux-gnu.
 
URI encoding = ‘UTF-8’
--2015-10-03 11:10:01--  http://localhost/empty_file.js
Resolving localhost (localhost)... 127.0.0.1
Caching localhost => 127.0.0.1
Connecting to localhost (localhost)|127.0.0.1|:80... connected.
Created socket 4.
Releasing 0x00000000014cf180 (new refcount 1).
 
---request begin---
GET /empty_file.js HTTP/1.1
User-Agent: Wget/1.15 (linux-gnu)
Accept: */*
Host: localhost
Connection: Keep-Alive
 
---request end---
HTTP request sent, awaiting response...
---response begin---
HTTP/1.1 200 OK
Server: nginx/1.4.6 (Ubuntu)
Date: Sat, 03 Oct 2015 15:10:01 GMT
Content-Type: application/javascript
Content-Length: 0
Connection: keep-alive
Last-Modified: Sat, 03 Oct 2015 15:02:26 GMT
ETag: "0-52134917e9217"
Accept-Ranges: bytes
Ivan-Cache: MISS
CL: 0
CL0: 1
 
---response end---
200 OK
Registered socket 4 for persistent reuse.
Length: 0 [application/javascript]
Saving to: ‘/dev/null’
 
   [ <=>                                                                                                                 ] 0           --.-K/s   in 0s      
 
2015-10-03 11:10:01 (0.00 B/s) - ‘/dev/null’ saved [0/0]

Contact Us

Fill out the enquiry form and we'll get back to you as soon as possible.