# 跨域相关

本文不是八股文知识点,不会介绍 jsonp、iframe 等跨域方式,只介绍在开发中最常见的几种跨域方式。

# Access-Control-Allow-Origin

最简单的方式:服务端配置 Access-Control-Allow-Origin: *,借此复习一下几种响应头。

预检请求 (opens new window) (opens new window)相关响应头

# proxy 代理

由于 webpack-dev-server 是一个本地开发服务器,所以我们的应用在开发阶段是独立运行在 localhost 的一个端口上,而后端服务又是运行在另外一个地址上。但是最终上线过后,我们的应用一般又会和后端服务部署到同源地址下。

那这样就会出现一个非常常见的问题:在实际生产环境中能够直接访问的 API,回到我们的开发环境后,再次访问这些 API 就会产生跨域请求问题。

可能有人会说,我们可以用跨域资源共享(CORS)解决这个问题。确实如此,如果我们请求的后端 API 支持 CORS,那这个问题就不成立了。但是并不是每种情况下服务端的 API 都支持 CORS。如果前后端应用是同源部署,也就是协议 / 域名 / 端口一致,那这种情况下,根本没必要开启 CORS,所以跨域请求的问题仍然是不可避免的。

那解决这种开发阶段跨域请求问题最好的办法,就是在开发服务器中配置一个后端 API 的代理服务,也就是把后端接口服务代理到本地的开发服务地址。

webpack-dev-server 就支持直接通过配置的方式,添加代理服务。接下来,我们来看一下它的具体用法。

比如我们假定 GitHub 的 API 就是我们应用的后端服务,那我们的目标就是将 GitHub API 代理到本地开发服务器中,我们可以先在浏览器中尝试访问其中的一个接口: https://api.github.com/users (opens new window)

devServer: {
    contentBase: './public',
    proxy: {
      '/api': {
        // http://localhost:8080/api/users -> https://api.github.com/api/users
        target: 'https://api.github.com',
        // http://localhost:8080/api/users -> https://api.github.com/users
        pathRewrite: {
          '^/api': ''
        },
        // 不能使用 localhost:8080 作为请求 GitHub 的主机名
        changeOrigin: true
      }
    }
  },

1

那此时我们请求 http://localhost:8080/api/users ,就相当于请求了 https://api.github.com/users

// 此时再写跨域请求,就可正常访问
// 虽然 GitHub 支持 CORS,但是不是每个服务端都应该支持。
// fetch('https://api.github.com/users')
fetch('/api/users')
  .then(res => res.json())
// 实际请求到http://localhost:8080/api/users

# Allow CORS 插件

chrome网上应用商店地址 (opens new window)

开发时只需开启插件即可,原理也是添加响应头 Access-Control-Allow-Origin

Easily add (Access-Control-Allow-Origin: *) rule to the response header.

但是注意这种方式不一定完全适用,且不用的时候注意关掉,因为访问正常网站的时候可能会报错。

# 接口的形式

项目中的接口一般分为两种写法,第一种是区分测试环境和线上环境:

测试环境地址:api.github-test.com/api/users
正式环境地址:api.github.com/api/users

另一种是使用相对路径的方式,只写 /api/users,不用区分测试和线上环境,这种方式一般使用上述的 devserver 开发的,测试环境可以直接跑在开发机上,线上部署后由于相对路径请求起来也没问题。

# 预检请求

只有跨域的时候才可能会发送预检请求,此时还需要判断请求是否为简单请求

简单请求:同时满足以下两大条件,

  • 条件1:使用下列方法之一:GET,HEAD (opens new window),POST
  • 条件2:Content-Type 的值仅限于下列三者之一:
  • text/plain
  • multipart/form-data
  • application/x-www-form-urlencoded

除了以上条件之外,则会发起预检请求。

# 预检请求与重定向

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS (opens new window)

并不是所有浏览器都支持预检请求的重定向。如果一个预检请求发生了重定向,一部分浏览器将报告错误(chrome 有问题,safari 没问题):

Access to XMLHttpRequest at 'http://xxx' (redirected from 'http://yyy') from origin 'http://zzz' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.

CORS 最初要求浏览器具有该行为,不过在后续的修订 (opens new window)中废弃了这一要求。但并非所有浏览器都实现了这一变更,而仍然表现出最初要求的行为。

在浏览器的实现跟上规范之前,有两种方式规避上述报错行为:

情景1:用户登录页面 http://aaa.com 后,请求 http://aaa.com/getuser 接口,后端判断未登录,于是 302 重定向到单点登录网站 http://login.com,但是此时却报错:

Access to XMLHttpRequest at 'http://login.com' (redirected from 'http://aaa.com/getuser') from origin 'http://aaa.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: 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://aaa.com/getuser 接口的请求头中加了 Content-Type:application/json,使得发送了预检请求,然后在 chrome 中预检请求直接重定向有问题从而报错,把 application/json 删掉即可。

# 常见跨域错误

Access to XMLHttpRequest at 'xxxx' from origin 'yyyy' has been blocked by CORS policy: Request header field zzz is not allowed by Access-Control-Allow-Headers in preflight response.

这种问题的是因为后端配置的 access-control-allow-header 里面不允许 zzz,要么后端配置下,要么删一下请求头。表现形式一般是 options 请求可以请求成功,但是正式请求会报跨域错误。

# axios 请求的三种错误

  • 网络异常错误:当网络出现异常(比如网络不通)的时候,再比如上述所说的预检请求不允许重定向
  • 超时错误
  • 状态码错误:比如 http状态码为 500
instance.interceptors.response.use(
    response => {},
    error => {
        if (error.response && error.response.data) {
            return Promise.reject(error);
        }

        let {message} = error;
        if (message === 'Network Error') {
            message = '后端接口连接异常!';
        }
        if (message.includes('timeout')) {
            message = '后端接口请求超时';
        }
        if (message.includes('Request failed with status code')) {
            const code = message.substr(message.length - 3);
            message = '后端接口' + code + '异常';
        }
        Message.error(message);
        return Promise.reject(error);
    }
);
最后更新时间: 4/26/2023, 3:48:58 PM