前言
最近由于甲方那边想要一个在JAVA代码里面调用webservice实例的小demo,于是乎便接触到了这一块。主要是由于我们项目架了KONG网关并限定了请求方式的影响,导致平常的动态客户端方式无法成功调用webservice实例,因此便花了半天多时间研究了下webservice服务这一块,并利用soap协议通过http成功调用,虽然有局限性,但是至少提供了个思路。
问题
这里甲方写了两种方式进行接口调用,但是由于我们KONG网关限定了请求方式,导致怎么都调用不成功。
利用PostMan调用的时候,返回的是webservice的wsdl文档。
这里是动态调用的报错信息。
解决过程
一、创建webservice接口
这里主要是利用Apache提供的cxf包来模拟客户端调用webservice接口。
首先创建一个webservice接口端的工程
这是工程需要用到的依赖
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-bundle</artifactId>
<version>2.7.18</version>
</dependency>
</dependencies>
创建IWebService接口
package src;
import src.dao.Info;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
/**
* @author LinZS
* @description
* @date 2020/11/17 17:17
*/
@WebService
public interface IWebService
{
@WebMethod
public String excute(@WebParam(name="param") String s);
@WebMethod
public String testString(Info param);
}
创建IWebService实现类
package src.impl;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import src.IWebService;
import src.dao.Info;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
/**
* @author LinZS
* @description
* @date 2020/11/17 17:22
*/
@WebService
public class WebServiceImpl implements IWebService {
@WebMethod
@Override
public String excute(@WebParam(name="param") String param)
{
System.out.println(param);
Gson json = new Gson();//需要引入Gson包
Info info = json.fromJson(param, new TypeToken<Info>(){}.getType());
Integer id = info.getId();
System.out.println(id);
return "服务端成功接收消息";
}
@WebMethod
@Override
public String testString(Info param)
{
return "dddd";
}
}
创建Info实体类
package src.dao;
import lombok.Data;
/**
* @author LinZS
* @description
* @date 2020/11/17 17:55
*/
@Data
public class Info {
private Integer id;
private String key;
private String result;
}
创建服务端启动类
package src;
import org.apache.cxf.jaxws.JaxWsServerFactoryBean;
import src.impl.WebServiceImpl;
/**
* @author LinZS
* @description
* @date 2020/11/17 17:25
*/
public class Launcher {
public static void main(String[] args)
{
JaxWsServerFactoryBean factory = new JaxWsServerFactoryBean();
factory.setServiceClass(IWebService.class);
// 发布接口
factory.setAddress("http://localhost:9000/ws/" + IWebService.class.getSimpleName());
factory.setServiceBean(new WebServiceImpl());
factory.create();
}
}
至此,服务端接口就模拟完成了。
二、模拟客户端调用webservice
接下来让我们另起一个工程模拟客户端,来试验我们的接口能否使用。
maven依赖的引入是和服务端工程一样的。
并且同样创建一个Info实体类,和IWebService接口类,创建完成后,创建工程启动类。
package src;
import com.google.gson.Gson;
import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
import src.dao.Info;
import java.security.NoSuchAlgorithmException;
/**
* @author LinZS
* @description
* @date 2020/11/17 17:41
*/
public class TestImpl {
public static void main(String[] args) throws NoSuchAlgorithmException
{
Gson gson = new Gson();
Info info = new Info();
info.setId(123);
info.setResult("中国");
String s = gson.toJson(info);
//创建动态客户端
JaxWsProxyFactoryBean factoryBean=new JaxWsProxyFactoryBean();
/*factoryBean.getInInterceptors().add(new LoggingInInterceptor());
factoryBean.getOutInterceptors().add(new LoggingOutInterceptor()); */
factoryBean.setServiceClass(IWebService.class);
factoryBean.setAddress("http://localhost:9000/ws/IWebService?wsdl");
IWebService webService=(IWebService) factoryBean.create();
System.out.println(webService.excute(s));
}
}
创建完成之后,我们先启动接口类,再启动客户端类,如果没有问题的话,服务端打印如下:
客户端打印如下
至此,模拟客户端调用webservice接口的demo,就完成了。
三、复现甲方问题
由于我们工程是利用Kong网关做的资源转发,因此甲方在提供webservice实例的时候限定了GET请求,我在这就复现了一下。
结果很明显,和甲方一样的报错,因此我判断,应该是请求方式导致调用不成功。
因为我们的网关将资源转发出去后,适用的是HTTP请求,而webservice客户端用的是WSDL报文,两者的请求方式是不同的,而且我们Kong网关还加上了权限,需要在请求头的Authorization项带上Token,这也就间接导致了客户端方式直接调用不成功的问题。
四、改用Http请求调用webservice实例
首先在pom文件里添加上HttpClient需要的依赖
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.1</version>
</dependency>
然后在我们客户端的启动类里,改用以下方式调用webservice。
//返回体接收类
ResponseHandler<String> responseHandler = new ResponseHandler<String>() {
public String handleResponse(
final HttpResponse response) throws ClientProtocolException, IOException {
int status = response.getStatusLine().getStatusCode();
if (status >= 200 && status < 300) {
HttpEntity entity = response.getEntity();
if(null!=entity){
String result= EntityUtils.toString(entity);
return result;
}else{
return null;
}
} else {
throw new ClientProtocolException("Unexpected response status: " + status);
}
}
};
CloseableHttpClient httpclient = HttpClientBuilder.create().build();
//注意此处的ip要与webservice接口端的ip一致
HttpGet httpget = new HttpGet("http://192.168.124.9:8000/server/OvSR9h5V6w?WSDL");
httpget.setHeader("Authorization", "Basic djJ4YmRlbXBsb3lUb3pSVFZxVTokMmEkMTAkU0pweDE5Z1Y5TW9WdERCYnV2Nm5OTzRqTFFNOHZ5TjlsM3dKZ3k4NC55VmdWbDNXa1BvRmE=");
// 执行get请求.
String response = null;
try {
response = httpclient.execute(httpget,responseHandler);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("接收到的数据"+response);
改用http请求方式之后,我们没办法直接将想要发送的参数,传给服务端,并且由于限定了Get请求,服务端直接反馈了WSDL文档,无法正确调用。
在网上查阅了相关资料之后,发现大部分利用http对webservice的请求方式,都用的是POST,而非GET,我们这边GET请求只能获取WSDL报文的话,那我们将报文转成SOAP协议,问题不就迎刃而解了吗?
五、Http利用SOAP协议调用webservice
此时,我们已经有了WSDL文档,只要将它转成SOAP协议,我们就可以利用POST请求,成功调用被Kong网关转发的webservice实例了。
首先,我们需要它
这里附上下载链接
SoapUI 5.2.1下载地址
下载安装之后,右键project新建。
然后继续右键ADD WSDL
选择之后,会生成两个SOAP协议
因为我们刚才写的是excute请求方式,所以我们选择第一个。
客户端启动类代码改用如下:
Gson gson = new Gson();
Info info = new Info();
info.setId(123);
info.setResult("中国");
String s = gson.toJson(info);
String retStr = "";
//SOAP消息格式
String soapXml = "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:src=\"http://src/\">\n" +
" <soapenv:Header/>\n" +
" <soapenv:Body>\n" +
" <src:excute>\n" +
" <!--Optional:-->\n" +
" <param>"+s+"</param>\n" +
" </src:excute>\n" +
" </soapenv:Body>\n" +
"</soapenv:Envelope>";
CloseableHttpClient closeableHttpClient = HttpClientBuilder.create().build();
//创建httpPost
// HttpPost httpPost = new HttpPost("http://192.168.124.9:8000/server/OvSR9h5V6w?WSDL");
HttpPost httpPost = new HttpPost("http://192.168.124.9:8000/server/AQt0kTuF6r4?WSDL");
RequestConfig requestConfig = RequestConfig.custom()
.setSocketTimeout(30000)
.setConnectTimeout(30000).build();
httpPost.setConfig(requestConfig);
httpPost.setHeader("Authorization", "Basic djJ4YmRlbXBsb3lUb3pSVFZxVTokMmEkMTAkU0pweDE5Z1Y5TW9WdERCYnV2Nm5OTzRqTFFNOHZ5TjlsM3dKZ3k4NC55VmdWbDNXa1BvRmE=");
httpPost.setHeader("User-Agent",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.111 Safari/537.36");
httpPost.setHeader("Content-Type", "text/xml;charset=UTF-8");
httpPost.setHeader("SOAPAction", "");
try {
StringEntity data = new StringEntity(soapXml,
Charset.forName("UTF-8"));
httpPost.setEntity(data);
CloseableHttpResponse response = closeableHttpClient
.execute(httpPost);
HttpEntity httpEntity = response.getEntity();
if (httpEntity != null) {
// 打印响应内容
retStr = EntityUtils.toString(httpEntity, "UTF-8");
System.out.println("response:" + retStr);
}
// 释放资源
closeableHttpClient.close();
} catch (Exception e) {
e.printStackTrace();
}
改用SOAP协议的方式,我们成功利用Http请求调用了被Kong网关转发的webservice实例。
总结
甲方的demo要求解决了,虽然有局限性,需要事先得到WSDL文档,但是作为webservice实例的提供方,提供WSDL文档,或者说提供SOAP消息格式都是轻轻松松的事情,无伤大雅。
这次的问题解决,主要学到了以下几点:
1.客户端的请求方式和Http请求方式在请求头和请求体上的协议区别
2.HttpGet请求webservice实例由于无法将特定参数带过去,导致我们只能请求到WSDL文档。
3.在HttpPost请求里加上SOAP消息格式,并将所需参数放入消息格式中一起放入Entity里面,便可以成功调用webservice实例。