Receiving files over HTTP with Python

It's often very useful to be able to quickly send files over a network. One quick way to do this is to stand up a temporary HTTP server to send files. This can be done with Python's http.server module:

$ python -m http.server
Serving HTTP on 0.0.0.0 port 8000 ...
127.0.0.1 - - [01/Nov/2016 22:44:19] "GET / HTTP/1.1" 200 -

Note: In earlier versions of Python (2.x), the module is called SimpleHTTPServer.

Sending files the other way

In the example above files are pulled via a GET request. Unfortunately Python's server implementation does not respond to PUT requests:

$ curl -X PUT --upload-file text.txt http://localhost:8000
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
        "http://www.w3.org/TR/html4/strict.dtd">
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
        <title>Error response</title>
    </head>
    <body>
        <h1>Error response</h1>
        <p>Error code: 501</p>
        <p>Message: Unsupported method ('PUT').</p>
        <p>Error code explanation: HTTPStatus.NOT_IMPLEMENTED - Server does not support this operation.</p>
    </body>
</html>

It is however fairly easy to extend the server implementation to add the missing method. The script below implements the do_PUT method:

#!/usr/bin/env python

"""Extend Python's built in HTTP server to save files

curl or wget can be used to send files with options similar to the following

  curl -X PUT --upload-file somefile.txt http://localhost:8000
  wget -O- --method=PUT --body-file=somefile.txt http://localhost:8000/somefile.txt

__Note__: curl automatically appends the filename onto the end of the URL so
the path can be omitted.

"""
import os
try:
    import http.server as server
except ImportError:
    # Handle Python 2.x
    import SimpleHTTPServer as server

class HTTPRequestHandler(server.SimpleHTTPRequestHandler):
    """Extend SimpleHTTPRequestHandler to handle PUT requests"""
    def do_PUT(self):
        """Save a file following a HTTP PUT request"""
        filename = os.path.basename(self.path)

        # Don't overwrite files
        if os.path.exists(filename):
            self.send_response(409, 'Conflict')
            self.end_headers()
            reply_body = '"%s" already exists\n' % filename
            self.wfile.write(reply_body.encode('utf-8'))
            return

        file_length = int(self.headers['Content-Length'])
        with open(filename, 'wb') as output_file:
            output_file.write(self.rfile.read(file_length))
        self.send_response(201, 'Created')
        self.end_headers()
        reply_body = 'Saved "%s"\n' % filename
        self.wfile.write(reply_body.encode('utf-8'))

if __name__ == '__main__':
    server.test(HandlerClass=HTTPRequestHandler)

Running the script

The script above should work out of the box with either Python 2 or Python 3. Once it's downloaded cd to the directory you want to upload files to and run it:

$ mkdir upload
$ cd upload/
$ python /tmp/http_put_server.py
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

Files can then be uploaded to the directory using curl or wget:

$ curl -X PUT --upload-file test.txt http://localhost:8000
Saved "test.txt"

A quick note on security

The script above will blindly accept files without authentication, and no encryption is used in transit. For transferring files quickly across a private network this should be OK. However exposing the web server over the Internet is almost certainly a terrible idea!