(97: Address Family Not Supported by Protocol) Nginx
We have a spider web application which has to accept Mail-requests from clients.
In front of this awarding, there is some proxy service, no thing which – initially, nosotros faced the issues on an AWS's Application Load Balancer, so I reproduced them with NGINX, and it will "work" for whatever other proxying system.
Also proxying – this service also performs an HTTP (fourscore) to HTTPS (443) redirect.
The problem appears accurately during such a redirect:
- a client sends a
Mail servicerequests via HTTP - a proxy returns 301 or 302 redirect to HTTPS
- the client then sends a request via HTTPS, but:
- in some cases this
PostbecomesBecome - or it still will be
POSTbut all its information will be "lost"
- in some cases this
A testing environs setup
For the testing purpose, allow'due south apply the next configuration:
- a NINGX accepts API-requests
- NGINX passes the via
proxy_passby HTTP to a backend-service- to reproduce the
PosttoGetproblem – a backend with a Go awarding in Docker container will be used - to reproduce the "lost" data and empty
Сontent-length– a Python script will exist used to run as a web-server
- to reproduce the
NGINX
Just an mutual config – NGINX, heed on 80, makes redirects to HTTPS:
server { heed fourscore; server_name dev.poc.example.com; location / { return 302 https://dev.poc.example.com$request_uri; } } ... And a server 443 {} – also common config with a proxy_pass to the backend on an 8081 port:
... server { listen 443 ssl; server_name dev.poc.instance.com; ... location / { proxy_pass http://localhost:8081; proxy_set_header Host $host; proxy_set_header X-Existent-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } Go-app in Docker
Nosotros have multiple containers in a stack there, but currently we are interested in a get-queue-consumer with a local NGINX (the whole initial system however in the Proof of Concept state now, so don't wonder about too many NGINXes here).
We're just interested in its logs with a POST or GET methods.
Python web-server
And some other web-server to play the backend part to see the data length issue – speedily googled Python script:
#!/usr/bin/env python3 """ Very simple HTTP server in python for logging requests Usage:: ./server.py [<port>] """ from http.server import BaseHTTPRequestHandler, HTTPServer import logging class S(BaseHTTPRequestHandler): def _set_response(self): self.send_response(200) self.send_header('Content-blazon', 'text/html') self.end_headers() def do_GET(self): logging.info("GET request,\nPath: %south\nHeaders:\n%due south\n", str(self.path), str(self.headers)) cocky._set_response() self.wfile.write("Become request for {}".format(cocky.path).encode('utf-eight')) def do_POST(cocky): content_length = int(self.headers['Content-Length']) # <--- Gets the size of data post_data = cocky.rfile.read(content_length) # <--- Gets the data itself logging.info("Mail service asking,\nPath: %s\nHeaders:\n%southward\n\nBody:\n%s\due north", str(self.path), str(self.headers), post_data.decode('utf-8')) self._set_response() self.wfile.write("POST asking for {}".format(self.path).encode('utf-viii')) def run(server_class=HTTPServer, handler_class=South, port=8081): logging.basicConfig(level=logging.INFO) server_address = ('', port) httpd = server_class(server_address, handler_class) logging.info('Starting httpd...\n') effort: httpd.serve_forever() except KeyboardInterrupt: laissez passer httpd.server_close() logging.info('Stopping httpd...\northward') if __name__ == '__main__': from sys import argv if len(argv) == ii: run(port=int(argv[one])) else: run() Examples: reproduce the Trouble
A POST'southward data lost later on a redirect
The first thing we faced with was the fact that after HTTP to HTTPS redirects – our POST requests lose their data.
To reproduce information technology – let'south apply the Python script mentioned to a higher place.
Run it on the 8081 port:
root@ip-x-0-fifteen-118:/home/admin# ./test_post.py 8081
INFO:root:Starting httpd...
And run gyre with Postal service and some data in --information:
curl -vL -10 POST http://dev.poc.example.com/ -d "param1=value1¶m2=value2"
...
* Trying 52.***.***.224:fourscore...
...
> Content-Length: 27
...
< HTTP/i.one 302 Moved Temporarily
...
< Content-Length: 161
< Connectedness: keep-live
< Location: https://dev.poc.example.com/
...
> Post / HTTP/1.1
> Host: dev.poc.example.com
> User-Agent: curl/7.67.0
> Take: */*
>
* Mark bundle as non supporting multiuse
< HTTP/one.1 502 Bad Gateway
< Server: nginx/1.10.iii
...
< Content-Type: text/html
< Content-Length: 173
Permit's see what' happening here:
- a
Postal serviceasking is sent via HTTP, Content-Length: 27 - a 302 redirect issued to HTTPS
- and a
POSTrequest is sent via HTTPS, Content-Length: 173
In the NGINX logs, we tin meet a standard HTTP Mail service:
...
==> /var/log/nginx/dev.poc.example.com-error.log <==
2019/11/23 09:52:41 [fault] 19793#19793: *51100 upstream prematurely closed connectedness while reading response header from upstream, customer: 194.***.***.26, server: dev.poc.example.com, request: "Post / HTTP/one.1", upstream: "http://127.0.0.one:8081/", host: "dev.poc.case.com"
==> /var/log/nginx/dev.poc.example.com-access.log <==
194.***.***.26 - - [23/November/2019:09:52:41 +0000] "POST / HTTP/1.1" 502 173 "-" "gyre/vii.67.0"
...
But let's have a look at the stderr of the Python-server running:
...
Exception happened during processing of request from ('127.0.0.1', 38224)
Traceback (most recent call last):
File "/usr/lib/python3.5/socketserver.py", line 313, in _handle_request_noblock
self.process_request(request, client_address)
File "/usr/lib/python3.v/socketserver.py", line 341, in process_request
self.finish_request(request, client_address)
File "/usr/lib/python3.5/socketserver.py", line 354, in finish_request
self.RequestHandlerClass(request, client_address, self)
File "/usr/lib/python3.5/socketserver.py", line 681, in __init__
self.handle()
File "/usr/lib/python3.5/http/server.py", line 422, in handle
self.handle_one_request()
File "/usr/lib/python3.5/http/server.py", line 410, in handle_one_request
method()
File "./test_post.py", line 22, in do_POST
content_length = int(self.headers['Content-Length']) # <--- Gets the size of data
TypeError: int() statement must be a string, a bytes-like object or a number, non 'NoneType'
...
Pay attention at those lines:
…
content_length = int(self.headers['Content-Length'])
TypeError: int() argument must be a string, a bytes-like object or a number, not 'NoneType'
Due east.g., the application got an empty/missing Content-Length field.
Still, if issue a new request directly via HTTP (with the redirect disabled on NGINX) or via HTTPS – everything will piece of work, as expected:
curl -vL -X Mail service https://dev.poc.instance.com/ -d "param1=value1¶m2=value2"
...
> Post / HTTP/1.1
> Host: dev.poc.example.com
> User-Agent: coil/7.67.0
> Accept: */*
> Content-Length: 27
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 27 out of 27 bytes
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: nginx/1.10.3
< Appointment: Sat, 23 Nov 2019 09:55:07 GMT
< Content-Type: text/html
< Transfer-Encoding: chunked
< Connectedness: keep-alive
<
* Connection #0 to host dev.poc.instance.com left intact
POST asking for /
And the Python'due south application stdout:
...
INFO:root:POST request,
Path: /
Headers:
Host: dev.poc.example.com
X-Existent-IP: 194.***.***.26
X-Forwarded-For: 194.***.***.26
X-Forwarded-Proto: https
Connection: shut
Content-Length: 27
User-Agent: curl/vii.67.0
Accept: */*
Content-Blazon: awarding/x-world wide web-class-urlencoded
Body:
param1=value1¶m2=value2
127.0.0.1 - - [23/Nov/2019 09:55:07] "POST / HTTP/ane.0" 200 -
Now, let'southward get to the next second problem.
A Post became GET
During debugging the trouble discussed to a higher place, when a backend didn't receive data during Postal service I found another "exciting" behavior of a asking during HTTP-redirects.
Let'south see how our Mail service request volition go… a GET request!
The details and the root cause volition exist examined subsequently on this postal service, and now let'southward reproduce the problem using two HTTP-clients – curlicue and Postman.
scroll
Run a request with the POST blazon via HTTP with -L to follow a redirect to the HTTPS.
In this example, we are using not the Python script, used above, but our real backend Docker container to demonstrate its piece of work and to check its logs.
The information itself, besides as any errors, have no value here – nosotros are interested just in requests methods used – POST and GET.
coil -vL -X POST http://dev.poc.example.com/skin/api/v1/receipt -d "{}"
...
> POST /skin/api/v1/receipt HTTP/ane.1
> Host: dev.poc.case.com
> User-Amanuensis: curl/7.67.0
> Have: */*
>
* Marker bundle as non supporting multiuse
< HTTP/1.i 400 Bad Request
< Server: nginx/i.10.3
< Date: Saturday, 23 Nov 2019 10:07:37 GMT
< Content-Type: application/json; charset=utf-8
< Content-Length: 58
< Connection: keep-alive
<
* Connection #1 to host dev.poc.instance.com left intact
{"message":"Validation failed: unable to parse json body"}
Once again – do not look at the errors hither – instead, let'south go to the NGINX'due south logs:
==> /var/log/nginx/dev.poc.instance.com-access.log <==
194.***.***.26 - - [23/Nov/2019:x:07:37 +0000] "Postal service /skin/api/v1/receipt HTTP/1.ane" 400 58 "-" "curl/7.67.0"
Seems all practiced hither? A POST was sent – a POST was received.
Simply to check – permit'due south run curl with explicitly specified Get type to meet the backend'south response and NGINX logs in this example:
curl -L -X Get http://dev.poc.example.com/peel/api/v1/receipt -d "{}"
404 folio not institute
And NGINX logs with this Get:
==> /var/log/nginx/dev.poc.example.com-access.log <==
194.***.***.26 - - [23/Nov/2019:10:07:37 +0000] "POST /skin/api/v1/receipt HTTP/i.ane" 400 58 "-" "coil/7.67.0"
194.***.***.26 - - [23/Nov/2019:10:09:57 +0000] "GET /skin/api/v1/receipt HTTP/i.1" 404 18 "-" "curl/7.67.0"
All good, all seems to be right – no issues at all, huh?
Postman
But now, permit's utilise Postman to send the same request to the same endpoint: a POST via HTTP to trigger and follow a redirect to the HTTPS:
And?…
And – permit's see NGINX logs now:
==> /var/log/nginx/dev.poc.example.com-access.log <==
194.***.***.26 - - [23/Nov/2019:x:07:37 +0000] "POST /skin/api/v1/receipt HTTP/one.1" 400 58 "-" "scroll/7.67.0"
194.***.***.26 - - [23/Nov/2019:10:09:57 +0000] "GET /skin/api/v1/receipt HTTP/1.i" 404 18 "-" "whorl/7.67.0"
194.***.***.26 - - [23/Nov/2019:10:11:44 +0000] "GET /peel/api/v1/receipt HTTP/1.ane" 404 xviii "http://dev.poc.case.com/skin/api/v1/receipt" "PostmanRuntime/7.19.0"
Errr…
What?!?
But I sent an explicitly specified POST request?
Again:
- brand a request with
coil, HTTP => HTTPS redirect issued,Postin logs – all good - make a request with Postman, HTTP => HTTPS redirect issued, merely
GETin logs – WTF???
Mail, GET and a "lost" data
Well, at present we tin can understand where our information went – because our POST became GET.
The root cause, 3xx redirects, and HTTP RFC
Actually, both problems are caused by the same reason.
Let's start our investigation by reading the 301 lawmaking description in the RFC 2616 – https://tools.ietf.org/html/rfc2616#section-x.3.2, specially its Note :
Note: When automatically redirecting a Post request after
receiving a 301 status code, some existing HTTP/i.0 user agents
will erroneously modify it into a Go request.
I.e., some clients after sending POST and receiving 301 – will change the requests' type to the GET.
But this just the very starting time!
During the further reading, at the 302 code description in the same RFC 2016 – https://tools.ietf.org/html/rfc2616#section-10.iii.3 we'll see another Note :
Notation: RFC 1945 and RFC 2068 specify that the client is not allowed
to alter the method on the redirected request. However, most
existing user amanuensis implementations treat 302 equally if it were a 303
response, performing a Become on the Location field-value regardless
of the original request method. The status codes 303 and 307 accept
been added for servers that wish to brand unambiguously clear which
kind of reaction is expected of the customer.
RFC 1945 about 3хх redirects – https://tools.ietf.org/html/rfc1945#section-9.three
RFC 2068 about 3хх redirects – https://tools.ietf.org/html/rfc2068#section-ten.3.2
I.e., RFC 1945 and RFC 2068 states that a client has no modify blazon for a redirected request, but most of them will treat the 302 code as… 303!
Let's get to the next office and read about the 303 code – https://tools.ietf.org/html/rfc2616#section-10.3.4:
The response to the asking can exist constitute nether a different URI and
SHOULD be retrieved using a GET method on that resource.
I.east., when a customer considers that it received the 303 code – it always will perform the GET.
So, what we saw with the Postman case (and our mobile application clients where nosotros faced that issue at first):
- a client performs
Mailby HTTP - gets a redirect to the HTTPS with either 301 or 302
- considers it equally 303
- and changes its own request's type sent via HTTPS from to the
Get, "losing" all original'southward information
The Solution
I found the solution after reading the Mozilla's documentation (although there was a hint in the RFC 2016 Notes for the 302), which says almost 301 and 302 redirects:
It is therefore recommended to set the 302 code only equally a response for Get or Head methods and to use 307 Temporary Redirect instead, as the method change is explicitly prohibited in that case.
Okay – let's update our NGINX and change lawmaking 302 to the 307:
server { listen 80; ... location / { # render 302 https://dev.poc.example.com$request_uri; return 307 https://dev.poc.example.com$request_uri; } } ... Reload configs, and repeat the request from curl:
roll -L -X POST http://dev.poc.example.com/pare/api/v1/receipt -d "{}"
{"message":"Validation failed: fields 'hardware_id' and 'receipt' are mandatory"}
And nosotros got a valid response from the backend, which simply asks for a normal data now.
Redirect worked, a POST request was received.
And the NGINX log:
==> /var/log/nginx/dev.poc.example.com-access.log <==
194.***.***.26 - - [23/Nov/2019:ten:07:37 +0000] "Mail service /skin/api/v1/receipt HTTP/one.1" 400 58 "-" "curl/7.67.0"
194.***.***.26 - - [23/Nov/2019:x:09:57 +0000] "GET /skin/api/v1/receipt HTTP/one.ane" 404 xviii "-" "scroll/7.67.0"
194.***.***.26 - - [23/Nov/2019:10:11:44 +0000] "GET /skin/api/v1/receipt HTTP/1.1" 404 18 "http://dev.poc.example.com/skin/api/v1/receipt" "PostmanRuntime/7.19.0"
194.***.***.26 - - [23/November/2019:10:35:51 +0000] "Mail service /pare/api/v1/receipt HTTP/i.1" 422 81 "-" "curl/7.67.0"
194.***.***.26 - - [23/Nov/2019:ten:36:00 +0000] "Mail /skin/api/v1/receipt HTTP/one.1" 422 81 "-" "ringlet/7.67.0"
Repeat the request from the Postman:
NGINX logs:
==> /var/log/nginx/dev.poc.example.com-access.log <==
194.***.***.26 - - [23/Nov/2019:ten:07:37 +0000] "Post /pare/api/v1/receipt HTTP/1.one" 400 58 "-" "curl/seven.67.0"
194.***.***.26 - - [23/Nov/2019:10:09:57 +0000] "Become /peel/api/v1/receipt HTTP/one.ane" 404 18 "-" "roll/7.67.0"
194.***.***.26 - - [23/Nov/2019:10:11:44 +0000] "Get /skin/api/v1/receipt HTTP/i.one" 404 18 "http://dev.poc.example.com/skin/api/v1/receipt" "PostmanRuntime/7.19.0"
194.***.***.26 - - [23/Nov/2019:10:35:51 +0000] "POST /skin/api/v1/receipt HTTP/1.1" 422 81 "-" "curl/7.67.0"
194.***.***.26 - - [23/Nov/2019:10:36:00 +0000] "Mail service /pare/api/v1/receipt HTTP/1.one" 422 81 "-" "curl/7.67.0"
194.***.***.26 - - [23/Nov/2019:10:37:57 +0000] "POST /peel/api/v1/receipt HTTP/1.1" 422 81 "http://dev.poc.example.com/skin/api/v1/receipt" "PostmanRuntime/vii.xix.0"
All works, as expected.
AWS Awarding Load Balancer redirects
Unfortunately, I wasn't able to discover how to set up 307 when using AWS ALB:
That'south all, folks!
Useful links
Those problems on other sites
- Postal service Request redirects to GET in Nginx proxy and NodeJS
- Trouble while passing POST asking through proxy_pass
- How to proxy_pass Postal service with headers
- Nginx removes Content-Length header for chunked content
- Mail service Request with response 302 and content-length 0 crashes nginx
- Reverse Proxy POST problem
- HTTP POST redirect – no Content-Length
Helped to find the solution
- Post request does non follow 301/ 302 redirects
- Why doesn't HTTP take Post redirect?
Also published on Medium.
Source: https://rtfm.co.ua/en/http-redirects-post-and-get-requests-and-lost-data/
0 Response to "(97: Address Family Not Supported by Protocol) Nginx"
Post a Comment