【解决问题】soap webService通过拦截器修改请求报文和响应报文
Spring boot如何开发CXF 框架的Webservice服务,参考上篇《Springboot开发WebService服务端和客户端》
做这个webService服务是因为甲方项目是集成平台的,要求我们开发webService服务端接收他们统一推送的信息进行同步数据,现在的情况是,集成平台要求服务端的请求报文和响应报文必须按照他们的格式来。
修改请求报文
这是我的请求报文:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://server.dandelion.com">
<soapenv:Header/>
<soapenv:Body>
<ser:getData>
<!--Optional:-->
<action>同步用户信息</action>
<!--Optional:-->
<msg>
<![CDATA[
<user>
<id>100123322</id>
<name>蒲公英不是梦</name>
</user>
]]>
</msg>
</ser:getData>
</soapenv:Body>
</soapenv:Envelope>
这是他们要求的请求报文:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header/>
<soapenv:Body>
<esb:getData xmlns:esb="mdm.standard.com">
<!--Optional:-->
<action>同步用户信息</action>
<!--Optional:-->
<msg>
<![CDATA[
<user>
<id>100123322</id>
<name>蒲公英不是梦</name>
</user>
]]>
</msg>
</esb:getData>
</soapenv:Body>
</soapenv:Envelope>
区别在于:
- 去掉了根节点的命名空间信息
- 方法名增加了命名空间并修改前缀ser为esb
- 命名空间由“http://server.dandelion.com”修改为“mdm.standard.com”
其中修改命名空间简单,在webService接口那里修改注解信息即可,问题在于如何把他从根节点取消掉,然后显示在方法名上,并且修改前缀。
在网上找了一些答案,大部分是客户端自定义请求报文,或者内容不全,或者webservice服务端是spring框架开发的,通过修改自定义的xml文件来实现。
我尝试了几种方法最终都不能修改到请求的模版格式,本着情况急任务紧的事实,我就利用webservice的拦截器在请求未执行之前获取请求的消息体,暴力修改内容达到目的。
思路是这样的:
通过SoapUI看到的客户端报文,仍然是原来的请求报文,但是允许集成平台按照他们的格式修改请求报文。发送请求时,拦截器进行拦截,将请求报文修改为webService服务端原来的请求报文,在执行任务。
CXF框架提供了AbstractPhaseInterceptor抽象类,通过定义phase(阶段)来拦截不同阶段的请求。
请求拦截器
import org.apache.commons.lang3.StringUtils;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.helpers.IOUtils;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
public class WsInInterceptor extends AbstractPhaseInterceptor<SoapMessage> {
public WsInInterceptor(String phase) {
super(phase);
}
@Override
public void handleMessage(SoapMessage message) throws Fault {
try {
// 从流中获取请求消息体并以字符串形式输出,注意IOUtils是cxf的包;
String input = IOUtils.toString(message.getContent(InputStream.class), "UTF-8");
// 如果内容不为空(第一次连接也会被拦截,此时input为空)
if (StringUtils.isNotBlank(input)){
// 修改请求消息体为webservice服务要求的格式
input = input.replace("<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\">","<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:ser=\"http://server.dandelion.com\">")
.replace("<esb:getData xmlns:esb=\"mdm.stardand.com\">","<ser:getData>")
.replace("</esb:getData>", "</ser:getData>");
}
// 重新写入
message.setContent(InputStream.class, new ByteArrayInputStream(input.getBytes()));
} catch (Exception e) {
System.out.println(String.format("解析报文异常: %s", e.getMessage()));
}
}
}
创建WsInInterceptor类继承AbstractPhaseInterceptor抽象类,需要创建构造方法传入phase参数并重写handleMessage方法。
首先通过message.getContent(InputStream.class)从流中获取请求报文。
我这边构造参数传入的是Phase.RECEIVE,所以通过SoapUI连接webService服务端时也会拦截请求,只不过请求报文为空字符串,所以需要进行判空。
将根节点的命名空间去掉
将方法名的命名空间去掉并修改前缀为原来的
然后重新写入
注入拦截器
在发布接口的配置文件中注入拦截器,并设置phase为receive阶段。
通过SoapUI测试效果:
打印效果:
修改响应报文
这是我的响应报文:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:getDataResponse xmlns:ns2="http://server.dandelion.com">
<ns2:return>
<![CDATA[
<root>
<code>1</code>
<msg>同步成功</msg>
</root>
]]>
</ns2:return>
</ns2:getDataResponse>
</soap:Body>
</soap:Envelope>
这是他们要求的响应报文:
<soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope" xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<soapenv:Body>
<return>
<![CDATA[
<root>
<code>1</code>
<msg>同步成功</msg>
</root>
]]>
</return>
</soapenv:Body>
</soapenv:Envelope>
同样是利用webservice的拦截器在结果未返回之前进行拦截,暴力修改响应报文达到目的。
响应拦截器
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.helpers.IOUtils;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.io.CachedOutputStream;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class WsOutInterceptor extends AbstractPhaseInterceptor<SoapMessage> {
public WsOutInterceptor(String phase) {
super(phase);
}
@Override
public void handleMessage(SoapMessage message) throws Fault {
try {
// 从流中获取返回内容
OutputStream os = message.getContent(OutputStream.class);
CachedStream cs = new CachedStream();
message.setContent(OutputStream.class, cs);
message.getInterceptorChain().doIntercept(message);
CachedOutputStream cachedOutputStream = (CachedOutputStream) message.getContent(OutputStream.class);
InputStream in = cachedOutputStream.getInputStream();
String output = IOUtils.toString(in, "UTF-8");
// 修改内容为集成平台要求的格式
output = output.replace("<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">","<soapenv:Envelope xmlns:soapenv=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\">")
.replace("</soap:Envelope>", "</soapenv:Envelope>")
.replace("<soap:Body>", "<soapenv:Body>")
.replace("</soap:Body>", "</soapenv:Body>")
.replace("<ns2:getDataResponse xmlns:ns2=\"http://server.dandelion.com\">", "")
.replace("</ns2:getDataResponse>", "");
// 处理完后写回流中
IOUtils.copy(new ByteArrayInputStream(output.getBytes()), os);
cs.close();
os.flush();
message.setContent(OutputStream.class, os);
} catch (Exception e) {
System.out.println(String.format("解析报文异常: %s", e.getMessage()));
}
}
private static class CachedStream extends CachedOutputStream {
public CachedStream() {
super();
}
@Override
protected void doFlush() throws IOException {
currentStream.flush();
}
@Override
protected void doClose() throws IOException {
}
@Override
protected void onWrite() throws IOException {
}
}
}
注入拦截器
在发布接口的配置文件中注入拦截器,并设置phase为pre_stream阶段。
通过SoapUI测试效果:
如果是按照响应结果中转义符转义失败,可能是接口的注解问题,化繁为简即可:
webservice接口:
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
@WebService(targetNamespace = "http://server.dandelion.com")
public interface TestService {
@WebMethod(operationName="getData")
String execute(@WebParam(name = "action") String action, @WebParam(name = "msg") String msg);
}
wbservice接口实现:
import javax.jws.WebService;
@WebService(targetNamespace = "http://server.dandelion.com",
endpointInterface = "com.dandelion.server.TestService")
public class TestServiceImpl implements TestService{
@Override
public String execute(String action, String msg) {
System.out.println(String.format("action: %s", action));
System.out.println(String.format("msg: %s", msg));
return "<root><code>1</code><msg>同步成功</msg></root>";
}
}
如果你有更好的解决方案,希望不吝赐教!!!
【参考博客】
CXF支持的Phase(阶段/工作时机):spring boot-实现WebService(CXF实现)的拦截器(Interceptor)
拦截去修改请求报文:CXF服务端改写请求报文