目录
一、HTTP与URL
1、HTTP协议
2、URL
3、URL编码
4、报文与报头
报文(Message)
报头(Header)
二、HTTP(超文本传输协议)的内部运作机理
请求部分:
响应部分:
三、实现HTTP服务器
Sock.hpp
HttpServer.hpp
Utill.hpp
Usage.hpp
HttpServer.cc
四、GET方法和POST方法
1、GET方法
基本特点:
应用场景:
测试:
2、POST方法
基本特点:
应用场景:
总结
五、状态码
200 OK
404 Not Found
403 Forbidden
302 Redirect (Found)
504 Gateway Timeout
六、Header
1. Content-Type
2. Content-Length
3. Host
4. User-Agent
5. Referer
6. Location
7. Cookie
测试
七、特征
一、HTTP与URL
1、HTTP协议
HTTP(HyperText Transfer Protocol)是一种广泛应用的面向事务的应用层协议,用于在万维网(World Wide Web,简称Web)上传输数据。它是互联网上最常用的数据交换协议之一,主要负责客户端(如Web浏览器)与服务器端(如Web服务器)之间的通信。HTTP协议的设计使得用户能够通过浏览器轻松访问、获取和呈现超文本(如HTML文档、图像、视频、脚本等资源),从而构成了现代Web体验的基础。
特性与工作原理:
-
基于请求-响应模型:HTTP协议采用客户端发起请求,服务器端响应请求的模式。客户端(通常是Web浏览器)发送一个HTTP请求到指定的服务器,服务器接收到请求后处理并返回一个HTTP响应给客户端。
-
无状态:HTTP协议本身不保存任何关于之前请求或响应的状态信息。这意味着每次请求都是独立的,服务器不会记住客户端的上下文。若需要保持状态,通常会借助于cookies、session或其他状态管理机制。
-
灵活的头信息:HTTP请求和响应均包含头部(headers),用于传递附加信息,如内容类型、压缩算法、缓存指示、认证凭证、代理指令等。这些头信息对客户端和服务器间的交互提供了丰富的控制手段。
-
支持多种方法:HTTP定义了一系列方法(也称作动词),如GET、POST、PUT、DELETE、HEAD、OPTIONS等,用于表示不同的操作意图。例如,GET用于获取资源,POST用于提交数据,PUT用于更新资源,DELETE用于删除资源。
-
状态码:HTTP响应中包含一个三位数字的状态码,用来表示请求的处理结果。如200表示成功,404表示未找到资源,500表示服务器内部错误等。
-
内容协商:HTTP允许客户端和服务器就响应的内容格式、语言、编码等进行协商,以满足客户端的个性化需求。
版本演进:
- HTTP/1.0:最初的HTTP版本,提供了基本的请求-响应机制和报文格式。
- HTTP/1.1:目前最广泛使用的版本,引入了持久连接、管道化、分块传输、缓存控制、Host头等改进,提高了Web性能。
- HTTP/2:大幅优化了传输效率,支持多路复用、头部压缩、服务器推送等特性,降低了延迟并节省了带宽。
- HTTP/3:基于QUIC协议,进一步优化了性能,尤其是减少了网络丢包对连接的影响,并实现了加密传输的默认化。
2、URL
URL,(Uniform Resource Locator)全称为统一资源定位符,是互联网上资源的标准地址,通常表现为我们在浏览器地址栏中输入的“网址”。URL由以下几个部分组成:
https://www.example.com:8080/path/to/resource?key1=value1&key2=value2#section1
-
协议(Scheme):指明访问资源所使用的协议,如
http://
、https://
、ftp://
等。在Web环境中,最常见的就是HTTP和HTTPS(安全的HTTP)。 -
主机名(Host):标识提供资源的服务器域名或IP地址,如
www.example.com
或192.168.1.1
。 -
端口(Port):可选部分,用于指定服务器上的服务端口。如果省略,默认情况下HTTP使用端口80,HTTPS使用端口443。
-
路径(Path):标识服务器上资源的具体位置,由斜杠(
/
)分隔的一系列层次结构。如/path/to/resource
。 -
查询参数(Query Parameters):可选部分,通过问号(
?
)开始,以键值对形式列出附加信息,各对之间用&
分隔。如?key1=value1&key2=value2
。 -
片段标识符(Fragment Identifier):可选部分,以井号(
#
)开始,用于指向资源内部的特定位置或命名锚点。浏览器通常在加载完页面后自行处理这部分。
示例解析:
https://www.example.com:8080/path/to/resource?key1=value1&key2=value2#section1
- 协议:
https://
- 主机名:
www.example.com
- 端口:
8080
- 路径:
/path/to/resource
- 查询参数:
key1=value1&key2=value2
- 片段标识符:
#section1
3、URL编码
URL编码(URL encoding),又称为百分号编码(Percent-encoding),是一种将特殊字符和非ASCII字符转换为可以在URL中安全传递的格式的方法。当某些字符在URL中具有特殊含义(如 /
, ?
, :
, 等)或不是标准ASCII字符时,为了确保它们能够被正确解析且不会引起歧义,需要进行URL编码。
转义规则如下:
-
确定需要转义的字符:
- 特殊字符:包括空格(
/
,?
,:
,@
,&
,=
,+
,$
,,
,;
,#
,[
,]
,'
,(
,)
,{
,}
,<
,>
,"
,%
)以及控制字符等,这些字符在URL中有特定用途或可能引起解析错误。 - 非ASCII字符:对于非英文字符(如中文、日文、韩文等),以及其他不在ASCII字符集中的字符,也需要进行URL编码。
- 特殊字符:包括空格(
-
将字符转为UTF-8编码的字节序列:
- 对于非ASCII字符,通常先将其转换为Unicode编码(如UTF-8),然后再进行URL编码。UTF-8是一种变长编码,不同字符可能由1至4个字节组成。
-
将每个字节转为16进制表示:
- 对于每个字节,将其二进制值转换为两位的十六进制数。例如,字节值0xA3转为十六进制为“
A3
”。
- 对于每个字节,将其二进制值转换为两位的十六进制数。例如,字节值0xA3转为十六进制为“
-
添加百分号前缀:
- 在每个字节的十六进制表示前添加百分号(
%
)作为转义符号。例如,字节值0xA3经过URL编码后变为“%A3
”。
- 在每个字节的十六进制表示前添加百分号(
-
组合多个字节的编码:
- 如果原始字符转换为多个字节(如多字节UTF-8编码),则每个字节独立进行上述步骤,得到一串以
%
开头的十六进制编码序列。例如,一个由两个字节组成的UTF-8编码字符,其URL编码结果可能是“%AB%CD
”。
- 如果原始字符转换为多个字节(如多字节UTF-8编码),则每个字节独立进行上述步骤,得到一串以
举例说明:
假设要编码字符“/
”,其ASCII值为0x2F。按照规则:
- 字符“
/
”是需要转义的特殊字符。 - “
/
”已经是ASCII字符,无需转换为UTF-8。 - 直接将其ASCII值0x2F转换为十六进制“
2F
”。 - 添加百分号前缀,得到“
%2F
”。
所以,字符“/
”经过URL编码后为“%2F
”。
再比如,要编码汉字“中国”,其UTF-8编码为三个字节:E4 B8 AD E5 9B BD。按照规则:
- 字节E4转为
%E4
- 字节B8转为
%B8
- 字节AD转为
%AD
- 字节E5转为
%E5
- 字节9B转为
%9B
- 字节BD转为
%BD
因此,汉字“中国”的URL编码结果为“%E4%B8%AD%E5%9B%BD
”。
解码(URLDecode)过程:
URL解码则是上述过程的逆向操作:
-
识别百分号编码:找到URL中以
%
开头,后跟两位十六进制数的序列。 -
将十六进制数转换为字节:将每个这样的序列(
%XX
)解析为对应的字节值(即把十六进制数转换为二进制)。 -
合并字节序列:对于多字节字符(如UTF-8编码的非ASCII字符),将连续的字节序列合并。
-
解码字节到字符:对于ASCII字符,字节值直接对应字符;对于非ASCII字符,将字节序列按照相应的字符编码(如UTF-8)解码为字符。
通过URL编码和解码,可以确保包含特殊字符和非ASCII字符的文本能够在URL中正确传递,同时保持数据的可解析性。在实际应用中,如网页表单提交、API请求参数传递等场景,常常需要使用urlencode
函数对参数进行编码,而服务器端则使用urldecode
函数对收到的URL参数进行解码,恢复原始数据。
4、报文与报头
在计算机网络通信中,报文(Message)和报头(Header)是两个核心概念,分别代表了数据传输的基本单元和其中携带的控制信息。下面对这两个概念进行详细讲解:
报文(Message)
定义与作用: 报文是网络通信中发送和接收的基本单位,它封装了一次完整的信息交换内容。报文可以包含各种类型的数据,如文本、图片、音频、视频、软件包、数据库记录等,以及与这些数据相关的控制信息。报文在传输过程中作为一个整体被处理,到达目的地后,接收方根据报文内容进行相应的解析和处理。
报文的构成:
-
报头(Header):报文的起始部分,包含了控制、描述和辅助传输该报文的信息。报头内容因协议不同而异,通常包括源地址、目的地址、协议版本、报文长度、序列号、校验和、时间戳、优先级、选项等字段。
-
有效载荷(Payload):也称为数据区或正文,是报文的主要内容,承载了实际要传输的信息。有效载荷的具体格式和内容取决于应用层协议的要求,如HTTP请求/响应的HTML、JSON、XML数据,FTP传输的文件内容,SMTP发送的电子邮件正文等。
-
尾部(Trailer):在某些协议中(如HTTP/2),报文可能包含尾部,用于存放额外的元数据或结束标记。尾部并不常见,很多协议的报文仅包含报头和有效载荷两部分。
报文的生命周期:
- 生成:发送方根据应用需求构造报文,设置报头字段,填充有效载荷。
- 封装:报文在传输层(如TCP、UDP)被封装成数据段(Segment),在数据链路层(如Ethernet)进一步封装成帧(Frame),并添加必要的网络层(如IP)、链路层地址和控制信息。
- 传输:封装后的报文通过物理网络(如以太网、Wi-Fi、光纤等)从发送方传输到接收方。
- 解封装:接收方从底层到高层逐层剥离封装信息,还原出原始报文。
- 解析:接收方根据报文类型和报头信息,对报文进行解析,提取有效载荷并进行相应处理。
报头(Header)
定义与作用: 报头是报文的控制部分,包含了关于报文本身、发送者、接收者、传输要求、安全信息等关键数据,用于指导报文在网络中的正确传输、路由、处理和接收。报头信息对网络设备(如路由器、交换机、防火墙)和接收端软件(如Web服务器、邮件客户端)进行决策至关重要。
常见的报头字段:
- 源地址:标识报文的发送者,如IP地址、MAC地址、邮箱地址等。
- 目的地址:标识报文的接收者,与源地址类似。
- 协议版本:表明报文遵循的协议版本,确保双方能正确解析报文。
- 报文长度:标明报文总长度,有助于接收方判断何时完成报文接收。
- 序列号:在有序传输中,用于标识报文在数据流中的位置,确保正确重组。
- 校验和:计算报文内容的校验值,用于检测传输过程中是否发生错误。
- 时间戳:记录报文生成或发送的时间,用于测量延迟、超时或进行时间同步。
- 优先级:指示报文的处理优先级,适用于实时通信或服务质量(QoS)管理。
- 选项:携带额外的控制信息,如最大报文大小、窗口大小、加密算法等。
报头的类型:
- 通用报头:适用于多种协议或应用,如IP报头、TCP报头。
- 专用报头:针对特定协议或应用定制,如HTTP报头、SMTP报头、FTP报头。
报头的处理:
- 添加:发送方在构造报文时,根据协议规范和应用需求设置报头字段。
- 解析:中间设备和接收方依据报头信息做出路由选择、流量控制、错误检测等决策。
- 更新:在传输过程中,某些报头字段可能被中间设备(如路由器)更新,如TTL(Time to Live)字段。
- 移除:在解封装过程中,报头信息被逐层剥离,最终仅保留应用层报头供接收端处理。
综上所述,报文是网络通信的基本单元,包含了报头和有效载荷两部分。报头作为报文的控制部分,提供了关于报文传输、路由、处理和接收所需的关键信息。理解和正确处理报文及报头对于构建健壮、高效的网络应用程序至关重要。
二、HTTP(超文本传输协议)的内部运作机理
这张图文并茂的示意图深入剖析了HTTP(超文本传输协议)的内部运作机理,它构建了Web浏览器与服务器之间信息交换的桥梁。下面,我们对其中的各个关键组件进行细致解读。
请求部分:
POST http://job.xjtu.edu.cn/companyLogin.do HTTP/1.1
Host: job.xjtu.edu.cn
Connection: keep-alive
Content-Length: 36
Cache-Control: max-age=0
Origin: http://job.xjtu.edu.cn
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: http://job.xjtu.edu.cn/companyLogin.do
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8
Cookie: JSESSIONID=D628A75845A74D29D991DB47A461E4FC; Hm_lvt_783e83ce0ee350e23a9d389df580f658=1504963710,1506661798: Hm_lpvt_783e83ce0ee350e23a9d389df580f658=1506661802
username=hgtz2222&password=222222222
-
方法:这是HTTP请求的核心指令,指示服务器执行特定操作。常见的方法包括:
GET
:请求获取指定资源。POST
:向指定资源提交数据,通常导致服务器端状态的改变。PUT
:替换指定资源的内容。DELETE
:请求删除指定资源。 这些方法定义了客户端与服务器之间的交互模式,指导数据的检索、更新、创建或删除行为。
-
URL:全称为统一资源定位符(Uniform Resource Locator),它指定了请求的目标资源在网络上的唯一地址。URL包含了协议类型(如
http://
或https://
)、主机名(如www.example.com
)、端口(默认情况下可省略,如:80
或:443
)、路径(如/path/to/resource
)以及可能的查询参数(如?param=value&anotherParam=anotherValue
)。这些元素共同定位了客户端希望从服务器获取或操作的具体信息源。 -
HTTP版本:标识请求使用的HTTP协议版本,如
HTTP/1.1
或HTTP/2
。不同的版本提供了不同的性能优化、功能扩展及错误处理机制,确保协议随着互联网技术的发展而不断演进。 -
请求头:一组键值对形式的字段,传递与请求本身或客户端环境相关的重要信息。常见的请求头包括:
Host: job.xjtu.edu.cn Connection: keep-alive Content-Length: 36 Cache-Control: max-age=0 Origin: http://job.xjtu.edu.cn Upgrade-Insecure-Requests: 1 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Referer: http://job.xjtu.edu.cn/companyLogin.do Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.8 Cookie: JSESSIONID=D628A75845A74D29D991DB47A461E4FC; Hm_lvt_783e83ce0ee350e23a9d389df580f658=1504963710,1506661798: Hm_lpvt_783e83ce0ee350e23a9d389df580f658=1506661802
-
Host:指定请求的目标主机名,这里是
job.xjtu.edu.cn
,虽然已经在URL中给出,但作为单独的头字段发送是为了支持通过代理服务器转发请求时保持正确的目的地信息。 -
Connection:设置连接管理策略,
keep-alive
表示希望在完成本次请求后保持TCP连接开放,以便后续请求复用,减少建立新连接的开销。 -
Content-Length:声明请求正文的字节长度,这里是
36
,表示即将发送的数据大小。 -
Cache-Control:控制缓存行为,
max-age=0
指示任何缓存的响应都应视为过期,必须重新从服务器获取。 -
Origin:指出请求发起的源站点URL,用于CORS(跨源资源共享)场景的安全检查。
-
Upgrade-Insecure-Requests:客户端提示服务器如果可能,将HTTP请求升级为HTTPS,增强安全性。
-
Content-Type:定义请求正文的数据格式,此处为
application/x-www-form-urlencoded
,表示正文包含URL编码的键值对形式的数据。 -
User-Agent:描述发起请求的客户端软件及其版本,这里是Chrome浏览器的具体版本信息。
-
Accept:列出客户端能够处理的响应内容类型及其优先级,表明客户端希望接收HTML、XML、图像等多种类型的数据。
-
Referer:提供当前请求的来源页面URL,常用于统计、日志记录或防盗链等目的。
-
Accept-Encoding:声明客户端能解码的压缩编码方式,如
gzip
和deflate
,有助于服务器选择合适的压缩算法以减少响应数据传输量。 -
Accept-Language:指定客户端首选的语言和地区,服务器可根据此信息提供相应语言的响应内容。
-
Cookie:包含客户端存储的由服务器先前设置的cookie值,用于维持会话状态、个性化设置等。在这个例子中,有两组cookie值,分别与JSESSIONID和Hm_lvt、Hm_lpvt相关,可能涉及会话管理和网站分析功能。
-
-
空行:在请求头列表之后,一个明显的空行标志着请求头部分的结束。它的存在为报文解析器提供了明确的分隔标志,以便识别后续的有效载荷。
-
请求正文:对于某些方法(如
POST
、PUT
),请求正文承载了发送给服务器的实际数据。这可以是表单数据(通常以application/x-www-form-urlencoded
或multipart/form-data
格式编码)、JSON对象、XML文档、文件上传或其他二进制数据。请求正文内容的格式和存在与否取决于所使用的HTTP方法及相关的请求头设置。
响应部分:
HTTP/1.1 200 OK
Server: YxlinkWAF
Content-Type: text/html;charset=UTF-8
Content-Language: zh-CN
Transfer-Encoding: chunked
Date: Fri, 29 Sep 2017 05:10:13 GMT
<!DOCTYPE html>
<html>
<head>
<title>test</title>
<meta name="viewport" content="width=device-width, initial-scale=1.">
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<meta http-equiv="content-Type" content="text/html; charset=utf-8” />
<link rel="shortcut icon" href="/renovation/images/icon.ico">
<link href="/renovation/css/main.css" rel="stylesheet" media="screen" />
<link href="/renovation/css/art default.css" rel="stylesheet" media="screen"
/2
<link href="/renovation/css/font-awesome.css" rel="stylesheet" media="screen" />
<script type="text/javascript" src="/renovation/js/jquery1.7.1.min.js"></script>
<script type="text/iavascript" src="/renovation/is/main.is"></script><!--main-->
<link href="/style/warmTipsstyle.css" rel="stylesheet" type="text/css">
</head>
-
HTTP版本:服务器在响应中表明其所遵循的HTTP协议版本,与请求方保持一致,确保双方能够理解和处理彼此的消息。
-
状态码:三位数字形式的代码,简洁地传达请求处理的结果。常见的状态码包括:
200 OK
:请求成功,预期的响应已随消息体返回。404 Not Found
:请求的资源在服务器上不存在。500 Internal Server Error
:服务器遇到意外情况,无法完成请求。 状态码系统为客户端提供了快速评估请求结果和采取后续行动的基础。
-
状态码描述:与状态码关联的简短文本,进一步解释状态码的含义,便于人类阅读和理解。
-
状态行:将上述HTTP版本、状态码和状态码描述合并成一行,如
HTTP/1.1 200 OK
,构成了响应的首行,即状态行,它是响应消息的总览。 -
响应头:类似于请求头,响应头也是一组键值对,提供关于响应内容、服务器属性、缓存策略等附加信息。一些常见响应头包括:
Content-Type
:定义响应正文的媒体类型和字符集,如text/html; charset=UTF-8
。Cache-Control
:指示客户端如何缓存响应内容,控制缓存有效期、重验证策略等。 这些头字段帮助客户端正确解析、存储和管理接收到的数据。
-
空行:响应头之后的空行同样作为分隔符,标志着响应头部分的结束,接下来将开始响应正文。
-
响应正文:包含服务器实际返回给客户端的数据,可能是HTML页面、JSON数据、图像、音频、视频等任何类型的媒体。其具体内容、格式和大小取决于请求的目标资源以及响应头中的
Content-Type
指示。
报文结构与传输:
-
HTTP请求和响应都是通过报文来传递的,每个报文由报头和有效载荷两部分组成。报头包含了元信息,有效载荷则承载实际数据。两者间以一个空行清晰划分,确保报文在传输过程中能够被准确解析。
-
HTTP依赖于可靠的传输层协议TCP(传输控制协议)进行数据交换,确保数据包按序、无丢失且无错地到达目的地。TCP提供的连接建立、数据流控、错误检测和重传机制为HTTP通信提供了坚实的基础。
三、实现HTTP服务器
接下来我们实现了一个简单的基于多进程的HTTP服务器,可以接受客户端的HTTP请求,并返回相应的HTML内容。
-
Sock.hpp:定义了一个名为Sock的类,用于封装Socket编程相关的操作,包括创建Socket、绑定地址、监听连接、接受连接以及连接远程服务器等功能。
-
HttpServer.hpp:定义了一个名为HttpServer的类,用于创建HTTP服务器。该类包含一个成员函数Start,用于启动HTTP服务器,并不断接受客户端连接。当有新的客户端连接到达时,会创建一个子进程来处理该连接,实现了多进程并发处理客户端请求的功能。
-
Usage.hpp:定义了一个名为Usage的函数,用于显示程序的使用说明。
-
Util.hpp:定义了一个名为Util的类,包含了一些通用的工具函数,比如cutString函数用于按指定分隔符切割字符串。
-
HttpServer.cc:主程序文件,包含了main函数和HTTP请求处理函数HandlerHttpRequest。在main函数中,首先检查命令行参数数量是否正确,然后创建一个HttpServer对象,传入监听端口号和处理HTTP请求的回调函数。最后调用HttpServer对象的Start方法启动服务器。HTTP请求处理函数HandlerHttpRequest负责解析HTTP请求、读取请求的文件内容并发送HTTP响应。
Sock.hpp
这个 Sock
类封装了创建、配置、监听以及连接 TCP 套接字的基本操作。
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <unistd.h>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <ctype.h>
#include "Log.hpp"
class Sock
{
private:
const static int gbacklog = 20;
public:
Sock() {}
int Socket()
{
int listensock = socket(AF_INET, SOCK_STREAM, 0);
if (listensock < 0)
{
logMessage(FATAL, "create socket error, %d:%s", errno, strerror(errno));
exit(2);
}
logMessage(NORMAL, "create socket success, listensock: %d", listensock);
return listensock;
}
void Bind(int sock, uint16_t port, std::string ip = "0.0.0.0")
{
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port);
inet_pton(AF_INET, ip.c_str(), &local.sin_addr);
if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0)
{
logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));
exit(3);
}
}
void Listen(int sock)
{
if (listen(sock, gbacklog) < 0)
{
logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));
exit(4);
}
logMessage(NORMAL, "listen success");
}
int Accept(int listensock, std::string *ip, uint16_t *port)
{
struct sockaddr_in src;
socklen_t len = sizeof(src);
int servicesock = accept(listensock, (struct sockaddr *)&src, &len);
if (servicesock < 0)
{
logMessage(FATAL, "accept error, %d:%s", errno, strerror(errno));
exit(5);
}
if (port)
*port = ntohs(src.sin_port);
if (ip)
*ip = inet_ntoa(src.sin_addr);
return servicesock;
}
bool Connect(int sock, const std::string &server_ip, const uint16_t &server_port)
{
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(server_port);
inet_pton(AF_INET, server_ip.c_str(), &server.sin_addr);
if (connect(sock, (struct sockaddr *)&server, sizeof(server)) == 0)
return true;
else
return false;
}
~Sock(){}
};
HttpServer.hpp
#pragma once
#include <iostream>
#include <signal.h>
#include <functional>
#include "Sock.hpp"
class HttpServer
{
public:
using func_t = std::function<void(int)>;
private:
int listensock_;
uint16_t port_;
Sock sock;
func_t func_;
public:
HttpServer(const uint16_t &port, func_t func) : port_(port), func_(func)
{
listensock_ = sock.Socket();
sock.Bind(listensock_, port_);
sock.Listen(listensock_);
}
void Start()
{
signal(SIGCHLD, SIG_IGN);
while (1)
{
std::string clientIp;
uint16_t clientPort = 0;
int sockfd = sock.Accept(listensock_, &clientIp, &clientPort);
if (sockfd < 0)
continue;
if (fork() == 0)
{
close(listensock_);
func_(sockfd);
close(sockfd);
exit(0);
}
close(sockfd);
}
}
~HttpServer()
{
if (listensock_ >= 0)
close(listensock_);
}
};
类定义与依赖:
class HttpServer
{
public:
using func_t = std::function<void(int)>;
private:
int listensock_;
uint16_t port_;
Sock sock;
func_t func_;
public:
// ...
};
HttpServer
类使用了std::function<void(int)>
类型别名func_t
来表示处理客户端连接的回调函数。- 类内部包含以下私有成员变量:
int listensock_
: 存储监听套接字的文件描述符。uint16_t port_
: 服务器监听的端口号。Sock sock
: 实例化的Sock
类对象,用于封装网络操作(创建、绑定、监听套接字等)。func_t func_
: 存储用户提供的处理客户端连接的回调函数。
构造函数:
HttpServer(const uint16_t systemport, func_t func)
: port_(port), func_(func)
{
listensock_ = sock.Socket();
sock.Bind(listensock_, port_);
sock.Listen(listensock_);
}
- 构造函数接受两个参数:服务器要监听的端口号
systemport
和处理客户端连接的回调函数func
。 - 它首先将传入的端口号和回调函数赋值给类的相应私有成员。
- 然后通过调用
Sock
类的方法创建、绑定和监听服务器套接字,并将监听套接字的文件描述符存储在listensock_
中。
成员方法:
void Start()
{
signal(SIGCHLD, SIG_IGN);
for (;;) // 无限循环,保持服务器运行
{
// ... 接收客户端连接、处理请求、关闭连接等逻辑 ...
}
}
~HttpServer()
{
if (listensock_ >= 0) close(listensock_);
}
Start()
方法启动 HTTP 服务器的主循环,无限等待并处理客户端连接。它首先忽略子进程退出信号SIGCHLD
,然后进入循环,每次循环执行如下操作:- 调用
sock.Accept()
接受一个客户端连接,创建一个新的子进程来处理该连接。 - 子进程中,关闭监听套接字,调用用户提供的回调函数
func_
处理客户端请求,然后关闭连接并退出子进程。 - 主进程中,关闭已由子进程接管的客户端连接套接字。
- 调用
- 析构函数
~HttpServer()
在服务器对象销毁时确保关闭监听套接字。
Utill.hpp
Util.hpp
文件定义了一个名为 Util
的类,该类包含一个静态成员函数 cutString
。这个函数的主要目的是将输入字符串按照指定的分隔符分割成多个子字符串,并将这些子字符串存储在一个已存在的 std::vector<std::string>
中。
#pragma once
#include <iostream>
#include <vector>
class Util
{
public:
// aaaa\r\nbbbbb\r\nccc\r\n\r\n
static void cutString(std::string s, const std::string &sep, std::vector<std::string> *out)
{
std::size_t start = 0;
while (start < s.size())
{
auto pos = s.find(sep, start);
if (pos == std::string::npos)
break;
std::string sub = s.substr(start, pos - start);
out->push_back(sub);
start += sub.size() + sep.size();
}
if (start < s.size())
out->push_back(s.substr(start));
}
};
静态成员函数 cutString:
-
参数:
std::string s
: 待分割的输入字符串。const std::string &sep
: 分割字符串的分隔符。std::vector<std::string> *out
: 一个指向已存在的std::vector<std::string>
的指针,用于存放分割后得到的子字符串。
-
功能:
- 函数遍历输入字符串
s
,查找指定分隔符sep
。每当找到一个分隔符时,它将截取从上次分割点到当前分隔符之间的子字符串,并将其添加到out
向量中。 - 如果输入字符串结尾没有分隔符,函数还会将最后一个子字符串(从最后一个分隔符到字符串末尾,或者整个字符串如果没有遇到分隔符)添加到
out
向量中。
- 函数遍历输入字符串
-
实现细节:
- 使用
start
变量记录当前分割点的位置。 - 在一个无限循环中,使用
std::string::find
查找下一个分隔符位置pos
。 - 如果找到分隔符,则截取子字符串
sub
(从start
到pos - 1
),将其添加到out
向量,并更新start
为sub
的结束位置加上分隔符的长度。 - 当找不到更多分隔符时(即
pos
为std::string::npos
),跳出循环。 - 最后,如果
start
仍在字符串有效范围内,说明输入字符串末尾还有未分割的部分,此时将这部分内容添加到out
向量中。
- 使用
Usage.hpp
#pragma once
#include <iostream>
#include <string>
void Usage(std::string proc)
{
std::cout << "\nUsage: " << proc << " port\n" << std::endl;
}
HttpServer.cc
HttpServer.cc
文件实现了基于C++的简单HTTP服务器,监听指定端口,接收客户端请求,解析请求路径,从本地Web根目录查找对应文件,根据文件是否存在构建HTTP响应,并将响应发送回客户端。
#include <iostream>
#include <memory>
#include <cassert>
#include <fstream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "HttpServer.hpp"
#include "Usage.hpp"
#include "Util.hpp"
// 一般http都要有自己的web根目录
#define ROOT "./wwwroot" // ./wwwroot/index.html
// 如果客户端只请求了一个/,我们返回默认首页
#define HOMEPAGE "index.html"
//处理HTTP请求的回调函数
void HandlerHttpRequest(int sockfd)//已连接的套接字描述符,用于与客户端通信
{
// 1. 读取请求 for test
char buffer[10240]; // 缓冲区,用于暂存接收到的HTTP请求数据
ssize_t s = recv(sockfd, buffer, sizeof(buffer) - 1, 0); // 接收请求数据
if (s > 0)
{
buffer[s] = 0; // 添加终止符,便于后续字符串操作
}
//std::cout << buffer << "\n--------------------\n" << std::endl;
std::vector<std::string> vline; // 用于存储请求数据按行分割后的结果
Util::cutString(buffer, "\n", &vline); // 按换行符分割请求数据
std::vector<std::string> vblock; // 用于存储请求行按空格分割后的结果
Util::cutString(vline[0], " ", &vblock); // 按空格分割请求行
std::string file = vblock[1]; // 请求的文件路径(如 /index.html)
std::string target = ROOT; // 实际要访问的文件路径(基于Web根目录)
if(file == "/") file = "/index.html"; // 若请求路径为 /,则替换为默认首页
target += file; // 拼接实际文件路径
std::cout << target << std::endl; // 输出实际文件路径(调试用)
std::string content; // 存储读取到的文件内容
std::ifstream in(target); // 打开目标文件
if(in.is_open())
{
std::string line;
while(std::getline(in, line))
{
content += line;
}
in.close(); // 关闭文件
}
std::string HttpResponse; // 存储待发送的HTTP响应
if(content.empty()) // 若文件未找到或读取失败
{
HttpResponse = "HTTP/1.1 404 NotFound\r\n"; // 构建404响应状态码和消息
}
else
{
HttpResponse = "HTTP/1.1 200 OK\r\n"; // 构建200响应状态码和消息
}
HttpResponse += "\r\n"; // 添加空行分隔状态行与响应体
HttpResponse += content; // 追加文件内容至响应体
// 下面的注释代码已省略,用于调试输出请求行字段
// 发送HTTP响应至客户端
send(sockfd, HttpResponse.c_str(), HttpResponse.size(), 0);
}
int main(int argc, char *argv[])
{
if (argc != 2) // 检查命令行参数数量
{
Usage(argv[0]); // 参数不足时打印使用说明
exit(0); // 退出程序
}
// 创建并启动HTTP服务器
std::unique_ptr<HttpServer> httpserver(new HttpServer(atoi(argv[1]), HandlerHttpRequest)); // 创建HttpServer对象,传入端口号和请求处理器
httpserver->Start(); // 启动服务器
return 0; // 程序正常结束
}
-
包含头文件: 开始时,文件包含了所需的头文件,如
<iostream>
、<string>
、<vector>
等标准库头文件,以及自定义的HttpServer.hpp
、Usage.hpp
和Util.hpp
。 -
宏定义: 定义了两个宏:
ROOT
:指定Web根目录为当前工作目录下的./wwwroot
,服务器将从这个目录查找并返回客户端请求的资源。HOMEPAGE
:如果客户端请求路径仅为/
,服务器将默认返回名为index.html
的首页。
-
HandlerHttpRequest函数: 这个函数作为HTTP请求处理器,负责处理接收到的客户端请求,并生成对应的HTTP响应。
a. 接收请求: 使用
recv
函数从已连接套接字(sockfd
)中读取客户端发送的HTTP请求数据,存储在buffer
字符数组中,并添加终止符\0
便于后续字符串操作。b. 解析请求: 调用
Util::cutString
函数两次,先按换行符\n
将请求数据分割成多行(存储在vline
中),再按空格将第一行(请求行)分割成多个字段(存储在vblock
中)。其中,vblock[1]
保存了请求的文件路径(如/index.html
或其他资源路径)。c. 处理请求: 根据请求的文件路径,拼接实际要访问的文件路径(
target
)。如果请求路径为/
,则将其替换为默认首页index.html
。然后尝试打开该文件,读取其内容并存储到content
字符串中。d. 构建响应: 根据文件是否成功打开和读取,构造不同的HTTP响应状态码和消息。若文件未找到或读取失败,则发送
HTTP/1.1 404 NotFound
;否则发送HTTP/1.1 200 OK
。接着添加一个空行,然后将文件内容追加到响应中。e. 发送响应: 使用
send
函数将构建好的HTTP响应发送回客户端。 -
主函数main: a. 参数检查: 检查命令行参数数量。如果参数不足2个(程序名 + 端口号),则调用
Usage
函数打印使用说明并退出程序。b. 创建HttpServer对象: 使用
std::unique_ptr
创建一个HttpServer
对象实例,传入命令行提供的端口号和HandlerHttpRequest
函数作为请求处理器。c. 启动服务器: 调用
httpserver
对象的Start
方法启动HTTP服务器。服务器将监听指定端口,接受客户端连接,为每个连接创建子进程处理HTTP请求,并在子进程中调用HandlerHttpRequest
函数。d. 程序结束: 主进程等待子进程完成请求处理,最后程序正常退出。
四、GET方法和POST方法
HTTP(Hypertext Transfer Protocol)是互联网上应用最为广泛的一种协议,用于客户端(如Web浏览器)与服务器端之间交换数据。在HTTP中,主要有两种常用的方法用于请求资源或执行操作:GET方法和POST方法。以下是这两者的详细解释和对比:
1、GET方法
基本特点:
-
目的:GET方法主要用于请求获取指定资源。当客户端发送一个GET请求时,它期望服务器返回请求URI(Uniform Resource Identifier)所标识的资源的内容。
-
参数传递:GET方法的请求参数通常附在请求的URL(Uniform Resource Locator)之后,作为查询字符串。查询字符串由问号(?)开始,后面跟随键值对(key=value),多个键值对之间用&分隔。例如:
http://example.com/resource?param1=value1¶m2=value2
。这些参数对可见于URL中,对用户和任何能够查看网络流量的中间设备都是透明的。 -
安全性:由于GET请求的参数直接包含在URL中,这种暴露的方式可能带来一定的安全风险,特别是对于敏感信息(如密码、个人身份信息等)。同时,URL长度有限制(一般为2048个字符左右),这限制了GET方法所能携带的数据量。
-
幂等性:GET请求被认为是幂等的,即无论请求多少次,只要URI相同,对服务器资源的状态不会造成影响,每次请求的结果都应该是相同的。这意味着GET请求主要是用于读取数据,而不是改变服务器状态。
-
缓存性:GET请求的响应通常可以被浏览器、代理服务器等缓存,这对于提高性能和减少网络带宽消耗是有利的。响应是否可缓存取决于响应头中的相关指示。
应用场景:
- 查询或检索数据:如搜索、列表展示、详情页加载等。
- 资源浏览:用户通过浏览器直接输入URL访问网页内容。
- API调用:获取数据接口,尤其是那些不涉及修改服务器状态的操作。
测试:
添加std::cout << buffer
将 buffer
中暂存的HTTP请求数据输出到标准输出
void HandlerHttpRequest(int sockfd)
{
// 1. 读取请求 for test
char buffer[10240]; // 缓冲区,用于暂存接收到的HTTP请求数据
ssize_t s = recv(sockfd, buffer, sizeof(buffer) - 1, 0); // 接收请求数据
if (s > 0)
{
buffer[s] = 0; // 添加终止符,便于后续字符串操作
}
std::cout << buffer << "\n--------------------\n" << std::endl;
//………………
./index.html
页面主体(<body>
)包含一个<h3>
元素显示文本“Linux-System-TEST”,以及一个表单(<form>
)用于用户输入用户名和密码。表单的action
属性设置为./test/test.html
,表示当用户提交表单时,将向服务器发送一个GET请求到相对路径test/test.html
;method
属性设置为get
,表明表单数据将以URL查询字符串的形式随请求发送。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>我的测试</title>
</head>
<body>
<h3>Linux-HTTP-TEST</h3>
<form name="input" action="./test/test.html" method="get">
Username: <input type="text" name="username"><br/>
Password: <input type="password" name="password"><br/>
<input type="submit" value="Submit">
</form>
</body>
</html>
访问服务器
输入姓名为2并提交
服务器试图打开并读取文件./wwwroot/test/test.html?username=2&password=
。由于查询字符串不属于文件路径组成部分,实际文件系统中不存在这样的文件名。因此,服务器无法找到并打开该文件,导致std::ifstream in(target);
未能成功打开文件流。
服务器输出HTTP请求头
./wwwroot/index.html
GET /test/test.html?username=2&password= HTTP/1.1
Host: 82.157.236.136:8080
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://82.157.236.136:8080/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
--------------------
./wwwroot/test/test.html?username=2&password=
这段输出显示了浏览器在用户点击表单提交按钮后,向服务器发送的HTTP GET请求的头部信息。各字段含义如下:
GET /test/test.html?username=2&password= HTTP/1.1
: 请求方法(GET)、请求资源路径(/test/test.html
,包括查询字符串?username=2&password=
)及HTTP协议版本(HTTP/1.1)。Host: 82.157.236.136:8080
: 请求的目标主机名及端口号。Connection: keep-alive
: 请求保持持久连接,以便复用TCP连接以提高性能。Upgrade-Insecure-Requests: 1
: 浏览器提示服务器若可能,应提供HTTPS响应而非HTTP。User-Agent: Mozilla/5.0 ...
: 客户端(浏览器)的标识信息,包括操作系统、浏览器类型、版本等。Accept: ...
: 客户端声明能接受的响应内容类型及优先级,如HTML、XML、图像等。Referer: http://82.157.236.136:8080/
: 表明当前请求是从哪个URL(即主页http://82.157.236.136:8080/
)发起的。Accept-Encoding: gzip, deflate
: 客户端声明支持的压缩编码方式,如gzip和deflate,以便服务器可压缩响应内容以减少传输量。Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,...
: 客户端声明优先接受的语言种类及其优先级。
2、POST方法
基本特点:
-
目的:POST方法主要用于向指定资源提交数据,请求服务器进行处理(如存储数据、更新状态或触发特定操作)。POST请求可能会导致创建新资源或修改现有资源的状态。
-
参数传递:POST方法的请求参数并不包含在URL中,而是置于HTTP请求的主体(body)内。主体可以承载任意类型的数据,包括文本、JSON、XML等,并且支持更大的数据容量。由于数据不在URL中显示,POST请求提供了更好的数据隐私性和安全性。
-
安全性:相比GET,POST方法在传输敏感数据时更为安全,因为参数不在URL中明文显示,减少了数据泄露的风险。此外,POST请求不受到URL长度限制,可以传输大量数据。
-
幂等性:POST方法通常不具有幂等性,即多次执行同样的POST请求可能会导致不同的结果(如多次提交订单可能导致多次购买)。然而,这并非绝对,设计良好的API可能会实现特定POST请求的幂等性。
-
缓存性:根据HTTP规范,POST请求的响应默认不应被缓存。如果需要缓存,需要显式设置响应头来允许缓存。
应用场景:
- 数据提交:如表单提交(用户注册、登录、发表评论等)、上传文件、更新用户信息等。
- 创建资源:在RESTful API中,POST常用于新建一个资源,如新增一篇文章、创建一个新的用户账户等。
- 执行复杂操作:当请求涉及到复杂的业务逻辑、需要传输大量数据或者需要改变服务器状态时,通常选择POST方法。
总结
GET方法和POST方法在HTTP中有着明确的分工:
- GET 主要用于获取资源,其请求参数包含在URL中,适合简单、安全且不需要改变服务器状态的场景,具有可缓存性和幂等性。
- POST 用于提交数据并可能改变服务器状态,其请求参数封装在请求体中,适用于需要保密、传输大量数据或执行非幂等操作的情况。
五、状态码
HTTP状态码(HTTP Status Codes)是一组三位数字,由服务器在响应客户端HTTP请求时返回,用于表示请求处理的结果。这些状态码分为五大类别:成功(1xx)、重定向(3xx)、客户端错误(4xx)、服务器错误(5xx)以及其他(保留)。
200 OK
-
含义:表示服务器成功处理了客户端的请求,并返回了请求资源的预期内容。当浏览器成功加载网页、API请求成功返回数据时,通常会收到这个状态码。
-
应用场景:这是最常见、最理想的响应状态,适用于GET、POST、PUT等各种HTTP方法成功完成的情况。
404 Not Found
-
含义:表示服务器无法找到客户端请求的资源。这可能是由于资源已被删除、URL拼写错误、服务器配置问题或资源迁移等原因导致。
-
应用场景:当用户尝试访问不存在的网页、请求不存在的API资源,或者资源路径不正确时,服务器会返回404状态码。
403 Forbidden
-
含义:服务器理解了客户端的请求,但拒绝提供服务。这通常是由于客户端缺少必要的访问权限、请求被服务器策略禁止,或者需要认证而未提供有效凭据。
-
应用场景:访问受保护的资源(如需要登录的页面)但未通过身份验证,或者客户端请求超出其权限范围时,服务器会返回403状态码。
302 Redirect (Found)
-
含义:临时重定向。服务器告诉客户端,请求的资源已移动到新的URL,客户端应使用提供的新URL重新发起请求。
-
应用场景:网站维护、URL规范化、负载均衡、A/B测试等场景下,服务器可能需要临时将请求重定向到其他位置。浏览器会自动执行重定向,用户通常察觉不到。
504 Gateway Timeout
-
含义:作为网关或代理的服务器(如反向代理、API网关)在等待上游服务器(如应用服务器、第三方服务)响应时超时。这意味着尽管网关收到了请求,但它未能在预设时间内从上游服务器获得完整的响应。
-
应用场景:当服务器架构中存在中间层(如反向代理、API网关),而这些中间层与后端服务之间的通信出现问题(如后端服务过载、网络延迟、后端服务崩溃等),可能导致504状态码返回给客户端。
六、Header
HTTP Header(头部)是HTTP请求和响应中包含的元数据,它们提供了额外的信息来指导和控制客户端与服务器间的通信过程。
1. Content-Type
-
含义:指示消息体(Body)的数据格式或媒体类型(MIME type)。它告诉接收方应该如何解析和处理消息体的内容。
-
示例:
Content-Type: text/html; charset=UTF-8
或Content-Type: application/json
-
应用场景:
- 请求:客户端在发送POST、PUT等包含数据的请求时,声明发送数据的格式,如HTML、JSON、XML、multipart/form-data等。
- 响应:服务器在返回资源时,声明资源内容的类型,如HTML页面、JSON数据、图像文件等。
2. Content-Length
-
含义:表示消息体的字节长度。它告知接收方应该接收多少数据,以便正确解析整个消息。
-
示例:
Content-Length: 1234
-
应用场景:用于确定请求或响应消息体的完整接收,特别是在数据流传输或分块传输编码(chunked transfer encoding)未启用的情况下。对于有固定长度的消息体,它是必需的;对于长度未知或动态生成的内容,可能需要其他机制(如chunked encoding)替代。
3. Host
-
含义:指定请求的目标主机名和端口号。它区分了同一服务器上的多个虚拟主机或端口服务。
-
示例:
Host: example.com:8080
-
应用场景:现代Web服务通常在一台服务器上托管多个网站(通过虚拟主机),
Host
头使得服务器可以根据域名正确路由请求。此外,当使用非标准端口时,它也指定了请求的服务端口。
4. User-Agent
-
含义:识别发起请求的客户端软件信息,包括浏览器类型、版本、操作系统等。
-
示例:
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3
-
应用场景:服务器可以依据
User-Agent
头信息来定制响应内容,如提供特定浏览器优化的样式或脚本,统计用户浏览器分布,或实施浏览器兼容性策略。
5. Referer
-
含义:指出当前请求是从哪个URL(网页)发起的。即点击链接或提交表单后,浏览器自动在新请求中包含原页面URL。
-
示例:
Referer: https://example.com/path/to/page
-
应用场景:
- 统计分析:网站统计工具使用
Referer
来跟踪用户来源和页面间跳转路径。 - 防盗链:服务器可能检查
Referer
以防止未经授权的外部链接直接访问资源。 - 安全考虑:一些隐私敏感操作可能要求
Referer
头存在且符合特定规则,以防止跨站请求伪造(CSRF)攻击。
- 统计分析:网站统计工具使用
6. Location
-
含义:配合3xx重定向状态码使用,提供需要客户端重新访问的URL。
-
示例:
Location: https://new.example.com/path
-
应用场景:
- 资源迁移:当请求的资源已永久(301)或临时(302)移到新位置时,服务器返回
Location
头指向新URL。 - URL短化:短链接服务返回
Location
头,使客户端跳转到实际长链接。 - 应用逻辑:根据特定条件,服务器可能通过
Location
头将客户端引导至不同的处理页面。
- 资源迁移:当请求的资源已永久(301)或临时(302)移到新位置时,服务器返回
7. Cookie
-
含义:用于在客户端(通常是浏览器)和服务器之间传递小块状态信息。服务器可以在响应中设置
Set-Cookie
头,客户端在后续请求中通过Cookie
头回传这些信息。 -
示例:
Set-Cookie: session_id=abc123; expires=Wed, 21 Oct 2021 07:28:00 GMT; Secure; HttpOnly
-
应用场景:
- 会话管理:维持用户登录状态、购物车内容等,无需在每个请求中传递完整用户信息。
- 个性化设置:存储用户偏好、语言设置等,以提供个性化的用户体验。
- 追踪和分析:用于识别和跟踪用户行为,支持网站统计和广告定向。
测试
if(content.empty()) // 若文件未找到或读取失败
{
HttpResponse = "HTTP/1.1 404 NotFound\r\n"; // 构建404响应状态码和消息
}
else
{
HttpResponse = "HTTP/1.1 200 OK\r\n"; // 构建200响应状态码和消息
HttpResponse += "Set-Cookie: 这是一个cookie\r\n";
}
设置Set-Cookie头部
HttpResponse += "Set-Cookie: 这是一个cookie\r\n";
Set-Cookie
头部用于服务器向客户端发送一个或多个Cookie。在这个例子中,设置了一个简单的示例Cookie,其值为“这是一个cookie”。实际应用中,Cookie应包含更具体的信息,如键值对、过期时间、路径等。客户端在接收到此头部后,会按照规范存储该Cookie,并在后续请求中附带发送回服务器,以便服务器识别用户状态或提供个性化服务。
七、特征
HTTP(Hypertext Transfer Protocol)是互联网上应用最为广泛的一种数据通信协议,用于在Web浏览器和服务器之间交换数据。其主要特征包括:
-
简单快速
-
简单:HTTP协议设计得相对简洁,易于实现和解析。它使用明文(ASCII编码)进行通信,报文结构清晰,由起始行、头部字段、空行以及可选的主体部分组成。这种简单性使得不同开发者、不同的开发平台都能轻松地实现HTTP客户端和服务器端的软件
-
快速:HTTP协议在设计时考虑到了高效传输数据的需求。它允许客户端仅请求所需资源,而不是整个网站内容。此外,HTTP头部可以包含诸如压缩、缓存控制等优化指令,以减少数据传输量和提升响应速度。同时,HTTP协议允许并行请求(如通过多线程或管道化连接),进一步加速网页加载。
-
-
无连接 无连接 这并不意味着HTTP完全不建立连接,而是指短连接或非持久连接。
在HTTP/1.x版本中,客户端(通常是浏览器)每次发起请求时都会与服务器建立一个新的TCP连接,服务器在处理完该请求并发送响应后,会立即关闭此连接。这种机制简化了服务器端的管理,因为无需维护大量长时间活跃的连接,尤其是在高并发场景下。尽管每次请求都需要经历TCP握手、数据传输和连接释放的过程,但考虑到HTTP请求通常较短且网页浏览行为具有突发性和短暂性,这种设计在早期互联网环境下能够有效利用资源。随着HTTP/2及后续版本引入多路复用技术,虽然物理连接依然保持无连接的特点(即每个请求完成后可以关闭连接),但在单一TCP连接上可以并行处理多个请求和响应,从而减少了建立连接的开销,提高了数据传输效率。
-
无状态 无状态 这意味着HTTP协议本身不对事务处理过程中的任何信息进行持久化存储或记忆。每次请求都是独立的,服务器在处理请求时,不会主动保留客户端的任何上下文信息(如身份、历史操作记录、会话状态等)。服务器响应请求时,仅基于当前请求的内容和可能存在的附加信息(如Cookie、Authorization头等),而不依赖于之前交互的历史状态。
无状态设计简化了服务器端逻辑,使得服务器可以快速响应新的请求,无需担心资源消耗在维护客户端状态上。然而,这也给需要连续交互或维持用户会话的应用带来了挑战。为弥补这一不足,Web开发中引入了以下机制:
尽管HTTP协议本身无状态,但通过这些补充手段,Web应用程序能够在保持HTTP协议简洁性和可扩展性的基础上,实现用户状态管理和持续交互。
- Cookies:客户端可以存储小块数据(如会话ID),并在后续请求中随HTTP头一起发送给服务器,使服务器能够关联请求到特定的会话状态。
- Session:服务器端创建并管理会话对象,关联用户的特定状态信息。客户端通过Cookie中的会话标识符与服务器端的会话数据对应起来。
- Token-based Authentication:使用JSON Web Tokens (JWT) 或其他令牌机制,将用户身份和权限信息编码在令牌中,客户端每次请求时携带令牌,服务器通过验证令牌即可恢复必要的状态信息。