Python解析multipart boundary:aiohttp與requests文件上傳詳解
目錄
1. 什么是boundary?
2. requests庫中boundary的處理
? 2.1 自動(dòng)處理boundary
? 2.2 手動(dòng)設(shè)置 boundary
? 2.3 手動(dòng)構(gòu)建boundary
3. aiohttp庫中boundary的處理
? 3.1 自動(dòng)處理boundary
? 3.2 手動(dòng)設(shè)置 boundary
? 3.3 手動(dòng)構(gòu)建boundary
4. aiohttp與requests的優(yōu)缺點(diǎn)對(duì)比
5. 總結(jié)
6. 相關(guān)閱讀
1. 什么是boundary?
在HTTP協(xié)議中,當(dāng)我們使用multipart/form-data提交表單時(shí),整個(gè)請(qǐng)求體包含多個(gè)部分,每部分之間的邊界由一個(gè)稱為boundary的字符串分隔。例如,HTTP請(qǐng)求頭中可能包含如下內(nèi)容:
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
這個(gè)boundary字符串保證服務(wù)器能夠正確解析各個(gè)字段和文件內(nèi)容,是構(gòu)造復(fù)雜表單數(shù)據(jù)的重要組成部分。
2. requests庫中boundary的處理
2.1 自動(dòng)處理boundary
使用requests發(fā)送表單數(shù)據(jù)時(shí),只需要將文件或字段通過files和data參數(shù)傳遞,requests會(huì)自動(dòng)生成boundary并封裝數(shù)據(jù)。
import requests
# 目標(biāo)URL(測(cè)試用:httpbin.org可返回提交的數(shù)據(jù))
url = 'http://httpbin.org/post'
# 構(gòu)造文件上傳數(shù)據(jù):requests會(huì)自動(dòng)構(gòu)造multipart/form-data請(qǐng)求
files = {
# 第一個(gè)參數(shù)為字段名稱,元組中依次為:(文件名, 文件對(duì)象, MIME類型)
'file':('test.txt', open('test.txt', 'rb'), 'text/plain')
}
# 發(fā)送POST請(qǐng)求
response = requests.post(url, files=files)
# 打印服務(wù)器返回內(nèi)容
print(response.text)
注釋說明:
? 此示例中,requests自動(dòng)在請(qǐng)求頭中生成Content-Type及其中的boundary,無需開發(fā)者手動(dòng)干預(yù)。
? 適用于大部分常規(guī)使用場(chǎng)景。
2.2 手動(dòng)設(shè)置 Boundary
在某些特殊情況下,可能需要手動(dòng)指定 boundary。此時(shí)可以借助 requests-toolbelt 庫中的 MultipartEncoder。
首先需安裝 requests-toolbelt:
pip install requests-toolbelt
下面是手動(dòng)指定 boundary 的示例代碼:
from requests_toolbelt.multipart.encoder import MultipartEncoder
import requests
def send_formdata_manual():
url = 'http://httpbin.org/post'
# 自定義 boundary 字符串
boundary = '----WebKitFormBoundary7MA4YWxkTrZu0gW'
# 使用 MultipartEncoder 構(gòu)造 multipart 數(shù)據(jù),同時(shí)指定 boundary
encoder = MultipartEncoder(
fields={
'field1': 'value1',
'file': ('test.txt', open('test.txt', 'rb'), 'text/plain')
},
boundary=boundary
)
# 設(shè)置 Content-Type 頭,包含自定義的 boundary
headers = {'Content-Type': encoder.content_type}
# 發(fā)送 POST 請(qǐng)求
response = requests.post(url, data=encoder, headers=headers)
print("手動(dòng)設(shè)置 boundary 的響應(yīng):", response.text)
send_formdata_manual()
2.3 手動(dòng)構(gòu)建boundary
有時(shí)我們需要對(duì)請(qǐng)求體的格式進(jìn)行更精細(xì)的控制,此時(shí)可以選擇手動(dòng)構(gòu)建multipart/form-data格式的數(shù)據(jù)。
import requests
# 目標(biāo)URL
url = 'http://httpbin.org/post'
# 自定義boundary字符串
boundary = '----WebKitFormBoundary7MA4YWxkTrZu0gW'
# 構(gòu)造請(qǐng)求體各部分?jǐn)?shù)據(jù),注意各部分之間以boundary分隔
data_lines = []
# 添加第一個(gè)字段:普通文本字段
data_lines.append('--' + boundary)
data_lines.append('Content-Disposition: form-data; name="field1"')
data_lines.append('') # 空行分隔頭與內(nèi)容
data_lines.append('value1')
# 添加第二個(gè)字段:文件字段
data_lines.append('--' + boundary)
data_lines.append('Content-Disposition: form-data; name="file"; filename="test.txt"')
data_lines.append('Content-Type: text/plain')
data_lines.append('')
# 讀取文件內(nèi)容(確保當(dāng)前目錄下有test.txt文件)
with open('test.txt', 'r', encoding='utf-8') as f:
data_lines.append(f.read())
# 結(jié)束標(biāo)志:加上結(jié)尾的boundary標(biāo)記
data_lines.append('--' + boundary + '--')
# 將各部分用CRLF連接
body = '\r\n'.join(data_lines)
# 構(gòu)造請(qǐng)求頭,指明Content-Type及boundary
headers = {
'Content-Type': 'multipart/form-data; boundary=' + boundary
}
# 發(fā)送POST請(qǐng)求,此處需要將body轉(zhuǎn)換為字節(jié)串
response = requests.post(url, data=body.encode('utf-8'), headers=headers)
print(response.text)
注釋說明:
? 手動(dòng)構(gòu)造的流程:先定義好boundary,再將每個(gè)部分的數(shù)據(jù)按照標(biāo)準(zhǔn)格式拼接(包括Content-Disposition和Content-Type等)。
? 最后將拼接好的字符串通過encode('utf-8')轉(zhuǎn)為字節(jié)發(fā)送。
3. aiohttp庫中boundary的處理
3.1 自動(dòng)處理boundary
aiohttp作為異步HTTP庫,同樣支持通過aiohttp.FormData構(gòu)造multipart/form-data數(shù)據(jù),并自動(dòng)管理boundary。
import aiohttp
import asyncio
async def main():
url = 'http://httpbin.org/post'
# 使用aiohttp提供的FormData構(gòu)造表單數(shù)據(jù)
form = aiohttp.FormData()
form.add_field('field1', 'value1')
# 添加文件字段,注意以二進(jìn)制方式打開文件
form.add_field('file',
open('test.txt', 'rb'),
filename='test.txt',
content_type='text/plain')
# 使用異步上下文管理器發(fā)送請(qǐng)求
async with aiohttp.ClientSession() as session:
async with session.post(url, data=form) as resp:
print(await resp.text())
# 運(yùn)行異步任務(wù)
asyncio.run(main())
注釋說明:
? aiohttp.FormData會(huì)自動(dòng)生成適合的boundary,并構(gòu)造請(qǐng)求體。
? 異步寫法適合高并發(fā)或異步應(yīng)用場(chǎng)景。
3.2 手動(dòng)設(shè)置 Boundary
有時(shí)需要自定義 boundary,比如為了和服務(wù)端進(jìn)行特殊交互,此時(shí)可以使用 aiohttp.MultipartWriter 手動(dòng)構(gòu)造 multipart 數(shù)據(jù)。
import aiohttp
import asyncio
async def send_formdata_manual():
# 自定義 boundary 字符串(注意確保不會(huì)與數(shù)據(jù)內(nèi)容沖突)
boundary = '----WebKitFormBoundary7MA4YWxkTrZu0gW'
# 創(chuàng)建 MultipartWriter 對(duì)象,手動(dòng)指定 boundary
mp_writer = aiohttp.MultipartWriter(boundary=boundary)
# 添加普通字段
part1 = mp_writer.append('value1')
part1.set_content_disposition('form-data', name='field1')
# 添加文件字段
with open('test.txt', 'rb') as f:
part2 = mp_writer.append(f.read(), {'Content-Type': 'text/plain'})
part2.set_content_disposition('form-data', name='file', filename='test.txt')
# 發(fā)送 POST 請(qǐng)求
async with aiohttp.ClientSession() as session:
async with session.post('http://httpbin.org/post', data=mp_writer) as resp:
result = await resp.text()
print("手動(dòng)設(shè)置 boundary 的響應(yīng):", result)
# 運(yùn)行異步任務(wù)
asyncio.run(send_formdata_manual())
代碼說明:
? 使用 aiohttp.MultipartWriter 手動(dòng)構(gòu)造 multipart 數(shù)據(jù),并通過參數(shù) boundary 指定自定義分隔符。
? 每個(gè)字段使用 append 方法添加,并通過 set_content_disposition 設(shè)置字段名稱與文件信息。
? 通過 aiohttp 異步發(fā)送請(qǐng)求,觀察服務(wù)端對(duì)自定義 boundary 的處理結(jié)果。
3.3 手動(dòng)構(gòu)建boundary
與requests類似,aiohttp也支持手動(dòng)構(gòu)造請(qǐng)求體,適用于需要完全自定義請(qǐng)求體格式的場(chǎng)景。
import aiohttp
import asyncio
async def main():
url = 'http://httpbin.org/post'
# 自定義boundary
boundary = '----WebKitFormBoundary7MA4YWxkTrZu0gW'
parts = []
# 添加普通文本字段
parts.append('--' + boundary)
parts.append('Content-Disposition: form-data; name="field1"')
parts.append('')
parts.append('value1')
# 添加文件字段
parts.append('--' + boundary)
parts.append('Content-Disposition: form-data; name="file"; filename="test.txt"')
parts.append('Content-Type: text/plain')
parts.append('')
with open('test.txt', 'r', encoding='utf-8') as f:
parts.append(f.read())
# 結(jié)束標(biāo)記
parts.append('--' + boundary + '--')
# 構(gòu)造完整請(qǐng)求體
body = '\r\n'.join(parts)
headers = {
'Content-Type': 'multipart/form-data; boundary=' + boundary
}
async with aiohttp.ClientSession() as session:
async with session.post(url, data=body.encode('utf-8'), headers=headers) as resp:
print(await resp.text())
asyncio.run(main())
注釋說明:
? 手動(dòng)構(gòu)造流程與requests類似,需自行拼接各部分?jǐn)?shù)據(jù)和boundary。
? 注意在異步環(huán)境中,通過await獲取響應(yīng)數(shù)據(jù)。
4. aiohttp與requests的優(yōu)缺點(diǎn)對(duì)比
特性 | requests | aiohttp |
同步/異步 | 同步,適合簡單腳本及同步流程 | 異步,適合高并發(fā)、大規(guī)模請(qǐng)求場(chǎng)景 |
易用性 | API設(shè)計(jì)直觀、簡單易用,自動(dòng)處理multipart表單數(shù)據(jù) | API設(shè)計(jì)靈活,適合異步編程,但學(xué)習(xí)曲線稍陡 |
性能 | 在低并發(fā)場(chǎng)景下表現(xiàn)良好,但阻塞I/O可能導(dǎo)致性能瓶頸 | 利用異步機(jī)制高效處理并發(fā)請(qǐng)求,性能優(yōu)勢(shì)明顯 |
手動(dòng)構(gòu)造支持 | 允許手動(dòng)構(gòu)造請(qǐng)求體,適用于對(duì)請(qǐng)求數(shù)據(jù)精細(xì)控制的需求 | 同樣支持手動(dòng)構(gòu)造,但通常建議使用內(nèi)置FormData自動(dòng)處理 |
社區(qū)與文檔 | 社區(qū)成熟,文檔詳細(xì),示例豐富 | 社區(qū)活躍,文檔逐步完善,但部分高級(jí)用法可能需要參考源碼 |
注釋說明:
? 如果項(xiàng)目對(duì)并發(fā)和性能有較高要求,aiohttp無疑是更好的選擇;
? 對(duì)于多數(shù)普通應(yīng)用,requests的簡單易用更能提高開發(fā)效率。
5. 總結(jié)
本文詳細(xì)介紹了multipart/form-data中boundary的作用,并對(duì)Python中requests與aiohttp兩種HTTP請(qǐng)求庫在處理boundary時(shí)的自動(dòng)與手動(dòng)構(gòu)造方式進(jìn)行了深入解析。通過完整的代碼示例,你可以看到兩者在實(shí)際應(yīng)用中的實(shí)現(xiàn)細(xì)節(jié)及各自的優(yōu)缺點(diǎn)。無論是同步的requests還是異步的aiohttp,都能滿足大部分場(chǎng)景的需求,而如何選擇則應(yīng)基于具體項(xiàng)目需求和性能要求。