Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sporatic errors with nginx connection pipelining #344

Closed
nhumrich opened this issue Apr 4, 2019 · 67 comments
Closed

Sporatic errors with nginx connection pipelining #344

nhumrich opened this issue Apr 4, 2019 · 67 comments

Comments

@nhumrich
Copy link

nhumrich commented Apr 4, 2019

There seems to be some weird issue with pipelining and or connection reuse and nginx. I have nginx sitting in front of my uvicorn/starlette app, and on patch/post requests I occasionally get 502's from nginx (about 30-40% of the time). The nginx logs say:

upstream prematurely closed connection while reading response header from upstream

all over the place

and my app logs say:

WARNING: Invalid HTTP request received.
WARNING: Invalid HTTP request received.

with not much more information.

I forced connection reuse to not work by adding the default header Connection: close, which forces nginx to kill the connection. Performance drops significantly, but at least I don't get 502's.

uvicorn.run(app, host='0.0.0.0', port=8445, headers=[('Server', 'custom'), ('Connection', 'close')], proxy_headers=True)

potentially relevant: the 502 doesn't seem to ever happen on a GET

@tomchristie
Copy link
Member

Okay - so I'd start by trying each of the h11 and httptools implementations, and see if replicates in both cases. You could also dig into if you can replicate with hypercorn or daphne.

That'll give us a first pass onto what's going on.

Uvicorn will respond like that if it gets a malformed request. So another route into figuring out the issue would be to change the source code so that it logs out the raw byte streams that are being sent, and examine how they look in the broken cases.

@nhumrich
Copy link
Author

nhumrich commented Apr 6, 2019

Great idea. How do I swap between h11 and httptools?

@tomchristie
Copy link
Member

So. --http h11 or --http httptools.

Def. also recommend trying to replicate on Daphne or Hypercorn following that too.

Let's narrow it down as much as poss as a first step.

@nhumrich
Copy link
Author

nhumrich commented Apr 9, 2019

The issue only happens with httptools.
I could not reproduce with h11 on uvicorn.
I could not reproduce using hypercorn.
I wasn't able to get daphne working.

Hope that helps.

@leobuskin
Copy link

I can confirm this issue, can see such behavior from uvicorn==0.4.3 (didn't test earlier). Seems related to websockets, going to investigate deeper next week. @nhumrich are you using ws?

@nhumrich
Copy link
Author

@leobuskin no, I am not using websockets at all

@gvbgduh
Copy link
Member

gvbgduh commented May 13, 2019

Yeah, I see occasionally similar behaviour as well, quite rare though. It seems to be related to concurrent long running requests with some heavy CPU and memory usage in my case. Eg. csv files uploads (2-9 Mb) with background validation and writing to a db (yeah, via POST). So, if I make 4 requests at the same time the 3rd and/or 4th are likely to throw 502 back. Probably it might affect other requests, not sure.

I’ll try to reliably recreate this one and hunt it down, happy there’s an issue already.
Run it behind nginx locally was a missing peace of puzzle to catch it.

@gvbgduh
Copy link
Member

gvbgduh commented May 14, 2019

I was able to replicate it (sort of reliable), but under some load.

I've tried --worker-class uvicorn.workers.UvicornH11Worker and --worker-class uvicorn.workers.UvicornWorker and it appears in both cases.

On the surface, it seems that it happens for the consecutive request when the former one makes the worker timed out.
Not sure if it's a big surprise, but I'll try to dig deeper.

nginx_1  | 172.21.0.1 - - [14/May/2019:15:44:31 +0000] "POST /endpoint/bb960881-5f1f-4e5c-a38d-6d893445bea8 HTTP/1.1" 200 805 "-" "python-requests/2.21.0" "-"
nginx_1  | 2019/05/14 15:44:31 [warn] 8#8: *19 a client request body is buffered to a temporary file /var/cache/nginx/client_temp/0000000010, client: 172.21.0.1, server: , request: "POST /endpoint/bb960881-5f1f-4e5c-a38d-6d893445bea8 HTTP/1.1", host: "localhost:1337"
web_1    | [2019-05-14 15:44:31 +0000] [37] [DEBUG] ('172.21.0.4', 43134) - Connected
web_1    | [2019-05-14 15:44:46 +0000] [1] [CRITICAL] WORKER TIMEOUT (pid:37)
nginx_1  | 172.21.0.1 - - [14/May/2019:15:44:46 +0000] "POST /endpoint/bb960881-5f1f-4e5c-a38d-6d893445bea8 HTTP/1.1" 502 158 "-" "python-requests/2.21.0" "-"
nginx_1  | 2019/05/14 15:44:46 [error] 8#8: *19 upstream prematurely closed connection while reading response header from upstream, client: 172.21.0.1, server: , request: "POST /endpoint/bb960881-5f1f-4e5c-a38d-6d893445bea8", upstream: "/endpoint/bb960881-5f1f-4e5c-a38d-6d893445bea8", host: "localhost:1337"
nginx_1  | 2019/05/14 15:44:46 [warn] 8#8: *21 a client request body is buffered to a temporary file /var/cache/nginx/client_temp/0000000011, client: 172.21.0.1, server: , request: "POST /endpoint/bb960881-5f1f-4e5c-a38d-6d893445bea8 HTTP/1.1", host: "localhost:1337"
web_1    | [2019-05-14 15:44:46 +0000] [53] [INFO] Booting worker with pid: 53
web_1    | [2019-05-14 15:44:47 +0000] [53] [INFO] Started server process [53]
web_1    | [2019-05-14 15:44:47 +0000] [53] [INFO] Waiting for application startup.
web_1    | [2019-05-14 15:44:47 +0000] [53] [DEBUG] ('172.21.0.4', 43138) - Connected
web_1    | [2019-05-14 15:44:47 +0000] [53] [INFO] ('172.21.0.4', 43138) - "POST /endpoint/bb960881-5f1f-4e5c-a38d-6d893445bea8 HTTP/1.0" 200

@gvbgduh
Copy link
Member

gvbgduh commented May 15, 2019

All in all, I think there's no issue in uvicorn for me. Adjusting the timeout in uvicorn/gunicorn fixes it. If this timeout is greater than the nginx one it can hit 504 from it.

@nhumrich
Copy link
Author

for me, the behavior isn't timeout related. I didn't have any timing out requests, and it didn't require much load to reproduce

@gvbgduh
Copy link
Member

gvbgduh commented May 16, 2019

@nhumrich interesting, how would you reproduce it?
Are you running it via gunicorn or just plain uvicorn?

@nhumrich
Copy link
Author

@gvbgduh I tried it both ways. Both reproduce the error when I use httptools, but are fine when I use h11. Just a web server behind nginx. Basically, I have ALB -> nginx -> myservice. I haven't really spent the time to reproduce with a minimal example, but I imagine reproducing has moreso to do with nginx configuration then the actual code.

@matthax
Copy link

matthax commented Jul 30, 2019

Encountering the same issue with a similar configuration, however, I also noticed in the deployment instructions there is mention of a --proxy-headers flag that should be set, but it appears I can't do that while also using Gunicorn?

Are there other steps I can take to address this issue?


Additional details:
GET, DELETE and PUT work correctly. POST and PATCH fail (exactly) half the time.

Headers:

"x-request-id": "f70bbcc408f3a1347dbd6d377e97e13d",
"x-real-ip": "xxx.xxx.xxx",
"x-forwarded-for": "xxx.xxx.xxx",
"x-forwarded-host": "sample.domain",
"x-forwarded-port": "443",
"x-forwarded-proto": "https",
"x-original-uri": "/ping?test=true",
"x-scheme": "https",
"x-request-start": "t=1564505305.680",

Here is a link to the docker image we are using for the deployment as well.

Removed GUnicorn and tried running just uvicorn for testing and I'm still facing the same issue:

DEBUG: ('147.191.218.178', 0) - ASGI [20] Sent {'type': 'http.request', 'body': '<17 bytes>', 'more_body': False}
DEBUG: ('147.191.218.178', 0) - ASGI [20] Received {'type': 'http.response.start', 'status': 200, 'headers': '<...>'}
INFO: ('147.191.218.178', 0) - "POST /ping?test=true HTTP/1.1" 200
DEBUG: ('147.191.218.178', 0) - ASGI [20] Received {'type': 'http.response.body', 'body': '<552 bytes>'}
DEBUG: ('147.191.218.178', 0) - ASGI [20] Completed
WARNING: Invalid HTTP request received.
DEBUG: ('10.42.4.0', 53902) - Disconnected
DEBUG: ('xxx.xxx.xx.xxx', 36620) - Connected
DEBUG: ('xxx.xxx.xx.xxx', 36620) - ASGI [21] Started
DEBUG: ('xxx.xxx.xx.xxx', 36620) - ASGI [21] Received {'type': 'http.response.start', 'status': 200, 'headers': '<...>'}
INFO: ('xxx.xxx.xx.xxx', 36620) - "GET /about HTTP/1.1" 200
DEBUG: ('xxx.xxx.xx.xxx', 36620) - ASGI [21] Received {'type': 'http.response.body', 'body': '<80 bytes>'}
DEBUG: ('xxx.xxx.xx.xxx', 36620) - ASGI [21] Completed
DEBUG: ('xxx.xxx.xx.xxx', 36620) - Disconnected
DEBUG: ('xxx.xxx.xx.xxx', 36626) - Connected
DEBUG: ('xxx.xxx.xx.xxx', 36626) - ASGI [22] Started
DEBUG: ('xxx.xxx.xx.xxx', 36626) - ASGI [22] Received {'type': 'http.response.start', 'status': 200, 'headers': '<...>'}
INFO: ('xxx.xxx.xx.xxx', 36626) - "GET /about HTTP/1.1" 200
DEBUG: ('xxx.xxx.xx.xxx', 36626) - ASGI [22] Received {'type': 'http.response.body', 'body': '<80 bytes>'}
DEBUG: ('xxx.xxx.xx.xxx', 36626) - ASGI [22] Completed
DEBUG: ('xxx.xxx.xx.xxx', 36626) - Disconnected

@gvbgduh
Copy link
Member

gvbgduh commented Aug 1, 2019

@matthax I can't really see an occurrence of 502s in the log, only the line - WARNING: Invalid HTTP request received., makes me a bit curios how an invalid request is handled tho.
It was mentioned before, but have you tried different worker classes, different servers Daphne or Hypercorn?
My last hunt for this issue revealed misconfigured timeout for long running requests in gunicorn and then an issue in the proxying service in front of everything.
And then I wasn't able to replicate this issue.

@matthax
Copy link

matthax commented Aug 1, 2019

@gvbgduh 502s occur on NGINX's side, accompanied by a message about the worker prematurely terminating a connection.

Requests are not long running and there's no load on the server. We've increased the timeout to well over 30 seconds just to confirm. Quick glance at metrics puts the average response time under 5ms between nginx and the worker.

Removing gunicorn resulted in the same 502s, same result after removing https from the equation.

hypercorn is working flawlessly.

@gvbgduh
Copy link
Member

gvbgduh commented Aug 2, 2019

interesting @matthax, would it be possible to have a look at nginx logs and, ideally, at uvicorn ones for the same timeline?

@matthax
Copy link

matthax commented Aug 2, 2019 via email

@matthax
Copy link

matthax commented Aug 2, 2019

@gvbgduh
Here are the nginx logs associated with the error

xxx.xxx.xxx.xxx - [xxx.xxx.xxx.xxx] - - [02/Aug/2019:19:43:29 +0000] "POST /ping?test=true HTTP/1.1" 404 22 "-" "insomnia/6.5.4" 183 0.003 [notification-notification-80] 10.42.4.238:80 22 0.003 404 da977f29bc1798af801d3327b316cd0d
2019/08/02 19:43:30 [error] 4098#4098: *37449 upstream prematurely closed connection while reading response header from upstream, client: xxx.xxx.xxx.xxx, server: uvicorn.test, request: "POST /ping?test=true HTTP/1.1", upstream: "http://10.42.4.238:80/ping?test=true", host: "uvicorn.test"
xxx.xxx.xxx.xxx - [xxx.xxx.xxx.xxx] - - [02/Aug/2019:19:43:30 +0000] "POST /ping?test=true HTTP/1.1" 502 174 "-" "insomnia/6.5.4" 183 0.002 [notification-notification-80] 10.42.4.238:80 0 0.002 502 7e232c6f6575170a314b4465d6a891bd
xxx.xxx.xxx.xxx - [] - - [02/Aug/2019:19:43:32 +0000] "POST /ping?test=true HTTP/1.1" 404 22 "-" "insomnia/6.5.4" 183 0.002 [notification-notification-80] 10.42.4.238:80 22 0.003 404 4b1602b75aa902dde44b9e713c1a3389
2019/08/02 19:43:33 [error] 4098#4098: *37449 upstream prematurely closed connection while reading response header from upstream, client: xxx.xxx.xxx.xxx, server: uvicorn.test, request: "POST /ping?test=true HTTP/1.1", upstream: "http://10.42.4.238:80/ping?test=true", host: "uvicorn.test"
xxx.xxx.xxx.xxx - [xxx.xxx.xxx.xxx] - - [02/Aug/2019:19:43:33 +0000] "POST /ping?test=true HTTP/1.1" 502 174 "-" "insomnia/6.5.4" 183 0.001 [notification-notification-80] 10.42.4.238:80 0 0.001 502 745466b5781fd1309c61bb913e1e965c
xxx.xxx.xxx.xxx - [xxx.xxx.xxx.xxx] - - [02/Aug/2019:19:43:34 +0000] "POST /ping?test=true HTTP/1.1" 404 22 "-" "insomnia/6.5.4" 183 0.004 [notification-notification-80] 10.42.4.238:80 22 0.004 404 454920caa93418da601016be01a9a50c
2019/08/02 19:43:35 [error] 4098#4098: *37449 upstream prematurely closed connection while reading response header from upstream, client: xxx.xxx.xxx.xxx, server: uvicorn.test, request: "POST /ping?test=true HTTP/1.1", upstream: "http://10.42.4.238:80/ping?test=true", host: "uvicorn.test"
xxx.xxx.xxx.xxx - [xxx.xxx.xxx.xxx] - - [02/Aug/2019:19:43:35 +0000] "POST /ping?test=true HTTP/1.1" 502 174 "-" "insomnia/6.5.4" 183 0.001 [notification-notification-80] 10.42.4.238:80 0 0.002 502 707758941a0d5fa7d9137a627d4b00db
xxx.xxx.xxx.xxx - [xxx.xxx.xxx.xxx] - - [02/Aug/2019:19:43:36 +0000] "POST /ping?test=true HTTP/1.1" 404 22 "-" "insomnia/6.5.4" 183 0.003 [notification-notification-80] 10.42.4.238:80 22 0.003 404 6762d54b7c0a309a9875525a9cb24742
2019/08/02 19:43:39 [error] 4098#4098: *37449 upstream prematurely closed connection while reading response header from upstream, client: xxx.xxx.xxx.xxx, server: uvicorn.test, request: "POST /ping?test=true HTTP/1.1", upstream: "http://10.42.4.238:80/ping?test=true", host: "uvicorn.test"
xxx.xxx.xxx.xxx - [xxx.xxx.xxx.xxx] - - [02/Aug/2019:19:43:39 +0000] "POST /ping?test=true HTTP/1.1" 502 174 "-" "insomnia/6.5.4" 183 0.002 [notification-notification-80] 10.42.4.238:80 0 0.002 502 2b6c9a210804c1fcd771b5a21e8eae8f

Note that in this instance I wasn't hitting an actual endpoint, just posting to a route we disabled and even then we get a consistent 502 every other request.

@gbgduh
Copy link

gbgduh commented Aug 5, 2019

thanks a lot @matthax, i'll try to re-create this one, it gives a rough picture.

@MilovdZee
Copy link

MilovdZee commented Sep 9, 2019

I did some tcpdump'ing and found that uvicorn does not send the tcp-FIN packet to nginx and so nginx assumes the connection is still useable. nginx then sends the next http request to the open socket but uvicorn replies with a tcp-FIN packet and logs the invalid request error message. So uvicorn did not have an open connection??? Even though it did not send the FIN...

Workarounds:

  • Setting keep-alive to 0 fixes this while uvicorn then sends a tcp-FIN after each request.
    gunicorn -k uvicorn.workers.UvicornWorker -c /gunicorn_conf.py main:app --keep-alive=0
  • Also switching to the H11 worker fixes the issue because this also closes the connection after each request.
    gunicorn -k uvicorn.workers.UvicornH11Worker -c /gunicorn_conf.py main:app

But I prefer to have reuse of connection available and working. Still investigating how to get that working.

@ademoverflow
Copy link

Hi all ! Any update on this topic ?

@tomchristie
Copy link
Member

Can anyone reduce this down into the simplest possible way to reproduce this. I'm happy to have a dig into this myself, but it'd be super helpful if there's a trivial set of instructions for me to work through and know that it's likely to reliably reproduce the issue if I follow those.

Is it sufficient simply to setup nginx and uvicorn and issue some POST requests or is there more to it?

@matthax
Copy link

matthax commented Nov 22, 2019

I think you quite literally just need an NGINX instance set up with a reverse proxy to reproduce. Send POST/PUT/PATCH to get the error.

If you're not able to reproduce it like that let me know and I'll set you up a Dockerfile with a minimal example.

@gvbgduh
Copy link
Member

gvbgduh commented Nov 22, 2019

I think it would be great @matthax. I was trying to reproduce it for quite a while from time to time without luck. (except the case from the above).

I used a simple example of the whole stack (please see below details).

I tested it with plain postman and with wrk to give it some load

$ wrk -t10 -c100 -d10s -s ./wrk.post.lua http://127.0.0.1/api
Running 10s test @ http://127.0.0.1/api
  10 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    75.79ms   21.80ms 198.54ms   74.54%
    Req/Sec   132.32     37.30   260.00     69.40%
  13299 requests in 10.11s, 2.35MB read
Requests/sec:   1316.05
Transfer/sec:    237.71KB

app/app.py

from starlette.applications import Starlette
from starlette.endpoints import HTTPEndpoint
from starlette.responses import JSONResponse

class Endpoint(HTTPEndpoint):
    async def get(self, request):
        return JSONResponse({"success": True, "method": "GET"})

    async def post(self, request):
        return JSONResponse({"success": True, "method": "POST"})


app = Starlette()
app.debug = True
app.mount("/api", app=Endpoint)

docker-compose.py

version: '3.7'

services:
  web:
    stdin_open: true
    tty: true
    build:
      context: ./
      dockerfile: ./Dockerfile
    command: gunicorn --log-level debug --bind :8080 --reuse-port --workers 1 --worker-class uvicorn.workers.UvicornWorker app.app:app --timeout 600 --graceful-timeout 500 --capture-output
    expose:
      - 8080
    environment:
      - DEBUG=1
      - PORT=8080

  nginx:
    build:
      context: ./nginx
      dockerfile: ./Dockerfile
    ports:
      - 80:80
    depends_on:
      - web

Dockerfile

FROM gcr.io/google-appengine/python
RUN virtualenv /env -p python3.7
ENV VIRTUAL_ENV /env
ENV PATH /env/bin:$PATH
ADD requirements.txt /requirements.txt
RUN pip install -r /requirements.txt
ADD . /app
EXPOSE 8080

nginx/Dockerfile

FROM nginx:1.15.12-alpine

RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d

nginx/nginx.conf

upstream ds-mgmt-local {
    server web:8080;
}

server {

    listen 80;

    location / {
        proxy_pass http://local-test;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_redirect off;

    }

    client_max_body_size 200M;

    proxy_connect_timeout       600;
    proxy_send_timeout          600;
    proxy_read_timeout          600;
    send_timeout                600;
}

wrk.post.lua

wrk.method = "POST"
wrk.body   = '{"firstKey": "somedata", "secondKey": "somedata"}'
wrk.headers["Content-Type"] = "application/json"
wrk.headers["Accept"] = "*/*"
wrk.headers["Accept-Encoding"] = "gzip, deflate"

requirements.txt

Click==7.0
gunicorn==20.0.0
h11==0.8.1
httptools==0.0.13
starlette==0.13.0
uvicorn==0.10.8
uvloop==0.14.0
websockets==8.1

@gvbgduh
Copy link
Member

gvbgduh commented Nov 22, 2019

I have a sneaky suspicion it might be related to how errors are handled and propagated, so it crashes the uvicorn worker while nginx is still trying to communicate.
But without a solid example, it's tough to be sure and say what can be done.

@tomchristie
Copy link
Member

I am not able to reproduce this at the moment.

The application

example.py:

async def app(scope, receive, send):
    assert scope['type'] == 'http'
    await send({
        'type': 'http.response.start',
        'status': 200,
        'headers': [
            [b'content-type', b'text/plain'],
        ]
    })
    await send({
        'type': 'http.response.body',
        'body': b'Hello, world!',
    })

Running with uvicorn...

$ uvicorn example:app --uds /tmp/uvicorn.sock

The proxy

An nginx configuration, in ~/nginx.conf...

http {
  server {
    listen 4000;
    client_max_body_size 4G;

    server_name example.com;

    location / {
      proxy_set_header Host $http_host;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;
      proxy_redirect off;
      proxy_buffering off;
      proxy_pass http://uvicorn;
    }

    location /static {
      # path for static files
      root /path/to/app/static;
    }
  }

  upstream uvicorn {
    server unix:/tmp/uvicorn.sock;
  }

}

events {
}

Running nginx...

$ nginx -c ~/nginx.conf

The client requests...

wrk.post.lua:

wrk.method = "POST"
wrk.body   = '{"firstKey": "somedata", "secondKey": "somedata"}'
wrk.headers["Content-Type"] = "application/json"
wrk.headers["Accept"] = "*/*"
wrk.headers["Accept-Encoding"] = "gzip, deflate"

Making the requests:

$ wrk -t10 -c100 -d10s -s ./wrk.post.lua http://127.0.0.1:4000/

Anyone able to provide a configuration setup that does replicate the issue?

I'd prefer example cases not to include docker, but just be strictly narrowed down to:

  • An application, and the uvicorn command line.
  • An nginx config.
  • A client script using wrk, requests or curl.

@florimondmanca florimondmanca mentioned this issue Dec 12, 2020
1 task
@florimondmanca
Copy link
Member

Hey all! Uvicorn 0.13.2 has been released. Feel free to try it out and report any enhanced tracebacks you can see from these errors. :-) Thank you!

