1. 程式人生 > 其它 >soap webService通過攔截器修改請求報文和響應報文

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>";
    }
}