You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

640 lines
20 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include "httpserver.hpp"
#include <iostream>
#include <sstream>
#include <cstring>
#include <algorithm>
#include <memory>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <signal.h>
#include <fcntl.h>
#include <ctime>
// ==================== 工具函数实现 ====================
std::string HTTPServer::urlDecode(const std::string &encoded)
{
std::string decoded;
for (size_t i = 0; i < encoded.length(); ++i)
{
if (encoded[i] == '%' && i + 2 < encoded.length())
{
int hex;
std::istringstream hexStream(encoded.substr(i + 1, 2));
if (hexStream >> std::hex >> hex)
{
decoded += static_cast<char>(hex);
i += 2;
}
else
{
decoded += encoded[i];
}
}
else if (encoded[i] == '+')
{
decoded += ' ';
}
else
{
decoded += encoded[i];
}
}
return decoded;
}
std::map<std::string, std::string> HTTPServer::parseQueryString(const std::string &query)
{
std::map<std::string, std::string> params;
std::istringstream stream(query);
std::string pair;
while (std::getline(stream, pair, '&'))
{
size_t equalsPos = pair.find('=');
if (equalsPos != std::string::npos)
{
std::string key = urlDecode(pair.substr(0, equalsPos));
std::string value = urlDecode(pair.substr(equalsPos + 1));
params[key] = value;
}
else if (!pair.empty())
{
params[urlDecode(pair)] = "";
}
}
return params;
}
std::map<std::string, std::string> HTTPServer::parseHeaders(const std::string &headers)
{
std::map<std::string, std::string> header_map;
std::istringstream stream(headers);
std::string line;
while (std::getline(stream, line))
{
if (line.empty() || line == "\r")
continue;
// 移除可能的回车符
if (!line.empty() && line.back() == '\r')
{
line.pop_back();
}
size_t colonPos = line.find(':');
if (colonPos != std::string::npos)
{
std::string key = line.substr(0, colonPos);
// 跳过冒号和空格
size_t value_start = colonPos + 1;
while (value_start < line.length() && line[value_start] == ' ')
{
value_start++;
}
std::string value = line.substr(value_start);
header_map[key] = value;
}
}
return header_map;
}
std::string HTTPServer::getContentType(const std::map<std::string, std::string> &headers)
{
auto it = headers.find("Content-Type");
if (it != headers.end())
{
return it->second;
}
return "";
}
int HTTPServer::getContentLength(const std::map<std::string, std::string> &headers)
{
auto it = headers.find("Content-Length");
if (it != headers.end())
{
try
{
return std::stoi(it->second);
}
catch (const std::exception &e)
{
std::cerr << "解析Content-Length错误: " << e.what() << std::endl;
return 0;
}
}
return 0;
}
void HTTPServer::sendResponse(int client_socket, const std::string &response,
const std::string &content_type)
{
std::string httpResponse =
"HTTP/1.1 200 OK\r\n"
"Content-Type: " +
content_type + "\r\n"
"Content-Length: " +
std::to_string(response.length()) + "\r\n"
"Connection: close\r\n"
"\r\n" +
response;
send(client_socket, httpResponse.c_str(), httpResponse.length(), 0);
}
void HTTPServer::sendError(int client_socket, int code, const std::string &message)
{
std::string response = "<html><body><h1>" + std::to_string(code) + " " + message + "</h1></body></html>";
std::string httpResponse =
"HTTP/1.1 " + std::to_string(code) + " " + message + "\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"Content-Length: " +
std::to_string(response.length()) + "\r\n"
"Connection: close\r\n"
"\r\n" +
response;
send(client_socket, httpResponse.c_str(), httpResponse.length(), 0);
}
void HTTPServer::sendJSONResponse(int client_socket, const std::string &json)
{
sendResponse(client_socket, json, "application/json");
}
// ==================== 私有方法实现 ====================
std::string HTTPServer::readFullRequest(int client_socket, int timeout_ms)
{
std::string request;
char buffer[4096];
fd_set read_fds;
struct timeval timeout;
// 设置非阻塞模式
int flags = fcntl(client_socket, F_GETFL, 0);
fcntl(client_socket, F_SETFL, flags | O_NONBLOCK);
// 设置超时
timeout.tv_sec = timeout_ms / 1000;
timeout.tv_usec = (timeout_ms % 1000) * 1000;
while (true) {
FD_ZERO(&read_fds);
FD_SET(client_socket, &read_fds);
int activity = select(client_socket + 1, &read_fds, nullptr, nullptr, &timeout);
if (activity < 0) {
std::cerr << "select error" << std::endl;
break;
} else if (activity == 0) {
// 超时,检查是否已经有数据
if (!request.empty()) {
break;
}
std::cerr << "read timeout" << std::endl;
break;
}
if (FD_ISSET(client_socket, &read_fds)) {
ssize_t bytes_read = recv(client_socket, buffer, sizeof(buffer) - 1, 0);
if (bytes_read > 0) {
buffer[bytes_read] = '\0';
request.append(buffer, bytes_read);
// 检查是否已经收到完整的请求
if (isRequestComplete(request)) {
break;
}
} else if (bytes_read == 0) {
// 连接关闭
break;
} else {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
std::cerr << "recv error: " << strerror(errno) << std::endl;
}
break;
}
}
}
// 恢复阻塞模式
fcntl(client_socket, F_SETFL, flags);
return request;
}
bool HTTPServer::isRequestComplete(const std::string& request)
{
// 查找请求头和请求体的分隔符
size_t header_end = request.find("\r\n\r\n");
if (header_end == std::string::npos) {
return false; // 没有找到完整头部
}
// 提取头部部分
std::string headers_part = request.substr(0, header_end);
// 解析Content-Length
int content_length = getContentLength(parseHeaders(headers_part));
// 如果有Content-Length检查是否收到完整body
if (content_length > 0) {
size_t body_start = header_end + 4;
if (request.length() >= body_start + content_length) {
return true;
}
return false;
}
// 如果没有Content-Length检查是否以空行结尾表示没有body
if (request.length() >= header_end + 4 && request.substr(header_end + 4).empty()) {
return true;
}
// 对于没有Content-Length的GET请求有分隔符就认为完整
if (request.find("GET") == 0 || request.find("HEAD") == 0) {
return true;
}
return false;
}
void HTTPServer::logRequest(const std::string& method, const std::string& client_ip,
const std::map<std::string, std::string>& headers)
{
// 获取当前时间
std::time_t now = std::time(nullptr);
char time_str[100];
std::strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", std::localtime(&now));
std::cout << "[" << time_str << "] " << client_ip << " " << method;
auto user_agent = headers.find("User-Agent");
if (user_agent != headers.end()) {
std::cout << " (" << user_agent->second.substr(0, 50) << ")";
}
std::cout << std::endl;
}
void HTTPServer::handleConnection(int client_socket, const std::string& client_ip)
{
// 读取完整的请求
std::string request = readFullRequest(client_socket);
if (!request.empty()) {
handleRequest(client_socket, request);
} else {
std::cerr << "收到空请求或读取失败" << std::endl;
sendError(client_socket, 400, "Bad Request");
}
close(client_socket);
}
void HTTPServer::handleRequest(int client_socket, const std::string& request)
{
// 提取请求行
size_t line_end = request.find("\r\n");
if (line_end == std::string::npos) {
sendError(client_socket, 400, "Bad Request");
return;
}
std::string request_line = request.substr(0, line_end);
std::istringstream iss(request_line);
std::string method, path, protocol;
iss >> method >> path >> protocol;
// 提取头部
std::string headers_str;
size_t headers_end = request.find("\r\n\r\n");
if (headers_end != std::string::npos) {
headers_str = request.substr(line_end + 2, headers_end - line_end - 2);
}
auto headers = parseHeaders(headers_str);
for (const auto& header : headers) {
std::cout << header.first << ": " << header.second << std::endl;
}
// 记录请求日志
logRequest(method, "", headers);
// 处理请求
if (method == "POST") {
handlePOST(client_socket, request, headers);
} else if (method == "GET") {
handleGET(client_socket, request_line);
} else {
sendError(client_socket, 405, "Method Not Allowed");
}
}
void HTTPServer::handlePOST(int client_socket, const std::string& request,
const std::map<std::string, std::string>& headers)
{
std::cout << "\n=== 收到POST请求 ===\n";
// 获取请求体
int contentLength = getContentLength(headers);
std::string body;
if (contentLength > 0) {
size_t bodyPos = request.find("\r\n\r\n");
if (bodyPos != std::string::npos) {
bodyPos += 4; // 跳过空行
if (bodyPos < request.length()) {
body = request.substr(bodyPos);
// 确保body长度不超过Content-Length
if (body.length() > static_cast<size_t>(contentLength)) {
body = body.substr(0, contentLength);
}
}
}
}
std::cout << "请求体长度: " << body.length() << " 字节" << std::endl;
// 如果有自定义处理器,使用它
if (custom_handler) {
custom_handler(client_socket, request, headers, body);
return;
}
// 默认处理逻辑
std::string contentType = getContentType(headers);
std::string response;
if (contentType.find("application/x-www-form-urlencoded") != std::string::npos) {
// 表单数据
auto params = parseQueryString(body);
response = "<html><body><h1>POST数据已接收</h1>";
response += "<h2>表单数据:</h2>";
if (params.empty()) {
response += "<p>没有表单数据</p>";
response += "<p>原始body: " + body + "</p>";
} else {
response += "<ul>";
for (const auto& param : params) {
response += "<li><strong>" + param.first + ":</strong> " + param.second + "</li>";
}
response += "</ul>";
}
response += "</body></html>";
// 打印到控制台
std::cout << "\n解析的表单数据:\n";
for (const auto& param : params) {
std::cout << param.first << " = " << param.second << std::endl;
}
} else if (contentType.find("application/Json") != std::string::npos) {
// JSON数据
this->PostMsg = body;
response = "<html><body><h1>JSON数据已接收</h1>";
response += "<h2>原始JSON:</h2>";
response += "<pre>" + body + "</pre>";
response += "</body></html>";
std::cout << "\nJSON数据:\n" << body << std::endl;
} else {
// 其他类型
response = "<html><body><h1>数据已接收</h1>";
response += "<p>Content-Type: " + (contentType.empty() ? "未指定" : contentType) + "</p>";
response += "<p>数据长度: " + std::to_string(body.length()) + " 字节</p>";
if (!body.empty() && body.length() < 1000) {
response += "<h2>原始数据:</h2>";
response += "<pre>" + body + "</pre>";
} else if (!body.empty()) {
response += "<h2>原始数据前1000字节:</h2>";
response += "<pre>" + body.substr(0, 1000) + "...</pre>";
}
response += "</body></html>";
std::cout << "\n原始数据:\n" << body.substr(0, std::min(body.length(), (size_t)500)) << std::endl;
if (body.length() > 500) {
std::cout << "... (只显示前500字节)" << std::endl;
}
}
sendResponse(client_socket, response);
std::cout << "\n=== 处理完成 ===\n\n";
}
void HTTPServer::handleGET(int client_socket, const std::string& request_line)
{
std::istringstream iss(request_line);
std::string method, path, protocol;
iss >> method >> path >> protocol;
// 默认提供测试表单页面
std::string response =
"<html>"
"<head><title>HTTP POST测试服务器</title>"
"<meta charset=\"utf-8\">"
"<style>"
"body { font-family: Arial, sans-serif; margin: 40px; }"
".container { max-width: 800px; margin: 0 auto; }"
"form { margin: 20px 0; padding: 20px; border: 1px solid #ddd; background: #f9f9f9; }"
"h2 { color: #333; border-bottom: 2px solid #007bff; padding-bottom: 10px; }"
"input, textarea, select { width: 100%; margin: 10px 0; padding: 8px; border: 1px solid #ccc; border-radius: 4px; }"
"button { padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }"
"button:hover { background: #0056b3; }"
".log { background: #333; color: #fff; padding: 15px; border-radius: 4px; font-family: monospace; margin-top: 20px; }"
"</style>"
"<script>"
"function submitForm(formId, contentType) {"
" const form = document.getElementById(formId);"
" const formData = new FormData(form);"
" let body;"
" let headers = { 'Content-Type': contentType };"
" "
" if (contentType === 'application/json') {"
" const obj = {};"
" formData.forEach((value, key) => obj[key] = value);"
" body = JSON.stringify(obj);"
" } else if (contentType === 'application/x-www-form-urlencoded') {"
" const params = new URLSearchParams();"
" formData.forEach((value, key) => params.append(key, value));"
" body = params.toString();"
" } else {"
" body = formData;"
" }"
" "
" fetch('/', {"
" method: 'POST',"
" headers: headers,"
" body: contentType === 'multipart/form-data' ? formData : body"
" }).then(res => res.text()).then(html => {"
" document.getElementById('result').innerHTML = html;"
" document.getElementById('result').scrollIntoView();"
" });"
" return false;"
"}"
"</script>"
"</head>"
"<body>"
"<div class='container'>"
"<h1>HTTP POST测试服务器</h1>"
"<p>服务器运行在端口 " + std::to_string(port) + "</p>"
"<h2>1. 表单提交测试 (application/x-www-form-urlencoded)</h2>"
"<form id='form1' onsubmit='return submitForm(\"form1\", \"application/x-www-form-urlencoded\")'>"
"<label>姓名: <input type='text' name='name' value='张三' required></label><br>"
"<label>邮箱: <input type='email' name='email' value='test@example.com' required></label><br>"
"<label>消息: <textarea name='message' rows='4'>这是一个测试消息</textarea></label><br>"
"<button type='submit'>提交表单数据</button>"
"</form>"
"<h2>2. JSON提交测试 (application/json)</h2>"
"<form id='form2' onsubmit='return submitForm(\"form2\", \"application/json\")'>"
"<label>用户名: <input type='text' name='username' value='john_doe'></label><br>"
"<label>分数: <input type='number' name='score' value='95'></label><br>"
"<button type='submit'>提交JSON数据</button>"
"</form>"
"<h2>3. 使用curl测试</h2>"
"<div class='log'>"
"<p>表单数据:</p>"
"<code>curl -X POST http://localhost:" + std::to_string(port) + "/ \\<br>"
" -H \"Content-Type: application/x-www-form-urlencoded\" \\<br>"
" -d \"name=张三&email=test@example.com&message=Hello%20World\"</code><br><br>"
"<p>JSON数据:</p>"
"<code>curl -X POST http://localhost:" + std::to_string(port) + "/ \\<br>"
" -H \"Content-Type: application/json\" \\<br>"
" -d '{\"name\":\"李四\",\"age\":25,\"city\":\"北京\"}'</code>"
"</div>"
"<div id='result'></div>"
"</div>"
"</body></html>";
sendResponse(client_socket, response);
}
// ==================== 公有方法实现 ====================
HTTPServer::HTTPServer(int port) : port(port), server_fd(-1), custom_handler(nullptr) {}
HTTPServer::~HTTPServer() {
stop();
}
bool HTTPServer::start() {
// 创建socket
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
std::cerr << "创建socket失败: " << strerror(errno) << std::endl;
return false;
}
// 设置socket选项避免地址占用
int opt = 1;
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
std::cerr << "设置socket选项失败: " << strerror(errno) << std::endl;
close(server_fd);
return false;
}
// 绑定地址和端口
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(port);
if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {
std::cerr << "绑定端口 " << port << " 失败: " << strerror(errno) << std::endl;
close(server_fd);
return false;
}
// 开始监听
if (listen(server_fd, 10) < 0) {
std::cerr << "监听失败: " << strerror(errno) << std::endl;
close(server_fd);
return false;
}
std::cout << "HTTP服务器启动成功监听端口: " << port << std::endl;
std::cout << "访问 http://localhost:" << port << " 进行测试" << std::endl;
std::cout << "按 Ctrl+C 停止服务器" << std::endl;
return true;
}
void HTTPServer::run()
{
fd_set readfds;
struct timeval timeout;
FD_ZERO(&readfds);
FD_SET(server_fd, &readfds);
// 设置1秒超时
timeout.tv_sec = 1;
timeout.tv_usec = 0;
int activity = select(server_fd + 1, &readfds, NULL, NULL, &timeout);
if (activity < 0)
{
std::cerr << "select error: " << strerror(errno) << std::endl;
return;
}
if (activity == 0)
{
// 超时,检查是否应该停止
return;
}
if (FD_ISSET(server_fd, &readfds))
{
struct sockaddr_in client_address;
socklen_t client_len = sizeof(client_address);
int client_socket = accept(server_fd, (struct sockaddr *)&client_address, &client_len);
if (client_socket < 0)
{
std::cerr << "接受连接失败: " << strerror(errno) << std::endl;
return;
}
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_address.sin_addr, client_ip, INET_ADDRSTRLEN);
handleConnection(client_socket, client_ip);
}
}
void HTTPServer::stop()
{
if (server_fd != -1)
{
close(server_fd);
server_fd = -1;
std::cout << "服务器已停止" << std::endl;
}
}
void HTTPServer::SetPort(int port)
{
this->port = port;
}
void HTTPServer::setRequestHandler(const RequestHandler& handler) {
custom_handler = handler;
}