不到50行代码搞懂跨域(CORS)

目标

使用 Hapi 框架在实例 CORS 场景下测试首部字段做用。这里不须要你掌握 Hapi 框架的使用,以及任何 Node 知识。前端

准备工做

你须要懂得哪方面的知识?git

  • 会使用 XMLHttpRequest(Ajax) / Fetch 发起 HTTP 请求
  • 基本的 HTTP 知识
  • 理解基本的 CORS 知识

下面咱们会对照这 MDN 上 CORS 部分的讲解(下面简称 讲解),使用具体代码来测试首部字段的做用。github

代码部分

主体结构咱们按照 Hapi 官网的示例,修改路由部分。json

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);
  });
复制代码

收到如下错误:api

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 字段为 *,容许任何源的跨域请求。cookie

再次访问,成功获取到内容。对照 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 中文文档,以为有用的小伙伴能够点点关注。

相关文章
相关标签/搜索