SOAP with Attachments(简称SwA)是一种将SOAP消息与一个或多个附件通过MIME多部分(multipart/related)机制打包传输的规范。在需要上传XML描述信息同时附带文件或二进制数据的场景中,SwA避免了把附件做Base64编码后硬塞进SOAP体,从而显著降低消息体积与解析开销。实际处理时,核心在于服务端能够正确拆分MIME部分、识别根SOAP信封、并根据cid(Content-ID)引用把附件与XML中的逻辑字段关联起来。

SwA消息的MIME结构解析
一个典型的SwA请求,HTTP头中Content-Type一般为multipart/related,并带有一个boundary参数。消息体被这个boundary切成若干部分:第一部分通常是根SOAP信封,其Content-Type为text/xml,并通过start参数指向它的Content-ID;后续部分则是各类附件,每个附件有自己的Content-ID与媒体类型。服务端首先要按boundary把原始字节流拆成多个BodyPart,再判断哪一个是根消息。
在XML上传场景里,根SOAP体可能长这样:其中用一个cid:xxx的URI来代表某个附件的引用,而不是把文件内容写死在XML里。这种解耦让XML保持小巧、易校验,附件走原始二进制,网络与内存都更友好。如果服务端错误地只读取第一个部分当SOAP却忽略附件映射,就会出现字段为空或附件丢失。
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<uploadReq>
<meta>订单附件上传</meta>
<fileRef>cid:file123@ipipp.com</fileRef>
</uploadReq>
</soapenv:Body>
</soapenv:Envelope>
使用Axis2在Java中接收SwA上传
Axis2对SwA有原生支持,只需要在服务类中把参数声明为javax.attachment.DataHandler,框架便会自动把对应cid的附件注入。下面示例展示一个接收XML元信息加一个文件附件的服务方法。注意Axis2默认开启SwA识别,客户端也要用SwA模式发送,否则附件不会进Attachments集合。
代码中我们直接从AxisOperation的MessageContext拿到Attachments,用getAttachment方法按cid取流。相比手动解析MIME,这种方式更稳,也避免重复造轮子。但你要确保客户端设置的Content-ID与服务端引用的cid一致,否则getAttachment返回null。
import org.apache.axis2.context.MessageContext;
import javax.activation.DataHandler;
import java.io.InputStream;
import java.io.FileOutputStream;
public class SwaUploadService {
public String uploadXmlWithFile(String meta, DataHandler fileHandler) {
try {
// fileHandler由Axis2根据cid自动绑定
InputStream in = fileHandler.getInputStream();
FileOutputStream out = new FileOutputStream("/tmp/upload/" + System.currentTimeMillis() + ".bin");
byte[] buf = new byte[8192];
int len;
while ((len = in.read(buf)) != -1) {
out.write(buf, 0, len);
}
out.close();
in.close();
return "received meta=" + meta;
} catch (Exception e) {
return "error: " + e.getMessage();
}
}
}
手动解析MIME多部分的处理思路
如果不用Axis2这类框架,比如你在网关层用Netty或Servlet直接收请求,就得自己按boundary拆流。Servlet里可通过HttpServletRequest.getInputStream拿到原始体,再结合Content-Type头里的boundary做分割。切分后遍历每个部分,读取头部判断是否是根SOAP(看Content-ID是否匹配start参数),其余部分按Content-ID放进Map备用。
手动处理的优势是可控、无框架依赖;缺点是容易在boundary换行符、转义、嵌套多部分上出错。建议用javax.mail.internet.MimeMultipart来解析,它能正确处理标准MIME。下面代码展示用MimeMultipart读取SwA并提取XML与附件的基本流程。
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeBodyPart;
import javax.mail.Part;
import java.io.InputStream;
public void parseSwa(InputStream raw, String boundary) throws Exception {
MimeMultipart mp = new MimeMultipart(new javax.mail.util.ByteArrayDataSource(raw, "multipart/related; boundary=" + boundary));
for (int i = 0; i < mp.getCount(); i++) {
MimeBodyPart part = (MimeBodyPart) mp.getBodyPart(i);
String cid = part.getHeader("Content-ID") == null ? "" : part.getHeader("Content-ID")[0];
if (part.isMimeType("text/xml")) {
// 根SOAP信封
String soap = (String) part.getContent();
System.out.println("SOAP=" + soap);
} else {
// 附件
InputStream attach = part.getInputStream();
System.out.println("附件cid=" + cid + " 大小=" + attach.available());
}
}
}
SwA与Base64内联上传的对比
很多老系统把文件转Base64直接写进XML节点,这种做法实现简单,但体积膨胀约33%,且服务端必须把整段字符串读进内存解码,大文件极易触发OOM。SwA由于附件独立成MIME部分,XML只留cid引用,解析XML阶段根本不碰附件字节,内存曲线平稳。
| 方案 | 消息体积 | 内存占用 | 解析复杂度 |
|---|---|---|---|
| Base64内联 | 膨胀33% | 高 | 低 |
| SwA | 原始大小 | 低 | 中 |
| MTOM | 原始大小 | 低 | 中高 |
从表中可见,SwA在体积与内存上优于内联,而MTOM是更现代的替代(基于XOP),但很多遗留银行、政务系统仍只认SwA,所以掌握SwA处理依旧实用。选择时若对方系统指定SwA,就按本文方式落地;若你主导接口,可考虑MTOM减少自定义解析。
常见误区与排查建议
一个高频误区是认为SOAP里的cid引用会自动变成XML节点里的字节。实际上cid只是逻辑指针,必须服务端主动用附件API或MIME解析去取流。另一个坑是客户端没设对Content-Type,例如漏写multipart/related,导致服务端当普通text/xml处理,附件直接被忽略。
排查时建议先用tcpdump或Charles抓原始请求,确认boundary与各部分Content-ID;再在代码里打印MimeMultipart的count与每个part的content-type。只要根SOAP能解析且cid对得上,XML上传加附件的处理就走通了。
SOAP_with_AttachmentsXML上传MIME封装修改时间:2026-08-09 23:36:42