记录我开发工作中遇到HTTP跨域和OPTION请求的一个坑
我通过这篇文章把今天工作中遇到的HTTP跨域和OPTION请求的一个坑记录下来。
场景是我需要在部署在域名a的Web应用里用JavaScript去消费一个部署在域名b的服务器上的服务。域名b上的服务也是我开发的,因此我将域名a加到了该服务的HTTP响应结构的头文件里,这样就允许了域名a上的JavaScript代码用AJAX访问域名b的服务。
域名b上的服务是一个Servlet,允许域名a跨域访问的代码就一行:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 做业务逻辑
response.setHeader("Access-Control-Allow-Origin", "域名a");
}
我在域名a的Web应用里用AJAX发起服务请求:
执行后,发现并没有显示200的弹出窗口。
错误消息:Request header field Authorization is not allowed by Access-Control-Allow-Headers in preflight response.
观察Chrome开发者工具,发现其实域名b的服务已经成功执行了,确实返回了200的Status code,
而且我已经从Chrome开发者工具里观察到浏览器已经成功接到域名b发送回来的请求了。
那这个错误是什么鬼呢?根据错误消息“Request header field Authorization is not allowed by Access-Control-Allow-Headers in preflight response” Google了一下,发现一些朋友遇到同样的问题:
- 如何解决出现AXIOS的Request header field Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.
网页地址: https://www.cnblogs.com/caimuqing/p/6733405.html
这位朋友的解决方案:
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "*");
response.setHeader("Access-Control-Allow-Headers", "Content-Type,Access-Token");
response.setHeader("Access-Control-Expose-Headers", "*");
if (request.getMethod().equals("OPTIONS")) {
HttpUtil.setResponse(response, HttpStatus.OK.value(), null);
return;
}
但我试过,在我的场景下还是不工作,因为我的例子里,服务器已经针对OPTIONS请求返回HTTP 200的状态码了。
2. 这个Stackoverflow的帖子里,很多朋友都提供了自己的解决方案。
我一一试过,在我的场景里都不能工作。
于是我查询了Mozilla的一篇文档:HTTP访问控制(CORS)
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS
里面谈到了,在某些情况下,浏览器在发起“需要预检的请求”之前,必须首先发起一个“预检请求(Preflight)”到服务器,以探测服务器是否允许这个实际请求。"预检请求"机制的使用,是为了避免跨域请求对服务器的用户数据产生未预期的影响。
那么哪些请求算作“需要预检的请求”呢?Mozilla的这篇文档定义得很清楚:
当请求满足下述任一条件时,即应首先发送预检请求:
- 使用了下面任一 HTTP 方法:
- PUT
- DELETE
- CONNECT
- OPTIONS
- TRACE
- PATCH
- 人为设置了对 CORS 安全的首部字段集合之外的其他首部字段。该集合为:
- Accept
- Accept-Language
- Content-Language
- Content-Type (but note the additional requirements below)
- DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
- Content-Type 的值不属于下列之一:
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
我再检查我的代码,因为我在HTTP请求里用xhr.setRequestHeader(“Authorization”, “用户名:密码的base64编码” )添加了用于Basic Authentication的头部,因此迫使该请求成为了“需要预检的请求”,所以才有了OPTION请求的发送。
现在我将其注释掉:
这次遇到了401 Unauthorized错误了:
然而没有预检请求OPTION发出来了,请求类型变成了我期望的POST方式了。
但是现在就陷入了一个矛盾的境地:如果在请求头部加上Basic Authentication的信息,会遇到错误消息“Request header field Authorization is not allowed by Access-Control-Allow-Headers in preflight response.”。如果去掉,虽然避免了预检请求,但是又遇到401 Unauthorized错误了。
于是,我换了一种认证方式,终于成功实现了期望的跨域请求,在我域名a的前端应用里打印出了来自于域名b的服务的响应。
我使用了form认证方式,这种方式不会造成该请求成为一个”需要预检的请求“,所以最后跨域成功了。
var formData = new FormData();
formData.append('sap-client', "001");
formData.append('sap-user', "用户名");
formData.append('sap-password', "用户密码");
var request = new XMLHttpRequest();
request.open("POST", "域名b的url",false);
request.send(formData);
alert("response: " + request.responseText);
希望我的这个踩坑经历对大家有点帮助。
我们平时阅读一些开源项目,可能会发现有些项目的package.json里的scripts区域定义的脚本很复杂,令人眼花缭乱。
其实这些脚本是有规律可循的。让我们从最简单的一个例子开始学习。
新建一个空文件夹,执行命令npm init,会自动在该文件夹下生成一个package.json。
这个init其实是一个向导,会针对package.json里待生成的每一个字段询问您想填什么值。一路回车,使用默认值即可。
自动生成的package.json内容如下。
我们现在对这个package.json进行少量修改,以此来学习scripts的生命周期管理(lifecycle management)。
{
"name": "nodejsexample",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "node example.js",
"dev": "node example.js",
"start": "node example.js",
"prestart": "node example.js",
"poststart": "node example.js"
},
"author": "",
"license": "ISC"
}
重点看第9行~第11行,意思就是当npm执行命令时,如果执行到名为start的生命周期事件(lifecycle event),则执行nodejs的命令node example.js,如果执行到名为prestart的生命周期事件, 则执行nodejs命令node example.js,对于poststart也同理。
然后我们再来编写example.js。为了简单起见,用example.js同时响应prestart, start和poststart三个事件,因此在example.js里需要判断当该文件执行时,到底所处哪个生命周期事件。这个可以用环境变量process.env.npm_lifecycle_event获得:
const ENVIRONMENT = process.env.npm_lifecycle_event;
if (ENVIRONMENT === "build") {
console.log("Running your build tasks!");
}
if ( ENVIRONMENT === "dev") {
console.log("Running the dev server!");同
}
if ( ENVIRONMENT === "prestart") {
console.log("Prestart event: Prepare for Start event!");
}
if ( ENVIRONMENT === "poststart") {
console.log("Poststart event: Do some cleanup task!");
}
if ( ENVIRONMENT === "start") {
console.log("Running App in production!");
}
现在执行命令行npm start,可以看到依次执行了事件prestart->start->poststart对应的script。
package.json里所有支持写script的事件在这个链接里详细定义了:
https://docs.npmjs.com/misc/scripts
要获取更多Jerry的原创技术文章,请关注公众号"汪子熙"。
- 点赞
- 收藏
- 关注作者
评论(0)