网页端js直接调用webservice接口,ie浏览器正常,谷歌和微软edge都无法正常请求,问题如下:
十月 27, 2020 2:38:57 下午 com.sun.xml.internal.ws.transport.http.server.WSHttpHandler handleExchange
警告: 无法处理 HTTP 方法: OPTIONS
测试用js如下:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script>
function RequestWebService() {
var data = '<?xml version="1.0" encoding="utf-8"?>'//SOAP 1.1 请求报文格式,1.2在网上可以找到
+ '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">'
+ '<soap:Body>'
+ '<dny xmlns="http://test/">'//这里就是发布的方法名和xml文档中的命名空间地址
+ '</dny>'
+ '</soap:Body>'
+ '</soap:Envelope>';
alert(data);//SOAP请求报文格式
var request = new XMLHttpRequest();//第一步:创建需要的对象
request.onreadystatechange = function() {
if (request.readyState == 4){
var text = request.responseText;
alert('结果'+'\n'+text); //SOAP响应报文格式
document.getElementById("data").innerHTML = text;
}
};
//第二步:打开连接/***发送XML格式文件必须设置请求头 ;如下 - */
request.open('POST', "http://127.0.0.1:51072/test?wsdl", true);
//SOAP 1.1为text/xml ; 1.2为 application/soap+xml,这里修改了content-Type,所以是非简单请求
request.setRequestHeader("Content-Type","text/xml;charset=utf-8");
request.send(data);
}
</script>
<input type="button" value="test" onclick="RequestWebService()"></input>
<p id="data"></p>
</body>
</html>
原来是浏览器发送非简单请求时,会先发送一个OPTIONS请求,再来一个POST请求(相关资料网上很多,不再赘述)。想通过代码实现取消OPTIONS请求是不可能的,这触及到了我的知识盲区,所以还是得在后台处理,思路就是拦截该请求。
我一开始实现webservice用的是完全轻量化的框架(其实就是没有用框架,无jar引用,jdk自带的方法),开始尝试如下方式:
Endpoint endpoint = Endpoint.create(new Dny());
@SuppressWarnings("rawtypes")
List<Handler> handler = new ArrayList<>();
handler.add(new SOAPHandler<SOAPMessageContext>() {
@Override
public boolean handleMessage(SOAPMessageContext context) {
System.out.println(context);
return true;
}
@Override
public boolean handleFault(SOAPMessageContext context) {
System.out.println(context);
return false;
}
@Override
public void close(MessageContext context) {
System.out.println(context);
}
@Override
public Set<QName> getHeaders() {
return null;
}
});
endpoint.getBinding().setHandlerChain(handler);
endpoint.publish("http://127.0.0.1:51072/test");
但是很可惜,这里定义的handler是在程序处理完网络传输数据后才执行的,而在处理数据阶段,也就是在com.sun.xml.internal.ws.transport.http.server.WSHttpHandler源码里,handleExchange方法已经判断请求为OPTIONS时会警告并中止,后边根本不会执行。
终究还是用上了cxf,先上最终解决方案:
-------------------------------------------------------
↓↓↓↓↓↓↓↓↓↓↓最终方案↓↓↓↓↓↓↓↓↓↓↓
public class TestInterceptor extends AbstractPhaseInterceptor<SoapMessage> {
public TestInterceptor() {
super(Phase.RECEIVE);//父类没有无参构造函数,定义拦截阶段,这里意思为收到数据即执行
}
@Override
public void handleMessage(SoapMessage message) throws Fault {
System.out.println(message);
Response response = (Response) message.get(AbstractHTTPDestination.HTTP_RESPONSE);
response.setHeader("Access-Control-Allow-Origin", "*");
if ("OPTIONS".equals(message.get(SoapMessage.HTTP_REQUEST_METHOD))) {
response.setHeader("Access-Control-Allow-Headers",
((Request) message.get(AbstractHTTPDestination.HTTP_REQUEST))
.getHeader("Access-Control-Request-Headers"));
message.getInterceptorChain().doInterceptStartingAfter(message, null);
}
}
}
public static void main(String[] args) {
ServerFactoryBean bean = new ServerFactoryBean();
bean.setAddress("http://127.0.0.1:51072/test");
bean.setServiceBean(new Dny());
bean.getInInterceptors().add(new TestInterceptor());
bean.create();
}
↑↑↑↑↑↑↑↑↑↑↑最终方案↑↑↑↑↑↑↑↑↑↑↑
-------------------------------------------------------
官网下载的cxf的jar包巨多,很多都是不必要的。上图就是程序中所必需的所有jar,多一个多余,少一个报错。测试用的js还是最上面那个,结果正常。接下来说下思路:
首先当然是判断OPTIONS之前拦截,所以是super(Phase.RECEIVE),单拦截还不行,还得告诉前端你可以继续发送POST,所以是
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Headers",
((HttpServletRequest) message.get(AbstractHTTPDestination.HTTP_REQUEST))
.getHeader("Access-Control-Request-Headers"));
这两个头部缺一不可,否则前端就不往下发POST了。
到这里呢message还是OPTIONS的,如果不做任何操作,执行到后边还是会报错,所以加上这句message.getInterceptorChain().doInterceptStartingAfter(message, null),实现类和方法是org.apache.cxf.phase.PhaseInterceptorChain.doInterceptStartingAfter,第二个参数为startingAfterInterceptorID,是拦截器id,包括默认拦截器在内所有拦截器都有一个id,我上边定义的拦截器没有自定义id,所以默认id就是TestInterceptor.class.getName()。结合方法名意思就是从传参id的拦截器之后开始执行。看源代码可以知道如果找不到该id的拦截器,那就是跳过该拦截链所有拦截器,所以我传参为null,跳过后直接进入返回阶段。
经过上边设置,OPTIONS的信息交互已经能顺利完成,接下来前端会发送POST,后台接收到POST执行业务逻辑后返回,原本呢response.setHeader("Access-Control-Allow-Origin", "*");这一句也在if里的,这样的话涉及到跨域问题,后台返回的POST结果浏览器会拒绝接收,浏览器不会接收未知域发送过来的数据,所以上边这句放到了if外边对所有请求添加允许的域,*通配符代表所有域。
完结撒花。
-----------
2020.10.28更新
发现一点小...知识?(词汇匮乏了)
cxf不会对前端请求头Content-Type进行校验,也就是说前端没有这句也是request.setRequestHeader("Content-Type","text/xml;charset=utf-8");可以的。
原先使用jdk实现webservice时,jdk自带的方法是会校验Content-Type,非text/xml会直接报错,只能使用非简单请求。
而cxf不校验Content-Type的话,那么前端就可以改为简单请求,进而不会发送OPTIONS,按理说也不用处理OPTIONS了,只需要解决跨域问题就行。
但是我认为先前的工作并不是无用功,OPTIONS也要特殊处理,因为你并不知道前端客户就一定会发送简单请求,而且前端发送的数据是xml格式,那么请求头设置成Content-Type=text/xml是很合情理的。