Java模拟文件上传需手动构造符合RFC 7578的multipart/form-data请求体:正确生成唯一boundary、严格使用CRLF换行、按序写入字段与文件字节、Content-Type头同步声明,且HttpURLConnection配置顺序不可错。
Java 中模拟文件上传,本质是构造符合 HTTP multipart/form-data 协议的请求体 —— java.io 本身不处理 HTTP,直接用 FileInputStream 或 ByteArrayInputStream 读取文件内容只是第一步,关键在如何把它“塞进”正确的边界格式里。
浏览器上传时自动生成随机 boundary,服务端靠它分割字段。Java 模拟时若漏写、写错或未在 Content-Type 中同步声明,后端(如 Spring @RequestParam MultipartFile)会直接解析失败,报 Required request part 'file' is not present 或空文件。
boundary 必须全局唯一,推荐用 UUID.randomUUID().toString() 生成Content-Type 请求头必须形如:multipart/form-data; boundary=----WebKitFormBoundaryXXXXX
--{boundary},最后以 --{boundary}-- 结尾(注意末尾两个短横)Content-Disposition: form-data; name="file"; filename="a.txt" 和 Content-Type: text/plain
HttpURLConnection 发送时禁用 setDoOutput(true) 后再设 setRequestMethod("POST")
顺序错误会导致连接进入只读模式,getOutputStream() 抛 java.net.ProtocolException: cannot write to a URLConnection if doOutput=false —— 这是初学者高频翻车点。
conn.setDoOutput(true),再调 conn.setRequestMethod("POST")
setRequestProperty("Content-Type", "...") 必须在 getOutputStream() 之前设置conn.setDoInput(true)(默认已开启),否则可能干扰输出流获取writeBytes(),要用 write() + byte[] 避免编码污染用 PrintStream 或 writeBytes(String) 写二进制文件(如图片、PDF)会触发平台默认字符集(如 Windows 的 GBK)转码,导致文件损坏。上传后打开乱码或无法识别,就是这个原因。
writeBytes(str + "\r\n")(确保 CRLF)outputStream.write(byteArray),且 byteArray 来自 Files.readAllBytes(Paths.get("xxx")) 或 FileInputStream.read()
new String(bytes).getBytes() 转换String boundary = "----WebKitFormBoundary" +UUID.randomUUID().toString(); URL url = new URL("http://localhost:8080/upload"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setDoOutput(true); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary); try (OutputStream out = conn.getOutputStream(); PrintWriter writer = new PrintWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8), true)) { // 写普通字段 writer.append("--").append(boundary).append("\r\n"); writer.append("Content-Disposition: form-data; name=\"desc\"\r\n\r\n"); writer.append("test upload").append("\r\n"); // 写文件字段 writer.append("--").append(boundary).append("\r\n"); writer.append("Content-Disposition: form-data; name=\"file\"; filename=\"hello.txt\"\r\n"); writer.append("Content-Type: text/plain\r\n\r\n"); writer.flush(); // 写文件内容(纯字节,不经过 writer) byte[] fileBytes = "Hello from Java IO!".getBytes(StandardCharsets.UTF_8); out.write(fileBytes); // 结束 boundary out.write("\r\n--".getBytes()); out.write(boundary.getBytes()); out.write("--\r\n".getBytes()); }
真正难的不是读文件,而是让字节流严格符合 RFC 7578;边界字符串、CRLF 换行、字段顺序、结尾双短横——错一个,后端就收不到文件。别信“用工具类自动封装”,先手写一次,才能看清 multipart 的骨架。