256 lines
8.6 KiB
C++
256 lines
8.6 KiB
C++
#include <string>
|
||
#ifdef MALLOC_TRIM
|
||
#include <malloc.h>
|
||
#endif // MALLOC_TRIM
|
||
#ifndef CPPHTTPLIB_LISTEN_BACKLOG
|
||
#define CPPHTTPLIB_LISTEN_BACKLOG 10240
|
||
#endif // CPPHTTPLIB_LISTEN_BACKLOG
|
||
#define CPPHTTPLIB_MAX_LINE_LENGTH 819200
|
||
#define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 819200
|
||
#define CPPHTTPLIB_HEADER_MAX_LENGTH 819200
|
||
#define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 819200
|
||
#include "httplib.h"
|
||
|
||
#include "utils/base64/base64.h"
|
||
#include "utils/logger.h"
|
||
#include "utils/stl_extra.h"
|
||
#include "utils/string_hash.h"
|
||
#include "utils/urlencode.h"
|
||
#include "handler/settings.h"
|
||
#include "webserver.h"
|
||
|
||
|
||
static const char *request_header_blacklist[] = {"host", "accept",
|
||
"accept-encoding"};
|
||
|
||
static inline bool is_request_header_blacklisted(const std::string &header) {
|
||
for (auto &x : request_header_blacklist) {
|
||
if (strcasecmp(x, header.c_str()) == 0) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
void WebServer::stop_web_server() { SERVER_EXIT_FLAG = true; }
|
||
|
||
static httplib::Server::Handler makeHandler(const responseRoute &rr) {
|
||
return [rr](const httplib::Request &request, httplib::Response &response) {
|
||
Request req;
|
||
Response resp;
|
||
req.method = request.method;
|
||
req.url = request.path;
|
||
req.remote_addr = request.remote_addr;
|
||
req.remote_port = request.remote_port;
|
||
for (auto &h : request.headers) {
|
||
if (startsWith(h.first, "LOCAL_") || startsWith(h.first, "REMOTE_") ||
|
||
is_request_header_blacklisted(h.first)) {
|
||
continue;
|
||
}
|
||
req.headers.emplace(h.first.data(), h.second.data());
|
||
}
|
||
req.argument = request.params;
|
||
if (request.method == "POST" || request.method == "PUT" ||
|
||
request.method == "PATCH") {
|
||
if (request.get_header_value("Content-Type") ==
|
||
"application/x-www-form-urlencoded") {
|
||
req.postdata = urlDecode(request.body);
|
||
} else {
|
||
req.postdata = request.body;
|
||
}
|
||
}
|
||
auto result = rr.rc(req, resp);
|
||
response.status = resp.status_code;
|
||
for (auto &h : resp.headers) {
|
||
response.set_header(h.first, h.second);
|
||
}
|
||
auto content_type = resp.content_type;
|
||
if (content_type.empty()) {
|
||
content_type = rr.content_type;
|
||
}
|
||
response.set_content(result, content_type);
|
||
};
|
||
}
|
||
|
||
static std::string dump(const httplib::Headers &headers) {
|
||
std::string s;
|
||
for (auto &x : headers) {
|
||
if (startsWith(x.first, "LOCAL_") || startsWith(x.first, "REMOTE_"))
|
||
continue;
|
||
s += x.first + ": " + x.second + "|";
|
||
}
|
||
return s;
|
||
}
|
||
|
||
int WebServer::start_web_server_multi(listener_args *args) {
|
||
httplib::Server server;
|
||
for (auto &x : responses) {
|
||
switch (hash_(x.method)) {
|
||
case "GET"_hash:
|
||
case "HEAD"_hash:
|
||
server.Get(x.path, makeHandler(x));
|
||
break;
|
||
case "POST"_hash:
|
||
server.Post(x.path, makeHandler(x));
|
||
break;
|
||
case "PUT"_hash:
|
||
server.Put(x.path, makeHandler(x));
|
||
break;
|
||
case "DELETE"_hash:
|
||
server.Delete(x.path, makeHandler(x));
|
||
break;
|
||
case "PATCH"_hash:
|
||
server.Patch(x.path, makeHandler(x));
|
||
break;
|
||
}
|
||
}
|
||
server.Options(R"(.*)",
|
||
[&](const httplib::Request &req, httplib::Response &res) {
|
||
auto path = req.path;
|
||
std::string allowed;
|
||
for (auto &rr : responses) {
|
||
if (rr.path == path) {
|
||
allowed += rr.method + ",";
|
||
}
|
||
}
|
||
if (!allowed.empty()) {
|
||
allowed.pop_back();
|
||
res.status = 200;
|
||
res.set_header("Access-Control-Allow-Methods", allowed);
|
||
res.set_header("Access-Control-Allow-Origin", "*");
|
||
res.set_header("Access-Control-Allow-Headers",
|
||
"Content-Type,Authorization");
|
||
} else {
|
||
res.status = 404;
|
||
}
|
||
});
|
||
server.set_pre_routing_handler([&](const httplib::Request &req,
|
||
httplib::Response &res) {
|
||
if (shouldLog(LOG_LEVEL_DEBUG)) {
|
||
writeLog(0,
|
||
"接受客户端连接:" + req.remote_addr + ":" +
|
||
std::to_string(req.remote_port),
|
||
LOG_LEVEL_DEBUG);
|
||
}
|
||
if (shouldLog(LOG_LEVEL_VERBOSE)) {
|
||
writeLog(0,
|
||
"处理请求:method=" + req.method + " uri=" +
|
||
req.target,
|
||
LOG_LEVEL_VERBOSE);
|
||
writeLog(0, "请求头:" + dump(req.headers), LOG_LEVEL_VERBOSE);
|
||
}
|
||
|
||
if (req.has_header("SubConverter-Request")) {
|
||
res.status = 500;
|
||
res.set_content("Internal error: loop request detected.\n"
|
||
"内部错误:检测到循环请求。\n"
|
||
"Please check subscription URLs and proxy settings to "
|
||
"avoid routing the service back to itself.\n"
|
||
"请检查订阅链接和代理设置,避免服务请求回到自身。",
|
||
"text/plain");
|
||
return httplib::Server::HandlerResponse::Handled;
|
||
}
|
||
res.set_header("Server",
|
||
"SubConverter-Extended/" VERSION " cURL/" LIBCURL_VERSION);
|
||
if (require_auth) {
|
||
static std::string auth_token =
|
||
"Basic " + base64Encode(auth_user + ":" + auth_password);
|
||
auto auth = req.get_header_value("Authorization");
|
||
if (auth != auth_token) {
|
||
res.status = 401;
|
||
res.set_header("WWW-Authenticate",
|
||
"Basic realm=" + auth_realm + ", charset=\"UTF-8\"");
|
||
res.set_content("Unauthorized: missing or invalid credentials.\n"
|
||
"未授权:认证凭据缺失或无效。",
|
||
"text/plain");
|
||
return httplib::Server::HandlerResponse::Handled;
|
||
}
|
||
}
|
||
res.set_header("X-Client-IP", req.remote_addr);
|
||
if (req.has_header("Access-Control-Request-Headers")) {
|
||
res.set_header("Access-Control-Allow-Headers",
|
||
req.get_header_value("Access-Control-Request-Headers"));
|
||
}
|
||
res.set_header("Access-Control-Allow-Origin", "*");
|
||
return httplib::Server::HandlerResponse::Unhandled;
|
||
});
|
||
for (auto &x : redirect_map) {
|
||
server.Get(x.first,
|
||
[x](const httplib::Request &req, httplib::Response &res) {
|
||
auto arguments = req.params;
|
||
auto query = x.second;
|
||
auto pos = query.find('?');
|
||
query += pos == std::string::npos ? '?' : '&';
|
||
for (auto &p : arguments) {
|
||
query += p.first + "=" + urlEncode(p.second) + "&";
|
||
}
|
||
if (!query.empty()) {
|
||
query.pop_back();
|
||
}
|
||
res.set_redirect(query);
|
||
});
|
||
}
|
||
server.set_exception_handler([](const httplib::Request &req,
|
||
httplib::Response &res,
|
||
const std::exception_ptr &e) {
|
||
try {
|
||
if (e)
|
||
std::rethrow_exception(e);
|
||
} catch (const httplib::Error &err) {
|
||
res.set_content(to_string(err), "text/plain");
|
||
} catch (const std::exception &ex) {
|
||
std::string return_data =
|
||
"Internal server error while processing request.\n"
|
||
"处理请求时发生内部服务器错误。\n"
|
||
"Request / 请求: " +
|
||
req.target + "\n";
|
||
return_data += "\n Exception / 异常: ";
|
||
return_data += type(ex);
|
||
return_data += "\n what(): ";
|
||
return_data += ex.what();
|
||
res.status = 500;
|
||
res.set_content(return_data, "text/plain");
|
||
} catch (...) {
|
||
res.status = 500;
|
||
}
|
||
});
|
||
if (serve_file) {
|
||
server.set_mount_point("/", serve_file_root);
|
||
}
|
||
server.new_task_queue = [args] {
|
||
return new httplib::ThreadPool(args->max_workers,
|
||
global.maxServerThreads);
|
||
};
|
||
if (!server.bind_to_port(args->listen_address, args->port, 0)) {
|
||
writeLog(0,
|
||
"无法绑定 HTTP 服务地址:" + args->listen_address + ":" +
|
||
std::to_string(args->port),
|
||
LOG_LEVEL_FATAL);
|
||
return 1;
|
||
}
|
||
|
||
std::thread thread([&]() {
|
||
if (!server.listen_after_bind() && !SERVER_EXIT_FLAG) {
|
||
writeLog(0, "HTTP 服务在接受请求前停止。",
|
||
LOG_LEVEL_ERROR);
|
||
SERVER_EXIT_FLAG = true;
|
||
}
|
||
});
|
||
|
||
while (!SERVER_EXIT_FLAG) {
|
||
if (args->looper_callback) {
|
||
args->looper_callback();
|
||
}
|
||
std::this_thread::sleep_for(
|
||
std::chrono::milliseconds(args->looper_interval));
|
||
}
|
||
|
||
server.stop();
|
||
thread.join();
|
||
return 0;
|
||
}
|
||
|
||
int WebServer::start_web_server(listener_args *args) {
|
||
return start_web_server_multi(args);
|
||
}
|