Apache HttpClient返回Bad Request,请求排查配置问题
Apache HttpClient配置S3表单上传返回Bad Request问题排查
问题背景
已导出可正常运行的Postman POST请求JSON,按该请求配置Apache HttpClient后,始终收到Bad Request响应,需排查配置问题。
Postman导出的请求内容
{ "info": { "_postman_id": "eae7c603-8b49-496f-9f71-a16b475f34ce", "name": "Upload Documents", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", "_exporter_id": "14164917" }, "item": [ { "name": "Upload a supporting document", "request": { "method": "POST", "header": [], "body": { "mode": "formdata", "formdata": [ { "key": "Key", "value": "someValue", "type": "text" }, { "key": "X-Amz-Algorithm", "value": "AWS4-HMAC-SHA256", "type": "text" }, { "key": "X-Amz-Credential", "value": "someValue", "type": "text" }, { "key": "X-Amz-Date", "value": "20220802T142430Z", "type": "text" }, { "key": "X-Amz-Security-Token", "value": "someValue", "type": "text" }, { "key": "Policy", "value": "someValue", "type": "text" }, { "key": "X-Amz-Signature", "value": "someValue", "type": "text" }, { "key": "X-Amz-Meta-X-Swan-Supporting-Document-Id", "value": "someValue", "type": "text" }, { "key": "file", "type": "file", "src": "/path/test.pdf" } ] }, "url": { "raw": "https://s3.eu-west-1.amazonaws.com/document-sandbox", "protocol": "https", "host": [ "s3", "eu-west-1", "amazonaws", "com" ], "path": [ "document-sandbox" ] } }, "response": [] } ] }
现有Apache HttpClient代码
private static CloseableHttpResponse upload(String url, Map<String, String> requestParameters, File file) { CloseableHttpClient httpClient = HttpClients.createDefault(); HttpPost httpPost = new HttpPost(url); FileBody fileBody = new FileBody(file, ContentType.DEFAULT_BINARY); try { MultipartEntityBuilder builder = MultipartEntityBuilder.create(); builder.addBinaryBody("file", file, ContentType.DEFAULT_BINARY, fileBody.getFilename()); requestParameters.forEach((key, value) -> builder.addTextBody(key, value, ContentType.TEXT_PLAIN)); httpPost.setEntity(builder.build()); CloseableHttpResponse response = httpClient.execute(httpPost); httpClient.close(); return response; } catch (Exception e) { throw new RuntimeException("Uploading document failed, error: " + e.getMessage()); } }
问题分析与修复
1. 表单字段顺序错误
S3预签名表单上传对字段顺序有严格要求,必须先传递所有文本参数,最后传递文件字段。你的代码中先添加了file字段,再循环添加文本参数,顺序与Postman完全相反,这会导致S3签名验证失败,返回Bad Request。
2. 文本字段ContentType设置不当
Postman中的文本字段使用form-data默认的文本类型,而代码中指定了ContentType.TEXT_PLAIN,会给文本字段额外添加Content-Type: text/plain头,与Postman的请求格式不一致,可能触发S3的验证逻辑报错。
3. 客户端资源关闭时机错误
代码在执行请求后立刻关闭httpClient,返回的CloseableHttpResponse会因为客户端已关闭而无法正常读取响应体,不利于排查具体错误原因。
修复后的代码
private static CloseableHttpResponse upload(String url, Map<String, String> requestParameters, File file) { try (CloseableHttpClient httpClient = HttpClients.createDefault()) { HttpPost httpPost = new HttpPost(url); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); // 先添加所有文本参数,保持与Postman一致的顺序 requestParameters.forEach((key, value) -> builder.addTextBody(key, value)); // 最后添加文件字段 builder.addBinaryBody("file", file, ContentType.DEFAULT_BINARY, file.getName()); httpPost.setEntity(builder.build()); return httpClient.execute(httpPost); } catch (Exception e) { throw new RuntimeException("Uploading document failed, error: " + e.getMessage()); } }
说明:
- 使用
try-with-resources自动管理CloseableHttpClient的生命周期,避免提前关闭导致响应无法读取 - 调整字段顺序,先添加所有文本参数,最后添加文件
- 去掉
addTextBody的ContentType.TEXT_PLAIN参数,使用默认的form-data文本类型
内容的提问来源于stack exchange,提问作者woozny
相关产品推荐
相关产品推荐

