From 3b2f825475328290896bfc55a6ae58aa6544d099 Mon Sep 17 00:00:00 2001 From: fonwin Date: Thu, 23 Aug 2018 14:38:11 +0800 Subject: [PATCH] Add IoManager, HttpParser. --- Overview/Manage.md | 5 + TODO.md | 13 +- build/vs2015/Fon9Co/Fon9Co.vcxproj | 4 + build/vs2015/Fon9Co/Fon9Co.vcxproj.filters | 8 + build/vs2015/libfon9/libfon9.vcxproj | 7 + build/vs2015/libfon9/libfon9.vcxproj.filters | 27 ++ fon9/CMakeLists.txt | 7 +- fon9/ConfigParser.cpp | 2 +- fon9/ConsoleIO.cpp | 12 +- fon9/ConsoleIO.hpp | 6 - fon9/StrTools.cpp | 32 ++ fon9/StrTools.hpp | 7 + fon9/auth/PolicyMaster.hpp | 8 +- fon9/auth/PolicyTable.hpp | 1 - fon9/auth/PolicyTree.cpp | 8 +- fon9/framework/Fon9Co.cpp | 75 +++- fon9/framework/HttpManSession.cpp | 114 +++++ fon9/framework/HttpManSession.hpp | 35 ++ fon9/framework/IoFactory.hpp | 78 ++++ fon9/framework/IoFactoryTcpClient.cpp | 34 ++ fon9/framework/IoFactoryTcpServer.cpp | 34 ++ fon9/framework/IoManager.cpp | 434 +++++++++++++++++++ fon9/framework/IoManager.hpp | 151 +++++++ fon9/framework/SeedSession.hpp | 3 + fon9/io/Device.cpp | 6 +- fon9/io/DeviceParseConfig.hpp | 2 +- fon9/io/FdrService.hpp | 2 +- fon9/io/IoBase.hpp | 41 +- fon9/io/IoServiceArgs.cpp | 9 - fon9/io/IoServiceArgs.hpp | 4 + fon9/io/Server.cpp | 42 +- fon9/io/Server.hpp | 31 +- fon9/io/SimpleManager.hpp | 24 +- fon9/io/TcpClientBase.cpp | 6 +- fon9/io/win/IocpTcpServer.cpp | 2 + fon9/seed/NamedPark.hpp | 7 +- fon9/seed/SeedFairy.cpp | 2 +- fon9/seed/SeedFairy.hpp | 5 +- fon9/seed/TreeLockContainerT.hpp | 23 +- fon9/seed/TreeOp.hpp | 44 +- fon9/web/HttpParser.cpp | 199 +++++++++ fon9/web/HttpParser.hpp | 152 +++++++ 42 files changed, 1567 insertions(+), 139 deletions(-) create mode 100644 fon9/framework/HttpManSession.cpp create mode 100644 fon9/framework/HttpManSession.hpp create mode 100644 fon9/framework/IoFactory.hpp create mode 100644 fon9/framework/IoFactoryTcpClient.cpp create mode 100644 fon9/framework/IoFactoryTcpServer.cpp create mode 100644 fon9/framework/IoManager.cpp create mode 100644 fon9/framework/IoManager.hpp create mode 100644 fon9/web/HttpParser.cpp create mode 100644 fon9/web/HttpParser.hpp diff --git a/Overview/Manage.md b/Overview/Manage.md index 4bad45e..364aa8f 100644 --- a/Overview/Manage.md +++ b/Overview/Manage.md @@ -51,6 +51,11 @@ --------------------------------------- ## 通訊管理 +* IoManager + * 那些東西有記錄 log? + * DeviceState/dtor: 使用 TRACE 紀錄. + * SessionState: 使用 INFO 紀錄. + * Id 可為任意 ASCII 字串. --------------------------------------- diff --git a/TODO.md b/TODO.md index 2a49fcf..f040c49 100644 --- a/TODO.md +++ b/TODO.md @@ -5,6 +5,8 @@ libfon9 TODO list ## 演算法/容器 --------------------------------------- ## 雜項 +* DefaultThreadPoolArgs: ThreadCount_, CpuAffinity_; + --------------------------------------- ## 檔案/儲存/載入 * Serialize/Deserialize @@ -23,12 +25,21 @@ libfon9 TODO list * Fon9Co * Linux: to daemon. * Windows: install to service. + * 啟動時參數指定預設的 LogLevel. +* SeedSession/SeedFairy/SeedSearcher: 還需要再整理其中的關連(並重新命名). --------------------------------------- ## 通訊基礎建設 -* IoManager/SessionFactory/DeviceFactory/FactoryPark +* IoManager/SessionFactory/DeviceFactory + * 提供 IoManager 的使用說明. + * Sch 設定排程時間. + * 註冊 Factory 異動事件, 讓稍晚註冊的 Factory 可以建立 Device. + * NeedsApply + * HttpMan: ip白名單、黑名單? * UDP/Multicast * FileDevice * TLS +* Device.DeviceCommand() 傳回值: success+message or fail+message. +* DeviceId 拿掉開頭的 '|' 字元. --------------------------------------- ## Simple FIX engine diff --git a/build/vs2015/Fon9Co/Fon9Co.vcxproj b/build/vs2015/Fon9Co/Fon9Co.vcxproj index 006a7b1..58a569d 100644 --- a/build/vs2015/Fon9Co/Fon9Co.vcxproj +++ b/build/vs2015/Fon9Co/Fon9Co.vcxproj @@ -80,6 +80,10 @@ + + + + diff --git a/build/vs2015/Fon9Co/Fon9Co.vcxproj.filters b/build/vs2015/Fon9Co/Fon9Co.vcxproj.filters index a2a8b8f..61425f3 100644 --- a/build/vs2015/Fon9Co/Fon9Co.vcxproj.filters +++ b/build/vs2015/Fon9Co/Fon9Co.vcxproj.filters @@ -17,5 +17,13 @@ Source Files + + Source Files + + + + + Header Files + \ No newline at end of file diff --git a/build/vs2015/libfon9/libfon9.vcxproj b/build/vs2015/libfon9/libfon9.vcxproj index adc9761..cb653a7 100644 --- a/build/vs2015/libfon9/libfon9.vcxproj +++ b/build/vs2015/libfon9/libfon9.vcxproj @@ -128,6 +128,8 @@ + + @@ -246,6 +248,7 @@ + @@ -294,6 +297,9 @@ + + + @@ -365,6 +371,7 @@ + diff --git a/build/vs2015/libfon9/libfon9.vcxproj.filters b/build/vs2015/libfon9/libfon9.vcxproj.filters index 0da6b05..6bd7af3 100644 --- a/build/vs2015/libfon9/libfon9.vcxproj.filters +++ b/build/vs2015/libfon9/libfon9.vcxproj.filters @@ -144,6 +144,12 @@ {b380d7de-1187-4b29-9910-0a3993f8b45e} + + {8aec7ba0-b31e-415b-9e7a-d7573ca64ba3} + + + {e3920260-e31f-4599-9e20-53816d652f0f} + @@ -665,6 +671,15 @@ Header Files\_base\_Tools / Utility + + Header Files\framework + + + Header Files\framework + + + Header Files\web + @@ -1003,5 +1018,17 @@ Source Files + + Source Files\framework + + + Source Files\framework + + + Source Files\framework + + + Source Files\web + \ No newline at end of file diff --git a/fon9/CMakeLists.txt b/fon9/CMakeLists.txt index 80b8520..e783092 100644 --- a/fon9/CMakeLists.txt +++ b/fon9/CMakeLists.txt @@ -77,6 +77,8 @@ set(fon9src io/FdrTcpClient.cpp io/FdrTcpServer.cpp + web/HttpParser.cpp + seed/SeedBase.cpp seed/Tab.cpp seed/Layout.cpp @@ -112,6 +114,9 @@ set(fon9src framework/Framework.cpp framework/SeedSession.cpp + framework/IoManager.cpp + framework/IoFactoryTcpClient.cpp + framework/IoFactoryTcpServer.cpp ) add_library(fon9_s STATIC ${fon9src}) target_link_libraries(fon9_s pthread rt) @@ -119,7 +124,7 @@ target_link_libraries(fon9_s pthread rt) add_library(fon9 SHARED ${fon9src}) target_link_libraries(fon9 pthread rt dl) -add_executable(Fon9Co framework/Fon9Co.cpp) +add_executable(Fon9Co framework/Fon9Co.cpp framework/HttpManSession.cpp) target_link_libraries(Fon9Co pthread fon9) # unit tests: Tools / Utility diff --git a/fon9/ConfigParser.cpp b/fon9/ConfigParser.cpp index d155a8e..dc9a7be 100644 --- a/fon9/ConfigParser.cpp +++ b/fon9/ConfigParser.cpp @@ -55,7 +55,7 @@ fon9_API void RevPrint(RevBuffer& rbuf, const ConfigParser::ErrorEventArgs& e) { } bool ConfigParserMsg::OnErrorBreak(ErrorEventArgs& e) { - RevPrint(this->RBuf_, "err=", e, '\n'); + RevPrint(this->RBuf_, "|err=", e); return false; } diff --git a/fon9/ConsoleIO.cpp b/fon9/ConsoleIO.cpp index 759dbf2..3e23456 100644 --- a/fon9/ConsoleIO.cpp +++ b/fon9/ConsoleIO.cpp @@ -4,7 +4,11 @@ #include #include -#ifndef fon9_WINDOWS +#ifdef fon9_WINDOWS +fon9_BEFORE_INCLUDE_STD +#include +fon9_AFTER_INCLUDE_STD +#else #include #include #include @@ -12,7 +16,11 @@ namespace fon9 { -#ifndef fon9_WINDOWS +#ifdef fon9_WINDOWS +fon9_API int getch() { + return _isatty(_fileno(stdin)) ? _getch() : fgetc(stdin); +} +#else fon9_API int getch() { struct termios cur; memset(&cur, 0, sizeof(cur)); diff --git a/fon9/ConsoleIO.hpp b/fon9/ConsoleIO.hpp index 3d83798..fb7b972 100644 --- a/fon9/ConsoleIO.hpp +++ b/fon9/ConsoleIO.hpp @@ -12,13 +12,7 @@ namespace fon9 { -#ifdef fon9_WINDOWS -inline int getch() { - return ::_getch(); -} -#else extern fon9_API int getch(); -#endif /// \ingroup Misc /// 要求使用者從 console 輸入密碼. diff --git a/fon9/StrTools.cpp b/fon9/StrTools.cpp index e857cc5..b0fb33d 100644 --- a/fon9/StrTools.cpp +++ b/fon9/StrTools.cpp @@ -55,6 +55,38 @@ const char Digs0099[] = //--------------------------------------------------------------------------// +fon9_API bool iequals(StrView a, StrView r) { + auto sz = a.size(); + if (sz != r.size()) + return false; + const char* p = r.begin(); + for (char ch : a) { + if (tolower(ch) != tolower(*p++)) + return false; + } + return true; +} +fon9_API int icompare(const char* a, const char* b, size_t sz) { + while (sz) { + if (int c = tolower(*a++) - tolower(*b++)) + return c; + --sz; + } + return 0; +} +fon9_API int icompare(StrView a, StrView r) { + const size_t lhsSize = a.size(); + const size_t rhsSize = r.size(); + const size_t minSize = (lhsSize < rhsSize ? lhsSize : rhsSize); + if (const int retval = icompare(a.begin(), r.begin(), minSize)) + return retval; + return (lhsSize < rhsSize ? -1 + : lhsSize > rhsSize ? 1 + : 0); +} + +//--------------------------------------------------------------------------// + fon9_API StrView StrView_TruncUTF8(StrView utf8str, size_t expectLen) { if (utf8str.size() <= expectLen) return utf8str; diff --git a/fon9/StrTools.hpp b/fon9/StrTools.hpp index 3d81bce..6f3efe9 100644 --- a/fon9/StrTools.hpp +++ b/fon9/StrTools.hpp @@ -79,6 +79,9 @@ inline char* Put2Digs(char* pout, uint8_t i00_99) { //--------------------------------------------------------------------------// +constexpr int toupper(int ch) { return 'a' <= ch && ch <= 'z' ? (ch - 'a' + 'A') : ch; } +constexpr int tolower(int ch) { return 'A' <= ch && ch <= 'Z' ? (ch - 'A' + 'a') : ch; } + constexpr bool iscntrl(int ch) { return static_cast(ch) <= '\x1f' || ch == '\x7f'; } constexpr bool isprint(int ch) { return '\x20' <= ch && ch <= '\x7e'; } constexpr bool isspace(int ch) { return '\x20' == ch || ('\x9' <= ch && ch <= '\xd'); } @@ -101,6 +104,10 @@ constexpr bool isnotlower(int ch) { return !islower(ch); } constexpr bool isnotalpha(int ch) { return !isalpha(ch); } constexpr bool isnotdigit(int ch) { return !isdigit(ch); } +fon9_API bool iequals(StrView a, StrView r); +fon9_API int icompare(const char* a, const char* b, size_t sz); +fon9_API int icompare(StrView a, StrView r); + //--------------------------------------------------------------------------// /// \ingroup AlNum diff --git a/fon9/auth/PolicyMaster.hpp b/fon9/auth/PolicyMaster.hpp index 1f74616..d2dd686 100644 --- a/fon9/auth/PolicyMaster.hpp +++ b/fon9/auth/PolicyMaster.hpp @@ -167,7 +167,7 @@ class DetailPolicyTreeTable : public DetailPolicyTree { TreeOp(DetailPolicyTree& tree) : base(tree) { } - virtual void GridView(const seed::GridViewRequest& req, seed::FnGridViewOp fnCallback) { + void GridView(const seed::GridViewRequest& req, seed::FnGridViewOp fnCallback) override { seed::GridViewResult res{this->Tree_, req.Tab_}; { DetailTableLocker map{static_cast(&this->Tree_)->DetailTable_}; @@ -183,7 +183,7 @@ class DetailPolicyTreeTable : public DetailPolicyTree { if (op.IsModified_ || isForceWrite) static_cast(&this->Tree_)->WriteUpdated(lockedMap); } - virtual void Get(StrView strKeyText, seed::FnPodOp fnCallback) override { + void Get(StrView strKeyText, seed::FnPodOp fnCallback) override { { DetailTableLocker lockedMap{static_cast(&this->Tree_)->DetailTable_}; auto ifind = this->GetIteratorForPod(*lockedMap, strKeyText); @@ -194,7 +194,7 @@ class DetailPolicyTreeTable : public DetailPolicyTree { } // unlock. fnCallback(seed::PodOpResult{this->Tree_, seed::OpResult::not_found_key, strKeyText}, nullptr); } - virtual void Add(StrView strKeyText, seed::FnPodOp fnCallback) override { + void Add(StrView strKeyText, seed::FnPodOp fnCallback) override { if (this->IsTextBegin(strKeyText) || this->IsTextEnd(strKeyText)) { fnCallback(seed::PodOpResult{this->Tree_, seed::OpResult::not_found_key, strKeyText}, nullptr); return; @@ -210,7 +210,7 @@ class DetailPolicyTreeTable : public DetailPolicyTree { } this->OnPodOp(lockedMap, strKeyText, *ifind, std::move(fnCallback), isForceWrite); } - virtual void Remove(StrView strKeyText, seed::Tab* tab, seed::FnPodRemoved fnCallback) override { + void Remove(StrView strKeyText, seed::Tab* tab, seed::FnPodRemoved fnCallback) override { seed::PodRemoveResult res{this->Tree_, seed::OpResult::not_found_key, strKeyText, tab}; PodKey key{KeyMaker::StrToKey(strKeyText)}; { diff --git a/fon9/auth/PolicyTable.hpp b/fon9/auth/PolicyTable.hpp index 0161d92..1e77fca 100644 --- a/fon9/auth/PolicyTable.hpp +++ b/fon9/auth/PolicyTable.hpp @@ -33,7 +33,6 @@ struct fon9_API PolicyMapsImpl { void WriteUpdated(PolicyItem& rec); }; - using PolicyMaps = MustLock; //--------------------------------------------------------------------------// diff --git a/fon9/auth/PolicyTree.cpp b/fon9/auth/PolicyTree.cpp index b5cb600..f6daa2a 100644 --- a/fon9/auth/PolicyTree.cpp +++ b/fon9/auth/PolicyTree.cpp @@ -62,7 +62,7 @@ struct PolicyTree::TreeOp : public seed::TreeOp { FieldsCellRevPrint(tab->Fields_, seed::SimpleRawRd{**ivalue}, rbuf, seed::GridViewResult::kCellSplitter); RevPrint(rbuf, (*ivalue)->PolicyId_); } - virtual void GridView(const seed::GridViewRequest& req, seed::FnGridViewOp fnCallback) { + void GridView(const seed::GridViewRequest& req, seed::FnGridViewOp fnCallback) override { seed::GridViewResult res{this->Tree_, req.Tab_}; { PolicyMaps::Locker maps{static_cast(&this->Tree_)->PolicyMaps_}; @@ -80,7 +80,7 @@ struct PolicyTree::TreeOp : public seed::TreeOp { maps->WriteUpdated(rec); } } - virtual void Get(StrView strKeyText, seed::FnPodOp fnCallback) override { + void Get(StrView strKeyText, seed::FnPodOp fnCallback) override { { PolicyMaps::Locker maps{static_cast(&this->Tree_)->PolicyMaps_}; auto ifind = this->GetIteratorForPod(maps->ItemMap_, strKeyText); @@ -91,7 +91,7 @@ struct PolicyTree::TreeOp : public seed::TreeOp { } // unlock. fnCallback(seed::PodOpResult{this->Tree_, seed::OpResult::not_found_key, strKeyText}, nullptr); } - virtual void Add(StrView strKeyText, seed::FnPodOp fnCallback) override { + void Add(StrView strKeyText, seed::FnPodOp fnCallback) override { if (this->IsTextBegin(strKeyText) || this->IsTextEnd(strKeyText)) { fnCallback(seed::PodOpResult{this->Tree_, seed::OpResult::not_found_key, strKeyText}, nullptr); return; @@ -106,7 +106,7 @@ struct PolicyTree::TreeOp : public seed::TreeOp { } this->OnPodOp(maps, **ifind, std::move(fnCallback), isForceWrite); } - virtual void Remove(StrView strKeyText, seed::Tab* tab, seed::FnPodRemoved fnCallback) override { + void Remove(StrView strKeyText, seed::Tab* tab, seed::FnPodRemoved fnCallback) override { seed::PodRemoveResult res{this->Tree_, seed::OpResult::not_found_key, strKeyText, tab}; if (static_cast(&this->Tree_)->Delete(strKeyText)) res.OpResult_ = seed::OpResult::removed_pod; diff --git a/fon9/framework/Fon9Co.cpp b/fon9/framework/Fon9Co.cpp index 626d1c8..8a66565 100644 --- a/fon9/framework/Fon9Co.cpp +++ b/fon9/framework/Fon9Co.cpp @@ -1,4 +1,4 @@ -// \file fon9/seed/fon9Co.cpp +// \file fon9/framework/fon9Co.cpp // fon9 console // \author fonwinz@gmail.com #include "fon9/framework/Framework.hpp" @@ -9,7 +9,10 @@ #include "fon9/ConsoleIO.hpp" #include "fon9/CountDownLatch.hpp" #include "fon9/CmdArgs.hpp" -#include "fon9/RevPrint.hpp" +#include "fon9/Log.hpp" + +#include "fon9/framework/HttpManSession.hpp" +#include "fon9/framework/IoManager.hpp" //--------------------------------------------------------------------------// @@ -33,17 +36,25 @@ static const char* AppBreakMsg_ = nullptr; static BOOL WINAPI WindowsCtrlBreakHandler(DWORD dwCtrlType) { switch (dwCtrlType) { case CTRL_C_EVENT: AppBreakMsg_ = ""; break;// Handle the CTRL-C signal. - case CTRL_CLOSE_EVENT: AppBreakMsg_ = ""; break;// CTRL-CLOSE: confirm that the user wants to exit. case CTRL_BREAK_EVENT: AppBreakMsg_ = ""; break;// Pass other signals to the next handler. + case CTRL_CLOSE_EVENT: AppBreakMsg_ = ""; break;// CTRL-CLOSE: confirm that the user wants to exit. case CTRL_LOGOFF_EVENT: AppBreakMsg_ = ""; break; case CTRL_SHUTDOWN_EVENT: AppBreakMsg_ = ""; break; default: AppBreakMsg_ = ""; break; } - FILE* fstdin = nullptr; - freopen_s(&fstdin, "NUL:", "r", stdin); + while(AppBreakMsg_) { + CancelIoEx(GetStdHandle(STD_INPUT_HANDLE), nullptr); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + //GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0); + //FILE* fstdin = nullptr; + //freopen_s(&fstdin, "NUL:", "r", stdin); // 如果遇到 CTRL_CLOSE_EVENT(或其他?) 會卡死在這兒!? return TRUE; } static void SetupCtrlBreakHandler() { + SetConsoleCP(CP_UTF8); + SetConsoleOutputCP(CP_UTF8); SetConsoleCtrlHandler(&WindowsCtrlBreakHandler, TRUE); } #else @@ -51,6 +62,7 @@ static void SetupCtrlBreakHandler() { static void UnixSignalTermHandler(int) { AppBreakMsg_ = ""; if (!freopen("/dev/null", "r", stdin)) { + // Linux 可透過 freopen() 中斷執行中的 fgets(); } } static void SetupCtrlBreakHandler() { @@ -111,12 +123,14 @@ class ConsoleSeedSession : public fon9::SeedSession { this->WritePrompt(&prompt); // TODO: fgets() 可以考慮使用 gnu readline library. if (!fgets(cmdbuf, sizeof(cmdbuf), stdin)) - return State::Broken; + return State::Broken; fon9::StrView cmdln{fon9::StrView_cstr(cmdbuf)}; if (StrTrim(&cmdln).empty()) continue; - if (cmdln == "quit") + if (cmdln == "quit") { + fon9_LOG_IMP("main.quit|user=", this->GetAuthr().GetUserId()); return State::QuitApp; + } if (cmdln == "exit") return State::UserExit; this->FeedLine(cmdln); @@ -171,12 +185,41 @@ int main(int argc, char** argv) { //_CrtSetBreakAlloc(176); #endif + #if defined(NDEBUG) + fon9::LogLevel_ = fon9::LogLevel::Info; + #endif + SetupCtrlBreakHandler(); fon9::Framework fon9sys; fon9sys.Initialize(argc, argv); fon9::auth::PlantScramSha256(*fon9sys.MaAuth_); fon9::auth::PolicyAclAgent::Plant(*fon9sys.MaAuth_); + //------vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv + fon9::IoManagerArgs iomArgs; + iomArgs.Name_ = "MaIo"; + iomArgs.IoServiceCfgstr_ = "ThreadCount=2|Capacity=100"; + iomArgs.DeviceFactoryPark_ = fon9::seed::FetchNamedPark(*fon9sys.Root_, "DeviceFactoryPark"); + iomArgs.DeviceFactoryPark_->Add(fon9::GetIoFactoryTcpClient()); + iomArgs.DeviceFactoryPark_->Add(fon9::GetIoFactoryTcpServer()); + fon9::NamedIoManager* iomgr; + fon9sys.Root_->Add(iomgr = new fon9::NamedIoManager{iomArgs}); + iomgr->GetIoManager().SessionFactoryPark_->Add(fon9::HttpManSession::MakeFactory(fon9sys)); + + fon9::IoConfigItem iocfg; + iocfg.Enabled_ = fon9::EnabledYN::Yes; + //iocfg.Sch_; + iocfg.SessionName_.assign(fon9_kCSTR_HttpManSession); + iocfg.SessionArgs_.assign("$HtmlDir={/} $Seed={/seed}"); + + iocfg.DeviceName_.assign("TcpServer"); + iocfg.DeviceArgs_.assign("6080"); + iomgr->GetIoManager().AddConfig("Id001", iocfg); + + iocfg.DeviceName_.assign("TcpClient"); + iocfg.DeviceArgs_.assign("dn=NoHost:6080"); + iomgr->GetIoManager().AddConfig("Id002", iocfg); + //------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ fon9sys.Start(); // 使用 "--admin" 啟動 AdminMode. @@ -204,12 +247,26 @@ int main(int argc, char** argv) { default: case ConsoleSeedSession::State::Authing: case ConsoleSeedSession::State::Logouting: - case ConsoleSeedSession::State::Broken: // stdin EOF, wait signal for quit app. coSession->Wait(); res = coSession->GetState(); - break;; + break; + case ConsoleSeedSession::State::Broken: + // stdin EOF, sleep() and retry read. + // wait signal for quit app. + // 一旦遇到 EOF, 就需要重新登入? + int c; + do { + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + clearerr(stdin); + c = fgetc(stdin); + } while (c == EOF && AppBreakMsg_ == nullptr); + ungetc(c, stdin); + res = coSession->GetState(); + continue; } } + fon9_LOG_IMP("main.quit|cause=console:", AppBreakMsg_); puts(AppBreakMsg_); + AppBreakMsg_ = nullptr; return 0; } diff --git a/fon9/framework/HttpManSession.cpp b/fon9/framework/HttpManSession.cpp new file mode 100644 index 0000000..9959ddf --- /dev/null +++ b/fon9/framework/HttpManSession.cpp @@ -0,0 +1,114 @@ +// \file fon9/framework/HttpManSession.cpp +// \author fonwinz@gmail.com +#include "fon9/framework/HttpManSession.hpp" +#include "fon9/framework/IoManager.hpp" +#include "fon9/RevPrint.hpp" + +namespace fon9 { + +class HttpManSession::Server : public io::SessionServer { + fon9_NON_COPY_NON_MOVE(Server); + Framework& Fon9Sys_; +public: + Server(Framework& fon9sys) : Fon9Sys_(fon9sys) { + } + io::SessionSP OnDevice_Accepted(io::DeviceServer&) override { + return new HttpManSession{}; + } +}; +SessionFactorySP HttpManSession::MakeFactory(Framework& fon9sys) { + struct Factory : public SessionFactory { + fon9_NON_COPY_NON_MOVE(Factory); + Framework& Fon9Sys_; + Factory(Framework& fon9sys) + : SessionFactory(fon9_kCSTR_HttpManSession) + , Fon9Sys_(fon9sys) { + } + io::SessionSP CreateSession(IoManager&, const IoConfigItem&) override { + return io::SessionSP{}; // 僅允許透過 server 建立, 所以這裡不提供 session. + } + io::SessionServerSP CreateSessionServer(IoManager&, const IoConfigItem&) override { + return new HttpManSession::Server{this->Fon9Sys_}; + } + }; + return new Factory{fon9sys}; +} +//--------------------------------------------------------------------------// + +void HttpManSession::HttpRequestReady(io::Device& dev, web::HttpResult res) { + if (res == web::HttpResult::ChunkAppended) { + printf("\n" "ChunkAppended:\n" "chunk-ext=[%s]\n" "chunk-data=[%s]\n", + this->Request_.ChunkExt().ToString().c_str(), + this->Request_.ChunkData().ToString().c_str()); + return; + } + // web::HttpResult::FullMessage + printf("\n" "FullMessage:[%s]\n" "[%s]\n", + this->Request_.StartLine().ToString().c_str(), + this->Request_.Body().ToString().c_str()); + if (this->Request_.IsChunked()) + printf("chunk-ext=[%s]\n" "trailer=[%s]\n", + this->Request_.ChunkExt().ToString().c_str(), + this->Request_.ChunkTrailer().ToString().c_str()); + + StrView startline = this->Request_.StartLine(); + StrView method = StrFetchNoTrim(startline, ' '); + StrView target = StrFetchNoTrim(startline, ' '); + if (target != "/") { + dev.StrSend(StrView{"HTTP/1.1 404 OK\r\n\r\n"}); + return; + } + dev.StrSend(StrView{ + "HTTP/1.1 200 OK\r\n" + "Server: Fon9/MaWeb\r\n" + "Content-Type: text/html\r\n" + #if 0 + "Content-Length: 52\r\n" + "\r\n" + "\r\n" + "

