文章目录
- HTTP的请求方法
- HTTP的状态码
- 模拟404状态
- 重定向状态码
- 状态码与浏览器的联系
- TCP的短连接与长连接
- Connection 头部
- Content-Type 头部
- Set-Cookie 头部
- Session ID
本文代码参照前一篇博客 |
---|
HTTP的请求方法
HTTP
协议存在多种请求方法,但是较为常用的请求方法基本为GET
方法与POST
方法;
其中GET
方法与POST
方法的不同之处在于:
-
GET
方法GET
方法主要为从服务器中获取资源,用于请求数据而不对数据进行更改;同时
GET
方法将会把参数以url
的形式提交给服务器;有一段
html
代码为如下:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <h1>Hello World</h1> <p>Here is page 1.</p> <form method="get"> Name:<br> <input type="text" name="firstname" value="Username"> <br> Passwd:<br> <input type="password" name="lastname"> <br> <br> <input type="submit" value="Submit"> </form> </body> </html>
该网页对应的样式为:
其中表单标签中的
method
方法设置为get
方法,这意味着当进行submit
提交时表单中的信息将会以GET
方式提交给服务器(默认情况,即表单标签中不设置method
属性时默认也以GET
方式进行提参);假设此处文本框内填入的信息为
abcde
与123456
并进行提交操作;参数以
url
的形式被提交给服务器,此时提交的信息存在暴露风险; -
POST
方法当使用
POST
方法对参数进行提交时对应的参数将会以请求正文的方式交由给服务器;对上述
html
代码中的get
方法修改为post
方法再次进行一次输入与提交;<form method="post">
对应的请求方法由
GET
变成了POST
,同时提参的方式由url
形式提参变成了以请求正文的方式进行提参;
基本上大多数的搜索引擎对于关键词搜索的方式都采用的是GET
的方式;
以www.baidu.com
为例;
在这个页面中,对于搜索框的表单中未指定method
方法,表示默认使用GET
方法,当然从搜索后的url
也可表明这一点;
对于GET
方法而言,其通过URL
进行提参,参数数量受限,同时提参信息不私密;
POST
方法也支持提交参数,但采用的方式是以正文形式进行提交;
对于安全性而言,POST
与GET
方法都不安全,安全是以是否对提交的信息进行加密处理从而判断其提交信息是否安全而不是对应的提交方式;
HTTP的状态码
-
1XX
以
1
为开头的状态码被称为信息性状态码;通常情况下当一个客户端向服务端发起请求时,在请求未处理完成时服务端将率先响应给客户端一个状态码为
1XX
的响应,表示当前请求还在被处理中;通常情况下这种响应的出现较少,在实际应用中并不频繁;
主要用于一些高级和特化的用途,如果设计应用需要处理复杂的请求过程,协议升级或是长时处理的请求则可能会使用
1XX
的状态码; -
2XX
通常情况下以
2
开头的状态码才是最常见的状态码也是最常用的状态码;以
200
为例,这个状态码表示来自客户端的请求已经处理完毕并且返回一个正确的响应给客户端; -
4XX
4
开头的状态码一般为服务器无法处理请求,通常情况下这类状态码表示客户端错误;以常见的
404
为例,该状态码一般表示服务器站点中不存在对应的资源;通常情况下一个站点中都将为自己的站点设置一个对应的
404
页面以提示用户其所访问的资源不存在;这里以
Google
搜索引擎为例:当试图访问一个为
google.com/a/cpanel/b/c/d.html
路径时,对应的将会跳出一个404
的页面;这是一种处理当用户访问该站点下不存在的资源时的一种措施,以明确提醒所访问的资源不存在;
这种错误被归咎于客户端错误的原因是在一个站点下并不是存在所有资源,客户端向服务器发起请求时若是试图访问一个站点(服务器)下不存在的资源或是无权限进行访问的资源即表示该请求是一个不合法的请求,所以为客户端错误;
-
5XX
5
开头的状态码通常表示服务器错误,假设一个客户端试图向一个已经高负载的服务端发出一个合法的请求,当服务端接收到请求时或许需要创建一个新的线程来处理这个来自客户端的合法请求,而服务端由于负载过高无法再创建线程来处理该合法请求即可能会返回客户端一个5
开头状态码的响应表示服务器错误;或者是在处理过程中出现无法与数据库连接等错误都为服务器错误;
模拟404状态
在博客『 Linux 』HTTP(二)以及之前的博客中,模拟实现了一个简单的HTTP
协议服务器,但是并没有完全完善其中的代码;
在这个服务器当中没有设置对应的404
措施;
模拟实现这个状态的思路即为判断当前服务器中是否存在该资源,若是不存在对应的路径资源则返回一个特定的404
页面以表示当前资源路径不存在;
假设存在一个html
文件,其文件名为err.html
,其对应代码为如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<center>
<h2>
Resources not found
</h2>
<a href="http://example:8049/">
Come back to Home Page;
</a>
</center>
</body>
</html>
其页面展示为:
对应的需要实现出这种效果需要在服务器中判断所访问的资源是否存在;
/* httpserver.html */
class HttpServer
{
public:
static std::string ReadHtmlContent(const std::string &path)
{
std::ifstream in(path);
if (!in.is_open())
{
return ""; // 打开失败则 返回空串
}
// 打开失败时此函数不进行后续操作
std::string content;
std::string line;
while (std::getline(in, line))
{
content += line;
}
in.close();
return content;
}
// 单独封装处理工作
static void HandlerHttp(int sockfd)
{
char buffer[10240];
ssize_t n = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
if (n > 0)
{
buffer[n] = 0;
HttpRequest request;
request.Deserialize(buffer);
request.Parse();
request.DebugPrint();
// 返回响应
std::string line_feed = "\r\n"; // 换行
std::string text;
bool ok = true; // 设置变量用于条件判断从而修改返回响应时能够返回对应的状态码
text = ReadHtmlContent(request.resource_path); // 调用函数对文件进行读取 // 响应正文
if (text.empty())
{
// 当返回的为空串时则表示访问资源不存在 返回响应时返回对应的404页面即可
std::string err_html = wwwroot;
err_html += "/";
err_html += "err.html";
text = ReadHtmlContent(err_html);
ok = false;
}
std::string response_line;
// 根据条件变量状态来返回不同的状态码
if (ok)
response_line = "HTTP/1.0 200 OK" + line_feed;
else
response_line = "HTTP/1.0 404 Not Found" + line_feed;
std::string response_header = "Content-Length: ";
response_header += std::to_string(text.size());
response_header += line_feed;
std::string blank_line = "\r\n";
response_header += line_feed;
std::string response = response_line + response_header + text;
send(sockfd, response.c_str(), response.size(), 0);
}
close(sockfd);
}
private:
uint16_t port_;
static const uint16_t defaultport;
NetSocket listensock_;
};
在这段代码中调用ReadHtmlContent()
函数打开请求所访问资源,若是当打开资源失败时则返回一个空串;
调用该函数的函数判断返回的是否为空串,如果为空串则修改所要访问的资源为指定路径下的err.html
文件并修改对应响应的状态码;
重新编译代码,运行服务器并尝试访问一个不存在的资源;
从网页以及Fiddler
的抓包来看,实现已经成功;
当服务器返回404
响应给客户端时实际上也是一个响应,只不过对应响应的状态码为404
;
重定向状态码
3XX
的状态码通常表示重定向操作;
重定向即表示客户端向服务端对应发起请求,服务端返回客户端一个响应,但是这个响应是一个重定向响应,这个重定向响应中存在一个3
开头的状态码表示需要进行重定向,并且在响应的报头中将会存在Location
字段,如Location: http://www.example.com
,并且该报头后将会携带对应重定向的url
以告诉客户端第二次请求时对应的位置,对应的客户端当收到这个重定向响应时将会向该重定向响应中Location
字段中的url
发起请求;
通常情况下服务端最常用的重定向为两种,分别为临时重定向302
与永久重定向301
;
-
302
状态码该重定向表示临时重定向,临时重定向通常发生在该服务端正在维护,当客户端向服务端发起请求时服务端将会返回一个重定向响应,对应的浏览器将会根据
Location
报头进行跳转,因为是临时的,所以通常情况下发生临时重定向时用户只需要访问旧的IP,根据浏览器做对应跳转即可; -
301
状态码301
状态码表示永久性重定向,永久性重定向通常表示该服务器已弃用等情况,对应的同样当客户端向永久重定向的服务器发起请求时,对应的会根据Location
字段进行跳转;
两个重定向都会进行响应的跳转,但不同的是临时重定向时浏览器都会根据Location
进行重新发起请求;
而永久重定向时对应的浏览器将会缓存永久重定向的Location
字段,每次向该永久重定向的服务器发起请求时默认将直接访问新的URL
而不对旧的URL
进行请求;
对于永久重定向的缓存是根据缓存设定而定的,同时不同的浏览器将会有不同的行为,无法确保统一;
但是总体策略一致:
-
缓存机制
浏览器通常采用内存级和磁盘级两种缓存机制,内存缓存用于回话级别的临时缓存,磁盘缓存用于长期存储;
此处将对临时重定向进行模拟实现;
对应的只需将状态码修改为302
,并且在响应中添加Location
报头字段并增加对应的URL
即可;
/* httpserver.hpp */
static void HandlerHttp(int sockfd)
{
char buffer[10240];
ssize_t n = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
if (n > 0)
{
buffer[n] = 0;
HttpRequest request;
request.Deserialize(buffer);
request.Parse();
request.DebugPrint();
std::string line_feed = "\r\n";
std::string text;
bool ok = true;
text = ReadHtmlContent(request.resource_path);
if (text.empty())
{
std::string err_html = wwwroot;
err_html += "/";
err_html += "err.html";
text = ReadHtmlContent(err_html);
ok = false;
}
std::string response_line;
if (ok)
// response_line = "HTTP/1.0 200 OK" + line_feed; // 注释 200状态码 默认状态码为302
response_line = "HTTP/1.0 302 Found" + line_feed; // 302状态码对应的状态码描述为 Found
else
response_line = "HTTP/1.0 404 Not Found" + line_feed;
std::string response_header = "Content-Length: ";
response_header += std::to_string(text.size());
response_header += line_feed;
response_header += "Location: https://www.baidu.com";
// 添加Location: 字段并添加对应的url
response_header += line_feed; // 换行
std::string blank_line = "\r\n";
response_header += line_feed;
std::string response = response_line + response_header + text;
send(sockfd, response.c_str(), response.size(), 0);
}
close(sockfd);
}
在这段代码中注释掉了原有的200
状态码的响应状态行,设置了一个新的状态码为302
的状态码响应行;
同时设置了一个Location:
报头字段,后面跟上了https://www.baidu.com
表示临时重定向至该URL
;
重新编译并运行服务器重新对服务器进行访问并用Fiddler
工具进行抓包;
最终结果为客户端接收到对应的重定向响应后将重新向Location
字段中的URL
重新发起请求;
状态码与浏览器的联系
状态码可以视为是HTTP协议中的一种约定,约定在不同的场景中返回不同的状态码,而实际上在实际的开发中,状态码的约束能力并不是很严格,主要是不同的浏览器作为客户端对请求的处理手段不同;
这些差异包括但不限于:
-
重定向处理
许多现代浏览器会自动处理
301
和302
,并且在301
重定向的情况下会缓存新的URL
;对于
307
(临时重定向)和308
(永久重定向),一些焦躁的浏览器可能不支持或是处理的方式不同; -
缓存策略
即使服务器返回相同的状态码,不同的浏览器可能使用不同的缓存策略,例如IE浏览器和Chrome浏览器可能对
Cache-Control:
头部的支持和解析有差异; -
错误页面显示
用户在浏览一些错误页面时,不同的浏览器可能提供不同的用户体验,如
404
错误页面在不同的浏览器中可能会有不同的设计和内容; -
安全性考虑
对于某些安全相关的状态码
401
和403
状态码,不同浏览器可能在具体处理和警告用户的方式上存在不同;
同时在开发过程中,由于状态码的非严格约束,导致可能一个服务器开发者在非重定向等响应中的状态码都将返回200 ok
的状态码与状态码描述;
通常情况下3XX
系列的状态码必须与Location
头部配合使用;
TCP的短连接与长连接
-
短连接
短连接顾名思义即短的连接,一次的TCP连接只保障一次HTTP请求与响应;
当单次的请求与响应结束之后,对应的TCP连接将会被关闭,当再次需要进行一次HTTP的请求与响应时对应的要重新建立TCP连接;
这意味着一次TCP连接在进行完三次握手后一次请求与响应结束了就要进行四次挥手并关闭连接;
流程如下:
-
建立连接(三次握手)
客户端发送SYN报文;
服务器返回SYN-ACK报文;
客户端回应ACK报文;
-
发送请求和接收响应
客户端发送HTTP请求(如
HTTP GET
请求);服务器处理请求并返回响应;
-
关闭连接(四次挥手)
服务器发送FIN报文,表示要关闭连接;
客户端返回ACK报文,确认收到了服务器的FIN报文;
客户端发送FIN报文表示自己也准备关闭连接;
服务器返回ACK报文确认收到客户端的FIN报文,至此连接关闭;
对于短连接而言每次的请求与响应都要进行上述流程;
-
-
长连接
长连接则保持TCP连接在多个请求和响应期间不关闭,只有在显式关闭或是超时时才关闭这个TCP连接;
这种方式减少了TCP连接的建立和关闭次数,尤其是在频繁通信时具有显著优势;
流程如下:
-
建立连接(三次握手)
客户端发送SYN报文;
服务器返回SYN-ACK报文;
客户端回应ACK报文;
-
持续发送请求和接收响应
客户端发送HTTP请求(如
HTTP GET
请求);服务器处理请求;
重复发送新请求,不需要重新建立连接;
-
关闭连接(四次挥手)
服务器发送FIN报文,表示要关闭连接;
客户端返回ACK报文,确认收到了服务器的FIN报文;
客户端发送FIN报文表示自己也准备关闭连接;
服务器返回ACK报文确认收到客户端的FIN报文,至此连接关闭;
-
Connection 头部
短连接多用于一些较为简单场景,比如HTTP/1.0
,每个请求响应对使用一个单独的TCP
连接,一些较为简单的一次性数据传入,如简单的文件下载和网页浏览也可以使用短连接;
长连接多用于频繁数据交互的场景,如HTTP/1.1
与HTTP/1.2
,即支持持久连接和服用,允许多个请求响应对复用一个TCP
连接;
对于HTTP/1.1
与HTTP/2.0
而言,其在对多个请求的情况处理有所不同,但两者都为长连接的使用方,对于HTTP/1.1
而言,其虽然是长连接,但是在多个请求中仍然需要一对一进行处理,即当出现多个请求时将会一个请求发出并且等待服务器处理并响应回才会发送下一个请求,而对HTTP/2.0
而言,其允许单个TCP请求中并行发送多个请求并处理多个响应,响应和请求之间互不干扰;
当一个网页,即html
文件多资源的情况(这里的资源包括但不限于图像,音频,视频等资源)时,客户端发给服务端的请求应当不只有一个而是多个,这是因为多资源的情况下其他的资源也必然存在在Web
根目录中或者其中的子目录当中,所以对应的需要发起对应的请求,在这种情况下即为短连接的短板,其无法在一次TCP
连接中处理多个请求与响应,将会增大开销;
而通信双*是使用HTTP协议时双方都必须约定所使用的HTTP协议版本,通常情况下该版本的诉求方为客户端,因为通常为客户端向服务端发起请求,在请求中会存在所使用的HTTP版本,对应的在进行服务端开发时应做好不同HTTP版本的开发以应对不同版本客户端所发的请求;
在HTTP报文中的报头存在字段Connection:
,该字段用于服务端与客户端双方约定是否使用长连接技术;
在报头中可以设置Connection
字段来设置是否使用长连接技术:
-
HTTP/1.0
HTTP/1.0
默认情况下在每个请求/响应结束后将会关闭TCP连接,即默认是短连接的,但如果设置Connection: keep-alive
头字段进行指定;这意味着虽然
HTTP/1.0
没有原生支持长连接的机制,但是可以通关设置这个字段实现类似于HTTP/1.1
的持久连接功能; -
HTTP/1.1
该版本的HTTP协议默认支持长连接,即一次TCP连接中可以进行多对请求/响应,其默认行为与设置
Connection: keep-alive
字段相同,即无论是否设置该字段为使用长连接都采用的是长连接的方式;对应的若是不需要使用长连接手段则需要显式将该字段设置为
close
,即Connection: close
;
Content-Type 头部
假设当前Web
根目录的结果为如下:
/* /wwwroot/ */
$ tree .
.
├── err.html
├── image
│ └── helloworld.png
├── index.html
└── pages
├── page_2.html
└── page_3.html
2 directories, 5 files
在该Web
根目录中存在一个image/
目录,其中存在这样一张图片,对应的图片命名为helloworld.png
:
将该图片以资源的方式放置在html
文件index.html
中;
其对应的html
文件代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>Hello World</h1>
<p>Here is page 1.</p>
<form method="post">
Name:<br>
<input type="text" name="firstname" value="Username">
<br>
Passwd:<br>
<input type="password" name="lastname">
<br><br>
<input type="submit" value="Submit">
</form>
<center>
<div>
<img src="./image/helloworld.png"> <!-- 图片 -->
<h4>Hello World</h4>
</div>
</center>
</body>
</html>
运行服务器并进行访问;
从访问结果来看,图片并没有如约进行显示,本质原因是在自定义编写的HTTP
服务器中,读取的方式是无论什么文件类型都采用文本的形式进行读取,而图片若是采用文本的形式进行读取将会出现错误故无法显示;
通常情况下图片是以二进制流的方式进行存储,故对应的读取方式应该也是使用二进制流;
当服务端在解析文件的时候需要根据不同文件的类型对不同类型的文件进行读取,否则将读取失败;
对于网页文件,如html
文件而言,其标签<!DOCTYPE html>
可以明确告诉浏览器该文件是一个html
的文档文件,通常情况下大部分的浏览器都会直接将其以网页的形式进行解析,但图片文件不同,图片文件由于不存在类似的头部,浏览器无法明确知道该资源的类型,所以需要明确的指出资源的类型;
通常情况下这个文件资源的类型是由服务器返回给客户端的响应中携带的内容,以Content-Type:
头部进行指定;
Content-Type
头部中的属性格式根据不同的文件类型同样也存在许多不同的格式,一些常用的