Opened 8 years ago
Closed 7 years ago
#1232 closed defect (wontfix)
nginx returns 412 given if-match, thus ignoring etag from upstream
Reported by: | Alexander Sergeyev | Owned by: | |
---|---|---|---|
Priority: | minor | Milestone: | |
Component: | other | Version: | 1.11.x |
Keywords: | Cc: | ||
uname -a: | Linux 4.10.3-gentoo x86_64 | ||
nginx -V: |
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 |
Description
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.
Change History (8)
comment:1 by , 8 years ago
comment:2 by , 8 years ago
If you want to supply custom ETag's by your backend, consider checking conditional headers by the backend as well.
Well, conditional headers are checked by the backend. The problem is a bit more complicated.
If-Match is verified by the backend and it succeeds (no precondition failure). But then nginx is not passing the response as it is expected, but returns 412 instead. And the reason is that nginx carries out its own check for if-match and during this check there is no etag to match (because it would be added later).
comment:3 by , 8 years ago
while it is possible to add any headers to the response using the add_header directive, these headers do not affect processing of conditional headers
This makes sense, yes. But this also means If-Match
is broken for X-Accel-Redirect
+ backend ETag
, because there is no way to prevent precondition failure error (nginx doesn't known about upstream etag when it is needed). Unless there is some other way to configure backend etags (ie not add_header
).
comment:4 by , 8 years ago
Ok, so the only way seems to use proxying with If-*
headers removed after X-Accel-Redirect. This will resolve the problem by disabling conditional headers processing in nginx itself.
comment:5 by , 8 years ago
use proxying with
If-*
headers removed afterX-Accel-Redirect
I think you're suggesting something like:
location ~ ^/x-accel-landing/(.*)$ { internal; proxy_pass http://127.0.0.1:8000/actual-static/$1; proxy_set_header If-Match ""; } location /actual-static { alias /tmp; } location / { uwsgi_pass ...; }
Is this what you're proposing?
This approach has some problems: (*) rebuffering file data in self-proxied request (*) excessive fd usage.
Also, I cannot make actual-static
location internal
, thus creating some security issues. Fixing these will employ additional complexity and inefficiency which is sad.
Is this issue techically/architecturally possible to fix given that I would like to try fixing these myself (maybe with some guidance though)?
follow-up: 7 comment:6 by , 8 years ago
Yes, obviously this won't be as effective as serving static files directly. As for access control, there is more than one way to do things properly.
In your particular case, most simple solution would be to flip the "redirect" flag in ngx_http_upstream_headers_in
(in ngx_http_upstream.c
) for ETag
, so nginx will copy / inherit ETag
from the original response with X-Accel-Redirect
. Though for obvious reasons this solution isn't something can be committed.
As for the add_header
behaviour, changes here are likely to introduce many other problems, and better to be avoided without very good understanding of the consequences. Therefore "unlikely to be changed" above.
comment:7 by , 8 years ago
Though for obvious reasons this solution isn't something can be committed.
Therefore "unlikely to be changed" above
I see. There is always an other way to introduce some option for add_header
which defaults to the current behaviour or just entirely new directive. But then I suppose it's too rare usecase for this to be justified.
Thank you.
PS. Nevertheless, it's not so obvious pitfall, maybe it should be mentioned somewhere around X-Accel-Redirect
or add_header
(ie here is not the whole story: https://forum.nginx.org/read.php?2,205636,205665).
comment:8 by , 7 years ago
Resolution: | → wontfix |
---|---|
Status: | new → closed |
Closing this. As explained above, current behaviour is highly unlikely to be changed.
That's expected behaviour. Custom headers as configured with
add_header
are added to the response byngx_http_headers_filter
, and this happens afterngx_http_not_modified_filter
which checksIf-Not-Modified
,If-Match
, and other conditional headers. Therefore, while it is possible to add any headers to the response using theadd_header
directive, these headers do not affect processing of conditional headers.(Moreover, the "do not affect processing" thing is also generally true for other headers as well, even if they are expected to be handled by filters which come after
ngx_http_headers_filter
, as headers filter generally do not try to overwrite existing headers and/or set appropriate internal pointers to the headers added.)This is unlikely to be changed. If you want to supply custom ETag's by your backend, consider checking conditional headers by the backend as well.