开发网站时经常会用到跨域资源共享(简称cors,后面使用简称)来解决跨域问题,但是在使用cors的时候,http请求会被划分为两类,简单请求和复杂请求,而这两种请求的区别主要在于是否会触发cors预检请求。

首先我们要明白cors的原理(引自MDN):

跨域资源共享标准新增了一组 HTTP 首部字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。另外,规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP 认证相关数据)

从上面的文字中我们得到如下信息:

1、跨域资源共享标准新增了一组 HTTP 首部字段,服务器通过这些字段来控制浏览器有权访问哪些资源。

2、为了安全起见请求方式分为两类,一类不会预先发送options请求,一些会预先发送options请求。

3、 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求会触发options请求。

4、服务器验证OPTIONS完成后才会允许发送世界的http请求。

不会触发http预检请求的便是简单请求,想法能够触发http预检请求的便是复杂请求。

那么有哪些简单请求呢?以下是来自MDN官方引用:

1、使用下列方法之一:

GET、

POST、

HEAD。

2、不得人为设置该集合之外的其他首部字段。该集合为:

Accept

Accept-Language

Content-Language

Content-Type

3、Content-Type 的值仅限于下列三者之一:

text/plain

multipart/form-data

application/x-www-form-urlencoded

4、请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问

5、请求中没有使用 ReadableStream 对象

那什么是复杂请求呢,除了简单请求都是复杂请求。

简单请求的发送从代码上来看和普通的XHR没太大区别,但是HTTP头当中要求总是包含一个域(Origin)的信息。该域包含协议名、地址以及一个可选的端口。不过这一项实际上由浏览器代为发送,并不是开发者代码可以触及到的。

简单请求的部分响应头及解释如下:

Access-Control-Allow-Origin(必含)- 不可省略,否则请求按失败处理。该项控制数据的可见范围,如果希望数据对任何人都可见,可以填写"*"。 Access-Control-Allow-Credentials(可选) – 该项标志着请求当中是否包含cookies信息,只有一个可选值:true(必为小写)。如果不包含cookies,请略去该项,而不是填写false。这一项与XmlHttpRequest2对象当中的withCredentials属性应保持一致,即withCredentials为true时该项也为true;withCredentials为false时,省略该项不写。反之则导致请求失败。 Access-Control-Expose-Headers(可选) – 该项确定XmlHttpRequest2对象当中getResponseHeader()方法所能获得的额外信息。通常情况下,getResponseHeader()方法只能获得如下的信息: Cache-Control Content-Language Content-Type Expires Last-Modified Pragma 当你需要访问额外的信息时,就需要在这一项当中填写并以逗号进行分隔

如果仅仅是简单请求,那么即便不用CORS也没有什么大不了,但CORS的复杂请求就令CORS显得更加有用了。简单来说,任何不满足上述简单请求要求的请求,都属于复杂请求。比如说你需要发送PUT、DELETE等HTTP动作,或者发送Content-Type: application/json的内容。

复杂请求表面上看起来和简单请求使用上差不多,但实际上浏览器发送了不止一个请求。其中最先发送的是一种"预请求",此时作为服务端,也需要返回"预回应"作为响应。预请求实际上是对服务端的一种权限请求,只有当预请求成功返回,实际请求才开始执行。

预请求以OPTIONS形式发送,当中同样包含域,并且还包含了两项CORS特有的内容

Access-Control-Request-Method – 该项内容是实际请求的种类,可以是GET、POST之类的简单请求,也可以是PUT、DELETE等等。 Access-Control-Request-Headers – 该项是一个以逗号分隔的列表,当中是复杂请求所使用的头部。

显而易见,这个预请求实际上就是在为之后的实际请求发送一个权限请求,在预回应返回的内容当中,服务端应当对这两项进行回复,以让浏览器确定请求是否能够成功完成。

复杂请求的部分响应头及解释如下:

Access-Control-Allow-Origin(必含) – 和简单请求一样的,必须包含一个域。 Access-Control-Allow-Methods(必含) – 这是对预请求当中Access-Control-Request-Method的回复,这一回复将是一个以逗号分隔的列表。尽管客户端或许只请求某一方法,但服务端仍然可以返回所有允许的方法,以便客户端将其缓存。 Access-Control-Allow-Headers(当预请求中包含Access-Control-Request-Headers时必须包含) – 这是对预请求当中Access-Control-Request-Headers的回复,和上面一样是以逗号分隔的列表,可以返回所有支持的头部。这里在实际使用中有遇到,所有支持的头部一时可能不能完全写出来,而又不想在这一层做过多的判断,没关系,事实上通过request的header可以直接取到Access-Control-Request-Headers,直接把对应的value设置到Access-Control-Allow-Headers即可。 Access-Control-Allow-Credentials(可选) – 和简单请求当中作用相同 Access-Control-Max-Age(可选) – 以秒为单位的缓存时间。预请求的的发送并非免费午餐,允许时应当尽可能缓存。

