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

Java HttpServer保存APK文件为空问题排查求助

问题原因与修复方案

你的代码核心问题是请求体的输入流被重复读取,导致第二次读取时已经没有内容:

  1. 当调用up.parseRequest(...)时,ServletFileUpload已经将he.getRequestBody()的输入流完全读取并解析成FileItem列表,此时输入流指针已走到末尾。
  2. 之后再调用InputStream inputStream = he.getRequestBody()拿到的流是空的,inputStream.read(buffer)直接返回-1,没有字节写入文件,最终文件大小为0。

修复步骤

1. 直接用FileItem的流保存文件

不需要重新获取请求体流,直接使用每个FileItem提供的getInputStream()读取文件内容:

// 移除原inputStream相关代码,替换为遍历FileItem处理
for (FileItem fi : result) {
    if (!fi.isFormField()) { // 排除普通表单字段,只处理文件项
        // 构建保存路径,确保父目录存在
        File apkDir = new File(Environment.getExternalStorageDirectory(), "test/configXml");
        apkDir.mkdirs();
        File apkFile = new File(apkDir, "test.apk");
        
        try (InputStream fis = fi.getInputStream();
             FileOutputStream fos = new FileOutputStream(apkFile)) {
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, bytesRead);
            }
            Log.i(HttpServerManager.getHttpServerManager().TAG, "文件保存完成,大小:" + fi.getSize());
        }
    }
    // 处理响应输出
    os.write(fi.getName().getBytes());
    os.write("\r\n".getBytes());
    Log.i(HttpServerManager.getHttpServerManager().TAG, "File-Item: " + fi.getFieldName() + " = " + fi.getName() + " get size " + fi.getSize() );
}

2. 修正getContentLength实现

RequestContext的getContentLength()返回0是错误的,需返回实际请求内容长度,否则ServletFileUpload可能无法正确解析:

@Override
public int getContentLength() {
    String length = he.getRequestHeaders().getFirst("Content-Length");
    return length != null ? Integer.parseInt(length) : -1;
}

3. 调整响应发送时机

建议在文件处理完成后再发送响应,避免提前发送导致的潜在问题:

// 先处理文件,再发送响应
he.getResponseHeaders().add("Content-type", "text/plain");
String responseText = "文件上传完成";
he.sendResponseHeaders(200, responseText.getBytes().length);
try (OutputStream os = he.getResponseBody()) {
    os.write(responseText.getBytes());
}

4. 优化路径拼接

不要硬拼路径,用File类的构造方法保证跨平台兼容性:

// 替代硬拼的路径写法
File apkDir = new File(Environment.getExternalStorageDirectory(), "test/configXml");
apkDir.mkdirs();
File apkFile = new File(apkDir, "test.apk");

完整修复后的核心代码片段

class postApkFile implements HttpHandler {

    @RequiresApi(api = Build.VERSION_CODES.O)
    @Override
    public void handle(HttpExchange he) throws IOException {

        he.getResponseHeaders().add("Access-Control-Allow-Origin", "*");

        if (he.getRequestMethod().equalsIgnoreCase("OPTIONS")) {
            he.getResponseHeaders().add("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); // 补充POST方法
            he.getResponseHeaders().add("Access-Control-Allow-Headers", "Content-Type,Authorization");
            he.sendResponseHeaders(204, -1);
            return;
        }

        DiskFileItemFactory d = new DiskFileItemFactory();

        try {
            ServletFileUpload up = new ServletFileUpload(d);
            List<FileItem> result = up.parseRequest(new RequestContext() {

                @Override
                public String getCharacterEncoding() {
                    return "UTF-8";
                }

                @Override
                public int getContentLength() {
                    String length = he.getRequestHeaders().getFirst("Content-Length");
                    return length != null ? Integer.parseInt(length) : -1;
                }

                @Override
                public String getContentType() {
                    return he.getRequestHeaders().getFirst("Content-type");
                }

                @Override
                public InputStream getInputStream() throws IOException {
                    return he.getRequestBody();
                }

            });

            StringBuilder responseSb = new StringBuilder();
            // 处理文件保存
            for (FileItem fi : result) {
                responseSb.append(fi.getName()).append("\r\n");
                Log.i(HttpServerManager.getHttpServerManager().TAG, "File-Item: " + fi.getFieldName() + " = " + fi.getName() + " get size " + fi.getSize() );
                
                if (!fi.isFormField()) {
                    File apkDir = new File(Environment.getExternalStorageDirectory(), "test/configXml");
                    if (!apkDir.exists()) {
                        apkDir.mkdirs();
                    }
                    File apkFile = new File(apkDir, "test.apk");
                    
                    try (InputStream fis = fi.getInputStream();
                         FileOutputStream fos = new FileOutputStream(apkFile)) {
                        byte[] buffer = new byte[1024];
                        int bytesRead;
                        while ((bytesRead = fis.read(buffer)) != -1) {
                            fos.write(buffer, 0, bytesRead);
                        }
                    }
                }
            }

            // 发送响应
            he.getResponseHeaders().add("Content-type", "text/plain");
            byte[] responseBytes = responseSb.toString().getBytes();
            he.sendResponseHeaders(200, responseBytes.length);
            try (OutputStream os = he.getResponseBody()) {
                os.write(responseBytes);
            }

        } catch (Exception e) {
            e.printStackTrace();
            // 发送错误响应
            he.sendResponseHeaders(500, -1);
        }
    }
}

内容的提问来源于stack exchange,提问作者Ozgur Baykal

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 00:50:16