【网络基础】HTTP协议的基本知识与服务器实现

2024-08-21 02:44

本文主要是介绍【网络基础】HTTP协议的基本知识与服务器实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 1. 认识URL
  • 2. urlencode和urldecode
  • 3. http 协议格式
    • **3.1 HTTP请求格式**
    • **3.2 HTTP响应格式**
  • 4. HTTP 常见头部-Header
  • 5. HTTP的方法
    • **幂等性与安全性解释**
      • **幂等性**
      • **安全性**
  • 6. HTTP的状态码
    • HTTP 状态码简介
      • 成功状态码
      • 客户端错误状态码
      • 服务器错误状态码
  • 7. 编写HTTP服务器获取http请求与响应信息
    • 7.0 Logger.hpp
    • 7.1 Socket.hpp
    • 7.2 TcpServer.hpp
    • 7.3 Session.hpp
    • 7.4 HttpProtocol.hpp
    • 7.5 main.cc
    • 7.6 代码链接:

1. 认识URL

URL即我们俗称的网址,一般标注的URL都有以下的几部分,我们以一个示例来举证:
在这里插入图片描述

  • 协议部分(Protocol): https://,指示使用HTTPS协议进行通信。
  • 登录信息部分(Username and Password): username:password@,表示需要提供用户名和密码来访问该资源。注意,这种方式传输的密码明文可能会被窃取,因此不建议在实际应用中使用。
  • 域名部分(Domain Name): www.example.com,表示需要访问的服务器的域名或IP地址。
  • 端口号部分(Port Number): 未指定端口号,默认使用协议的默认端口。
  • 路径部分(Path): /path/to/resource,表示需要访问的资源的路径。
  • 查询参数部分(Query Parameters): ?key1=value1&key2=value2,表示需要传递给服务器的额外数据。多个键值对之间使用 & 符号连接。
  • 片段标识部分(Fragment Identifier): #section,表示需要访问的页面内的特定位置(例如HTML文档中的锚点)。

2. urlencode和urldecode

我们可以注意到,URL中会出现如 /, ?, : 等特殊字符,它们在URL中具有特别的意义。如果数据或参数中包含这些特殊字符,就需要进行转义处理。转义规则如下:

