Search code examples
tornadomultipartform-data

Tornado: testing multipart request using AsyncHTTPTestCase


I am writing an API for user upload file using multipart request. I see that Tornado version 4.5 has supported multipart request. But after that, I want to test this API.

My question is:

  • How can I test an multipart request on Tornado. I have google many references, but I cannot find useful resources.
  • If cannot doing this in Tornado, how can I propose a multipart request so I can use with tornado's AsyncHTTPTestCase.

Thanks


Solution

  • Tornado does not have a built-in support for making multipart requests. You'll have to complie a multipart request manually.

    Let's first look at how a multipart/form-data request looks like.

    Sample form:

    <form method="POST" enctype="multipart/form-data">
        <input type="text" name="field1">
        <input type="file" name="field2">
    
        <input type="submit">
    </form>
    

    If you enter Hello in field1 and choose a file called myfile.png for field2, the HTTP request will look like this:

    POST /url HTTP/1.1
    Content-Type: multipart/form-data; boundary="boundary"
    
    --boundary
    Content-Disposition: form-data; name="field1"
    
    Hello
    --boundary
    Content-Disposition: form-data; name="field2"; filename="myfile.png"
    Conent-Type: image/png
    
    <binary content of myfile.png>
    --boundary--
    

    All you have to do is compile a similar request.


    Before I show you an example, let me make something clear to you, if you don't already know - in the raw HTTP request example above, at the end of every line there are these characters - \r\n. They aren't visible here but they are present in an actual HTTP request. Even the blank lines have \r\n characters present.

    This is important to know. If you're gonna compile an HTTP request manually, you'll have to add \r\n characters at the end of every line.

    Let's get to the example.

    class MyTestCase(AsyncHTTPTestCase):
        def test_something(self):
            # create a boundary
            boundary = 'SomeRandomBoundary'
    
            # set the Content-Type header
            headers = {
                'Content-Type': 'multipart/form-data; boundary=%s' % boundary
            }
    
            # create the body
    
            # opening boundary
            body = '--%s\r\n' % boundary 
    
            # data for field1
            body += 'Content-Disposition: form-data; name="field1"\r\n'
            body += '\r\n' # blank line
            body += 'Hello\r\n'
    
            # separator boundary
            body += '--%s\r\n' % boundary 
    
            # data for field2
            body += 'Content-Disposition: form-data; name="field2"; filename="myfile.png"\r\n'
            body += '\r\n' # blank line
            # now read myfile.png and add that data to the body
            with open('myfile.png', 'rb') as f:
                body += '%s\r\n' % f.read()
    
            # the closing boundary
            body += "--%s--\r\n" % boundary
    
    
            # make a request
            self.fetch(url, method='POST', headers=headers, body=body)
    

    The above code is very basic. If you have multiple files and arguments, you should consider writing a separate function for that and make use of for loops. Click here for an example code from Tornado's github repo.