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