Repository: benoitc/gunicorn

Issue:
Support wsgi.input_terminated flag
Gunicorn supports chunked transfer encoding, but unfortunately the WSGI spec has no support for it. A standard was proposed by @mitsuhiko here: https://gist.github.com/mitsuhiko/5721547. If the WSGI server supports streams without a content length, it should set `environ['wsgi.input_terminated'] = True`. Other servers and clients are starting to support this.


Discussion:
as i told him, it will be part of the next release. somtimes this week. thanks for the head up anyway 🙂
Sorry, I did a quick search of the code first but it didn't come up. I know you were already aware of it though. 🙂
Thanks for opening the issue. It's good to track it.
To be clear, this flag is not just for chunked encoding. Any time Gunicorn terminates input, it should set this. For example, if it already limits the stream to `Content-Length` when present, there's no need for the application to do that again.
Good discussion in #1659 about maximum content length.

> Any time Gunicorn terminates input, it should set this. For example, if it already limits the stream to Content-Length when present, there's no need for the application to do that again.

Gunicorn does not currently limit the input stream. If Gunicorn supports `wsgi.input_terminated`, support for a content length limit should be added, as well.
> support for a content length limit should be added, as well.

what do you mean ? I'm not sure to follow. If a content length is given , we actually makes sure we only read until it and no more: https://github.com/benoitc/gunicorn/blob/master/gunicorn/http/body.py#L107

DO you mean we shoudl let the application know with this change?
Since you already do that, you can set this flag then as well to signal that the application doesn't need to.
I think I did not read closely enough and missed this: https://github.com/benoitc/gunicorn/blob/master/gunicorn/http/body.py#L116
No, now I am doubting myself :)

What I mean is that Gunicorn could support `wsgi.input_terminated` even for streaming, chunked responses by having a configurable body length limit. Right now it is only possible for bodies with known length.
> as i told him, it will be part of the next release. somtimes this week. thanks for the head up anyway

Any update on this? Looks like it didn't make its way into 19.8.0.
> What I mean is that Gunicorn could support wsgi.input_terminated even for streaming, chunked responses by having a configurable body length limit. Right now it is only possible for bodies with known length. 

well that's quite normal if you follow the HTTP 1.1 spec where one has precedence on other.

@Dundee i planned to send a PR sometimes this week even if I would have preferred a real update of the spec not requiring such a hack.
i've bee side tracked but the plan is the following:

- [ ] add support for  `wsgi.input_terminated`  
- [ ] make sure old API is still working: we don't want to break 10 years of a working API 
Probably postpone past R20?
@tilgovi i may have a working version by tomorrow, it's not really different from the way we handle websockets or stream chunk encoded requests, just more hackish. if not yes, let's not pass the date.
@tilgovi @mitsuhiko the part i'm not sure about this extension to the PEP 3333 is what we should do when we get a"Content-Length" header. Right now gunicorn reads it on the fly and fill the `wsgi.input` with ethe content it receives until the the length is achieved. Then return an empty byte which correspond to the spec:

```
The server is not required to read past the client's specified Content-Length, and should simulate an end-of-file condition if the application attempts to read past that point. The application should not attempt to read more data than is specified by the CONTENT_LENGTH variable.

A server should allow read() to be called without an argument, and return the remainder of the client's input stream.

A server should return empty bytestrings from any attempt to read from an empty or exhausted input stream.
```

But the [gist](
https://gist.github.com/mitsuhiko/5721547)  is not clear about it. It seems to imply that the application could expect a different behaviour if `wsgi.input_terminated=false` . At least this part is not clear at all. 

In my understanding in the context of gunicorn, I would do the following:

* set `wsgi.input_terminated=true` for chunked encoding requests or requests without`Content-Length` header
* set `wsgi.input_terminated=false` for requests with a `Content-Length` header.

Is this what you had in mind @mitsuhiko ? If not how do the server to know that the application stop to read the request though?

Also is that extension supposed to bring support for websockets?
I think your reading of it is correct. Werkzeug understands basic `Content-Length` termination, so you can either not set the flag or set it to false in that case.

I don't think the extension is supposed to handle websockets.
@benoitc it's entirely up to the server what it sets. For all intents and purposes the only thing this flag solves is what happens if the content length header is absent or wrong (for instance because the server or a middleware modifies the input stream). So your proposal in the spirit of how this should work.

Also no, this is not about websockets. It's to support chunking on the way in and transcoding input data.
@mitsuhiko @davidism thanks for the feedback :) 

So I will implement it as I described. It should normally be part of this week release.
@mitsuhiko is there any simple way to test it with flask?

Resolving patch:
diff --git a/gunicorn/http/wsgi.py b/gunicorn/http/wsgi.py
--- a/gunicorn/http/wsgi.py
+++ b/gunicorn/http/wsgi.py
@@ -73,6 +73,7 @@ def base_environ(cfg):
         "wsgi.multiprocess": (cfg.workers > 1),
         "wsgi.run_once": False,
         "wsgi.file_wrapper": FileWrapper,
+        "wsgi.input_terminated": True,
         "SERVER_SOFTWARE": SERVER_SOFTWARE,
     }
 
@@ -130,6 +131,7 @@ def create(req, sock, client, server, cfg):
             continue
         elif hdr_name == "CONTENT-LENGTH":
             environ['CONTENT_LENGTH'] = hdr_value
+            environ['wsgi.input_terminated'] = False
             continue
 
         key = 'HTTP_' + hdr_name.replace('-', '_')


Test patch: