【Http】浏览器处理请求的那些事儿以及服务器代理实现

浏览器处理请求的那些事儿以及服务器代理实现


介绍

本篇会解释下面这些问题:

  • 浏览器是如何处理请求的
  • 跨域是怎么回事儿
  • Cors处理跨域的底层逻辑是什么
  • 为何要对请求进行分类
  • 请求预检是怎么回事儿
  • 如何实现服务器代理(中转层)

源(Origin)

什么是,什么是同源策略,这是个老话题了,简单过下。
Web 内容的源由用于访问它的 URL 的方案(协议)、主机名(域名)和端口定义。只有当协议、主机和端口都匹配时,两个对象才具有相同的源。

总结:只要两个URL的协议、域名和端口号都相同,就是同源;反之,则不是

浏览器为何要进行同源限制

为了安全,防止跨域访问时所带来的恶意攻击。这里提供一个漏洞练习的靶场,可以玩一下XSS , XSRF


浏览器是如何处理请求的

先简单介绍下基础概念:

浏览器会将请求分为两类

  • 简单请求
  • 非简单请求

这两个概念可以参考:

https://juejin.cn/post/7206264862657445947#comment

https://github.com/amandakelake/blog/issues/62

至于为何要这么分类,可以参考分类原因

这里做个总结:
简单请求就是普通的get , post , head请求,没有额外的请求头。其他请求就都是非简单请求。

对简单请求,不会做预检请求。而对非简单请求,会做预检请求。也是为了安全考虑。

简单来说,预检请求就是在实际请求之前,浏览器先使用OPTIONS请求方式进行一次请求,为了验证服务器是否允许本次请求。如果允许,就继续完成实际的请求;如果不允许,浏览器就会拦截此次的实际请求。


具体是怎么做的,我们眼见为实,来实际操作一下。这里我们起两个服务器,前端使用Live Server,后端使用koa
前端地址:http://127.0.0.1:5500/index.html
后端地址:http://localhost:4001/
先来看简单请求,前端代码

// index.html

const xhr = new XMLHttpRequest();
xhr.open('GET', 'http://localhost:4001/string');
xhr.send();
xhr.onreadystatechange = function(e) {
	if(e.target.readyState === 4 && e.target.status === 200) {
		console.log(e.target.response);
	}
}

可以看到,前端发送了跨域请求
在这里插入图片描述
点进去可以看到
在这里插入图片描述
后端:
在这里插入图片描述

可以看到状态码是200,说明我们的请求是成功的,但是并没有数据返回,这是怎么回事儿呢?
实际过程是,在发送简单请求时,我们的请求是正常发出的,浏览器会通过cors带上Origin请求头,服务端会收到请求,把数据响应给浏览器。浏览器收到响应后,会检查响应头中是否有Access-Control-Allow-Origin字段,该字段值和请求头的Origin是否匹配。如果匹配,则浏览器不会拦截数据,我们就能拿到响应数据;如果不匹配,浏览器就会将数据拦截。

我们在后端配置cors跨域再重新请求,可以看到我们正常发送了一次请求,类型是xhr
在这里插入图片描述
点进去可以看到请求头的Origin字段和响应头的Access-Control-Allow-Origin的值是相同的
在这里插入图片描述
这样,前端就获取到了想要的数据
在这里插入图片描述


再来看看非简单请求和预检请求是怎么回事儿。我们以DELETE来触发预检请求,先让后端不允许跨域。
前端代码

const xhr = new XMLHttpRequest();
xhr.open('DELETE', 'http://localhost:4001/test');
xhr.send();
xhr.onreadystatechange = function(e) {
	if(e.target.readyState === 4 && e.target.status === 200) {
		console.log(e.target.response);
	}
}

后端代码:

router.delete('/test', async (ctx, next) => {
  ctx.body = '已删除'
})

