js调用webservice接口时后台无法处理OPTIONS请求的解决方法(警告: 无法处理 HTTP 方法: OPTIONS)

网页端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是很合情理的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值