文章目录
一、 前言
经过前面的配置,现在我们再加入HTTP
报文的解析与响应就可以当作简单的服务器了!
本节代码均可在仓库TinyWebServer 中找到
二、前置知识
这里尽量让零基础小白也能听懂。
1 HTTP
协议到底是什么?
简单来说就是协议
。可能还是有点抽象,举个例子,买东西的过程,选择商品 -> 付钱 -> 卖家商品交付。
这个过程,就是协议,你给钱,我给商品得到过程,协议规定给钱而不是石头,这就是协议
2 HTTP
规定的协议是什么呢?
分为请求报文和响应报文。
2.1 请求报文由四个部分组成:
- 请求行: 包括请求方法、请求 URL、HTTP 协议和版本
- 请求头: 一些键值对(表示客户端的一些信息)
- 空行: 请求头之后是一个空行,通知服务器以下不再有请求头、空行后面的内容是请求体
- 请求体: 用于发送POST、PUT等请求方法时发送的数据。
2.2 响应报文由四个部分组成:
- 状态行: HTTP协议和版本、状态码、状态描述
- 响应头
- 空行
- 响应体
值得一提的是,我们做的是
HTTP
后端服务,前端发送请求报文是由浏览器发送的,我们并不需要管,我们只需要能够解析发送过来的报文,以及对这个报文进行响应 。
3 HTTP
版本
主流版本1.1
、2.0
、3.0
每一次的版本更新迭代都是解决现有问题(为了更快,更强!),HTTP
也不例外,虽然笔者很想一步就用最好的3.0
但对于初学者困难太多,太不友好,所以这里先用1.1
协议(现在也仍然是主流,并未被淘汰)。
4 解析HTTP
报文的方式
常见的方式有很多中如正则表达式方法、字符串分割方法、第三方库方法等等。这些要么低效要么就是增加项目依赖,不够轻量。那么我来介绍一种用的最多个效率最好是方式状态机方法
4.1 状态机方法优缺点:
- 特点:使用有限状态机(FSM)来解析 HTTP 请求。
- 优点:高效、内存使用少、适合流式处理。
- 缺点:实现可能较为复杂。
4.2 状态机的实现方式
- 状态定义: 我们定义了一系列状态,如 METHOD, URL, VERSION, HEADER_NAME, HEADER_VALUE, BODY 等。
- 字符处理: 解析器逐字符处理输入,根据当前状态和输入字符决定下一步操作。
- 状态转换: 根据解析的内容,状态机在不同状态之间转换。例如,从 METHOD 状态遇到空格后转到 URL 状态。
- 增量解析: 我们的解析器支持增量解析,这意味着它可以处理部分数据,并在接收到更多数据时继续解析。
- 错误处理: 状态机设计允许我们在解析过程中随时检测和处理错误。
现在就实现了子类
http1.1
的状态机,后续会补上,所以这里定义状态机父类。
5 HTTP
报文响应
这里笔者用简单工厂方法返回不同状态码
方法。
5.1 什么是状态码
?
简单来说就是,解析请求报文后服务器返回给客户端的标识,比如成功了!或者没有这个资源,在或者服务器崩溃啦!
分5大类
- 1xx:指示信息,表示请求已接收,继续处理
- 2xx:成功,表示请求已被成功接受,处理。
- 200 OK:客户端请求成功
- 204 No Content:无内容。服务器成功处理,但未返回内容。一般用在只是客户端向服务器发送信息,而服务器不用向客户端返回什么信息的情况。不会刷新页面。
- 206 Partial Content:服务器已经完成了部分GET请求(客户端进行了范围请求)。响应报文中包含Content-Range指定范围的实体内容
- 3xx:重定向
- 301 Moved Permanently:永久重定向,表示请求的资源已经永久的搬到了其他位置。
- 302 Found:临时重定向,表示请求的资源临时搬到了其他位置
- 303 See Other:临时重定向,应使用GET定向获取请求资源。303功能与302一样,区别只是303明确客户端应该使用GET访问
- 307 Temporary Redirect:临时重定向,和302有着相同含义。POST不会变成GET
- 304 Not Modified:表示客户端发送附带条件的请求(GET方法请求报文中的IF…)时,条件不满足。返回304时,不包含任何响应主体。
- 4xx:客户端错误
- 400 Bad Request:客户端请求有语法错误,服务器无法理解。
- 401 Unauthorized:请求未经授权,这个状态代码必须和WWW- Authenticate报头域一起使用。
- 403 Forbidden:服务器收到请求,但是拒绝提供服务
- 404 Not Found:请求资源不存在。比如,输入了错误的url
- 415 Unsupported media type:不支持的媒体类型
- 5xx:服务器端错误,服务器未能实现合法的请求。
- 500 Internal Server Error:服务器发生不可预期的错误。
- 503 Server Unavailable:服务器当前不能处理客户端的请求,一段时间后可能恢复正常
状态码很多我也只列出来了一点,服务器上也是只实现了大类,后面用到了,再加吧。
6 client_context
的变化
很好理解因为他是存储每个客户端的消息的,当然在这里可以解析客户端的消息。
7 server
的变化
添加一些方法来处理不同的请求,生成相对应的响应报文,以及设置资源目录的路径。
三、代码实现
// http_parser.h
#pragma once
#include <functional>
#include <optional>
#include <sstream>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
enum class HttpMethod { GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH, UNKNOWN };
enum class HttpVersion { HTTP_1_0, HTTP_1_1, HTTP_2_0, UNKNOWN };
struct HttpRequest {
HttpMethod method = HttpMethod::UNKNOWN;
std::string url;
HttpVersion version = HttpVersion::UNKNOWN;
std::unordered_map<std::string, std::string> headers;
std::vector<char> body;
HttpRequest();
};
class IHttpParser {
public:
using ErrorCallback = std::function<void(const std::string &)>;
virtual ~IHttpParser() = default;
virtual void reset() = 0;
virtual std::optional<HttpRequest> parse(std::string_view data) = 0;
virtual void setErrorCallback(ErrorCallback cb) = 0;
};
class HttpParser : public IHttpParser {
public:
HttpParser();
void reset() override;
std::optional<HttpRequest> parse(std::string_view data) override;
void setErrorCallback(ErrorCallback cb) override;
static std::unordered_map<std::string, std::string> parseQueryParams(const std::string &url);
private:
enum class ParserState { METHOD, URL, VERSION, HEADER_NAME, HEADER_VALUE, BODY, COMPLETE, ERROR };
ParserState state;
HttpRequest request;
std::string currentHeaderName;
std::string currentHeaderValue;
size_t contentLength;
ErrorCallback errorCallback;
bool parseChar(char c);
bool parseMethod(char c);
bool parseUrl(char c);
bool parseVersion(char c);
bool parseHeaderName(char c);
bool parseHeaderValue(char c);
bool parseBody(char c);
void processHeader();
static HttpMethod stringToMethod(std::string_view method);
static std::string trim(const std::string &s);
};
std::string_view toString(HttpMethod method);
std::string_view toString(HttpVersion version);
// http_parser.cpp
#include "http_parser.h"
#include <algorithm>
#include <array>
#include <cctype>
#include <sstream>
std::string_view toString(HttpMethod method) {
static constexpr std::array<std::string_view, 10> methodStrings = {
"GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS", "TRACE", "CONNECT", "PATCH", "UNKNOWN"
};
return methodStrings[static_cast<int>(method)];
}
std::string_view toString(HttpVersion version) {
static constexpr std::array<std::string_view, 4> versionStrings = {
"HTTP/1.0", "HTTP/1.1", "HTTP/2.0", "UNKNOWN"
};
return versionStrings[static_cast<int>(version)];
}
HttpRequest::HttpRequest() : method(HttpMethod::UNKNOWN), version(HttpVersion::UNKNOWN) {}
HttpParser::HttpParser() {
reset();
}
void HttpParser::reset() {
state = ParserState::METHOD;
request = HttpRequest{};
currentHeaderName.clear();
currentHeaderValue.clear();
contentLength = 0;
}
std::optional<HttpRequest> HttpParser::parse(std::string_view data) {
for (char c : data) {
if (!parseChar(c)) {
if (errorCallback) {
errorCallback("Parsing error");
}
return std::nullopt;
}
if (state == ParserState::COMPLETE) {
auto result = std::move(request);
reset();
return result;
}
}
return std::nullopt;
}
void HttpParser::setErrorCallback(ErrorCallback cb) {
errorCallback = std::move(cb);
}
bool HttpParser::parseChar(char c) {
switch (state) {
case ParserState::METHOD: return parseMethod(c);
case ParserState::URL: return parseUrl(c);
case ParserState::VERSION: return parseVersion(c);
case ParserState::HEADER_NAME: return parseHeaderName(c);
case ParserState::HEADER_VALUE: return parseHeaderValue(c);
case ParserState::BODY: return parseBody(c);
default: return false;
}
}
bool HttpParser::parseMethod(char c) {
if (c == ' ') {
request.method = stringToMethod(request.url);
request.url.clear();
state = ParserState::URL;
} else {
request.url += c;
}
return true;
}
bool HttpParser::parseUrl(char c) {
if (c == ' ') {
state = ParserState::VERSION;
} else {
request.url += c;
}
return true;
}
bool HttpParser::parseVersion(char c) {
static const std::string_view httpVersion = "HTTP/";
static size_t versionIndex = 0;
if (versionIndex < httpVersion.length()) {
if (c == httpVersion[versionIndex]) {
++versionIndex;
} else {
return false;
}
} else if (c == '1' || c == '2') {
request.version = (c == '1') ? HttpVersion::HTTP_1_1 : HttpVersion::HTTP_2_0;
versionIndex = 0;
state = ParserState::HEADER_NAME;
} else {
return false;
}
return true;
}
bool HttpParser::parseHeaderName(char c) {
if (c == ':') {
state = ParserState::HEADER_VALUE;
} else if (c == '\r') {
// Skip carriage return
} else if (c == '\n') {
if (!currentHeaderName.empty()) {
processHeader();
} else {
state = (contentLength > 0) ? ParserState::BODY : ParserState::COMPLETE;
}
} else {
currentHeaderName += std::tolower(c);
}
return true;
}
bool HttpParser::parseHeaderValue(char c) {
if (c == '\r') {
// Skip carriage return
} else if (c == '\n') {
processHeader();
state = ParserState::HEADER_NAME;
} else {
currentHeaderValue += c;
}
return true;
}
bool HttpParser::parseBody(char c) {
request.body.push_back(c);
if (request.body.size() == contentLength) {
state = ParserState::COMPLETE;
}
return true;
}
void HttpParser::processHeader() {
if (currentHeaderName == "content-length") {
contentLength = std::stoul(currentHeaderValue);
}
request.headers[currentHeaderName] = trim(currentHeaderValue);
currentHeaderName.clear();
currentHeaderValue.clear();
}
HttpMethod HttpParser::stringToMethod(std::string_view method) {
static const std::unordered_map<std::string_view, HttpMethod> methodMap = {
{"GET", HttpMethod::GET}, {"POST", HttpMethod::POST}, {"PUT", HttpMethod::PUT},
{"DELETE", HttpMethod::DELETE}, {"HEAD", HttpMethod::HEAD}, {"OPTIONS", HttpMethod::OPTIONS},
{"TRACE", HttpMethod::TRACE}, {"CONNECT", HttpMethod::CONNECT}, {"PATCH", HttpMethod::PATCH}
};
auto it = methodMap.find(method);
return (it != methodMap.end()) ? it->second : HttpMethod::UNKNOWN;
}
std::string HttpParser::trim(const std::string& s) {
auto start = std::find_if_not(s.begin(), s.end(), ::isspace);
auto end = std::find_if_not(s.rbegin(), s.rend(), ::isspace).base();
return (start < end) ? std::string(start, end) : std::string();
}
std::unordered_map<std::string, std::string> HttpParser::parseQueryParams(const std::string& url) {
std::unordered_map<std::string, std::string> params;
size_t pos = url.find('?');
if (pos != std::string::npos) {
std::string query = url.substr(pos + 1);
std::istringstream iss(query);
std::string pair;
while (std::getline(iss, pair, '&')) {
size_t eq_pos = pair.find('=');
if (eq_pos != std::string::npos) {
std::string key = pair.substr(0, eq_pos);
std::string value = pair.substr(eq_pos + 1);
params[key] = value;
}
}
}
return params;
}
// http_response.h
#pragma once
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
class HttpResponse {
public:
int status_code = 200;
std::string status_message = "OK";
std::unordered_map<std::string, std::string> headers;
std::vector<char> body;
HttpResponse() = default;
HttpResponse(int code, std::string_view message);
void setBody(std::string_view content);
void setBody(const std::vector<char>& content);
std::string toString() const;
};
class HttpResponseFactory {
public:
static HttpResponse createNotFoundResponse();
static HttpResponse createOkResponse(const std::vector<char>& body, std::string_view contentType);
static HttpResponse createRedirectResponse(std::string_view location);
static HttpResponse createServerErrorResponse();
};
// http_response.cpp
#include "http_response.h"
#include <sstream>
HttpResponse::HttpResponse(int code, std::string_view message)
: status_code(code), status_message(message) {}
void HttpResponse::setBody(std::string_view content) {
body.assign(content.begin(), content.end());
headers["Content-Length"] = std::to_string(body.size());
}
void HttpResponse::setBody(const std::vector<char>& content) {
body = content;
headers["Content-Length"] = std::to_string(body.size());
}
std::string HttpResponse::toString() const {
std::ostringstream response;
response << "HTTP/1.1 " << status_code << " " << status_message << "\r\n";
for (const auto& [key, value] : headers) {
response << key << ": " << value << "\r\n";
}
response << "\r\n";
return response.str();
}
HttpResponse HttpResponseFactory::createNotFoundResponse() {
HttpResponse response(404, "Not Found");
response.setBody("<html><body><h1>404 Not Found</h1></body></html>");
response.headers["Content-Type"] = "text/html";
return response;
}
HttpResponse HttpResponseFactory::createOkResponse(const std::vector<char>& body, std::string_view contentType) {
HttpResponse response(200, "OK");
response.setBody(body);
response.headers["Content-Type"] = std::string(contentType);
return response;
}
HttpResponse HttpResponseFactory::createRedirectResponse(std::string_view location) {
HttpResponse response(302, "Found");
response.headers["Location"] = std::string(location);
return response;
}
HttpResponse HttpResponseFactory::createServerErrorResponse() {
HttpResponse response(500, "Internal Server Error");
response.setBody("<html><body><h1>500 Internal Server Error</h1></body></html>");
response.headers["Content-Type"] = "text/html";
return response;
}
// server.h
#pragma once
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/epoll.h> // 添加这行
#include <sys/socket.h>
#include <unistd.h> // 添加这行
#include <cstring>
#include <functional>
#include <iostream>
#include <memory>
#include <mutex>
#include <unordered_map>
#include <vector>
#include "client_context.h"
#include "http_parser.h"
#include "thread_pool.h"
const int MAX_EVENTS = 10;
const int BUFFER_SIZE = 1024;
class IServer {
public:
virtual ~IServer() = default;
virtual void run() = 0;
};
class Server : public IServer {
public:
using RequestHandler = std::function<HttpResponse(const HttpRequest &)>;
Server(int port);
void run() override;
void registerHandler(HttpMethod method, const std::string &path, RequestHandler handler);
void setPublicDirectory(const std::string &path);
private:
int server_fd;
int epoll_fd;
ThreadPool pool;
std::unordered_map<int, std::shared_ptr<ClientContext>> clients;
std::mutex clients_mutex;
std::unordered_map<HttpMethod, std::unordered_map<std::string, RequestHandler>> handlers;
std::string publicDirectory;
std::unordered_map<std::string, std::string> mimeTypes;
void handleNewConnection();
void handleClientEvent(epoll_event &event);
void handleRead(int client_fd);
void handleWrite(int client_fd);
void removeClient(int client_fd);
void modifyEpollEvent(int fd, uint32_t events);
// 新增方法
HttpResponse generateResponse(const HttpRequest &request);
HttpResponse handleGetRequest(const HttpRequest &request);
HttpResponse handleHeadRequest(const HttpRequest &request);
HttpResponse handlePostRequest(const HttpRequest &request);
HttpResponse handlePutRequest(const HttpRequest &request);
HttpResponse handleDeleteRequest(const HttpRequest &request);
HttpResponse handleOptionsRequest(const HttpRequest &request);
HttpResponse createMethodNotAllowedResponse();
void addCommonHeaders(HttpResponse &response);
std::string getCurrentDate() const;
HttpResponse serveStaticFile(const HttpRequest &request);
std::string getMimeType(const std::string &filename);
};
// server.cpp
#include "server.h"
#include <cstring>
#include <iostream>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <fstream>
#include<filesystem>
Server::Server(int port) : pool(4) {
server_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
if (server_fd < 0) {
throw std::runtime_error("Socket creation failed");
}
sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(port);
if (bind(server_fd, (sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
close(server_fd);
throw std::runtime_error("Bind failed");
}
if (listen(server_fd, SOMAXCONN) < 0) {
close(server_fd);
throw std::runtime_error("Listen failed");
}
epoll_fd = epoll_create1(0);
if (epoll_fd < 0) {
close(server_fd);
throw std::runtime_error("epoll_create1 failed");
}
epoll_event event;
event.data.fd = server_fd;
event.events = EPOLLIN | EPOLLET;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) < 0) {
close(server_fd);
close(epoll_fd);
throw std::runtime_error("epoll_ctl failed");
}
setPublicDirectory("./public"); // 设置公共目录
// 初始化MIME类型映射
mimeTypes = {
{".html", "text/html"},
{".css", "text/css"},
{".js", "application/javascript"},
{".json", "application/json"},
{".png", "image/png"},
{".jpg", "image/jpeg"},
{".jpeg", "image/jpeg"},
{".gif", "image/gif"},
{".svg", "image/svg+xml"},
{".ico", "image/x-icon"},
{".webp", "image/webp"}
};
}
void Server::run() {
std::vector<epoll_event> events(MAX_EVENTS);
while (true) {
int event_count = epoll_wait(epoll_fd, events.data(), MAX_EVENTS, -1);
if (event_count < 0) {
std::cerr << "epoll_wait failed: " << strerror(errno) << std::endl;
break;
}
for (int i = 0; i < event_count; i++) {
if (events[i].data.fd == server_fd) {
handleNewConnection();
} else {
handleClientEvent(events[i]);
}
}
}
}
void Server::registerHandler(HttpMethod method, const std::string &path, RequestHandler handler) {
handlers[method][path] = std::move(handler);
}
void Server::handleNewConnection() {
while (true) {
sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_fd = accept4(server_fd, (sockaddr *)&client_addr, &client_len, SOCK_NONBLOCK);
if (client_fd < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
break;
} else {
std::cerr << "Accept failed: " << strerror(errno) << std::endl;
break;
}
}
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &(client_addr.sin_addr), client_ip, INET_ADDRSTRLEN);
std::cout << "New connection from " << client_ip << ":" << ntohs(client_addr.sin_port) << std::endl;
epoll_event event;
event.data.fd = client_fd;
event.events = EPOLLIN | EPOLLET;
int epoll_ctl_result = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event);
if (epoll_ctl_result < 0) {
std::cerr << "epoll_ctl failed for client socket: " << strerror(errno) << std::endl;
close(client_fd);
} else {
std::lock_guard<std::mutex> lock(clients_mutex);
clients[client_fd] = std::make_shared<ClientContext>();
std::cout << "Client " << client_fd << " added to epoll" << std::endl;
}
}
}
void Server::handleClientEvent(epoll_event &event) {
int client_fd = event.data.fd;
if (event.events & (EPOLLERR | EPOLLHUP)) {
if (event.events & EPOLLERR) {
std::cerr << "Error event for client " << client_fd << std::endl;
}
if (event.events & EPOLLHUP) {
std::cout << "Hangup event for client " << client_fd << std::endl;
}
removeClient(client_fd);
} else {
if (event.events & EPOLLIN) {
std::cout << "Read event for client " << client_fd << std::endl;
handleRead(client_fd);
}
if (event.events & EPOLLOUT) {
std::cout << "Write event for client " << client_fd << std::endl;
handleWrite(client_fd);
}
}
}
void Server::handleRead(int client_fd) {
pool.enqueue([this, client_fd] {
std::shared_ptr<ClientContext> client;
{
std::lock_guard<std::mutex> lock(clients_mutex);
auto it = clients.find(client_fd);
if (it == clients.end() || !it->second->isActive()) {
std::cout << "Client " << client_fd << " not found or not active, skipping read handling" << std::endl;
return;
}
client = it->second;
}
std::string buffer(BUFFER_SIZE, 0);
while (true) {
int read_len = read(client_fd, buffer.data(), buffer.size());
if (read_len < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK)
break;
else {
std::cerr << "Read failed on socket " << client_fd << std::endl;
removeClient(client_fd);
break;
}
} else if (read_len == 0) {
std::cout << "Client disconnected: " << client_fd << std::endl;
removeClient(client_fd);
break;
} else {
auto request = client->parser.parse(std::string_view(buffer.data(), read_len));
if (request) {
HttpResponse response = generateResponse(*request);
client->pushResponse(response);
client->setWriteReady(true);
modifyEpollEvent(client_fd, EPOLLIN | EPOLLOUT);
}
}
}
});
}
void Server::handleWrite(int client_fd) {
pool.enqueue([this, client_fd] {
std::shared_ptr<ClientContext> client;
{
std::lock_guard<std::mutex> lock(clients_mutex);
auto it = clients.find(client_fd);
if (it == clients.end() || !it->second->isActive()) {
return;
}
client = it->second;
}
if (!client->isWriteReady() || !client->hasResponses()) return;
HttpResponse response = client->popResponse();
std::string headers = response.toString();
// 发送头部
if (send(client_fd, headers.c_str(), headers.length(), 0) < 0) {
std::cerr << "Failed to send headers" << std::endl;
removeClient(client_fd);
return;
}
// 发送主体
size_t total_sent = 0;
while (total_sent < response.body.size()) {
ssize_t sent = send(client_fd, response.body.data() + total_sent, response.body.size() - total_sent, 0);
if (sent < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// 资源暂时不可用,稍后重试
continue;
} else {
std::cerr << "Send error" << std::endl;
removeClient(client_fd);
return;
}
}
total_sent += sent;
}
if (!client->hasResponses()) {
client->setWriteReady(false);
modifyEpollEvent(client_fd, EPOLLIN);
}
});
}
void Server::removeClient(int client_fd) {
std::shared_ptr<ClientContext> client;
{
std::lock_guard<std::mutex> lock(clients_mutex);
auto it = clients.find(client_fd);
if (it != clients.end()) {
client = it->second;
clients.erase(it);
}
}
if (client) {
client->deactivate();
int epoll_ctl_result = epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_fd, nullptr);
if (epoll_ctl_result < 0) {
std::cerr << "Failed to remove client from epoll: " << strerror(errno) << std::endl;
}
close(client_fd);
}
}
void Server::modifyEpollEvent(int fd, uint32_t events) {
epoll_event event;
event.data.fd = fd;
event.events = events | EPOLLET;
if (epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &event) < 0) {
std::cerr << "Failed to modify epoll event for fd " << fd << std::endl;
}
}
HttpResponse Server::generateResponse(const HttpRequest &request) {
switch (request.method) {
case HttpMethod::GET:
return handleGetRequest(request);
case HttpMethod::HEAD:
return handleHeadRequest(request);
case HttpMethod::POST:
return handlePostRequest(request);
case HttpMethod::PUT:
return handlePutRequest(request);
case HttpMethod::DELETE:
return handleDeleteRequest(request);
case HttpMethod::OPTIONS:
return handleOptionsRequest(request);
// ... 其他方法 ...
default:
return createMethodNotAllowedResponse();
}
}
void Server::setPublicDirectory(const std::string &path) {
publicDirectory = path;
}
HttpResponse Server::serveStaticFile(const HttpRequest& request) {
std::filesystem::path filePath = publicDirectory + request.url;
if (std::filesystem::is_directory(filePath)) {
filePath /= "index.html";
}
if (std::filesystem::exists(filePath) && std::filesystem::is_regular_file(filePath)) {
std::ifstream file(filePath, std::ios::binary);
if (file) {
HttpResponse response;
response.status_code = 200;
response.status_message = "OK";
// 读取文件内容
file.seekg(0, std::ios::end);
size_t size = file.tellg();
file.seekg(0, std::ios::beg);
response.body.resize(size);
file.read(response.body.data(), size);
response.headers["Content-Type"] = getMimeType(filePath.string());
response.headers["Content-Length"] = std::to_string(response.body.size());
return response;
}
}
// 文件不存在,返回 404
HttpResponse response;
response.status_code = 404;
response.status_message = "Not Found";
response.setBody("<html><body><h1>404 Not Found</h1></body></html>");
response.headers["Content-Type"] = "text/html";
response.headers["Content-Length"] = std::to_string(response.body.size());
return response;
}
std::string Server::getMimeType(const std::string& filename) {
size_t dotPos = filename.find_last_of('.');
if (dotPos != std::string::npos) {
std::string ext = filename.substr(dotPos);
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
auto it = mimeTypes.find(ext);
if (it != mimeTypes.end()) {
return it->second;
}
}
return "application/octet-stream"; // 默认二进制流
}
HttpResponse Server::handleGetRequest(const HttpRequest &request) {
// 现有的静态文件服务逻辑
return serveStaticFile(request);
}
HttpResponse Server::handleHeadRequest(const HttpRequest &request) {
HttpResponse response = handleGetRequest(request);
response.body.clear(); // HEAD 请求不返回响应体
return response;
}
HttpResponse Server::handlePostRequest(const HttpRequest &request) {
// 处理 POST 请求的逻辑
// 例如,可以解析表单数据或 JSON 数据
// ...
}
HttpResponse Server::handlePutRequest(const HttpRequest &request) {
// 处理 PUT 请求的逻辑
// 例如,更新资源
// ...
}
HttpResponse Server::handleDeleteRequest(const HttpRequest &request) {
// 处理 DELETE 请求的逻辑
// 例如,删除资源
// ...
}
HttpResponse Server::handleOptionsRequest(const HttpRequest &request) {
HttpResponse response;
response.status_code = 200;
response.status_message = "OK";
response.headers["Allow"] = "GET, HEAD, POST, PUT, DELETE, OPTIONS";
return response;
}
HttpResponse Server::createMethodNotAllowedResponse() {
HttpResponse response;
response.status_code = 405;
response.status_message = "Method Not Allowed";
response.headers["Allow"] = "GET, HEAD, POST, PUT, DELETE, OPTIONS";
return response;
}
void Server::addCommonHeaders(HttpResponse &response) {
response.headers["Server"] = "YourServerName/1.0";
response.headers["Date"] = getCurrentDate(); // 实现 getCurrentDate() 方法返回当前时间
response.headers["Connection"] = "close"; // 或者 "keep-alive",取决于您的实现
}
std::string Server::getCurrentDate() const {
auto now = std::chrono::system_clock::now();
auto in_time_t = std::chrono::system_clock::to_time_t(now);
std::stringstream ss;
ss << std::put_time(std::gmtime(&in_time_t), "%a, %d %b %Y %H:%M:%S GMT");
return ss.str();
}
#pragma once
#include <atomic>
#include <mutex>
#include <queue>
#include <string>
#include "http_parser.h"
#include "http_response.h" // 添加这行
class ClientContext {
public:
ClientContext();
void pushResponse(const HttpResponse &response);
bool hasResponses() const;
HttpResponse popResponse();
void setWriteReady(bool ready);
bool isWriteReady() const;
bool isActive() const;
void deactivate();
HttpParser parser;
private:
std::queue<HttpResponse> response_queue;
bool write_ready;
mutable std::mutex mtx;
std::atomic<bool> active;
};
#include "client_context.h"
ClientContext::ClientContext() : write_ready(false), active(true) {}
void ClientContext::pushResponse(const HttpResponse &response) {
std::lock_guard<std::mutex> lock(mtx);
response_queue.push(response);
}
bool ClientContext::hasResponses() const {
std::lock_guard<std::mutex> lock(mtx);
return !response_queue.empty();
}
HttpResponse ClientContext::popResponse() {
std::lock_guard<std::mutex> lock(mtx);
HttpResponse response = response_queue.front();
response_queue.pop();
return response;
}
void ClientContext::setWriteReady(bool ready) {
std::lock_guard<std::mutex> lock(mtx);
write_ready = ready;
}
bool ClientContext::isWriteReady() const {
std::lock_guard<std::mutex> lock(mtx);
return write_ready;
}
bool ClientContext::isActive() const {
return active;
}
void ClientContext::deactivate() {
active = false;
}
四、最后
具体代码可以看我的仓库。