理论聊完之后,咱们来看一下实践,首先启动两个服务,一个端口为3000,的静态资源服务器,用于请求接口,另一台端口为5000的接口服务器,如图所示:

后端接口服务器代码如下:

const  express = require("express");

const app = express();

const bodyParser = require('body-parser');

app.use(bodyParser.urlencoded({extended:false}));

app.use(bodyParser.json());

// 实现CORS

app.use(function(req, res, next) {

res.header("Access-Control-Allow-Origin", "*");

res.header('Access-Control-Allow-Methods', 'OPTIONS,GET,POST,PUT,DELETE');

res.header("Access-Control-Allow-Headers", "Origin,X-Requested-With,Content-Type,Accept,Authorization");

res.header("cache-control", "no-cache");

res.header("content-type", "application/json; charset=utf-8");

res.header("ETag", '');

next();

});

app.post("/p",(req,res)=>{

res.send(req.body)

})

app.listen(5000,()=>{

console.log("5000")

})

前端请求资源脚本代码如下:

axios.post("http://localhost:5000/p",{name:"zs",age:"18"}).then((data)=>{

console.log(data.data);

})

我们用axios这个http请求库发送了一个post请求,axios发送post请求默认会把数据转化为json格式,并且会默认设置请求头:Content-Type:application/json,很显然这是一个复杂请求,这样的话,会触发options请求。

我们分别启动两个服务,并打开浏览器,访问页面,加载请求接口脚本,观察network如图:

我们看到,代码中命名只发送了一次异步请求为什么显示两次呢?详细截图如下:

我们看到确实发送了两次请求一次为OPTIONS一次为POST,而我们代码中并没有处理对OPTIONS请求的响应处理,所以上面服务端代码是不合理的,综合考虑,OPTIONS请求并会对实际http请求差生影响,所以我们统一的对OPTIONS请求返回204,服务端负责支持CORS的中间件修正代码如下:

app.use(function(req, res, next) {

res.header("Access-Control-Allow-Origin", "*");

res.header('Access-Control-Allow-Methods', 'OPTIONS,GET,POST,PUT,DELETE');

res.header("Access-Control-Allow-Headers", "Origin,X-Requested-With,Content-Type,Accept,Authorization");

res.header("cache-control", "no-cache");

res.header("content-type", "application/json; charset=utf-8");

res.header("ETag", '');

//header头信息设置结束后,结束程序往下执行,返回

if(req.method.toLocaleLowerCase() === 'options'){

res.status(204);

return res.json({});   //直接返回空数据,结束此次请求

}else{

next();

}

});

总结一下:

1. 简单请求:

满足一下两个条件的请求。

(1) 请求方法是以下三种方法之一:

  • HEAD
  • GET
  • POST

(2)HTTP的头信息不超出以下几种字段:

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

2. 复杂请求:

非简单请求就是复杂请求。

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

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

预检请求为OPTIONS请求,用于向服务器请求权限信息的。

预检请求被成功响应后,才会发出真实请求,携带真实数据。

axios默认请求就是application/json,所以不需要自己加上头部(不需要在config中加headers),所以总是会发出options请求的,看看是不是配置的时候加了不必要的headers配置项。
另外,如果真的需要预检,后台也需要进行设置,允许options请求。

