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

Send HTTP 400 response for invalid request #205

Conversation

markbreedlove
Copy link

Given an invalid request, respond with an HTTP 400 error instead of closing the connection without a response.

Before this change, a request with invalid characters would result in the HTTP connection being dropped, as in this example:

$ curl -i -X GET 'http://localhost:8000/example?coordinates=76.916664, 8.483333'
curl: (52) Empty reply from server

Such a request should result in an HTTP 400 (Bad Request).

With a reverse-proxied application, this would typically be presented to the user as a 502 (Bad Gateway) error.

markbreedlove pushed a commit to dpla-attic/dplaapi that referenced this pull request Sep 28, 2018
Temporarily pull uvicorn from the forked repository
https://github.com/markbreedlove/uvicorn.git, which contains a bugfix
for bad requests with invalid URLs.

See encode/uvicorn#205
... This change can be reverted when that PR is merged and a new release
of uvicorn comes out with the fix.
@tomchristie
Copy link
Member

Great work, thanks!

Before we decide on behaviour here I’d like to have a better idea of existing implementation behaviours.

Any of the following would be really useful to know:

  • How do gunicorn or node deal with that request, and does it make it to the application or not?
  • How does nginx deal with a request like that?

@markbreedlove
Copy link
Author

Hi, Tom,

I've been running uvicorn with gunicorn as follows.

  • In development: gunicorn -k uvicorn.workers.UvicornWorker --log-level=debug --reload dplaapi:app
  • In production: gunicorn -w 2 -b 0.0.0.0:8000 -k uvicorn.workers.UvicornWorker dplaapi:app
    ... both exhibiting the same behavior from that curl request.

I'm running in AWS Elastic Beanstalk in production. The application is Dockerized and reverse-proxied with nginx.

Here's a sample line from nginx's access.log:

172.30.0.141 - - [28/Sep/2018:12:21:00 +0000] "GET /v2/items?api_key=[redacted]&sourceResource.spatial.coordinates=76.916664, 8.483333 HTTP/1.1" 502 173 "-" "-"

Here's the corresponding error.log line:

2018/09/28 12:21:00 [error] 22285#0: *15872 upstream prematurely closed connection while reading response header from upstream, client: 172.30.0.141, server: , request: "GET /v2/items?api_key=[redacted]&sourceResource.spatial.coordinates=76.916664, 8.483333 HTTP/1.1", upstream: "http://172.17.0.2:8000/v2/items?api_key=[redacted]&sourceResource.spatial.coordinates=76.916664, 8.483333", host: "api.dp.la"

Here's what gunicorn was printing on stdout; having it bubble up from uvicorn:

[2018-09-28 12:21:00 +0000] [10] [WARNING] Invalid HTTP request received.

The request did not make it past gunicorn and did not get down to my application, so to speak. As far as I can tell, gunicorn (or uvicorn?) never got to the point of making a call on my Api Star ASyncApp object. I'm not sure if that's the best way to describe it -- just let me know if that isn't clear!

@tomchristie
Copy link
Member

Sorry I wasn’t clear. I meant what response do we get running just regular gunicorn and a plain old hello world wsgi app. That’d be a really useful point of comparison. (Node’s behaviour would be interesting too)

@markbreedlove
Copy link
Author

Aha, of course, makes sense. Here's what I observe with gunicorn 19.9.0 and the "hello world" app at https://gunicorn.org.

(venv) uvicorn-pull205-test$ cat gunicornapp.py 
def app(environ, start_response):
    data = b"Hello, World!\n"
    start_response("200 OK", [
        ("Content-Type", "text/plain"),
        ("Content-Length", str(len(data)))
    ])
    return iter([data])
(venv) uvicorn-pull205-test$ 
(venv) uvicorn-pull205-test$ gunicorn -w 2 gunicornapp:app
[2018-10-02 17:11:52 -0400] [73491] [INFO] Starting gunicorn 19.9.0
[2018-10-02 17:11:52 -0400] [73491] [INFO] Listening at: http://127.0.0.1:8000 (73491)
[2018-10-02 17:11:52 -0400] [73491] [INFO] Using worker: sync
[2018-10-02 17:11:52 -0400] [73494] [INFO] Booting worker with pid: 73494
[2018-10-02 17:11:52 -0400] [73495] [INFO] Booting worker with pid: 73495
^C[2018-10-02 17:12:30 -0400] [73491] [INFO] Handling signal: int

... other terminal ...

~$ curl -i 'http://localhost:8000/?x=y z'
HTTP/1.1 400 Bad Request
Connection: close
Content-Type: text/html
Content-Length: 198

<html>
  <head>
    <title>Bad Request</title>
  </head>
  <body>
    <h1><p>Bad Request</p></h1>
    Invalid HTTP Version &#x27;Invalid HTTP Version: &#x27;z HTTP/1.1&#x27;&#x27;
  </body>
</html>

I'll check Node.js a bit later -- looks like tomorrow morning at this point ...

@markbreedlove
Copy link
Author

markbreedlove commented Oct 2, 2018

Actually, this is easy enough in Node and I can do it now rather than later. I'm using this Hello World from https://nodejs.org/en/about/ ...

uvicorn-pull205-test$ cat nodeapp.js 
const http = require('http');

const hostname = '127.0.0.1';
const port = 3000;

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello World\n');
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});
uvicorn-pull205-test$ 
uvicorn-pull205-test$ node nodeapp.js 
Server running at http://127.0.0.1:3000/
^C

... in another terminal ...

~$ curl -i 'http://localhost:3000/?x=y z'
curl: (52) Empty reply from server

So Node just closes the connection, whereas gunicorn responds with a 400.

I'm using Node v8.11.3.

Given an invalid request, respond with an HTTP 400 error instead of
closing the connection without a response.
@tomchristie
Copy link
Member

Okay, I like this, yup. Looks like there's a test failure for us to dig into.

@gvbgduh
Copy link
Member

gvbgduh commented Sep 19, 2019

This could possibly be related to #344

@Kludex
Copy link
Sponsor Member

Kludex commented Dec 22, 2021

I'm interested on this. How Hypercorn deals with it: https://gitlab.com/pgjones/hypercorn/-/blob/main/src/hypercorn/protocol/h11.py#L147-153

@Kludex
Copy link
Sponsor Member

Kludex commented Dec 22, 2021

@markbreedlove Sorry for taking so long. Would you mind rebasing it?

@euri10
Copy link
Member

euri10 commented Feb 11, 2022

closed by #1352

@euri10 euri10 closed this Feb 11, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants