背景:
在项目的真实开发场景中,对应不同第三方厂商的时候,会遇到通过WebService接口交互的场景,由于第三方可能会有部署Ngnix负载,导致tool.exe调用异常,SoapUI调用是成功的,Soapui是有通过解析WS的远程文件,生成对应请求头。
W3C组织对其的定义如下,它是一个软件系统,为了支持跨网络的机器间相互操作交互而设计。Web Service服务通常被定义为一组模块化的API,它们可以通过网络进行调用,来执行远程系统的请求服务。
简单的说:WebService即Web服务,它是一种跨编程语言和跨操作系统平台的远程调用技术。
Web Service = SOAP + HTTP + WSDL。其中,SOAP Simple Object Access Protocol)协议是web service的主体,它通过HTTP或者SMTP等应用层协议进行通讯,自身使用XML文件来描述程序的函数方法和参数信息,从而完成不同主机的异构系统间的计算服务处理。这里的WSDL(Web Services Description Language)web 服务描述语言也是一个XML文档,它通过HTTP向公众发布,公告客户端程序关于某个具体的 Web service服务的URL信息、方法的命名,参数,返回值等。
调用方式:
1. 通过动态客户端调用
cfx动态代理模式,不用生成本地WS代理类,通过反射调用WS的对应方法,传入对应参数,访问cxf-server-web项目下的webservice,通过测试jaxws-rt发布WebService web方式。
public class CxfDynamicClientOnJwsRtWeb {
private static final Logger log = LoggerFactory.getLogger(CxfClient.class);
private static final String JWS_RT_WSDL_URI = "http://localhost:8080/cxf_server_web/jws_services?wsdl";
public static void main(String[] args) throws Exception {
log.info("======CXF-WS Dynamic Client start!======");
JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance();
Client client = dcf.createClient(JWS_RT_WSDL_URI);
HTTPConduit conduit = (HTTPConduit)client.getConduit();
HTTPClientPolicy policy = new HTTPClientPolicy();
policy.setConnectionTimeout(10000);
policy.setAllowChunking(false);
policy.setReceiveTimeout(10000);
conduit.setClient(policy);
Object[] invokeResult = client.invoke("sum", 17,8);
log.info("=======sumResult:" + invokeResult[0]);
}
}
2. 通过动态客户端直接获取到QName调用WebService对应的操作,这种方式针对SIB和SEI的targetNamespace不相同,同时不再同一个包。
/**
* 根据WS请求获取数据
*
* @param wsUrl 接口地址
* @param wsMethod 接口方法
* @param params 请求参数
* @return
* @throws Exception
*/
public static String getDataByWs3(String wsUrl, String wsMethod, Object... params) throws Exception {
logger.info("调用WS请求:{},请求方法:{},请求参数:{}", new Object[]{wsUrl, wsMethod, params});
ResultModel resultModel;
String result = "";
try {
JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance();
Client client = dcf.createClient(wsUrl);
HTTPConduit conduit = (HTTPConduit) client.getConduit();
HTTPClientPolicy policy = new HTTPClientPolicy();
policy.setConnectionTimeout(10000);
policy.setAllowChunking(false);
policy.setReceiveTimeout(10000);
conduit.setClient(policy);
//获取操作对应的Qname
// QName operateQName = getOperateQName(client, wsMethod);
//如果Qname已知,可以通过如下方式,直接创建QName
QName operateQName = new QName("http://portal.webserver.jhip.goodwillcis.com/", "InputPara");
Object[] resultObj = client.invoke(operateQName, params);
logger.info("ws请求返回结果:{}", result);
// Object[] resultObj = WSRequestUtil.doRequest(wsUrl, wsMethod, params);
result = (String) resultObj[0];
} catch (Exception e) {
logger.info("HTTP请求异常地址{},{},{}", wsUrl, e, result);
throw ApiException.createBizEx("HTTP_ERROR_URL", wsUrl);
}
return result;
}
/**
* 针对SEI和SIB不在统一个包内的情况,先查找操作对应的Qname,
* client通过Qname调用对应操作
*
* @param client
* @param operation
* @return
*/
private static QName getOperateQName(Client client, String operation) {
Endpoint endpoint = client.getEndpoint();
QName opName = new QName(endpoint.getService().getName().getNamespaceURI(), operation);
BindingInfo bindingInfo = endpoint.getEndpointInfo().getBinding();
if (bindingInfo.getOperation(opName) == null) {
for (BindingOperationInfo operationInfo : bindingInfo.getOperations()) {
if (operation.equals(operationInfo.getName().getLocalPart())) {
opName = operationInfo.getName();
break;
}
}
}
logger.info("Operation:" + operation + ",namespaceURI:" + opName.getNamespaceURI());
return opName;
}
3、 HttpURLConnection调用方式
基于上面的两种方式调用还无果的情况,可以通过SoapUI获取对应的请求头报文信息,在代码里面动态拼接对应的请求头信息,进行调用相当于模拟各种Http请求。
最终是通过封装请求头参数的形式;
String tokenTem = "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:por=\"http://portal.webserver.jhip.goodwillcis.com/\">\n" +
" <soapenv:Header/>\n" +
" <soapenv:Body>\n" +
" <por:getUserDetailInfo>\n" +
" <!--Optional:-->\n" +
" <por:InputPara><![CDATA[\n" +
" <REQUEST>\n" +
"\t<SESSION_ID>{}</SESSION_ID>\n" +
"<SYSTEM_CODE>{}</SYSTEM_CODE>\n" +
"</REQUEST>\n" +
"]]></por:InputPara>\n" +
" </por:getUserDetailInfo>\n" +
" </soapenv:Body>\n" +
"</soapenv:Envelope>";
String dataByWs2 = SpringContextHolder.getBean(ApiUtils.class).getDataByJHWs(url, method, tokenXml);
protected final RestTemplate restTemplate = buildRestTemplate();
public static RestTemplate buildRestTemplate() {
RestTemplate restTemplate = new RestTemplate();
SimpleClientHttpRequestFactory repFac = getUnsafeClientHttpRequestFactory();
repFac.setConnectTimeout(60000);
repFac.setReadTimeout(60000);
restTemplate.setRequestFactory(repFac);
return restTemplate;
}
private static SimpleClientHttpRequestFactory getUnsafeClientHttpRequestFactory() {
TrustManager[] byPassTrustManagers = new TrustManager[]{new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
}};
final SSLContext sslContext;
try {
sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, byPassTrustManagers, new SecureRandom());
sslContext.getSocketFactory();
} catch (NoSuchAlgorithmException | KeyManagementException e) {
throw new RuntimeException(e);
}
return new SimpleClientHttpRequestFactory() {
@Override
protected void prepareConnection(HttpURLConnection connection,
String httpMethod) throws IOException {
super.prepareConnection(connection, httpMethod);
if (connection instanceof HttpsURLConnection) {
((HttpsURLConnection) connection).setHostnameVerifier(new SkipHostnameVerifier());
((HttpsURLConnection) connection).setSSLSocketFactory(
sslContext.getSocketFactory());
}
}
};
}
private static class SkipHostnameVerifier implements HostnameVerifier {
@Override
public boolean verify(String s, SSLSession sslSession) {
return true;
}
}
/**
* 根据WS请求获取数据
*
* @param wsUrl 接口地址
* @param wsMethod 接口方法
* @param body 请求参数
* @return
* @throws Exception
*/
public String getDataByJHWs(String wsUrl, String wsMethod, String body) throws Exception {
HttpHeaders headers = new HttpHeaders();
String response = "";
headers.set("Content-Type", "application/soap+xml;charset=UTF-8");
HttpEntity httpEntity = new HttpEntity<>(body, headers);
ResponseEntity<String> responseEntity = null;
try {
responseEntity = restTemplate.exchange(wsUrl, HttpMethod.POST, httpEntity, String.class);
response = responseEntity.getBody();
} catch(Exception e) {
logger.info("HTTP请求结果JSON解析异常地址{}", wsUrl, e.getMessage());
throw ApiException.createBizEx("HTTP_ERROR_URL", wsUrl, e.getMessage());
}
return response;
}
通过postman就可以进行接口的调用,但是需要配置请求头信息,请求头信息是从SoapUI拷贝出来的。
content-type:text/xml
请求体body,选择raw 以及通过XML(text/xml),进行数据调用
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:por="http://portal.webserver.jhip.goodwillcis.com/">
<soapenv:Header/>
<soapenv:Body>
<por:getUserDetailInfo>
<!--Optional:-->
<por:InputPara><![CDATA[
<REQUEST>
<SESSION_ID>b5c579f2-49c4-453e-b7a2-db4a00e6b125</SESSION_ID>
<SYSTEM_CODE>HIS_SET</SYSTEM_CODE>
</REQUEST>
]]></por:InputPara>
</por:getUserDetailInfo>
</soapenv:Body>
</soapenv:Envelope>