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>"; } }
关键修复点说明
- 全字节流处理:所有请求内容(包括头和上传数据)都用字节流处理,彻底避免字符流对二进制数据的破坏。
- 正确解析multipart边界:从
Content-Type头中提取boundary值,用边界判断文件内容的起始和结束位置,解决读取无法终止的问题。 - 请求头与内容分离:读取请求头时,将剩余未读的字节重新拼接回输入流,确保上传内容的完整性。
- 多线程处理请求:主线程用线程处理每个客户端请求,避免单个请求阻塞服务器。
- 健壮的参数解析:用正则表达式提取
Content-Length和boundary,避免字符串截取的脆弱性。
内容的提问来源于stack exchange,提问作者KevinV
相关产品推荐
相关产品推荐