@euri10
Copy link
Member

euri10 commented Dec 27, 2020

Hey all! Uvicorn 0.13.2 has been released. Feel free to try it out and report any enhanced tracebacks you can see from these errors. :-) Thank you!

I was looking at old PR tonight @florimondmanca...
it seems like #205 is relevant here, we dont answer a 400 in case of an invalid request like gunicorn or hypercorn and apparently at the reverse-proxy level it turns into a 502, might be worth resurrecting that PR ?

@euri10 euri10 added the uds label Dec 28, 2020
@euri10
Copy link
Member

euri10 commented Dec 28, 2020

If people impacted could look at their logs (if running 0.13.2 🍏 ) and tell us if they see something about a potentially malformed request that would help


note to self, reproduction steps, hopefully what we see here

  1. run nginx with: docker run --rm --name nginx_uvicorn -p 80:80 -v /tmp/uvicorn.sock:/tmp/uvicorn.sock -v $PWD/nginx.conf:/etc/nginx/nginx.conf:ro nginx with quite a minimal nginx.conf
events {
}

http {
  server {
    listen 80;
    location / {
      proxy_pass http://uvicorn;
    }
  }
  upstream uvicorn {
    server unix:/tmp/uvicorn.sock fail_timeout=0;
  }
}

  1. run uvicorn : uvicorn app:app --uds /tmp/uvicorn.sock

  2. send incorrect request with curl -i 'http://localhost/?x=y z'


