文章目录
- [1. 认识URL](#1. 认识URL)
- [2. urlencode和urldecode](#2. urlencode和urldecode)
- [3. http 协议格式](#3. http 协议格式)
-
- [**3.1 HTTP请求格式**](#3.1 HTTP请求格式)
- [**3.2 HTTP响应格式**](#3.2 HTTP响应格式)
- [4. HTTP 常见头部-Header](#4. HTTP 常见头部-Header)
- [5. HTTP的方法](#5. HTTP的方法)
- [6. HTTP的状态码](#6. HTTP的状态码)
- [7. 编写HTTP服务器获取http请求与响应信息](#7. 编写HTTP服务器获取http请求与响应信息)
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中具有特别的意义。如果数据或参数中包含这些特殊字符,就需要进行转义处理。转义规则如下:
转义规则
-
空格 :空格通常被转义为
%20
。 -
保留字符:以下字符是URL中的保留字符,在某些上下文中具有特殊含义,因此需要进行转义。
- 冒号(:) 转义为
%3A
- 斜杠(/) 转义为
%2F
- 问号(?) 转义为
%3F
- 井号(#) 转义为
%23
- 方括号([ 和 ]) 转义为
%5B
和%5D
- 百分号(%) 转义为
%25
- 冒号(:) 转义为
-
非ASCII字符 :非ASCII字符需要进行URL编码。它们将以
%xx
的形式表示,其中xx
是字符的ASCII码值的十六进制表示。 -
其他特殊字符:除了上述情况外,其他特殊字符也可能需要转义,例如各种标点符号。
总结
转义的过程就是 urlencode
到 urldecode
的过程。urldecode
是 urlencode
的逆过程。
3. http 协议格式
**HTTP(Hypertext Transfer Protocol)**是一种用于传输超文本的应用层协议,用于在客户端和服务器之间传输信息。下面是一个简单的HTTP请求和响应的格式:
3.1 HTTP请求格式
一个简单的HTTP请求由以下几个部分组成:
-
请求行:包含请求方法、请求的URL以及协议版本。
-
请求头:包含了关于请求资源、客户端和更多信息的头部信息。
-
空行:用于分隔请求头和请求体。
-
请求体:包含实际发送给服务器的数据。
示例:
cpp
GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
Accept-Language: en-US
Hello, server!
3.2 HTTP响应格式
一个简单的HTTP响应由以下几个部分组成:
-
状态行:包含协议版本、状态码以及状态信息。
-
响应头:包含了响应资源、服务器和更多信息的头部信息。
-
空行:用于分隔响应头和响应体。
-
响应体:包含实际返回给客户端的数据。
示例:
c
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
cpp
#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
cpp
#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套接字管理类,通过
cpp
#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
cpp
#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协议的相关操作,如序列化反序列化,解码格式等
cpp
#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 -> .html
ParseSuffix();
}
// 输出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; // 将文件内容读取到content
content.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 POST
std::string _url; // 请求行中的url
std::string _http_version; // HTTP/1.1
std::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
cpp
#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;
}
结果演示: