Uploading files using Twisted Web

Inputting information through web forms is really easy under Twisted web. You just have to set up render_GET to display a form, where each input element has a name (e.g. "name", "email", whatever). Then, render_POST, taking an argument of request, can access these values as follows:

name = request.args["name"][0]
email = request.args["email"][0]

(Note that the variable used to store these values does not need to be the same as the name of the input element in the form.)

Now, if you want to include a file input, rather than simpler forms (text boxes, drop-down lists, etc.), this can be easily added to the form:

 <input type="file" name="filename">

This form element will insert a button, that allows the user to browse for and select a file. However, if we access it as before, we end up just with the name of the file the user input, rather than its contents. Which is, obviously, of limited use.

Now, I looked at all the fields listed in the Request API, and didn't see anything relating to the file, other than its name. This was curious, as clearly the file contents would have to be transmitted somehow.

Googling the issue turned up a solution using cgi.FieldStorage:

def render_POST(self, request):

    self.headers = request.getAllHeaders()
    img = cgi.FieldStorage(
    fp = request.content,
    headers = self.headers,
    environ = {'REQUEST_METHOD':'POST',
    'CONTENT_TYPE': self.headers['content-type'],
    }
    )

    print img["upl_file"].name, img["upl_file"].filename,
    print img["upl_file"].type, img["upl_file"].type
    out = open(img["upl_file"].filename, 'wb')
    out.write(img["upl_file"].value)
    out.close()
    request.redirect('/tests')
    return ''

cgi.FieldStorage does appear to be useful for processing html forms; however, the documentation online doesn't appear to cover how to initialize it from an existing request. The above example is all I had to go on.

The FieldStorage constructor optionally takes an argument fp, the file pointer. By default this is sys.stdin (which is what the documented examples appear to use). The example above passes in request.content as the file pointer. This is a bit troubling, as request.content appears to be a string containing the different input field values, with just the file name for the file input field. Nonetheless, maybe there's some magic to the file pointer, that will allow FieldStorage to see contents that I can't.

When trying the code above, img["upl_file"].name contains the name of the field (i.e. "upl_file"), img["upl_file"].filename and img["upl_file"].type both have values of None, and img["upl_file"].value contains the name of the file input by the user. The output code fails entirely, as it attempts to open a file with a name of None. And even if it were adjusted, the contents of that file would be only the name of the user's file, rather than the file's contents.

Now, this was the only example I could find that used cgi.FieldStorage to upload files under Twisted.web. I did, however, find a couple others that solved the problem using Twisted.web2. I would prefer not to use these, I they would involve installing a new web framework, but I did take a look at them to see if I could figure out where the file contents might be hiding. One of the examples included not just the request handling code, but also the html code for the form itself. Which is where I found the important missing component.

Rather than declare the form as

<form method="post">

I should have been including an enctype attribute:

<form enctype="multipart/form-data" method="post">

The default value for enctype isĀ application/x-www-form-urlencoded, which, as far as I can tell, is stripping the file contents from the request.

With this attribute set to multipart/form-data, the code snippet above works correctly. img["upl_file"].filename now contains the name of the user's file, and img["upl_file"].value contains the file's contents. The code needs only to be modified to check that a file has been input (if not if currently fails), and it will save a copy of the user's file on the server, as we require.

social