转义规则

  1. 空格:空格通常被转义为%20

  2. 保留字符:以下字符是URL中的保留字符,在某些上下文中具有特殊含义,因此需要进行转义。

    • 冒号(:) 转义为%3A
    • 斜杠(/) 转义为%2F
    • 问号(?) 转义为%3F
    • 井号(#) 转义为%23
    • 方括号([ 和 ]) 转义为%5B%5D
    • 百分号(%) 转义为%25
  3. 非ASCII字符:非ASCII字符需要进行URL编码。它们将以 %xx 的形式表示,其中 xx 是字符的ASCII码值的十六进制表示。

  4. 其他特殊字符:除了上述情况外,其他特殊字符也可能需要转义,例如各种标点符号。

总结
转义的过程就是 urlencodeurldecode 的过程。urldecodeurlencode 的逆过程。


3. http 协议格式

HTTP(Hypertext Transfer Protocol)是一种用于传输超文本的应用层协议,用于在客户端和服务器之间传输信息。下面是一个简单的HTTP请求和响应的格式:

3.1 HTTP请求格式

一个简单的HTTP请求由以下几个部分组成:

  1. 请求行:包含请求方法、请求的URL以及协议版本。

  2. 请求头:包含了关于请求资源、客户端和更多信息的头部信息。

  3. 空行:用于分隔请求头和请求体。

  4. 请求体:包含实际发送给服务器的数据。

示例

GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
Accept-Language: en-USHello, server!

3.2 HTTP响应格式

一个简单的HTTP响应由以下几个部分组成:

  1. 状态行:包含协议版本、状态码以及状态信息。

  2. 响应头:包含了响应资源、服务器和更多信息的头部信息。

  3. 空行:用于分隔响应头和响应体。

  4. 响应体:包含实际返回给客户端的数据。

示例

HTTP/1.1 200 OK
Server: Apache
Content-Type: text/html<html>
<head><title>Hello, client!</title></head>
<body>
<h1>Welcome to my website!</h1>
</body>
</html>

4. HTTP 常见头部-Header

头部名描述
Content-Type指定请求或响应中的实体主体的媒体类型。
Content-Length指定请求或响应中实体主体的长度,以字节为单位。
User-Agent标识发起请求的用户代理(例如浏览器、爬虫等)的名称和版本号。
Cache-Control指定请求或响应的缓存机制。
Authorization用于向服务器提供身份验证凭据。
Cookie用于在客户端和服务器之间传递会话信息的标头字段。
Accept指定客户端接受的媒体类型,用于响应内容协商。
Location指定了重定向的目标位置。
Referer指定了当前请求来源的URL。
If-None-Match用于条件请求,指定了请求资源的实体标签(ETag)。
ETag用于响应中表示实体标签,用于缓存验证。

5. HTTP的方法

方法描述幂等性安全性
GET获取指定资源的表示形式
POST向指定资源提交数据进行处理
PUT将指定的资源用请求的表示替换掉目标资源
DELETE删除指定的资源
HEAD获取指定资源的头部信息
OPTIONS获取当前资源支持的HTTP方法列表和其他通信选项
TRACE对请求的传输路径进行追踪,主要用于测试和诊断
CONNECT建立与资源的双向连接,主要用于代理服务器
PATCH对指定资源进行局部修改
COPY将指定的资源复制到新的位置
MOVE将指定的资源从原位置移动到新位置
LINK将指定资源与另外一个资源进行关联
UNLINK取消指定资源与另外一个资源的关联

幂等性与安全性解释

幂等性

定义: 幂等性是指对同一个请求执行一次或多次,结果都应该是一样的。也就是说,对于具有幂等性的操作,重复执行多次不会产生副作用。

  • GET: 获取资源的请求。无论请求多少次,资源的状态不会改变,因此GET请求是幂等的。
  • PUT: 替换资源的请求。即使多次执行同样的PUT请求,资源的状态也不会改变,因此PUT请求是幂等的。
  • DELETE: 删除资源的请求。重复删除同一资源不会有额外的副作用,因为资源已经被删除,因此DELETE请求是幂等的。
  • HEAD: 获取资源头部信息。与GET类似,不会改变资源的状态,因此HEAD请求是幂等的。
  • OPTIONS: 获取资源支持的HTTP方法。不会改变资源状态,因此OPTIONS请求是幂等的。
  • TRACE: 追踪请求的传输路径。不会改变资源状态,因此TRACE请求是幂等的。

非幂等操作:

  • POST: 向服务器提交数据进行处理。重复执行POST请求可能会导致不同的结果或副作用,因此POST请求不是幂等的。
  • PATCH: 对资源进行局部修改。重复PATCH请求可能会产生不同的结果,因此PATCH请求不是幂等的。
  • COPY: 复制资源到新位置。重复执行COPY请求可能会创建多个副本,因此COPY请求不是幂等的。
  • MOVE: 移动资源到新位置。重复执行MOVE请求可能会产生不同的结果,因此MOVE请求不是幂等的。
  • LINK: 将资源与另一个资源关联。重复LINK请求可能会导致多个关联,因此LINK请求不是幂等的。
  • UNLINK: 取消资源与另一个资源的关联。重复UNLINK请求可能会产生不同的结果,因此UNLINK请求不是幂等的.
  • CONNECT: 建立与资源的双向连接。重复CONNECT请求可能会导致不同的连接,因此CONNECT请求不是幂等的。

安全性

定义: 安全性是指执行请求不会对服务器资源状态产生影响。安全的操作不会导致资源的状态发生改变。

  • GET: 获取资源的请求。此请求不会修改资源,因此GET请求是安全的。
  • HEAD: 获取资源的头部信息。此请求不会修改资源,因此HEAD请求是安全的.
  • OPTIONS: 获取资源支持的HTTP方法。此请求不会修改资源,因此OPTIONS请求是安全的.
  • TRACE: 追踪请求的传输路径。此请求不会修改资源,因此TRACE请求是安全的。

非安全操作:

  • POST: 向服务器提交数据进行处理。此请求可能会修改服务器上的资源,因此POST请求不是安全的。
  • PUT: 替换资源。此请求会改变资源的状态,因此PUT请求不是安全的。
  • DELETE: 删除资源。此请求会改变资源的状态,因此DELETE请求不是安全的.
  • PATCH: 局部修改资源。此请求会改变资源的状态,因此PATCH请求不是安全的。
  • COPY: 复制资源。此请求会改变资源的状态,因此COPY请求不是安全的.
  • MOVE: 移动资源。此请求会改变资源的状态,因此MOVE请求不是安全的.
  • LINK: 关联资源。此请求会改变资源的状态,因此LINK请求不是安全的.
  • UNLINK: 取消资源的关联。此请求会改变资源的状态,因此UNLINK请求不是安全的.
  • CONNECT: 建立双向连接。此请求可能会改变服务器的状态,因此CONNECT请求不是安全的.

6. HTTP的状态码

HTTP 状态码简介

HTTP状态码是服务器对客户端请求的响应结果的标识,采用三位数字代码的形式。每个状态码表示服务器对请求处理的结果,并帮助客户端了解请求的处理状态。以下是一些常见的HTTP状态码及其含义:

成功状态码

  • 200 OK
    说明: 请求成功,服务器已成功处理了请求。
    示例: 访问一个网页或获取数据成功。

  • 201 Created
    说明: 请求已成功处理,并在服务器上创建了一个新的资源。
    示例: 用户注册成功,新账号被创建。

  • 204 No Content
    说明: 服务器成功处理了请求,但没有返回任何内容。
    示例: 删除操作成功,但没有需要返回的数据。

客户端错误状态码

  • 400 Bad Request
    说明: 客户端发出的请求有语法错误或无法理解。
    示例: 请求中缺少必需的参数或包含无效的数据。

  • 401 Unauthorized
    说明: 请求需要用户身份验证。
    示例: 用户尝试访问受保护的资源,但未提供有效的凭证。

  • 403 Forbidden
    说明: 服务器理解请求,但拒绝执行。通常是因为请求的资源对用户不可用。
    示例: 用户尝试访问不允许的页面或资源。

  • 404 Not Found
    说明: 服务器无法找到请求的资源。
    示例: 用户访问的网页或文件不存在。

服务器错误状态码

  • 500 Internal Server Error
    说明: 服务器遇到了意外情况,无法完成请求。
    示例: 服务器内部错误导致请求失败。

  • 502 Bad Gateway
    说明: 作为网关或代理服务器的服务器从上游服务器收到无效的响应。
    示例: 网关服务器无法从上游服务器获取有效的响应。

  • 503 Service Unavailable
    说明: 服务器当前无法处理请求,通常用于服务器维护或过载时。
    示例: 服务器正在进行维护,暂时无法提供服务。


7. 编写HTTP服务器获取http请求与响应信息

在了解了http的相关内容后,下面我们编写一个http服务,用来获取http相应与请求信息。

包含以下部分:

  • Socket.hpp:套接字的管理,tcpSocket;
  • TcpServer.hpp:封装了Tcp服务器类
  • Session.hpp:用户会话,以及sessionManager类,管理用户信息
  • HttpProtocol.hpp:http协议的相关操作,如解析
  • wwwroot文件夹:存放html文件、图片文件等
  • main.cc:主文件,用于启动服务器,最后生成可执行文件
  • Logger.hpp: 日志输出类,用于打印错误日志等

7.0 Logger.hpp

#ifndef __M_LOG_H__
#define __M_LOG_H__#include <iostream>
#include <cstdio>
#include <ctime>#define DBG_LEVEL 0
#define INF_LEVEL 1
#define ERR_LEVEL 2
#define DEFAULT_LEVEL DBG_LEVEL#define LOG(level_str, level, format, ...) \if (level >= DEFAULT_LEVEL) { \time_t t = time(nullptr); \struct tm* ptm = localtime(&t); \char time_str[32]; \strftime(time_str, /*sizeof(time_str)*/31, "%H:%M:%S", ptm); \printf("[%s][%s][%s:%d]\t" format "\n", level_str, time_str, __FILE__, __LINE__, ##__VA_ARGS__); \} \

#define DLOG(format, ...) LOG("DBG", DBG_LEVEL, format, ##__VA_ARGS__)
#define ILOG(format, ...) LOG("INF", INF_LEVEL, format, ##__VA_ARGS__)
#define ELOG(format, ...) LOG("ERR", ERR_LEVEL, format, ##__VA_ARGS__)#endif

7.1 Socket.hpp

#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "Logger.hpp"#define Convert(addrptr) ((struct sockaddr *)addrptr)namespace network
{const static int defaultsockfd = -1; // 默认socket文件描述符const int backlog = 5; // 最大连接请求数enum // 错误类型{SocketError = 1,BindError,ListenError,SendError,};// 封装Socket接口类(设计模式:模板方法类)class Socket{public:virtual ~Socket() {}virtual void CreateSocketOrDie() = 0;virtual void BindSocketOrDie(uint16_t port) = 0;virtual void ListenSocketOrDie(int backlog) = 0;virtual Socket *AcceptConnection(std::string *peerip, uint16_t *peerport) = 0;virtual bool ConnectServer(std::string &serverip, uint16_t serverport) = 0;virtual int GetSockFd() = 0;virtual void SetSockFd(int sockfd) = 0;virtual void CloseSocket() = 0;virtual bool Recv(std::string *buffer, int size) = 0;virtual void Send(std::string &send_str) = 0;virtual void ReUseAddr() = 0;public:// 建立监听套接字void BuildListenSocketMethod(uint16_t port, int backlog){CreateSocketOrDie();ReUseAddr();BindSocketOrDie(port);ListenSocketOrDie(backlog);}// 连接服务器bool BuildConnectSocketMethod(std::string &serverip, uint16_t serverport){CreateSocketOrDie();return ConnectServer(serverip, serverport);}// 建立普通套接字void BuildNormalSocketMethod(int sockfd){SetSockFd(sockfd);}};class TcpSocket : public Socket{public:TcpSocket(int sockfd = defaultsockfd) : _sockfd(sockfd) {}~TcpSocket() { // CloseSocket(); }void CreateSocketOrDie() override{_sockfd = socket(AF_INET, SOCK_STREAM, 0);if(_sockfd < 0){std::cerr << "socket create error" << std::endl;exit(SocketError);}}void BindSocketOrDie(uint16_t port) override{struct sockaddr_in addr;memset(&addr, 0, sizeof(addr));addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = INADDR_ANY;if(::bind(_sockfd, Convert(&addr), sizeof(addr)) < 0) {                ELOG("bind error: %s", strerror(errno));exit(SocketError);}}void ListenSocketOrDie(int backlog) override{if(listen(_sockfd, backlog) < 0) {std::cerr << "listen error" << std::endl;exit(SocketError);}}Socket *AcceptConnection(std::string *peerip, uint16_t *peerport) override{struct sockaddr_in peer;socklen_t len = sizeof(peer);int newfd = accept(_sockfd, Convert(&peer), &len);if(newfd < 0) {std::cerr << "accept error" << std::endl;exit(SocketError);}*peerport = ntohs(peer.sin_port);*peerip = inet_ntoa(peer.sin_addr);return new TcpSocket(newfd); // -> Socket *socket = new TcpSocket(newfd);}bool ConnectServer(std::string &serverip, uint16_t serverport) override{struct sockaddr_in server;server.sin_family = AF_INET;server.sin_port = htons(serverport);server.sin_addr.s_addr = inet_addr(serverip.c_str());if(connect(_sockfd, Convert(&server), sizeof(server) < 0)) {std::cerr << "connect error" << std::endl;return false;}return true;}int GetSockFd() override{return _sockfd;}void SetSockFd(int sockfd) override{_sockfd = sockfd;}void CloseSocket() override{if(_sockfd > defaultsockfd) {close(_sockfd);}}bool Recv(std::string *buffer, int size) override{char recvBuffer[size];int recvLen = recv(_sockfd, recvBuffer, size, 0);if(recvLen < 0) {std::cerr << "recv error" << std::endl;return false;} else if(recvLen == 0) {return false;}recvBuffer[recvLen] = '\0';*buffer += recvBuffer;return true;}void Send(std::string &send_str) override{if(send(_sockfd, send_str.c_str(), send_str.size(), 0)){std::cerr << "send error" << std::endl;exit(SendError);}}void ReUseAddr() override{int opt = 1;setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));}private:int _sockfd;};
}

在上面整个文件中,我们将socket有关的类与参数都实现在一个命名空间network中,Socket类作为TcpSocket的父类生命了相关的套接字操作函数,后者将这些函数重写实现。


7.2 TcpServer.hpp

TcpServer类基于实现的TcpSocket套接字管理类,通过

#pragma once#include "Socket.hpp"
#include <iostream>
#include <pthread.h>
#include <functional>// http协议底层为tcp协议
using func_t = std::function<std::string(std::string &request)>;class TcpServer;class ThreadData
{
public:ThreadData(TcpServer *tcp_this, network::Socket *sockp) : _this(tcp_this), _sockp(sockp){}public:TcpServer *_this;network::Socket *_sockp;
};class TcpServer
{
public:TcpServer(uint16_t port, func_t request_handler) : _port(port), _listen_socket(new network::TcpSocket()), _request_handler(request_handler){_listen_socket->BuildListenSocketMethod(_port, network::backlog);}~TcpServer(){delete _listen_socket;}static void* ThreadRun(void* args){pthread_detach(pthread_self()); // 分离线程: 线程结束时自动回收资源ThreadData* td = static_cast<ThreadData*>(args);std::string http_request; // http请求// 接收请求if(td->_sockp->Recv(&http_request, 4096)) {// std::string http_response = td->_this->_request_handler(http_request);if(!http_request.empty()) {td->_sockp->Send(http_response); // 发送响应}}td->_sockp->CloseSocket();delete td->_sockp;delete td;return nullptr;}// 循环监听void Loop(){while(true) {std::string peerip;uint16_t peerport;network::Socket* newSock = _listen_socket->AcceptConnection(&peerip, &peerport);if(!newSock) {// std::cerr << "accept error" << std::endl;continue;}std::cout << "new connection from " << peerip << ":" << peerport << std::endl;ThreadData* td = new ThreadData(this, newSock);pthread_t tid;pthread_create(&tid, nullptr, ThreadRun, td);}}private:int _port;network::Socket *_listen_socket;
public:func_t _request_handler;
};

7.3 Session.hpp

#pragma once#include <iostream>
#include <memory>
#include <unordered_map>
#include "Logger.hpp"class Session
{
public:using ptr = std::shared_ptr<Session>;Session(){}Session(std::string username, std::string password, std::string status, uint64_t login_time){}~Session(){}private:std::string username;std::string password;std::string status;uint64_t login_time; // 时刻// 等等阶段信息、参数
};class SessionManager
{
public:SessionManager(){}~SessionManager(){}bool FindSession(std::string& session_id){if(sessions.find(session_id) == sessions.end()) {DLOG("session_id not found\n");return false;}return true;}std::string AddSession(std::string username, std::string password, std::string status, uint64_t login_time){std::string session_id = username + std::to_string(login_time);sessions[session_id] = std::make_shared<Session>(username, password, status, login_time);return session_id;}void DelSession(std::string& session_id){if(sessions.find(session_id) == sessions.end()) {DLOG("session_id not found\n");}sessions.erase(session_id);// return true;}void ModifySession(std::string& session_id, Session::ptr session){if(sessions.find(session_id) == sessions.end()) {DLOG("session_id not found\n");return ;}sessions[session_id] = session;}private:std::unordered_map<std::string, Session::ptr> sessions;
};

7.4 HttpProtocol.hpp

HttpProtocol.hpp包含对http协议的相关操作,如序列化反序列化,解码格式等

#pragma once#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <fstream>
#include "Session.hpp"const std::string HTTPSEP = "\r\n";
const std::string HOMEPAGE = "index.html";
const std::string WWWROOT = "./wwwRoot";class HttpRequest
{
public:HttpRequest() : _req_blank(HTTPSEP), _path(WWWROOT){}~HttpRequest(){}// line: 输出型参数bool GetLine(std::string& str, std::string* line){auto pos = str.find(HTTPSEP);if (pos == std::string::npos) return false;*line = str.substr(0, pos);str.erase(0, pos + HTTPSEP.size());return true;}// 反序列化bool Deserialize(std::string& request) {std::string line;if(!GetLine(request, &line))return false;_req_line = line;while(true){bool flag = GetLine(request, &line);if(flag && line.empty()) {_req_content = request;break;} else if (flag && !line.empty()) {_req_header.push_back(line);} else {break;}}return true;}// 解析请求行void ParseReqLine(){std::stringstream ss(_req_line);ss >> _method >> _url >> _http_version;if(_url == "/") {_path += _url;_path += HOMEPAGE;} else {_path += _url;}}void ParseSuffix() {auto pos = _path.rfind(".");if(pos == std::string::npos)_suffix = ".html";else_suffix = _path.substr(pos);    }void Parse(){// 解析请求行ParseReqLine();// 解析后缀 wwwroot/A/B/C.html -> .htmlParseSuffix();}// 输出http请求信息void DebugHttp(){std::cout << "_req_line: " << _req_line << std::endl;for (auto &line : _req_header){std::cout << "---> " << line << std::endl;}std::cout << "_req_blank: " << _req_blank << std::endl;std::cout << "_req_content: " << _req_content << std::endl;std::cout << "Method: " << _method << std::endl;std::cout << "url: " << _url << std::endl;std::cout << "http_version: " << _http_version << std::endl;}// 获取文件内容std::string GetFileContent(const std::string& path) {std::ifstream in(path, std::ios::binary);if (!in.is_open())return "";in.seekg(0, in.end); // 设置文件流的读取位置int fSize = in.tellg(); // 获取文件大小in.seekg(0, in.beg);std::string content; // 将文件内容读取到contentcontent.resize(fSize);in.read(&content[0], fSize);in.close();return content;}std::string GetFileContent(){return GetFileContent(_path);}std::string Get_ERROR_404(){return GetFileContent("./wwwRoot/404.html");}std::string Url(){return _url;}std::string Path(){return _path;}std::string Suffix(){return _suffix;}private:// 报文请求内容std::string _req_line;std::vector<std::string> _req_header;std::string _req_blank;std::string _req_content;// 解析后std::string _method; // GET POSTstd::string _url;      // 请求行中的urlstd::string _http_version; // HTTP/1.1std::string _path; // "./wwwRoot"std::string _suffix; // 请求资源的后缀
};const std::string BlankSep = " ";
const std::string LineSep = "\r\n";class HttpResponse
{
public:HttpResponse() : _http_version("HTTP/1.0"), _status_code(200), _status_code_desc("OK"), _resp_blank(LineSep){}~HttpResponse(){}void SetCode(int code) {_status_code = code;}void SetCodeDesc(const std::string& desc){_status_code_desc = desc;}void SetHttpVersion(const std::string& version){_http_version = version;}void SetContent(const std::string& content){_resp_content = content;}void AddHeader(const std::string& header){_resp_header.push_back(header);}void MakeStatusLine(){// HTTP/1.1 200 OK\r\n_status_line = _http_version + BlankSep + std::to_string(_status_code) + BlankSep + _status_code_desc + LineSep;}std::string Serialize(){std::string resp_str = _status_line;for (auto &header : _resp_header){// resp_str += header + LineSep;resp_str += header;}resp_str += _resp_blank + _resp_content;return resp_str;}private:std::string _status_line;std::vector<std::string> _resp_header;std::string _resp_blank;std::string _resp_content;std::string _http_version;int _status_code;std::string _status_code_desc;
};

7.5 main.cc

#include <iostream>
#include <memory>
#include <string>
#include <fstream>
#include <unistd.h>
#include "HttpProtocol.hpp"
#include "Session.hpp"
#include "TcpServer.hpp"std::string CodeToDesc(int code)
{switch (code){case 200:return "OK";case 301:return "Moved Permanently";case 302:return "Found";case 307:return "Temporary Redirect";case 400:return "Bad Request";case 404:return "Not Found";case 500:return "Internal Server Error";default:return "Unknown";}
}std::string SuffixToType(std::string suffix)
{if(suffix == ".html") {return "text/html";} else if(suffix == ".png") {return "img/png";} else if(suffix == ".jpg") {return "img/jpg";} else {return "text/html";}
}SessionManager online_user;std::string HttpRequestHandler(std::string& request) {HttpRequest req;// 反序列化请求req.Deserialize(request);req.Parse();req.DebugHttp(); // 打印请求信息int status_code = 200;std::string content = req.GetFileContent();if(content.empty()) {status_code = 404;content = req.Get_ERROR_404(); // 返回404页面}HttpResponse resp;std::string user = "李天所";std::string password = "114514";std::string status = "logined"; // 示例状态,代表用户状态的值uint64_t login_time = static_cast<uint64_t>(std::time(nullptr)); // 当前时间的时间戳,确保使用 uint64_t 类型if (req.Url() == "/register") {std::string sessionid = online_user.AddSession(user, password, status, login_time);// 处理 sessionid,可能需要将其包含在响应中std::string session = "Set-Cookie: sessionid=" + sessionid + "\r\n";resp.AddHeader(session); // 添加 session 到响应头}if(req.Url() == "/login") {std::string sessionid;if(online_user.FindSession(sessionid)) {// 处理 sessionid,可能需要将其包含在响应中std::string session = "Set-Cookie: sessionid=" + sessionid + "\r\n";resp.AddHeader(session); // 添加 session 到响应头}}// 测试resp.SetCode(status_code);resp.SetCodeDesc(CodeToDesc(status_code));resp.MakeStatusLine();std::string content_len_str = "Content-Length: " + std::to_string(content.size()) + "\r\n";resp.AddHeader(content_len_str);std::string content_type_str = "Content-Type: " + SuffixToType(req.Suffix()) + "\r\n"; // 正文的类型resp.AddHeader(content_type_str);std::string namecookie = "Set-Cookie: username=李天所\r\n";resp.AddHeader(namecookie);std::string passwdcookie = "Set-Cookie: password=114514\r\n";resp.AddHeader(passwdcookie);std::string statuscookie = "Set-Cookie: status=logined\r\n";resp.AddHeader(statuscookie);resp.SetContent(content);return resp.Serialize();
}int main(int argc, char* argv[])
{if(argc != 2) {std::cerr << "Usage: " << argv[0] << " <port>" << std::endl;return -1;}uint16_t port = std::stoi(argv[1]);std::unique_ptr<TcpServer> svr(new TcpServer(port, HttpRequestHandler));svr->Loop();return 0;
}

结果演示:
在这里插入图片描述

7.6 代码链接:

这篇关于【网络基础】HTTP协议的基本知识与服务器实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1091899

相关文章

服务器集群同步时间手记

1.时间服务器配置(必须root用户) (1)检查ntp是否安装 [root@node1 桌面]# rpm -qa|grep ntpntp-4.2.6p5-10.el6.centos.x86_64fontpackages-filesystem-1.41-1.1.el6.noarchntpdate-4.2.6p5-10.el6.centos.x86_64 (2)修改ntp配置文件 [r

关于数据埋点,你需要了解这些基本知识

产品汪每天都在和数据打交道,你知道数据来自哪里吗? 移动app端内的用户行为数据大多来自埋点,了解一些埋点知识,能和数据分析师、技术侃大山,参与到前期的数据采集,更重要是让最终的埋点数据能为我所用,否则可怜巴巴等上几个月是常有的事。   埋点类型 根据埋点方式,可以区分为: 手动埋点半自动埋点全自动埋点 秉承“任何事物都有两面性”的道理:自动程度高的,能解决通用统计,便于统一化管理,但个性化定

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]