目录
1. HTTP请求方式
1.1. HTML 表单
1.2. GET && POST方法
1.2.1. 用 GET 方法提交表单数据
1.2.2. 用 POST 方法提交表单数据
1.2.3. 总结
1.3. 其他方法
2. HTTP的状态码
2.1. 重定向
2.1.1. 临时重定向 && 永久重定向
2.1.2. 302 && 307 的差别
2.1.3. Location 属性
2.1.4. HttpServer.cc (实现一个 302 重定向)
2.1.5. HttpServer.cc (实现一个 302 重定向 && 重定向到自己服务器上的资源)
3. HTTP 常见报头
4. 会话管理
4.1. HTTP 的特征
4.2. Cookie && Set-Cookie
4.3. 网站如何进行用户管理
4.3.1. 方案一:
4.3.2. 方案二
4.4. HttpServer.cc (包含 Cookie && Set-Cookie )
5. 总结
1. HTTP请求方式
首先, 我们要知道, 客户平时的上网行为其实就两种:
- 从服务端获取资源:客户端通过发送 HTTP 请求到服务端,从服务端获取各种资源,如网页、图片、视频、文档等。这些资源是用户可以感知并理解的数据,通过客户端的浏览器或应用程序展示给用户使用。当用户在浏览器中输入 URL 或点击链接时,就是在请求服务端获取相应资源进行展示。
- 将资源上传到服务器:客户端也可以将一些数据或文件通过 HTTP 请求上传到服务器,例如上传图片、视频、文档等文件。客户端可以通过表单提交、文件上传接口等方式将用户生成的数据或文件发送到服务端,服务端接收和处理这些数据,并根据业务需求进行相应操作。
因此,HTTP 协议中的客户端主要是通过请求获取服务端的资源或将数据上传至服务器,这些资源是用户能够感知和理解的各种形式的数据,包括图片、视频、文档等。客户端和服务端之间的数据交互和资源传输是 Web 应用程序正常运行的基础。
在 HTTP 通信过程中,服务器和客户端本质上可以被视为是两个独立的进程。
客户端进程发送 HTTP 请求到服务器并接收和处理从服务器返回的 HTTP 响应。
服务器进程负责接收来自客户端的 HTTP 请求,处理请求并生成对应的 HTTP 响应;
因此,HTTP 请求和响应的传输过程实质上就是服务器进程和客户端进程之间的数据交互和通信,即进程间通信。通过进程间通信,客户端能够请求所需的资源并接收服务端返回的数据,从而实现 Web 应用程序的功能和交互。
在一般的 HTTP 应用场景中,GET 和 POST 方法是最常用的两种 HTTP 请求方法。
对于从服务器获取数据:一般都是GET方法;
对于向服务器提交数据: GET && POST方法都可以,但更多的是POST方法。
- 在一般的 HTTP 应用场景中,大部分情况下 HTTP 请求方法就是 GET 和 POST,两种方法通常可以满足大多数 Web 应用程序的需求。 其他的方法要么服务器不支持,要么被服务端禁用了。
- 在安全考虑和最小接口原则下,服务端会限制或禁用一些不常用或潜在安全风险较大的 HTTP 请求方法。通过仅开放必要的请求方法,可以减少潜在安全漏洞的可能性,提高系统的安全性和稳定性。
- 总的来说,确保 Web 服务器提供最基本功能并保持安全性是很重要的,因此一些不常用或不必要的 HTTP 请求方法可能会被禁用或限制。
为了解释 GET 和 POST 方式的差别,我们需要引入一个表单的概念。
1.1. HTML 表单
表单在 Web 应用中通常用于收集用户输入的数据,并将这些数据按照特定方式传输到服务器 (表单中是有方法 (method) 的)。当用户填写完表单后,表单数据会被添加到 HTTP 请求的一部分,然后通过网络发送到服务器端。
既然表单会作为 HTTP 请求的一部分, 是需要提交给服务端的, 因此,需要指明 HTTP 的提交方法 (GET、 POST),因此,我们就要看一看,当用 GET 和 POST方法提交表单数据会有怎样的差异呢?
首先,我们先见一见下面这个表单 (index.html):
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<title> 咸蛋超人的网站 </title>
</head>
<body>
<form name="input" action="haha.html" method="GET">
Username: <input type="text" name="user"> <br>
Paseword: <input type="password" name ="password"> <br>
<input type="submit" value="登录">
</form>
</body>
</html>
- action 和 method 是 <form> 表单元素的两个属性,分别用于指定表单提交的目标URL和提交数据的HTTP请求方法;
- action 属性用于指定表单提交的目标URL,即在用户提交表单数据后,数据将发送到该URL对应的服务器端资源进行处理。在这个例子中,action="haha.html" 表示表单数据将提交到 haha.html 这个页面或资源;
- method 属性用于指定提交表单数据时所使用的 HTTP 请求方法,可以是 GET 或 POST。在这个例子中,method="GET" 表示表单数据将使用 GET 方法提交给服务器;
- 因此,这个 HTML 中的 <form> 元素表示一个简单的登录表单,当用户填写用户名和密码后,点击登录按钮提交表单数据时,数据将被以 GET 方法提交到 haha.html 这个目标URL。
1.2. GET && POST方法
上面我们已经见了见表单,现在,我们就要分别以GET和POST方式将表单数据提交到服务端,看看它们的差别是什么。
首先,我们的准备工作:
index.html,就是我们服务端的默认首页,也即是接下来的表单;
haha.html,就是表单中的action, 也就是表单提交的目标URL;
error.html,如果资源不存在,状态描述符是404;
其次, 我们用的HTTP demo,就是HTTP --- 上的 demo;
1.2.1. 用 GET 方法提交表单数据
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<title> 咸蛋超人的网站 </title>
</head>
<body>
<form name="input" action="haha.html" method="GET">
Username: <input type="text" name="user"> <br>
Paseword: <input type="password" name ="password"> <br>
<input type="submit" value="登录">
</form>
</body>
</html>
上面这个表单使用的提交方式是GET,即以GET方式将表单数据提交到haha.html 这个网页中。
服务端跑起来,因为这个表单此时是服务端的默认首页 (index.html),因此在浏览器输入IP:Port,就会跳转到该表单页面,具体如下:
然后,我们输入 Username && Password,假设我们分别输入李四,123456:
点击登录, 注意,我们观察输入前后的URL,现象如下:
HTTP请求如下:
可以清楚的看到, 当我们使用GET方法将表单数据传入服务端时,此时表单数据作为查询字符串的一部分添加到了 URL 中。即此时HTTP请求的 URL 是表单中的action?表单输入的数据 (多个输入数据用 '&' 连接)。
总而言之,GET 方法是将表单数据附加到HTTP请求的URL中传递给服务端,这使得参数在 HTTP 请求中是可见的,易于查看和理解。
1.2.2. 用 POST 方法提交表单数据
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<title> 咸蛋超人的网站 </title>
</head>
<body>
<form name="input" action="haha.html" method="POST">
Username: <input type="text" name="user"> <br>
Paseword: <input type="password" name ="password"> <br>
<input type="submit" value="登录">
</form>
</body>
</html>
服务端跑起来,因为这个表单此时是服务端的默认首页 (index.html),因此在浏览器输入IP:Port,就会跳转到该表单页面,具体如下:
然后,我们输入 Username && Password,假设我们分别输入李四,123456:
点击登录, 注意,我们观察输入前后的URL,现象如下:
HTTP 请求如下:
- 可以清楚的看到,当表单使用 POST 方法提交时,POST 方法通过 HTTP 请求的请求正文提交参数至服务器,而非像 GET 方法那样将参数直接附加在 URL 中。
- 这使得 POST 方法更适合用于传输敏感信息。如用户的登录凭证 (私密性) 或表单中的大量输入数据 (因为它不会添加到URL中)。
1.2.3. 总结
上面的过程大致是相同的,只不过提交表单的方式不同罢了, 但为了更好地理解,我们可以画一下图,形象的描述下这个过程:
因为,在我们的测试中,服务端的默认首页就是表单,因此,客户端通过IP:Port就能访问到服务端的默认首页,也就是表单,表单收集用户的数据,并将数据以特定的方式 (GET、POST) 传递给服务端。
GET方式,是将表单数据附加到HTTP请求的URL中传递给服务端。由于数据明文添加在 URL 中,因此用户输入的信息会在 URL 中回显,不够私密,适合传输一些不敏感的数据。
POST方式,是将表单数据添加到HTTP请求的请求正文中传递给服务器。因为数据不会被明文附加在 URL 中,用户输入的信息不会在 URL 中回显,具有一定的私密性,适合传输一些敏感数据,如用户密码或个人信息。
上面提了一个私密性,请注意:私密性不等同于安全性。
由于HTTP协议是明文传输数据,因此,是具有一定的安全隐患的,而私密性只不过可以让一些小白看不到罢了,它不等同于安全性,如果网络数据是以明文传送,那么安全性无法得到保证,只有对数据进行加密和解密才具有安全保证,也就是我们后面要说的HTTPS协议。
具体讲:
私密性仅仅意味着数据不会被直接暴露给普通用户,但并不提供足够的保障以防止数据在传输过程中被拦截和窥视。在HTTP协议下,数据是以明文形式传输的,这意味着网络中的任何人都可以轻松地截取和查看传输的数据,包括敏感信息。
因此,即使使用了 POST 方法将数据放在请求正文中传输,也无法确保数据的安全性。为了保障数据在传输过程中的安全,确实需要对数据进行加密和解密。HTTPS 协议正是为了解决这一问题而设计的,它通过使用 SSL/TLS 加密数据传输,提供了一种经过加密保护的安全通信机制,确保数据在传输过程中的机密性、完整性和信任。
这就是我们对GET和POST方法的理解,done;
1.3. 其他方法
当谈到 HTTP 方法时,除了常见的 GET 和 POST 方法外,还有一些其它的方法,它们可能并不常用,但简单了解一下还是需要的:
方法 | 方法描述 |
---|---|
PUT | 用于将请求的数据存储到指定的 URL 中,类似于 POST 方法,但 PUT 要求请求修改或替换指定的资源。 |
HEAD | 与 GET 方法类似,但服务器不返回实体主体部分。主要用于获取资源的元数据,如响应头信息、响应状态等。 |
DELETE | 用于请求服务器删除指定的资源。很显然,这个方法服务端一般都会禁用。 |
OPTIONS | 用于获取目标资源所支持的通信选项,这个方法可使服务器描述其支持的方法。 |
TRACE | 回显服务器接收到的请求,用于诊断、测试或调试。 |
CONNECT | 用于将连接改为管道方式的代理服务器。通常用于 SSL / TLS 连接的建立。 |
LINK | 用于建立与资源的连接关系。 |
UNLINK | 用于断开资源与连接关系。 |
最后, 对于HTTP请求方式, 最常用的就是GET和POST,其他都简单了解即可。
2. HTTP的状态码
进程有自己的退出码,用于表示进程的执行结果;而对于一个HTTP请求而言,它也有自己的状态码,用以表示这次 HTTP 请求的结果,并且这个状态码都有与之匹配的状态码描述,用来描述这次 HTTP 请求的结果。
状态码和状态码描述提供了关于请求处理情况的信息,让客户端能够更好地了解服务器对请求的响应。具体如下:
类别 | 原因短语 | |
1xx | Information (信息状态码) | 接受的请求正在处理 |
2xx | Success (成功状态码) | 请求正常处理完毕 |
3xx | Redirection (重定向状态码) | 需要进行附加操作以完成请求 |
4xx | Client Error (客户端错误状态码) | 服务器无法处理请求 |
5xx | Server Error (服务端错误状态码) | 服务器处理请求出错 |
接下来,列举一些,常见的状态码和相应的状态码描述:
1xx 信息性状态码:
- 100 Continue:服务器已收到请求的部分,需要客户端继续发送剩余部分。
- 101 Switching Protocols:请求切换协议,服务器已经理解客户端的请求,并将通过升级首部通知客户端使用不同的协议。
2xx 成功状态码:
- 200 OK:请求成功。
- 201 Created:请求已经被实现,新资源已经创建。
- 204 No Content:服务器成功处理请求,但未返回内容。
3xx 重定向状态码:
- 301 Moved Permanently:请求的资源已被永久移动到新位置。
- 302 Found:请求资源临时被移动。
- 307 Temporary Redirect:临时重定向。服务器要求客户端进行临时重定向至另一个 URL,并且客户端应该保持原始的请求方法不变去请求新的 URL。
4xx 客户端错误状态码:
- 400 Bad Request:请求无效。
- 401 Unauthorized:请求需要用户认证。
- 403 Forbidden:服务器理解请求,但拒绝执行。
- 404 Not Found:请求的资源未找到,客户端的非法请求。
5xx 服务器错误状态码:
- 500 Internal Server Error: 服务器遇到错误,无法完成请求。
- 503 Service Unavailable: 服务器当前无法处理请求。
- 504 Gateway Timeout: 网关超时。服务器作为网关或代理,在设定的时间内无法从上游服务器或辅助服务器获得响应,导致超时。
最后,强调一下, HTTP状态码和状态码描述是有自己的标准的,因此,在未来我们编写HTTP代码时,我们最好保持状态码和状态码描述一致,这有助于确保代码的可读性、可维护性和互操作性。
2.1. 重定向
2.1.1. 临时重定向 && 永久重定向
临时重定向(302 && 307):
- 临时重定向状态码表示请求的资源暂时被移动到另一个位置,客户端需要继续使用原始的请求 URI(资源统一资源标识符)来访问资源。
- 这种情况下,对用户的后续请求策略通常不产生影响,因为服务器告诉客户端这是一个临时重定向,客户端会继续使用原来的 URI。
- 临时重定向常用于需要临时切换位置的情况,比如临时维护、临时性重定向等。
永久重定向(301):
- 永久重定向状态码表示请求的资源已经永久性地移到了一个新的位置,客户端应该记住这个变更并且使用新的位置来访问资源。
- 这种情况下,影响用户后续请求策略,因为客户端会记住资源的永久转移,并且之后的请求都会直接访问新的位置。
- 永久重定向常用于需要长期改变资源位置的案例,比如网站域名变更、资源永久移动等。
2.1.2. 302 && 307 的差别
302 Found 和 307 Temporary Redirect 确实都是表示临时重定向的状态码,它们之间的主要区别在于处理重定向时对于原始请求方法的处理:
302 Found:
- 302 Found 是最常见的临时重定向状态码之一。当服务器返回 302 状态码时,表示请求的资源暂时被移动到一个新的位置。客户端应该使用 GET 方法来重发请求。
- 在使用 302 Found 时,客户端在重定向请求时可能会将原始请求方法更改为 GET 方法,导致 POST 请求被转换为 GET 请求,可能会对业务逻辑产生影响。
307 Temporary Redirect:
- 307 Temporary Redirect 也表示请求的资源暂时被移动到新的位置。与 302 不同的是,307 要求客户端继续使用相同的请求方法。如果原始请求是 POST 方法,重定向后的请求也应该是 POST 方法。
- 使用 307 Temporary Redirect 可以确保客户端在重定向时保持原始请求方法不变,避免了在处理 POST 请求时出现的问题。
因此,虽然 302 Found 和 307 Temporary Redirect 都表示临时重定向,但在处理原始请求方法上存在差别。如果需要保持原始请求方法不变,可以选择使用 307 Temporary Redirect,以确保请求在重定向后能够正确执行。
2.1.3. Location 属性
上面我们都是站在理论的角度看待重定向问题,可是,这个重定向过程究竟是怎样的呢?
- 客户端发起 HTTP 请求:客户端向服务器发送 HTTP 请求,请求的资源可能由于某种原因需要重定向到另一个位置。
- 服务器解析请求:服务器接收到客户端的 HTTP 请求后,会解析请求,并发现需要重定向。
- 服务器发送重定向 HTTP 响应:当服务器确定需要进行重定向时,会向客户端发送包含适当重定向状态码(如 301、302、307 等)的响应,同时在响应报头中包含新的目标位置。
- 客户端处理重定向:客户端接收到包含重定向状态码的响应后,根据状态码以及响应报头中的新目标位置信息,会自动发起新的 HTTP 请求,请求重定向后的资源。
- 新的服务端响应:新的服务端接收到客户端的请求后,会处理请求并返回相应的 HTTP 响应数据。
总体来说,重定向是通过在服务器对客户端的请求中返回特定的状态码和新的目标位置来触发的。客户端在接收到重定向响应后,会根据新的目标位置继续请求资源,从而完成整个重定向过程。
可是上面存在一个问题,服务端是如何告诉客户端新的服务端的地址呢? 换言之, 服务端发送给客户端的 HTTP 响应中的什么属性包含了服务端的地址呢?
答案是:在HTTP响应报头中有一种属性,Location,其对应的值就是新的服务端的地址。
因此,我们可以演示一下这个场景,我们引用 HTTP --- 上的 3.3.3. 的代码,在此基础之上做出更改。
2.1.4. HttpServer.cc (实现一个 302 重定向)
思路: 当客户端要访问的资源路径服务端不存在时,服务端返回一个带有302重定向的响应,其中包含了 Location 属性,就是重定向后的服务端的地址,我们在这里以 cpluscplus网站为例,代码如下:
void HttpServerHandle(int sock)
{
// 上面的代码没有做出更改, 在这里省略
// 如果该文件不存在, 那么HTTP响应中的状态码就是404
if(!in.is_open())
{
// 302 重定向
// 状态行(服务器端HTTP版本 状态码 状态码描述符\r\n)
HttpResponse = "HTTP/1.1 302 Found\r\n";
// 添加响应报头
// Location 代表的就是重定向后的新的服务端的地址
HttpResponse += "Location: https://cplusplus.com/\r\n";
// 空行
HttpResponse += "\r\n";
// done
}
else
{
// 省略
}
// 读取文件完毕后, 关闭文件
in.close();
// 打印一下HTTP 响应
std::cout << "****************************" << std::endl;
std::cout << HttpResponse ;
std::cout << "****************************" << std::endl;
// 发送给客户端
send(sock, HttpResponse.c_str(), HttpResponse.size(), 0);
}
int main(int arg, char** argv)
{
// 省略
}
同时我们将我们的默认首页,也就是我们的 HTML 表单中的 action填一个不存在的资源,如下所示:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<title> 咸蛋超人的网站 </title>
</head>
<body>
<form name="input" action="abcdhaha.html" method="GET">
Username: <input type="text" name="user"> <br>
Paseword: <input type="password" name ="password"> <br>
<input type="submit" value="登录">
</form>
</body>
</html>
因为此时的action = "abcdhaha.html",这个资源在我的服务端是不存在的, 因此客户端通过我的IP:Port进入我的默认首页,填充表单,在以GET的方式将表单数据发送给服务端 (也就是登录)。
如下图所示:
由于这个资源服务端不存在,因此服务端打开文件会失败,故会进入重定向的逻辑, 而此时的Location填充的就是 cplusplus 这个网站,因此此时浏览器就会跳转到 cplusplus 网站。
2.1.5. HttpServer.cc (实现一个 302 重定向 && 重定向到自己服务器上的资源)
当然我们也可以重定向到我们自己的服务器上的资源,本质上,我们只需要更改Location即可,如下所示:
void HttpServerHandle(int sock)
{
// 上面的代码没有做出更改, 在这里省略
// 如果该文件不存在, 那么HTTP响应中的状态码就是404
if(!in.is_open())
{
// 302 重定向
// 状态行(服务器端HTTP版本 状态码 状态码描述符\r\n)
HttpResponse = "HTTP/1.1 302 Found\r\n";
// 添加响应报头
// Location 代表的就是重定向后的新的服务端的地址
HttpResponse += "Location: HTTP://82.157.67.44:8080/error.html\r\n";
// 空行
HttpResponse += "\r\n";
// done
}
else
{
// 省略
}
// 读取文件完毕后, 关闭文件
in.close();
// 打印一下HTTP 响应
std::cout << "****************************" << std::endl;
std::cout << HttpResponse ;
std::cout << "****************************" << std::endl;
// 发送给客户端
send(sock, HttpResponse.c_str(), HttpResponse.size(), 0);
}
int main(int arg, char** argv)
{
// 省略
}
现象如下:
填充HTML表单,并提交表单数据,也就是登录。
我们的预期:当登录后,浏览器就会自动跳转到 error.html 这个网页。
如下图所示:
这就是重定向的整个过程, done;
3. HTTP 常见报头
当客户端发送 HTTP 请求或服务器返回 HTTP 响应时,会使用各种不同类型的报头(Header)来携带和传递信息。下面是一些常见的 HTTP 报头:
通用报头(General Header):
- Cache-Control:控制缓存的行为,例如缓存有效时间;
- Connection:指定与连接相关的选项,close (短链接),keep-alive (长连接);
- Date:提供消息创建的日期和时间;
- Via:指定代理服务器信息;
请求报头(Request Header):
- Host:指定被请求资源的主机名和端口号;
- User-Agent:包含发起请求的用户代理信息,通常是浏览器或应用程序的标识;
- Accept:指定客户端接受哪些类型的响应内容;
- Cookie:包含客户端的 Cookie 信息;
- Referer:用于指示客户端发起当前请求的来源页面的地址。通俗讲,当前页面是从哪个页面跳转过来的;
响应报头(Response Header):
- Server:提供关于服务器软件的信息;
- Content-Type:指定响应内容的类型 (例如 text/html(网页)、text/plain(纯文本) 等等);
- Set-Cookie:服务器通过该报头将 Cookie 信息发送给客户端;
- Location:搭配3xx的状态码使用,用于指定重定向的目标位置;
实体报头(Entity Header):
- Content-Length:指定响应正文的长度;
- Content-Encoding:指定响应正文的编码方式;
- Content-Disposition:指定如何显示响应正文,通常用于下载文件;
4. 会话管理
4.1. HTTP 的特征
- 简单快速:HTTP是设计成简单且快速的协议,它的简单性使得它容易实现,同时也有利于减少通信的开销。
- 无连接:HTTP是无连接的,也就是每次请求都需要创建新的连接,而在请求结束后这个连接就会关闭。但是,由于这种方式会带来一定的开销,因此在实际应用中,引入了持久连接(HTTP keep-alive),允许在单个连接上发送和接收多个请求与响应,以减少建立和关闭连接的开销。
- 无状态:HTTP是无状态的,这意味着服务器不会在多个请求之间保持任何状态信息。每个请求都是独立的,服务器不会记住之前的请求状态。这就需要应用程序自己去维护客户端的状态,例如通过Cookie等方式。
在这里解释一下无连接和无状态。
无连接:我们之前说过, HTTP是一个应用层的协议,底层采用的是传输层的TCP协议,而TCP协议是面向连接的,那为什么说HTTP是无连接的呢?
- 很简单, 在HTTP协议看来,它只负责HTTP请求或响应数据的传输;
- HTTP作为一个应用层协议,并不直接处理连接的建立和维护,而是依赖于底层的传输层协议(TCP)来处理连接相关的操作。
- 即连接不是HTTP该考虑的事情,这应该是TCP需要维护的,因此 HTTP 是无连接的。
无状态:什么叫做无状态呢?
- 无状态(Stateless)是指HTTP协议本身不会在服务器端保留客户端请求的状态信息;
- 每个请求都是独立的,服务器不会记住之前请求的信息。
- 换句话说,服务器不会对客户端的状态进行记录,也不会保留关于客户端的会话信息。
可是,不对啊,我们在实际中使用的时候,一般的网站是会记录下用户状态的啊。
例如,一些网站,登陆后,如果关闭,重新进入该网站,此时依旧处于登陆状态啊,这该如何解释呢?
注意: 虽然HTTP是无状态的, 但并不代表这些网络服务不会记录用户的状态,事实上, 记录用户状态这件事情,本来就不应该是HTTP该考虑的, 而应该是由上层业务逻辑去考虑并实现的, HTTP 只要负责好网络功能就可以了。
因此,虽然HTTP是无状态的, 但是为了支持网站对用户状态进行管理,在HTTP报头属性中有两个字段: Cookie (请求报头中涵盖的), Set-Cookie (响应报头中涵盖的)。
4.2. Cookie && Set-Cookie
当客户端(通常是浏览器)首次访问一个使用了 Cookie 的网站时,服务器会在响应报头中包含 Set-Cookie 字段,用来在客户端存储一个名为 Cookie 的会话标识符。在随后的每次请求中,客户端会将这个会话标识符通过 Cookie 字段发送回服务器。
- Set-Cookie:服务器在响应报头中设置的字段,用来向客户端发送一个新的 Cookie。它包含了要在客户端存储的 Cookie 的名称、值以及其他属性,如过期时间、域名、路径等。服务器可以通过 Set-Cookie 字段来指示客户端存储特定的会话标识符或其他信息。
- Cookie:客户端在每次请求中发送的字段,用来携带先前存储的 Cookie 信息。在客户端接收到服务器发送的 Set-Cookie 字段后,将会在随后的每次请求中自动包含相应的Cookie字段,以便服务器能够识别和关联每个请求属于哪个用户或会话。
这两个字段配合使用,实现了在客户端和服务器之间传递和存储状态信息的功能,用于实现用户状态的管理和保持。
4.3. 网站如何进行用户管理
为了更好地理解上面这两个属性,我们来了解一般网站是如何进行用户管理的。
4.3.1. 方案一:
- 客户端向服务端发起 HTTP 请求, 在这里我们就具体一点,输入用户的账号和密码;
- 服务端收到客户端的请求之后, 服务端会对请求进行相关认证 (比如用户密码的验证),是否为合法用户;
- 如果是, 服务端将该客户信息以及其它属性,构成HTTP响应返回给客户端;
- (上面就是一个登录的过程);
- 此时客户端 (浏览器) 会维护一个 文件 or 内存文件,该文件将浏览器曾经获得的私密信息 (用户名 && 密码) 保存起来, 保存到浏览器本地。
- 之后,客户端的每一次 HTTP 请求,会携带用户曾经输入的信息,因此,只要用户第一次成功登录,后续再访问该网站,就不需要再输入登录信息了,即可以保证用户处于一种登陆状态;
- 我们一般将客户端 ( 浏览器 ) 中保存客户私密信息的文件称之为 Cookie 文件。 Cookie 有磁盘文件 ,也有内存级别的文件。
- 浏览器通常会将 Cookie 存储在磁盘文件中,以便将这些信息保存下来并在浏览器重新打开时仍然有效,同时也会在内存中维护一份Cookie的副本,用于实时的交互和状态共享。
那我们能不能看到这个 Cookie 呢? 如下:
上面的方案是存在严重问题的,如下:
- 有没有一种可能,作为一个小白用户,点了一个未知链接,导致客户端 (浏览器) 被植入了木马程序;
- 此时这个木马程序就扫描用户的客户端 (浏览器),并查找和窃取保存在其中的 Cookie 文件;
- 并通过木马程序,将 Cookie 文件(包含用户的私密信息) 推送给了其它人;
- 那么此时其它人就可以通过这个 Cookie 文件在自己的浏览器使用这些 Cookie 信息,欺骗网站,以用户的身份登录到相关网站;
- 甚至,如果这个 Cookie 文件是明文保存用户的私密信息, 那么就会导致用户信息严重泄漏,甚至可能造成财产损失或其他安全问题。
可以看到,这种方案实在是太不具备安全性了, 因此,在当今网络环境中, 这种方案不会被采用,因为它无法保证用户信息的安全性。
那该如何处理呢? 这里的Cookie 文件不用了吗? 答案是, 还要用,但方案需要做出调整,如下:
4.3.2. 方案二
- 客户端发起 HTTP 请求, 输入相关信息 (用户名 + 密码);
- 服务端收到HTTP请求后,对用户信息进行相关认证;
- 验证成功后,服务端会创建一个包含用户私密信息的文件,并使用特定算法生成一个唯一的 session ID,将该ID作为文件名;
- 服务端在 HTTP 响应中返回这个session ID 给客户端;
- 客户端本地维护 Cookie 文件,并将服务端发送的 session ID 保存到 Cookie 文件中,当然还有其他的信息 (诸如时间等等);
- 当客户端再次访问该网站时,会携带存储有 session ID 的 Cookie 文件,服务端通过session ID进行识别和验证,后续客户端再访问该网站,就不需要再输入登录信息了,即可以保证用户处于一种登陆状态;
在方案一中,这个 Cookie 文件保存的使用户的私密信息, 又被泄露的风险;而在方案二中,Cookie 文件保存的是 session ID, 不会泄露用户的私密信息。
可是这样就不会存在安全问题了吗?如下:
- 作为一个小白用户,我又点了一个未知链接,导致客户端 (浏览器) 被植入了木马程序;
- 此时这个木马程序就扫描用户的客户端 (浏览器),并查找和窃取保存在其中的 Cookie 文件;
- 并通过木马程序,将 Cookie 文件( session ID) 推送给了其它人;
- 那么此时其它人就可以通过这个 Cookie 文件在自己的浏览器使用 session ID,以同样的方式访问网站上的服务。
基于 session ID 的认证和状态管理方式 && 直接在Cookie存储用户私密信息 相比:
- 虽然此时仍然存在被恶意程序窃取 Cookie 的风险,但至少用户的私密信息不会明文暴露在 Cookie 里;
- 其它人通过这个被窃取的 session ID 虽然可以欺骗网站,以用户的身份访问网站,但他们无法获取用户的实际私密信息;
- 因为这些私密信息并没有存储在 Cookie 中,而是由服务端在后台根据session ID和文件关联进行处理和验证。
session ID 这种策略本质上就是把用户信息的管理工作和会话保持工作全部交给服务端,由服务端统一维护,客户端只需要在初始登录时获取并保存session ID,之后的会话管理和验证工作都由服务端负责。
那么有人说,这种问题不能避免吗?
答案是: 不能避免。 在现实生活中,无论多么强大的安全措施都无法完全避免账户被盗取或信息泄露等等问题的发生。我们能做的就是,采取相关策略一定程度上降低发生这种事件的可能,但无法完全避免这种事情。
针对账户安全和信息保护,一些常见的策略包括:
- 异地访问检测:当用户的IP地址或其他访问特征发生异常变化时,服务端可以将相关的session ID设置为失效状态,要求用户重新进行身份验证。这可以帮助检测和防止恶意访问。
- 超时策略:通过设置 Cookie 文件中相关信息的超时时间,可以控制用户会话的有效期,一旦超过设定时间,系统会要求用户重新登录,从而降低长时间保持登录状态带来的风险。
这些策略可以在一定程度上增加账户和信息的安全性,及时发现异常活动,并减少恶意访问的可能性。同时,用户也需要注意保护自己的账户信息,如定期更改密码、不在不安全的网络环境下登录等。
尽管安全措施无法完全杜绝风险,但我们可以通过综合利用不同的安全策略来降低被盗取或泄露的风险,从而尽最大可能地保护用户的账户和信息安全。
4.4. HttpServer.cc (包含 Cookie && Set-Cookie )
Set-Cookie: 在 HTTP 响应中构造该报头,向客户端写回 Cookie 信息;
Cookie:当客户端接收到包含 Set-Cookie 头部的 HTTP 响应时,会自动将 Cookie 信息保存起来。在之后的每次HTTP请求中,客户端会将之前保存的 Cookie 信息包含在 HTTP 请求报头的 Cookie 字段中发送给服务器。
在2.1.5. 基础之上做出更改:
void HttpServerHandle(int sock)
{
// 上面的代码没有做出更改, 在这里省略
// 如果该文件不存在, 那么HTTP响应中的状态码就是404
if(!in.is_open())
{
// 302 重定向
// 状态行(服务器端HTTP版本 状态码 状态码描述符\r\n)
HttpResponse = "HTTP/1.1 302 Found\r\n";
// 添加响应报头
// Location 代表的就是重定向后的新的服务端的地址
HttpResponse += "Location: HTTP://82.157.67.44:8080/error.html\r\n";
// 空行
HttpResponse += "\r\n";
// done
}
else
{
// 状态行
HttpResponse = "HTTP/1.1 200 OK\r\n";
// Set-Cookie:
HttpResponse += "Set-Cookie: 这是一个Cookie\r\n";
// 空行
HttpResponse += "\r\n";
// 有效载荷
std::string content;
while(std::getline(in, content))
{
HttpResponse += content;
}
// done
}
// 读取文件完毕后, 关闭文件
in.close();
// 打印一下HTTP 响应
std::cout << "****************************" << std::endl;
std::cout << HttpResponse ;
std::cout << "****************************" << std::endl;
// 发送给客户端
send(sock, HttpResponse.c_str(), HttpResponse.size(), 0);
}
int main(int arg, char** argv)
{
// 省略
}
我们的预期现象:
- 现象一:当客户端用 IP:Port 进入我的服务端的默认首页,也就是该表单,此时浏览器就会有一个Cookie 文件, 文件内容就是:"这是一个Cookie";
- 现象二:当表单被提交,也就是我们点击登录后, HTTP 会发起一个请求,该请求内会有一个Cookie属性,内容就是:"这是一个Cookie";
现象一如下:
符合预期, 同时我们发现,这个 Cookie 文件的到期:浏览会话结束时,它代表的是:
- 如果此时浏览器退出了,这个 Cookie 文件也就会消失;
- 这个 Cookie 文件属于会话级别的 Cookie ,也称为会话 Cookie 或内存 Cookie;
- 会话 Cookie 是存储在内存中,而不是硬盘上的文件中;
- 当用户关闭浏览器时,会话Cookie会被删除,因此在下次会话开始时不会保留任何信息;
- 这种Cookie对于临时保存用户数据并在会话结束时自动删除非常有用。
现象二如下:
现象符合我们的预期。
5. 总结
至此,我们的HTTP就结束了。我们学了什么呢?
- 在 HTTP --- 上, 我们对 URL 以及 Web 根目录有了一定的理解,当然还有 HTTP 请求以及 HTTP 报头格式,当然还有一些 HTTP demo;
- 在 HTTP --- 下, 我们见了见 HTML 表单, 借助表单理解了 GET 和 POST的差别,以及了解了其他的请求方法;
- 在之后,我们谈了 HTTP 状态码及其状态码描述, 主要了解了重定向,并通过相关 demo 加以验证;
- 其次, 我们了解了常见的报头属性;
- 最后, 我们谈了会话管理这个话题。
HTTP 的相关属性体现了协议的特点 (诸如特定的请求方式是什么意思,特定的报头属性是什么含义,如何区分报头和有效载荷等等);
同时 HTTP 协议通过行为单位,将报头提取出来,再通过报头的相关属性确定有效载荷,体现了序列化和反序列化。
最后,再介绍两个工具 Postman && Fiddler;
Postman:
- 功能:可以通过 Postman 客户端向目标服务器构建HTTP请求;
- 特点:提供了用户友好的界面,支持创建和发送HTTP请求(包括GET、POST、PUT、DELETE等)并查看服务器响应。用户可以轻松设置请求参数、请求头、身份验证信息等,也可以进行测试脚本编写、测试集合管理、环境变量管理等。
- 用途:开发人员可以使用Postman来快速测试API端点、验证功能、调试问题,并共享和协作API测试集合。
Fiddler:
- 功能:Fiddler主要是用来进行抓包的。
- 特点:Fiddler可以截获计算机与网络之间的所有HTTP通信,展示请求和响应的详细信息,包括头部、正文、时间线等。同时还提供了各种调试功能,如重放请求、修改请求/响应、自定义规则等。
- 用途:开发人员可以利用Fiddler来调试Web应用程序、排查网络问题、优化性能,甚至模拟不同网络速度和条件下的交互情况。
HTTP是一种明文传送数据的协议。这意味着通过HTTP传输的数据在网络上是以明文形式传输的,容易被攻击者窃听、篡改或偷取敏感信息。因此,为了确保数据在传输过程中的安全性和保密性,发展出了HTTPS协议。
HTTPS(HyperText Transfer Protocol Secure)是基于HTTP的加密通信协议,通过在传输层上加入SSL/TLS协议来对HTTP数据进行加密和身份认证,确保数据在传输过程中的安全。使用HTTPS协议可以保护用户数据的隐私和完整性,有效抵御中间人攻击、窃取数据等安全威胁。
HTTP done, HTTPS 见。