0


【网络】应用层——HTTP协议

🐱作者:一只大喵咪1201
🐱专栏:《网络》
🔥格言:你只管努力,剩下的交给时间!
图

🏀认识HTTP协议

上篇文章中,本喵带着大家对HTTP有了一个初步的认识,今天就来详细讲解一下这个应用层协议。

HTTP协议

⚽urlencode和urldecode

图
如上图所示的

url

(网址),里面包含有

/

以及

?

等字符。

  • 像这样的字符,已经被url当做特殊意义理解了,因此这些字符不能随意出现。

如果

url

中要包含这些特殊意义的字符,就需要对其做转义处理,就类似C语言中的转义字符一样。但是这里是网络,转义的规则不和C语言一样,它有自己的规则:取出字符的ASCII码:

  • 转成16进制,然后前面加上百分号即可。

比如,

+

号被转义后成为

%2B

,这个过程就叫做encode,而将

%2B

转回到

+

号就叫做decode

这个过程并不需要我们自己去做,有需要进行编码和解码的需求在网上直接查就可以,URL编码/解码。

tu
如上图,将字符

C++

进行urlencode后的结果是

C%2B%2B

,同样也可以进行urldecode进行解码。

图
上图是本喵在百度上搜索C++后跳转到的网页的

URL

(网址),可以看到红色框中的

wd=C%2B%2B

,其中

wd

表示关键字,

=

号后面的内容就是

encode

后的结果。

当服务器收到

url

请求后,会自行对特殊字符

%xx

进行

decode

。包括汉字也需要进行encode和decode。

⚽HTTP协议宏观格式

  • HTTP是基于请求和响应的应用层协议,使用的是TCP套接字,客户端向服务端发送request请求,服务端收到请求后会作response响应返回给客户端。

图

如上图所示,是HTTP协议的宏观格式,包括客户端的

request

和服务端的

response

  • 客户端:

请求行: 如上图红色框中所示,包含

GET

方法,

url

,还有http协议的版本,如

http/1.0

或者

http/1. 1

,这三部分构成请求行,必须使用

\r\n

来结束这一行。

请求报头: 如上图绿色框中所示,包含的都是请求属性,采用的是键值对的形式,

name

表示属性的名称,如

HOST

等,

value

是属性的内容,如具体的一个网址。每一个属性必须以

\r\n

来结束。

空行: 如上图紫色框中所示,这一行什么内容都没有,只有

\r\n

用来表示空行。

请求正文: 如上图最下面的黑色框中所示,包含请求的正文,如

username

用户名,

passwd

密码等,同样每个正文后面必须以

\r\n

来结束。

  • 正文可以省略不写,但是其他三部分必须有,以空行为界,空行后面的是请求正文,空行前面的是请求行和请求报头。
  • 每一部分都是以字符串形式放在报文中,如GET url http/1.0\r\nname: value\r\n\r\n,这一个报文中,包含请求行,一行属性以及空行。

请求行和请求报头两部分可以看成我们自定义协议中的报头,请求正文是有效载荷。

  • 服务端:

状态行: 如上图红色框中所示,包含

http/1.1

HTTP协议版本,状态码以及状态码描述,最后是

\r\n

响应报头: 如上图绿色框中所示,包含响应属性,和

request

的请求报头类似。

空行: 如上图紫色框中所示,也是只有一个

\r\n

响应正文: 如上图黑色框中所示,包含服务器要给客户端返回的数据内容,如

html/css/js

以及图片,视频,音频等网络资源。

  • 状态行和响应报头同样类似我们自定义协议中的报头,响应正文是有效载荷。
  • 服务端的response和客户端的request格式相同,只是每一块的内容有所差异。
  • 每一部分中具体名词的含义后面本喵会讲解。

按照上面HTTP协议的宏观格式,存在几个细节问题:

  1. 应用层怎么保证完整的读取了一个请求或者响应?

首先肯定可以完整的读完一行,因为每一行都是以

\r\n

结尾的,所以使用

while(完整读取一行)

的循环可以读完

请求行+请求报头

,直到空行再停止。

