请求跨域问题总结

最近在项目中出现了好多跨域的问题 写个回忆
简单说下跨域请求的发生:当 Web 资源请求由其它域名或端口提供的资源时,会发起跨域 HTTP 请求(cross-origin HTTP request)


问题描述

本次请求跨域主要发生在前端调用后端API上
会先发一个 Options 的预检请求 这个的返回一直过不去 导致真正的接口调用失败

在正式讲这个之前 需要讲一下简单请求和非简单请求
具体参考可以看 廖雪峰大神的博客 讲的很细很具体 点我走你

先来简单讲下 CORS(Cross-origin resource sharing) 跨域资源共享
他允许浏览器向本站以外的站点发起 XMLHttpRequest请求 说白就是你可以发送类似 Ajax 的请求,请求别人
浏览器一旦发现你是Ajax的跨域请求 会自动附加一些头信息 这个头信息决定了当前的请求是简单请求还是非简单请求

简单请求 (Simple Request)

在 Cors 中的简单请求
1.包含 GET HEAD POST

2.HTTP的头信息不超出以下几种字段:Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

这样就是一个简单请求 简单会直接请求 服务器只要回复中允许 Origin 的源就可以了

非简单请求 (Not-so-Simple Request)

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json

非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为”预检”请求(preflight)。

浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。

只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错

关于Options请求

OPTIONS方法是用于请求获得由Request-URI标识的资源在请求/响应的通信过程中可以使用的功能选项。通过这个方法,客户端可以在采取具体资源请求之前,决定对该资源采取何种必要措施,或者了解服务器的性能

关于Http请求有8种 OPTIONSGETHEADPOSTPUTDELETETRACECONNECT

其中最常用的莫过于 GETPOST

Options 请求主要实现2个功能

1.获取服务器支持的HTTP请求方法

2.用来检查服务器的性能。例如:AJAX进行跨域请求时的预检,需要向另外一个域名的资源发送一个HTTP OPTIONS请求头,用以判断实际发送的请求是否安全

为什么会产生 Options 请求?

因为浏览器发现你当前不是一个简单请求 在正式通信前发送了一次预检请求 也就是 Options 请求 来请求服务器允许的请求方法 和 一些Header头

服务器需要回复几个相关的字段就好了 如果没有这些字段 浏览器就会默认服务器不同意预检 后面的真正请求也就不会发出去 同时控制台可以看到类似的错误

XMLHttpRequest cannot load http://local.xxx.com.Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.

服务器方需要回复 下面的相关字段来应答 预检的 Options 请求

1
2
3
4
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000

问题和基本概况已经讲解

因为后端 是使用的 Lumen 框架做的微服务 提供 API 给前端调用 我直接很暴力的加了个 Cors 的中间件给路由使用

CorsMiddleware.php 代码片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public function handle($request, Closure $next)
{
$headers = [
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Methods' => 'POST, GET, OPTIONS, PUT, DELETE',
'Access-Control-Allow-Credentials' => 'true',
'Access-Control-Max-Age' => '86400',
'Access-Control-Allow-Headers' => 'Content-Type, Accept, Authorization, X-Requested-With, Application',
'Access-Control-Request-Headers' => 'Content-Type',//CORS请求会额外发送的头信息字段
];
if ($request->isMethod('OPTIONS'))
{
$response = $next($request);
foreach($headers as $key => $value)
{
$response->headers->set($key, $value);
}
}
return $response;
}

然后在 Kernel.phprouteMiddleware 中 增加了如下配置

1
2
3
4
5
6
7
8
9
/**
* The application's route middleware.
* These middleware may be assigned to groups or used individually.
* @var array
*/
protected $routeMiddleware = [
//跨域请求头信息
'cors' => \App\Http\Middleware\Cors::class,
];

直接给Options 请求增加返回了上面的一个header头

但是最后发现并没有生效 因为这里只是声明了一个中间件的名字 而你需要在对应的请求路由设置 去引用这个中间件才能生效

然后在路由中加了中间件 还是发现没有生效 这时又发现了个问题 因为我的路由控制中都是写的post或get请求 即使你使用了中间件 他也只是会在对应的请求方式过来的时候才会去加载中间件 所以option请求过来是没有自动加载执行上面的中间件的 如果给每个请求都设置一次options的请求路由会累死啊 最后又去看了下Laravel文档 发现了一个解决办法

Kennel.php

1
2
3
4
5
6
7
8
9
/**
* The application's global HTTP middleware stack.
* These middleware are run during every request to your application.
* @var array
*/
protected $middleware = [
//中间件加到这里才会使每个请求都生效 都会先走这个中间件检测
\App\Http\Middleware\Cors::class,
];

加载到这里的中间件才会全局执行 不论什么请求进来

至此解决了本次项目中的接口请求跨域问题

对框架理解还是不够深啊 加油!


-------------The End-------------