Jetty-http抛出"Incomplete Multipart"异常的原因及解决咨询
问题:Jetty9 + Apache Camel解析Multipart请求时抛出Incomplete Multipart异常
环境信息
- 运行环境:基于Apache ServiceMix的Jetty9(v2022)、Apache Camel 2.25.4
- 现象:通过Firefox开发者工具发送Multipart请求到Jetty端点时,抛出
java.io.IOException: Incomplete Multipart异常
Camel路由配置(Blueprint XML)
<route id="restservice"> <from uri="jetty:http://0.0.0.0:10060/input?handlers=securityHandler&httpMethodRestrict=PUT,POST&mapHttpMessageBody=true"/> <log message="1 DEBUG BODY: ${body}"/> <toD uri="log:showdetails?showAll=true"/> </route>
请求详情
Fetch请求代码
await fetch("DUMMY", { "credentials": "include", "headers": { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0", "Accept": "*/*", "Accept-Language": "de,en-US;q=0.7,en;q=0.3", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Pragma": "no-cache", "Cache-Control": "no-cache", "Authorization": "DUMMY", "Content-Length": 151, "Content-Type": "multipart/form-data;boundary=---------------------------2446457052814" }, "referrer": "DUMMY", "body": "-----------------------------24464570528145\nContent-Disposition: form-data; name=\"f\";\n\n\nthe larger icon\n-----------------------------24464570528145--\n", "method": "POST", "mode": "cors" });
实际请求Body
-----------------------------24464570528145 Content-Disposition: form-data; name="f"; the larger icon -----------------------------24464570528145--
异常信息
Jetty日志
org.eclipse.jetty.util - 9.4.46.v20220331 | handleException /path java.io.IOException: Incomplete Multipart
完整异常栈
HTTP ERROR 500 Server Error URI: /path STATUS: 500 MESSAGE: Server Error SERVLET: org.apache.camel.component.jetty.CamelContinuationServlet-71f4fb97 CAUSED BY java.io.IOException: Incomplete Multipart java.io.IOException: Incomplete Multipart at org.eclipse.jetty.http.MultiPartFormInputStream.parse(MultiPartFormInputStream.java:570) at org.eclipse.jetty.http.MultiPartFormInputStream.getParts(MultiPartFormInputStream.java:427) at org.eclipse.jetty.server.MultiParts$MultiPartsHttpParser.getParts(MultiParts.java:67) at org.eclipse.jetty.server.Request.getParts(Request.java:2425) at org.eclipse.jetty.server.Request.getParts(Request.java:2409) at org.apache.camel.component.jetty.MultiPartFilter.doFilter(MultiPartFilter.java:53) at org.apache.camel.component.jetty.CamelFilterWrapper.doFilter(CamelFilterWrapper.java:44) at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:193) at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1601) at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:548) at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:233) at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1440) at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:188) at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:501) at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:186) at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1355) at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141) at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:560) at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127) at org.eclipse.jetty.server.Server.handle(Server.java:516) at org.eclipse.jetty.server.HttpChannel.lambda$handle$1(HttpChannel.java:487) at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:732) at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:479) at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:277) at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:311) at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:105) at org.eclipse.jetty.io.ChannelEndPoint$1.run(ChannelEndPoint.java:104) at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:338) at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:315) at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:173) at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:131) at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:409) at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:883) at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1034) at java.lang.Thread.run(Thread.java:750) Suppressed: java.lang.Throwable at org.eclipse.jetty.http.MultiPartFormInputStream.throwIfError(MultiPartFormInputStream.java:470) at org.eclipse.jetty.http.MultiPartFormInputStream.getParts(MultiPartFormInputStream.java:428) ... 33 more
排查尝试
- 已定位到Jetty的
MultiPartFormInputStream类解析逻辑触发异常,但未明确具体原因 - 测试发送简单Body(如
--f--)时请求能正常通过,说明Header配置无问题,但Body包含实际内容时就会触发异常
原因分析与解决方案
核心原因
- Boundary不匹配:请求Header中声明的boundary是
---------------------------2446457052814,但Body中使用的boundary是-----------------------------24464570528145(末尾多了一个5),Jetty解析时无法匹配到正确分隔符,导致认定Multipart内容不完整。 - Content-Length不准确:Header中声明的
Content-Length为151,但实际Body字节数可能与该值不符。Jetty会严格校验Content-Length与实际接收字节数,一旦不匹配就会判定内容不完整。 - 换行符格式不符合规范:Multipart规范要求使用
CRLF(\r\n)作为换行符,而请求Body中使用的是\n,可能导致解析器无法正确识别分隔边界和内容块。
解决方案
1. 修正Boundary匹配
确保Header中Content-Type的boundary值与Body中使用的分隔符完全一致:
- Header中的boundary:
---------------------------2446457052814 - Body中的分隔符应为:
-----------------------------2446457052814(开头多两个-),结束分隔符为-----------------------------2446457052814--
2. 保证Content-Length准确
不要手动设置Content-Length,让请求库自动计算并添加该Header,避免手动输入错误。如果必须手动设置,需精确计算Body的字节数(注意编码差异,比如UTF-8下特殊字符的字节长度)。
3. 统一换行符为CRLF
将Body中的\n替换为\r\n,符合HTTP Multipart规范要求。
4. 调整Camel Jetty端点配置
尝试在Jetty端点URI中添加multipartFilter=false禁用Camel的MultiPartFilter,让Jetty原生处理;或者升级Camel版本(2.25.4版本较老,后续版本修复了部分Multipart解析问题):
<from uri="jetty:http://0.0.0.0:10060/input?handlers=securityHandler&httpMethodRestrict=PUT,POST&mapHttpMessageBody=true&multipartFilter=false"/>
5. 测试验证示例
使用符合规范的请求格式测试:
const boundary = "---------------------------2446457052814"; const body = [ `--${boundary}\r\n`, 'Content-Disposition: form-data; name="f"\r\n', '\r\n', 'the larger icon\r\n', `--${boundary}--\r\n` ].join(''); await fetch("http://0.0.0.0:10060/input", { "credentials": "include", "headers": { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0", "Accept": "*/*", "Accept-Language": "de,en-US;q=0.7,en;q=0.3", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Pragma": "no-cache", "Cache-Control": "no-cache", "Authorization": "DUMMY", "Content-Type": `multipart/form-data;boundary=${boundary}` }, "body": body, "method": "POST", "mode": "cors" });
内容的提问来源于stack exchange,提问作者reddiamond92
相关产品推荐
相关产品推荐

