From 4ce35260479d609cf2dfbacd69aeba934084e6c5 Mon Sep 17 00:00:00 2001 From: Mike Nolan Date: Sun, 12 Jan 2025 19:32:19 -0600 Subject: [PATCH] Add server content data and mountableserver --- CMakeLists.txt | 2 + examples/webserverex.cpp | 63 ++++++++++---- include/TessesFramework/Common.hpp | 19 +++++ .../TessesFramework/Http/CallbackServer.hpp | 16 ++++ include/TessesFramework/Http/HttpServer.hpp | 21 ++++- .../TessesFramework/Http/MountableServer.hpp | 25 ++++++ include/TessesFramework/TessesFramework.hpp | 2 + src/Http/CallbackServer.cpp | 22 +++++ src/Http/HttpServer.cpp | 18 ++++ src/Http/MountableServer.cpp | 85 +++++++++++++++++++ src/Streams/FileStream.cpp | 11 +++ 11 files changed, 265 insertions(+), 19 deletions(-) create mode 100644 include/TessesFramework/Http/CallbackServer.hpp create mode 100644 include/TessesFramework/Http/MountableServer.hpp create mode 100644 src/Http/CallbackServer.cpp create mode 100644 src/Http/MountableServer.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index da8a616..6798824 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,8 @@ set(CMAKE_CXX_STANDARD 20) list(APPEND TESSESFRAMEWORK_SOURCE src/Http/FileServer.cpp +src/Http/MountableServer.cpp +src/Http/CallbackServer.cpp src/Http/HttpServer.cpp src/Http/HttpUtils.cpp src/Http/HttpClient.cpp diff --git a/examples/webserverex.cpp b/examples/webserverex.cpp index fa04880..d95eb83 100644 --- a/examples/webserverex.cpp +++ b/examples/webserverex.cpp @@ -5,11 +5,24 @@ using namespace Tesses::Framework::Http; using namespace Tesses::Framework::Streams; using namespace Tesses::Framework::TextStreams; using namespace Tesses::Framework::Threading; - +class Johnny : public ServerContextData +{ + public: + Johnny() + { + text = "Steve Ballmer"; + } + std::string text; + ~Johnny() + { + std::cout << "Destroying" << std::endl; + } +}; class MyWebServer : public IHttpServer { public: bool Handle(ServerContext& ctx) { + std::cout << ctx.path << std::endl; if(ctx.path == "/") { FileStream fs("index.html","rb"); @@ -58,10 +71,23 @@ class MyWebServer : public IHttpServer { return new FileStream(filename,"wb"); }); } + else if(ctx.path == "/steve") + { + Johnny* data = ctx.GetServerContentData("mytag"); + data->text = "Demi Lovato"; + } + return false; } - bool Handle1(ServerContext& ctx) + +}; +class MyOtherWebServer : public IHttpServer +{ + public: + bool Handle(ServerContext& ctx) { + if(ctx.path == "/") + { std::string name; if(ctx.queryParams.TryGetFirst("name",name)) { @@ -74,6 +100,14 @@ class MyWebServer : public IHttpServer { return true; } + } + else if(ctx.path == "/mymount/steve") + { + Johnny* data = ctx.GetServerContentData("mytag"); + ctx.SendText(data->text); + return true; + } + return false; } }; @@ -81,20 +115,13 @@ class MyWebServer : public IHttpServer { int main(int argc, char** argv) { TF_Init(); - TcpServer server(9985U,10); - MyWebServer svr; - while(true) - { - std::string ip; - uint16_t port; - auto res = server.GetStream(ip, port); - if(res == nullptr) return 0; - std::cout << "Got from " << ip << ":" << port << std::endl; - Thread thrd([ip,port,res,&svr]()->void { - HttpServer::Process(*res, svr, ip,port, false); - delete res; - }); - thrd.Detach(); - } - + MyOtherWebServer myo; + MyWebServer mws; + + MountableServer mountable(myo); + mountable.Mount("/mymount/",mws); + HttpServer server(10001,mountable); + server.StartAccepting(); + TF_RunEventLoop(); + std::cout << "Closing server" << std::endl; } \ No newline at end of file diff --git a/include/TessesFramework/Common.hpp b/include/TessesFramework/Common.hpp index e3342b7..b3ec5a2 100644 --- a/include/TessesFramework/Common.hpp +++ b/include/TessesFramework/Common.hpp @@ -8,6 +8,25 @@ #include #include + + class TextException : public std::exception { + + std::string error_message; + public: + + TextException(std::string ex) + { + error_message = "TextException: "; + error_message.append(ex); + } + + + + const char * what() const noexcept override + { + return error_message.c_str(); + } + }; namespace Tesses::Framework { void TF_Init(); diff --git a/include/TessesFramework/Http/CallbackServer.hpp b/include/TessesFramework/Http/CallbackServer.hpp new file mode 100644 index 0000000..291bdf6 --- /dev/null +++ b/include/TessesFramework/Http/CallbackServer.hpp @@ -0,0 +1,16 @@ +#pragma once +#include "HttpServer.hpp" + +namespace Tesses::Framework::Http +{ + class CallbackServer : public IHttpServer + { + std::function cb; + std::function destroy; + public: + CallbackServer(std::function cb); + CallbackServer(std::function cb,std::function destroy); + bool Handle(ServerContext& ctx); + ~CallbackServer(); + }; +} \ No newline at end of file diff --git a/include/TessesFramework/Http/HttpServer.hpp b/include/TessesFramework/Http/HttpServer.hpp index ffb8500..19f7325 100644 --- a/include/TessesFramework/Http/HttpServer.hpp +++ b/include/TessesFramework/Http/HttpServer.hpp @@ -3,12 +3,18 @@ #include "HttpUtils.hpp" #include "../Threading/Thread.hpp" +#include namespace Tesses::Framework::Http { - + class ServerContextData { + public: + virtual ~ServerContextData(); + }; + class ServerContext { bool sent; Tesses::Framework::Streams::Stream* strm; + std::map data; public: HttpDictionary requestHeaders; HttpDictionary responseHeaders; @@ -22,6 +28,7 @@ namespace Tesses::Framework::Http std::string version; bool encrypted; ServerContext(Tesses::Framework::Streams::Stream* strm); + ~ServerContext(); Tesses::Framework::Streams::Stream& GetStream(); std::string GetOriginalPathWithQuery(); std::string GetUrlWithQuery(); @@ -46,6 +53,18 @@ namespace Tesses::Framework::Http ServerContext& WithMimeType(std::string mime); ServerContext& WithContentDisposition(std::string filename, bool isInline); ServerContext& WriteHeaders(); + + template + T* GetServerContentData(std::string tag) + { + std::string name = typeid(T).name(); + name.push_back(' '); + name.append(tag); + if(data.count(name) > 0) return dynamic_cast(data[name]); + T* item = new T(); + data[name] = item; + return item; + } }; class IHttpServer { diff --git a/include/TessesFramework/Http/MountableServer.hpp b/include/TessesFramework/Http/MountableServer.hpp new file mode 100644 index 0000000..d60a719 --- /dev/null +++ b/include/TessesFramework/Http/MountableServer.hpp @@ -0,0 +1,25 @@ +#pragma once +#include "HttpServer.hpp" +#include "../Filesystem/VFSFix.hpp" +#include "../Filesystem/VFS.hpp" + +namespace Tesses::Framework::Http +{ + class MountableServer : public IHttpServer + { + IHttpServer* root; + bool owns; + std::vector>> servers; + std::string Subpath(Filesystem::VFSPath fullPath, Filesystem::VFSPath offsetPath); + bool StartsWith(Filesystem::VFSPath fullPath, Filesystem::VFSPath offsetPath); + public: + MountableServer(); + MountableServer(IHttpServer* root, bool owns); + MountableServer(IHttpServer& root); + void Mount(std::string path, IHttpServer* server, bool owns); + void Mount(std::string path, IHttpServer& server); + void Unmount(std::string path); + bool Handle(ServerContext& ctx); + ~MountableServer(); + }; +} \ No newline at end of file diff --git a/include/TessesFramework/TessesFramework.hpp b/include/TessesFramework/TessesFramework.hpp index 4b20fcf..211e207 100644 --- a/include/TessesFramework/TessesFramework.hpp +++ b/include/TessesFramework/TessesFramework.hpp @@ -2,6 +2,8 @@ #include "Http/HttpServer.hpp" #include "Http/HttpClient.hpp" #include "Http/FileServer.hpp" +#include "Http/CallbackServer.hpp" +#include "Http/MountableServer.hpp" #include "Http/ContentDisposition.hpp" #include "Streams/FileStream.hpp" #include "Streams/MemoryStream.hpp" diff --git a/src/Http/CallbackServer.cpp b/src/Http/CallbackServer.cpp new file mode 100644 index 0000000..2123c4d --- /dev/null +++ b/src/Http/CallbackServer.cpp @@ -0,0 +1,22 @@ +#include "TessesFramework/Http/CallbackServer.hpp" + +namespace Tesses::Framework::Http +{ + CallbackServer::CallbackServer(std::function cb) : CallbackServer(cb,[]()->void{}) + { + + } + CallbackServer::CallbackServer(std::function cb,std::function destroy) + { + this->cb = cb; + this->destroy=destroy; + } + bool CallbackServer::Handle(ServerContext& ctx) + { + return this->cb(ctx); + } + CallbackServer::~CallbackServer() + { + this->destroy(); + } +} \ No newline at end of file diff --git a/src/Http/HttpServer.cpp b/src/Http/HttpServer.cpp index 0681720..285b202 100644 --- a/src/Http/HttpServer.cpp +++ b/src/Http/HttpServer.cpp @@ -379,10 +379,23 @@ namespace Tesses::Framework::Http { if(strm == nullptr) return; SendStream(*strm); + } + ServerContext::~ServerContext() + { + for(auto item : this->data) + { + delete item.second; + } + } + ServerContextData::~ServerContextData() + { + } void ServerContext::SendStream(Stream& strm) { if(sent) return; + if(!strm.CanRead()) throw TextException("Cannot read from stream"); + if(strm.EndOfStream()) throw TextException("End of stream"); if(strm.CanSeek()) { int64_t len=strm.GetLength(); @@ -666,6 +679,11 @@ namespace Tesses::Framework::Http { ctx.SendException(ex); } + catch(...) + { + TextException ex("An unknown error occurred"); + ctx.SendException(ex); + } if(ctx.version != "HTTP/1.1" ) return; diff --git a/src/Http/MountableServer.cpp b/src/Http/MountableServer.cpp new file mode 100644 index 0000000..dc9318b --- /dev/null +++ b/src/Http/MountableServer.cpp @@ -0,0 +1,85 @@ +#include "TessesFramework/Http/MountableServer.hpp" + +namespace Tesses::Framework::Http { +std::string MountableServer::Subpath(Filesystem::VFSPath fullPath, Filesystem::VFSPath offsetPath) +{ + if(fullPath.path.size() < offsetPath.path.size()) return {}; //this shouldn't happen but here just in case + Filesystem::VFSPath p; + p.relative=false; + + for(size_t i = offsetPath.path.size(); i < fullPath.path.size(); i++) + { + p.path.push_back(fullPath.path[i]); + } + return p.ToString(); +} +bool MountableServer::StartsWith(Filesystem::VFSPath fullPath, Filesystem::VFSPath offsetPath) +{ + if(fullPath.path.size() < offsetPath.path.size()) return false; + for(size_t i = 0; i < offsetPath.path.size(); i++) + { + if(fullPath.path[i] != offsetPath.path[i]) return false; + } + return true; +} +MountableServer::MountableServer() : MountableServer(nullptr,false) +{ + +} +MountableServer::MountableServer(IHttpServer* root, bool owns) +{ + this->root = root; + this->owns = owns; +} +MountableServer::MountableServer(IHttpServer& root) : MountableServer(&root,false) +{ + +} +void MountableServer::Mount(std::string path, IHttpServer* server, bool owns) +{ + this->servers.insert(this->servers.begin(), std::pair>(path, std::pair(owns,server))); +} +void MountableServer::Mount(std::string path, IHttpServer& server) +{ + Mount(path,&server,false); +} +void MountableServer::Unmount(std::string path) +{ + for(auto i = this->servers.begin(); i != this->servers.end(); i++) + { + auto& item = *i; + if(item.first == path) + { + if(item.second.first) delete item.second.second; + this->servers.erase(i); + return; + } + } +} +bool MountableServer::Handle(ServerContext& ctx) +{ + std::string oldPath = ctx.path; + for(auto item : this->servers) + { + if(StartsWith(oldPath, item.first)) + { + ctx.path = Subpath(oldPath, item.first); + if(item.second.second->Handle(ctx)) + { + ctx.path = oldPath; + return true; + } + ctx.path = oldPath; + break; + } + } + ctx.path=oldPath; + if(this->root != nullptr && this->root->Handle(ctx)) return true; + return false; +} +MountableServer::~MountableServer() +{ + if(this->owns) delete this->root; + for(auto svr : this->servers) if(svr.second.first) delete svr.second.second; +} +} \ No newline at end of file diff --git a/src/Streams/FileStream.cpp b/src/Streams/FileStream.cpp index e24a381..d2761a5 100644 --- a/src/Streams/FileStream.cpp +++ b/src/Streams/FileStream.cpp @@ -49,10 +49,12 @@ namespace Tesses::Framework::Streams } size_t FileStream::Read(uint8_t* buff, size_t sz) { + if(!CanRead()) throw TextException("Cannot read from stream"); return fread(buff,1, sz, this->f); } size_t FileStream::Write(const uint8_t* buff, size_t sz) { + if(!CanWrite()) throw TextException("Cannot write to stream"); return fwrite(buff,1, sz, f); } bool FileStream::CanRead() @@ -69,11 +71,14 @@ namespace Tesses::Framework::Streams } bool FileStream::EndOfStream() { + if(!f) return true; return feof(this->f); } int64_t FileStream::GetPosition() { + + if(!f) return 0; #if defined(_WIN32) return (int64_t)_ftelli64(this->f); #else @@ -82,10 +87,14 @@ namespace Tesses::Framework::Streams } void FileStream::Flush() { + + if(!f) return; fflush(this->f); } void FileStream::Seek(int64_t pos, SeekOrigin whence) { + + if(!f) return; #if defined(_WIN32) _fseeki64(this->f,pos,whence == SeekOrigin::Begin ? SEEK_SET : whence == SeekOrigin::Current ? SEEK_CUR : SEEK_END); #else @@ -94,6 +103,8 @@ namespace Tesses::Framework::Streams } FileStream::~FileStream() { + + if(!f) return; if(this->owns) fclose(this->f); }