Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: tornadoweb/tornado
...
head fork: nephics/tornado
compare: streambody
Checking mergeability… Don't worry, you can still create the pull request.
  • 19 commits
  • 4 files changed
  • 0 commit comments
  • 4 contributors
Commits on Jan 03, 2012
@jcbsnd jcbsnd Support for streaming request body handling 27813aa
Commits on Jan 05, 2012
@jcbsnd jcbsnd Merge branch 'master' of git://github.com/facebook/tornado into strea…
…mbody
05a6b41
Commits on Jan 24, 2012
@jcbsnd jcbsnd Ensure that request body is read before trying to match xsrf cookie 08871d4
@jcbsnd jcbsnd Merge branch 'master' of git://github.com/facebook/tornado into strea…
…mbody
68eb7f5
Commits on Jan 28, 2012
@jcbsnd jcbsnd Support for streaming request body handling cfb73ed
@jcbsnd jcbsnd Ensure that request body is read before trying to match xsrf cookie ac7b77e
@jcbsnd jcbsnd Merge branch 'streambody' of https://github.com/nephics/tornado into …
…streambody
5dfc231
Commits on Jan 31, 2012
@jcbsnd jcbsnd Merge commit '02bc76155' into streambody d53808f
Commits on May 03, 2012
@jcbsnd jcbsnd Update to v2.2.1 by merge of commit '235cb17a0ae1b23d4eee592f358bf847…
…07d89edc' into streambody
b9f5195
Commits on Jun 04, 2012
@jcbsnd jcbsnd Update to version 2.3 by merging commit '13598908a7872080c6cc20b0dcfb…
…cb0ce89a3447' into streambody
6df83f7
Commits on Sep 20, 2012
@jcbsnd jcbsnd Update to version 2.4
Merge commit '648bebf0eeb220ebfaa8200bab3bac41add9d650' into streambody

Conflicts:
	tornado/httpserver.py
581def2
Commits on Dec 19, 2012
@jcbsnd jcbsnd Update to the newest commit in the main Tornado repository
Merge branch 'master' of git://github.com/facebook/tornado into streambody

Fixed conflicts for:
	tornado/httpserver.py
	tornado/web.py
