背景
现在有这么一个需求,我们的系统(M)作为各个系统信息交换中间平台。系统A和系统B需要交互,但是他们的网络是隔离的。系统B通过webservice发布了一些接口服务,然后提供WSDL给系统A调用,然而系统A是无法访问系统B的网络,所以数据先要发送中间系统M。其实也就是一个简单的服务转发代理的功能,只是涉及到soap协议的交互。
一种简单的方式
系统A使用soapui导入WSDL文件,获取每个方法的请求报文->提交M->原样提交到系统B->将系统B的处理结果返回
其中主要涉及到soap协议的一些报文头的设置,下边会有调用代码说明。
转发的实现
主要是在中间系统M注册filter来拦截webservice请求,然后将请求数据流构造一个新的http请求发送给系统B。这里请求的发送和转换采用httpcomponents实现。引入依赖:
1 | <dependency> |
filter代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106package cn.com.egova.easyshare.web.filter;
/**
* @auth gongxufan
* @Date 2017/9/20
**/
import org.apache.cxf.helpers.IOUtils;
import org.apache.http.Header;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
import java.util.ResourceBundle;
/**
* WSDL服务器请求转发
*/
public class WebServiceDispatchFilter implements Filter {
private static String WEBSERVICE_URL;
private static String WEBSERVICE_HOST;
static {
ResourceBundle bundler = ResourceBundle.getBundle("config");
// 获取调用接口的路径
WEBSERVICE_URL = bundler.getString("WEBSERVICE_URL");
WEBSERVICE_HOST = bundler.getString("WEBSERVICE_HOST");
}
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
try {
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(WEBSERVICE_URL);
//构造新的请求数据流
InputStreamEntity entity = new InputStreamEntity(
request.getInputStream());
httpPost.setEntity(entity);
//复制请求参数
for (Enumeration<String> e = request.getHeaderNames(); e
.hasMoreElements(); ) {
String name = e.nextElement().toString();
//注意这里不要设置,发送请求会抛异常
if ("Content-Length".equalsIgnoreCase(name)) {
continue;
}
httpPost.setHeader(name, request.getHeader(name));
}
//host参数需要覆盖,设置为webservice服务提供这的主机
httpPost.setHeader("host", WEBSERVICE_HOST);
//提交soap请求
CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
//复制soap相应的消息头
for (Header h : httpResponse.getAllHeaders()) {
response.setHeader(h.getName(), h.getValue());
}
//讲soap的返回流复制到filter的输出流
IOUtils.copyAndCloseInput(httpResponse.getEntity().getContent(),
response.getOutputStream());
} catch (Exception e) {
response.setContentType("text/xml;charset=UTF-8");
String result = "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"
+ "<soap:Body><soap:Fault><faultcode>"
+ "9999"
+ "</faultcode>"
+ "<faultstring>"
+ "失败:"
+ e.getMessage()
+ "</faultstring></soap:Fault></soap:Body></soap:Envelope>";
response.getWriter().print(result);
}
}
public void init(FilterConfig filterConfig) throws ServletException {
}
public void destroy() {
}
}
代码实现也很简单:1
2CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(WEBSERVICE_URL);
这里构造了一个新的httpPost对象,然后读取请求流构造InputStreamEntity,接着设置httpPost的header。执行CloseableHttpResponse httpResponse = httpClient.execute(httpPost);这样就获取了接口调用的结果最后将httpResponse原样输出到系统A的调用者。
为了测试方便,我们使用天气预报的webservice接口进行测试,上面代码的主机和服务地址是写在配置文件的,其内容如下:
WEBSERVICE_HOST=www.webxml.com.cn
WEBSERVICE_URL=http://www.webxml.com.cn/WebServices/WeatherWebService.asmx?wsdl
配置filter拦截:1
2
3
4
5
6
7
8<filter>
<filter-name>WsTransFilter</filter-name>
<filter-class>cn.com.egova.easyshare.web.filter.WebServiceDispatchFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>WsTransFilter</filter-name>
<url-pattern>/webservice/*</url-pattern>
</filter-mapping>
使用soapUI进行接口测试
打开soapUI,新建一个soap工程,并导入wsdl文件,这样就可以测试接口了.我们测试下getSupportCity这个方法,只要把地址栏的地址替换成filter的访问地址即可:http://localhost:8080/easyshare-web/
使用HttpClient测试接口
只要按照soapUI测试接口的头部信息发送请求报文,就可以成功调用接口。因此我们的客户端调用代码按照这个模式就可以执行接口的调用了,只是要注意我们调用的目标是转发服务的地址。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33package cn.com.egova.easyshare.test.web;
import cn.com.egova.easyshare.web.utils.HttpUtils;
import java.util.ResourceBundle;
/**
* @auth gongxufan
* @Date 2017/9/27
**/
public class SoapInvokeTest {
public static void main(String[] arg) {
//soap测试
String postUrl = "http://localhost:8080/easyshare-web/webservice";
//采用SOAP1.1调用服务端,这种方式能调用服务端为soap1.1和soap1.2的服务
String orderSoapXml = "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:web=\"http://WebXml.com.cn/\">\n" +
" <soapenv:Header/>\n" +
" <soapenv:Body>\n" +
" <web:getSupportCity>\n" +
" <!--Optional:-->\n" +
" <web:byProvinceName>云南</web:byProvinceName>\n" +
" </web:getSupportCity>\n" +
" </soapenv:Body>\n" +
"</soapenv:Envelope>";
String soap1_1 = HttpUtils.doPostSoap1_1(postUrl, orderSoapXml, "http://WebXml.com.cn/getSupportCity");
System.out.println(soap1_1);
//soap1.2没有soapAction头,直接设置在content-type
String soap1_2 = HttpUtils.doPostSoap1_2(postUrl, orderSoapXml, "http://WebXml.com.cn/getSupportCity");
System.out.println(soap1_2);
System.out.println("end");
}
}
1 | public static String doPostSoap1_1(String postUrl, String soapXml, |
上面1.1和1.2方法的区别就是头部设置的差异,这个是按照soapUI调用时自动生成的header来设置的。
end
本文实现的方法就是按照原始的soapUI调用,主要导入WSDL文件根据其定义的方法,将请求报文和请求头发送到代理服务器然后在转发到webservice服务方即可实现调用的转发。
其实我们还可以采用apache camel进行服务的转发,不过camel适用于较复杂的规则路由场景。其具体使用也很方便,只要配置好路由规则即可实现的服务的路由和转发,具体可参考其文档。