【问题标题】:Python requests_toolbelt MultipartEncoder filenamePython requests_toolbelt MultipartEncoder 文件名
【发布时间】:2015-05-26 04:54:14
【问题描述】:

使用 requests_toolbelt 以 Multipart 形式上传大文件,我在下面构建了一个成功上传文件的方法,但是我无法访问发布的文件名。如何访问服务器上的文件名?

# client-side
file = open('/Volumes/Extra/test/my_video.mpg', 'rb')
payload = MultipartEncoder({file.name: file})
r = requests.post(url, data=payload, headers={'Content-Type': 'application/octet-stream'})

# server-side
@view_config(route_name='remote.agent_upload', renderer='json')
def remote_agent_upload(request):
    r = request.response
    fs = request.body_file
    f = open('/Volumes/Extra/tests2/bar.mpg', 'wb')  # wish to use filename here
    f.write(fs.read())
    fs.close()
    f.close()
    return r

【问题讨论】:

    标签: python python-requests


    【解决方案1】:

    好的,看起来您正在使用文件名作为字段名。此外,您这样做的方式似乎是整个帖子内容都被写入文件......这是预期的结果吗?你有没有试过在服务器端写完mpg文件后实际播放?

    我目前没有可用于测试的 HTTP 服务器,它自动给我一个请求对象,但我假设请求对象是一个 webob.Request 对象(至少看起来是这样的,如有错误请指正)

    好的,让我向您展示我的测试。 (这适用于 python3.4,不确定您使用的是哪个版本的 Python,但我认为它也应该适用于 Python 2.7 - 虽然尚未测试)

    这个测试中的代码有点长,但是它被大量注释以帮助你理解我在每个步骤中做了什么。希望它能让您更好地了解 HTTP 请求和响应如何使用您正在使用的工具在 python 中工作

    # My Imports
    from requests_toolbelt import MultipartEncoder
    from webob import Request
    import io
    
    # Create a buffer object that can be read by the MultipartEncoder class
    # This works just like an open file object
    file = io.BytesIO()
    
    # The file content will be simple for my test.
    # But you could just as easily have a multi-megabyte mpg file
    # Write the contents to the file
    file.write(b'test mpg content')
    
    # Then seek to the beginning of the file so that the
    # MultipartEncoder can read it from the beginning
    file.seek(0)
    
    # Create the payload
    payload = MultipartEncoder(
        {
    
            # The name of the file upload field... Not the file name
            'uploadedFile': (
    
                # This would be the name of the file
                'This is my file.mpg',
    
                # The file handle that is ready to be read from
                file,
    
                # The content type of the file
                'application/octet-stream'
            )
        }
    )
    
    # To send the file, you would use the requests.post method
    # But the content type is not application-octet-stream
    # The content type is multipart/form-data; with a boundary string
    # Without the proper header type, your server would not be able to
    # figure out where the file begins and ends and would think the
    # entire post content is the file, which it is not. The post content
    # might even contain multiple files
    # So, to send your file, you would use:
    #
    # response = requests.post(url, data=payload, headers={'Content-Type': payload.content_type})
    
    # Instead of sending the payload to the server,
    # I am just going to grab the output as it would be sent
    # This is because I don't have a server, but I can easily
    # re-create the object using this output
    postData = payload.to_string()
    
    # Create an input buffer object
    # This will be read by our server (our webob.Request object)
    inputBuffer = io.BytesIO()
    
    # Write the post data to the input buffer so that the webob.Request object can read it
    inputBuffer.write(postData)
    
    # And, once again, seek to 0
    inputBuffer.seek(0)
    
    # Create an error buffer so that errors can be written to it if there are any
    errorBuffer = io.BytesIO()
    
    # Setup our wsgi environment just like the server would give us
    environment = {
        'HTTP_HOST': 'localhost:80',
        'PATH_INFO': '/index.py',
        'QUERY_STRING': '',
        'REQUEST_METHOD': 'POST',
        'SCRIPT_NAME': '',
        'SERVER_NAME': 'localhost',
        'SERVER_PORT': '80',
        'SERVER_PROTOCOL': 'HTTP/1.0',
        'CONTENT_TYPE': payload.content_type,
        'wsgi.errors': errorBuffer,
        'wsgi.input': inputBuffer,
        'wsgi.multiprocess': False,
        'wsgi.multithread': False,
        'wsgi.run_once': False,
        'wsgi.url_scheme': 'http',
        'wsgi.version': (1, 0)
    }
    
    # Create our request object
    # This is the same as your request object and should have all our info for reading
    # the file content as well as the file name
    request = Request(environment)
    
    # At this point, the request object is the same as what you get on your server
    # So, from this point on, you can use the following code to get
    # your actual file content as well as your file name from the object
    
    # Our uploaded file is in the POST. And the POST field name is 'uploadedFile'
    # Grab our file so that it can be read
    uploadedFile = request.POST['uploadedFile']
    
    # To read our content, you can use uploadedFile.file.read()
    print(uploadedFile.file.read())
    
    # And to get the file name, you can use uploadedFile.filename
    print(uploadedFile.filename)
    

    所以,我认为这个修改后的代码对你有用。 (希望) 同样,没有测试,因为我实际上没有要测试的服务器。而且,我不知道你的“请求”对象在服务器端是什么样的对象......好吧,这里是:

    # client-side
    import requests
    file = open('/Volumes/Extra/test/my_video.mpg', 'rb')
    payload = MultipartEncoder({'uploadedFile': (file.name, file, 'application/octet-stream')})
    r = requests.post('http://somewhere/somefile.py', data=payload, headers={'Content-Type': payload.content_type})
    
    # server-side
    @view_config(route_name='remote.agent_upload', renderer='json')
    def remote_agent_upload(request):
    
        # Write your actual file contents, not the post data which contains multi part boundary
        uploadedFile = request.POST['uploadedFile']
        fs = uploadedFile.file
    
        # The file name is insecure. What if the file name comes through as '../../../etc/passwd'
        # If you don't secure this, you've just wiped your /etc/passwd file and your server is toast
        # (assuming the web user has write permission to the /etc/passwd file
        # which it shouldn't, but just giving you a worst case scenario)
        fileName = uploadedFile.filename
    
        # Secure the fileName here... 
        # Make sure it doesn't have any slashes or double dots, or illegal characters, etc.
        # I'll leave that up to you
    
        # Write the file
        f = open('/Volumes/Extra/tests2/' + fileName, 'wb')
        f.write(fs.read())
    

    【讨论】:

    • 如果我想添加json,如何在服务器端读取这个?
    • Json 只是一个字符串。它像任何其他文本字段一样被上传。获得字符串后,您只需使用json.loads(string) 对其进行解析
    【解决方案2】:

    对于原始 OP 来说可能为时已晚,但可能对其他人有所帮助。这就是我使用 MultipartEncoder 在多部分/表单数据上传中上传带有 json 文件的方法。当我需要一个文件作为多部分请求的一部分以单个 json 字符串作为二进制文件上传时(因此只有两个部分,文件和 json)。请注意,在创建我的请求标头(它是接收服务器指定的自定义标头)时,我从编码对象中获取 content_type(它通常以 multipart/form-data 的形式出现)。我使用的是 simplejson.dumps,但我相信你可以使用 json.dumps。

    m = MultipartEncoder([
        ('json', (None, simplejson.dumps(datapayload), 'text/plain')),
        ('file', (os.path.basename(file_path), open(file_path, 'rb'), 'text/plain'))],
        None, encoding='utf-8')
    
    headers = {'Authorization': 'JwToken' + ' ' + jwt_str, 'content-type': m.content_type}
    response = requests.post(uri, headers=headers, data=m, timeout=45, verify=True )
    

    在文件部分,该字段称为“文件”,但我使用os.path.basename(file_path) 仅从完整文件路径获取文件名,例如c:\temp\mytestfile.txt 。如果我愿意,我可以在此字段中轻松地将文件称为其他名称(这不是原始名称)。

    【讨论】:

    • 谢谢。花了很多时间才终于开始工作
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-12-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-27
    相关资源
    最近更新 更多