You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java中HTTP响应Multipart二进制文件读取与转发问题求助

解决方案:读取Multipart二进制响应并转发

核心问题说明

  1. 读取二进制文件错误:原代码用InputStreamReader将二进制流转为字符流,会破坏字节结构,导致Multipart中的文件损坏。
  2. 构建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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 18:46:00