soap webService通过拦截器修改请求报文和响应报文

Posted 蒲公英不是梦

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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>";
    }
}

以上是关于soap webService通过拦截器修改请求报文和响应报文的主要内容,如果未能解决你的问题,请参考以下文章

CXF通过拦截器修改请求报文

webservice之拦截器

Node.js 使用 soap 模块请求 WebService 服务接口

采用WebService客户端调用WSDL/SOAP网络报错的解决办法

java使用POST发送soap报文请求webservice返回500错误解析

web service接口 wsdl和asmx有啥区别