此时,我们可以看到,虽然我们只发了一次请求,但是实际却请求了两次
在这里插入图片描述
第一次请求类型为preflight,即预检请求,状态码为404。第二次请求是我们的xhr,状态为跨域。
我们先看看预检请求做了什么
在这里插入图片描述
预检的请求方法是OPTIONS,而把我们实际的请求方法DELETE作为Access-Control-Request-Method的值。
再看后端的反应
在这里插入图片描述
后端给出了404,这过程中发生了什么
如果是非简单请求,浏览器会进行预检请求,请求方式为OPTIONS,向后端发送一次请求,来验证当前的跨域请求是否被服务器允许,如果不允许,浏览器就会拦截,让我们无法把请求发送给后端。所以这时候,后端并不会收到我们的DELETE请求。下图是我们的DELETE请求,什么请求也没有。
在这里插入图片描述
这时候,我们把后端设置cors允许前端的请求。我们的预检请求就会通过
在这里插入图片描述
之后的DELETE请求就会收到正常的响应结果,而且此时,后端收到的不是OPTIONS请求,而是我们的DELETE请求
在这里插入图片描述
当跨域请求可以正常响应时,浏览器依然会发送预检请求,但是当服务器同意我们的DELETE请求时,服务器就会按照预检请求中的Access-Control-Request-Method值作为正式请求方法,把结果响应给前端。所以我们在后端看到的是只有DELETE

总结:对于简单请求,浏览器是直接放行请求的,如果后端也会正常返回结果给浏览器。如果服务器并没有允许跨域请求,浏览器就会拦截此次响应结果,不返回给前端。
对于非简单请求,浏览器会先进行预检请求。如果后端不允许跨域请求,后端会收到OPTIONS请求,但并不会把结果返回给浏览器。如果后端允许跨域请求,后端会将Access-Control-Request-Method的值作为实际请求方式,把结果返回给浏览器。前端会显示两次请求,但是服务器始终只需进行一次响应。


服务器代理

跨域实现方法中,有一种方法叫做代理服务器实现跨域。原理就是服务器没有同源策略限制。如果前端想要获取服务器B中的资源,那么我们就建立一个服务器A,让前端向服务器A发送请求,A会把本次请求通过代理转接到服务器B上,前端就可以获取到服务器B的资源了。

具体如何实现呢,本次以koa为例。我们用koa再创建一个服务器B,地址为
http://localhost:3002
为了区分,我们把之前创建的http://localhost:4001服务器就成为服务器A。
先在B中写入创建一个接口

router.get('/resource', async (ctx, next) => {
  ctx.body = '呐!这是你要的资源!';
})

此时,直接向B发送请求,会报跨域错误

const xhr = new XMLHttpRequest();
xhr.open('GET', 'http://localhost:3002/resource');
xhr.send();
xhr.onreadystatechange = function(e) {
	if(e.target.readyState === 4 && e.target.status === 200) {
		console.log(e.target.response);
	}
}

在这里插入图片描述
现在我们来配置服务器A,让A来做代理服务器。首先,需要A需要打开跨域请求,允许前端向A发出请求。然后A需要将请求目标地址代理到B。下面直接看做法

// 在A中安装插件

// 跨域插件
npm i koa2-cors 
// 这里我们使用的是express的跨域插件,因为这个好用
npm i http-proxy-middleware 
// koa不能直接使用express的插件,需要使用connect转接一下
npm i koa2-connect

然后在app.js中做如下配置:

app.use(cors());

// https://github.com/chimurai/http-proxy-middleware
app.use(
	connect(
    // 代理全部以 / 开头的 HTTP 请求
		createProxyMiddleware('/', {
			target: 'http://localhost:3002', // 目标服务器B的地址
			changeOrigin: true	// 允许跨域
		})
	)
);

现在,让前端向A发送请求

const xhr = new XMLHttpRequest();
xhr.open('GET', 'http://localhost:4001/resource');
xhr.send();
xhr.onreadystatechange = function(e) {
	if(e.target.readyState === 4 && e.target.status === 200) {
		console.log(e.target.response);
	}
}

在浏览器中查看
在这里插入图片描述
成功获取到服务器B中的响应。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值