ajax跨域简单请求与复杂请求的更多相关文章

  1. ajax跨域POST时执行OPTIONS请求服务端返回403forbidden的解决方法

    ajax访问服务端restful api时,由于contentType类型的原因,浏览器会先发送OPTIONS请求. 本人服务端用的是spring mvc框架,web服务器用的是tomcat的,以下给 ...

  2. js便签笔记(13)——jsonp其实很简单【ajax跨域请求】

    前两天被问到ajax跨域如何解决,还真被问住了,光知道有个什么jsonp,迷迷糊糊的没有说上来.抱着有问题必须解决的态度,我看了许多资料,原来如此... 为何一直知道jsonp,但一直迷迷糊糊的不明白 ...

  3. jsonp其实很简单【ajax跨域请求】

    js便签笔记(13)——jsonp其实很简单[ajax跨域请求] 前两天被问到ajax跨域如何解决,还真被问住了,光知道有个什么jsonp,迷迷糊糊的没有说上来.抱着有问题必须解决的态度,我看了许多资 ...

  4. 原创:【ajax | axios跨域简单请求+复杂请求】自定义header头Token请求Laravel5后台【亲测可用】

    如标题:我想在ajax的header头增加自定义Token进行跨域api认证并调用,api使用laravel5编写,如何实现? 首先,了解下CORS简单请求和复杂请求.  -- CORS简单请求 -- ...

  5. 简单ajax跨域请求

    最近遇到需要ajax跨域的需求 首先看下不做任何处理特别处理的ajax跨域请求会出现什么样的错误 客户端代码: <script type="text/javascript"& ...

  6. Ajax操作如何实现跨域请求 (JSONP和CORS实现Ajax跨域的原理)

    由于浏览器存在同源策略机制,同源策略阻止ajax (XMLHttpRequest) 从一个源加载的文档或脚本获取或设置另一个源加载的文档的属性. 特别的:由于同源策略是浏览器的限制,所以请求的发送和响 ...

  7. 浅谈linux 下,利用Nginx服务器代理实现ajax跨域请求。

    ajax跨域请求对于前端开发者几乎在任何一个项目中都会用到,众所周知,跨域请求有三种方式: jsonp; XHR2 代理: jsonp: 这种应该是开发中是使用的最多的,最常见的跨域请求方法,其实aj ...

  8. 解决ajax跨域请求 (总结)

    ajax跨域请求,目前已用几种方法实现:   1)用原生js的xhr对象实现.                var url="http://freegeoip.net/json/" ...

  9. ASP.NET MVC 实现AJAX跨域请求方法《1》

    ASP.NET MVC 实现AJAX跨域请求的两种方法 通常发送AJAX请求都是在本域内完成的,也就是向本域内的某个URL发送请求,完成部分页面的刷新.但有的时候需要向其它域发送AJAX请求,完成数据 ...

随机推荐

  1. Tomcat 报错 The APR based Apache Tomcat Native library which allows optimal performance in production environmen

    这个问题在我一次重新装了tomcat和myeclipse时出现 说实话 出现这个问题头大 但是好在解决了 美滋滋 最开始到处寻找各种解决方案 最后直接注释了server.xml中的一行 直接解决这个报 ...

  2. 用Python怎么telnet到网络设备

    0.前言 Telnet协议属于TCP/IP协议族里的一种,对于我们这些网络攻城狮来说,再熟悉不过了,常用于远程登陆到网络设备进行操作,但是,它的缺陷太明显了,就是不安全,信息明文传送,极容易被攻击窃取 ...

  3. java虚拟机体系分析

      一.JVM的生命周期: 1)程序开始执行,他就运行,程序停止,它就结束.有几个程序在执行,就有几个虚拟机在工作.只要Java虚拟机中还有普通的线程在执行,Java虚拟机就不会停止. 2)Java虚 ...

  4. sbt 学习笔记(1)sbt安装和交互式界面使用

    下载sbt: http://www.scala-sbt.org/download.html 解压zip文件F:\sbt-0.13.15 配置环境变量 如果需要可以修改F:\sbt-0.13.15\sb ...

  5. SpringBoot-ElasticJob封装快速上手使用(分布式定时器)

    elastic-job-spring-boot qq交流群:812321371 1 简介 Elastic-Job是一个分布式调度解决方案,由两个相互独立的子项目Elastic-Job-Lite和Ela ...

  6. 《Java语言程序设计》编程练习8.9(游戏:#字游戏)

    8.9 (游戏:#字游戏)在并字游戏中,两个玩家使用各自的标志(一方用X则另一方就用O),轮流填写3x3的网格中的某个空格.当一个玩家在网格的水平方向.垂直方向或者对角线方向上出 现了三个相同的X或三 ...

  7. 使用lombok中的log

    idea中安装lombok插件 引入lombok依赖 <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> ...

  8. 超炫酷的 IntelliJ IDEA 插件(一)

    工善其事必先利器 打开setting文件选择Plugins选项 Ctrl + Alt + S File -> Setting 我的idea是最新版本2019.02 有的和别人界面可能不一样 主界 ...

  9. 关于Python读写Excel

    关于Excel的读写本人最近看到一篇不错的文章话不多少直接连接https://blog.csdn.net/qq_41185868/article/details/80469355

  10. Focus on the Good 专注于好的方面

    [1]  Dealing with people is like digging for gold. When you go digging for an ounce of gold, you hav ...