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

Java HTTP Server文件上传功能实现问题求助

问题分析与修复方案

你的Java HTTP服务器在文件上传时遇到两个核心问题:二进制文件损坏、读取操作无法自动终止,根源在于错误使用字符流处理二进制数据、multipart请求解析逻辑混乱,以下是具体问题点和修复方案:

核心问题拆解

  • 二进制文件损坏:使用BufferedReader/InputStreamReader处理上传流,这类字符流会将原始二进制字节按字符编码转换,破坏非文本文件的字节结构。
  • 读取无法终止:重复创建BufferedReader导致流位置混乱,且未正确解析multipart/form-data的边界,无法判断文件内容的结束位置,导致循环一直等待读取。
  • 请求头读取后的流偏移:用字符流读取请求头后,流的指针已经移动,后续读取上传内容时会丢失部分数据。

修复后的完整代码

import java.io.*;
import java.net.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class HTTP {
    public static void main(String[] args) throws IOException {
        int port = 8080;
        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("Server started on port " + port);

        while (true) {
            Socket clientSocket = serverSocket.accept();
            // 用线程处理请求,避免阻塞主线程
            new Thread(() -> {
                try {
                    handleRequest(clientSocket);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }

    private static void handleRequest(Socket clientSocket) throws IOException {
        InputStream inputStream = clientSocket.getInputStream();
        OutputStream outputStream = clientSocket.getOutputStream();

        // 读取请求头(用字节流处理,避免破坏二进制数据)
        StringBuilder requestHeaders = new StringBuilder();
        byte[] headerBuffer = new byte[1024];
        int bytesRead;
        boolean headerEnd = false;
        ByteArrayOutputStream headerStream = new ByteArrayOutputStream();

        // 读取直到遇到空行(\r\n\r\n)
        while (!headerEnd && (bytesRead = inputStream.read(headerBuffer)) != -1) {
            headerStream.write(headerBuffer, 0, bytesRead);
            String headerStr = headerStream.toString();
            if (headerStr.contains("\r\n\r\n")) {
                headerEnd = true;
                // 拆分请求头和剩余内容
                int splitIndex = headerStr.indexOf("\r\n\r\n") + 4;
                requestHeaders.append(headerStr.substring(0, splitIndex));
                // 将剩余的字节写回临时流,后续处理上传内容
                ByteArrayInputStream remainingStream = new ByteArrayInputStream(
                        headerStream.toByteArray(), splitIndex, headerStream.size() - splitIndex);
                inputStream = new SequenceInputStream(remainingStream, inputStream);
            }
        }

        String headerStr = requestHeaders.toString();
        if (headerStr.contains("GET /upload")) {
            sendHtmlResponse(outputStream, getUploadPage());
        } else if (headerStr.contains("POST /upFile")) {
            // 解析Content-Length
            int contentLength = getContentLength(headerStr);
            // 解析boundary
            String boundary = getBoundary(headerStr);
            if (boundary != null && contentLength > 0) {
                saveUploadedFile(inputStream, contentLength, boundary);
                sendHtmlResponse(outputStream, getUploadSuccessPage());
            } else {
                sendHtmlResponse(outputStream, "<html><body><h1>Upload failed: Invalid request</h1></body></html>");
            }
        } else {
            sendHtmlResponse(outputStream, getDefaultPage());
        }

        clientSocket.close();
    }

    private static void sendHtmlResponse(OutputStream outputStream, String content) throws IOException {
        String response = "HTTP/1.1 200 OK\r\n" +
                "Content-Type: text/html\r\n" +
                "Content-Length: " + content.getBytes().length + "\r\n" +
                "\r\n" +
                content;
        outputStream.write(response.getBytes());
        outputStream.flush();
    }

    private static void saveUploadedFile(InputStream inputStream, int contentLength, String boundary) throws IOException {
        String fileName = null;
        ByteArrayOutputStream fileContent = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int totalBytesRead = 0;
        boolean isFileContent = false;
        String boundaryDelimiter = "--" + boundary;
        String boundaryEnd = "--" + boundary + "--";
        ByteArrayOutputStream lineBuffer = new ByteArrayOutputStream();

        while (totalBytesRead < contentLength && (bytesRead = inputStream.read(buffer)) != -1) {
            totalBytesRead += bytesRead;
            int pos = 0;
            while (pos < bytesRead) {
                if (!isFileContent) {
                    // 读取每行,直到找到文件内容的起始位置
                    while (pos < bytesRead && buffer[pos] != '\n') {
                        lineBuffer.write(buffer[pos]);
                        pos++;
                    }
                    if (pos < bytesRead) {
                        lineBuffer.write(buffer[pos]);
                        pos++;
                        String line = lineBuffer.toString().trim();
                        lineBuffer.reset();

                        if (line.startsWith(boundaryDelimiter)) {
                            // 开始新的表单部分
                            continue;
                        }
                        if (line.startsWith("Content-Disposition: form-data;")) {
                            // 提取文件名
                            Matcher matcher = Pattern.compile("filename=\"(.*?)\"").matcher(line);
                            if (matcher.find()) {
                                fileName = matcher.group(1);
                            }
                        } else if (line.isEmpty()) {
                            // 空行后开始是文件内容
                            isFileContent = true;
                        }
                    }
                } else {
                    // 检查是否到达边界
                    int endPos = pos;
                    while (endPos < bytesRead && endPos - pos < boundaryEnd.length()) {
                        endPos++;
                    }
                    String temp = new String(buffer, pos, endPos - pos);
                    if (temp.contains(boundaryDelimiter)) {
                        // 找到边界,停止读取文件内容
                        int boundaryStart = temp.indexOf(boundaryDelimiter);
                        fileContent.write(buffer, pos, boundaryStart);
                        pos += boundaryStart;
                        isFileContent = false;
                    } else {
                        // 写入文件内容
                        fileContent.write(buffer, pos, bytesRead - pos);
                        pos = bytesRead;
                    }
                }
            }
        }

        if (fileName != null && fileContent.size() > 0) {
            saveBytesToFile(fileContent.toByteArray(), fileName);
        }
    }

    private static int getContentLength(String requestHeaders) {
        Matcher matcher = Pattern.compile("Content-Length: (\\d+)").matcher(requestHeaders);
        if (matcher.find()) {
            return Integer.parseInt(matcher.group(1));
        }
        return 0;
    }

    private static String getBoundary(String requestHeaders) {
        Matcher matcher = Pattern.compile("boundary=(.*)").matcher(requestHeaders);
        if (matcher.find()) {
            return matcher.group(1).trim();
        }
        return null;
    }

    private static void saveBytesToFile(byte[] content, String filename) throws IOException {
        Path path = Paths.get(System.getProperty("user.dir"), filename);
        System.out.println("Saving file to: " + path.toAbsolutePath());
        try (FileOutputStream fos = new FileOutputStream(path.toAbsolutePath().toString())) {
            fos.write(content);
        }
        System.out.println("File saved: " + filename);
    }

    private static String getUploadPage() {
        return "<html><body><form action=\"/upFile\" method=\"post\" enctype=\"multipart/form-data\">\n" +
                "  <input type=\"file\" name=\"file\"/><br/>\n" +
                "  <input type=\"submit\" value=\"Upload\"/>\n" +
                "</form></body></html>";
    }

    private static String getUploadSuccessPage() {
        return "<html><body><h1>File uploaded successfully!</h1></body></html>";
    }

    private static String getDefaultPage() {
        return "<html><body><h1>Welcome to the Simple HTTP Server</h1></body></html>";
    }
}

关键修复点说明

  1. 全字节流处理:所有请求内容(包括头和上传数据)都用字节流处理,彻底避免字符流对二进制数据的破坏。
  2. 正确解析multipart边界:从Content-Type头中提取boundary值,用边界判断文件内容的起始和结束位置,解决读取无法终止的问题。
  3. 请求头与内容分离:读取请求头时,将剩余未读的字节重新拼接回输入流,确保上传内容的完整性。
  4. 多线程处理请求:主线程用线程处理每个客户端请求,避免单个请求阻塞服务器。
  5. 健壮的参数解析:用正则表达式提取Content-Length和boundary,避免字符串截取的脆弱性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 15:44:56