python urllib3 multipart/form-data 的坑

python urllib3 multipart/form-data 的坑

################################3.上传文件时带了个header与urllib3.request.py不兼容

传header的Content-Type时,应区分大小写

def request_encode_body(self, method, url, fields=None, headers=None,
                            encode_multipart=True, multipart_boundary=None,
                            **urlopen_kw):
    ...
    extra_kw['body'] = body
    extra_kw['headers'] = {'Content-Type': content_type}#这里header中必须是Content-Type,如果用户传来的header中已经有了'content-type', 则最终实际会出现2个这个重复值, 访问接口时会报错
            
    extra_kw['headers'].update(headers)
    ...


#实际使用的header为:
#'content-type': 'multipart/form-data; boundary=----WebKitFormBoundary0b1047518e88296', 
#'Content-Type': 'multipart/form-data; boundary=----WebKitFormBoundary0b1047518e88296'

################################2.上传文件后缀总是为null的问题解决

上传文件后缀总是为null的问题解决

注意这里的example.jpeg只能是文件名, 不能是带路径的, 若写成"\\image\\example.jpeg"上传的文件后缀就成null了.

fields={
         'text': ('None', "upload a string",'None'),
         'file': ('example.jpeg',file_data,"image/jpeg"),
     }

##################################1

在一个项目中用到python urllib3上传文件

urllib3.disable_warnings()
http = urllib3.PoolManager()
with open('example.jpeg','rb') as fp:
     file_data = fp.read()
r = http.request(
     'POST',
     'http://httpbin.org/post',
     headers = {'Content-Type':"multipart/form-data"},
     fields={
         'text': ('None', "upload a string",'None'),
         'file': ('example.jpeg',file_data,"image/jpeg"),
     })

结果上传错误, 试着打印出urllib3组成的post body, 打开D:\python27\Lib\site-packages\urllib3\request.py的request_encode_body函数, 在return 前面加上print extra_kw, 打印出的body结果如下:

我使用postman发送该接口上传文件都是对的:

点击Send下面一行的红色 Code,HTTP显示的是:

发现这里的boundary与python中的urllib3不一样. 原来urllib3 是post form-data时, 没有传multipart_boundary该参数会自动生成:D:\python27\Lib\site-packages\urllib3\filepost.py

def encode_multipart_formdata(fields, boundary=None):
    ...
    if boundary is None:
        boundary = choose_boundary()

def choose_boundary():
    """
    Our embarrassingly-simple replacement for mimetools.choose_boundary.
    """
    return uuid4().hex#生成的是32为的唯一码

试着在python中修改为以WebKitFormBoundary开头的试着再执行, 还是报错, 用同样的方法, 查找到headers的Content-Type没有带上boundary,在header中明明带了Content-Type:multipart/form-data, 调试发现:header中传了Content-Type后, urllib3生成的带boundary的Content-Type会被传进来的覆盖掉,如下:

D:\python27\Lib\site-packages\urllib3\request.py
def request_encode_body(self, method, url, fields=None, headers=None,
                            encode_multipart=True, multipart_boundary=None,
                            **urlopen_kw):
        if headers is None:
            headers = self.headers

        extra_kw = {'headers': {}}

        if fields:
            if 'body' in urlopen_kw:
                raise TypeError(
                    "request got values for both 'fields' and 'body', can only specify one.")

            if encode_multipart:
                body, content_type = encode_multipart_formdata(fields, boundary=multipart_boundary)#产生了新的content-type
            else:
                body, content_type = urlencode(fields), 'application/x-www-form-urlencoded'

            extra_kw['body'] = body
            extra_kw['headers'] = {'Content-Type': content_type}#也将产生的新的content-type加到headers中了
            
        extra_kw['headers'].update(headers)#一更新headers又还原成传进来的headers了
        extra_kw.update(urlopen_kw)

        
        return self.urlopen(method, url, **extra_kw)

总结可行方案:

1.http.request传的参数中带fields时,带上特定的boundary(以WebKitFormBoundary开头的, 原因未知, 知道的朋友可以留言告知),headers中不要带Content-Type;

2.http.request传的参数要带上headers的话,格式应该是:Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

最终的代码修改:

urllib3.disable_warnings()
http = urllib3.PoolManager()
with open('example.jpeg','rb') as fp:
     file_data = fp.read()
boundary = '----WebKitFormBoundary7MA4YWxkTrZu0gW'
#方案1
r = http.request(
     'POST',
     'http://httpbin.org/post',
     headers = {},
     multipart_boundary = boundary,
     fields={
         'text': ('None', "upload a string",'None'),
         'file': ('example.jpeg',file_data,"image/jpeg"),
     })


#方案2
r = http.request(
     'POST',
     'http://httpbin.org/post',
     headers = {'Content-Type':"multipart/form-data;%s"%boundary},
     multipart_boundary = boundary,
     fields={
         'text': ('None', "upload a string",'None'),
         'file': ('example.jpeg',file_data,"image/jpeg"),
     })

 

 

 

<think>好的,我现在需要解决用户关于在Python中设置multipart/form-data请求时boundary包含时间数据的问题。首先,我得确认用户的具体需求。他们希望在使用Python发送HTTP请求时,为Content-Type头中的boundary参数添加时间数据,可能是为了确保每次请求的boundary唯一,避免重复导致的问题。 首先,我应该回忆一下multipart/form-data的基本结构。这个内容类型用于上传文件或表单数据,boundary的作用是分隔不同的数据部分。根据RFC规范,boundary需要是一个唯一的字符串,通常由客户端生成,确保在请求体中不会与数据内容重复。 接下来,用户提到要在boundary中包含时间数据。时间数据可以用来保证每次生成的boundary都是不同的,这样可以有效避免冲突。常见的做法是使用时间戳,可能结合随机字符串,比如使用uuid或者时间戳加随机数。 然后,我需要考虑如何在Python中实现这一点。Python的requests库通常会自动处理boundary的生成,用户不需要手动设置。但当需要自定义时,比如加入时间数据,可能需要手动构造multipart数据,并设置对应的Content-Type头。 这里可能需要用到requests库的files参数,或者手动构建请求体。不过,手动构建比较麻烦,容易出错,所以最好利用现有的库来生成multipart数据。不过requests默认会自动生成boundary,但用户想要自定义,可能需要覆盖这个行为。 例如,当使用requests.post时,可以通过headers参数设置Content-Type,并指定boundary的值。同时,在files参数中传递文件或数据时,需要确保boundary与header中的一致。不过,可能需要注意requests库是否会允许自定义boundary,或者是否需要使用其他库如httplib2或urllib3来实现更底层的控制。 另外,生成boundary字符串时,可以结合时间戳和随机字符串。比如使用time模块获取当前时间戳,转换为字符串,再加上一个随机部分,比如uuid.uuid4().hex,这样可以确保唯一性。 然后,我需要验证这种方法是否符合HTTP标准。根据RFC 2046,boundary可以包含1到70个字符,使用ASCII码的字母、数字和部分符号(如&#39;+-_./:=&#39;),但不能以空格结尾,且不能包含某些特殊字符。因此,时间戳和uuid的组合应该是合法的。 可能的步骤是: 1. 生成包含时间戳的boundary字符串。 2. 设置Content-Type头,包含这个boundary。 3. 使用requests库发送请求,确保数据正确构造。 需要测试是否在设置自定义boundary时,requests库会正确生成multipart体。例如,当使用files参数时,requests会自动生成boundary,但如果手动设置Content-Type,可能会覆盖这个行为,导致数据构造错误。因此可能需要使用第三方库如requests-toolbelt来更灵活地处理multipart编码。 或者,如果手动构造multipart体,需要正确格式化各部分数据,并插入正确的boundary。这可能比较繁琐,但能更精确控制。 总结可能的解决方案: - 使用requests-toolbelt的MultipartEncoder,允许自定义boundary。 - 手动生成boundary,包含时间数据,并设置到headers和encoder中。 然后,给出具体的代码示例,比如使用MultipartEncoder来自定义boundary,并设置正确的Content-Type头。 同时,需要注意边界字符串的格式,确保不包含非法字符,长度合适,并且每次请求都生成新的。 最后,检查是否有潜在的陷阱,比如编码问题,或者请求库的自动处理与手动设置的冲突,确保用户能正确应用这个方法。</think>在Python中实现自定义boundary并包含时间数据的步骤如下: ### 分步说明 1. **生成唯一boundary** - 使用时间戳+随机字符串确保唯一性 - 格式示例:`boundary = "BOUNDARY-" + str(int(time.time())) + "-" + uuid.uuid4().hex` 2. **构造multipart/form-data请求** - 推荐使用`requests-toolbelt`库的`MultipartEncoder` - 需要单独安装:`pip install requests-toolbelt` 3. **设置请求头** - 将自定义boundary注入`Content-Type` ### 完整代码示例 ```python import time import uuid from requests_toolbelt.multipart.encoder import MultipartEncoder import requests # 生成带时间戳的boundary timestamp = str(int(time.time())) random_str = uuid.uuid4().hex custom_boundary = f&#39;---------{timestamp}-{random_str}&#39; # 构造multipart数据 multipart_data = MultipartEncoder( fields={ &#39;file&#39;: (&#39;test.txt&#39;, open(&#39;test.txt&#39;, &#39;rb&#39;), &#39;text/plain&#39;), &#39;field1&#39;: &#39;value1&#39; }, boundary=custom_boundary # 注入自定义boundary ) # 发送请求 headers = { &#39;Content-Type&#39;: multipart_data.content_type # 自动包含boundary参数 } response = requests.post( &#39;https://httpbin.org/post&#39;, data=multipart_data, headers=headers ) print(response.status_code) print(response.text) ``` ### 关键点说明 1. **boundary生成规范** - 长度:1-70个字符 - 合法字符:ASCII字母、数字及`&#39;()+_,-./:=? `等符号 - 必须避免与内容重复 2. **为什么用requests-toolbelt** - 标准`requests`库不支持自定义boundary - 该库提供更灵活的multipart构造方式 3. **时间戳的作用** - 确保每次请求的boundary不同 - 方便后期请求追踪和调试 ### 注意事项 1. 不要手动拼接multipart数据体,容易出错 2. boundary字符串中避免使用`\`或中文等非法字符 3. 当上传大文件时,建议使用`MultipartEncoderMonitor`实现进度监控 这种方法既符合HTTP规范,又能通过时间戳实现请求唯一标识,适用于需要严格区分每次请求的场景(如文件上传服务)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值