client result:

- ❯ curl -i 'http://localhost/?x=y z'
HTTP/1.1 502 Bad Gateway
Server: nginx/1.19.1
Date: Mon, 28 Dec 2020 19:27:46 GMT
Content-Type: text/html
Content-Length: 157
Connection: keep-alive

<html>
<head><title>502 Bad Gateway</title></head>
<body>
<center><h1>502 Bad Gateway</h1></center>
<hr><center>nginx/1.19.1</center>
</body>
</html>

nginx result:

2020/12/28 19:27:46 [error] 29#29: *4 upstream prematurely closed connection while reading response header from upstream, client: 172.17.0.1, server: , request: "GET /?x=y z HTTP/1.1", upstream: "http://unix:/tmp/uvicorn.sock:/?x=y z", host: "localhost"
172.17.0.1 - - [28/Dec/2020:19:27:46 +0000] "GET /?x=y z HTTP/1.1" 502 157 "-" "curl/7.64.0"

uvicorn result:

WARNING:  Invalid HTTP request received.
Traceback (most recent call last):
  File "/home/lotso/PycharmProjects/uvicorn/uvicorn/protocols/http/httptools_impl.py", line 165, in data_received
    self.parser.feed_data(data)
  File "httptools/parser/parser.pyx", line 193, in httptools.parser.parser.HttpParser.feed_data