按照我们自定义协议中的逻辑,此时报头(请求行+请求报头)完全读完了,还剩下有效载荷(请求正文)。同样地,在请求报头中有一个属性`Content-Length:XXX正文长度``,根据这个正文长度就可以将所有的请求正文读取完毕。

此时一个完整的请求

request

就读取到了,同样的方式也可以读取到一个完整的响应

response

  1. 请求和响应是怎么做到序列化和反序列化的?

序列化和反序列化是由HTTP自己实现的,因为协议中的内容都是字符串,第一行(请求行/状态行) + 请求/响应报头,只要按照

\r\n

为判断条件,一直循环下去,就可以拼接(序列化)或者拆分(反序列化)。

正文不用进行序列化和反序列化,因为它本身就是字符串,根据

Content-Length:XXX正文长度

读取相应字节数就可以。

🏀验证HTTP协议格式

上面都是本喵在说HTTP是这样的格式,那么到底是不是呢?下面写代码来看一下。

服务器底层连接代码仍然用之前

TCP

套接字写好的:

图
其他代码本喵就不截图了,在本喵文章TCP网络通信有详细讲解,这里就直接拿来用了,如上图所示,

handlerEnter

是服务器处理任务的入口。

图

如上图所示,不一样的地方还有在增加了一个回调函数,包装器类型:

using func_t= function<bool(const request& req, response& resp)>

。除此之外,类名也变成了

httpServe

重点在于下面的代码,服务器是处理任务的:

图
先在

protocol.hpp

中定义请求

request

和响应

response

,如上图所示,此时请求和响应中只有一个

string

类型的成员变量。

图
当服务器在执行

handlerEnter

的时候,首先从网络中读取数据,这里假设

recv

一次就能完整读取一个请求(后面再进行优化),然后将读取到的报文完整赋值到请求

req

的字符串

inbuffer

中,再通过

_func

回调函数根据请求

req

构建响应

resp

,再将响应发送到网络中。

体
如上图所示的

DealReq

就是服务器调用的回调函数,用来处理请求并构建响应的,这里本喵暂时仅打印请求

req

inbuffer

中的内容。

  • 该函数在构造服务器httpServe对象的时候就被作为参数传给了构造函数,服务器中的回调函数掉的就是该函数。

由于从网络中读取到的数据,也就是客户端的请求

request

并没有经过反序列化,也没有去除过报头,所以此时

req

inbuffer

中的内容就是一个完整的报文(字符串),包括报头和有效载荷。

图

先将我们的服务端程序运行起来,如上图序号1所示,然后在

windows

端打开浏览器,在输入网址的地方输入

http://服务端公网IP+端口号

,如上图序号2所示,然后访问这个

url

图
此时

windows

的浏览器就是客户端,当访问这个

url

的时候,客户端就向服务端发起了请求,使用的协议是

http

协议。在服务端将收到的请求完整打印出来,结果如上图所示。

  • 打印出来的http请求的格式符合本喵前面介绍的宏观格式,并且将每一块中的具体信息都打印了出来。

下面本喵就来介绍一下,

http

请求中每一行的具体内容是什么。

⚽请求的格式

请求行:

请求行中的内容是

GET / HTTP/1.1

  • GET:表示请求方法,后面详细讲解。
  • / :表示url,也就是要访问服务器上文件所在的具体路径。

这里本喵在

windows

端输入网址的时候,并没有指定路径,所以在客户端(浏览器)的

http

协议会将请求路径默认构建成

\

表示根目录。

tu

windows

浏览器中输入网址的时候,加上请求地址

/a/b/c

,如上图红色框中所示,在客户端发起请求时,服务端收到的

http

请求的请求行中也会有相应的请求地址。

请求地址中的第一个

/

就表示根目录,和默认请求路径中的根目录是一个路径,但是这个根目录并不是Linux服务器上的根目录,而是

web

根目录。

图
如上图红色框所示,存在一个目录

wwwroot

,这个目录就是

web

根目录,它位于当前服务端进程所在的当前目录,所以说,

/

根目录可以是任何一个目录,因为

wwwroot

可以创建在任何地方。

图

windows

客户端发起请求的时候,输入网址中的路径

/a/b/c

访问的就是上图红色框中所示的

c.html

文件。

  • HTTP/1.1表示http协议的版本号是1.1,常用的版本号还有1.0

之所以在请求行中要包含

http

协议的版本号,是为了服务器能够合理的给客户端提供对应的服务。由于客户端的更新情况不一致,有的是

1.0

版本的,有的是

1.1

版本的,此时服务端根据请求行中的版本号就能够给不同版本的客户端提供不同的服务。


请求报头:

请求报头中包含许多请求属性,每一条属性为一行,使用

\r\n

结尾。

  • Host:43.143.106.44:8080,表示客户端所请求服务端是套接字(IP地址 + 端口号)。
  • Connection: keep-alive,表示长连接,后面详细讲解。
  • Upgrade-Insecure-Requests: 1,表示浏览器(客户端)支持自动将HTTP请求升级到HTTPS请求。在学习了https协议后才能感受到这一属性的作用。
  • User-Agent: 相关信息: 用于表示客户端的相关信息。

图
如上图所示,

User-Agent

后面的内容包括客户端的操作系统和使用的浏览器等信息。

  • Accept: 相关信息:表示该请求要请求的资源类型。

图
由于本喵使用的是

windows

端的浏览器向服务端发起的请求,并且没有设置

Accept

属性,也就是没有指定要请求的资源类型,所以浏览器默认将

http

能请求的所有资源类型都加在了

Accept

属性中。

  • Accept-Encoding: gzip, deflate:表示客户端支持两种encode格式。

服务器可以根据客户端的支持情况采用不同的压缩算法进行内容压缩。常见的压缩算法有gzip和deflate。

  • Accept-Language: zh-CN,zh;q=0.9:表示客户端支持的语言格式。

请求报头中的所有属性都是采用

name: val

的键值对形式,并且是一个字符串。


之后就是一个空行,只有一个

\r\n

⚽响应的格式

图
在处理请求构建响应的函数

DealReq

中,先打印出请求的内容,和之前一样。之后开始构建响应,按照

HTTP

的宏观格式构建响应:

  • 构建状态行,包括HTTP版本,状态码,状态码描述,最后以\r\n结束。
  • 构建响应报头,也就是响应属性,这里本喵暂时只写一个属性Content-Type: text/html\r\n,暂时不用管它是什么意思。
  • 构建空行,只有\r\n但是这个空行必须有。
  • 构建响应正文:本喵暂时不写任何响应正文。

构建好了响应

response

之后就是将其序列化,也就是将这些不同类型的字符串拼接在一起,如上图绿色框中所示,最后由服务器发送到网络中。

如何看到服务器构建的响应呢?使用一个

telnet

工具,如果你的Linux服务器上没有的话,使用

yum

下载一个即可。

图

  • 使用telnet工具,通过本地环回的IP地址和端口号向服务器发起请求。
  • 键盘上按ctrl + 回车,出现telnet>,再按一下回车跳到下一行。
  • 输入GET / HTTP/1.1,手动输入请求行,然后按回车。

此时就向服务端发起了请求,会收到服务端的响应,如上图绿色框中所示,这部分内容就是我们上面代码中构建的响应内容,可以看到有状态行,响应报头,还有空行。

此时我们已经验证了

http

的请求和响应和本喵前面介绍的宏观合适是相一致的。

🏀理解HTTP协议

上面本喵已经通过代码让大家看到了HTTP的请求和响应,从而也验证了HTTP宏观格式,接下来就对HTTP协议做一个更深入的理解。

图
在请求中增加几个成员变量,包括请求访问

method

,请求的

url

,请求的http版本

httpversion

,以及一个请求路径。

再通过成员函数

parse

对请求进行反序列化,首先使用

getOneLine

读取请求中的请求行,然后将请求行中的三个字段分离出来。

图
创建一个工具类

Util

,将一些公用的工具类函数放在里面,如上图所示,获取请求一行内容的函数

getOneLine

就放在里面。

图
再在服务器的处理入口

handlerEnter

中调用请求的成员函数

parse

进行反序列化。

图
在处理请求,构建响应的函数

DealReq

中,将请求行中的三个字段打印出来,由于通过

parse

已经反序列化了,所以直接打印

req

中的字段成员变量即可。

在构建响应正文的时候,添加一段

html

的代码,如上图所示,并且将代码以字符串的形式拼接到响应上。

图
使用

telnet

工具向服务端发起请求,此时就会得到服务端的响应,如上图所示,包括响应正文(

html

的代码)。

图
在服务端就可以看到

telnet

在发送请求是输入的请求行中的三个字段,如上图红色框中所示。

  • telnet发起请求后,得到的响应正文中的html代码到底是什么意思呢?

图

windows

上的浏览器来访问服务器,可以得到如上图所示的一个网页,网页中有一个字符串

Hello HTTP

,而这个字符串在响应正文的

html

代码中也出现过。

  • 响应正文中的html代码就是一个网页,是服务端响应给客户端的一个网页。
  • Linux中使用telnet得到响应正文并没有被解释,所以是完整的html代码,使用Windows的浏览器得到响应正文会被浏览器做解释,解释后得到的就是一个网页。

关于

html

的知识,用到的去查一下即可,本喵这里就直接用了。

⚽服务器和网页分离

上面代码中,服务器构建响应正文时,直接将

html

代码放在了

string resp_body

中,这种将前端代码嵌入到后端代码中的方式是不妥的,所以要将前端的

html

代码和后端分离开来。

图
如上图所示,在

wwwroot

根目录下创建几个文件,其中

404.html

index.html

直接位于根目录中,

a.html

b.hmtl

位于

wwwroot/test

目录下。

这四个

html

的文件内容不同,表示的网页也不同,作用也不同。

404.html:

图
如上图代码所示,当客户端发起的请求中

url

错误或者无效时,会将该文件中的内容作为响应正文返回给客户端,告知客户端访问资源不存在,并且显示404错误。

index.html:

图

如上图代码所示,当客户端发起的请求中

url

\

时,例如

http://127.0.0.1:8080/

,此时会将该文件中的内容作为响应正文给客户端返回。

因为此时客户端访问的就是

web

根目录,也就是

./wwwroot

目录,所以当用户访问根目录的时候,本喵将该目录下的

index.html

作为响应返回。

a.htmlh和b.html:

图
如上图代码所示,当客户端发起的请求中

url

/test/a.html

/test/b.html

的时候,服务器就会将这两个文件的没人作为响应正文返回给客户端,客户端就会得到两个网页。


上面4个文件中的代码都是

html

代码,如果将文件内容响应给Windows的浏览器,就会以网页的形式展现,这点本喵在前面展示过。

虽然是网页,但是归根到底,仍然是存在于Linux服务器磁盘上的四个文件,只是后缀是

.html

而已。

所以服务端要想将文件中的内容作为响应正文返回给客户端,就需要将文件中的内容读出来,并且以字符串的形式拼接到响应中。

图

Util.hpp

中再增加一个工具函数,如上图所示的

readFile

,该函数专门用来读取文件中的内容,并且以二进制的方式读取到缓冲区

buffer

中。

  • 必须以二进制的方式,如果以文本的形式读取图片就会出现bug。

tu

如上图代码所示,在

DealReq

中构建响应时,使用

readFile

工具函数读取请求中用户所指定路径中

html

文件中的内容。

如果读取失败,说明没有用户指定的文件,则读取

404.html

文件中的内容作为响应正文返回给客户端。

在调用

readFile

的时候,会传入

req.path

客户端指定的路径,以及

resp_body

,还有

req.size

,那么

path

size

是怎么来的呢?

可以看到,这两个参数都是

req

的成员变量,所以要在

httprequest

中处理:
图

获取客户端指定路径:

首先将

web

根目录定义出来,如上图代码所示的

default_root

就表示

web

根目录。

parse

函数中,先给

path

赋值为

default_root

path

的值为

./wwwroot

。再拼接请求中的

url

,例如请求是

http://127.0.0.1:8080/test/a.html

,将

/test/a.html

拼接到

path

中以后就成为

./wwwroot/test/a.html

如果请求中没有写

url

也就是采用默认的

/

,那么拼接以后

path

的值就是

./wwwroot/

表示访问

web

根目录,此时让其访问

home_page

首页。

获取html文件大小:

图
Linux中存在一个系统调用

stat

,就是用来获取文件大小的,如上图所示。

参数:

  • path:目标文件的路径。
  • buf:是一个struct stat类型的自定义变量,它的成员变量就有文件的大小(以字节为单位)。
  • 返回值:调用成功返回0,调用失败返回-1。

图
上图所示就是

struct stat

的定义,成员包含很多文件的属性,如文件的

inode

,硬链接数等等,而我们需要的就是

st_size

文件大小。

所以在

httpRequest

中通过系统调用

stat

来获取文件的大小,如上图代码中紫色框所示。

  • 如果文件不存在,就获取404.html文件的大小。

此时我们前端的网页以及后端的服务器都写好了,接下来就可以运行了。

tu
在Windows上的浏览器访问根目录,就会出现上图

大喵咪网址首页

,而在服务端可以看到这次请求,其中

url

/

,path

./wwwroot/index.html

图

用浏览器访问根目录下的

test/a.html

,如上图所示,会得到网页

我是a网页

。同样可以在服务端看到

url

/test/a.html

,path是

./wwwroot/test/a.html

访问

b.html

和上面一样,只是最后的文件名不一样而已。

tu
当访问一个不存在的网络资源时,就会得到

404

错误的网页,表示访问资源不存在。

此时就做到了服务器和网页的分离。

⚽请求方法

请求方法非常多,但是常用的就两种,分别是

GET

POST

方法。

tu
在Windows上的浏览器,百度的搜索框对应的

html

代码如上图所示。我们之所以能够搜索东西,是因为这个搜索框本质上是一个

form

表单,如上图红色框所示。

我们搜索的关键字就填入这个表单中,然后再将表单的内容一起通过

HTTP

协议发送请求到服务端,服务端再做出相应的响应。

  • 我们进行数据提交的时候,本质是前端要通过form表单提交,浏览器会自动将form表单中的内容转化成为GET/POST方法请求。

GET方法:

tu

index.html

中增加

form

表单,如上图代码所示,其中

action

是客户端提交信息后要服务端执行的动作,本喵这里是让服务端执行

/test/a

路径下的

python

程序。

method

是请求方法,这里本喵设置为

GET

方法。

提交的

form

表单中,第一行是姓名,第二行是密码,第三行是登录按钮。

tu
在浏览器访问根目录,也就是在访问服务器的

index.html

文件,如上图所示。此时是客户端第一次发起请求,所以服务端会将

index.html

的内容返回给客户端,此时返回的网页中包含

form

表单。

图
如上图所示,服务端收到的第一次请求是请求

./wwwroot/index.html

路径下的文件。

  • 第一次默认采用GET方法。

图
填好姓名和密码后登陆就会弹出如上图所示的网页,此时是客户端向服务端发送的第二次请求。

  • 提交后的action是让服务器执行/test/a/c.py下的文件,由于不存在,所以返回资源不存在。
  • form表单中的姓名和密码以xname=damiaomi&ypwd=123456的形式拼接在了url中,如上图网址中的绿色框。

图
如上图是服务器在客户端提交

form

表单后收到的请求,可以看到,表单中的内容被拼接到了

url

中。

POST方法:

图
如上图代码所示,将

index.html

中的

method

改成

POST

方法,其他内容不变。进行前面那样的操作:

图
第一次发起请求时同样访问服务端的根目录,上图所示的网页是服务端给客户端的第一次响应,也是包含一个

form

表单。

图
从服务器中看到,此次请求的方法仍然是

GET

方法,因为这是第一次请求,我们指定的

POST

方法是在提交表单时使用的,也就是在第二次请求中使用的。

tu
输入姓名密码后点击登录,输入的内容和前面一样,同样会出现资源不存在的网页,原因也是

action

的文件不存在。

  • 此时form表单中的内容没有拼接到url中,如上图红色框中所示。

图

如上图所示是服务器收到的第二次请求内容,可以看到,此时使用的是

POST

请求方法,

url

以及

path

都是截止到

action

指定的路径处,如上图红色框中所示。

  • form表单中的内容以请求正文的形式提交给了服务器,如上图橘色框中所示。

通过上面对比我们发现,

GET/POST

请求方法的区别在于:

  • GET方法通过url提交参数。
  • POST方法通过HTTP请求正文提交参数。

站在客户端的角度,如果使用

GET

方法,在提交

form

表单的时候,内容会拼接到

url

中,在浏览器的网址栏中可以看到,如果是账号密码的话其他人就能够直接看到。

如果使用

POST

方法,在提交

form

表单的时候,内容是通过请求正文提交的,在浏览器的网址栏中看不到。

所以,如果提交的数据是比较私密的,如账号密码等,就使用

POST

方法,如果无所谓的数据就使用

POST/GET

哪个都行。

  • 本喵并不是说POST方法比GET方法安全,仅仅是POST方法无法直观的看到提交的数据。
  • POST/GET两种方法都是不安全的。

⚽常见报头属性

tu
在使用

POST

方法发起HTTP请求后,多了几个

GET

方法中没有的属性,如上图所示。

Content-Type: xxx\r\n

表示连接类型,请求和响应中都有。

上图所示的是请求中的

Content-Type

,由于会用表单提交数据所以它的值如上图红色框中所示。这是浏览器在告诉服务器,它提交的请求类型是

form

,服务器需要按照表单的处理方式来处理。

图
上图所示是响应中的

Content-Type

,本喵在处理请求构建响应时,响应报头中只有一个属性

Content-Type: text/html\r\n

由于服务器响应给客户端浏览器的是一个

html

文件的内容,查阅一下

Content-Type

对照表:

如
如上图红色框中所示,

html

对应的

Content-Type

的值必须是

text/html

,这是在告诉浏览器,服务器给它的响应是

html

类型的数据,浏览器需要按照

html

的处理方式来解释。

Content-Length: XXX\r\n

使用

POST

方法发起请求时,服务器收到的请求中就有

Content-Length

这一属性,用来表示请求正文的长度,以字节为单位。

同样的,在构建响应的时候,也可以将这属性作为响应报头加进去,表示响应正文的长度,浏览器可以自动识别这一属性。

图

DealReq

函数中构建响应的时候,添加

Content-Length: xxx

属性,如上图红色框中所示,该属性的值就是服务器返回给客户端的文件大小,存放在

req

中。

然后将响应正文长度属性拼接到相应报头中,一起发出去。

图
此时使用

telnet

工具向服务器发起请求时,得到响应中包含

Content-Length

,如上图红色框中所示。在使用浏览器发起请求时,浏览器能自动识别

Content-Length

这一响应属性,并作相应处理。

⚽Connection: keep-alive

tu
此时

index.html

中有内容,有跳转网页,有图片,还有表单等等内容,如上图所示。

tu
浏览器访问根目录时,得到的网页中有这么多内容,如上图所示。

tu
在浏览器上本喵只输入了一次网址,然后回车,但是服务器上却收到了多个请求,如上图所示(本喵仅截图几个)。不同请求的

url

path

不同。

  • 一个网页中有多种类型的资源,而一次请求只能获取一种类型资源。
  • 虽然我们在浏览器中只访问一次,但是浏览器会通过多个线程发起多次请求。
  • 多次请求的多个响应共同组成了一个网页。

我们知道HTTP协议使用的是

TCP

网络通信那一套,所以客户端的每一个请求,服务器都会创建一个套接字。

像上面这种请求,仅访问一个网页就会创建多个套接字,会导致套接字资源紧张,所以就出现了长连接的解决方案。

长连接:

  • 一个客户端对应一个套接字,客户端的一个请求响应完后,套接字不关闭,只有客户端退出了,或者指定关闭时,套接字才关闭。
  • 一个客户端无论有多少个请求,都通过一个套接字和服务器进行网络通信。

这里本喵仅能从概念上来给大家讲解,无法做出具体的实验现象。

⚽会话保持(Cookie和Session)

Cookie技术:

假设访问CSDN使用的是HTTP协议。

CSDN在第一次登录后,之后打开CSDN就不用再进行登陆了。根据前面学习我们知道,登录时输入的信息其实就是

form

表单,然后将数据提交给服务器,让服务器进行鉴权,如果权限符合就会返回对应的响应。

  • HTTP实际上是一种无状态协议,每次请求并不会记录它曾经请求了什么。

所以,在第一次登录CSDN后,在站内进行网页跳转(从一篇文章到另一篇文章)时,理论上需要再次输入账号密码进行登录,让服务器进行鉴权,因为HTTP的每次请求/响应之间是没有任何关系的。

  • 但我们在使用浏览器访问CSDN的时候发现并不是这样的,只需要登录一次即可。

这是因为浏览器在我们第一次登录CSDN的时候,将我们的账号密码等登录信息保存了下来。

当我们进行网页跳转或者再次打开CSDN的时候,浏览器自动将保存的用户登录信息添加到了请求报头中,并通过HTTP协议发送给了服务器,服务器进行鉴权并返回对应的响应。

  • 登录还是需要的,只是浏览器帮我们做了这个事。
  • 这种技术就叫做Cookie技术。

图
如上图所示,点击网址前面的小锁,可以查看当前浏览器正在使用的

Cookie

当我们将上图中和CSDN有关的

Cookie

数据删除后就需重新输入账号密码来登录了。

  • 用户在第一次输入账号和密码时,浏览器会进行保存(Cookie),近期再次访问同一个网站(发送http请求),浏览器会自动将用户信息添加到报头中推送给服务器。
  • 这样只要用户首次输入密码,一段时间内将不用再做登录操作了。
Cookie

又分为内存级和文件级:

  • 内存级Cookie:将信息保存在浏览器的缓冲区中,当浏览器被关闭时,意味着进程结束,保存的信息也就没有了,重新打开浏览器后还需要重新登录。
  • 文件级Cookie:将信息保存在文件中,文件是放在磁盘上的,无论浏览器怎么打开关闭,文件中的信息都不会删除,在之后发送HTTP请求时,浏览器从该文件中读取信息并加到请求报头中。

根据日常使用浏览器的情况,我们可以知道,大部分情况下的

Cookie

都是文件级别的,因为关闭了浏览器下次打开不用再重新登录。

Cookie

文件也是存在我们电脑上的,具体路径有兴趣的小伙伴可以去找一下。


Session:

如果我们电脑上的

Cookie

文件被不法份子盗取,那么它就能以我们的身份去登录我们的CSDN,并且进行一些非法操作。

  • 为了保证信息安全,新的做法是将用户的账号密码信息以及浏览痕迹等信息保存在服务器上。
  • 每个用户对应一个文件,这个文件被叫做Session文件,由于存在很多的Session文件,所以给每个文件一个名字,叫做Session id
  • 服务器将Session id作为响应返回给用户,此时用户的Cookie中保存的就是这个id值。

图

如上图所示,当第一次登录时,浏览器的

form

表单中的用户信息提交到了服务器,服务器创建

Session

文件并保存用户信息,然后再生成一个

id

返回给浏览器。

此时浏览器的

Cookie

保存的就是这个

id

值。当进行站内页面跳转或者再次打开CSDN的时候,浏览器自动将

Cookie

Session id

加到请求报头中提交给服务端。

服务端收到请求后,拿到请求报头中的

Session id

找到对应的

Session

文件进行鉴权,看看身份是否符合。

鉴权完毕后做出相应的响应返回给浏览器。

  • 服务端存储用户信息的技术就叫做Session技术。

为什么新的会话保持(

Cookie和Session

)技术能够提高用户信息的安全性呢?

  • 服务端是由专业的人员维护的,服务器中存在病毒以及流氓软件的可能想更小,所以用户信息在服务端会更安全。
  • 如果客户端的Cookie中的Session id被盗用,不法分子使用该id向服务端发起请求时,会因为常用IP地址不一样而被服务端强制下线,此时只有手里真正有账号密码的人才能够再次登录。

保证

Session

安全的策略非常多,有兴趣的小伙伴可以自行了解。


**写入

Cookie

信息:**

我们知道,浏览器的

Cookie

信息是服务端响应返回的,所以在我们构建响应的时候也可以构建

Cookie

信息让浏览器去保存。

图

DealReq

函数中构建响应时,设置

Cookie

信息,内容是

name=123456abc

,有效时间是三分钟,然后加到响应报头中返回给客户端,如上图所示。

图
使用浏览器访问根目录的时候,如上图所示,会得

index.html

文件表示的网页,查看该网页的

Cookie

信息,可以看到

name

123456abc

,有效时间是3分钟,和我们在服务端构建响应时写的内容一模一样。

  • 浏览器将我们在响应中设置的Cookie内容当作了Session id

真正生成

Session id

是有一套复杂的算法的,它能够保证每一个

Session

文件的

id

都是独一无二的。

  • CookieSession两种技术共同组成了HTTP的会话保持。

⚽HTTP状态码

图
本喵在

DealReq

中构建响应的时候,状态行中的状态码直接写的

200

,状态码描述是

OK

。那么状态码到底有哪些呢?它们代表的意义是什么?

状态码有五种类型,分别以1~5开头:
状态码类别原因短语1XXinforma(信息性状态码)接收的请求正在处理2XXSuccess(成功状态码)请求正常且处理完毕3XXRedirection(重定向状态码)需要进行附加操作以完成请求4XXClient Error(客户端错误状态码)服务器无法处理请求5XXServer Error(服务器错误状态码)服务器处理请求出错
最常见的一些状态码,如200(OK),404(Not Found),403(Forbidden请求权限不够),302(Redirect),504(Bad Gateway)。

重定向状态码(3XX):

这些状态码没啥好说的,重点说一下重定向。

  • 重定向就是将网络请求重新定个方向转到其它位置(跳转网站),此时这个服务器相当于提供了一个引路的服务。

相信都有过这样的经历,打开一个网址以后,自动就弹出一些广告网页,这就是一种重定向。

  • 浏览器发送请求给服务端,服务端返回一个新的url,并且状态码是3XX,浏览器会自动用这个新的url向新地址的服务端发起请求。

所以说,重定向是由客户端完成的,当客户端浏览器收到的响应中状态码是

3XX

后,它就会自动从响应中寻找返回的新的

url

并发起请求。

重定向又有两种:

  • 永久重定向:状态码为301
  • 临时重定向:状态码为302307

临时重定向和永久重定向本质是影响客户端的标签,决定客户端是否需要更新目标地址。

如果某个网站是永久重定向,那么第一次访问该网站时由浏览器帮你进行重定向,但后续再访问该网站时就不需要浏览器再进行重定向了,此时直接访问的就是重定向后的网站。

而如果某个网站是临时重定向,那么每次访问该网站时都需要浏览器来帮我们完成重定向跳转到目标网站。

图
如上图代码所示,在

DealReq

函数中构建响应时,状态行中的状态码设为

307

,状态码描述为

Temporary Redirect

,表示临时重定向。

设置属性

Location: XXX

,其值是重定向后的地址,这里是本喵CSDN的首页地址。

然后将属性

Location

拼接到响应报头中,最后再发送给客户端。

图

在浏览器中访问

index.html

,但是发起请求后,返回的并不是

index.html

中的内容,而且属性

Location

的值所指向的网页,也就是本喵CSDN的首页。

图
当浏览器发起请求访问

index.html

的时候,收到的响应中,状态码是

307

,所以浏览器不解释

index.html

,而是根据响应报头中的

Location

属性,得到新的

url

,再向新的

url

发起请求,得到新的响应,也就是本喵的CSDN首页。

  • 永久重定向无法演示出来,效果和临时重定向一样。

🏀总结

这篇文章的思路是,先宏观介绍HTTP协议的格式,然后通过代码去验证这个格式,然后再把验证结果中的具体属性进行讲解。一些重点属性会单独举例进行讲解。


本文转载自: https://blog.csdn.net/weixin_63726869/article/details/131964690
版权归原作者 一只大喵咪1201 所有, 如有侵权,请联系我们删除。

“【网络】应用层——HTTP协议”的评论:

还没有评论