115 lines
3.3 KiB
Python
Executable File
115 lines
3.3 KiB
Python
Executable File
#!/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 asyncio
|
|
import mimetypes
|
|
import os
|
|
import sys
|
|
from functools import partial
|
|
from urllib.parse import quote
|
|
from uuid import uuid4
|
|
|
|
from tornado import gen, httpclient
|
|
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()
|
|
mtype = mimetypes.guess_type(filename)[0] or "application/octet-stream"
|
|
buf = (
|
|
(b"--%s\r\n" % boundary_bytes)
|
|
+ (
|
|
b'Content-Disposition: form-data; name="%s"; filename="%s"\r\n'
|
|
% (filename_bytes, filename_bytes)
|
|
)
|
|
+ (b"Content-Type: %s\r\n" % mtype.encode())
|
|
+ b"\r\n"
|
|
)
|
|
yield write(buf)
|
|
with open(filename, "rb") as f:
|
|
while True:
|
|
# 16k at a time.
|
|
chunk = f.read(16 * 1024)
|
|
if not chunk:
|
|
break
|
|
yield write(chunk)
|
|
|
|
yield write(b"\r\n")
|
|
|
|
yield 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
|
|
|
|
yield 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)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
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
|
|
asyncio.run(method(filenames))
|