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.