
【计算机网络】HTTP协议详解(举例解释,超级详细)
假设你使用Web浏览器(例如Chrome)访问一个网页。当你在浏览器中输入网址并按下"Enter"键时,浏览器会向服务器发送一个HTTP请求。HTTP协议是一种用于在Web浏览器和Web服务器之间传输数据的应用层协议。它具有无状态、基于请求-响应模型、支持多媒体、可用于缓存、简单灵活等特点。
文章目录
4、4、2 Cookie 与 Session id 实现用户身份验证
🙋♂️ 作者:@Ggggggtm 🙋♂️
👀 专栏:计算机网络 👀
💥 标题:HTTP协议 💥
❣️ 寄语:与其忙着诉苦,不如低头赶路,奋路前行,终将遇到一番好风景
一、HTTP协议简单介绍
1、1 什么是HTTP协议
HTTP(超文本传输协议)是一种用于在Web浏览器和Web服务器之间传输数据的应用层协议。它是一种无状态协议,即服务器不会保留与客户端的任何连接状态信息,每个请求都被视为一个独立的事务。
假设你使用Web浏览器(例如Chrome)访问一个网页。当你在浏览器中输入网址并按下"Enter"键时,浏览器会向服务器发送一个HTTP请求。你也可以理解为HTTP协议是在客户端(浏览器)和服务器之间传输数据的基础(约定)。
1、2 再次理解“协议”
协议是指在通信过程中,参与方之间所达成的一种约定或规范。在网络通信中,协议是用来定义数据传输规则和通信方式的一组规范。
具体来说,基于HTTP协议,它定义了客户端(例如Web浏览器)和服务器之间进行通信时所需遵循的规范。
HTTP协议主要包含以下几个方面的规定:
请求方式:HTTP协议定义了一系列的请求方法,如GET、POST、PUT、DELETE等,用于告知服务器进行何种操作。
请求和响应格式:HTTP协议规定了请求消息和响应消息的格式。请求消息由请求行、请求头部和请求正文组成,而响应消息由状态行、响应头部和响应正文组成。
状态码:HTTP协议定义了一系列的状态码,用于表示服务器对请求的处理结果。例如,200表示成功、404表示资源未找到、500表示服务器内部错误等。
头部信息:HTTP协议通过头部字段来携带各种元数据,例如Content-Type用于指示请求或响应的数据类型,Content-Length表示消息正文的长度等。
连接管理:HTTP协议还定义了一些机制用于管理连接,如持久连接(keep-alive)允许多个请求和响应复用同一个TCP连接,以减少连接建立的开销。
后文会对上述的各个规定进行详细解释。
二、HTTP请求
2、1 HTTP的工作过程
我们不妨先来了解一下HTTP的工作过程。当你在浏览器中输入一个网址并按下"Enter"键时,浏览器就会向服务器发送一个HTTP请求。请求时,浏览器会给服务器发送请求报文。当服务器收到请求后,它会根据请求报文进行相应的处理,并生成一个HTTP响应(响应报文)返回给浏览器。一个请求再加一个回应,就完成了客户端与服务器的数据传输与交互。
上述讲述的都是概念。下面我们结合一段代码来理解。在看代码之前,强调一下HTTP 是一种应用层协议,是基于 TCP/IP 通信协议来传递数据的。具体也可看下图:
2、1、1 demo代码
首先我们需要基于套接字实现一个服务端HttpServer.hpp:
#include <iostream> #include <signal.h> #include "Sock.hpp" class HttpServer { public: using func_t = std::function<void(int)>; private: Sock _serverSock; int _sock; std::string _ip; uint16_t _port; func_t _func; public: HttpServer(uint16_t port, func_t func, std::string ip = "0.0.0.0") :_port(port) ,_func(func) ,_ip(ip) { _sock = _serverSock.Socket(); _serverSock.Bind(_sock, _port, _ip); _serverSock.Listen(_sock); } void start() { signal(SIGCHLD, SIG_IGN); while(true) { std::string clientIP; uint16_t clientPort = 0; int sockfd = _serverSock.Accept(_sock, &clientIP, &clientPort); if(sockfd < 0) continue; if(fork() == 0) { close(_sock); _func(sockfd); close(sockfd); exit(0); } close(sockfd); } } ~HttpServer() { if(_sock >= 0) close(_sock); } };
下面是对套接字操作的封装代码Sock.hpp:
#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 <arpa/inet.h> #include <netinet/in.h> #include <ctype.h> #include "LogTest.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, "init server success"); } // 一般经验 // const std::string &: 输入型参数 // std::string *: 输出型参数 // std::string &: 输入输出型参数 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(ERROR, "accept error, %d:%s", errno, strerror(errno)); return -1; } 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); server.sin_addr.s_addr = inet_addr(server_ip.c_str()); if(connect(sock, (struct sockaddr*)&server, sizeof(server)) == 0) return true; else return false; } ~Sock() {} };
下面我们要做的就是启动服务器,然后用Web浏览器访问我们所启动的服务器,这时候是浏览器向我们所写的服务器发送请求。根据上述HTTP协议的工作过程,这时候会像服务器发送一个请求报文。我们启动服务器HTTPServer.cc:
#include <iostream> #include <memory> #include <vector> #include <fstream> #include "Util.hpp" #include "HttpServer.hpp" void Usage(std::string name) { std::cout << "\nUsage :" << name << " Port\n" << std::endl; } void HandlerHttpRequest(int sockfd) { // 1. 读取请求 for test char buffer[10240]; ssize_t s = recv(sockfd, buffer, sizeof(buffer) - 1, 0); if (s > 0) { buffer[s] = 0; std::cout << buffer << "--------------------\n" << std::endl; } } int main(int argc, char* argv[]) { if(argc != 2) { Usage(argv[0]); exit(0); } std::unique_ptr<HttpServer> httpServer(new HttpServer(atoi(argv[1]), HandlerHttpRequest)); httpServer->start(); return 0; }
我们来看一下运行结果:
我们看到确实我们所写的服务器发送了一些信息。该信息就是请求报文。但发现无法打开此页面,是因为我们并没有向浏览器发送任何响应数据。接下来我们详细了解一下HTTP的请求。
2、2 URL 介绍
URL(Uniform Resource Locator)是用于标识和定位互联网上资源的字符串。URL由多个组件构成,包括协议、域名(或IP地址)、端口号、路径和查询参数等。
下面是一个示例URL:http://www.example.com:8080/path/to/resource?param1=value1¶m2=value2
解释:
- 协议:URL的第一部分是协议,这里是"http"。协议指定了浏览器与服务器之间的通信规则,常见的有HTTP和HTTPS。
- 域名(或IP地址):在示例中,域名是"www.example.com"。域名是用于标识互联网上特定站点的字符串,也可以使用IP地址来代替。
- 端口号:示例中的端口号是"8080"。默认情况下,HTTP使用80端口,HTTPS使用443端口,但可以使用不同的端口号来访问特定的服务。
- 路径:路径指定了在服务器上资源的位置,示例中是"/path/to/resource"。路径可以是文件、目录或其他资源的位置。
- 查询参数:在示例中,查询参数是"?param1=value1¶m2=value2"。查询参数用于向服务器传递额外的信息,以便执行特定的操作或获取特定的结果。
平时我们俗称的 "网址" ,其实就是说的 URL。 具体也可看下图:
域名就是服务器地址。浏览器会对域名进行解析,解析后就会转换为对应的地址。一个服务器地址,再加上端口号,这就标示了该服务器的唯一进程。端口号后面用 ‘ / ’ 分隔的就是我们所请求资源在该服务器上的路径。
2、2、1 urlencode 和 urldecode
我们经常看到如下的URL:
这就涉及到了urlencode和urldecode处理URL编码和解码。
urlencode: urlencode函数用于将URL中的特殊字符转换为URL编码形式。它将URL中的非字母、数字和一些特殊字符(如冒号、斜杠、问号、百分号等)转换为%后面跟着两位16进制表示的字符,以便于在URL中传输和解析。例如,空格会被转换为%20,冒号会被转换为%3A等。 使用方法:urlencode(string, encoding) 参数说明:
- string: 需要进行URL编码的字符串。
- encoding(可选): 指定字符串的编码格式,默认为utf-8。
urldecode: urldecode函数用于将URL编码的字符串解码回原始字符串。它将URL编码形式转换为原始字符,使其可读性更好。例如,%20会被还原成空格,%3A会被还原为冒号等。 使用方法:urldecode(string, encoding) 参数说明:
- string: 需要进行URL解码的字符串。
- encoding(可选): 指定字符串的编码格式,默认为utf-8。
举例说明: 假设有一个字符串需要进行URL编码:"Hello World!" 使用urlencode进行编码:urlencode("Hello World!") 编码后的结果为:"Hello%20World%21"
再假设有一个经过URL编码的字符串需要进行解码:"Hello%20World%21" 使用urldecode进行解码:urldecode("Hello%20World%21") 解码后的结果为:"Hello World!"
像 ' / ' 、' ? ' : 等这样的字符, 已经被url当做特殊意义理解了。因此这些字符不能随意出现。比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义。
转义的规则:将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式。
2、3 HTTP 请求格式
服务器收到一个HTTP请求后,请求格式如下:
请求行:浏览器发送的第一部分是请求行,它包含了请求的方法(例如GET)、要访问的资源路径(例如/index.html)以及使用的HTTP版本(例如HTTP/1.1)。
请求头部:接下来,浏览器发送请求头部,其中包含一些额外的信息,例如浏览器类型、所支持的编码方式、语言首选项等。
空行:请求头部之后是一个空行,用于分隔请求头部和请求正文。
请求正文(可选):有些请求可能包含请求正文,例如表单数据或上传的文件。
其实我们对照我们刚刚举例的运行结果,也可总结出请求报文的格式,具体如下图:
当服务器拿到请求报文后,会对请求报文进行分析。例如,其中就包含了请求的方法(例如GET)、请求的资源路径和协议版本,结合请求报头就会对此进行分析,找到资源并形成响应报文进行返回。其中有许多细节并未解释,后文会详细解释。下面我们先来看一下响应报文的格式。
三、HTTP响应
3、1 响应demo
上述我们例子中并未看到有任何界面。原因就是在于Web浏览器并未收到任何响应。根本在于我们所写的服务器就没有对此进行响应。我们不妨先看一下响应的实例。
我们的思路:拿到请求响应报文,对此进行分析出所请求资源的路径,然后形成一个HTTP响应进行返回。代码HttpServer.cc:
#include <iostream> #include <memory> #include <vector> #include <fstream> #include "Util.hpp" #include "HttpServer.hpp" // 一般http都要有自己的web根目录 #define ROOT "./wwwroot" // ./wwwroot/index.html // 如果客户端只请求了一个/,我们返回默认首页 #define HOMEPAGE "index.html" void Usage(std::string name) { std::cout << "\nUsage :" << name << " Port\n" << std::endl; } void HandlerHttpRequest(int sockfd) { // 1. 读取请求 for test char buffer[10240]; ssize_t s = recv(sockfd, buffer, sizeof(buffer) - 1, 0); if (s > 0) { buffer[s] = 0; //std::cout << buffer << "--------------------\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]; std::string target = ROOT; 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; if(content.empty()) HttpResponse = "HTTP/1.1 404 NotFound\r\n"; else HttpResponse = "HTTP/1.1 200 OK\r\n"; HttpResponse += "\r\n"; HttpResponse += content; // 2. 试着构建一个http的响应 send(sockfd, HttpResponse.c_str(), HttpResponse.size(), 0); } int main(int argc, char* argv[]) { if(argc != 2) { Usage(argv[0]); exit(0); } std::unique_ptr<HttpServer> httpServer(new HttpServer(atoi(argv[1]), HandlerHttpRequest)); httpServer->start(); return 0; }
整体路径如下图:
这里有一个细节:当我们输入URL没有请求资源路径时,浏览器会自动加上一个 ’ / ‘,代表着根目录。这里的根目录与Liunx 的根目录是不同的。一般服务器都会设置默认的Web根目录。这时候就是访问的默认界面。index.html:
<!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>HTTP响应</title> </head> <body> <h3>现在你能够看到我了</h3> <p>我是一个Linux的学习者,我正在进行http的测试工作!!</p> <p>我是一个Linux的学习者,我正在进行http的测试工作!!</p> <p>我是一个Linux的学习者,我正在进行http的测试工作!!</p> <p>我是一个Linux的学习者,我正在进行http的测试工作!!</p> <p>我是一个Linux的学习者,我正在进行http的测试工作!!</p> <p>我是一个Linux的学习者,我正在进行http的测试工作!!</p> <p>我是一个Linux的学习者,我正在进行http的测试工作!!</p> </body> </html>
我们看运行结果:
确实有了界面,也正是我们所设计的界面。通过HTTP,客户端可以获取到Web服务器上的各种资源,例如HTML文档、图像、视频、样式表等。
3、2 HTTP 响应格式
当服务器收到请求后,它会进行相应的处理,并生成一个HTTP响应返回给浏览器。
响应状态行:响应的第一部分是状态行,它包含了响应的HTTP版本(例如HTTP/1.1)、响应状态码(例如200表示成功)以及对应的状态消息(例如"OK")。
响应头部:接下来,服务器发送响应头部,其中包含一些额外的信息,例如服务器类型、响应时间、返回的数据类型等。
空行:响应头部之后是一个空行,用于分隔响应头部和响应正文。
响应正文:响应正文包含了服务器返回的实际数据,例如HTML页面、图像、CSS样式表等。
具体也可结合下图理解:
四、HTTP 请求和响应中的细节
4、1 HTTP方法
HTTP方法是用于指定HTTP请求类型的一种规范。HTTP方法定义了对服务器资源的操作方式。通过我们所举的例子中,看到的请求报文中的第一行就含有请求方法,如下图:
HTTP方法有很多,以下是常见的HTTP方法的详解:
GET:用于获取服务器上的资源。当客户端发送GET请求时,服务器将返回相应请求的资源。GET请求是幂等的,也就是说多次发送同一个GET请求不会产生不同的结果。此外,GET请求的参数会附在URL后面,因此可能会受到长度限制。
POST:用于向服务器提交数据,并请求服务器进行处理。POST请求通常用于向服务器发送表单数据、上传文件、进行用户登录等操作。相对于GET请求,POST请求没有长度限制,并且不会将参数暴露在URL中,而是放在请求体中进行传输。
PUT:用于创建或更新服务器上的资源。PUT请求类似于POST请求,但PUT请求要求在指定的URL上创建或更新资源。如果资源已存在,则会进行更新;如果资源不存在,则会进行创建。
DELETE:用于删除服务器上的资源。DELETE请求会删除指定URL对应的资源。删除后,相应的URL将不再存在。
HEAD:类似于GET请求,但只返回资源的报头信息,而不返回实际的资源内容。HEAD请求常用于检查资源的元数据,例如最后的修改时间、ETag等。
OPTIONS:用于获取目标URL所支持的请求方法。服务器收到OPTIONS请求后,会返回该URL所支持的请求方法列表。
TRACE:用于将请求上的报头信息回显给客户端,用于调试或诊断信息。
CONNECT:主要用于代理服务器,指示代理服务器与目标服务器建立隧道连接。
LINK:LINK请求方法用于创建与目标URL之间的链接。它允许客户端在一个URL上创建一个指向另一个URL的链接关系。
UNLINK:UNLINK请求方法用于解除与目标URL之间的链接。它允许客户端移除已经存在的链接关系
具体也可参照下图:
其中最重要的,也是最常见的两种方法:GET和POST方法。本篇文章也会对这两种HTTP方法进行详解。
4、1、1 GET 方法
我们客户平时在上网时,无非就两种需求:1、从服务器上拿资源;2、把客户端数据上传到服务器。
GET 方法是 HTTP 中最常用的方法之一,用于向服务器请求获取某个资源的表示。上述我们所举的例子就是使用了GET方法向服务器请求资源。
4、1、2 form 表单中的GET方法上传数据
同时,也可以使用GET方法向服务器上传数据。下面我们通过一个例子来看一下。当你在网页上填写一个表单时,<form> 元素是必不可少的。它定义了一个 HTML 表单,用于向服务器发送用户输入的数据。下面是一个例子,展示了如何创建一个简单的表单:
<!DOCTYPE html> <html> <head> <title>表单示例</title> </head> <body> <h2>用户信息</h2> <form action="/submit" method="post"> <label for="name">姓名:</label> <input type="text" id="name" name="name"><br><br> <label for="email">邮箱:</label> <input type="email" id="email" name="email"><br><br> <label for="password">密码:</label> <input type="password" id="password" name="password"><br><br> <input type="submit" value="提交"> </form> </body> </html>
这个例子中,我们创建了一个简单的注册表单。
<form>
元素的action
属性指定了在提交表单时将数据发送到哪个 URL,method
属性指定了使用 POST 方法发送数据。<label>
元素用于定义表单字段的标签,在示例中分别表示姓名、邮箱和密码。<input>
元素用于创建实际的表单字段,它们使用不同的type
属性来指定字段的类型。当用户点击提交按钮时,浏览器将把表单中的数据封装为一个 HTTP 请求,并向服务器发送该请求。服务器可以使用各种编程语言和框架来处理这个请求。通过读取请求的内容,服务器可以获取用户在表单中输入的数据,并根据需要进行进一步的处理。
我们把上述的表单加入到我们默认请求的页面上,代码index.html:
<!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>HTTP响应</title> </head> <body> <h2>用户信息</h2> <form action="/submit" method="GET"> <label for="name">姓名:</label> <input type="text" id="name" name="name"><br><br> <label for="email">邮箱:</label> <input type="email" id="email" name="email"><br><br> <label for="password">密码:</label> <input type="password" id="password" name="password"><br><br> <input type="submit" value="提交"> </form> <h3>现在你能够看到我了</h3> <p>我是一个Linux的学习者,我正在进行http的测试工作!!</p> <p>我是一个Linux的学习者,我正在进行http的测试工作!!</p> <p>我是一个Linux的学习者,我正在进行http的测试工作!!</p> <p>我是一个Linux的学习者,我正在进行http的测试工作!!</p> <p>我是一个Linux的学习者,我正在进行http的测试工作!!</p> <p>我是一个Linux的学习者,我正在进行http的测试工作!!</p> <p>我是一个Linux的学习者,我正在进行http的测试工作!!</p> </body> </html>
在HTML表单中,method属性用于定义向服务器发送数据的方法。我们这里先看一下GET方法。运行结果如下图:
当我们填写完信息后,选择提交时,再来看一下运行结果:
我们通过上图发现,GET方法是将表单数据附加到URL的末尾,作为查询字符串发送给服务器。
这样有一个很明显的缺点:我的密码很容易就泄露了啊。这样就是不够私密。这里再对GET方法进行总结:
- GET方法是将表单数据附加到URL的末尾,作为查询字符串发送给服务器。
- GET方法适合传输非敏感数据,因为数据以明文形式出现在URL中,可能会被第三方获取。
- GET方法的数据量有限制,因为URL的长度有限。不同的浏览器和服务器对URL的长度限制可能不同,通常为2048个字符。
- GET方法适用于获取服务器上的数据,对于只读操作更为合适。
- GET方法易于书签、缓存和分享。
下面我们再看一下POST方法。
4、1、3 POST方法
把HTML表单中method属性改为POST方法。我们再来看一下运行结果,如下图:
POST方法并不会将表单数据附加到URL的末尾,我们打印出请求报文来看一下,如下图:
发现POST方法是将表单数据作为请求的主体发送给服务器。相对GET方法来说更加安全一点。POST方法总结:
- POST方法是将表单数据作为请求的主体发送给服务器。
- POST方法适合发送敏感数据,因为数据以加密形式传输,并且不会在URL中显示。
- POST方法没有长度限制,可以发送大量数据。
- POST方法对于更新、插入、删除等需要修改服务器上的数据的操作更为合适。
- 由于数据被封装在请求主体中,POST方法对URL参数的数量和长度没有限制。
GET方法与POST方法的区别:
- 安全性:POST方法比GET方法更安全,因为它将数据作为请求主体发送,而不是明文暴露在URL中。
- 数据量:POST方法没有数据量限制,而GET方法受到URL长度的限制。
- 数据类型:POST方法可以发送各种类型的数据,而GET方法主要用于发送文本数据。
- 缓存:POST方法不会被浏览器缓存,而GET方法可能会被缓存。
GET方法与POST方法的总结:
- 如果需要传输敏感或大量的数据,或者进行更新、插入、删除等操作,应使用POST方法。
- 如果只需要获取数据或发送少量数据,且不涉及安全问题,可以使用GET方法。
4、2 请求报头 Header
HTTP协议中的请求报头(Request Headers)是客户端发送给服务器的一组键值对,用于携带额外的信息,以控制请求的行为和提供相关的上下文信息。以下是常见的请求报头属性及其详细解释:
- Accept:指定客户端可接受的响应内容的媒体类型。
- Accept-Encoding:指定客户端可接受的响应内容的压缩编码方式。
- Accept-Language:指定客户端可接受的响应内容的语言。
- Authorization:用于在发送请求时进行身份验证的凭证信息。
- Cache-Control:指定客户端或代理如何缓存响应。
- Connection:指定当前连接的类型,例如保持连接或关闭连接。
- Content-Length:请求正文的长度。
- Content-Type:指定请求主体的媒体类型,常用于POST请求中。
- Cookie:用于在客户端存储少量信息. 通常用于实现会话(session)的功能。
- Host:指定请求的目标服务器的主机名和端口号(服务器主机可以是域名,也可以是 IP;端口号有默认可以省略或者指定)。
- If-Modified-Since:指定只有在指定日期之后修改过的资源才会返回。
- Referer:指定当前请求的来源页面的URL,也就是从那个页面跳转过来的。
- User-Agent:标识发起请求的用户代理应用程序的信息,主要是操作系统和浏览器版本信息。
下面我们重点看一下Connection,Cookie。
4、3 Connection 长连接与短连接
Connection是用来指定当前连接的类型,例如保持连接(长连接)或关闭连接(短连接)。HTTP协议中的连接可以分为长连接和短连接,它们主要用于控制客户端和服务器之间的通信方式。
短连接(short connection):
- 在短连接中,每个请求/响应对都使用独立的连接。也就是说,客户端发送请求后,服务器返回响应后即关闭连接。
- 在每次请求和响应后都需要建立和关闭连接,这种方式能够确保每个连接都是短暂的,并能够释放服务器资源,在请求完成后即可关闭连接,以便其他客户端能够及时连接服务器。
- 短连接适用于请求处理时间短、频繁的场景,如网页浏览。
长连接(persistent connection):
- 在长连接中,客户端和服务器之间的连接会保持打开状态,允许多个请求和响应在同一个连接上交互。
- 客户端可以发送多个请求,而无需在每个请求后重新建立连接。服务器可以保持连接打开,并持续发送响应给客户端。
- 长连接可以减少建立和关闭连接的开销,以及降低网络延迟,提高性能。
- 长连接适用于请求处理时间较长、频繁交互的场景,如实时通信或在线游戏。
在HTTP/1.0版本中,每个请求/响应默认都使用短连接。而在HTTP/1.1版本中,引入了持久连接的概念,也就是长连接。在HTTP/1.1中,连接默认为持久连接,除非显式地指定关闭连接。在长连接中,可以通过设置"Connection: keep-alive"头部来告知服务器保持连接打开。
需要注意的是,长连接并不意味着连接会一直保持开放,服务器或客户端都可以选择在适当的时机关闭连接。此外,长连接中仍然存在一些问题,如服务器资源占用和连接的建立和断开成本等,因此在实际应用中需要根据具体情况进行权衡和优化。
4、4 Cookie 介绍
4、4、1 什么是 Cookie
HTTP本身是无状态的,意味着服务器不会对与客户端之间的通信进行状态追踪。但我们在一个网址登陆后,关掉网页再次打开后,任然会记得我们登录的状态,这是为什么呢?为了提供更好的用户体验和实现个性化功能,浏览器会提供相应的服务,记录用户的状态信息。这是通过使用HTTP协议中的一些机制来实现的。例如,请求报文头的Cookie和响应报头中的Set-Cookie。
在HTTP中,Cookie是一种由服务器发送给浏览器并存储在用户计算机上的小文件。它用于跟踪和识别用户,并在用户访问同一网站时提供持久性数据的能力。
举个例子,假设我们访问一个需要登录才能访问的网站,我们首次登录时,服务器会下发一个Set-Cookie响应头来设置一个名为sessionid的cookie,值为一串唯一的随机字符串。之后,我们每次再访问该网站时,浏览器都会自动在请求包头中加入“Cookie: sessionid=随机字符串”的信息,告诉服务器我们之前已经登录过了。下面我们详解一下此过程。
4、4、2 Cookie 与 Session id 实现用户身份验证
Session是服务器端用来跟踪用户状态的机制。当用户首次访问网站时,服务器为其分配一个唯一的会话ID,并将该ID发送到浏览器。浏览器则将该会话ID保存在Cookie中。服务器使用该会话ID来跟踪用户的状态,并根据需要更新或保存用户的相关信息。
最早的时候,在没有Session时,服务器是直接将用户名和密码返回到浏览器保存在Cookie文件中的。就是为了让用户不用每打开一个网页进行验证。具体如下图:
但是,这时候一个黑客向你电脑注入了木马,去扫描浏览器的文件很容易就获取你的用户信息了。这样就太不安全了。
后来服务端就不会再返回给浏览器用户名和密码等隐私信息了,服务端会生成一个Session id来进行返回,也进行了身份验证。
举个例子,假设一个用户通过浏览器登陆了一个网站,并勾选了“保持登录状态”选项。当用户登录成功后,服务器会创建一个唯一的Session id,并将这个Session id与该用户的相关信息(例如用户名、权限等)进行绑定,并将这些信息存储在服务器端的数据库或缓存中。并将这个Session id返回存储在Cookie中。当用户访问其他页面或进行其他操作时,浏览器会自动将Cookie中的Session id一同发送给服务器,以便服务器能够辨识出是哪个用户在访问。这样就实现了用户身份的验证。具体过程如下图:
Session id是一个用于标识用户会话的唯一标识符。它通常由服务器生成,并在客户端与服务器之间进行传输。Session id可以保存在Cookie中,也可以以其他形式进行传输,例如URL参数或隐藏字段。
这样就安全了吗?不是的,只能说是相对安全。黑客依然可以拿到你的Session id 进行登录。 但是不会看到你的隐私信息了。现如今由很多的抓包工具,可以捕获到HTTP的请求报文和响应报文,进而提取更多的信息。HTTP是不安全的,因为它的通信过程是明文的,这意味着在传输过程中,潜在的攻击者可以截获、窃听或篡改传输的数据。这就需要对传输过程的数据进行加密,也就输HTTPS。后续也会对此进行详解。
4、5 状态码
HTTP状态码是服务器用于向客户端表示请求的处理结果的三位数字代码。它们告知客户端发送的请求在服务器端的处理情况,并根据不同的情况提供不同的响应。以下是几个常见的HTTP状态码及其含义:
1xx信息状态码:
- 100 Continue:表示服务器已经接收到部分请求,客户端应该继续发送剩余的请求。
- 101 Switching Protocols:表示服务器将切换到新的协议,如HTTP升级到WebSocket。
2xx成功状态码:
- 200 OK:表示请求成功,并返回相应的结果。
- 201 Created:表示在服务器上成功创建了新资源。
- 204 No Content:表示服务器成功处理了请求,但不返回任何内容。
3xx重定向状态码:
- 301 Moved Permanently:永久性重定向,请求的资源已被分配到新的URL。
- 302 Found:临时性重定向,请求的资源临时被分配到新的URL。
- 304 Not Modified:表示资源未被修改,客户端可以使用缓存的数据。
4xx客户端错误状态码:
- 400 Bad Request:表示服务器无法解析客户端的请求。
- 401 Unauthorized:表示请求需要用户身份验证。
- 404 Not Found:表示请求的资源不存在。
5xx服务器错误状态码:
- 500 Internal Server Error:表示服务器遇到了错误,无法完成请求。
- 503 Service Unavailable:表示服务器当前无法处理请求,通常是由于过载或维护等原因导致。
以上只是HTTP状态码的一些常见示例,实际上还有其他更多的状态码。了解HTTP状态码可以帮助开发人员和网络管理员了解请求的处理结果,并根据不同的状态码采取相应的操作。
4、6 重定向
HTTP中的重定向是一种常见的机制,用于将用户的请求从一个URL重定向到另一个URL。重定向可以分为临时重定向和永久重定向,它们有着不同的含义和使用场景。
临时重定向(Temporary Redirect):
- 状态码:302 Found 或 307 Temporary Redirect。
- 含义:临时重定向表示被请求的资源目前暂时性地移动到了另一个URL。客户端在接收到此状态码后,应当继续使用原始的URL进行以后的请求。临时重定向可能只是暂时的,因此客户端在将来的请求中仍然应该使用原始的URL。
- 使用场景:
- 当某个网站或页面需要进行临时的维护、更新或修复时,可以使用临时重定向将用户的请求导向到一个提示页面或另一个可用的URL上。
- 在某些情况下,网站可能根据用户的地理位置或设备类型,将用户的请求暂时重定向到最适合的服务器或页面上。
永久重定向(Permanent Redirect):
- 状态码:301 Moved Permanently 或 308 Permanent Redirect。
- 含义:永久重定向表示被请求的资源已经永久性地移动到了另一个URL。客户端在接收到此状态码后,应当使用新的URL进行以后的请求。常见的浏览器会自动将之前的URL缓存,并直接请求新的URL。
- 使用场景:
- 当某个网站或页面的URL发生变化,但内容和功能保持不变时,可以使用永久重定向来告知搜索引擎和用户此变化,并保持原有的搜索引擎排名、链接关系和用户体验。
- 在对某个URL进行优化和搜索引擎优化(SEO)方面的考虑,永久重定向可以确保搜索引擎能够正确地索引和排名你的网站。
五、HTTP 总结
HTTP协议是一种用于在Web浏览器和Web服务器之间传输数据的应用层协议。它具有无状态、基于请求-响应模型、支持多媒体、可用于缓存、简单灵活等特点。到这里你在看这段总结可能就会有不一样的感觉。HTTP的主要特点如下:
无状态:HTTP协议是无状态的,即服务器不会记录客户端的请求过程,每个请求都是独立的。这意味着服务器对于相同的客户端请求没有记忆,因此,无论之前是否有过请求或者连接,客户端都需要提供完整的请求信息。
基于请求-响应模型:客户端发送一个HTTP请求到服务器,并等待服务器的响应。请求包括请求行(包含请求方法、URL和协议版本)、请求头部和可选的请求体;响应包括状态行(包含状态码和状态文本)、响应头部和响应体。
支持多媒体:HTTP协议支持多种类型的媒体,包括文本、图片、音频、视频等。通过指定适当的Content-Type头部字段,可以告知客户端和服务器发送或接收的数据类型。
可用于缓存:HTTP协议使用缓存来减轻服务器的负载和提高性能。服务器可以通过在响应头部添加相应的Cache-Control字段来指示客户端是否可以缓存响应内容,客户端可以通过在请求头部添加If-Modified-Since字段来检查是否可使用缓存的内容。
简单灵活:HTTP协议的格式简单、灵活,易于实现和扩展。协议定义了许多不同类型的请求方法(如GET、POST、PUT、DELETE等)和状态码(如200、404、500等),使得开发者能够基于需求进行合适的请求和处理。
更多推荐
所有评论(0)