httptools.parser.errors.HttpParserError: invalid constant string

@saulbein
Copy link

saulbein commented Dec 29, 2020

Hi, after encountering the same issue with a web application hosted on docker in AWS (no nginx, just gunicorn and a load balancer, it is not public). I've updated to the latest version, and now I see the actual error is that the URL is invalid, and is truncated so that the error only printed the last part of the URL - around 900 chars, the full URL is longer than that (also I am aware that these things should be send via body).

For example, the full URL should be like this:

/send_email/<long list of encoded emails>/54ae40a9-ec04-401f-ab2e-f903518aaa3f/2020-12-28/

But the error has only the last part of the URL:

Traceback (most recent call last):
File "httptools/parser/parser.pyx", line 245, in httptools.parser.parser.cb_on_url
File "/usr/local/lib/python3.8/site-packages/uvicorn/protocols/http/httptools_impl.py", line 214, in on_url
    parsed_url = httptools.parse_url(url)
File "httptools/parser/parser.pyx", line 468, in httptools.parser.parser.parse_url
    httptools.parser.errors.HttpParserInvalidURLError: invalid url b'gmail.com%2C<other emails>/54ae40a9-ec04-401f-ab2e-f903518aaa3f/2020-12-28/'

During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/local/lib/python3.8/site-packages/uvicorn/protocols/http/httptools_impl.py", line 165, in data_received
    self.parser.feed_data(data)
