Java中HTTP响应Multipart二进制文件读取与转发问题求助
解决方案:读取Multipart二进制响应并转发
核心问题说明
- 读取二进制文件错误:原代码用
InputStreamReader将二进制流转为字符流,会破坏字节结构,导致Multipart中的文件损坏。 - 构建Multipart请求错误:直接转发原始
requestBody可能未正确维护Multipart边界、编码属性,或未匹配目标接口的Content-Type要求。
一、正确读取Multipart二进制响应示例
方式1:使用Apache Commons FileUpload解析(推荐)
依赖(Maven)
<dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.5</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.15.1</version> </dependency>
解析代码
import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.apache.commons.io.IOUtils; import javax.servlet.http.HttpServletRequest; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.util.List; // 解析Multipart响应 private void parseMultipartResponse(HttpURLConnection conn) throws IOException { // 1. 读取完整响应字节数组 byte[] responseBytes = IOUtils.toByteArray(conn.getInputStream()); // 2. 提取Content-Type中的边界标识 String contentType = conn.getContentType(); String boundary = contentType.split("boundary=")[1]; // 3. 初始化文件上传解析器 DiskFileItemFactory factory = new DiskFileItemFactory(); factory.setRepository(new File(System.getProperty("java.io.tmpdir"))); ServletFileUpload upload = new ServletFileUpload(factory); // 模拟HttpServletRequest供解析器使用 ByteArrayInputStream bais = new ByteArrayInputStream(responseBytes); HttpServletRequest mockRequest = new MockHttpServletRequest() { @Override public String getContentType() { return contentType; } @Override public int getContentLength() { return responseBytes.length; } @Override public InputStream getInputStream() throws IOException { return bais; } }; try { List<FileItem> items = upload.parseRequest(mockRequest); for (FileItem item : items) { if (!item.isFormField()) { // 处理二进制文件 String fileName = item.getName(); byte[] fileBytes = item.get(); System.out.println("读取到文件:" + fileName + ",大小:" + fileBytes.length + "字节"); // 可保存文件或留存用于后续转发 } else { // 处理普通表单字段 String fieldName = item.getFieldName(); String fieldValue = item.getString("UTF-8"); System.out.println("表单字段:" + fieldName + "=" + fieldValue); } } } catch (Exception e) { e.printStackTrace(); } } // 简易MockHttpServletRequest实现(仅实现必要方法) class MockHttpServletRequest implements HttpServletRequest { @Override public String getContentType() { return null; } @Override public int getContentLength() { return 0; } @Override public InputStream getInputStream() throws IOException { return null; } // 其他未用到的方法可抛出UnsupportedOperationException @Override public String getAuthType() { throw new UnsupportedOperationException(); } // 按需补充其他方法 }
方式2:手动解析Multipart边界(无依赖)
private void parseMultipartManually(byte[] responseBytes, String boundary) throws IOException { String boundaryStr = "--" + boundary; String endBoundary = boundaryStr + "--"; // 用ISO-8859-1保证字节不丢失 String responseStr = new String(responseBytes, "ISO-8859-1"); String[] parts = responseStr.split(boundaryStr); for (String part : parts) { if (part.isEmpty() || part.equals(endBoundary)) continue; // 分隔头部和内容 int headerEndIndex = part.indexOf("\r\n\r\n"); if (headerEndIndex == -1) continue; String headers = part.substring(0, headerEndIndex); byte[] contentBytes = part.substring(headerEndIndex + 4).getBytes("ISO-8859-1"); // 判断是否为文件字段 if (headers.contains("filename=")) { String fileName = headers.split("filename=\"")[1].split("\"")[0]; System.out.println("读取到文件:" + fileName + ",大小:" + contentBytes.length + "字节"); } else { // 普通表单字段 String fieldName = headers.split("name=\"")[1].split("\"")[0]; String fieldValue = new String(contentBytes, "UTF-8"); System.out.println("表单字段:" + fieldName + "=" + fieldValue); } } }
二、构建并发送Multipart请求示例
import org.apache.commons.fileupload.FileItem; import org.apache.commons.io.IOUtils; import java.io.IOException; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.List; private void sendMultipartRequest(URL targetUrl, String authHeader, List<FileItem> fileItems) throws IOException { HttpURLConnection conn = (HttpURLConnection) targetUrl.openConnection(); conn.setRequestMethod("POST"); conn.setDoOutput(true); conn.setDoInput(true); // 设置认证头 if (authHeader != null && !authHeader.isEmpty()) { conn.setRequestProperty("Authorization", authHeader); } // 生成随机边界 String boundary = "----WebKitFormBoundary" + System.currentTimeMillis(); conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary); try (OutputStream os = conn.getOutputStream()) { for (FileItem item : fileItems) { // 写入边界 os.write(("--" + boundary + "\r\n").getBytes("UTF-8")); // 写入字段头部 os.write(item.getHeaders().getBytes("UTF-8")); os.write("\r\n".getBytes("UTF-8")); // 写入字段内容(二进制文件直接写字节) os.write(item.get()); os.write("\r\n".getBytes("UTF-8")); } // 写入结束边界 os.write(("--" + boundary + "--\r\n").getBytes("UTF-8")); os.flush(); } // 处理目标接口响应 int responseCode = conn.getResponseCode(); System.out.println("目标接口响应码:" + responseCode); if (responseCode == HttpURLConnection.HTTP_OK) { byte[] responseBytes = IOUtils.toByteArray(conn.getInputStream()); System.out.println("响应内容:" + new String(responseBytes, "UTF-8")); } else { byte[] errorBytes = IOUtils.toByteArray(conn.getErrorStream()); System.out.println("错误响应:" + new String(errorBytes, "UTF-8")); } conn.disconnect(); }
原代码修改建议(修复二进制读取问题)
将字符流读取改为字节流读取,避免损坏二进制数据:
private void postLocalRequest(URL url, byte[] requestBody, String contentType, String authHeader) throws IOException, ProtocolException, Exception { logger.info("本地请求URL:::" + url); logger.info("本地请求Content-Type:::" + contentType); HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection(); httpURLConnection.setRequestMethod("POST"); if (!authHeader.isEmpty()) { httpURLConnection.setRequestProperty("Authorization", authHeader); } httpURLConnection.addRequestProperty("Accept", "application/json; charset=UTF-8"); httpURLConnection.setRequestProperty("Content-Type", contentType); httpURLConnection.setDoOutput(true); httpURLConnection.setDoInput(true); OutputStream outputStream = httpURLConnection.getOutputStream(); outputStream.write(requestBody); outputStream.flush(); logger.info("等待本地响应"); logger.info("HTTP响应码:::" + httpURLConnection.getResponseCode()); deviceResponseContentType = httpURLConnection.getContentType(); logger.info("设备响应Content-Type::" + deviceResponseContentType); // 修复:用字节流读取响应,避免二进制数据损坏 if (httpURLConnection.getResponseCode() == HttpURLConnection.HTTP_OK) { byte[] responseBytes = IOUtils.toByteArray(httpURLConnection.getInputStream()); // 针对Multipart响应做解析 if (deviceResponseContentType != null && deviceResponseContentType.startsWith("multipart/form-data")) { String boundary = deviceResponseContentType.split("boundary=")[1]; parseMultipartManually(responseBytes, boundary); // 解析后的文件可留存用于后续转发 } else { deviceResponse = new String(responseBytes, "UTF-8"); XWCConnectorSupplementalLogging.logXML(logger, "plr", "设备请求输出", deviceResponse); } } else { try { byte[] errorBytes = IOUtils.toByteArray(httpURLConnection.getInputStream()); deviceResponse = new String(errorBytes, "UTF-8"); XWCConnectorSupplementalLogging.logXML(logger, "plr", "设备请求输出", deviceResponse); } catch (Exception e) { try { byte[] errorBytes = IOUtils.toByteArray(httpURLConnection.getErrorStream()); String error = new String(errorBytes, "UTF-8"); if(httpURLConnection.getResponseCode() != HttpURLConnection.HTTP_NOT_FOUND) { deviceResponse = error; } XWCConnectorSupplementalLogging.logXML(logger, "plr", "设备请求错误", error); } catch (Exception e1) { logger.severe("无法读取请求错误或输出"); } } } }
内容的提问来源于stack exchange,提问作者Raj
相关产品推荐
相关产品推荐

