Tomcat 11无法接收PHP发送的multipart/form-data请求附件问题排查
Tomcat 11无法接收PHP发送的multipart/form-data请求附件问题排查
最近碰到个头疼的老项目兼容问题:一套运行多年的PHP代码,自定义构造multipart/form-data请求(包含XML字符串字段和文件附件)发送到Tomcat服务器,在Tomcat 9.x上一直正常,升级到Tomcat 11.0.9后直接崩了,折腾了好一阵才捋清楚问题,分享下排查过程和解决思路。
问题复盘
先明确下核心场景:
- PHP端:手动拼接multipart请求体,包含
_tnix(XML内容)和attachment(文件附件)两个部分 - Java端:原Servlet直接读取
InputStream,手动解析multipart边界、字段和附件内容 - 升级Tomcat 11后遇到两个问题:
- Tomcat直接拦截了multipart请求 → 已通过在项目context配置中添加
allowCasualMultipartParsing="true"解决 - 即使开启宽松解析,自定义的
InputStream解析逻辑依然找不到请求边界,直接抛出异常
- Tomcat直接拦截了multipart请求 → 已通过在项目context配置中添加
先排查PHP端的格式坑(Tomcat 11对标准更严格)
翻了下提供的PHP代码,发现手动构造multipart请求体存在几个隐形问题,这些在Tomcat 9中可能被宽松兼容,但Tomcat 11严格遵循RFC 7578标准,直接触发解析失败:
- HTTP头引号转义错误:PHP代码里用
"代替双引号(比如name="_tnix"),但HTTP头中的双引号不需要HTML转义,Tomcat解析时会把字段名识别为"_tnix"而非_tnix,直接导致找不到对应字段。 - 手动计算Content-Length易出错:代码里手动统计请求体长度,一旦拼接时的换行、boundary长度计算偏差,Tomcat会因长度不匹配截断请求体,自然找不到边界。
- 二进制流处理风险:手动读取文件二进制流拼接,虽然用了
rb模式,但如果文件包含特殊字节,可能和boundary冲突,或导致长度计算偏差。
PHP端修复方案:让CURL自动构造multipart请求
放弃手动拼接请求体,改用CURL原生的multipart支持,它会自动处理boundary生成、头格式、长度计算,完全符合标准:
function SendTmsNewsToHost($host, $port, $dest_url, $tnixdata, $attachment) { global $debug, $TMS_LineId; if ($port == 0) $port = 8080; $destination = "http://$host:$port/$dest_url"; // 构造POST字段数组 $postFields = [ '_tnix' => $tnixdata ]; // 处理附件(PHP 5.5+推荐用CURLFile,旧版本可改用@$attachment,但@已被弃用) if (!empty($attachment)) { $postFields['attachment'] = new CURLFile($attachment); } $curl = curl_init($destination); if (is_resource($curl)) { curl_setopt_array($curl, [ CURLOPT_HTTPHEADER => [ "Host: $host:$port", "Connection: Close" ], CURLOPT_FAILONERROR => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_HEADER => false, CURLOPT_RETURNTRANSFER => true, CURLOPT_VERBOSE => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $postFields ]); $response = curl_exec($curl); curl_close($curl); return $response; } return false; }
这个改动能从根源上解决格式兼容问题,毕竟CURL的multipart实现经过严格测试,适配所有符合标准的服务器。
Java端:放弃自定义解析,用Servlet原生API
原手动读取InputStream解析multipart的方式过于脆弱,完全依赖Tomcat旧版的宽松兼容逻辑,升级后直接失效。Servlet 3.0+提供了原生Part API,能完美适配Tomcat 11的严格解析,代码更简洁可靠。
第一步:配置Servlet支持multipart请求
推荐用注解方式配置(更直观),也可通过web.xml配置:
@WebServlet("/your-servlet-path") @MultipartConfig( fileSizeThreshold = 1024 * 1024 * 2, // 2MB(超过该大小的文件会写入磁盘) maxFileSize = 1024 * 1024 * 10, // 单文件最大10MB maxRequestSize = 1024 * 1024 * 50 // 整个请求最大50MB ) public class YourMultipartServlet extends HttpServlet { // 业务逻辑代码 }
第二步:在doPost方法中直接获取字段和附件
无需再手动解析流,直接用request.getPart()获取对应字段:
@Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 获取XML字段_tnix Part tnixPart = request.getPart("_tnix"); if (tnixPart == null) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, "缺少必填字段_tnix"); return; } // 读取XML内容(编码根据实际业务调整) String xmlContent = new String(tnixPart.getInputStream().readAllBytes(), StandardCharsets.UTF_8); // 处理XML业务逻辑... // 2. 获取附件attachment Part attachmentPart = request.getPart("attachment"); if (attachmentPart != null) { String fileName = getSubmittedFileName(attachmentPart); if (fileName != null && !fileName.isBlank()) { // 示例:将附件保存到临时目录,根据业务调整逻辑 try (InputStream fileIn = attachmentPart.getInputStream(); OutputStream fileOut = new FileOutputStream(new File("/tmp/" + fileName))) { fileIn.transferTo(fileOut); } // 附件业务逻辑... } } // 返回响应 response.setStatus(HttpServletResponse.SC_OK); response.getWriter().write("请求处理成功"); } // 兼容不同Tomcat版本的文件名获取方法 private String getSubmittedFileName(Part part) { String contentDisposition = part.getHeader("Content-Disposition"); if (contentDisposition == null) return null; for (String segment : contentDisposition.split(";")) { segment = segment.trim(); if (segment.startsWith("filename=")) { String fileName = segment.substring(9).trim(); // 移除文件名前后的引号 return fileName.replaceFirst("^\"", "").replaceFirst("\"$", ""); } } return null; }
为什么自定义解析在Tomcat 11失效?
Tomcat 11基于Servlet 6.0规范,对multipart请求的校验更严格:
- 边界字符串的前后必须严格匹配RFC格式,不允许多余空格或换行
- Content-Disposition头的字段名、文件名必须符合标准格式,不允许非法字符
- 请求体的Content-Length必须与实际字节数完全一致,否则直接截断请求
原自定义解析逻辑未考虑这些严格校验,依赖Tomcat 9的宽松兼容,升级后自然崩溃。
最后总结
- PHP端:永远不要手动拼接multipart请求体,交给CURL自动处理,避免格式兼容问题。
- Java端:放弃自定义流解析,用Servlet原生的
PartAPI,适配所有符合标准的Tomcat版本,代码更可靠。 - Tomcat配置:保留
allowCasualMultipartParsing="true",除非你的请求完全符合RFC 7578标准,否则Tomcat 11仍会拦截非标准请求。
内容来源于stack exchange
相关产品推荐
相关产品推荐