File "httptools/parser/parser.pyx", line 193, in httptools.parser.parser.HttpParser.feed_data
    httptools.parser.errors.HttpParserCallbackError: the on_url callback failed

Even if the browser would send a request like this (and unless some strange interaction with URL max length happens here, it doesn't, since this error happens on scheduled API calls) I would expect either a HTTP 404 or 414 response code, but instead the server just doesn't respond at all.

I would guess that #778 would solve my issue, but I have too many other tasks to have the time to test that.

@florimondmanca
Copy link
Member

florimondmanca commented Dec 29, 2020

@saulbein Thanks. Seems to be consistent with observations from @malthejorgensen and the fact that #778 solves it for them. :)

I guess we can look at moving forward on the "very long URL gets processed in pieces" path. We could use a unit test, and that might need to monkeypatch socket reads to simulate a fragmented packet, but that's not clear yet.

@euri10
Copy link
Member

euri10 commented Dec 29, 2020

see MagicStack/httptools#43

@malthejorgensen
Copy link

malthejorgensen commented Dec 29, 2020

I can say that for my case the issue is not URLs longer than 65,535 characters, but rather that on_url is being called multiple times for whatever reason: https://github.com/nodejs/http-parser#parsing-urls.

@saulbein
Copy link

saulbein commented Feb 8, 2021

After shortening URLs I have not encountered this issue again, so I can confirm that at the very least my issue was URL-length related (not hitting any limits, but seems likely that on_url gets called multiple times).

@vogre
Copy link

vogre commented Feb 9, 2021

Found this issue after encountering the error with long URLs as well.

@alexschoeberl
Copy link

I received this similar error httptools.parser.errors.HttpParserInvalidURLError: invalid URL from uvicorn because NGINX made requests to upstream without a leading / (something like GET users HTTP/1.0). With Flask those requests worked, so I never noticed.

@jacksmith15
Copy link

jacksmith15 commented Jun 15, 2021

Adding a note that I've observed similar behaviour using Traefik instead of nginx. I see a mix of 502s and 200s on requests with very long request lines. Making the exact same request sequentially results in both status codes.

@nakulkorde
Copy link

nakulkorde commented Sep 10, 2021

After shortening URLs I have not encountered this issue again, so I can confirm that at the very least my issue was URL-length related (not hitting any limits, but seems likely that on_url gets called multiple times).

@saulbein What did you use to shorten the url?

@jo47011
Copy link

jo47011 commented Nov 12, 2021

We have the same issue: raising @nakulkorde question again:
@saulbein What did you use to shorten the url? Thanks.

@vogre
Copy link

vogre commented Feb 14, 2022

Looks like #1262 is an example for a reproduction.

@euri10
Copy link
Member

euri10 commented Feb 16, 2022

may be fixed by #1263
leaving it open for now but it would help if people can connfirm

@saulbein
Copy link

After shortening URLs I have not encountered this issue again, so I can confirm that at the very least my issue was URL-length related (not hitting any limits, but seems likely that on_url gets called multiple times).

@saulbein What did you use to shorten the url?

I simply put the URL parameter in the body of the request, nothing fancy.

@jmdeschenes
Copy link

jmdeschenes commented May 31, 2022

Hello, we have this issue, in our case, we have noticed that some clients were sending out invalid urls(including emojis). It seemed in our case that they were probing for a vulnerability.

As a reproducible example. I have confirmed that this has been fixed.

curl http://localhost:8000/👽
# We received:
# Invalid HTTP request received.% 

@paopjian
Copy link

paopjian commented Jun 2, 2022

Hello, we have this issue, in our case, we have noticed that some clients were sending out invalid urls(including emojis). It seemed in our case that they were probing for a vulnerability.

As a reproducible example. I have confirmed that this has been fixed.

curl http://localhost:8000/👽
# We received:
# Invalid HTTP request received.% 

We are using 0.17.6. This command also return error. Did you change some code to fix the problem?

[2022-06-02 11:06:12 +0800] [10632]: [WARNING] Invalid HTTP request received. | b'GET /\xf0\x9f\x91\xbd HTTP/1.1\r\nUser-Agent: curl/7.29.0\r\nHost: localhost:8002\r\nAccept: */*\r\n\r\n'
Traceback (most recent call last):
  File "httptools/parser/parser.pyx", line 309, in httptools.parser.parser.cb_on_headers_complete
  File "httptools/parser/parser.pyx", line 129, in httptools.parser.parser.HttpParser._on_headers_complete
  File "/ocr_with_fastapi-main/venv/lib/python3.9/site-packages/uvicorn/protocols/http/httptools_impl.py", line 218, in on_headers_complete
    parsed_url = httptools.parse_url(self.url)
  File "httptools/parser/url_parser.pyx", line 105, in httptools.parser.url_parser.parse_url
httptools.parser.errors.HttpParserInvalidURLError: invalid url b'/\xf0\x9f\x91\xbd'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/ocr_with_fastapi-main/venv/lib/python3.9/site-packages/uvicorn/protocols/http/httptools_impl.py", line 124, in data_received
    self.parser.feed_data(data)
  File "httptools/parser/parser.pyx", line 212, in httptools.parser.parser.HttpParser.feed_data
httptools.parser.errors.HttpParserCallbackError: User callback error

@jmdeschenes
Copy link

Hi,

It is only a warning, the error is caught and handled(returns a 400 error)

This is the line:

self.logger.warning(msg, exc_info=exc)

Previously, this type of error was not handled.

@euri10
Copy link
Member

euri10 commented Jun 14, 2022

ok I'm closing this long-standing issue, we think #1263 fixed it, and since then we received no complains so :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests