mirror of
https://github.com/azerothcore/mod-ale
synced 2025-11-29 15:38:17 +08:00
277 lines
6.7 KiB
C++
277 lines
6.7 KiB
C++
#include <thread>
|
|
extern "C"
|
|
{
|
|
#include "lua.h"
|
|
#include "lauxlib.h"
|
|
};
|
|
|
|
#if defined TRINITY || defined AZEROTHCORE
|
|
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
|
#endif
|
|
#include "libs/httplib.h"
|
|
#include "HttpManager.h"
|
|
#include "LuaEngine.h"
|
|
|
|
HttpWorkItem::HttpWorkItem(int funcRef, const std::string& httpVerb, const std::string& url, const std::string& body, const std::string& contentType, const httplib::Headers& headers)
|
|
: funcRef(funcRef),
|
|
httpVerb(httpVerb),
|
|
url(url),
|
|
body(body),
|
|
contentType(contentType),
|
|
headers(headers)
|
|
{ }
|
|
|
|
HttpResponse::HttpResponse(int funcRef, int statusCode, const std::string& body, const httplib::Headers& headers)
|
|
: funcRef(funcRef),
|
|
statusCode(statusCode),
|
|
body(body),
|
|
headers(headers)
|
|
{ }
|
|
|
|
HttpManager::HttpManager()
|
|
: workQueue(16),
|
|
responseQueue(16),
|
|
startedWorkerThread(false),
|
|
cancelationToken(false),
|
|
condVar(),
|
|
condVarMutex(),
|
|
parseUrlRegex("^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?")
|
|
{
|
|
StartHttpWorker();
|
|
}
|
|
|
|
HttpManager::~HttpManager()
|
|
{
|
|
StopHttpWorker();
|
|
}
|
|
|
|
void HttpManager::PushRequest(HttpWorkItem* item)
|
|
{
|
|
std::unique_lock<std::mutex> lock(condVarMutex);
|
|
workQueue.push(item);
|
|
condVar.notify_one();
|
|
}
|
|
|
|
void HttpManager::StartHttpWorker()
|
|
{
|
|
ClearQueues();
|
|
|
|
if (!startedWorkerThread)
|
|
{
|
|
cancelationToken.store(false);
|
|
workerThread = std::thread(&HttpManager::HttpWorkerThread, this);
|
|
startedWorkerThread = true;
|
|
}
|
|
}
|
|
|
|
void HttpManager::ClearQueues()
|
|
{
|
|
while (workQueue.front())
|
|
{
|
|
HttpWorkItem* item = *workQueue.front();
|
|
if (item != nullptr)
|
|
{
|
|
delete item;
|
|
}
|
|
workQueue.pop();
|
|
}
|
|
|
|
while (responseQueue.front())
|
|
{
|
|
HttpResponse* item = *responseQueue.front();
|
|
if (item != nullptr)
|
|
{
|
|
delete item;
|
|
}
|
|
responseQueue.pop();
|
|
}
|
|
}
|
|
|
|
void HttpManager::StopHttpWorker()
|
|
{
|
|
if (!startedWorkerThread)
|
|
{
|
|
return;
|
|
}
|
|
|
|
cancelationToken.store(true);
|
|
condVar.notify_one();
|
|
workerThread.join();
|
|
ClearQueues();
|
|
startedWorkerThread = false;
|
|
}
|
|
|
|
void HttpManager::HttpWorkerThread()
|
|
{
|
|
while (true)
|
|
{
|
|
{
|
|
std::unique_lock<std::mutex> lock(condVarMutex);
|
|
condVar.wait(lock, [&] { return workQueue.front() != nullptr || cancelationToken.load(); });
|
|
}
|
|
|
|
if (cancelationToken.load())
|
|
{
|
|
break;
|
|
}
|
|
if (!workQueue.front())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
HttpWorkItem* req = *workQueue.front();
|
|
workQueue.pop();
|
|
if (!req)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
try
|
|
{
|
|
std::string host;
|
|
std::string path;
|
|
|
|
if (!ParseUrl(req->url, host, path)) {
|
|
ELUNA_LOG_ERROR("[Eluna]: Could not parse URL %s", req->url.c_str());
|
|
continue;
|
|
}
|
|
|
|
httplib::Client cli(host);
|
|
cli.set_connection_timeout(0, 3000000); // 3 seconds
|
|
cli.set_read_timeout(5, 0); // 5 seconds
|
|
cli.set_write_timeout(5, 0); // 5 seconds
|
|
|
|
httplib::Result res = DoRequest(cli, req, path);
|
|
httplib::Error err = res.error();
|
|
if (err != httplib::Error::Success)
|
|
{
|
|
ELUNA_LOG_ERROR("[Eluna]: HTTP request error: %s", httplib::to_string(err).c_str());
|
|
continue;
|
|
}
|
|
|
|
if (res->status == 301)
|
|
{
|
|
std::string location = res->get_header_value("Location");
|
|
std::string host;
|
|
std::string path;
|
|
|
|
if (!ParseUrl(location, host, path))
|
|
{
|
|
ELUNA_LOG_ERROR("[Eluna]: Could not parse URL after redirect: %s", location.c_str());
|
|
continue;
|
|
}
|
|
httplib::Client cli2(host);
|
|
cli2.set_connection_timeout(0, 3000000); // 3 seconds
|
|
cli2.set_read_timeout(5, 0); // 5 seconds
|
|
cli2.set_write_timeout(5, 0); // 5 seconds
|
|
res = DoRequest(cli2, req, path);
|
|
}
|
|
|
|
responseQueue.push(new HttpResponse(req->funcRef, res->status, res->body, res->headers));
|
|
}
|
|
catch (const std::exception& ex)
|
|
{
|
|
ELUNA_LOG_ERROR("[Eluna]: HTTP request error: %s", ex.what());
|
|
}
|
|
|
|
delete req;
|
|
}
|
|
}
|
|
|
|
httplib::Result HttpManager::DoRequest(httplib::Client& client, HttpWorkItem* req, const std::string& urlPath)
|
|
{
|
|
const char* path = urlPath.c_str();
|
|
if (req->httpVerb == "GET")
|
|
{
|
|
return client.Get(path, req->headers);
|
|
}
|
|
if (req->httpVerb == "HEAD")
|
|
{
|
|
return client.Head(path, req->headers);
|
|
}
|
|
if (req->httpVerb == "POST")
|
|
{
|
|
return client.Post(path, req->headers, req->body, req->contentType.c_str());
|
|
}
|
|
if (req->httpVerb == "PUT")
|
|
{
|
|
return client.Put(path, req->headers, req->body, req->contentType.c_str());
|
|
}
|
|
if (req->httpVerb == "PATCH")
|
|
{
|
|
return client.Patch(path, req->headers, req->body, req->contentType.c_str());
|
|
}
|
|
if (req->httpVerb == "DELETE")
|
|
{
|
|
return client.Delete(path, req->headers);
|
|
}
|
|
if (req->httpVerb == "OPTIONS")
|
|
{
|
|
return client.Options(path, req->headers);
|
|
}
|
|
|
|
ELUNA_LOG_ERROR("[Eluna]: HTTP request error: invalid HTTP verb %s", req->httpVerb.c_str());
|
|
return client.Get(path, req->headers);
|
|
}
|
|
|
|
bool HttpManager::ParseUrl(const std::string& url, std::string& host, std::string& path)
|
|
{
|
|
std::smatch matches;
|
|
|
|
if (!std::regex_search(url, matches, parseUrlRegex))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
std::string scheme = matches[2];
|
|
std::string authority = matches[4];
|
|
std::string query = matches[7];
|
|
host = scheme + "://" + authority;
|
|
path = matches[5];
|
|
if (path.empty())
|
|
{
|
|
path = "/";
|
|
}
|
|
path += (query.empty() ? "" : "?") + query;
|
|
|
|
return true;
|
|
}
|
|
|
|
void HttpManager::HandleHttpResponses()
|
|
{
|
|
while (!responseQueue.empty())
|
|
{
|
|
HttpResponse* res = *responseQueue.front();
|
|
responseQueue.pop();
|
|
|
|
if (res == nullptr)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
LOCK_ELUNA;
|
|
|
|
lua_State* L = Eluna::GEluna->L;
|
|
|
|
// Get function
|
|
lua_rawgeti(L, LUA_REGISTRYINDEX, res->funcRef);
|
|
|
|
// Push parameters
|
|
Eluna::Push(L, res->statusCode);
|
|
Eluna::Push(L, res->body);
|
|
lua_newtable(L);
|
|
for (const auto& item : res->headers) {
|
|
Eluna::Push(L, item.first);
|
|
Eluna::Push(L, item.second);
|
|
lua_settable(L, -3);
|
|
}
|
|
|
|
// Call function
|
|
Eluna::GEluna->ExecuteCall(3, 0);
|
|
|
|
luaL_unref(L, LUA_REGISTRYINDEX, res->funcRef);
|
|
|
|
delete res;
|
|
}
|
|
}
|