164a88d
@jcbsnd jcbsnd Added streambody example server and client code 47cde12
Commits on Jun 18, 2013
@jcbsnd jcbsnd Merge Tornado 3.1 into the streambody branch 78131fe
@jcbsnd jcbsnd make streambody work with both Python 2 and 3 d53239c
Commits on Sep 10, 2013
@JohnVinyard JohnVinyard Fix bug whereby asynchronous decorator is applied to put and post met…
…hods on each class instantiation
c3828c3
@jcbsnd jcbsnd Merge pull request #1 from JohnVinyard/streambody
Fix bug whereby asynchronous decorator is applied to put and post method...
1c3b866
@jcbsnd jcbsnd Merge Tornado v3.1.1 (commit 'f36652d47fc42205c085ed65e740f4b155d4e5e…
…4') into streambody
a1de18e
Commits on Jun 26, 2014
@jcbsnd jcbsnd Merge v3.2.2 (commit 6765549) into streambody 7c132ab
View
54 demos/streambody/client.py
@@ -0,0 +1,54 @@
+'''Demo code of the functionality in the Tornado "streambody" branch,
+providing support for streaming request body data in POST and PUT requests.
+
+The streambody branch is available at:
+https://github.com/nephics/tornado
+
+Run the demo by first starting the server and then the client.
+'''
+
+import os
+import sys
+# use the local version of tornado
+sys.path.insert(0, os.path.abspath('../..'))
+
+import hashlib
+import tornado.httpclient as httpclient
+
+
+def upload(path, body):
+ url = 'http://localhost:8888' + path
+ http_client = httpclient.HTTPClient()
+ request = httpclient.HTTPRequest(url, 'PUT', body=body,
+ headers={'Content-Type': 'text/plain'})
+ try:
+ response = http_client.fetch(request)
+ # the server's calculated md5 hash is in the second line of the response
+ msg, md5 = response.body.decode('utf8').split('\n')
+ print(msg)
+ return md5
+ except httpclient.HTTPError as e:
+ print("Error:", e)
+ sys.exit()
+
+def main():
+ # generate 350k of random data, and remeber the MD5 hash
+ body = os.urandom(350000)
+ md5_orig = hashlib.md5(body).hexdigest()
+
+ # upload using normal upload handler
+ md5 = upload('/', body)
+ if md5 != md5_orig:
+ print('!! Hash mismatch with default upload handler !!')
+ return
+
+ # upload using streambody handler
+ md5 = upload('/stream', body)
+ if md5 != md5_orig:
+ print('!! Hash mismatch with streambody handler !!')
+ return
+
+ print('Hashes of uploaded data match the original.')
+
+if __name__ == '__main__':
+ main()
View
67 demos/streambody/server.py
@@ -0,0 +1,67 @@
+'''Demo code of the functionality in the Tornado "streambody" branch,
+providing support for streaming request body data in POST and PUT requests.
+
+The streambody branch is available at:
+https://github.com/nephics/tornado
+
+Run the demo by first starting the server and then the client.
+'''
+
+import os.path
+import sys
+# use the local version of tornado
+sys.path.insert(0, os.path.abspath('../..'))
+
+import hashlib
+import logging
+
+logging.basicConfig(level=logging.DEBUG,
+ format='%(asctime)-6s: %(levelname)s - %(message)s')
+
+
+import tornado.ioloop
+import tornado.web
+
+
+class MainHandler(tornado.web.RequestHandler):
+ def put(self):
+ md5 = hashlib.md5(self.request.body)
+ self.write('Default body handler: received %d bytes\n%s'
+ % (len(self.request.body), md5.hexdigest()))
+
+
+@tornado.web.stream_body
+class StreamHandler(tornado.web.RequestHandler):
+
+ def put(self):
+ self.read_bytes = 0
+ self.request.request_continue()
+ self.read_chunks()
+ self.md5 = hashlib.md5()
+
+ def read_chunks(self, chunk=''):
+ self.read_bytes += len(chunk)
+ if chunk:
+ logging.info('Received {} bytes'.format(len(chunk)))
+ self.md5.update(chunk)
+ chunk_length = min(100000,
+ self.request.content_length - self.read_bytes)
+ if chunk_length > 0:
+ self.request.connection.stream.read_bytes(
+ chunk_length, self.read_chunks)
+ else:
+ self.uploaded()
+
+ def uploaded(self):
+ self.write('Stream body handler: received %d bytes\n%s'
+ % (self.read_bytes, self.md5.hexdigest()))
+ self.finish()
+
+
+if __name__ == "__main__":
+ application = tornado.web.Application([
+ (r"/", MainHandler),
+ (r"/stream", StreamHandler),
+ ])
+ application.listen(8888)
+ tornado.ioloop.IOLoop.instance().start()
View
39 tornado/httpserver.py
@@ -320,10 +320,7 @@ def _on_headers(self, data):
content_length = int(content_length)
if content_length > self.stream.max_buffer_size:
raise _BadRequestException("Content-Length too long")
- if headers.get("Expect") == "100-continue":
- self.stream.write(b"HTTP/1.1 100 (Continue)\r\n\r\n")
- self.stream.read_bytes(content_length, self._on_request_body)
- return
+ self._request.content_length = content_length
self.request_callback(self._request)
except _BadRequestException as e:
@@ -332,18 +329,6 @@ def _on_headers(self, data):
self.close()
return
- def _on_request_body(self, data):
- self._request.body = data
- if self._request.method in ("POST", "PATCH", "PUT"):
- httputil.parse_body_arguments(
- self._request.headers.get("Content-Type", ""), data,
- self._request.body_arguments, self._request.files)
-
- for k, v in self._request.body_arguments.items():
- self._request.arguments.setdefault(k, []).extend(v)
- self.request_callback(self._request)
-
-
class HTTPRequest(object):
"""A single HTTP request.
@@ -478,6 +463,28 @@ def __init__(self, method, uri, version="HTTP/1.0", headers=None,
self.query_arguments = copy.deepcopy(self.arguments)
self.body_arguments = {}
+ def request_continue(self):
+ '''Send a 100-Continue, telling the client to send the request body'''
+ if self.headers.get("Expect") == "100-continue":
+ self.connection.stream.write(b"HTTP/1.1 100 (Continue)\r\n\r\n")
+
+ def _read_body(self, exec_req_cb):
+ self.request_continue()
+ self.connection.stream.read_bytes(self.content_length,
+ lambda data: self._on_request_body(data, exec_req_cb))
+
+ def _on_request_body(self, data, exec_req_cb):
+ self.body = data
+ if self.method in ("POST", "PATCH", "PUT"):
+ httputil.parse_body_arguments(
+ self.headers.get("Content-Type", ""), data,
+ self.body_arguments, self.files)
+
+ for k, v in self.body_arguments.items():
+ self.arguments.setdefault(k, []).extend(v)
+
+ exec_req_cb()
+
def supports_http_1_1(self):
"""Returns True if this request supports HTTP/1.1 semantics"""
return self.version == "HTTP/1.1"
View
63 tornado/web.py
@@ -1328,6 +1328,18 @@ def _execute(self, transforms, *args, **kwargs):
try:
if self.request.method not in self.SUPPORTED_METHODS:
raise HTTPError(405)
+ # read and parse the request body, if not disabled
+ exec_req_cb = lambda: self._execute_request(*args, **kwargs)
+ if (getattr(self, '_read_body', True) and
+ hasattr(self.request, 'content_length')):
+ self.request._read_body(exec_req_cb)
+ else:
+ exec_req_cb()
+ except Exception as e:
+ self._handle_request_exception(e)
+
+ def _execute_request(self, *args, **kwargs):
+ try:
self.path_args = [self.decode_argument(arg) for arg in args]
self.path_kwargs = dict((k, self.decode_argument(v, name=k))
for (k, v) in kwargs.items())
@@ -1457,6 +1469,57 @@ def _clear_headers_for_304(self):
self.clear_header(h)
+def stream_body(cls):
+ """Wrap RequestHandler classes with this to prevent the
+ request body of PUT and POST request from being read and parsed
+ automatically.
+
+ If this decorator is given, the request body is not read and parsed
+ when a PUT or POST handler method is executed. It is up to the request
+ handler to read the body from the stream in the HTTP connection.
+
+ Using this decorator automatically implies the asynchronous decorator.
+
+ Without this decorator, the request body is automatically read and
+ parsed before a PUT or POST method is executed. ::
+
+ @web.stream_body
+ class StreamHandler(web.RequestHandler):
+
+ def put(self):
+ self.read_bytes = 0
+ self.request.request_continue()
+ self.read_chunks()
+
+ def read_chunks(self, chunk=''):
+ self.read_bytes += len(chunk)
+ chunk_length = min(10000,
+ self.request.content_length - self.read_bytes)
+ if chunk_length > 0:
+ self.request.connection.stream.read_bytes(
+ chunk_length, self.read_chunks)
+ else:
+ self.uploaded()
+
+ def uploaded(self):
+ self.write('Uploaded %d bytes' % self.read_bytes)
+ self.finish()
+
+ """
+ if hasattr(cls, 'post'):
+ cls.post = asynchronous(cls.post)
+ if hasattr(cls, 'put'):
+ cls.put = asynchronous(cls.put)
+
+ class StreamBody(cls):
+ def __init__(self, *args, **kwargs):
+ if args[0]._wsgi:
+ raise Exception("@stream_body is not supported for WSGI apps")
+ self._read_body = False
+ cls.__init__(self, *args, **kwargs)
+ return StreamBody
+
+
def asynchronous(method):
"""Wrap request handler methods with this if they are asynchronous.

No commit comments for this range

Something went wrong with that request. Please try again.