mirror of
https://github.com/quizhizhe/LiteLoaderBDS-1.16.40.git
synced 2025-06-06 12:03:39 +00:00
1115 lines
42 KiB
C++
1115 lines
42 KiB
C++
#include "APIHelp.h"
|
|
#include <Utils/NetworkHelper.h>
|
|
#include <Engine/TimeTaskSystem.h>
|
|
#include <Engine/EngineManager.h>
|
|
#include <httplib/httplib.h>
|
|
#include <LightWebSocketClient/include/WebSocketClient.h>
|
|
#include "NetworkAPI.h"
|
|
#include <SafeGuardRecord.h>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <ScheduleAPI.h>
|
|
#include <LiteLoader/Main/Config.h>
|
|
using namespace std;
|
|
|
|
using namespace cyanray;
|
|
|
|
//////////////////// Classes ////////////////////
|
|
|
|
ClassDefine<void> NetworkClassBuilder =
|
|
defineClass("network")
|
|
.function("httpGet", &NetworkClass::httpGet)
|
|
.function("httpPost", &NetworkClass::httpPost)
|
|
.function("httpGetSync", &NetworkClass::httpGetSync)
|
|
|
|
// For compatibility
|
|
.function("newWebSocket", &NetworkClass::newWebSocket)
|
|
.build();
|
|
|
|
ClassDefine<WSClientClass> WSClientClassBuilder =
|
|
defineClass<WSClientClass>("WSClient")
|
|
.constructor(&WSClientClass::constructor)
|
|
.instanceProperty("status", &WSClientClass::getStatus)
|
|
.instanceFunction("connect", &WSClientClass::connect)
|
|
.instanceFunction("connectAsync", &WSClientClass::connectAsync)
|
|
.instanceFunction("send", &WSClientClass::send)
|
|
.instanceFunction("listen", &WSClientClass::listen)
|
|
.instanceFunction("close", &WSClientClass::close)
|
|
.instanceFunction("shutdown", &WSClientClass::shutdown)
|
|
.instanceFunction("errorCode", &WSClientClass::errorCode)
|
|
|
|
.property("Open", [] { return Number::newNumber((int)WebSocketClient::Status::Open); })
|
|
.property("Closing", [] { return Number::newNumber((int)WebSocketClient::Status::Closing); })
|
|
.property("Closed", [] { return Number::newNumber((int)WebSocketClient::Status::Closed); })
|
|
.build();
|
|
|
|
ClassDefine<HttpServerClass> HttpServerClassBuilder =
|
|
defineClass<HttpServerClass>("HttpServer")
|
|
.constructor(&HttpServerClass::constructor)
|
|
.instanceFunction("onGet", &HttpServerClass::onGet)
|
|
.instanceFunction("onPut", &HttpServerClass::onPut)
|
|
.instanceFunction("onPost", &HttpServerClass::onPost)
|
|
.instanceFunction("onPatch", &HttpServerClass::onPatch)
|
|
.instanceFunction("onDelete", &HttpServerClass::onDelete)
|
|
.instanceFunction("onOptions", &HttpServerClass::onOptions)
|
|
|
|
.instanceFunction("onPreRouting", &HttpServerClass::onPreRouting)
|
|
.instanceFunction("onPostRouting", &HttpServerClass::onPostRouting)
|
|
.instanceFunction("onError", &HttpServerClass::onError)
|
|
.instanceFunction("onException", &HttpServerClass::onException)
|
|
|
|
.instanceFunction("listen", &HttpServerClass::listen)
|
|
.instanceFunction("startAt", &HttpServerClass::listen)
|
|
.instanceFunction("stop", &HttpServerClass::stop)
|
|
.instanceFunction("close", &HttpServerClass::stop)
|
|
.instanceFunction("isRunning", &HttpServerClass::isRunning)
|
|
.build();
|
|
|
|
ClassDefine<HttpRequestClass> HttpRequestClassBuilder =
|
|
defineClass<HttpRequestClass>("HttpRequest")
|
|
.constructor(nullptr)
|
|
.instanceFunction("getHeader", &HttpRequestClass::getHeader)
|
|
|
|
.instanceProperty("headers", &HttpRequestClass::getHeaders)
|
|
.instanceProperty("method", &HttpRequestClass::getMethod)
|
|
.instanceProperty("body", &HttpRequestClass::getBody)
|
|
.instanceProperty("path", &HttpRequestClass::getPath)
|
|
.instanceProperty("params", &HttpRequestClass::getParams)
|
|
.instanceProperty("query", &HttpRequestClass::getParams)
|
|
.instanceProperty("remoteAddr", &HttpRequestClass::getRemoteAddr)
|
|
.instanceProperty("remotePort", &HttpRequestClass::getRemotePort)
|
|
.instanceProperty("version", &HttpRequestClass::getVersion)
|
|
.instanceProperty("matches", &HttpRequestClass::getRegexMatches)
|
|
.build();
|
|
|
|
ClassDefine<HttpResponseClass> HttpResponseClassBuilder =
|
|
defineClass<HttpResponseClass>("HttpResponse")
|
|
.constructor(nullptr)
|
|
.instanceFunction("getHeader", &HttpResponseClass::getHeader)
|
|
.instanceFunction("setHeader", &HttpResponseClass::setHeader)
|
|
.instanceFunction("write", &HttpResponseClass::write)
|
|
|
|
.instanceProperty("headers", &HttpResponseClass::getHeaders, &HttpResponseClass::setHeaders)
|
|
.instanceProperty("status", &HttpResponseClass::getStatus, &HttpResponseClass::setStatus)
|
|
.instanceProperty("body", &HttpResponseClass::getBody, &HttpResponseClass::setBody)
|
|
.instanceProperty("version", &HttpResponseClass::getVersion, &HttpResponseClass::setVersion)
|
|
.instanceProperty("reason", &HttpResponseClass::getReason, &HttpResponseClass::setReason)
|
|
.build();
|
|
|
|
//生成函数
|
|
WSClientClass::WSClientClass(const Local<Object>& scriptObj)
|
|
: ScriptClass(scriptObj)
|
|
, ws(std::make_shared<WebSocketClient>()) {
|
|
initListeners();
|
|
}
|
|
|
|
WSClientClass::WSClientClass()
|
|
: ScriptClass(ScriptClass::ConstructFromCpp<WSClientClass>{})
|
|
, ws(std::make_shared<WebSocketClient>()) {
|
|
initListeners();
|
|
}
|
|
|
|
void WSClientClass::initListeners() {
|
|
ws->OnTextReceived([nowList{&listeners[int(WSClientEvents::onTextReceived)]}](WebSocketClient& client, string msg) {
|
|
if (!nowList->empty())
|
|
for (auto& listener : *nowList) {
|
|
if (!EngineManager::isValid(listener.engine))
|
|
return;
|
|
EngineScope enter(listener.engine);
|
|
// dangerous
|
|
NewTimeout(listener.func.get(), {String::newString(msg)}, 1);
|
|
}
|
|
});
|
|
|
|
ws->OnBinaryReceived([nowList{&listeners[int(WSClientEvents::onBinaryReceived)]}](WebSocketClient& client, vector<uint8_t> data) {
|
|
if (!nowList->empty())
|
|
for (auto& listener : *nowList) {
|
|
if (!EngineManager::isValid(listener.engine))
|
|
return;
|
|
EngineScope enter(listener.engine);
|
|
NewTimeout(listener.func.get(), {ByteBuffer::newByteBuffer(data.data(), data.size())}, 1);
|
|
}
|
|
});
|
|
|
|
ws->OnError([nowList{&listeners[int(WSClientEvents::onError)]}](WebSocketClient& client, string msg) {
|
|
if (!nowList->empty())
|
|
for (auto& listener : *nowList) {
|
|
if (!EngineManager::isValid(listener.engine))
|
|
return;
|
|
EngineScope enter(listener.engine);
|
|
NewTimeout(listener.func.get(), {String::newString(msg)}, 1);
|
|
}
|
|
});
|
|
|
|
ws->OnLostConnection([nowList{&listeners[int(WSClientEvents::onLostConnection)]}](WebSocketClient& client, int code) {
|
|
if (!nowList->empty())
|
|
for (auto& listener : *nowList) {
|
|
if (!EngineManager::isValid(listener.engine))
|
|
return;
|
|
EngineScope enter(listener.engine);
|
|
NewTimeout(listener.func.get(), {Number::newNumber(code)}, 1);
|
|
}
|
|
});
|
|
}
|
|
|
|
void WSClientClass::initListeners_s() {
|
|
ws->OnTextReceived([nowList{&listeners[int(WSClientEvents::onTextReceived)]}, engine = EngineScope::currentEngine()](WebSocketClient& client, string msg) {
|
|
if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying())
|
|
return;
|
|
Schedule::nextTick([nowList, engine, msg = std::move(msg)]() {
|
|
if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying())
|
|
return;
|
|
EngineScope enter(engine);
|
|
if (!nowList->empty())
|
|
for (auto& listener : *nowList) {
|
|
listener.func.get().call({}, {String::newString(msg)});
|
|
}
|
|
});
|
|
});
|
|
|
|
ws->OnBinaryReceived([nowList{&listeners[int(WSClientEvents::onBinaryReceived)]}, engine = EngineScope::currentEngine()](WebSocketClient& client, vector<uint8_t> data) {
|
|
if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying())
|
|
return;
|
|
Schedule::nextTick([nowList, engine, data = std::move(data)]() mutable {
|
|
if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying())
|
|
return;
|
|
EngineScope enter(engine);
|
|
if (!nowList->empty())
|
|
for (auto& listener : *nowList) {
|
|
listener.func.get().call({}, {ByteBuffer::newByteBuffer(data.data(), data.size())});
|
|
}
|
|
});
|
|
});
|
|
|
|
ws->OnError([nowList{&listeners[int(WSClientEvents::onError)]}, engine = EngineScope::currentEngine()](WebSocketClient& client, string msg) {
|
|
if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying())
|
|
return;
|
|
Schedule::nextTick([nowList, engine, msg = std::move(msg)]() {
|
|
if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying())
|
|
return;
|
|
EngineScope enter(engine);
|
|
if (!nowList->empty())
|
|
for (auto& listener : *nowList) {
|
|
listener.func.get().call({}, {String::newString(msg)});
|
|
}
|
|
});
|
|
});
|
|
|
|
ws->OnLostConnection([nowList{&listeners[int(WSClientEvents::onLostConnection)]}, engine = EngineScope::currentEngine()](WebSocketClient& client, int code) {
|
|
if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying())
|
|
return;
|
|
Schedule::nextTick([nowList, engine, code]() {
|
|
if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying())
|
|
return;
|
|
EngineScope enter(engine);
|
|
if (!nowList->empty())
|
|
for (auto& listener : *nowList) {
|
|
listener.func.get().call({}, {Number::newNumber(code)});
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
void WSClientClass::clearListeners() {
|
|
ws->OnTextReceived([](WebSocketClient&, std::string) {});
|
|
ws->OnBinaryReceived([](WebSocketClient&, std::vector<uint8_t>) {});
|
|
ws->OnError([](WebSocketClient&, std::string) {});
|
|
ws->OnLostConnection([](WebSocketClient&, int) {});
|
|
}
|
|
|
|
WSClientClass* WSClientClass::constructor(const Arguments& args) {
|
|
return new WSClientClass(args.thiz());
|
|
}
|
|
|
|
//成员函数
|
|
void WSClientClass::addListener(const string& event, Local<Function> func) {
|
|
if (event == "onTextReceived")
|
|
listeners[(int)WSClientEvents::onTextReceived].push_back({EngineScope::currentEngine(), script::Global<Function>(func)});
|
|
else if (event == "onBinaryReceived")
|
|
listeners[(int)WSClientEvents::onBinaryReceived].push_back({EngineScope::currentEngine(), script::Global<Function>(func)});
|
|
else if (event == "onError")
|
|
listeners[(int)WSClientEvents::onError].push_back({EngineScope::currentEngine(), script::Global<Function>(func)});
|
|
else if (event == "onLostConnection")
|
|
listeners[(int)WSClientEvents::onLostConnection].push_back({EngineScope::currentEngine(), script::Global<Function>(func)});
|
|
else {
|
|
LOG_ERROR_WITH_SCRIPT_INFO("WSClient Event \"" + event + "\" No Found!\n");
|
|
}
|
|
}
|
|
|
|
Local<Value> WSClientClass::getStatus() {
|
|
try {
|
|
return Number::newNumber((int)ws->GetStatus());
|
|
} catch (const std::runtime_error& e) {
|
|
return Local<Value>();
|
|
}
|
|
CATCH("Fail in getStatus!");
|
|
}
|
|
|
|
Local<Value> WSClientClass::connect(const Arguments& args) {
|
|
CHECK_ARGS_COUNT(args, 1);
|
|
CHECK_ARG_TYPE(args[0], ValueKind::kString);
|
|
// if (args.size() > 1 && args[1].isFunction())
|
|
// return connectAsync(args);
|
|
try {
|
|
|
|
string target = args[0].toStr();
|
|
RecordOperation(ENGINE_OWN_DATA()->pluginName, "ConnectToWebsocketServer", target);
|
|
ws->Connect(target);
|
|
return Boolean::newBoolean(true);
|
|
} catch (const std::runtime_error& e) {
|
|
return Boolean::newBoolean(false);
|
|
}
|
|
CATCH("Fail in connect!");
|
|
}
|
|
|
|
// 异步连接ws客户端
|
|
Local<Value> WSClientClass::connectAsync(const Arguments& args) {
|
|
CHECK_ARGS_COUNT(args, 2);
|
|
CHECK_ARG_TYPE(args[0], ValueKind::kString);
|
|
CHECK_ARG_TYPE(args[1], ValueKind::kFunction);
|
|
|
|
try {
|
|
string target = args[0].toStr();
|
|
RecordOperation(ENGINE_OWN_DATA()->pluginName, "ConnectToWebsocketServer", target);
|
|
|
|
script::Global<Function> callbackFunc{args[1].asFunction()};
|
|
thread(
|
|
[ws{this->ws}, target, callback{std::move(callbackFunc)}, engine{EngineScope::currentEngine()}, pluginName{ENGINE_OWN_DATA()->pluginName}]() mutable {
|
|
#ifdef DEBUG
|
|
SetThreadDescription(GetCurrentThread(), L"LLSE Connect WebSocket");
|
|
#endif // DEBUG
|
|
if (!LL::isDebugMode())
|
|
_set_se_translator(seh_exception::TranslateSEHtoCE);
|
|
try {
|
|
bool result = false;
|
|
try {
|
|
ws->Connect(target);
|
|
result = true;
|
|
} catch (const std::runtime_error& e) {
|
|
result = false;
|
|
}
|
|
if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying())
|
|
return;
|
|
EngineScope enter(engine);
|
|
// fix get on empty Global
|
|
if (callback.isEmpty())
|
|
return;
|
|
NewTimeout(callback.get(), {Boolean::newBoolean(result)}, 0);
|
|
} catch (const seh_exception& e) {
|
|
logger.error("SEH Uncaught Exception Detected!\n{}", e.what());
|
|
logger.error("In WSClientClass::connectAsync");
|
|
logger.error("In Plugin: " + pluginName);
|
|
} catch (...) {
|
|
logger.error("WSClientClass::connectAsync Failed!");
|
|
logger.error("Uncaught Exception Detected!");
|
|
logger.error("In Plugin: " + pluginName);
|
|
}
|
|
})
|
|
.detach();
|
|
return Boolean::newBoolean(true);
|
|
} catch (const std::runtime_error& e) {
|
|
return Boolean::newBoolean(false);
|
|
}
|
|
CATCH("Fail in connectAsync!");
|
|
}
|
|
|
|
Local<Value> WSClientClass::send(const Arguments& args) {
|
|
CHECK_ARGS_COUNT(args, 1);
|
|
|
|
try {
|
|
if (args[0].isString())
|
|
ws->SendText(args[0].toStr());
|
|
else if (args[0].isByteBuffer())
|
|
ws->SendBinary((char*)args[0].asByteBuffer().getRawBytes(), args[0].asByteBuffer().byteLength());
|
|
else {
|
|
LOG_WRONG_ARG_TYPE();
|
|
return Local<Value>();
|
|
}
|
|
return Boolean::newBoolean(true);
|
|
} catch (const std::runtime_error& e) {
|
|
return Boolean::newBoolean(false);
|
|
}
|
|
CATCH("Fail in send!");
|
|
}
|
|
|
|
Local<Value> WSClientClass::listen(const Arguments& args) {
|
|
CHECK_ARGS_COUNT(args, 2);
|
|
CHECK_ARG_TYPE(args[0], ValueKind::kString);
|
|
CHECK_ARG_TYPE(args[1], ValueKind::kFunction);
|
|
|
|
try {
|
|
addListener(args[0].toStr(), args[1].asFunction());
|
|
return Boolean::newBoolean(true);
|
|
} catch (const std::runtime_error& e) {
|
|
return Boolean::newBoolean(false);
|
|
}
|
|
CATCH("Fail in listen!");
|
|
}
|
|
|
|
Local<Value> WSClientClass::close(const Arguments& args) {
|
|
try {
|
|
ws->Close();
|
|
return Boolean::newBoolean(true);
|
|
} catch (const std::runtime_error& e) {
|
|
return Boolean::newBoolean(false);
|
|
}
|
|
CATCH("Fail in close!");
|
|
}
|
|
|
|
Local<Value> WSClientClass::shutdown(const Arguments& args) {
|
|
try {
|
|
ws->Shutdown();
|
|
return Boolean::newBoolean(true);
|
|
} catch (const std::runtime_error& e) {
|
|
return Boolean::newBoolean(false);
|
|
}
|
|
CATCH("Fail in shutdown!");
|
|
}
|
|
|
|
Local<Value> WSClientClass::errorCode(const Arguments& args) {
|
|
try {
|
|
return Number::newNumber(WSAGetLastError());
|
|
} catch (const std::runtime_error& e) {
|
|
return Local<Value>();
|
|
}
|
|
CATCH("Fail in errorCode!");
|
|
}
|
|
|
|
//////////////////// Class HttpServer ////////////////////
|
|
|
|
using namespace httplib;
|
|
|
|
#define ADD_CALLBACK(method, path, func) \
|
|
callbacks.emplace(make_pair(path, HttpServerCallback{EngineScope::currentEngine(), script::Global<Function>{func}, HttpRequestType::method, path})); \
|
|
svr->##method##(path.c_str(), [this, engine = EngineScope::currentEngine()](const Request& req, Response& resp) { \
|
|
if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying()) \
|
|
return; \
|
|
auto task = Schedule::nextTick([this, engine, req, &resp] { \
|
|
if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying()) \
|
|
return; \
|
|
EngineScope enter(engine); \
|
|
for (auto& [k, v] : this->callbacks) { \
|
|
if (v.type != HttpRequestType::method) \
|
|
return; \
|
|
std::regex rgx(k); \
|
|
std::smatch matches; \
|
|
if (std::regex_match(req.path, matches, rgx)) { \
|
|
if (matches == req.matches) { \
|
|
auto reqObj = new HttpRequestClass(req); \
|
|
auto respObj = new HttpResponseClass(resp); \
|
|
v.func.get().call({}, reqObj, respObj); \
|
|
resp = *respObj->get(); \
|
|
break; \
|
|
} \
|
|
} \
|
|
} \
|
|
}); \
|
|
while (!task.isFinished()) \
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1)); \
|
|
});
|
|
|
|
HttpServerClass::HttpServerClass(const Local<Object>& scriptObj)
|
|
: ScriptClass(scriptObj)
|
|
, svr(new Server) {
|
|
}
|
|
HttpServerClass::HttpServerClass()
|
|
: ScriptClass(ScriptClass::ConstructFromCpp<HttpServerClass>{})
|
|
, svr(new Server) {
|
|
}
|
|
|
|
HttpServerClass::~HttpServerClass() {
|
|
svr->stop();
|
|
}
|
|
|
|
HttpServerClass* HttpServerClass::constructor(const Arguments& args) {
|
|
return new HttpServerClass(args.thiz());
|
|
}
|
|
|
|
Local<Value> HttpServerClass::onGet(const Arguments& args) {
|
|
CHECK_ARGS_COUNT(args, 2);
|
|
CHECK_ARG_TYPE(args[0], ValueKind::kString);
|
|
CHECK_ARG_TYPE(args[1], ValueKind::kFunction);
|
|
|
|
try {
|
|
auto path = args[0].toStr();
|
|
auto func = args[1].asFunction();
|
|
ADD_CALLBACK(Get, path, func);
|
|
/* for debug
|
|
callbacks.emplace(make_pair(path, HttpServerCallback{EngineScope::currentEngine(), script::Global<Function>{func}, HttpRequestType::Get, path}));
|
|
svr->Get(path.c_str(), [this, engine = EngineScope::currentEngine()](const Request& req, Response& resp) {
|
|
if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying())
|
|
return;
|
|
auto task = Schedule::nextTick([this, engine, req, &resp] {
|
|
if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying())
|
|
return;
|
|
EngineScope enter(engine);
|
|
for (auto& [k, v] : this->callbacks)
|
|
{
|
|
if (v.type != HttpRequestType::Get) return;
|
|
std::regex rgx(k);
|
|
std::smatch matches;
|
|
if (std::regex_match(req.path, matches, rgx))
|
|
{
|
|
if (matches == req.matches)
|
|
{
|
|
auto reqObj = new HttpRequestClass(req);
|
|
auto respObj = new HttpResponseClass(resp);
|
|
v.func.get().call({}, reqObj, respObj);
|
|
resp = *respObj->get();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
while (!task.isFinished())
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
});
|
|
*/
|
|
return this->getScriptObject();
|
|
}
|
|
CATCH("Fail in onGet")
|
|
}
|
|
|
|
Local<Value> HttpServerClass::onPut(const Arguments& args) {
|
|
CHECK_ARGS_COUNT(args, 2);
|
|
CHECK_ARG_TYPE(args[0], ValueKind::kString);
|
|
CHECK_ARG_TYPE(args[1], ValueKind::kFunction);
|
|
|
|
try {
|
|
auto path = args[0].toStr();
|
|
auto func = args[1].asFunction();
|
|
ADD_CALLBACK(Put, path, func);
|
|
return this->getScriptObject();
|
|
}
|
|
CATCH("Fail in onPut!");
|
|
}
|
|
|
|
Local<Value> HttpServerClass::onPost(const Arguments& args) {
|
|
CHECK_ARGS_COUNT(args, 2);
|
|
CHECK_ARG_TYPE(args[0], ValueKind::kString);
|
|
CHECK_ARG_TYPE(args[1], ValueKind::kFunction);
|
|
|
|
try {
|
|
auto path = args[0].toStr();
|
|
auto func = args[1].asFunction();
|
|
ADD_CALLBACK(Post, path, func);
|
|
return this->getScriptObject();
|
|
}
|
|
CATCH("Fail in onPost!");
|
|
}
|
|
|
|
Local<Value> HttpServerClass::onPatch(const Arguments& args) {
|
|
CHECK_ARGS_COUNT(args, 2);
|
|
CHECK_ARG_TYPE(args[0], ValueKind::kString);
|
|
CHECK_ARG_TYPE(args[1], ValueKind::kFunction);
|
|
|
|
try {
|
|
auto path = args[0].toStr();
|
|
auto func = args[1].asFunction();
|
|
ADD_CALLBACK(Patch, path, func);
|
|
return this->getScriptObject();
|
|
}
|
|
CATCH("Fail in onPatch!");
|
|
}
|
|
|
|
Local<Value> HttpServerClass::onDelete(const Arguments& args) {
|
|
CHECK_ARGS_COUNT(args, 2);
|
|
CHECK_ARG_TYPE(args[0], ValueKind::kString);
|
|
CHECK_ARG_TYPE(args[1], ValueKind::kFunction);
|
|
|
|
try {
|
|
auto path = args[0].toStr();
|
|
auto func = args[1].asFunction();
|
|
ADD_CALLBACK(Delete, path, func);
|
|
return this->getScriptObject();
|
|
}
|
|
CATCH("Fail in onDelete!");
|
|
}
|
|
|
|
Local<Value> HttpServerClass::onOptions(const Arguments& args) {
|
|
CHECK_ARGS_COUNT(args, 2);
|
|
CHECK_ARG_TYPE(args[0], ValueKind::kString);
|
|
CHECK_ARG_TYPE(args[1], ValueKind::kFunction);
|
|
|
|
try {
|
|
auto path = args[0].toStr();
|
|
auto func = args[1].asFunction();
|
|
ADD_CALLBACK(Options, path, func);
|
|
return this->getScriptObject();
|
|
}
|
|
CATCH("Fail in onOptions!");
|
|
}
|
|
|
|
|
|
Local<Value> HttpServerClass::onPreRouting(const Arguments& args) {
|
|
CHECK_ARGS_COUNT(args, 1);
|
|
CHECK_ARG_TYPE(args[0], ValueKind::kFunction);
|
|
|
|
try {
|
|
preRoutingCallback = {EngineScope::currentEngine(), script::Global{args[0].asFunction()}};
|
|
svr->set_pre_routing_handler([this, engine = EngineScope::currentEngine()](const Request& req, Response& resp) {
|
|
if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying())
|
|
return Server::HandlerResponse::Unhandled;
|
|
bool handled = false;
|
|
auto task = Schedule::nextTick([this, engine, req, &resp, &handled] {
|
|
if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying())
|
|
return;
|
|
EngineScope enter(engine);
|
|
auto reqObj = new HttpRequestClass(req);
|
|
auto respObj = new HttpResponseClass(resp);
|
|
auto res = this->preRoutingCallback.func.get().call({}, reqObj, respObj);
|
|
if (res.isBoolean() && res.asBoolean().value() == false) {
|
|
handled = true;
|
|
}
|
|
resp = *respObj->get();
|
|
});
|
|
while (!task.isFinished())
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
return handled ? Server::HandlerResponse::Handled : Server::HandlerResponse::Unhandled;
|
|
});
|
|
return this->getScriptObject();
|
|
}
|
|
CATCH("Fail in onPreRouting!");
|
|
}
|
|
|
|
Local<Value> HttpServerClass::onPostRouting(const Arguments& args) {
|
|
CHECK_ARGS_COUNT(args, 1);
|
|
CHECK_ARG_TYPE(args[0], ValueKind::kFunction);
|
|
|
|
try {
|
|
postRoutingCallback = {EngineScope::currentEngine(), script::Global{args[0].asFunction()}};
|
|
svr->set_post_routing_handler([this, engine = EngineScope::currentEngine()](const Request& req, Response& resp) {
|
|
if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying())
|
|
return;
|
|
auto task = Schedule::nextTick([this, engine, req, &resp] {
|
|
if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying())
|
|
return;
|
|
EngineScope enter(engine);
|
|
auto reqObj = new HttpRequestClass(req);
|
|
auto respObj = new HttpResponseClass(resp);
|
|
this->postRoutingCallback.func.get().call({}, reqObj, respObj);
|
|
resp = *respObj->get();
|
|
});
|
|
while (!task.isFinished())
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
});
|
|
return this->getScriptObject();
|
|
}
|
|
CATCH("Fail in onPostRouting!");
|
|
}
|
|
|
|
Local<Value> HttpServerClass::onError(const Arguments& args) {
|
|
CHECK_ARGS_COUNT(args, 1);
|
|
CHECK_ARG_TYPE(args[0], ValueKind::kFunction);
|
|
|
|
try {
|
|
errorCallback = {EngineScope::currentEngine(), script::Global{args[0].asFunction()}};
|
|
svr->set_error_handler([this, engine = EngineScope::currentEngine()](const Request& req, Response& resp) {
|
|
if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying())
|
|
return;
|
|
auto task = Schedule::nextTick([this, engine, req, &resp] {
|
|
if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying())
|
|
return;
|
|
EngineScope enter(engine);
|
|
auto reqObj = new HttpRequestClass(req);
|
|
auto respObj = new HttpResponseClass(resp);
|
|
this->errorCallback.func.get().call({}, reqObj, respObj);
|
|
resp = *respObj->get();
|
|
});
|
|
while (!task.isFinished())
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
});
|
|
return this->getScriptObject();
|
|
}
|
|
CATCH("Fail in onError!");
|
|
}
|
|
|
|
Local<Value> HttpServerClass::onException(const Arguments& args) {
|
|
CHECK_ARGS_COUNT(args, 1);
|
|
CHECK_ARG_TYPE(args[0], ValueKind::kFunction);
|
|
|
|
try {
|
|
exceptionCallback = {EngineScope::currentEngine(), script::Global{args[0].asFunction()}};
|
|
svr->set_exception_handler([this, engine = EngineScope::currentEngine()](const Request& req, Response& resp, std::exception_ptr e) {
|
|
if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying())
|
|
return;
|
|
auto task = Schedule::nextTick([this, engine, req, &resp, e] {
|
|
if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying())
|
|
return;
|
|
EngineScope enter(engine);
|
|
auto reqObj = new HttpRequestClass(req);
|
|
auto respObj = new HttpResponseClass(resp);
|
|
try {
|
|
if (e) {
|
|
std::rethrow_exception(e);
|
|
}
|
|
} catch(const std::exception& exp) {
|
|
this->exceptionCallback.func.get().call({}, reqObj, respObj, String::newString(exp.what()));
|
|
}
|
|
resp = *respObj->get();
|
|
});
|
|
while (!task.isFinished())
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
});
|
|
return this->getScriptObject();
|
|
}
|
|
CATCH("Fail in onException!");
|
|
}
|
|
|
|
Local<Value> HttpServerClass::listen(const Arguments& args) {
|
|
if (args.size() == 1) {
|
|
CHECK_ARG_TYPE(args[0], ValueKind::kNumber);
|
|
} else if (args.size() == 2) {
|
|
CHECK_ARG_TYPE(args[0], ValueKind::kString);
|
|
CHECK_ARG_TYPE(args[1], ValueKind::kNumber);
|
|
} else {
|
|
LOG_WRONG_ARG_TYPE();
|
|
return Local<Value>();
|
|
}
|
|
|
|
try {
|
|
string addr = "127.0.0.1";
|
|
int port = 80;
|
|
if (args.size() == 2) {
|
|
addr = args[0].toStr();
|
|
port = args[1].asNumber().toInt32();
|
|
} else {
|
|
port = args[0].asNumber().toInt32();
|
|
}
|
|
if (port < 0 || port > 65535) {
|
|
throw script::Exception("Invalid port number! (0~65535)");
|
|
}
|
|
|
|
RecordOperation(ENGINE_OWN_DATA()->pluginName, "StartHttpServer", fmt::format("on {}:{}", addr, port));
|
|
|
|
thread th([this](string addr, int port) {
|
|
svr->listen(addr.c_str(), port);
|
|
},
|
|
addr, port);
|
|
th.detach();
|
|
return this->getScriptObject(); // return self
|
|
}
|
|
CATCH("Fail in listen!");
|
|
}
|
|
|
|
Local<Value> HttpServerClass::stop(const Arguments& args) {
|
|
CHECK_ARGS_COUNT(args, 0);
|
|
|
|
try {
|
|
RecordOperation(ENGINE_OWN_DATA()->pluginName, "StopHttpServer", "");
|
|
svr->stop();
|
|
return Local<Value>();
|
|
}
|
|
CATCH("Fail in stop!");
|
|
}
|
|
|
|
Local<Value> HttpServerClass::isRunning(const Arguments& args) {
|
|
CHECK_ARGS_COUNT(args, 0);
|
|
|
|
try {
|
|
return Boolean::newBoolean(svr->is_running());
|
|
}
|
|
CATCH("Fail in isRunning!");
|
|
}
|
|
|
|
Local<Object> Headers2Object(const Headers& headers) {
|
|
auto obj = Object::newObject();
|
|
for (auto& header : headers) {
|
|
obj.set(header.first, Array::newArray());
|
|
}
|
|
for (auto& header : headers) {
|
|
auto arr = obj.get(header.first).asArray();
|
|
arr.add(String::newString(header.second));
|
|
obj.set(header.first, arr);
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
Local<Object> Params2Object(const Params& params) {
|
|
auto obj = Object::newObject();
|
|
for (auto& param : params) {
|
|
if (params.count(param.first) == 1)
|
|
obj.set(param.first, param.second);
|
|
else {
|
|
if (!obj.has(param.first)) {
|
|
obj.set(param.first, Array::newArray());
|
|
}
|
|
auto arr = obj.get(param.first).asArray();
|
|
arr.add(String::newString(param.second));
|
|
obj.set(param.first, arr);
|
|
}
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
HttpRequestClass::HttpRequestClass(const Local<Object>& scriptObj, const Request& req)
|
|
: ScriptClass(scriptObj)
|
|
, req(new Request(req)) {
|
|
}
|
|
HttpRequestClass::HttpRequestClass(const Request& req)
|
|
: ScriptClass(ScriptClass::ConstructFromCpp<HttpRequestClass>{})
|
|
, req(new Request(req)) {
|
|
}
|
|
|
|
std::shared_ptr<Request> HttpRequestClass::get() {
|
|
return req;
|
|
}
|
|
|
|
Local<Value> HttpRequestClass::getHeaders() {
|
|
try {
|
|
return Headers2Object(req->headers);
|
|
}
|
|
CATCH("Fail in getHeaders!");
|
|
}
|
|
|
|
Local<Value> HttpRequestClass::getHeader(const Arguments& args) {
|
|
CHECK_ARGS_COUNT(args, 1);
|
|
CHECK_ARG_TYPE(args[0], ValueKind::kString);
|
|
|
|
try {
|
|
auto key = args[0].toStr();
|
|
if (req->headers.count(key) == 0)
|
|
return Array::newArray();
|
|
auto value = req->headers.equal_range(key);
|
|
auto arr = Array::newArray();
|
|
for (auto it = value.first; it != value.second; ++it) {
|
|
arr.add(String::newString(it->second));
|
|
}
|
|
return arr;
|
|
}
|
|
CATCH("Fail in getHeader!");
|
|
}
|
|
|
|
Local<Value> HttpRequestClass::getBody() {
|
|
try {
|
|
return String::newString(req->body);
|
|
}
|
|
CATCH("Fail in getBody!");
|
|
}
|
|
|
|
Local<Value> HttpRequestClass::getMethod() {
|
|
try {
|
|
return String::newString(req->method);
|
|
}
|
|
CATCH("Fail in getMethod!");
|
|
}
|
|
|
|
Local<Value> HttpRequestClass::getPath() {
|
|
try {
|
|
return String::newString(req->path);
|
|
}
|
|
CATCH("Fail in getPath!");
|
|
}
|
|
|
|
Local<Value> HttpRequestClass::getParams() {
|
|
try {
|
|
return Params2Object(req->params);
|
|
}
|
|
CATCH("Fail in getParams!");
|
|
}
|
|
|
|
Local<Value> HttpRequestClass::getRemoteAddr() {
|
|
try {
|
|
return String::newString(req->remote_addr);
|
|
}
|
|
CATCH("Fail in getRemoteAddr!");
|
|
}
|
|
|
|
Local<Value> HttpRequestClass::getRemotePort() {
|
|
try {
|
|
return Number::newNumber(req->remote_port);
|
|
}
|
|
CATCH("Fail in getRemotePort!");
|
|
}
|
|
|
|
|
|
Local<Value> HttpRequestClass::getVersion() {
|
|
try {
|
|
return String::newString(req->version);
|
|
}
|
|
CATCH("Fail in getVersion!");
|
|
}
|
|
|
|
Local<Value> HttpRequestClass::getRegexMatches() {
|
|
try {
|
|
auto smt = req->matches;
|
|
auto arr = Array::newArray();
|
|
for (auto& match : smt) {
|
|
arr.add(String::newString(match));
|
|
}
|
|
return arr;
|
|
}
|
|
CATCH("Fail in getRegexMatches!");
|
|
}
|
|
|
|
HttpResponseClass::HttpResponseClass(const Local<Object>& scriptObj, const Response& resp)
|
|
: ScriptClass(scriptObj)
|
|
, resp(new Response(resp)) {
|
|
}
|
|
HttpResponseClass::HttpResponseClass(const Response& resp)
|
|
: ScriptClass(ScriptClass::ConstructFromCpp<HttpResponseClass>{})
|
|
, resp(new Response(resp)) {
|
|
}
|
|
|
|
std::shared_ptr<Response> HttpResponseClass::get() {
|
|
return resp;
|
|
}
|
|
|
|
Local<Value> HttpResponseClass::setHeader(const Arguments& args) {
|
|
CHECK_ARGS_COUNT(args, 2);
|
|
CHECK_ARG_TYPE(args[0], ValueKind::kString);
|
|
|
|
try {
|
|
auto key = args[0].toStr();
|
|
string value;
|
|
if (args[1].isString())
|
|
value = args[1].toStr();
|
|
else
|
|
value = ValueToString(args[1]);
|
|
resp->headers.insert(make_pair(key, value));
|
|
return this->getScriptObject(); // return self
|
|
}
|
|
CATCH("Fail in setHeader!");
|
|
}
|
|
|
|
Local<Value> HttpResponseClass::getHeader(const Arguments& args) {
|
|
CHECK_ARGS_COUNT(args, 1);
|
|
CHECK_ARG_TYPE(args[0], ValueKind::kString);
|
|
|
|
try {
|
|
auto key = args[0].toStr();
|
|
if (resp->headers.count(key) == 0)
|
|
return Array::newArray();
|
|
auto value = resp->headers.equal_range(key);
|
|
auto arr = Array::newArray();
|
|
for (auto it = value.first; it != value.second; ++it) {
|
|
arr.add(String::newString(it->second));
|
|
}
|
|
return arr;
|
|
}
|
|
CATCH("Fail in getHeader!");
|
|
}
|
|
|
|
Local<Value> HttpResponseClass::write(const Arguments& args) {
|
|
try {
|
|
for (size_t i = 0ULL, mEnd = args.size(); i < mEnd; ++i) {
|
|
resp->body += ValueToString(args[i]);
|
|
}
|
|
return this->getScriptObject();
|
|
}
|
|
CATCH("Fail in write!");
|
|
}
|
|
|
|
void HttpResponseClass::setHeaders(const Local<Value>& headers) {
|
|
CHECK_ARG_TYPE_S(headers, ValueKind::kObject);
|
|
|
|
try {
|
|
auto keys = headers.asObject().getKeys();
|
|
for (auto& key : keys) {
|
|
auto key_str = key.toString();
|
|
auto value = headers.asObject().get(key);
|
|
if (value.isArray()) {
|
|
for (size_t i = 0ULL; i < value.asArray().size(); ++i) {
|
|
resp->headers.insert(make_pair(key_str, ValueToString(value.asArray().get(i))));
|
|
}
|
|
return;
|
|
}
|
|
auto val_str = ValueToString(value);
|
|
resp->headers.insert(make_pair(key_str, val_str));
|
|
}
|
|
}
|
|
CATCH_S("Fail in setHeaders!");
|
|
}
|
|
|
|
void HttpResponseClass::setStatus(const Local<Value>& status) {
|
|
CHECK_ARG_TYPE_S(status, ValueKind::kNumber);
|
|
|
|
try {
|
|
resp->status = status.toInt();
|
|
}
|
|
CATCH_S("Fail in setStatus!");
|
|
}
|
|
|
|
void HttpResponseClass::setBody(const Local<Value>& body) {
|
|
CHECK_ARG_TYPE_S(body, ValueKind::kString);
|
|
|
|
try {
|
|
resp->body = body.toStr();
|
|
}
|
|
CATCH_S("Fail in setBody!");
|
|
}
|
|
|
|
void HttpResponseClass::setReason(const Local<Value>& reason) {
|
|
CHECK_ARG_TYPE_S(reason, ValueKind::kString);
|
|
|
|
try {
|
|
resp->reason = reason.toStr();
|
|
}
|
|
CATCH_S("Fail in setReason!");
|
|
}
|
|
|
|
void HttpResponseClass::setVersion(const Local<Value>& version) {
|
|
CHECK_ARG_TYPE_S(version, ValueKind::kString);
|
|
|
|
try {
|
|
resp->version = version.toStr();
|
|
}
|
|
CATCH_S("Fail in setVersion!");
|
|
}
|
|
|
|
Local<Value> HttpResponseClass::getHeaders() {
|
|
try {
|
|
return Headers2Object(resp->headers);
|
|
}
|
|
CATCH("Fail in getHeaders!");
|
|
}
|
|
|
|
Local<Value> HttpResponseClass::getStatus() {
|
|
try {
|
|
return Number::newNumber(resp->status);
|
|
}
|
|
CATCH("Fail in getStatus!");
|
|
}
|
|
|
|
Local<Value> HttpResponseClass::getBody() {
|
|
try {
|
|
return String::newString(resp->body);
|
|
}
|
|
CATCH("Fail in getBody!");
|
|
}
|
|
|
|
Local<Value> HttpResponseClass::getReason() {
|
|
try {
|
|
return String::newString(resp->reason);
|
|
}
|
|
CATCH("Fail in getReason!");
|
|
}
|
|
|
|
Local<Value> HttpResponseClass::getVersion() {
|
|
try {
|
|
return String::newString(resp->version);
|
|
}
|
|
CATCH("Fail in getVersion!");
|
|
}
|
|
|
|
//////////////////// APIs ////////////////////
|
|
|
|
Local<Value> NetworkClass::httpGet(const Arguments& args) {
|
|
CHECK_ARGS_COUNT(args, 2);
|
|
CHECK_ARG_TYPE(args[0], ValueKind::kString);
|
|
if (args.size() > 2) {
|
|
CHECK_ARG_TYPE(args[1], ValueKind::kObject);
|
|
CHECK_ARG_TYPE(args[2], ValueKind::kFunction);
|
|
} else {
|
|
CHECK_ARG_TYPE(args[1], ValueKind::kFunction);
|
|
}
|
|
|
|
try {
|
|
string target = args[0].toStr();
|
|
RecordOperation(ENGINE_OWN_DATA()->pluginName, "HttpGet", target);
|
|
script::Global<Function> callbackFunc{args[args.size() - 1].asFunction()};
|
|
|
|
auto lambda = [callback{std::move(callbackFunc)}, engine{EngineScope::currentEngine()}](int status, string body) {
|
|
if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying())
|
|
return;
|
|
|
|
EngineScope scope(engine);
|
|
try {
|
|
NewTimeout(callback.get(), {Number::newNumber(status), String::newString(body)}, 1);
|
|
// callback.get().call({}, {Number::newNumber(status), String::newString(body)});
|
|
}
|
|
CATCH_IN_CALLBACK("HttpGet")
|
|
};
|
|
if (args.size() > 2) {
|
|
httplib::Headers maps;
|
|
auto obj = args[1].asObject();
|
|
auto keys = obj.getKeyNames();
|
|
if (keys.size() > 0) {
|
|
for (size_t i = 0ULL, mEnd = keys.size(); i < mEnd; ++i) {
|
|
maps.insert({keys[i], obj.get(keys[i]).toStr()});
|
|
}
|
|
}
|
|
return Boolean::newBoolean(HttpGet(target, maps, lambda));
|
|
}
|
|
return Boolean::newBoolean(HttpGet(target, lambda));
|
|
}
|
|
CATCH("Fail in HttpGet");
|
|
}
|
|
|
|
Local<Value> NetworkClass::httpPost(const Arguments& args) {
|
|
CHECK_ARGS_COUNT(args, 4);
|
|
CHECK_ARG_TYPE(args[0], ValueKind::kString);
|
|
CHECK_ARG_TYPE(args[2], ValueKind::kString);
|
|
if (args.size() > 4) {
|
|
CHECK_ARG_TYPE(args[1], ValueKind::kObject);
|
|
CHECK_ARG_TYPE(args[3], ValueKind::kString);
|
|
CHECK_ARG_TYPE(args[4], ValueKind::kFunction);
|
|
} else {
|
|
CHECK_ARG_TYPE(args[1], ValueKind::kString);
|
|
CHECK_ARG_TYPE(args[3], ValueKind::kFunction);
|
|
}
|
|
|
|
|
|
try {
|
|
string target = args[0].toStr();
|
|
RecordOperation(ENGINE_OWN_DATA()->pluginName, "HttpPost", target);
|
|
script::Global<Function> callbackFunc{args[args.size() - 1].asFunction()};
|
|
|
|
auto lambda = [callback{std::move(callbackFunc)}, engine{EngineScope::currentEngine()}](int status, string body) {
|
|
if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying())
|
|
return;
|
|
|
|
EngineScope scope(engine);
|
|
try {
|
|
NewTimeout(callback.get(), {Number::newNumber(status), String::newString(body)}, 1);
|
|
// callback.get().call({}, {Number::newNumber(status), String::newString(body)});
|
|
}
|
|
CATCH_IN_CALLBACK("HttpPost")
|
|
};
|
|
if (args.size() > 4) {
|
|
httplib::Headers maps;
|
|
auto obj = args[1].asObject();
|
|
auto keys = obj.getKeyNames();
|
|
if (keys.size() > 0) {
|
|
for (size_t i = 0ULL, mEnd = keys.size(); i < mEnd; ++i) {
|
|
maps.insert({keys[i], obj.get(keys[i]).toStr()});
|
|
}
|
|
}
|
|
|
|
return Boolean::newBoolean(HttpPost(target, maps, args[2].toStr(), args[3].toStr(), lambda));
|
|
}
|
|
return Boolean::newBoolean(HttpPost(target, args[1].toStr(), args[2].toStr(), lambda));
|
|
}
|
|
CATCH("Fail in HttpPost");
|
|
}
|
|
|
|
Local<Value> NetworkClass::httpGetSync(const Arguments& args) {
|
|
CHECK_ARGS_COUNT(args, 1);
|
|
CHECK_ARG_TYPE(args[0], ValueKind::kString);
|
|
|
|
try {
|
|
string target = args[0].toStr();
|
|
RecordOperation(ENGINE_OWN_DATA()->pluginName, "HttpGetSync", target);
|
|
|
|
int status;
|
|
string result;
|
|
HttpGetSync(target, &status, &result);
|
|
Local<Object> res = Object::newObject();
|
|
res.set("status", status);
|
|
res.set("data", result);
|
|
return res;
|
|
}
|
|
CATCH("Fail in HttpGetSync");
|
|
}
|
|
|
|
|
|
// For compatibility
|
|
Local<Value> NetworkClass::newWebSocket(const Arguments& args) {
|
|
auto newp = new WSClientClass();
|
|
return newp->getScriptObject();
|
|
}
|
|
|
|
Local<Object> WSClientClass::newWSClient() {
|
|
auto newp = new WSClientClass();
|
|
return newp->getScriptObject();
|
|
} |