﻿id	summary	reporter	owner	description	type	status	priority	milestone	component	version	resolution	keywords	cc	uname	nginx_version
1232	nginx returns 412 given if-match, thus ignoring etag from upstream	Alexander Sergeyev		"The used workflow as follows: the upstream wsgi server knows etags/versions of resources, but these resources are static and available for nginx to serve directly from fs. The upstream returns x-accel-redirect to an internal location, but also provides etag header to pass along with response.

The sample configuration:

http {
    etag off;

    upstream test_upstream {
        server 127.0.0.1:8080;
    }

    server {
        listen 0.0.0.0:8000;

        location /x-accel-images {
            internal;
            add_header ETag $upstream_http_etag;
            alias /tmp;
        }

        location / {
            uwsgi_pass test_upstream;
            uwsgi_param QUERY_STRING    $query_string;
            uwsgi_param REQUEST_METHOD  $request_method;
            uwsgi_param CONTENT_TYPE    $content_type;
            uwsgi_param CONTENT_LENGTH  $content_length;
            uwsgi_param REQUEST_URI     $request_uri;
            uwsgi_param PATH_INFO       $document_uri;
            uwsgi_param DOCUMENT_ROOT   $document_root;
            uwsgi_param SERVER_PROTOCOL $server_protocol;
            uwsgi_param REQUEST_SCHEME  $scheme;
            uwsgi_param HTTPS           $https if_not_empty;
            uwsgi_param REMOTE_ADDR     $remote_addr;
            uwsgi_param REMOTE_PORT     $remote_port;
            uwsgi_param SERVER_PORT     $server_port;
            uwsgi_param SERVER_NAME     $server_name;
            uwsgi_param HTTP_PROXY      """";
        }
    }
}


This works okay when no special caching headers are in play. If-None-Match is handled on the upstream, works as expected. But a problem arises when If-Match is requested by a client.

The upstream server receives a request from nginx, sees that if-match is safisfied and returns a 200 response to nginx (providing etag along with it). But nginx receives the upstream response, seemingly ignores the upstream etag and returns 412 to the client.

More specifically, with ""etag on"" (just for debugging):
2017/03/31 12:32:11 [debug] 21044#0: *1 http im:""""123"""" etag:""58dd1610-e""
2017/03/31 12:32:11 [debug] 21044#0: *1 http special response: 412, ""/x-accel-images/123/456?""

Here it looks like nginx generates its own etag, sees unsatisfied if-match and returns 412.

With ""etag off"":
<...>
2017/03/31 12:34:20 [debug] 21076#0: *1 uwsgi param: ""HTTP_HOST: 127.0.0.1:8000""
2017/03/31 12:34:20 [debug] 21076#0: *1 uwsgi param: ""HTTP_USER_AGENT: curl/7.53.1""
2017/03/31 12:34:20 [debug] 21076#0: *1 uwsgi param: ""HTTP_ACCEPT: */*""
2017/03/31 12:34:20 [debug] 21076#0: *1 uwsgi param: ""HTTP_IF_MATCH: ""123""""
<...>
2017/03/31 12:34:20 [debug] 21076#0: *1 http uwsgi status 200 ""200 OK""
2017/03/31 12:34:20 [debug] 21076#0: *1 http uwsgi header: ""X-Accel-Redirect: /x-accel-images/123/456""
2017/03/31 12:34:20 [debug] 21076#0: *1 http uwsgi header: ""ETag: ""123""""
2017/03/31 12:34:20 [debug] 21076#0: *1 http uwsgi header: ""Content-Type: text/html; charset=utf-8""
2017/03/31 12:34:20 [debug] 21076#0: *1 http uwsgi header: ""Content-Length: 0""
2017/03/31 12:34:20 [debug] 21076#0: *1 http uwsgi header done
<...>
2017/03/31 12:34:20 [debug] 21076#0: *1 internal redirect: ""/x-accel-images/123/456?""
2017/03/31 12:34:20 [debug] 21076#0: *1 rewrite phase: 0
2017/03/31 12:34:20 [debug] 21076#0: *1 test location: ""/""
2017/03/31 12:34:20 [debug] 21076#0: *1 test location: ""x-accel-images""
2017/03/31 12:34:20 [debug] 21076#0: *1 using configuration ""/x-accel-images""
2017/03/31 12:34:20 [debug] 21076#0: *1 http cl:-1 max:1048576
2017/03/31 12:34:20 [debug] 21076#0: *1 rewrite phase: 2
2017/03/31 12:34:20 [debug] 21076#0: *1 post rewrite phase: 3
2017/03/31 12:34:20 [debug] 21076#0: *1 access phase: 4
2017/03/31 12:34:20 [debug] 21076#0: *1 post access phase: 5
2017/03/31 12:34:20 [debug] 21076#0: *1 content phase: 6
2017/03/31 12:34:20 [debug] 21076#0: *1 content phase: 7
2017/03/31 12:34:20 [debug] 21076#0: *1 content phase: 8
2017/03/31 12:34:20 [debug] 21076#0: *1 http filename: ""/tmp/123/456""
2017/03/31 12:34:20 [debug] 21076#0: *1 add cleanup: 00000000017558A0
2017/03/31 12:34:20 [debug] 21076#0: *1 http static fd: 9
2017/03/31 12:34:20 [debug] 21076#0: *1 http special response: 412, ""/x-accel-images/123/456?""
2017/03/31 12:34:20 [debug] 21076#0: *1 HTTP/1.1 412 Precondition Failed
Server: nginx/1.11.11
Date: Fri, 31 Mar 2017 09:34:20 GMT
Content-Type: text/html
Content-Length: 190
Connection: keep-alive


In code:

ngx_http_not_modified_header_filter (src/http/modules/ngx_http_not_modified_filter_module.c):
    r->headers_in.if_match is true => calls ngx_http_test_if_match

ngx_http_test_if_match (src/http/modules/ngx_http_not_modified_filter_module.c):
   r->headers_out.etag == NULL is true => returning 412

r->headers_out.etag is NULL in ngx_http_not_modified_header_filter(), it looks like add_header is processed later, thus causing this behaviour."	defect	closed	minor		other	1.11.x	wontfix			Linux 4.10.3-gentoo x86_64	"nginx version: nginx/1.11.11
configure arguments: --prefix=/usr --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error_log --pid-path=/run/nginx.pid --lock-path=/run/lock/nginx.lock --with-cc-opt='-I/usr/include -DNGX_HAVE_INET6=0' --with-ld-opt=-L/usr/lib64 --http-log-path=/var/log/nginx/access_log --http-client-body-temp-path=/var/lib/nginx/tmp/client --http-proxy-temp-path=/var/lib/nginx/tmp/proxy --http-fastcgi-temp-path=/var/lib/nginx/tmp/fastcgi --http-scgi-temp-path=/var/lib/nginx/tmp/scgi --http-uwsgi-temp-path=/var/lib/nginx/tmp/uwsgi --with-compat --with-debug --with-http_v2_module --with-pcre --without-http_access_module --without-http_auth_basic_module --without-http_autoindex_module --without-http_browser_module --without-http_charset_module --without-http_empty_gif_module --without-http_fastcgi_module --without-http_geo_module --without-http_limit_req_module --without-http_limit_conn_module --without-http_map_module --without-http_memcached_module --without-http_proxy_module --without-http_referer_module --without-http_scgi_module --without-http_ssi_module --without-http_split_clients_module --without-http_upstream_hash_module --without-http_upstream_ip_hash_module --without-http_upstream_keepalive_module --without-http_upstream_least_conn_module --without-http_upstream_zone_module --without-http_userid_module --with-http_auth_request_module --with-http_gzip_static_module --without-stream_access_module --without-stream_geo_module --without-stream_limit_conn_module --without-stream_map_module --without-stream_return_module --without-stream_split_clients_module --without-stream_upstream_hash_module --without-stream_upstream_least_conn_module --without-stream_upstream_zone_module --without-mail_imap_module --without-mail_pop3_module --without-mail_smtp_module --user=nginx --group=nginx"
