目标
使用 Hapi 框架在实例 CORS 场景下测试首部字段作用。这里不需要你掌握 Hapi 框架的使用,以及任何 Node 知识。
准备工作
你需要懂得哪方面的知识?
- 会使用
XMLHttpRequest
(Ajax) /Fetch
发起 HTTP 请求 - 基本的 HTTP 知识
- 理解基本的 CORS 知识
下面我们会对照这 MDN 上 CORS 部分的讲解(下面简称 讲解
),使用具体代码来测试首部字段的作用。
代码部分
主体结构我们按照 Hapi 官网的示例,修改路由部分。
const Hapi = require('@hapi/hapi')
const init = async () => {
const server = Hapi.server({
port: 3000,
host: 'localhost'
})
server.route({
method: 'GET',
path: '/',
handler: (request, h) => {
console.log(request.info)
console.log(request.headers)
return {
code: 200,
data: {
success: true
}
}
}
})
await server.start()
console.log('Server running on %s', server.info.uri)
}
process.on('unhandledRejection', err => {
console.log(err)
process.exit(1)
})
init()
复制代码
这样我们就有了一个本地 3000 端口的服务,并有一个 /
路径的 API。下面我们使用 Fetch
发起一个跨域请求,使用 Chrome 打开任意网站,打开 开发者工具
,在 Console
下进行测试。
fetch('http://localhost:3000')
.then(function(response) {
return response.json();
})
.then(function(myJson) {
console.log(myJson);
});
复制代码
收到以下错误:
Access to fetch at 'http://localhost:3000/' from origin 'developer.mozilla.org' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
直接访问 http://localhost:3000
是可以看到结果的。说明浏览器限制从脚本内发起的跨源HTTP请求。
下面增加允许跨域源的字段
path: '/',
+++ options: {
+++ cors: {
+++ origin: ['*']
+++ }
+++ },
复制代码
以上代码等同于在 response headers 中增加 access-control-allow-origin
字段为 *
,允许任何源的跨域请求。
再次访问,成功获取到内容。对照 MDN 讲解并查看 Chrome Network
面板中 headers
部分和服务日志。
简单请求 与 预检请求
对照讲解理解 “简单请求” 和 ”预检请求“ 的区别。
下面发起一个 POST JSON 的请求,并在服务端接收。
--- method: ['GET']
+++ method: ['GET', 'POST'],
// 输出请求体
+++ console.log(request.payload)
复制代码
fetch('http://localhost:3000', {
method: 'POST',
body: JSON.stringify({'user': 'kenny'}),
headers: new Headers({
'Content-Type': 'application/json'
})
})
复制代码
在 Network
中会看到有 2 次请求发起,因为我们修改了除规定以外的首部字段,所以首先发起了一个 options
的预检请求。
OPTIONS 是 HTTP/1.1 协议中定义的方法,用以从服务器获取更多信息。该方法不会对服务器资源产生影响。
同样我们查看 Network
中的 headers 部分进行对比和理解。
带 Cookie 的跨域请求
Fetch 与 CORS 的一个有趣的特性是,可以基于 HTTP cookies 和 HTTP 认证信息发送身份凭证。一般而言,对于跨域 XMLHttpRequest 或 Fetch 请求,浏览器不会发送身份凭证信息。如果要发送凭证信息,需要设置 XMLHttpRequest 的某个特殊标志位。
首先增加一个 domain 为 localhost 的 cookie 在测试域下。然后增加下面代码。
// 携带凭证
fetch('http://localhost:3000', {
credentials: 'include'
})
复制代码
origin: ['*']
+++ credentials: true
// 输出 cookie
+++ console.log(request.state)
复制代码
执行后会发现还是跨域错误。这是因为对于附带身份凭证的请求,服务器不得设置 Access-Control-Allow-Origin 的值为“*”。
所以修改 origin
--- origin: ['*'],
+++ origin: ['https://developer.mozilla.org'],
复制代码
现在执行,可以在日志中看到刚刚增加的 cookie。
反过来,我们在服务端设置 cookie,看前端能否生效。
h.state('user', 'kenny', {
isSecure: false,
isHttpOnly: false,
isSameSite: 'false',
domain: 'localhost'
})
复制代码
同样可行。
携带额外的首部信息 和 获取前端的首部信息
Access-Control-Allow-Headers
其指明了实际请求中允许携带的首部字段。
Access-Control-Expose-Headers
:
在跨域访问时,XMLHttpRequest对象的getResponseHeader()方法只能拿到一些最基本的响应头,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,如果要访问其他头,则需要服务器设置本响应头。
首先我们获取服务器时间
先删除多余代码
--- console.log(request.payload)
--- console.log(request.state)
--- h.state('user', 'kenny', {
--- isSecure: false,
--- isHttpOnly: false,
--- isSameSite: 'false',
--- domain: 'localhost'
--- })
复制代码
fetch('http://localhost:3000')
.then(function(response) {
console.log(response.headers.get('Date'))
})
// null
复制代码
后端增加代码,允许获取额外的头部。
+++ additionalExposedHeaders: ['Date']
复制代码
再次测试后显示接口获取时服务器的时间。
现在前端发送一个自定义的头(Region) 代表当前的地理位置(北京: 52),然后使用后端获取它。
fetch('http://localhost:3000', {
headers: new Headers({
'Region': 52
})
})
复制代码
+++ additionalHeaders: ['Region']
复制代码
可以从后端日志中看到 headers 含有 Region
字段
关于 Hapi 框架的 CORS 设置,可以参考:Hapi route cors
全部代码:
const Hapi = require('@hapi/hapi')
const init = async () => {
const server = Hapi.server({
port: 3000,
host: 'localhost'
})
server.route({
method: ['GET', 'POST'],
path: '/',
options: {
cors: {
origin: ['https://*.mozilla.org'],
credentials: true,
additionalExposedHeaders: ['Date'],
additionalHeaders: ['Region']
}
},
handler: (request, h) => {
console.log(request.info)
console.log(request.headers)
console.log(request.payload)
console.log(request.state)
// 设置 cookie
// h.state('user', 'kenny', {
// isSecure: false,
// isHttpOnly: false,
// isSameSite: 'false',
// domain: 'localhost'
// })
return {
code: 200,
data: {
success: true
}
}
}
})
await server.start()
console.log('Server running on %s', server.info.uri)
}
process.on('unhandledRejection', err => {
console.log(err)
process.exit(1)
})
init()
复制代码
结语
跨域问题其实并不复杂,网上教程也非常多,其实对于同源策略和跨域的概念,只要阅读 MDN 就可以了,自己动手创建一个服务器,对照调试工具和后端日志,查看 HTTP 请求和响应,加深理解。希望这个实例教程能帮助大家理解前端跨域 和 Hapi 框架的使用。
附上一个 整理和机翻的 Hapi 中文文档,觉得有用的小伙伴可以点点关注。