You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于AsyncHTTPTestCase测试Tornado的multipart请求

Testing Tornado Multipart File Upload API with AsyncHTTPTestCase

Great question! Testing multipart file uploads in Tornado using AsyncHTTPTestCase is totally doable—you just need to construct the correct multipart/form-data request payload and headers. Let's break this down with concrete examples.

Step 1: Create Your Tornado Upload Handler

First, let's define a simple handler that accepts file uploads (this is the API you're testing):

import tornado.web

class UploadHandler(tornado.web.RequestHandler):
    async def post(self):
        # Get the uploaded file from the request
        file_data = self.request.files.get("file")
        if not file_data:
            self.set_status(400)
            self.write({"error": "No file uploaded"})
            return
        
        file_info = file_data[0]
        filename = file_info["filename"]
        content = file_info["body"]
        
        # For testing purposes, return metadata about the file
        self.write({
            "filename": filename,
            "size": len(content),
            "content_type": file_info["content_type"]
        })

Step 2: Write the Test Case with AsyncHTTPTestCase

We'll cover two approaches: building the multipart payload manually (no external dependencies) and using a helper library for cleaner code.

Approach 1: Manual Multipart Payload Construction

This method uses only built-in Python modules, so no extra installs are needed:

import tornado.testing
import tornado.escape

class TestUploadAPI(tornado.testing.AsyncHTTPTestCase):
    def get_app(self):
        # Return your Tornado app with the upload handler
        return tornado.web.Application([
            (r"/upload", UploadHandler),
        ])
    
    def test_upload_file_manual(self):
        # Define a unique boundary string (separates parts in the multipart form)
        boundary = b"----TornadoTestBoundary123"
        
        # File details
        filename = b"sample.txt"
        file_content = b"Hello, this is a test file content!"
        content_type = b"text/plain"
        
        # Build the multipart form parts
        multipart_parts = [
            b"--" + boundary,
            b'Content-Disposition: form-data; name="file"; filename="' + filename + b'"',
            b"Content-Type: " + content_type,
            b"",  # Empty line separates headers from file content
            file_content,
            b"--" + boundary + b"--",  # End of multipart form
            b""
        ]
        
        # Join all parts with CRLF (required by multipart standards)
        request_body = b"\r\n".join(multipart_parts)
        
        # Set necessary headers
        headers = {
            "Content-Type": f"multipart/form-data; boundary={boundary.decode('utf-8')}",
            "Content-Length": str(len(request_body))
        }
        
        # Send the POST request
        response = self.fetch(
            "/upload",
            method="POST",
            headers=headers,
            body=request_body
        )
        
        # Validate the response
        self.assertEqual(response.code, 200)
        response_data = tornado.escape.json_decode(response.body)
        self.assertEqual(response_data["filename"], "sample.txt")
        self.assertEqual(response_data["size"], len(file_content))
        self.assertEqual(response_data["content_type"], "text/plain")

Approach 2: Using a Helper Library (requests-toolbelt)

If you prefer a cleaner approach without manual payload building, use requests-toolbelt's MultipartEncoder to handle formatting:

First, install the library:

pip install requests-toolbelt

Then, update your test method:

from requests_toolbelt.multipart.encoder import MultipartEncoder

class TestUploadAPI(tornado.testing.AsyncHTTPTestCase):
    # Keep the get_app() method from Approach 1
    
    def test_upload_file_with_toolbelt(self):
        # Create a MultipartEncoder with the file field
        encoder = MultipartEncoder(
            fields={
                "file": ("sample.txt", b"Hello, test content!", "text/plain")
            }
        )
        
        # The encoder handles the Content-Type header with the correct boundary
        headers = {
            "Content-Type": encoder.content_type
        }
        
        # Fetch the upload endpoint with the encoded body
        response = self.fetch(
            "/upload",
            method="POST",
            headers=headers,
            body=encoder.read()
        )
        
        # Validate the response
        self.assertEqual(response.code, 200)
        response_data = tornado.escape.json_decode(response.body)
        self.assertEqual(response_data["filename"], "sample.txt")
        self.assertEqual(response_data["size"], len(b"Hello, test content!"))

Key Notes

  • Tornado 4.5+ fully supports multipart/form-data requests, so self.request.files will correctly parse uploaded files into a dictionary of lists.
  • When building the payload manually, use CRLF (\r\n) to separate parts—this is required by the multipart standard.
  • The boundary string must be unique and not appear anywhere in the file content to avoid parsing errors.

内容的提问来源于stack exchange,提问作者Trần Kim Dự

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 02:30:25