Hello, World!

\r\n" + "" + #else + "Transfer-Encoding: chunked\r\n" + "Connection: close\r\n" + "\r\n" + + "3f\r\n" + "

Hello, World!

" + "\r\n" + "0\r\n\r\n" + #endif + }); +} +io::RecvBufferSize HttpManSession::OnDevice_Recv(io::Device& dev, DcQueueList& rxbuf) { + web::HttpResult res = web::HttpParser::Feed(this->Request_, rxbuf.MoveOut()); + for (;;) { + switch (res) { + case web::HttpResult::HeaderTooLarge: + dev.AsyncClose("Http header too large"); + return io::RecvBufferSize::CloseRecv; + case web::HttpResult::BadChunked: + dev.AsyncClose("Http bad chunked"); + return io::RecvBufferSize::CloseRecv; + case web::HttpResult::ChunkSizeTooLarge: + dev.AsyncClose("Http chunked size too large"); + return io::RecvBufferSize::CloseRecv; + case web::HttpResult::ChunkSizeLineTooLong: + dev.AsyncClose("Http chunked size line too long"); + return io::RecvBufferSize::CloseRecv; + case web::HttpResult::Incomplete: + return io::RecvBufferSize::Default; + case web::HttpResult::FullMessage: + case web::HttpResult::ChunkAppended: + this->HttpRequestReady(dev, res); + if (res == web::HttpResult::FullMessage) + this->Request_.RemoveFullMessage(); + res = web::HttpParser::ContinueEat(this->Request_); + continue; + } + dev.AsyncClose("Invalid http result"); + return io::RecvBufferSize::CloseRecv; + } +} + +} // namespace diff --git a/fon9/framework/HttpManSession.hpp b/fon9/framework/HttpManSession.hpp new file mode 100644 index 0000000..0e02c34 --- /dev/null +++ b/fon9/framework/HttpManSession.hpp @@ -0,0 +1,35 @@ +// \file fon9/framework/HttpManSession.hpp +// \author fonwinz@gmail.com +#ifndef __fon9_framework_HttpManSession_hpp__ +#define __fon9_framework_HttpManSession_hpp__ +#include "fon9/framework/IoFactory.hpp" +#include "fon9/framework/Framework.hpp" +#include "fon9/web/HttpParser.hpp" + +namespace fon9 { + +/// 提供一個簡易的 http server 管理 fon9 的 framework. +class HttpManSession : public io::Session { + fon9_NON_COPY_NON_MOVE(HttpManSession); + class Server; + + web::HttpMessage Request_; + + // 檢查 LinkBroken: 處理最後 chunked 訊息? => http server 不考慮此情況. + // if (this->Request_.IsChunked()) + // this->HttpRequestReady(); + //void OnDevice_StateChanged(io::Device& dev, const io::StateChangedArgs& e) override; + + io::RecvBufferSize OnDevice_Recv(io::Device& dev, DcQueueList& rxbuf) override; + + void HttpRequestReady(io::Device& dev, web::HttpResult res); + +public: + HttpManSession() = default; + + #define fon9_kCSTR_HttpManSession "HttpMan" + static SessionFactorySP MakeFactory(Framework& fon9sys); +}; + +} // namespace +#endif//__fon9_framework_WebManSession_hpp__ diff --git a/fon9/framework/IoFactory.hpp b/fon9/framework/IoFactory.hpp new file mode 100644 index 0000000..f9e0162 --- /dev/null +++ b/fon9/framework/IoFactory.hpp @@ -0,0 +1,78 @@ +/// \file fon9/framework/IoFactory.hpp +/// \author fonwinz@gmail.com +#ifndef __fon9_framework_IoFactory_hpp__ +#define __fon9_framework_IoFactory_hpp__ +#include "fon9/seed/NamedPark.hpp" +#include "fon9/io/Server.hpp" + +namespace fon9 { + +class fon9_API IoManager; +using IoManagerSP = intrusive_ptr; + +enum class EnabledYN : char { + /// 除了 Yes, 其餘皆為 No. + Yes = 'Y' +}; + +template +inline size_t ToBitv(RevBuffer& rbuf, EnabledYN value) { + return ToBitv(rbuf, value == EnabledYN::Yes); +} + +template +inline void BitvTo(RevBuffer& rbuf, EnabledYN& value) { + bool b = (value == EnabledYN::Yes); + BitvTo(rbuf, b); + value = b ? EnabledYN::Yes : EnabledYN{}; +} + +fon9_WARN_DISABLE_PADDING; +struct IoConfigItem { + EnabledYN Enabled_{}; + CharVector SchArgs_; + + CharVector SessionName_; + CharVector SessionArgs_; + + CharVector DeviceName_; + CharVector DeviceArgs_; +}; +fon9_WARN_POP; + +//--------------------------------------------------------------------------// + +class fon9_API SessionFactory : public seed::NamedSeed { + fon9_NON_COPY_NON_MOVE(SessionFactory); + using base = seed::NamedSeed; +public: + using base::base; + + virtual io::SessionSP CreateSession(IoManager& mgr, const IoConfigItem& cfg) = 0; + virtual io::SessionServerSP CreateSessionServer(IoManager& mgr, const IoConfigItem& cfg) = 0; +}; +using SessionFactoryPark = seed::NamedPark; +using SessionFactoryParkSP = intrusive_ptr; +using SessionFactorySP = SessionFactoryPark::ObjectSP; + +//--------------------------------------------------------------------------// + +class fon9_API DeviceFactory : public seed::NamedSeed { + fon9_NON_COPY_NON_MOVE(DeviceFactory); + using base = seed::NamedSeed; +public: + using base::base; + + virtual io::DeviceSP CreateDevice(IoManagerSP mgr, SessionFactory& sesFactory, const IoConfigItem& cfg) = 0; +}; +using DeviceFactoryPark = seed::NamedPark; +using DeviceFactoryParkSP = intrusive_ptr; +using DeviceFactorySP = DeviceFactoryPark::ObjectSP; + +//--------------------------------------------------------------------------// + +fon9_API DeviceFactorySP GetIoFactoryTcpClient(); +fon9_API DeviceFactorySP GetIoFactoryTcpServer(); + +} // namespaces +#endif//__fon9_framework_IoFactory_hpp__ diff --git a/fon9/framework/IoFactoryTcpClient.cpp b/fon9/framework/IoFactoryTcpClient.cpp new file mode 100644 index 0000000..6a51b55 --- /dev/null +++ b/fon9/framework/IoFactoryTcpClient.cpp @@ -0,0 +1,34 @@ +/// \file fon9/framework/IoFactoryTcpClient.cpp +/// \author fonwinz@gmail.com +#include "fon9/framework/IoManager.hpp" + +#ifdef fon9_WINDOWS +#include "fon9/io/win/IocpTcpClient.hpp" +static fon9::io::IocpTcpClient* CreateDevice(fon9::IoManager& mgr, fon9::io::SessionSP&& ses) { + return new fon9::io::IocpTcpClient(mgr.GetIocpService(), std::move(ses), &mgr); +} +#else +#include "fon9/io/FdrTcpClient.hpp" +static fon9::io::FdrTcpClient* CreateDevice(fon9::IoManager& mgr, fon9::io::SessionSP&& ses) { + return new fon9::io::FdrTcpClient(mgr.GetFdrService(), std::move(ses), &mgr); +} +#endif + +namespace fon9 { + +fon9_API DeviceFactorySP GetIoFactoryTcpClient() { + struct Factory : public DeviceFactory { + fon9_NON_COPY_NON_MOVE(Factory); + Factory() : DeviceFactory("TcpClient") { + } + io::DeviceSP CreateDevice(IoManagerSP mgr, SessionFactory& sesFactory, const IoConfigItem& cfg) override { + auto ses = sesFactory.CreateSession(*mgr, cfg); + if (!ses) + return io::DeviceSP{}; + return ::CreateDevice(*mgr, std::move(ses)); + } + }; + return new Factory; +} + +} // namespaces diff --git a/fon9/framework/IoFactoryTcpServer.cpp b/fon9/framework/IoFactoryTcpServer.cpp new file mode 100644 index 0000000..eb0c74e --- /dev/null +++ b/fon9/framework/IoFactoryTcpServer.cpp @@ -0,0 +1,34 @@ +/// \file fon9/framework/IoFactoryTcpServer.cpp +/// \author fonwinz@gmail.com +#include "fon9/framework/IoManager.hpp" + +#ifdef fon9_WINDOWS +#include "fon9/io/win/IocpTcpServer.hpp" +static fon9::io::IocpTcpServer* CreateDevice(fon9::IoManager& mgr, fon9::io::SessionServerSP&& ses) { + return new fon9::io::IocpTcpServer(mgr.GetIocpService(), std::move(ses), &mgr); +} +#else +#include "fon9/io/FdrTcpServer.hpp" +static fon9::io::FdrTcpServer* CreateDevice(fon9::IoManager& mgr, fon9::io::SessionServerSP&& ses) { + return new fon9::io::FdrTcpServer(mgr.GetFdrService(), std::move(ses), &mgr); +} +#endif + +namespace fon9 { + +fon9_API DeviceFactorySP GetIoFactoryTcpServer() { + struct Factory : public DeviceFactory { + fon9_NON_COPY_NON_MOVE(Factory); + Factory() : DeviceFactory("TcpServer") { + } + io::DeviceSP CreateDevice(IoManagerSP mgr, SessionFactory& sesFactory, const IoConfigItem& cfg) override { + auto ses = sesFactory.CreateSessionServer(*mgr, cfg); + if (!ses) + return io::DeviceSP{}; + return ::CreateDevice(*mgr, std::move(ses)); + } + }; + return new Factory; +} + +} // namespaces diff --git a/fon9/framework/IoManager.cpp b/fon9/framework/IoManager.cpp new file mode 100644 index 0000000..663a4b5 --- /dev/null +++ b/fon9/framework/IoManager.cpp @@ -0,0 +1,434 @@ +/// \file fon9/framework/IoManager.cpp +/// \author fonwinz@gmail.com +#include "fon9/framework/IoManager.hpp" +#include "fon9/seed/FieldMaker.hpp" +#include "fon9/Log.hpp" + +namespace fon9 { + +IoManager::IoManager(const IoManagerArgs& args) + : Name_{args.Name_} + , SessionFactoryPark_{args.SessionFactoryPark_ ? args.SessionFactoryPark_ : new SessionFactoryPark{"SessionFactoryPark"}} + , DeviceFactoryPark_{args.DeviceFactoryPark_ ? args.DeviceFactoryPark_ : new DeviceFactoryPark{"DeviceFactoryPark"}} + , IoServiceCfgstr_{args.IoServiceCfgstr_} { + if (args.IoServiceSrc_) { + #ifdef __fon9_io_win_IocpService_hpp__ + this->IocpService_ = args.IoServiceSrc_->GetIocpService(); + #endif + #ifdef __fon9_io_FdrService_hpp__ + this->FdrService_ = args.IoServiceSrc_->GetFdrService(); + #endif + } +} + +void IoManager::ParseIoServiceArgs(io::IoServiceArgs& iosvArgs) const { + RevBufferList rbuf{128}; + RevPutChar(rbuf, '\n'); + if (!ParseConfig(iosvArgs, &this->IoServiceCfgstr_, rbuf)) { + // fon9_LOG_ERROR: + RevPrint(rbuf, "IoManager.CreateIoService|name=", this->Name_, "info=use default config"); + LogWrite(LogLevel::Error, std::move(rbuf)); + } +} + +void IoManager::RaiseMakeIoServiceFatal(Result2 err) const { + std::string errmsg = RevPrintTo("IoManager.MakeIoService|name=", this->Name_, "err=", err); + fon9_LOG_FATAL(errmsg); + Raise(std::move(errmsg)); +} + +template +intrusive_ptr IoManager::MakeIoService() const{ + io::IoServiceArgs iosvArgs; + this->ParseIoServiceArgs(iosvArgs); + typename IoService::MakeResult err; + auto iosv = IoService::MakeService(iosvArgs, this->Name_, err); + if (!iosv) + this->RaiseMakeIoServiceFatal(err); + return iosv; +} + +#ifdef __fon9_io_win_IocpService_hpp__ +io::IocpServiceSP IoManager::GetIocpService() { + if (!this->IocpService_) + this->IocpService_ = this->MakeIoService(); + return this->IocpService_; +} +#endif + +#ifdef __fon9_io_FdrService_hpp__ +io::FdrServiceSP IoManager::GetFdrService() { + if (!this->FdrService_) { + io::IoServiceArgs iosvArgs; + this->ParseIoServiceArgs(iosvArgs); + + Result2 err; + this->FdrService_ = MakeDefaultFdrService(iosvArgs, this->Name_, err); + if (!this->FdrService_) + this->RaiseMakeIoServiceFatal(err); + } + return this->FdrService_; +} +#endif + +bool IoManager::CreateDevice(DeviceItem& item) { + if (item.Device_) + return true; + item.SessionSt_.clear(); + item.DeviceSt_.clear(); + auto sesFactory = this->SessionFactoryPark_->Get(ToStrView(item.Config_.SessionName_)); + if (!sesFactory) { + item.SessionSt_.assign("Session factory not found."); + return false; + } + if (auto devFactory = this->DeviceFactoryPark_->Get(ToStrView(item.Config_.DeviceName_))) { + if (!(item.Device_ = devFactory->CreateDevice(this, *sesFactory, item.Config_))) { + item.DeviceSt_.assign("IoManager.CreateDevice|err=cannot create this device."); + return false; + } + item.Device_->SetManagerBookmark(reinterpret_cast(&item)); + item.Device_->Initialize(); + return true; + } + item.DeviceSt_.assign("Device factory not found"); + return false; +} +bool IoManager::AddConfig(StrView id, const IoConfigItem& cfg) { + DeviceItemSP item{new DeviceItem}; + item->Id_.assign(id); + item->Config_ = cfg; + + DeviceMap::Locker map{this->DeviceMap_}; + if (!map->insert(item).second) + return false; + if (cfg.Enabled_ == EnabledYN::Yes) { + if (this->CreateDevice(*item)) { + if (cfg.DeviceArgs_.empty()) + // 沒提供 DeviceArgs_, 則由使用者 or Session 手動開啟. + item->DeviceSt_.assign("Waiting for manual open"); + else { + item->DeviceSt_.assign("Async opening"); + item->Device_->AsyncOpen(cfg.DeviceArgs_.ToString()); + } + } + } + return true; +} + +//--------------------------------------------------------------------------// + +IoManager::DeviceRun* IoManager::FromManagerBookmark(io::Device& dev) { + DeviceRun* item = reinterpret_cast(dev.GetManagerBookmark()); + if (item && item->Device_.get() == &dev) + return item; + return nullptr; +} +void IoManager::OnDevice_Initialized(io::Device&) { + // accepted client 在 OnDevice_Accepted() 時設定 SetManagerBookmark(); + // 一般 device 在 Device::Initialize() 之後設定 SetManagerBookmark(); + // 所以這裡不用做任何事情. +} +void IoManager::OnDevice_Destructing(io::Device& dev) { + if (DeviceRun* item = reinterpret_cast(dev.GetManagerBookmark())) { + // 只有 AcceptedClient 在 OnDevice_Destructing() 時還會保留 ManagerBookmark. + // 所以此時的 item 可以安全的刪除. + delete item; + // TODO: 如果 dev 是 AcceptedClient, 則更新 server st (accepted client 的剩餘數量)? + } + fon9_LOG_TRACE("IoManager.OnDevice_Destructing|dev=", ToPtr(&dev)); +} +void IoManager::OnDevice_Accepted(io::DeviceServer& server, io::DeviceAcceptedClient& client) { + DeviceRun* cliItem = new DeviceRun; // 在 OnDevice_Destructing() 時刪除. + cliItem->Device_.reset(&client); + cliItem->AcceptedClientSeq_ = client.GetAcceptedClientSeq(); + client.SetManagerBookmark(reinterpret_cast(cliItem)); + + DeviceMap::Locker map{this->DeviceMap_}; + if (auto serItem = this->FromManagerBookmark(server)) { + this->MakeAcceptedClientTree(*serItem, client.Owner_); + cliItem->Sapling_ = serItem->Sapling_; + } +} +void IoManager::OnDevice_StateChanged(io::Device& dev, const io::StateChangedArgs& e) { + this->UpdateDeviceState(dev, e.After_); +} +void IoManager::OnDevice_StateUpdated(io::Device& dev, const io::StateUpdatedArgs& e) { + this->UpdateDeviceState(dev, e); +} +void IoManager::UpdateDeviceState(io::Device& dev, const io::StateUpdatedArgs& e) { + if (io::DeviceAcceptedClient* cli = dynamic_cast(&dev)) { + auto clis = cli->Owner_->Lock(); + this->UpdateDeviceStateLocked(dev, e); + } + else { + DeviceMap::Locker map{this->DeviceMap_}; + this->UpdateDeviceStateLocked(dev, e); + } +} +void IoManager::UpdateDeviceStateLocked(io::Device& dev, const io::StateUpdatedArgs& e) { + RevBufferList rbuf{128}; + RevPutChar(rbuf, '\n'); + if (auto item = this->FromManagerBookmark(dev)) { + RevPrint(rbuf, + "|st=", GetStateStr(e.State_), + "|id={", e.DeviceId_, "}" + "|info=", e.Info_); + const BufferNode* bnode = rbuf.cfront(); + char* pmsg = static_cast(item->DeviceSt_.alloc(kDateTimeStrWidth + CalcDataSize(bnode))); + ToStrRev(pmsg += kDateTimeStrWidth, UtcNow()); + while (bnode) { + auto datsz = bnode->GetDataSize(); + memcpy(pmsg, bnode->GetDataBegin(), datsz); + pmsg += datsz; + bnode = bnode->GetNext(); + } + *(pmsg - 1) = 0; // for back '\n' => '\0'; + item->DeviceSt_.resize(item->DeviceSt_.size() - 1); // remove back '\n' + if (e.State_ == io::State::Opening) + item->OpenArgs_.assign(e.Info_); + else if (e.State_ == io::State::Disposing) { + item->Device_.reset(); + item->Sapling_.reset(); + if (item->AcceptedClientSeq_ == 0) + dev.SetManagerBookmark(0); + // item->AcceptedClientSeq_ != 0, accepted client: + // 保留 Bookmark, 等 OnDevice_Destructing() 事件時, 將 item 刪除. + } + } + // fon9_LOG_TRACE: + if (fon9_UNLIKELY(LogLevel::Trace >= fon9::LogLevel_)) { + RevPrint(rbuf, "IoManager.DeviceState|dev=", ToPtr(&dev)); + LogWrite(LogLevel::Trace, std::move(rbuf)); + } +} +void IoManager::OnSession_StateUpdated(io::Device& dev, StrView stmsg) { + if (io::DeviceAcceptedClient* cli = dynamic_cast(&dev)) { + auto clis = cli->Owner_->Lock(); + this->UpdateSessionStateLocked(dev, stmsg); + } + else { + DeviceMap::Locker map{this->DeviceMap_}; + this->UpdateSessionStateLocked(dev, stmsg); + } +} +void IoManager::UpdateSessionStateLocked(io::Device& dev, StrView stmsg) { + if (auto item = this->FromManagerBookmark(dev)) { + size_t sz = kDateTimeStrWidth + stmsg.size() + 1; + // 底下 -+sizeof(NumOutBuf) 是因為 RevPrint() 需要分配較多的保留大小. + // 但因我確定實際大小不會超過, 所以自行調整. + RevBufferFixedMem rbuf{static_cast(item->SessionSt_.alloc(sz)) - sizeof(NumOutBuf), sz + sizeof(NumOutBuf)}; + RevPrint(rbuf, UtcNow(), '|', stmsg); + } + fon9_LOG_INFO("IoManager.SessionState|dev=", ToPtr(&dev), "|st=", stmsg); +} + +//--------------------------------------------------------------------------// + +seed::LayoutSP IoManager::MakeAcceptedClientLayout() { + struct Aux { + static seed::LayoutSP MakeLayout() { + seed::Fields fields; + fields.Add(fon9_MakeField(Named{"SessionSt"}, DeviceRun, SessionSt_)); + fields.Add(fon9_MakeField(Named{"DeviceSt"}, DeviceRun, DeviceSt_)); + fields.Add(fon9_MakeField(Named{"OpenArgs"}, DeviceRun, OpenArgs_)); + seed::TabSP tabSt{new seed::Tab(Named{"St"}, std::move(fields))}; + return new seed::Layout1(seed::MakeField(Named{"Id"}, 0, *static_cast(nullptr)), + std::move(tabSt)); + } + }; + static seed::LayoutSP layout{Aux::MakeLayout()}; + return layout; +} +seed::LayoutSP IoManager::Tree::MakeLayout() { + struct Aux { + static seed::LayoutSP MakeLayoutImpl() { + seed::LayoutSP saplingLayout = IoManager::MakeAcceptedClientLayout(); + seed::Fields fields; + fields.Add(fon9_MakeField(Named{"Session"}, DeviceItem, Config_.SessionName_)); + fields.Add(fon9_MakeField(Named{"SessionSt"}, DeviceItem, SessionSt_)); + fields.Add(fon9_MakeField(Named{"Device"}, DeviceItem, Config_.DeviceName_)); + fields.Add(fon9_MakeField(Named{"DeviceSt"}, DeviceItem, DeviceSt_)); + fields.Add(fon9_MakeField(Named{"OpenArgs"}, DeviceItem, OpenArgs_)); + seed::TabSP tabSt{new seed::Tab(Named{"St"}, std::move(fields), saplingLayout)}; + + fields.Add(fon9_MakeField(Named{"Enabled"}, DeviceItem, Config_.Enabled_)); + fields.Add(fon9_MakeField(Named{"Sch"}, DeviceItem, Config_.SchArgs_)); + fields.Add(fon9_MakeField(Named{"Session"}, DeviceItem, Config_.SessionName_)); + fields.Add(fon9_MakeField(Named{"SessionArgs"}, DeviceItem, Config_.SessionArgs_)); + fields.Add(fon9_MakeField(Named{"Device"}, DeviceItem, Config_.DeviceName_)); + fields.Add(fon9_MakeField(Named{"DeviceArgs"}, DeviceItem, Config_.DeviceArgs_)); + seed::TabSP tabConfig{new seed::Tab(Named{"Config"}, std::move(fields), saplingLayout)}; + + return new seed::LayoutN(fon9_MakeField(Named{"Id"}, DeviceItem, Id_), + std::move(tabSt), + std::move(tabConfig)); + } + }; + static seed::LayoutSP Layout_{Aux::MakeLayoutImpl()}; + return Layout_; +} +void IoManager::Tree::OnParentSeedClear() { + // dispose all devices. + DeviceMap::Locker map{this->IoManager_->DeviceMap_}; + for (auto& i : *map) { + if (io::Device* dev = i->Device_.get()) + dev->AsyncDispose("Parent clear"); + } +} + +//--------------------------------------------------------------------------// + +fon9_WARN_DISABLE_PADDING; +fon9_MSC_WARN_DISABLE_NO_PUSH(4265 /* class has virtual functions, but destructor is not virtual. */ + 4355 /* 'this' : used in base member initializer list*/); +struct IoManager::Tree::PodOp : public seed::PodOpLockerNoWrite { + fon9_NON_COPY_NON_MOVE(PodOp); + using base = seed::PodOpLockerNoWrite; + DeviceItemSP Seed_; + PodOp(DeviceItem& seed, Tree& sender, seed::OpResult res, DeviceMap::Locker& locker) + : base{*this, sender, res, ToStrView(seed.Id_), locker} + , Seed_{&seed} { + } + DeviceItem& GetSeedRW(seed::Tab&) { + return *this->Seed_; + } + seed::TreeSP HandleGetSapling(seed::Tab&) { + return this->Seed_->Sapling_; + } + void HandleSeedCommand(DeviceMap::Locker& locker, SeedOpResult& res, StrView cmdln, seed::FnCommandResultHandler&& resHandler) { + io::DeviceSP dev = this->Seed_->Device_; + if(!dev) { + // cmd = "open" => 建立 device & open. + return; + } + locker.unlock(); + std::string msg = dev->DeviceCommand(cmdln); + resHandler(res, &msg); + } +}; +struct IoManager::Tree::TreeOp : public seed::TreeOp { + fon9_NON_COPY_NON_MOVE(TreeOp); + using base = seed::TreeOp; + TreeOp(Tree& tree) : base(tree) { + } + static void MakePolicyRecordView(DeviceMapImpl::iterator ivalue, seed::Tab* tab, RevBuffer& rbuf) { + if (tab) + FieldsCellRevPrint(tab->Fields_, seed::SimpleRawRd{**ivalue}, rbuf, seed::GridViewResult::kCellSplitter); + RevPrint(rbuf, (*ivalue)->Id_); + } + void GridView(const seed::GridViewRequest& req, seed::FnGridViewOp fnCallback) override { + seed::GridViewResult res{this->Tree_, req.Tab_}; + { + DeviceMap::Locker map{static_cast(&this->Tree_)->IoManager_->DeviceMap_}; + seed::MakeGridView(*map, this->GetIteratorForGv(*map, req.OrigKey_), + req, res, &MakePolicyRecordView); + } // unlock. + fnCallback(res); + } + void Get(StrView strKeyText, seed::FnPodOp fnCallback) override { + { + DeviceMap::Locker map{static_cast(&this->Tree_)->IoManager_->DeviceMap_}; + auto ifind = this->GetIteratorForPod(*map, strKeyText); + if (ifind != map->end()) { + PodOp op(**ifind, *static_cast(&this->Tree_), seed::OpResult::no_error, map); + fnCallback(op, &op); + return; + } + } // unlock. + fnCallback(seed::PodOpResult{this->Tree_, seed::OpResult::not_found_key, strKeyText}, nullptr); + } +}; +void IoManager::Tree::OnTreeOp(seed::FnTreeOp fnCallback) { + TreeOp op{*this}; + fnCallback(seed::TreeOpResult{this, seed::OpResult::no_error}, &op); +} + +//--------------------------------------------------------------------------// +struct IoManager::AcceptedTree : public seed::Tree { + fon9_NON_COPY_NON_MOVE(AcceptedTree); + using base = seed::Tree; + using Locker = io::AcceptedClients::ConstLocker; + struct PodOp : public seed::PodOpDefault { + fon9_NON_COPY_NON_MOVE(PodOp); + using base = PodOpDefault; + io::DeviceSP Device_; + Locker& Locker_; + PodOp(io::Device* seed, Tree& sender, const StrView& key, Locker& locker) + : base{sender, seed::OpResult::no_error, key} + , Device_{seed} + , Locker_(locker) { + } + void Lock() { + if (!this->Locker_.owns_lock()) + this->Locker_.lock(); + } + void Unlock() { + if (this->Locker_.owns_lock()) + this->Locker_.unlock(); + } + void BeginRead(seed::Tab& tab, seed::FnReadOp fnCallback) override { + this->Lock(); + if (auto item = IoManager::FromManagerBookmark(*this->Device_)) + this->BeginRW(tab, std::move(fnCallback), seed::SimpleRawRd{*item}); + else + base::BeginRead(tab, std::move(fnCallback)); + } + void OnSeedCommand(seed::Tab* tab, StrView cmdln, seed::FnCommandResultHandler resHandler) override { + this->Unlock(); + this->Tab_ = tab; + std::string msg = this->Device_->DeviceCommand(cmdln); + resHandler(*this, &msg); + } + }; + struct TreeOp : public seed::TreeOp { + fon9_NON_COPY_NON_MOVE(TreeOp); + using base = seed::TreeOp; + TreeOp(Tree& tree) : base(tree) { + } + static void MakePolicyRecordView(io::AcceptedClientsImpl::const_iterator ivalue, seed::Tab* tab, RevBuffer& rbuf) { + if (tab) { + if (auto item = IoManager::FromManagerBookmark(**ivalue)) + FieldsCellRevPrint(tab->Fields_, seed::SimpleRawRd{*item}, rbuf, seed::GridViewResult::kCellSplitter); + } + RevPrint(rbuf, (*ivalue)->GetAcceptedClientSeq()); + } + void GridView(const seed::GridViewRequest& req, seed::FnGridViewOp fnCallback) override { + seed::GridViewResult res{this->Tree_, req.Tab_}; + { + auto map = static_cast(&this->Tree_)->Listener_->Lock(); + seed::MakeGridView(*map, this->GetIteratorForGv(*map, req.OrigKey_), + req, res, &MakePolicyRecordView); + } // unlock. + fnCallback(res); + } + void Get(StrView strKeyText, seed::FnPodOp fnCallback) override { + { + auto map = static_cast(&this->Tree_)->Listener_->Lock(); + auto ifind = this->GetIteratorForPod(*map, strKeyText); + if (ifind != map->end()) { + PodOp op(*ifind, this->Tree_, strKeyText, map); + fnCallback(op, &op); + return; + } + } // unlock. + fnCallback(seed::PodOpResult{this->Tree_, seed::OpResult::not_found_key, strKeyText}, nullptr); + } + }; +public: + const io::DeviceListenerSP Listener_; + AcceptedTree(io::DeviceListenerSP&& listener) + : base{IoManager::MakeAcceptedClientLayout()} + , Listener_{std::move(listener)} { + } + void OnTreeOp(seed::FnTreeOp fnCallback) override { + TreeOp op{*this}; + fnCallback(seed::TreeOpResult{this, seed::OpResult::no_error}, &op); + } +}; +void IoManager::MakeAcceptedClientTree(DeviceRun& serItem, io::DeviceListenerSP listener) { + if (!serItem.Sapling_ || static_cast(serItem.Sapling_.get())->Listener_ != listener) + serItem.Sapling_.reset(new AcceptedTree{std::move(listener)}); +} +fon9_WARN_POP; + +} // namespaces diff --git a/fon9/framework/IoManager.hpp b/fon9/framework/IoManager.hpp new file mode 100644 index 0000000..2f665e0 --- /dev/null +++ b/fon9/framework/IoManager.hpp @@ -0,0 +1,151 @@ +/// \file fon9/framework/IoManager.hpp +/// \author fonwinz@gmail.com +#ifndef __fon9_framework_IoManager_hpp__ +#define __fon9_framework_IoManager_hpp__ +#include "fon9/framework/IoFactory.hpp" +#ifdef fon9_WINDOWS +#include "fon9/io/win/IocpService.hpp" +#else +#include "fon9/io/FdrService.hpp" +#endif + +namespace fon9 { + +struct IoManagerArgs { + std::string Name_; + + /// 若 IoServiceSrc_ != nullptr 則不理會 IoServiceCfgstr_; + /// 直接與 IoServiceSrc_ 共用 io service. + IoManagerSP IoServiceSrc_; + std::string IoServiceCfgstr_; + + /// 如果 SessionFactoryPark_ == nullptr, 則 IoManager 會自己建立一個. + SessionFactoryParkSP SessionFactoryPark_; + /// 如果 DeviceFactoryPark_ == nullptr, 則 IoManager 會自己建立一個. + DeviceFactoryParkSP DeviceFactoryPark_; +}; + +class fon9_API IoManager : public io::Manager { + fon9_NON_COPY_NON_MOVE(IoManager); + /// 在首次有需要時(GetIocpService() or GetFdrService()), 才會將該 io service 建立起來. +#ifdef __fon9_io_win_IocpService_hpp__ + private: + io::IocpServiceSP IocpService_; + // Windows 也可能會有 FdrService_? 或其他種類的 io service? + public: + io::IocpServiceSP GetIocpService(); +#endif + +#ifdef __fon9_io_FdrService_hpp__ + private: + io::FdrServiceSP FdrService_; + // Linux/Unix 可能會有其他種類的 io service? + public: + io::FdrServiceSP GetFdrService(); +#endif + +public: + const std::string Name_; + const SessionFactoryParkSP SessionFactoryPark_; + const DeviceFactoryParkSP DeviceFactoryPark_; + + IoManager(const IoManagerArgs& args); + + /// 若 id 重複, 則返回 false, 表示失敗. + /// 若 cfg.Enabled_ == EnabledYN::Yes 則會啟用該設定. + bool AddConfig(StrView id, const IoConfigItem& cfg); + + class Tree : public seed::Tree { + fon9_NON_COPY_NON_MOVE(Tree); + using base = seed::Tree; + struct TreeOp; + struct PodOp; + static seed::LayoutSP MakeLayout(); + public: + const IoManagerSP IoManager_; + Tree(const IoManagerArgs& args) + : base{MakeLayout()} + , IoManager_{new IoManager{args}} { + } + void OnTreeOp(seed::FnTreeOp fnCallback) override; + void OnParentSeedClear() override; + }; + +private: + std::string IoServiceCfgstr_; + + fon9_WARN_DISABLE_PADDING; + struct DeviceRun { + io::DeviceSP Device_; + CharVector SessionSt_; + CharVector DeviceSt_; + seed::TreeSP Sapling_; + CharVector OpenArgs_; + + /// - 如果此值==0 表示: + /// - this->Device_ 是從 DeviceItem 設定建立而來. + /// - 若 this->Device_ 為 DeviceServer, 則 this->Sapling_ 為 AcceptedClients tree. + /// - 如果此值!=0 表示: + /// - this->Device_ 是 Accepted Client. + /// - this->Sapling_ 為顯示此 AcceptedClients 的 tree. + io::DeviceAcceptedClientSeq AcceptedClientSeq_{0}; + }; + static DeviceRun* FromManagerBookmark(io::Device& dev); + + using DeviceItemId = CharVector; + struct DeviceItem : public DeviceRun, public intrusive_ref_counter { + DeviceItemId Id_; + IoConfigItem Config_; + }; + using DeviceItemSP = intrusive_ptr; + fon9_WARN_POP; + + struct CmpDeviceItemSP { + bool operator()(const DeviceItemSP& lhs, const DeviceItemSP& rhs) const { return lhs->Id_ < rhs->Id_; } + bool operator()(const DeviceItemSP& lhs, const StrView& rhs) const { return ToStrView(lhs->Id_) < rhs; } + bool operator()(const StrView& lhs, const DeviceItemSP& rhs) const { return lhs < ToStrView(rhs->Id_); } + bool operator()(const DeviceItemId& lhs, const DeviceItemSP& rhs) const { return lhs < rhs->Id_; } + bool operator()(const DeviceItemSP& lhs, const DeviceItemId& rhs) const { return lhs->Id_ < rhs; } + }; + using DeviceMapImpl = SortedVectorSet; + using DeviceMap = MustLock; + DeviceMap DeviceMap_; + + template + intrusive_ptr MakeIoService() const; + + void RaiseMakeIoServiceFatal(Result2 err) const; + void ParseIoServiceArgs(io::IoServiceArgs& iosvArgs) const; + + void OnDevice_Accepted(io::DeviceServer& server, io::DeviceAcceptedClient& client) override; + void OnDevice_Initialized(io::Device& dev) override; + void OnDevice_Destructing(io::Device& dev) override; + void OnDevice_StateChanged(io::Device& dev, const io::StateChangedArgs& e) override; + void OnDevice_StateUpdated(io::Device& dev, const io::StateUpdatedArgs& e) override; + void OnSession_StateUpdated(io::Device& dev, StrView stmsg) override; + void UpdateDeviceState(io::Device& dev, const io::StateUpdatedArgs& e); + void UpdateDeviceStateLocked(io::Device& dev, const io::StateUpdatedArgs& e); + void UpdateSessionStateLocked(io::Device& dev, StrView stmsg); + bool CreateDevice(DeviceItem& item); + static seed::LayoutSP MakeAcceptedClientLayout(); + static void MakeAcceptedClientTree(DeviceRun& serItem, io::DeviceListenerSP listener); + struct AcceptedTree; +}; + +//--------------------------------------------------------------------------// + +class fon9_API NamedIoManager : public seed::NamedSapling { + fon9_NON_COPY_NON_MOVE(NamedIoManager); + using base = seed::NamedSapling; +public: + NamedIoManager(const IoManagerArgs& args) + : base(new IoManager::Tree{args}, args.Name_) { + } + + IoManager& GetIoManager() const { + return *static_cast(this->Sapling_.get())->IoManager_; + } +}; + +} // namespaces +#endif//__fon9_framework_IoManager_hpp__ diff --git a/fon9/framework/SeedSession.hpp b/fon9/framework/SeedSession.hpp index d622d84..da01bc5 100644 --- a/fon9/framework/SeedSession.hpp +++ b/fon9/framework/SeedSession.hpp @@ -105,6 +105,9 @@ class fon9_API SeedSession : public intrusive_ref_counter { St::Locker st{this->St_}; prompt.assign(st->Prompt_); } + const auth::AuthResult& GetAuthr() const { + return this->Authr_; + } private: struct StateImpl { diff --git a/fon9/io/Device.cpp b/fon9/io/Device.cpp index b989150..4925e8e 100644 --- a/fon9/io/Device.cpp +++ b/fon9/io/Device.cpp @@ -136,7 +136,7 @@ bool Device::OpImpl_SetState(State afst, StrView stmsg) { State bfst = this->State_; if (bfst == afst) { if (afst < State::Disposing) { // Disposing 訊息不需要重複更新. - StateUpdatedArgs e{afst, stmsg}; + StateUpdatedArgs e{afst, stmsg, this->DeviceId_}; this->Session_->OnDevice_StateUpdated(*this, e); if (this->Manager_) this->Manager_->OnDevice_StateUpdated(*this, e); @@ -148,8 +148,8 @@ bool Device::OpImpl_SetState(State afst, StrView stmsg) { return false; this->State_ = afst; StateChangedArgs e{stmsg, this->DeviceId_}; - e.Before_ = bfst; - e.After_ = afst; + e.BeforeState_ = bfst; + e.After_.State_ = afst; this->OpImpl_StateChanged(e); this->Session_->OnDevice_StateChanged(*this, e); if (this->Manager_) diff --git a/fon9/io/DeviceParseConfig.hpp b/fon9/io/DeviceParseConfig.hpp index 691a821..898cd67 100644 --- a/fon9/io/DeviceParseConfig.hpp +++ b/fon9/io/DeviceParseConfig.hpp @@ -24,7 +24,7 @@ namespace fon9 { namespace io { /// }; /// \endcode template -inline static bool OpThr_DeviceParseConfig(DeviceT& dev, StrView cfgstr) { +inline bool OpThr_DeviceParseConfig(DeviceT& dev, StrView cfgstr) { dev.OpImpl_SetState(State::Opening, cfgstr); dev.Config_.SetDefaults(); diff --git a/fon9/io/FdrService.hpp b/fon9/io/FdrService.hpp index b6f3aa1..c52155f 100644 --- a/fon9/io/FdrService.hpp +++ b/fon9/io/FdrService.hpp @@ -129,7 +129,7 @@ class FdrService : public intrusive_ref_counter { using FdrServiceSP = intrusive_ptr; /// \ingroup io -/// 各個 OS, 有它自己的預設 FdrService: 例如 Linux = epoll. +/// 各個 OS 有它自己的預設 FdrService: 例如 Linux = FdrServiceEpoll. fon9_API FdrServiceSP MakeDefaultFdrService(const IoServiceArgs& ioArgs, const std::string& thrName, Result2& err); //--------------------------------------------------------------------------// diff --git a/fon9/io/IoBase.hpp b/fon9/io/IoBase.hpp index fccff00..3f438c7 100644 --- a/fon9/io/IoBase.hpp +++ b/fon9/io/IoBase.hpp @@ -116,33 +116,36 @@ inline bool IsAllowContinueSend(State st) { //--------------------------------------------------------------------------// -/// \ingroup io -/// OnDevice_StateChanged() 事件的參數. -struct StateChangedArgs { - fon9_NON_COPY_NON_MOVE(StateChangedArgs); - StateChangedArgs(StrView info, const std::string& deviceId) - : Info_{info} - , DeviceId_(deviceId) { - } - State Before_; - State After_; - /// if (After _== State::Opening) 則 Info_ = cfgstr; - StrView Info_; - const std::string& DeviceId_; -}; - fon9_WARN_DISABLE_PADDING; /// \ingroup io /// OnDevice_StateUpdated() 事件的參數. /// State 沒變, 但更新訊息. /// 例如: 還在 State::Linking, 但是嘗試不同的 RemoteAddress. struct StateUpdatedArgs { - StateUpdatedArgs(State st, StrView info) - : State_(st) + fon9_NON_COPY_NON_MOVE(StateUpdatedArgs); + StateUpdatedArgs(State st, StrView info, const std::string& deviceId) + : StateUpdatedArgs(info, deviceId) { + this->State_ = st; + } + StateUpdatedArgs(StrView info, const std::string& deviceId) + : DeviceId_(deviceId) , Info_{info} { } - State State_; - StrView Info_; + const std::string& DeviceId_; + /// if (State_== State::Opening) 則 Info_ = cfgstr; + StrView Info_; + State State_; +}; + +/// \ingroup io +/// OnDevice_StateChanged() 事件的參數. +struct StateChangedArgs { + fon9_NON_COPY_NON_MOVE(StateChangedArgs); + StateChangedArgs(const StrView& info, const std::string& deviceId) + : After_{info, deviceId} { + } + StateUpdatedArgs After_; + State BeforeState_; }; fon9_WARN_POP; diff --git a/fon9/io/IoServiceArgs.cpp b/fon9/io/IoServiceArgs.cpp index 3212ea9..2c0bf66 100644 --- a/fon9/io/IoServiceArgs.cpp +++ b/fon9/io/IoServiceArgs.cpp @@ -72,15 +72,6 @@ ConfigParser::Result IoServiceArgs::OnTagValue(StrView tag, StrView& value) { return ConfigParser::Result::Success; } -// const char* IoServiceArgs::Parse(StrView values) { -// StrView tag, value; -// while (StrFetchTagValue(values, tag, value)) { -// if (const char* perr = this->FromTagValue(tag, value)) -// return perr; -// } -// return nullptr; -// } - //--------------------------------------------------------------------------// void ServiceThreadArgs::OnThrRunBegin(StrView msgHead) const { diff --git a/fon9/io/IoServiceArgs.hpp b/fon9/io/IoServiceArgs.hpp index fa17b40..f462ad9 100644 --- a/fon9/io/IoServiceArgs.hpp +++ b/fon9/io/IoServiceArgs.hpp @@ -27,6 +27,10 @@ fon9_API StrView HowWaitToStr(HowWait value); /// args: "ThreadCount=n|Wait=Policy|Cpus=List|Capacity=0" /// Policy: Block(default) struct fon9_API IoServiceArgs { + /// 若有設定 CpuAffinity, 則每個 io service thread 會綁定一個固定的 cpu, 而不是所有的 thread 共用這裡設定的 cpu. + /// 例如: ThreadCount_=3; CpuAffinity=0,1; + /// 則 Thr0=Cpu0; Thr1=Cpu1; Thr2=Cpu0; + /// 此例: Thr0,Thr2 會共同綁在 Cpu0; using CpuAffinity = std::vector; CpuAffinity CpuAffinity_; diff --git a/fon9/io/Server.cpp b/fon9/io/Server.cpp index c664df2..25763e9 100644 --- a/fon9/io/Server.cpp +++ b/fon9/io/Server.cpp @@ -36,12 +36,34 @@ void DeviceAcceptedClient::OpImpl_Open(std::string deviceId) { } void DeviceAcceptedClient::OpImpl_StateChanged(const StateChangedArgs& e) { base::OpImpl_StateChanged(e); - if (e.Before_ <= State::LinkReady && State::LinkReady < e.After_) + if (e.BeforeState_ <= State::LinkReady && State::LinkReady < e.After_.State_) this->Owner_->RemoveAcceptedClient(*this); } //--------------------------------------------------------------------------// +AcceptedClientsImpl::const_iterator AcceptedClientsImpl::lower_bound(DeviceAcceptedClientSeq seq) const { + return std::lower_bound(this->begin(), this->end(), seq, + [](DeviceAcceptedClient* i, DeviceAcceptedClientSeq k) -> bool { + return i->GetAcceptedClientSeq() < k; + }); +} +AcceptedClientsImpl::const_iterator AcceptedClientsImpl::find(DeviceAcceptedClientSeq seq) const { + auto ifind = this->lower_bound(seq); + auto iend = this->end(); + if (ifind != iend && (*ifind)->GetAcceptedClientSeq() == seq) + return ifind; + return iend; +} +fon9_API AcceptedClientsImpl::const_iterator ContainerLowerBound(const AcceptedClientsImpl& container, StrView strKeyText) { + return container.lower_bound(StrTo(strKeyText, DeviceAcceptedClientSeq{})); +} +fon9_API AcceptedClientsImpl::const_iterator ContainerFind(const AcceptedClientsImpl& container, StrView strKeyText) { + return container.find(StrTo(strKeyText, DeviceAcceptedClientSeq{})); +} + +//--------------------------------------------------------------------------// + DeviceListener::~DeviceListener() { // 解構時沒必要再呼叫 Dispose() 了, 因為此時已經 AcceptedClients_ 必定為空! // this->Dispose("Listener.Dtor"); @@ -63,18 +85,6 @@ void DeviceListener::Dispose(std::string cause) { } } -DeviceListener::AcceptedClientsImpl::iterator -DeviceListener::FindAcceptedClients(AcceptedClientsImpl& devs, DeviceAcceptedClientSeq seq) { - auto iend = devs.end(); - auto ifind = std::lower_bound(devs.begin(), iend, seq, - [](DeviceAcceptedClient* i, DeviceAcceptedClientSeq k) -> bool { - return i->AcceptedClientSeq_ < k; - }); - if (ifind != iend && (*ifind)->AcceptedClientSeq_ == seq) - return ifind; - return iend; -} - void DeviceListener::AddAcceptedClient(DeviceServer& server, DeviceAcceptedClient& devAccepted, StrView connId) { { AcceptedClients::Locker devs{this->AcceptedClients_}; @@ -82,16 +92,16 @@ void DeviceListener::AddAcceptedClient(DeviceServer& server, DeviceAcceptedClien devs->push_back(&devAccepted); } intrusive_ptr_add_ref(&devAccepted); - devAccepted.Initialize(); if (server.Manager_) server.Manager_->OnDevice_Accepted(server, devAccepted); + devAccepted.Initialize(); devAccepted.AsyncOpen(connId.ToString()); } void DeviceListener::RemoveAcceptedClient(DeviceAcceptedClient& devAccepted) { if (this->IsDisposing_) return; AcceptedClients::Locker devs{this->AcceptedClients_}; - auto ifind = FindAcceptedClients(*devs, devAccepted.AcceptedClientSeq_); + auto ifind = devs->find(devAccepted.AcceptedClientSeq_); if (ifind != devs->end() && *ifind == &devAccepted) { devs->erase(ifind); intrusive_ptr_release(&devAccepted); @@ -105,7 +115,7 @@ DeviceSP DeviceListener::GetAcceptedClient(StrView* acceptedClientSeqAndOthers) StrTrim(acceptedClientSeqAndOthers); AcceptedClients::Locker devs{this->AcceptedClients_}; - auto ifind = FindAcceptedClients(*devs, seq); + auto ifind = devs->find(seq); return (ifind == devs->end() ? DeviceSP{} : DeviceSP{*ifind}); } void DeviceListener::OpImpl_CloseAcceptedClient(StrView acceptedClientSeqAndOthers) { diff --git a/fon9/io/Server.hpp b/fon9/io/Server.hpp index b5bd661..047767a 100644 --- a/fon9/io/Server.hpp +++ b/fon9/io/Server.hpp @@ -86,23 +86,29 @@ class fon9_API DeviceAcceptedClient : public Device { }; fon9_WARN_DISABLE_PADDING; +// 因為不確定: std::vector 是否能最佳化. +// - 例如: erase(iter); 只用 memmove(); 就足夠. +// - 所以, 自行呼叫: +// - intrusive_ptr_add_ref(): 在 AddAcceptedClient(); 之後呼叫. +// - intrusive_ptr_release(): 在 Dispose(); 及 RemoveAcceptedClient(); 呼叫 +struct fon9_API AcceptedClientsImpl : public std::vector { + const_iterator find(DeviceAcceptedClientSeq seq) const; + const_iterator lower_bound(DeviceAcceptedClientSeq seq) const; +}; +fon9_API AcceptedClientsImpl::const_iterator ContainerLowerBound(const AcceptedClientsImpl& container, StrView strKeyText); +fon9_API AcceptedClientsImpl::const_iterator ContainerFind(const AcceptedClientsImpl& container, StrView strKeyText); +using AcceptedClients = MustLock; + /// \ingroup io /// 各類 Listener(例如: TcpListener) 的基底, 負責管理 DeviceAcceptedClient. +/// - DeviceServer 在更改設定時, 可能會建立新的 DeviceListener, +/// 並且 Dispose 舊的 DeviceAcceptedClient. +/// 舊的 DeviceListener 就會在 DeviceAcceptedClient 全部死亡時, 自然消失. class fon9_API DeviceListener : public intrusive_ref_counter { fon9_NON_COPY_NON_MOVE(DeviceListener); friend DeviceAcceptedClient; bool IsDisposing_{false}; - - // 因為不確定: std::vector 是否能最佳化. - // - 例如: erase(iter); 只用 memmove(); 就足夠. - // - 所以, 自行呼叫: - // - intrusive_ptr_add_ref(): 在 AddAcceptedClient(); 之後呼叫. - // - intrusive_ptr_release(): 在 Dispose(); 及 RemoveAcceptedClient(); 呼叫 - using AcceptedClientsImpl = std::vector; - static AcceptedClientsImpl::iterator FindAcceptedClients(AcceptedClientsImpl& devs, DeviceAcceptedClientSeq seq); - - using AcceptedClients = MustLock; mutable AcceptedClients AcceptedClients_; void RemoveAcceptedClient(DeviceAcceptedClient& devAccepted); @@ -127,6 +133,11 @@ class fon9_API DeviceListener : public intrusive_ref_counter { } virtual ~DeviceListener(); + /// 提供給 IoManager 安全的取用 Device.ManagerBookmark. + AcceptedClients::ConstLocker Lock() const{ + return AcceptedClients::ConstLocker{this->AcceptedClients_}; + } + void Dispose(std::string cause); bool IsDisposing() const { return this->IsDisposing_; diff --git a/fon9/io/SimpleManager.hpp b/fon9/io/SimpleManager.hpp index 9f59be2..5d868b7 100644 --- a/fon9/io/SimpleManager.hpp +++ b/fon9/io/SimpleManager.hpp @@ -18,27 +18,29 @@ class SimpleManager : public Manager { public: SimpleManager() = default; - virtual void OnDevice_Destructing(Device& dev) override { + void OnDevice_Destructing(Device& dev) override { fon9_LOG_INFO("OnDevice_Destructing|dev=", ToPtr{&dev}); } - void OnDevice_Accepted(DeviceServer& server, DeviceAcceptedClient& client) { + void OnDevice_Accepted(DeviceServer& server, DeviceAcceptedClient& client) override { fon9_LOG_INFO("OnDevice_Accepted|server=", ToPtr(&server), "|client=", ToPtr(&client)); } - virtual void OnDevice_Initialized(Device& dev) override { + void OnDevice_Initialized(Device& dev) override { fon9_LOG_INFO("OnDevice_Initialized|dev=", ToPtr{&dev}); } - void OnDevice_StateChanged(Device& dev, const StateChangedArgs& e) { - fon9_LOG_INFO("OnDevice_StateChanged|dev=", ToPtr{&dev}, - "|st=", GetStateStr(e.After_), + + void UpdateDeviceState(StrView fn, Device& dev, const StateUpdatedArgs& e) { + fon9_LOG_INFO(fn, "|dev=", ToPtr{&dev}, + "|st=", GetStateStr(e.State_), "|id={", e.DeviceId_, "}" "|info=", e.Info_); } - void OnDevice_StateUpdated(Device& dev, const StateUpdatedArgs& e) { - fon9_LOG_INFO("OnDevice_StateUpdated|dev=", ToPtr{&dev}, - "|st=", GetStateStr(e.State_), - "|info=", e.Info_); + void OnDevice_StateChanged(Device& dev, const StateChangedArgs& e) override { + this->UpdateDeviceState("OnDevice_StateChanged", dev, e.After_); + } + void OnDevice_StateUpdated(Device& dev, const StateUpdatedArgs& e) override { + this->UpdateDeviceState("OnDevice_StateUpdated", dev, e); } - void OnSession_StateUpdated(Device& dev, StrView stmsg) { + void OnSession_StateUpdated(Device& dev, StrView stmsg) override { fon9_LOG_INFO("OnSession_StateUpdated|dev=", ToPtr{&dev}, "|stmsg=", stmsg); } }; diff --git a/fon9/io/TcpClientBase.cpp b/fon9/io/TcpClientBase.cpp index 0eedf63..4b4e17a 100644 --- a/fon9/io/TcpClientBase.cpp +++ b/fon9/io/TcpClientBase.cpp @@ -131,11 +131,11 @@ void TcpClientBase::OpImpl_Close(std::string cause) { OpThr_SetDeviceId(*this, std::string{}); } void TcpClientBase::OpImpl_StateChanged(const StateChangedArgs& e) { - if (IsAllowContinueSend(e.Before_)) { - if (e.After_ != State::Lingering) + if (IsAllowContinueSend(e.BeforeState_)) { + if (e.After_.State_ != State::Lingering) this->OpImpl_TcpLinkBroken(); } - else if (e.Before_ == State::Linking && e.After_ != State::LinkReady) + else if (e.BeforeState_ == State::Linking && e.After_.State_ != State::LinkReady) this->OpImpl_TcpClearLinking(); base::OpImpl_StateChanged(e); } diff --git a/fon9/io/win/IocpTcpServer.cpp b/fon9/io/win/IocpTcpServer.cpp index 5e247ef..fe62a6c 100644 --- a/fon9/io/win/IocpTcpServer.cpp +++ b/fon9/io/win/IocpTcpServer.cpp @@ -45,6 +45,8 @@ class IocpTcpListener::AcceptedClient : public DeviceAcceptedClient, public Iocp } virtual void OpImpl_Close(std::string cause) override { shutdown(this->Socket_.GetSocketHandle(), SD_SEND); + // 避免對方沒有 shutdown RECV, 所以這裡主動中斷 Recv, 以免要等很久才會釋放 this. + CancelIoEx(reinterpret_cast(this->Socket_.GetSocketHandle()), &this->RecvOverlapped_); OpThr_DisposeNoClose(*this, std::move(cause)); } diff --git a/fon9/seed/NamedPark.hpp b/fon9/seed/NamedPark.hpp index 0768073..729ca27 100644 --- a/fon9/seed/NamedPark.hpp +++ b/fon9/seed/NamedPark.hpp @@ -51,12 +51,15 @@ class fon9_API ParkTree : public MaTree { /// \copydoc ParkTree template class NamedPark : public NamedSapling { + fon9_NON_COPY_NON_MOVE(NamedPark); using base = NamedSapling; public: using ObjectSP = NamedSeedSPT; using FnEventHandler = std::function; - NamedPark(StrView name) : base{new ParkTree{name.ToString()}, name.ToString()} { + NamedPark(MaTree&, StrView name) : NamedPark{name} { + } + NamedPark(StrView name) : base(new ParkTree{name.ToString()}, name.ToString()) { } /// 把 obj 加入 Sapling(MaTree), 在返回前會先觸發 EventType_Add. @@ -91,7 +94,7 @@ inline NamedSeedSPT FetchNamedPark(MaTree& maTree, StrView parkName, Args if (park) return park; if (fon9_LIKELY(!newPark)) - newPark.reset(new ParkT{maTree, parkName, std::forward(ctorArgs)...}); + newPark.reset(new ParkT(maTree, parkName, std::forward(ctorArgs)...)); if (fon9_LIKELY(maTree.Add(newPark))) return newPark; // maTree.Add(newPark) 失敗, 另一 thread 同時 Add()! diff --git a/fon9/seed/SeedFairy.cpp b/fon9/seed/SeedFairy.cpp index 87425b9..d9dddb7 100644 --- a/fon9/seed/SeedFairy.cpp +++ b/fon9/seed/SeedFairy.cpp @@ -19,7 +19,7 @@ struct SeedFairy::AclTree : public Tree { using base = seed::TreeOp; TreeOp(AclTree& tree) : base(tree) { } - virtual void GridView(const GridViewRequest& req, FnGridViewOp fnCallback) { + void GridView(const GridViewRequest& req, FnGridViewOp fnCallback) override { GridViewResult res{this->Tree_, req.Tab_}; AccessList& acl = static_cast(&this->Tree_)->Acl_; MakeGridView(acl, this->GetIteratorForGv(acl, req.OrigKey_), diff --git a/fon9/seed/SeedFairy.hpp b/fon9/seed/SeedFairy.hpp index dd32ed3..d18452a 100644 --- a/fon9/seed/SeedFairy.hpp +++ b/fon9/seed/SeedFairy.hpp @@ -9,8 +9,9 @@ namespace fon9 { namespace seed { fon9_WARN_DISABLE_PADDING; /// \ingroup seed -/// 協助您在 Tree、Pod、Seed 之間來回穿梭、查看、操作的工具. -/// 所有的操作都 **不是 thread safe** +/// - 檢查存訪問的權限. +/// - 然後透過 SeedSearcher 協助您在 Tree、Pod、Seed 之間來回穿梭、查看、操作. +/// - 所有的操作都 **不是 thread safe** struct fon9_API SeedFairy : public intrusive_ref_counter { fon9_NON_COPY_NON_MOVE(SeedFairy); struct AclTree; diff --git a/fon9/seed/TreeLockContainerT.hpp b/fon9/seed/TreeLockContainerT.hpp index 4139aa4..fb32283 100644 --- a/fon9/seed/TreeLockContainerT.hpp +++ b/fon9/seed/TreeLockContainerT.hpp @@ -11,14 +11,14 @@ namespace fon9 { namespace seed { fon9_MSC_WARN_DISABLE(4265 /* class has virtual functions, but destructor is not virtual. */); template -class PodOpLocker : public PodOpDefault { - fon9_NON_COPY_NON_MOVE(PodOpLocker); +class PodOpLockerNoWrite : public PodOpDefault { + fon9_NON_COPY_NON_MOVE(PodOpLockerNoWrite); using base = PodOpDefault; public: Pod& Pod_; Locker& Locker_; - PodOpLocker(Pod& pod, Tree& sender, OpResult res, const StrView& key, Locker& locker) + PodOpLockerNoWrite(Pod& pod, Tree& sender, OpResult res, const StrView& key, Locker& locker) : base{sender, res, key} , Pod_(pod) , Locker_(locker) { @@ -37,10 +37,6 @@ class PodOpLocker : public PodOpDefault { this->Lock(); this->BeginRW(tab, std::move(fnCallback), SimpleRawRd{this->Pod_.GetSeedRW(tab)}); } - void BeginWrite(Tab& tab, FnWriteOp fnCallback) override { - this->Lock(); - this->BeginRW(tab, std::move(fnCallback), SimpleRawWr{this->Pod_.GetSeedRW(tab)}); - } /// TreeSP 是一個操作單元, 所以在取出 sapling 之後會 unlock. /// 避免在操作 sapling 時回頭需要 lock, 造成死結. @@ -55,6 +51,19 @@ class PodOpLocker : public PodOpDefault { this->Pod_.HandleSeedCommand(this->Locker_, *this, cmdln, std::move(resHandler)); } }; + +template +class PodOpLocker : public PodOpLockerNoWrite { + fon9_NON_COPY_NON_MOVE(PodOpLocker); + using base = PodOpLockerNoWrite; + +public: + using base::base; + void BeginWrite(Tab& tab, FnWriteOp fnCallback) override { + this->Lock(); + this->BeginRW(tab, std::move(fnCallback), SimpleRawWr{this->Pod_.GetSeedRW(tab)}); + } +}; fon9_MSC_WARN_POP; //--------------------------------------------------------------------------// diff --git a/fon9/seed/TreeOp.hpp b/fon9/seed/TreeOp.hpp index de41bb5..9f58143 100644 --- a/fon9/seed/TreeOp.hpp +++ b/fon9/seed/TreeOp.hpp @@ -126,6 +126,21 @@ using FnGridViewOp = std::function; //--------------------------------------------------------------------------// +template +static auto ContainerLowerBound(Container& container, StrView strKeyText) -> decltype(container.lower_bound(strKeyText)) { + return container.lower_bound(strKeyText); +} + +template +static auto ContainerFind(Container& container, StrView strKeyText) -> decltype(container.find(strKeyText)) { + return container.find(strKeyText); +} + +template +using ContainerIterator = conditional_t::value, + typename Container::const_iterator, + typename Container::iterator>; + /// \ingroup seed /// Tree 的(管理)操作不是放在 class Tree's methods? 因為: /// - 無法在操作前知道如何安全的操作 tree: @@ -179,43 +194,22 @@ class fon9_API TreeOp { return true; } - template - static auto LowerBound(Container& container, StrView strKeyText) -> decltype(container.lower_bound(strKeyText)){ - return container.lower_bound(strKeyText); - } - - template - static auto LowerBound(Container& container, StrView strKeyText) -> decltype(ContainerLowerBound(container,strKeyText)) { - return ContainerLowerBound(container, strKeyText); - } - - template + template > static Iterator GetIteratorForGv(Container& container, StrView strKeyText) { Iterator ivalue; if (GetIteratorForGv(container, ivalue, strKeyText.begin())) return ivalue; - return LowerBound(container, strKeyText); + return ContainerLowerBound(container, strKeyText); } virtual void GridView(const GridViewRequest& req, FnGridViewOp fnCallback); //--------------------------------------------------------------------------// - - template - static auto Find(Container& container, StrView strKeyText) -> decltype(container.find(strKeyText)) { - return container.find(strKeyText); - } - - template - static auto Find(Container& container, StrView strKeyText) -> decltype(ContainerFind(container, strKeyText)) { - return ContainerFind(container, strKeyText); - } - - template + template > static Iterator GetIteratorForPod(Container& container, StrView strKeyText) { if (strKeyText.begin() == kStrKeyText_Begin_) return container.begin(); - return Find(container, strKeyText); + return ContainerFind(container, strKeyText); } /// 增加一個 pod. diff --git a/fon9/web/HttpParser.cpp b/fon9/web/HttpParser.cpp new file mode 100644 index 0000000..73bdd1c --- /dev/null +++ b/fon9/web/HttpParser.cpp @@ -0,0 +1,199 @@ +// \file fon9/web/HttpParser.cpp +// \author fonwinz@gmail.com +#include "fon9/web/HttpParser.hpp" +#include "fon9/StrTo.hpp" + +namespace fon9 { namespace web { + +constexpr size_t kMinHeaderSize = sizeof("GET / HTTP/" "\r\n\r\n"); +constexpr size_t kMaxHeaderSize = 1024 * 8; + +static bool TrimHeadAndAppend(std::string& dst, BufferNode* front) { + while (front) { + const char* pend = reinterpret_cast(front->GetDataEnd()); + const char* pbeg = StrFindTrimHead(reinterpret_cast(front->GetDataBegin()), pend); + if (pbeg != pend) { + dst.append(pbeg, pend); + while ((front = front->GetNext()) != nullptr) + dst.append(reinterpret_cast(front->GetDataBegin()), front->GetDataSize()); + return true; + } + front = front->GetNext(); + } + return false; +} + +//--------------------------------------------------------------------------// + +void HttpMessage::RemoveFullMessage() { + const char* origBegin = this->OrigStr_.c_str(); + StrView tail{origBegin + (this->IsChunked() ? this->ChunkTrailer_.End() : this->Body_.End()), + origBegin + this->OrigStr_.size()}; + if (StrTrimHead(&tail).empty()) + this->OrigStr_.clear(); + else + this->OrigStr_.erase(0, static_cast(tail.begin() - origBegin)); + this->ClearFields(); +} +void HttpMessage::ClearAll() { + this->OrigStr_.clear(); + this->ClearFields(); +} +void HttpMessage::ClearFields() { + this->StartLine_.Size_ = 0; + this->Body_.Pos_ = this->Body_.Size_ = 0; + this->ContentLength_ = 0; + this->NextChunkSize_ = 0; + this->CurrChunkFrom_ = 0; + this->ChunkExt_.clear(); + this->ChunkTrailer_.Pos_ = this->ChunkTrailer_.Size_ = 0; + this->HeaderFields_.clear(); +} + +//--------------------------------------------------------------------------// + +HttpResult HttpParser::Feed(HttpMessage& msg, BufferList buf) { + if (msg.IsHeaderReady()) { + BufferAppendTo(buf, msg.OrigStr_); + if (msg.IsChunked()) + return HttpParser::ParseChunk(msg); + return HttpParser::AfterFeedBody(msg); + } + if (size_t bfsz = msg.OrigStr_.size()) { + BufferAppendTo(buf, msg.OrigStr_); + return HttpParser::AfterFeedHeader(msg, bfsz); + } + if (TrimHeadAndAppend(msg.OrigStr_, buf.front())) + return HttpParser::AfterFeedHeader(msg, 0); + return HttpResult::Incomplete; +} +HttpResult HttpParser::AfterFeedHeader(HttpMessage& msg, size_t bfsz) { + if (msg.OrigStr_.size() < kMinHeaderSize) // min http header. + return HttpResult::Incomplete; + size_t pHeadEnd = msg.OrigStr_.find("\r\n\r\n", bfsz > 3 ? bfsz - 3 : 0, 4); + if (pHeadEnd == std::string::npos) { + if (msg.OrigStr_.size() > kMaxHeaderSize) + return HttpResult::HeaderTooLarge; + return HttpResult::Incomplete; + } + msg.Body_.Pos_ = pHeadEnd + 4; + msg.Body_.Size_ = 0; + + const char* const origBegin = msg.OrigStr_.c_str(); + StrView header{origBegin, pHeadEnd}; + msg.StartLine_.FromStrView(origBegin, StrFetchTrim(header, '\r')); + using SubStr = HttpMessage::SubStr; + SubStr sname; + while (!StrTrim(&header).empty()) { + StrView value = StrFetchNoTrim(header, '\r'); + StrView name = StrFetchNoTrim(value, ':'); + sname.FromStrView(origBegin, name); + msg.HeaderFields_.kfetch(sname).second.FromStrView(origBegin, StrTrim(&value)); + } + StrView val = msg.FindHeadField("transfer-encoding"); + while (!val.empty()) { + StrView v = StrFetchTrim(val, ','); + if (iequals(v, "chunked")) + msg.ContentLength_ = kHttpContentLengthChunked; + } + if (msg.IsChunked()) + msg.ChunkTrailer_.Pos_ = msg.Body_.Pos_; + else + msg.ContentLength_ = StrTo(msg.FindHeadField("content-length"), 0u); + return HttpParser::AfterFeedBody(msg); +} +HttpResult HttpParser::AfterFeedBody(HttpMessage& msg) { + if (fon9_UNLIKELY(msg.IsChunked())) + return HttpParser::ParseChunk(msg); + if (msg.Body_.Size_ >= msg.ContentLength_) + return HttpResult::FullMessage; + return HttpResult::Incomplete; +} +HttpResult HttpParser::ParseChunk(HttpMessage& msg) { + if (msg.NextChunkSize_ == 0) + return HttpParser::FetchNextChunkSize(msg); + return HttpParser::CheckChunkAppend(msg); +} +HttpResult HttpParser::CheckChunkAppend(HttpMessage& msg) { + assert(msg.NextChunkSize_ > 0); + auto bodyEnd = msg.Body_.End(); + if (msg.OrigStr_.size() - bodyEnd < msg.NextChunkSize_) + return HttpResult::Incomplete; + msg.CurrChunkFrom_ = bodyEnd; + msg.Body_.Size_ += msg.NextChunkSize_; + msg.ChunkTrailer_.Pos_ += msg.NextChunkSize_; + msg.NextChunkSize_ = 0; + return HttpResult::ChunkAppended; +} +HttpResult HttpParser::FetchNextChunkSize(HttpMessage& msg) { + assert(msg.NextChunkSize_ == 0); + if (msg.ChunkTrailer_.Size_ > 0) + return HttpParser::FetchChunkTrailer(msg); + const char* const origBegin = msg.OrigStr_.c_str(); + StrView chunk{origBegin + msg.ChunkTrailer_.Pos_, origBegin + msg.OrigStr_.size()}; + if (StrTrimHead(&chunk).empty()) { + msg.OrigStr_.erase(msg.ChunkTrailer_.Pos_); + return HttpResult::Incomplete; + } + const char* const pCR = chunk.Find('\r'); + if (pCR == nullptr || pCR == chunk.end() - 1) { + if (chunk.size() > kHttpMaxChunkLineLength) + return HttpResult::ChunkSizeLineTooLong; + msg.ChunkTrailer_.Pos_ = static_cast(chunk.begin() - origBegin); + return HttpResult::Incomplete; + } + if (pCR[1] != '\n') + return HttpResult::BadChunked; + const char* pHexEnd; + msg.NextChunkSize_ = HexStrTo(chunk, &pHexEnd); + if (msg.NextChunkSize_ > kHttpMaxChunkedSize) + return HttpResult::ChunkSizeTooLarge; + while (pHexEnd != pCR) { + if (*pHexEnd == ';') { + ++pHexEnd; + break; + } + if (!isspace(static_cast(*pHexEnd))) + return HttpResult::BadChunked; + ++pHexEnd; + } + msg.ChunkExt_.assign(pHexEnd, pCR); + chunk.SetBegin(pCR + 2); + + if (msg.NextChunkSize_ > 0) { + msg.OrigStr_.erase(msg.ChunkTrailer_.Pos_, static_cast(chunk.begin() - origBegin - msg.ChunkTrailer_.Pos_)); + return HttpParser::CheckChunkAppend(msg); + } + // msg.NextChunkSize_==0: last-chunk. + msg.CurrChunkFrom_ = msg.Body_.Pos_; + msg.ChunkTrailer_.Pos_ = static_cast(chunk.begin() - origBegin); + if (chunk.Get1st() == '\r') // 懶惰一下: 不檢查 '\n', 直接排除 trailer '\r'及之後的空白. + return HttpResult::FullMessage; + // trailer = *(entity-header CRLF); + msg.ChunkTrailer_.Size_ = 1; + return chunk.size() <= 3 ? HttpResult::Incomplete : HttpParser::FetchChunkTrailer(msg); +} +HttpResult HttpParser::FetchChunkTrailer(HttpMessage& msg) { + assert(msg.NextChunkSize_ == 0 && msg.ChunkTrailer_.Size_ > 0); + size_t pos = msg.ChunkTrailer_.Size_; + if (pos == 1 && *(msg.OrigStr_.c_str() + msg.ChunkTrailer_.Pos_) == '\r') { + msg.ChunkTrailer_.Size_ = 0; + return HttpResult::FullMessage; + } + pos = (pos > 3 ? (msg.ChunkTrailer_.Pos_ + pos - 3) : msg.ChunkTrailer_.Pos_); + size_t pChunkTailerEnd = msg.OrigStr_.find("\r\n\r\n", pos, 4); + if (pChunkTailerEnd == std::string::npos) { + msg.ChunkTrailer_.Size_ = msg.OrigStr_.size() - msg.ChunkTrailer_.Pos_; + return HttpResult::Incomplete; + } + msg.ChunkTrailer_.Size_ = pChunkTailerEnd - msg.ChunkTrailer_.Pos_; + return HttpResult::FullMessage; +} +HttpResult HttpParser::ContinueEat(HttpMessage& msg) { + if (!msg.IsHeaderReady()) + return HttpParser::AfterFeedHeader(msg, 0); + assert(msg.IsChunked()); + return HttpParser::ParseChunk(msg); +} + +} } // namespace diff --git a/fon9/web/HttpParser.hpp b/fon9/web/HttpParser.hpp new file mode 100644 index 0000000..4b857c1 --- /dev/null +++ b/fon9/web/HttpParser.hpp @@ -0,0 +1,152 @@ +// \file fon9/web/HttpParser.hpp +// \author fonwinz@gmail.com +#ifndef __fon9_web_HttpParser_hpp__ +#define __fon9_web_HttpParser_hpp__ +#include "fon9/StrTools.hpp" +#include "fon9/SortedVector.hpp" +#include "fon9/buffer/BufferList.hpp" + +namespace fon9 { namespace web { + +enum : size_t { + kHttpContentLengthChunked = static_cast(-1), + kHttpMaxChunkedSize = 1024 * 1024 * 8, + /// strlen("chunk-size[chunk-ext]\r\n"); + kHttpMaxChunkLineLength = 64, +}; + +enum class HttpResult { + HeaderTooLarge = -1, + BadChunked = -2, + /// chunked size 超過 kHttpMaxChunkedSize + ChunkSizeTooLarge = -3, + ChunkSizeLineTooLong = -4, + Incomplete = 0, + FullMessage = 1, + ChunkAppended = 2, +}; + +struct fon9_API HttpParser; +struct fon9_API HttpMessage { + HttpMessage() : HeaderFields_{Compare{&OrigStr_}} { + } + + /// 一筆 FullMessage 處理完畢後的清理. + void RemoveFullMessage(); + + /// 通常用於 TcpClient 斷線後的清理工作. + void ClearAll(); + + /// - 移除前後空白之後的 start-line. + /// - e.g. "GET /index.html HTTP/1.1" + /// - e.g. "HTTP/1.1 200 OK" + /// - 後續可用 StrFetchNoTrim(startline, ' '); 取得各個元素的內容, e.g.: + /// \code + /// StrView method = StrFetchNoTrim(startline, ' '); + /// StrView target = StrFetchNoTrim(startline, ' '); + /// StrView version = startline; + /// \endcode + StrView StartLine() const { + return this->StartLine_.ToStrView(this->OrigStr_.c_str()); + } + /// 傳回值==nullptr, 則表示沒有設該欄位. + StrView FindHeadField(StrView name) { + auto ifind = this->HeaderFields_.find(name); + return ifind == this->HeaderFields_.end() ? StrView{nullptr} + : ifind->second.ToStrView(this->OrigStr_.c_str()); + } + StrView Body() const { + return this->Body_.ToStrView(this->OrigStr_.c_str()); + } + /// 當解析結果為 HttpResult::ChunkAppended, 則可取得此次新增的部分. + StrView ChunkData() const { + const char* origBegin = this->OrigStr_.c_str(); + return StrView{origBegin + this->CurrChunkFrom_, origBegin + this->Body_.End()}; + } + /// 尚未解析的 chunk-ext. + StrView ChunkExt() const { + return &this->ChunkExt_; + } + /// 尚未解析的 chunk-trailer. + StrView ChunkTrailer() const { + return this->ChunkTrailer_.ToStrView(this->OrigStr_.c_str()); + } + + bool IsHeaderReady() const { + return this->StartLine_.Size_ != 0; + } + bool IsChunked() const { + return (this->ContentLength_ == kHttpContentLengthChunked); + } + +private: + void ClearFields(); + + friend struct fon9_API HttpParser; + struct SubStr { + size_t Pos_{0}; + size_t Size_{0}; + StrView ToStrView(const char* from) const { + return StrView{from + this->Pos_, this->Size_}; + } + void FromStrView(const char* from, StrView s) { + this->Pos_ = static_cast(s.begin() - from); + this->Size_ = s.size(); + } + size_t End() const { + return this->Pos_ + this->Size_; + } + }; + + /// 完整的訊息內容. + std::string OrigStr_; + SubStr StartLine_; + SubStr Body_; + + /// 優先順序如下: + /// Transfer-Encoding: chunked 則此值為 kHttpContentLengthChunked. + /// Content-Length: 所設定的長度. + size_t ContentLength_{0}; + + size_t NextChunkSize_{0}; + size_t CurrChunkFrom_{0}; + + /// 尚未解析的 chunk-ext. + /// 因為 chunk-size[;chunk-ext] 在收到 chunk-data 時, + /// 會從 body 移除, 讓 body 成為沒有 chunk-size 的「乾淨」訊息, + /// 所以需要用額外的空間儲存 ChunkExt_; + std::string ChunkExt_; + /// 尚未解析的 chunk-trailer. + SubStr ChunkTrailer_; + + struct Compare { + std::string* OrigStr_; + bool operator()(const SubStr& a, const SubStr& b) const { + return icompare(a.ToStrView(this->OrigStr_->c_str()), b.ToStrView(this->OrigStr_->c_str())) < 0; + } + bool operator()(const StrView& a, const SubStr& b) const { + return icompare(a, b.ToStrView(this->OrigStr_->c_str())) < 0; + } + bool operator()(const SubStr& a, const StrView& b) const { + return icompare(a.ToStrView(this->OrigStr_->c_str()), b) < 0; + } + }; + using Fields = SortedVector; + Fields HeaderFields_; +}; + +struct fon9_API HttpParser { + static HttpResult Feed(HttpMessage& msg, BufferList buf); + static HttpResult ContinueEat(HttpMessage& msg); + +private: + static HttpResult AfterFeedBody(HttpMessage& msg); + static HttpResult AfterFeedHeader(HttpMessage& msg, size_t bfsz); + static HttpResult ParseChunk(HttpMessage& msg); + static HttpResult CheckChunkAppend(HttpMessage& msg); + static HttpResult FetchNextChunkSize(HttpMessage& msg); + static HttpResult FetchChunkTrailer(HttpMessage& msg); +}; + +} } // namespaces +#endif//__fon9_web_HttpParser_hpp__