HTTP OPTIONS跨域请求

一、场景

今天在监测跨域代码时发现,在调用后端接口的时候会出现两次请求:OPTIONS请求和POST请求。代码如下:

/// <summary>
/// 自定义中间件要执行的逻辑
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public async Task Invoke(HttpContext context)
{
  context.Response.Headers.Add("Access-Control-Allow-Origin", "*");
  context.Response.Headers.Add("Access-Control-Allow-Headers", "*");
  context.Response.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
  //若为OPTIONS跨域请求则直接返回,不进入后续管道
  if (context.Request.Method.ToUpper() != "OPTIONS")
    await _next(context);//把context传进去执行下一个中间件
}

二、原因

XMLHttpRequest会遵守同源策略(same-origin policy),即脚本只能访问相同协议/相同主机名/相同端口的资源。 突破这个限制的情况叫做跨域,此时需要遵守跨域资源共享标准CORS(Cross-Origin Resource Sharing)机制。浏览器将CORS请求分为两类:简单跨域请求(simple request)和非简单跨域请求(not-simple-request)。简单请求浏览器请求不会触发预检请求,而非简单请求会触发预检请求。

三、跨域请求

跨域请求分为简单跨域请求和非简单跨域请求,这两种方式是怎么区分的呢?

1、简单跨域请求

(1)同时满足下列以下条件,就属于简单请求,否则属于非简单请求

  • 请求方式只能是:GET、POST、HEAD
  • HTTP请求头限制以下几种字段(不得人为设置该集合之外的其他首部字段):
Accept、Accept-Language、Content-Language、Content-Type(需要注意额外的限制)、DPR、Downlink、Save-Data、Viewport-Width、Width
  • Content-type只能取:application/x-www-form-urlencoded、multipart/form-data、text/plain
  • 请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。
  • 请求中没有使用 ReadableStream 对象。

(2)简单请求浏览器请求不会触发预检请求

2、非简单跨域请求

非简单请求会在正式通信之前,增加一次HTTP请求,称之为预检请求。浏览器会先发起OPTIONS方法到服务器,以获知服务器是否允许该实际请求。

四、如何避免非简单跨域请求

我们通过上边已经知道,只要满足简单请求的条件就可以避免发起OPTIONS请求,但是在实际开发中,简单请求肯定不会满足需求,比如以下请求:

  • 我们系统请求中除了GET/POST还有PUT,DELETE
  • 系统做了权限认证,请求头里需要带有用户验证信息
  • 我们的Content-Type绝大多数是application/json

然后只能寄希望于减少发起OPTIONS请求的次数,也就是说还是会用,但不是每次都用,方法命令如下:

Access-Control-Max-Age:(number) 数值代表预检请求的返回结果 可以被缓存多久,单位是秒

 例如将预检请求的结果缓存10分钟:

/// <summary>
/// 自定义中间件要执行的逻辑
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public async Task Invoke(HttpContext context)
{
  context.Response.Headers.Add("Access-Control-Allow-Origin", "*");
   context.Response.Headers.Add("Access-Control-Allow-Headers", "*");
   context.Response.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
   context.Response.Headers.Add("Access-Control-Max-Age", "600");
   //若为OPTIONS跨域请求则直接返回,不进入后续管道
   if (context.Request.Method.ToUpper() != "OPTIONS")
       await _next(context);//把context传进去执行下一个中间件
}

注意:

  • 不同浏览器有不同的上限,在Firefox中上限是24h,而在Chromium 中则是10min,Chromium同时规定了一个默认值 5 秒。如果值为 -1,则表示禁用缓存,每一次请求都需要提供预检请求。
  • Access-Control-Max-Age方法对完全一样的url的缓存设置生效,多一个参数也视为不同url。也就是说,如果设置了10分钟的缓存,在10分钟内,所有请求第一次会产生options请求,第二次以及第二次以后就只发送真正的请求了。
原文地址:https://www.cnblogs.com/qtiger/p/13886507.html