连接器客户端
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
#include "http_server.hpp"
|
||||
|
||||
#include "encoding.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define NOMINMAX
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
using Socket = SOCKET;
|
||||
constexpr Socket kInvalidSocket = INVALID_SOCKET;
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
using Socket = int;
|
||||
constexpr Socket kInvalidSocket = -1;
|
||||
#endif
|
||||
|
||||
namespace connector {
|
||||
namespace {
|
||||
|
||||
constexpr std::size_t kMaxBody = 200 * 1024 * 1024;
|
||||
constexpr std::size_t kMaxHeaders = 64 * 1024;
|
||||
|
||||
void close_socket(Socket socket) {
|
||||
#ifdef _WIN32
|
||||
closesocket(socket);
|
||||
#else
|
||||
close(socket);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string lowercase(std::string value) {
|
||||
std::transform(value.begin(), value.end(), value.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
return value;
|
||||
}
|
||||
|
||||
std::string status_text(int status) {
|
||||
switch (status) {
|
||||
case 200: return "OK";
|
||||
case 204: return "No Content";
|
||||
case 400: return "Bad Request";
|
||||
case 403: return "Forbidden";
|
||||
case 404: return "Not Found";
|
||||
case 413: return "Payload Too Large";
|
||||
case 500: return "Internal Server Error";
|
||||
case 503: return "Service Unavailable";
|
||||
default: return "Response";
|
||||
}
|
||||
}
|
||||
|
||||
void send_all(Socket socket, const std::string& data) {
|
||||
std::size_t sent = 0;
|
||||
while (sent < data.size()) {
|
||||
const int count = send(socket, data.data() + sent, static_cast<int>((std::min)(data.size() - sent, static_cast<std::size_t>(1 << 20))), 0);
|
||||
if (count <= 0) return;
|
||||
sent += static_cast<std::size_t>(count);
|
||||
}
|
||||
}
|
||||
|
||||
bool parse_request(Socket socket, HttpRequest& request, int& error_status) {
|
||||
std::string buffer;
|
||||
char chunk[8192];
|
||||
std::size_t header_end = std::string::npos;
|
||||
while ((header_end = buffer.find("\r\n\r\n")) == std::string::npos) {
|
||||
const int count = recv(socket, chunk, sizeof(chunk), 0);
|
||||
if (count <= 0) return false;
|
||||
buffer.append(chunk, count);
|
||||
if (buffer.size() > kMaxHeaders) { error_status = 413; return false; }
|
||||
}
|
||||
std::istringstream headers(buffer.substr(0, header_end));
|
||||
std::string line;
|
||||
if (!std::getline(headers, line)) return false;
|
||||
if (!line.empty() && line.back() == '\r') line.pop_back();
|
||||
std::istringstream first(line);
|
||||
std::string version;
|
||||
if (!(first >> request.method >> request.target >> version)) return false;
|
||||
while (std::getline(headers, line)) {
|
||||
if (!line.empty() && line.back() == '\r') line.pop_back();
|
||||
const auto separator = line.find(':');
|
||||
if (separator != std::string::npos) request.headers[lowercase(trim(line.substr(0, separator)))] = trim(line.substr(separator + 1));
|
||||
}
|
||||
const auto query_position = request.target.find('?');
|
||||
request.path = request.target.substr(0, query_position);
|
||||
if (query_position != std::string::npos) {
|
||||
const std::string query = request.target.substr(query_position + 1);
|
||||
std::size_t start = 0;
|
||||
while (start <= query.size()) {
|
||||
const auto end = query.find('&', start);
|
||||
const std::string part = query.substr(start, end - start);
|
||||
const auto equal = part.find('=');
|
||||
request.query[url_decode(part.substr(0, equal))] = url_decode(equal == std::string::npos ? "" : part.substr(equal + 1));
|
||||
if (end == std::string::npos) break;
|
||||
start = end + 1;
|
||||
}
|
||||
}
|
||||
std::size_t content_length = 0;
|
||||
if (const auto found = request.headers.find("content-length"); found != request.headers.end()) {
|
||||
try { content_length = static_cast<std::size_t>(std::stoull(found->second)); }
|
||||
catch (...) { error_status = 400; return false; }
|
||||
}
|
||||
if (content_length > kMaxBody) { error_status = 413; return false; }
|
||||
request.body = buffer.substr(header_end + 4);
|
||||
while (request.body.size() < content_length) {
|
||||
const int count = recv(socket, chunk, sizeof(chunk), 0);
|
||||
if (count <= 0) return false;
|
||||
request.body.append(chunk, count);
|
||||
}
|
||||
if (request.body.size() > content_length) request.body.resize(content_length);
|
||||
return true;
|
||||
}
|
||||
|
||||
void serve_client(Socket client, const RequestHandler& handler) {
|
||||
HttpRequest request;
|
||||
int error_status = 400;
|
||||
HttpResponse response;
|
||||
if (parse_request(client, request, error_status)) {
|
||||
try { response = handler(request); }
|
||||
catch (const std::exception& error) {
|
||||
response.status = 500;
|
||||
response.headers["Content-Type"] = "application/json; charset=utf-8";
|
||||
response.body = std::string("{\"code\":\"INTERNAL_ERROR\",\"message\":\"") + error.what() + "\"}";
|
||||
}
|
||||
} else {
|
||||
response.status = error_status;
|
||||
response.body = "Bad request";
|
||||
}
|
||||
response.headers["Content-Length"] = std::to_string(response.body.size());
|
||||
response.headers["Connection"] = "close";
|
||||
std::ostringstream output;
|
||||
output << "HTTP/1.1 " << response.status << ' ' << status_text(response.status) << "\r\n";
|
||||
for (const auto& [name, value] : response.headers) output << name << ": " << value << "\r\n";
|
||||
output << "\r\n";
|
||||
send_all(client, output.str());
|
||||
send_all(client, response.body);
|
||||
close_socket(client);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
HttpServer::HttpServer() = default;
|
||||
HttpServer::~HttpServer() { stop(); }
|
||||
|
||||
bool HttpServer::start(unsigned short port, RequestHandler handler, std::string& error) {
|
||||
if (running_) return true;
|
||||
#ifdef _WIN32
|
||||
WSADATA data{};
|
||||
if (WSAStartup(MAKEWORD(2, 2), &data) != 0) { error = "无法初始化网络服务"; return false; }
|
||||
#endif
|
||||
Socket server = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (server == kInvalidSocket) { error = "无法创建本地服务"; return false; }
|
||||
int reuse = 1;
|
||||
setsockopt(server, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<const char*>(&reuse), sizeof(reuse));
|
||||
sockaddr_in address{};
|
||||
address.sin_family = AF_INET;
|
||||
address.sin_port = htons(port);
|
||||
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
if (bind(server, reinterpret_cast<sockaddr*>(&address), sizeof(address)) != 0 || listen(server, 16) != 0) {
|
||||
close_socket(server);
|
||||
error = "端口 27843 已被占用,可能已有连接器正在运行";
|
||||
return false;
|
||||
}
|
||||
socket_ = static_cast<std::intptr_t>(server);
|
||||
handler_ = std::move(handler);
|
||||
running_ = true;
|
||||
thread_ = new std::thread([this] { accept_loop(); });
|
||||
return true;
|
||||
}
|
||||
|
||||
void HttpServer::accept_loop() {
|
||||
const Socket server = static_cast<Socket>(socket_);
|
||||
while (running_) {
|
||||
Socket client = accept(server, nullptr, nullptr);
|
||||
if (client == kInvalidSocket) {
|
||||
if (running_) continue;
|
||||
break;
|
||||
}
|
||||
std::thread(serve_client, client, handler_).detach();
|
||||
}
|
||||
}
|
||||
|
||||
void HttpServer::stop() {
|
||||
if (!running_.exchange(false)) return;
|
||||
#ifdef _WIN32
|
||||
shutdown(static_cast<Socket>(socket_), SD_BOTH);
|
||||
#else
|
||||
shutdown(static_cast<Socket>(socket_), SHUT_RDWR);
|
||||
#endif
|
||||
close_socket(static_cast<Socket>(socket_));
|
||||
if (thread_) {
|
||||
auto* thread = static_cast<std::thread*>(thread_);
|
||||
if (thread->joinable()) thread->join();
|
||||
delete thread;
|
||||
thread_ = nullptr;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
WSACleanup();
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace connector
|
||||
Reference in New Issue
Block a user