跨域--每个前端都该知道的事

646 阅读5分钟

跨域--通过代码一步一步了解其本质

跨域无论日常工作还是面试都会遇到。网上充斥着各种资料,但大多数资料都是重理论,没有实际的代码的支持,这篇文章从代码出发,轻理论重实践的讲解跨域。

我们会使用koa搭建一个本地服务器:http://127.0.0.1:3200。 使用webpack启动本地客户端:http://127.0.0.1:3000。 探究一下前后端分离项目怎么支持CORS(跨域资源共享)。完整代码我放到了git上,阅读readme获取正确姿势。文章的主要内容如下:

  • 跨域的基础知识
  • koa搭建服务器
  • kao中间件处理跨域

跨域的基础知识

关于跨域的基础知识,阮一峰老师有博客: 跨域资源共享 CORS 详解介绍得很清楚,接下来我通过图文简单介绍一些理论知识。

同源策略

image

跨域的反面就是同源。当协议、主机、端口相同的时候就是满足同源。

这次搭建的例子由于端口不同,所以客服端请求的时候我们就会遇到熟悉的错误。

Access to XMLHttpRequest at 'http://127.0.0.1:3000/user' from origin 'http://127.0.0.1:3200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

两种请求及CORS策略

了解了同源策略,我们聊聊请求。请求有复杂请求,也有简单请求。两种请求浏览器策略也不一样。一下列举了两种请求的一些例子:

  • 简单请求:请求方式为get/post
  • 复杂请求:请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json

简单请求的跨域策略如下图 image

  1. 客户端发送简单请求
  2. 请求头带上字段Origin表明来源
  3. 服务器收到请求,在响应头设置字段:Access-Control-Allow-Origin,用于告诉客服端是否可获取资源(*表示所有来源都可以)。
  4. 跨域资源共享完成

复杂请求的跨域策略如下图

image

  1. 客户端发起复杂请求
  2. 对于复杂请求首先发送一个预检请求,请求你式为options
  3. 服务器接受预检请求,设置Access-Control-Allow-Origin,Access-Control-Allow-Methods,Access-Control-Allow-Headers
  4. 三个字段告诉客户端:哪些来源允许跨域,哪些请求方式允许跨域,哪些请求头允许跨域。
  5. 客户端得到上诉信息,判断是否满足条件。满足条件,发起正式请求,跨域成功。

代码实现

了解了以上理论信息,下面我们进入实践环节。看看koa应该怎么写代码。

服务端

首先使用koa/koa-router搭建一个简单的服务器。

const Koa = require('koa')
const Router = require('koa-router')

const router = new Router() // 路由
const app = new Koa() //创建服务器实例


router.get('/name', async (ctx) => {
  ctx.body = {
    name: 'miujg',
    age: 26
  }
})

app.use(router.routes())
app.use(router.allowedMethods()) // 注册路由

app.listen(3000, ()=>{
  console.log('serve start on 3000')
})

简单的koa服务器,向外暴露接口。

客户端

我这里客户端是使用webpack、webpack-dev-server启动的一个服务。发起请求js代码如下

const axios = require("axios")

axios.get('http://127.0.0.1:3000/name').then(rs => {
  console.log(rs)
})

使用axios发起请求,你也可以使用原生ajax

实际效果如下:

image

简单请求中间件设置

由之前的理论知识我们可以知道,对于简单请求,如果服务器在响应头中设置Access-Control-Allow-Origin,就能达到效果。

这时我们就会想到koa的中间件,中间件的目的是在响应头上设置Access-Control-Allow-Origin字段

代码实现如下:

const Koa = require('koa')
const Router = require('koa-router')

const router = new Router()
const app = new Koa()

// 简单请求跨域处理
app.use(async (ctx, next) => {
  ctx.response.set('Access-Control-Allow-Origin', '*') // *标识允许所有来源,也可以设置数组
  next()
})

router.get('/name', async (ctx) => {
  ctx.body = {
    name: 'miujg'
  }
})

app.use(router.routes())
app.use(router.allowedMethods())

app.listen(3000, ()=>{
  console.log('serve start on 3000')
})

注意:koa中间件是有顺序的,跨域处理的那一个中间件,一定要在最前面。

请求效果如下:

image

image

可以发现响应头中设置了Access-Control-Allow-Origin,简单请求跨域资源共享成功。

复杂请求中间件设置

在以上代码的基础上,我们发起一个put请求。put为一个复杂请求

axios.put('http://127.0.0.1:3000/name').then(rs => {
  console.log(rs)
})

后端加一个put接口

router.put('/name', async (ctx) => {
  ctx.body = {
    name: 'miujg--put',
    age: 29
  }
})

实际情况如下:

image

image

以上测试环境为火狐浏览器,高版本谷歌浏览器做了优化会隐藏option请求

直接看报错:put方法不被允许,需要在options响应头中添加字段Access-Control-Allow-Methods。我们改造一下中间件:

app.use(async (ctx, next) => {
  ctx.response.set('Access-Control-Allow-Origin', '*')
  if(ctx.request.method == 'OPTIONS') {
    ctx.status = 204
    ctx.response.set('Access-Control-Allow-Methods', ['GET', 'PUT', 'POST', 'DELETE'])
    ctx.response.set('Access-Control-Allow-Headers', ['Content-Type', 'Accept'])
  }
  next()
})

我们在中间件中拦截下options请求,然后设置相应字段。

浏览器效果如下: image image

备注:不同的复杂请求,响应头设置的值也可能不一样。

到此我们从代码层面搞清楚怎么处理跨域了,在实际开发中,只需要引进koa-cors就行了。这里就不多介绍。

小结

要深刻理解跨域,首先要搞清楚理论知识,知道浏览器的跨域资源共享是一个怎么策略。然后本地模拟客户端和服务器,在火狐浏览器中看效果。我相信,有了编码这个过程,你能更深刻理解跨域。 最后在说一下简单请求(除开这些情况其他都是复杂请求):

  1. method为:get head post
  2. content-type: text/plain multipart/form-data application/xwww-form-urlencode

其他都是复杂请求