Skip to content

Commit

Permalink
Added support for chunked encoded requests when running gunicorn. Fixed
Browse files Browse the repository at this point in the history
#340.

In this commit:

- when we see a Transfer-Encoding: chunked request, and the server is gunicorn,
  we set environ wsgi.input_terminated, which is required by Werkzeug in the
  absence of Content-Lenght, or it will empty the data stream.
- for chunked requests to non-gunicorn, return 501 Not Implemented.
  • Loading branch information
javabrett committed Nov 30, 2017
1 parent 260ee9b commit 9e9cb84
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 1 deletion.
26 changes: 25 additions & 1 deletion httpbin/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import uuid
import argparse

from flask import Flask, Response, request, render_template, redirect, jsonify as flask_jsonify, make_response, url_for
from flask import Flask, Response, request, render_template, redirect, jsonify as flask_jsonify, make_response, url_for, abort
from flask_common import Common
from six.moves import range as xrange
from werkzeug.datastructures import WWWAuthenticate, MultiDict
Expand Down Expand Up @@ -84,6 +84,30 @@ def jsonify(*args, **kwargs):
# -----------
# Middlewares
# -----------
"""
https://github.com/kennethreitz/httpbin/issues/340
Adds a middleware to provide chunked request encoding support running under
gunicorn only.
Werkzeug required environ 'wsgi.input_terminated' to be set otherwise it
empties the input request stream.
- gunicorn seems to support input_terminated but does not add the environ,
so we add it here.
- flask will hang and does not seem to properly terminate the request, so
we explicityly deny chunked requests.

This comment has been minimized.

Copy link
@graingert

graingert Dec 18, 2017

Contributor

explicitly

This comment has been minimized.

Copy link
@kennethreitz

kennethreitz Dec 18, 2017

Contributor

@graingert pr! :)

"""
@app.before_request
def before_request():
if request.environ.get('HTTP_TRANSFER_ENCODING', '').lower() == 'chunked':
server = request.environ.get('SERVER_SOFTWARE', '')
if server.lower().startswith('gunicorn/'):
if 'wsgi.input_terminated' in request.environ:
app.logger.warning("environ wsgi.input_terminated already set, keeping: %s"
% request.environ['wsgi.input_terminated'])
else:
request.environ['wsgi.input_terminated'] = 1
else:
abort(501, "Chunked requests are not supported for server %s" % server)

@app.after_request
def set_cors_headers(response):
response.headers['Access-Control-Allow-Origin'] = request.headers.get('Origin', '*')
Expand Down
19 changes: 19 additions & 0 deletions test_httpbin.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,25 @@ def test_post_file_with_missing_content_type_header(self):
)
self.assertEqual(response.status_code, 200)

"""
This is currently a sort of negative-test.
We validate that when running Flask-only server that
Transfer-Encoding: chunked requests are unsupported and
we return 501 Not Implemented
"""
def test_post_chunked(self):
data = '{"animal":"dog"}'
response = self.app.post(
'/post',
content_type='application/json',
headers=[('Transfer-Encoding', 'chunked')],
data=data,
)
self.assertEqual(response.status_code, 501)
#self.assertEqual(response.status_code, 200)
#self.assertEqual(json.loads(response.data.decode('utf-8'))['data'], '{"animal":"dog"}')
#self.assertEqual(json.loads(response.data.decode('utf-8'))['json'], {"animal": "dog"})

def test_set_cors_headers_after_request(self):
response = self.app.get('/get')
self.assertEqual(
Expand Down

0 comments on commit 9e9cb84

Please sign in to comment.