如何实现SAP工时系统的multipart/mixed批量请求?
解决SAP OData Batch请求语法错误问题
问题诊断
你的请求代码存在两个明确语法错误,再加上手动构造batch请求的固有风险,导致服务器返回语法错误:
- Header拼接错误:
x-csrf-token: <some-token>==Content-Type: application/json缺少换行分隔,两个HTTP头被合并,服务器无法解析。 - JSON格式损坏:
"ApproverPernr":"<some-number"末尾缺少闭合双引号,破坏了JSON结构。 - 手动拼接字符串易出错:SAP OData对batch请求的格式要求严格,直接写字符串很容易在换行、边界符、长度校验等细节上出错。
解决方案
1. 快速修复现有代码
先修正两个明显错误,同时补上batch请求必需的Content-Type头:
r = session.post( "<some-url>", params={"sap-client": "100"}, headers={ "Content-Type": "multipart/mixed; boundary=batch_9003-5d2c-29c4" }, data='\r\n--batch_9003-5d2c-29c4\r\nContent-Type: multipart/mixed; boundary=changeset_34fb-a3ca-3327\r\n\r\n--changeset_34fb-a3ca-3327\r\nContent-Type: application/http\r\nContent-Transfer-Encoding: binary\r\n\r\nPOST TimeEventSet?sap-client=100 HTTP/1.1\r\nsap-contextid-accept: header\r\nAccept: application/json\r\nAccept-Language: de\r\nDataServiceVersion: 2.0\r\nMaxDataServiceVersion: 2.0\r\nx-csrf-token: <some-token>==\r\nContent-Type: application/json\r\nContent-Length: 152\r\n\r\n{"EmployeeID":"<some-number>","EventDate":"2024-04-19T00:00:00","EventTime":"PT17H10M6S","TimeType":"<some-code>","TimezoneOffset":"<some-offset>","ApproverPernr":"<some-number>"}\r\n--changeset_34fb-a3ca-3327--\r\n\r\n--batch_9003-5d2c-29c4--\r\n' )
修正点:
- 在
x-csrf-token值后添加\r\n分隔Header - 给
ApproverPernr的值补上闭合双引号 - 新增batch请求的
Content-Type头,指定对应boundary
2. 更可靠的实现:用工具构建multipart请求
手动拼字符串容错率低,推荐用requests-toolbelt的MultipartEncoder自动处理格式细节:
先安装依赖:
pip install requests-toolbelt
编写代码:
from requests_toolbelt.multipart.encoder import MultipartEncoder import requests session = requests.Session() # 后续替换为动态获取的CSRF token csrf_token = "<some-token>==" # 构造内部POST请求内容 changeset_body = ( f"POST TimeEventSet?sap-client=100 HTTP/1.1\r\n" f"sap-contextid-accept: header\r\n" f"Accept: application/json\r\n" f"Accept-Language: de\r\n" f"DataServiceVersion: 2.0\r\n" f"MaxDataServiceVersion: 2.0\r\n" f"x-csrf-token: {csrf_token}\r\n" f"Content-Type: application/json\r\n\r\n" '{"EmployeeID":"<some-number>","EventDate":"2024-04-19T00:00:00","EventTime":"PT15H15M29S","TimeType":"<some-code>","TimezoneOffset":"<some-offset>","ApproverPernr":"<some-number>"}' ) # 生成随机边界符(符合OData规范) batch_boundary = f"batch_{''.join(random.choices('abcdef0123456789', k=16))}" changeset_boundary = f"changeset_{''.join(random.choices('abcdef0123456789', k=16))}" # 构建changeset层级的multipart changeset_encoder = MultipartEncoder( fields={'': (None, changeset_body, 'application/http')}, boundary=changeset_boundary ) # 构建外层batch层级的multipart batch_encoder = MultipartEncoder( fields={'': (None, changeset_encoder.read(), f'multipart/mixed; boundary={changeset_boundary}')}, boundary=batch_boundary ) # 发送请求 response = session.post( "<some-url>", params={"sap-client": "100"}, headers={ "Content-Type": batch_encoder.content_type, # 带上登录后的Cookie等必要头信息 }, data=batch_encoder ) # 查看结果 print(response.status_code) print(response.text)
3. 额外注意事项
- 每次请求的batch/changeset边界符要随机生成,避免固定值触发服务器安全校验
- 确保session保留登录状态的Cookie,否则请求会被拦截
- 动态获取CSRF token时,先发送一个HEAD/GET请求到目标服务,从响应头的
x-csrf-token字段提取值
内容的提问来源于stack exchange,提问作者daeda
相关产品推荐
相关产品推荐

