【重难点】【计算机网络 03】HTTP 常用请求方式 、HTTP 的状态码、URI 和 URL的区别、Cookie 和 Session、跨域请求是什么
文章目录
一、HTTP 的请求方式
请求方式 | 用途 |
---|---|
GET | 对服务器资源获取的简单请求 |
POST | 用于发送包含用户提交数据的请求 |
PUT | 向服务器提交数据,以修改数据 |
HEAD | 请求页面的首部,获取资源的元信息 |
DELETE | 删除服务器上的某些资源 |
CONNECT | 用于 ssl 隧道的基于代理的请求 |
OPTIONS | 返回所有可用的方法,常用于跨域 |
TRACE | 追踪请求响应的传输路径 |
CONNECT
- HTTP 代理使用的就是 CONNECT 这个方法,CONNECT 在网页开发中不会使用到
- CONNECT 的作用就是将服务器作为代理,让服务器代替用户去访问其他网页(说白了,就是翻墙),之后将数据返回给用户
- CONNECT 是通过 TCP 连接代理服务器的。想要实现上述功能首先需要建立起一条从我的客户端到代理服务器的 TCP 连接,然后给代理服务器发送一个 HTTP 报文去请求(当然了,需要验证身份),请求成功就可以做 HTTP 操作了,发送的 HTTP 请求报文会通过代理服务器请求 Internet 服务器,然后返回给客户端
OPTIONS
OPTIONS 的主要用途有两个:
- 获取服务器支持的 HTTP 请求方法
- 用来检查服务器的性能。例如:AJAX 进行跨域请求时的预检,需要向另外一个域名的资源发送一个 HTTP OPTIONS 请求头,用以判断实际发送的请求是否安全,这是规范要求的,是必须的
TRACE
客户端发起一个请求时,这个请求可能要穿过防火墙、代理、网关或其他一些应用程序。每个中间节点都可能会修改原始的 HTTP 请求。TRACE 方法允许客户端在最终将请求发送给服务器时,看看它的变化
TRACE 请求会在目的服务端发起一个环回诊断。行程最后一站的服务器会弹回一条 TRACE 响应,并在响应主体中携带它收到的原始请求报文。这样,客户端就可以查看在所有中间 HTTP 应用程序组成的请求/响应链上,原始报文是否,以及如何被毁坏或修改
二、HTTP 的状态码
状态码的开头不同代表不同的类型:
- 1XX:代表指示信息,表示请求已接收、继续处理
- 2XX:代表成功,表示请求已被成功接收、理解、接受
- 3XX:重定向,表示完成请求必须进行进一步的操作
- 4XX:客户端错误,请求有语法错误或请求无法实现
- 5XX:服务端错误,服务器未能实现合法的请求
常见状态码:
- 200 OK:正常返回信息
- 400 Bad Request:客户端请求有语法错误,不能被服务器所理解
- 403 Forbidden:服务器收到请求,但是拒绝提供服务
- 404 Not Found:请求资源不存在,输入了错误的 URL
- 500 Internal Server Errot:服务器发生不可预期错误
- 503 Server Unavailable:服务器当前不能处理客户端的请求,一段时间后可能恢复正常
三、URI 和 URL的区别
URI,全称是 Uniform Resource Identifier,统一资源标识符,主要作用是唯一地标识一个资源
URL,全称是 Uniform Resource Location,统一资源定位符,主要作用是提供资源的路径
URI 更像是身份证,可以唯一标识一个人;而 URL 更像一个住址,可以通过 URL 找到这个人
四、Cookie 和 Session
1.Cookie
由于 HTTP 协议是无状态协议,如果客户通过浏览器访问 Web 应用时没有一个保存用户访问状态的机制,那么将不能持续跟踪应用的操作。比如当用户往购物车中添加了商品,Web 应用必须在用户浏览别的商品时仍保存购物车的状态,以便用户继续往购物车中添加商品
Cookie 是浏览器的一种缓存机制,它可用于维持客户端与服务端之间的会话
这里以最常见的登录案例讲解 Cookie 的使用过程:
- 首先用户在客户端浏览器向服务器发起登录请求
- 登录成功后,服务端会把登录的用户信息保存在 Cookie 中,返回给客户端浏览器
- 客户端浏览器接收到 Cookie 请求后,会把 Cookie 保存到本地(可能是内存,也可能是磁盘,看具体使用情况而定)
- 以后再次访问该 Web 应用时,客户端浏览器会把本地的 Cookie 带上,这样服务端就能根据 Cookie 获得用户信息了
2.Session
Session 是一种维持客户端与服务端会话的机制,与 Cookie 把会话信息保存在客户端本地不同,Session 会把会话保留在浏览器端
我们同样以登录案例为例讲解 Session 的使用过程:
- 首先用户在客户端浏览器发起登录请求
- 登录成功后,服务端会把用户信息保存在服务端,并返回一个唯一的 Session 标识给客户端浏览器
- 客户端浏览器会把这个唯一的 Session 标识保存起来
- 以后再次访问 Web 应用时,客户端浏览器会把这个唯一的 Session 标识带上,这样服务端就能根据这个唯一标识找到用户信息
把唯一的 Session 标识返回给客户端浏览器,然后保存起来,以后访问时带上,这不还是 Cookie 吗?没错,Session 只是一种会话机制,在许多 Web 应用中,Session 机制就是通过 Cookie 来实现的。也就是说它只是使用了 Cookie 的功能,并不是使用 Cookie 完成会话保存。与 Cookie 在客户端保存会话的机制相反,Session 通过 Cookie 的功能把会话信息保存到服务端
Session 只是一种维持会话的机制,它可以有不同的实现。以现在比较流行的小程序为例,阐述一个 Session 的实现方案:
- 首先用户登录后,需要把用户登录信息保存在服务端,这里我们可以采用 Redis。比如说给用户生成一个 userToken,然后以 userId 作为键,以 userToken 作为值保存到 Redis 中,并在返回时把 userToken 待会给小程序端
- 小程序端接收到 userToken 后把它缓存起来,以后每当访问后端服务时就把 userToken 带上
- 在后续的服务中服务端只要拿着小程序端带来的 userToken 和 Redis 中的 userToken 进行比对,就能确定用户的登录状态
3.Cookie 和 Session 对比
- Cookie 是浏览器提供的一种缓存机制,它可以用于维持客户端与服务端之间的会话;Session 指的是维持客户端与服务端会话的一种机制,它可以通过 Cookie 实现,也可以通过别的手段实现
- 如果用 Cookie 实现会话,那么会话会保存在客户端浏览器中;而 Session 机制提供的会话是保存在服务端的,当访问增多时,会比较占用服务器的性能,考虑到减轻服务器负担,可以选择使用 Cookie
- Cookie 的存储数据类型是 ASCII;Session 的存储数据类型可以是任意数据类型
- Cookie 的安全性相较 Session 要低,别人可以分析存放在本地的 Cookie 并进行 Cookie 欺骗
- Cookie 的存储空间相较 Session 要小
- Cookie 可以设置长时间保存;Session 的有效期较短,超时或者客户端关闭都会失效
五、跨域请求是什么
跨域是指浏览器在发起网络请求时,会检查该请求所对应的协议、域名、端口和当前网页是否一致,如果不一致则浏览器会进行限制,比如在 www.baidu.com 的某个网页中,如果使用 AJAX 去访问 www.jd.com 是不行的,但是如果使用 img、iframe、script 等标签的 src 属性去访问则是可以的(只支持 GET 请求,通过 src 属性加载的资源,浏览器会限制 JavaScript 的权限,使其不能读写返回的内容)。之所以浏览器要做这层限制,是为了用户信息安全
跨域请求之所以会导致用户信息不安全,是因为可能存在 CSRF 攻击
跨站请求伪造(Cross-site request forgery,CSRF)是一种挟持用户在当前已登录的 Web 应用程序上执行非本意的操作。攻击者可以通过一些技术手段欺骗用户的浏览器去访问一个曾经认证过的网站执行一些操作(如发邮件、发信息、甚至转账和购买商品),由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去执行。这里用了 Web 中用户身份验证的一个漏洞:简单的身份验证只能保证请求是发自某个用户的浏览器,却不能保证请求是用户自愿发出的
举个例子,假如一家银行用以运行转账操作的 URL 地址如下:
https://bank.example.com/withdraw?account=AccoutName&amount=1000&for=PayeeName
那么,一个恶意攻击者可以在另一个网站上放置如下代码
<img src=“https://bank.example.com/withdraw?account=Alice&amount=1000&for=Badman” />
如果有账户名为 Alice 的用户访问了恶意站点,而她之前刚访问过银行不久,登录信息尚未过期,那么她就会损失 1000 元
这种恶意的网址可以有很多种形式,藏身于网页中的许多地方。此外,攻击者也不需要控制放置恶意网址的网站。他可以将这种地址藏在论坛、博客等任何用户生成内容的网站中。这意味着如果服务端没有合适的防御措施的话,用户即使访问熟悉的可信网站也有受攻击的危险
透过例子能够看出,攻击者并不能通过 CSRF 攻击来直接获取用户的账户控制权,也不能直接窃取用户的任何信息。它们能做到的就是欺骗用户的浏览器,让其以用户的名义执行操作
但是如果开发者想要绕过这层限制也是可以的,常见的解决方案如下
JSONP
- 利用 <script> 标签 没有跨域限制的漏洞,网页可以得到从其它来源动态产生的 JSON 数据。JSONP 请求一定需要对方的服务器做支持才可以
- JSONP 和 AJAX 相同,都是客户端向服务端发起请求,从服务端获取数据的方式。但 AJAX 属于同源策略,JSONP 属于非同源策略(跨域请求)
- JSONP 优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。缺点是仅支持 GET 方法,具有局限性;不安全,可能遭受 CSRF 攻击
CORS
跨域资源共享(Cross-origin resource sharing,CORS),其实是浏览器制定的一个规范,浏览器会自动进行 CORS 通信,它的实现主要在服务端,它通过一些 HTTP Header 来限制可以访问的域,例如页面 A 需要访问 B 服务器上的数据,如果 B 服务器上声明了允许 A 的域名访问,那么从 A 到 B 的跨域请求就可以完成。对于那些会对服务器数据产生副作用的 HTTP 请求,浏览器会使用 OPTIONS 方法发起一个预检请求,从而可以获知服务端是否允许该跨域请求,服务端确认允许后,才会发起实际的请求。在预检请求的返回中,服务器端可以告知客户端是否需要身份认证信息。我们只需要设置响应头,即可进行跨域请求
实际使用 CORS 的时候,HTTP 请求会被划分为简单请求和复杂请求
简单请求的定义:
1、使用下列方法之一:GET、POST、HEAD
2、不得人为设置该集合之外的其它头部字段,该集合为:Accept、Accepet-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
不符合简单请求定义的就是复杂请求,浏览器对简单请求和复杂请求的处理是不一样的,这两种请求的区别主要在于是否会触发 CORS 预检请求
既然 CORS 是一种规范,就说明只要遵守这个规范,就能实现跨域资源共享,下面我们就来看看简单请求和复杂请求分别需要遵守哪些规则
1、简单请求
对于简单请求,浏览器直接发出 CORS 请求。具体来说,就是在请求头中增加一个 Origin 字段。在 Origin 字段中说明本次请求来自那个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求
在服务端我们应该为简单请求的响应头添加如下字段:
- Access-Control-Allow-Origin(必含):它的值要么是一个且仅有一个完整域名,要么是一个 *,表示接受任意域名的请求
- Access-Control-Allow-Credentials(可选):该项标志着是否允许客户端发送 cookies 信息,它的值要么为 true,要么就不设置。CORS 请求默认不发送 Cookie 和 HTTP 认证信息,如果该项设置为 true,也必须在 AJAX 请求中设置 withCredentials = true。此外,如果该项设置为 true,Access-Control-Allow-Origin 就不能设置为 *,必须明确指定为与请求网页一致的域名
- Access-Control-Expose-Headers(可选):XMLHttpRequest 对象的 getResponseHeader() 方法只能拿到 6 个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想要拿到其他字段就必须在 Access-Control-Expose-Headers 里面指定
如果 Origin 指定的源不在服务器的许可返回之内,服务器会返回一个正常的 HTTP 响应,但是没有任何 CORS 相关的头信息字段。浏览器收到响应后发现响应头没有 Access-Control-Allow-Origin 字段,就知道跨域请求失败了,从而抛出一个错误,然后会被 XMLHttpRequest 的 onerroe 回调函数捕获。注意,这种错误无法通过状态码识别,因为 HTTP 响应的状态码有可能是 200
2、复杂请求
复杂请求会在正式通信之前,增加一次 HTTP 查询请求,称为 CORS 预检请求。浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,只有得到肯定答复,浏览器才会发出正式的 XMLHttpRequest 请求,否则就报错
除了 Origin,预检请求头中还包括两项 CORS 特有的字段:
- Access-Control-Request-Method:用于列出浏览器的 CROS 请求会用到哪些 HTTP 请求方式
- Access-Control-Request-Headers:该项是一个以逗号分隔的字符串,指定浏览器 CORS 请求会额外发送的头信息字段
服务器收到预检请求后,检查了 Origin、Access-Control-Request-Method 和 Access-Control-Request-Headers 字段以后,确认允许跨域请求,就可以做出响应了
在服务端我们应该为复杂请求的响应头添加如下字段:
- Access-Control-Allow-Origin(必含):与简单请求一致
- Access-Control-Allow-Methods(必含):它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的方法,这是为了避免多次预检请求
- Access-Control-Allow-Headers(可选):如果浏览器请求包括 Access-Control-Request-Headers 字段,则该字段是必须的。它也是一个逗号分分隔的字符串,表明服务器支持的所有头信息字段,不但是浏览器请求的字段
- Access-Control-Allow-Credentials(可选):与简单请求一致
- Access-Control-Max-Age(可选):用来指定本次预检请求的有效期,单位为秒,在此期间不用发出另一条预检请求
一旦服务器同意了预检请求,以后每次浏览器正常的复杂请求,就都跟简单请求一样,会有一个 Origin 头信息字段。服务器的回应,也都会有一个 Access-Control-Allow-Orign 头信息字段
3.代码实现
package com.sisyphus.util;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
final CorsConfiguration corsConfiguration = new CorsConfiguration();
//是否允许请求带有验证信息
corsConfiguration.setAllowCredentials(true);
//允许访问的客户端域名
corsConfiguration.addAllowedOrigin("*");
//允许服务端访问的客户端请求头
corsConfiguration.addAllowedHeader("*");
//允许访问的方法名
corsConfiguration.addAllowedMethod("*");
//使用基于 URL 的配置源注册跨域配置,"/**" 表示拦截所有的请求,corsConfiguration 是需要注册的跨越配置
urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
return new CorsFilter(urlBasedCorsConfigurationSource);
}
}
@CrossOrigin 注解
加在 Controller 类上或者方法上即可实现跨域,该注解有 8 个参数:
- value:指定允许请求源列表,默认为 * 表示允许来自任意请求源的请求
- origins:value 与 origins 含义相同,可以任选 value 或 origins 进行设置。value 与 origins 若同时存在,两者的值需要保持一致,否则启动时会出现异常
- allowedHeaders:指定允许实际请求头字段列表,默认为 * 表示允许实际请求带有任意字段
- exposedHeaders:指定允许客户端访问的响应头字段列表,默认只允许访问:Cache-Control、Context-Language、Content-Type、Expires、Last-Modified、Pragma
- methods:指定允许请求的 HTTP 方法,默认与 @RequestMapping 相同
- allowCredentials:指定 Access-Control-Allow-Credentials 的值,表示客户端是否应将凭证(如 Cookies)和跨域请求一起发送到服务器,一般情况下不设置此值,即不允许使用任何凭证
- maxAge:指定预处理响应的最大缓存期限,单位为秒,即 Access-Control-Max-Age 的值。合理设置其值可以有效减少客户端与服务器预检请求的交互次数。其值为负,表示未定义,默认为 1800 秒
- DEFAULT_ORIGINS、DEFAULT_ALLOWED_HEADERS、DEFAULT_ALLOW_CREDENTIALS、DEFAULT_MAX_AGE:这些属性用于设置默认值,在 Spring 5.0+ 版本中已废弃,由 CorsConfiguration.applyPermitDefaultValues() 方法来完成其功能
使用 SpringCloud 网关
服务网关(zuul)又称路由中心,用来同意访问所有 API 接口,维护服务
SpringCloud Zuul 通过与 SpringCloud Eureka 的整合,实现了对服务实例的自动化维护,所以在使用服务路由配置的时候,我们不需要向传统路由配置方式那样去指定具体的服务实例地址,只需要通过 Ant 模式配置文件参数即可
Node 中间件代理(两次跨域)
实现原理:同源策略是浏览器需要遵循的标准,而如果是服务器向服务器请求就无需遵循同源策略。这样的话,我们可以让服务器替我们发送一个请求,请求其它服务器下面的数据。然后我们的页面访问当前服务器下的接口就没有跨域问题了。代理服务器需要做以下几个步骤:
- 接收客户端请求
- 将请求转发给服务器
- 拿到服务器响应数据
- 将响应转发给客户端
Nginx 反向代理
实现原理类似于 Node 中间件代理,需要你搭建一个中转 Nginx 服务器,用于转发请求。使用 Nginx 反向代理实现跨域是最简单的跨域方式。只需要修改 Nginx 的配置即可解决跨域问题,支持所有的浏览器,支持 Session,不需要修改任何代码,并且不会影响服务器性能
跨域实现方案总结
- JSONP 只支持 GET 请求,JSONP 的优势在于支持老式浏览器,以及可以向不支持 CORS 的网站请求数据
- CORS 支持所有 HTTP 请求方式,是跨域 HTTP 请求的根本解决方案
- 不管是Node 中间件代理还是 Nginx 反向代理,主要是利用同源策略不限制服务器跨域
- 日常工作中,CORS 和 Nginx 反向代理用的比较多