forked from tornadoweb/tornado
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs: Demonstrate uploading and receiving files
- Loading branch information
Showing
5 changed files
with
192 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
#!/usr/bin/env python | ||
|
||
"""Usage: python file_receiver.py | ||
Demonstrates a server that receives a multipart-form-encoded set of files in an | ||
HTTP POST, or streams in the raw data of a single file in an HTTP PUT. | ||
See file_uploader.py in this directory for code that uploads files in this format. | ||
""" | ||
|
||
import logging | ||
|
||
try: | ||
from urllib.parse import unquote | ||
except ImportError: | ||
# Python 2. | ||
from urllib import unquote | ||
|
||
import tornado.ioloop | ||
import tornado.web | ||
from tornado import options | ||
|
||
|
||
class POSTHandler(tornado.web.RequestHandler): | ||
def post(self): | ||
for field_name, files in self.request.files.items(): | ||
for info in files: | ||
filename, content_type = info['filename'], info['content_type'] | ||
body = info['body'] | ||
logging.info('POST "%s" "%s" %d bytes', | ||
filename, content_type, len(body)) | ||
|
||
self.write('OK') | ||
|
||
|
||
@tornado.web.stream_request_body | ||
class PUTHandler(tornado.web.RequestHandler): | ||
def initialize(self): | ||
self.bytes_read = 0 | ||
|
||
def data_received(self, chunk): | ||
self.bytes_read += len(chunk) | ||
|
||
def put(self, filename): | ||
filename = unquote(filename) | ||
mtype = self.request.headers.get('Content-Type') | ||
logging.info('PUT "%s" "%s" %d bytes', filename, mtype, self.bytes_read) | ||
self.write('OK') | ||
|
||
|
||
def make_app(): | ||
return tornado.web.Application([ | ||
(r"/post", POSTHandler), | ||
(r"/(.*)", PUTHandler), | ||
]) | ||
|
||
|
||
if __name__ == "__main__": | ||
# Tornado configures logging. | ||
options.parse_command_line() | ||
app = make_app() | ||
app.listen(8888) | ||
tornado.ioloop.IOLoop.current().start() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
#!/usr/bin/env python | ||
|
||
"""Usage: python file_uploader.py [--put] file1.txt file2.png ... | ||
Demonstrates uploading files to a server, without concurrency. It can either | ||
POST a multipart-form-encoded request containing one or more files, or PUT a | ||
single file without encoding. | ||
See also file_receiver.py in this directory, a server that receives uploads. | ||
""" | ||
|
||
import mimetypes | ||
import os | ||
import sys | ||
from functools import partial | ||
from uuid import uuid4 | ||
|
||
try: | ||
from urllib.parse import quote | ||
except ImportError: | ||
# Python 2. | ||
from urllib import quote | ||
|
||
from tornado import gen, httpclient, ioloop | ||
from tornado.options import define, options | ||
|
||
|
||
# Using HTTP POST, upload one or more files in a single multipart-form-encoded | ||
# request. | ||
@gen.coroutine | ||
def multipart_producer(boundary, filenames, write): | ||
boundary_bytes = boundary.encode() | ||
|
||
for filename in filenames: | ||
filename_bytes = filename.encode() | ||
write(b'--%s\r\n' % (boundary_bytes,)) | ||
write(b'Content-Disposition: form-data; name="%s"; filename="%s"\r\n' % | ||
(filename_bytes, filename_bytes)) | ||
|
||
mtype = mimetypes.guess_type(filename)[0] or 'application/octet-stream' | ||
write(b'Content-Type: %s\r\n' % (mtype.encode(),)) | ||
write(b'\r\n') | ||
with open(filename, 'rb') as f: | ||
while True: | ||
# 16k at a time. | ||
chunk = f.read(16 * 1024) | ||
if not chunk: | ||
break | ||
write(chunk) | ||
|
||
# Let the IOLoop process its event queue. | ||
yield gen.moment | ||
|
||
write(b'\r\n') | ||
yield gen.moment | ||
|
||
write(b'--%s--\r\n' % (boundary_bytes,)) | ||
|
||
|
||
# Using HTTP PUT, upload one raw file. This is preferred for large files since | ||
# the server can stream the data instead of buffering it entirely in memory. | ||
@gen.coroutine | ||
def post(filenames): | ||
client = httpclient.AsyncHTTPClient() | ||
boundary = uuid4().hex | ||
headers = {'Content-Type': 'multipart/form-data; boundary=%s' % boundary} | ||
producer = partial(multipart_producer, boundary, filenames) | ||
response = yield client.fetch('http://localhost:8888/post', | ||
method='POST', | ||
headers=headers, | ||
body_producer=producer) | ||
|
||
print(response) | ||
|
||
|
||
@gen.coroutine | ||
def raw_producer(filename, write): | ||
with open(filename, 'rb') as f: | ||
while True: | ||
# 16K at a time. | ||
chunk = f.read(16 * 1024) | ||
if not chunk: | ||
# Complete. | ||
break | ||
|
||
write(chunk) | ||
|
||
|
||
@gen.coroutine | ||
def put(filenames): | ||
client = httpclient.AsyncHTTPClient() | ||
for filename in filenames: | ||
mtype = mimetypes.guess_type(filename)[0] or 'application/octet-stream' | ||
headers = {'Content-Type': mtype} | ||
producer = partial(raw_producer, filename) | ||
url_path = quote(os.path.basename(filename)) | ||
response = yield client.fetch('http://localhost:8888/%s' % url_path, | ||
method='PUT', | ||
headers=headers, | ||
body_producer=producer) | ||
|
||
print(response) | ||
|
||
|
||
define("put", type=bool, help="Use PUT instead of POST", group="file uploader") | ||
|
||
# Tornado configures logging from command line opts and returns remaining args. | ||
filenames = options.parse_command_line() | ||
if not filenames: | ||
print("Provide a list of filenames to upload.", file=sys.stderr) | ||
sys.exit(1) | ||
|
||
method = put if options.put else post | ||
ioloop.IOLoop.current().run_sync(lambda: method(filenames)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters