Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

CherryPy 2.1 or better

The recipes on this page apply to CP 2.0. If you want to do file uploads in CP 2.1 or better, see cherrypy/tutorial/tut09_files.py.

Simple case

Here is a simple example that shows how file uploads are handled by CherryPy

from cherrypy import cpg
class Root:
    def index(self):
        return """
        <html><body>
            <form action="upload" method="post" enctype="multipart/form-data">
            filename: <input type="file" name="myFile"/><br/>
            <input type="submit"/>
            </form>
        </body></html>
        """
    index.exposed = True

    def upload(self, myFile):
        return """
        <html><body>
            myFile length: %s<br/>
            myFile filename: %s<br/>
            myFile mime-type: %s
        </body></html>
        """ % (
            len(myFile),
            cpg.request.filenameMap['myFile'],
            cpg.request.fileTypeMap['myFile']
        )
    upload.exposed = True

cpg.root = Root()
cpg.server.start()

As you can see, the file is being passed to the method as a regular string, and you have access to the filename and mime-type as well.

Streaming the uploaded file to disk (Updated)

If you are planning to deal with large files, then it might not be a good idea to store the file in a python string. In that case, you can write some code to stream this file directly to disk instead.

The StreamFilter? was updated to prevent server lock-ups in the case of get requests. This would happen if a user types in "http://localhost/postFile".

Here is some code that shows how to do it:

from cherrypy import cpg
from cherrypy.lib.filter import basefilter

import cgi

class StreamFilter(basefilter.BaseInputFilter):
    def afterRequestHeader(self):
        if cpg.request.path == '/postFile':
            # if you don't check that it is a post method the server might lock up
            # we also check to make shour something was submitted
            if not 'Content-Length' in cpg.request.headerMap or cpg.request.method != 'POST':
                """ the file is empty you might want to redirect"""
            else:
                # Tell CherryPy not to parse the POST data itself for this URL
                cpg.request.parsePostData = False

class Root:
    _cpFilterList = [StreamFilter()]
    def index(self):
        return """
            <html><body>
            <form method=post action=postFile enctype="multipart/form-data">
                Upload a file: <input type=file name=myFile><br>
                <input type=submit>
            </form>
            </body></html>
        """
    index.exposed = True

    def postFile(self):
        # Note: we could check Content-Length here and bail out if it's too big

        # Create a copy of cpg.request.headerMap with lower keys
        lowerHeaderMap = {}
        for key, value in cpg.request.headerMap.items():
            lowerHeaderMap[key.lower()] = value

        # Use cgi.FieldStorage to parse the POST data.
        # This will store the uploaded file in a tempfile
        dataDict = cgi.FieldStorage(fp=cpg.request.rfile, headers=lowerHeaderMap, environ={'REQUEST_METHOD':'POST'}, keep_blank_values=1)
 
        value = dataDict['myFile']

        # Value has 2 attributes:
        #    - filename contains the name of the uploaded file
        #    - file is an input stream opened for reading

        # Read the tempfile and store it in the final file
        # Note that you'd have to carefully choose the filename if you want to
        # be thread-safe
        f = open('/tmp/myFile', 'wb')
        while 1:
            data = value.file.read(1024 * 8) # Read blocks of 8KB at a time
            if not data: break
            f.write(data)
        f.close()

        return "<html><body>The file has been saved in /tmp/myFile</body></html>"
    postFile.exposed = True 

cpg.root = Root()
cpg.server.start()

You can try to run that code, upload a huge file and watch the RAM of your process not moving :-)

As an alternative to the while loop one could use shutil.copyfileobj, which is same code but you would gain any optimization work on shutil.

from shutil import copyfileobj
...
data.seek(0)   # just to ensure we're at the beginning
copyfileobj(fsrc=data, fdst=f, length=1024 * 8)
f.close()

edited for clarity for noobs like me

from shutil import copyfileobj
...
f = open('/tmp/myFile', 'wb')
data = value.file
data.seek(0)   # just to ensure we're at the beginning
copyfileobj(fsrc=data, fdst=f, length=1024 * 8)
f.close()

File Upload Filter

[This filter is supposed to work in CP 2.0 only, 2.1 is going to have such functionality on board]

This filter automatically stores uploaded file under a temporary name in a given directory. Requires also guid.py module by C. Albrecht (included). In the following example simple form is used to store file on the server and display its real and temporary name as well as file type recognized by server:

from cherrypy import cpg
from fileuploadfilter import FileUploadFilter

class Test:
	TempDir = 'c:\\temp\\' #directory to store uploaded files
        #UploadContext is the list of following tuples:
        #(exposed method name, parameter used to transfer file, temporary directory name)
	UploadContext = [('processUpload', 'fileob', TempDir)]
	_cpFilterList = [FileUploadFilter(UploadContext)]
	
	@cpg.expose
	def index(self):
		return """<HTML><BODY>
		<FORM ACTION="processUpload" METHOD="POST" ENCTYPE="multipart/form-data">
		<INPUT TYPE="file" name="fileob">
		<INPUT TYPE="submit">
		</FORM>
		</BODY></HTML>
		"""
		
	@cpg.expose
	def processUpload(self, fileob):
		yield 'File Name: %s<br>' % cpg.request.filenameMap['fileob']
		yield 'File Type: %s<br>' % cpg.request.fileTypeMap['fileob']
		yield 'Stored as: %s' % fileob

Attachments

Hosted by WebFaction

Log in as guest/cpguest to create tickets