mirror of
https://onedev.site.tesses.net/tesses-framework
synced 2026-02-08 07:45:46 +00:00
Add route server
This commit is contained in:
@@ -9,6 +9,7 @@ src/Random.cpp
|
|||||||
src/Date/Date.cpp
|
src/Date/Date.cpp
|
||||||
src/Http/FileServer.cpp
|
src/Http/FileServer.cpp
|
||||||
src/Http/MountableServer.cpp
|
src/Http/MountableServer.cpp
|
||||||
|
src/Http/RouteServer.cpp
|
||||||
src/Http/CallbackServer.cpp
|
src/Http/CallbackServer.cpp
|
||||||
src/Http/HttpServer.cpp
|
src/Http/HttpServer.cpp
|
||||||
src/Http/HttpUtils.cpp
|
src/Http/HttpUtils.cpp
|
||||||
|
|||||||
@@ -139,11 +139,35 @@ class MyOtherWebServer : public IHttpServer
|
|||||||
int main(int argc, char** argv)
|
int main(int argc, char** argv)
|
||||||
{
|
{
|
||||||
TF_InitWithConsole();
|
TF_InitWithConsole();
|
||||||
|
std::shared_ptr<RouteServer> routeSvr = std::make_shared<RouteServer>();
|
||||||
|
routeSvr->Get("/name/{name}/greeting",[](ServerContext& ctx)->bool{
|
||||||
|
std::string name;
|
||||||
|
if(ctx.pathArguments.TryGetFirst("name",name))
|
||||||
|
{
|
||||||
|
ctx.WithMimeType("text/plain").SendText("Hello " + name);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ctx.WithMimeType("text/plain").SendText("Please provide a name");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
routeSvr->Get("/name/{name}/length",[](ServerContext& ctx)->bool{
|
||||||
|
std::string name;
|
||||||
|
if(ctx.pathArguments.TryGetFirst("name",name))
|
||||||
|
{
|
||||||
|
ctx.WithMimeType("text/plain").SendText("The length of the name is " + std::to_string(name.size()));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ctx.WithMimeType("text/plain").SendText("Please provide a name");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
std::shared_ptr<MyOtherWebServer> myo = std::make_shared<MyOtherWebServer>();
|
std::shared_ptr<MyOtherWebServer> myo = std::make_shared<MyOtherWebServer>();
|
||||||
std::shared_ptr<MyWebServer> mws = std::make_shared<MyWebServer>();
|
std::shared_ptr<MyWebServer> mws = std::make_shared<MyWebServer>();
|
||||||
|
|
||||||
std::shared_ptr<MountableServer> mountable = std::make_shared<MountableServer>(myo);
|
std::shared_ptr<MountableServer> mountable = std::make_shared<MountableServer>(myo);
|
||||||
mountable->Mount("/mymount/",mws);
|
mountable->Mount("/mymount/",mws);
|
||||||
|
mountable->Mount("/routeSvr/", routeSvr);
|
||||||
HttpServer server(10001,mountable);
|
HttpServer server(10001,mountable);
|
||||||
server.StartAccepting();
|
server.StartAccepting();
|
||||||
TF_RunEventLoop();
|
TF_RunEventLoop();
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ namespace Tesses::Framework::Filesystem
|
|||||||
VFSPath(VFSPath p, std::string subent);
|
VFSPath(VFSPath p, std::string subent);
|
||||||
VFSPath(VFSPath p, VFSPath p2);
|
VFSPath(VFSPath p, VFSPath p2);
|
||||||
|
|
||||||
|
//does not check for ?
|
||||||
|
static VFSPath ParseUriPath(std::string path);
|
||||||
|
|
||||||
VFSPath GetParent() const;
|
VFSPath GetParent() const;
|
||||||
VFSPath CollapseRelativeParents() const;
|
VFSPath CollapseRelativeParents() const;
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ namespace Tesses::Framework::Http
|
|||||||
HttpDictionary requestHeaders;
|
HttpDictionary requestHeaders;
|
||||||
HttpDictionary responseHeaders;
|
HttpDictionary responseHeaders;
|
||||||
HttpDictionary queryParams;
|
HttpDictionary queryParams;
|
||||||
|
//used by path
|
||||||
|
HttpDictionary pathArguments;
|
||||||
std::string path;
|
std::string path;
|
||||||
std::string originalPath;
|
std::string originalPath;
|
||||||
std::string method;
|
std::string method;
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ namespace Tesses::Framework::Http
|
|||||||
int64_t GetPosition();
|
int64_t GetPosition();
|
||||||
size_t Read(uint8_t* buffer, size_t len);
|
size_t Read(uint8_t* buffer, size_t len);
|
||||||
size_t Write(const uint8_t* buffer, size_t len);
|
size_t Write(const uint8_t* buffer, size_t len);
|
||||||
|
void Close();
|
||||||
~HttpStream();
|
~HttpStream();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
41
include/TessesFramework/Http/RouteServer.hpp
Normal file
41
include/TessesFramework/Http/RouteServer.hpp
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "HttpServer.hpp"
|
||||||
|
#include "../Filesystem/VFSFix.hpp"
|
||||||
|
#include "../Filesystem/VFS.hpp"
|
||||||
|
|
||||||
|
namespace Tesses::Framework::Http
|
||||||
|
{
|
||||||
|
using ServerRequestHandler = std::function<bool(ServerContext&)>;
|
||||||
|
|
||||||
|
|
||||||
|
class RouteServer : public IHttpServer
|
||||||
|
{
|
||||||
|
class RouteServerRoute {
|
||||||
|
public:
|
||||||
|
std::vector<std::pair<std::string,bool>> parts;
|
||||||
|
std::string method;
|
||||||
|
ServerRequestHandler handler;
|
||||||
|
|
||||||
|
RouteServerRoute() = default;
|
||||||
|
RouteServerRoute(std::string route, std::string method, ServerRequestHandler handler);
|
||||||
|
bool Equals(Tesses::Framework::Filesystem::VFSPath& path, HttpDictionary& args);
|
||||||
|
};
|
||||||
|
std::vector<RouteServerRoute> routes;
|
||||||
|
std::shared_ptr<IHttpServer> root;
|
||||||
|
public:
|
||||||
|
RouteServer() = default;
|
||||||
|
RouteServer(std::shared_ptr<IHttpServer> root);
|
||||||
|
void Get(std::string pattern, ServerRequestHandler handler);
|
||||||
|
void Post(std::string pattern, ServerRequestHandler handler);
|
||||||
|
void Put(std::string pattern, ServerRequestHandler handler);
|
||||||
|
void Patch(std::string pattern, ServerRequestHandler handler);
|
||||||
|
|
||||||
|
void Delete(std::string pattern, ServerRequestHandler handler);
|
||||||
|
|
||||||
|
void Trace(std::string pattern, ServerRequestHandler handler);
|
||||||
|
void Options(std::string pattern, ServerRequestHandler handler);
|
||||||
|
void Add(std::string method, std::string pattern, ServerRequestHandler handler);
|
||||||
|
bool Handle(ServerContext& ctx);
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "Http/ChangeableServer.hpp"
|
#include "Http/ChangeableServer.hpp"
|
||||||
#include "Http/CGIServer.hpp"
|
#include "Http/CGIServer.hpp"
|
||||||
#include "Http/BasicAuthServer.hpp"
|
#include "Http/BasicAuthServer.hpp"
|
||||||
|
#include "Http/RouteServer.hpp"
|
||||||
#include "Streams/FileStream.hpp"
|
#include "Streams/FileStream.hpp"
|
||||||
#include "Streams/MemoryStream.hpp"
|
#include "Streams/MemoryStream.hpp"
|
||||||
#include "Streams/NetworkStream.hpp"
|
#include "Streams/NetworkStream.hpp"
|
||||||
|
|||||||
@@ -330,6 +330,35 @@ namespace Tesses::Framework::Filesystem
|
|||||||
this->path = p;
|
this->path = p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VFSPath VFSPath::ParseUriPath(std::string path)
|
||||||
|
{
|
||||||
|
std::string builder = {};
|
||||||
|
VFSPath vpath;
|
||||||
|
vpath.relative=true;
|
||||||
|
|
||||||
|
if(!path.empty() && path[0] == '/') vpath.relative=false;
|
||||||
|
|
||||||
|
for(auto item : path)
|
||||||
|
{
|
||||||
|
if(item == '/')
|
||||||
|
{
|
||||||
|
if(!builder.empty())
|
||||||
|
{
|
||||||
|
vpath.path.push_back(builder);
|
||||||
|
builder.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
builder.push_back(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!builder.empty())
|
||||||
|
{
|
||||||
|
vpath.path.push_back(builder);
|
||||||
|
}
|
||||||
|
return vpath;
|
||||||
|
}
|
||||||
|
|
||||||
bool VFSPath::HasExtension() const
|
bool VFSPath::HasExtension() const
|
||||||
{
|
{
|
||||||
if(this->path.empty()) return false;
|
if(this->path.empty()) return false;
|
||||||
@@ -377,6 +406,9 @@ namespace Tesses::Framework::Filesystem
|
|||||||
if(!str.empty())
|
if(!str.empty())
|
||||||
{
|
{
|
||||||
if(str.front() == '/') this->relative=false;
|
if(str.front() == '/') this->relative=false;
|
||||||
|
#if defined(_WIN32)
|
||||||
|
if(str.front() == '\\') this->relative=false;
|
||||||
|
#endif
|
||||||
if(!this->path.empty())
|
if(!this->path.empty())
|
||||||
{
|
{
|
||||||
auto firstPartPath = this->path.front();
|
auto firstPartPath = this->path.front();
|
||||||
|
|||||||
@@ -566,7 +566,12 @@ namespace Tesses::Framework::Http
|
|||||||
this->responseHeaders.SetValue("Transfer-Encoding","chunked");
|
this->responseHeaders.SetValue("Transfer-Encoding","chunked");
|
||||||
|
|
||||||
this->WriteHeaders();
|
this->WriteHeaders();
|
||||||
return std::make_shared<HttpStream>(this->strm,length,false,version == "HTTP/1.1");
|
auto strm = std::make_shared<HttpStream>(this->strm,length,false,version == "HTTP/1.1");
|
||||||
|
if(method == "HEAD")
|
||||||
|
{
|
||||||
|
strm->Close();
|
||||||
|
}
|
||||||
|
return strm;
|
||||||
}
|
}
|
||||||
std::shared_ptr<Stream> ServerContext::OpenRequestStream()
|
std::shared_ptr<Stream> ServerContext::OpenRequestStream()
|
||||||
{
|
{
|
||||||
@@ -788,23 +793,26 @@ namespace Tesses::Framework::Http
|
|||||||
this->WithSingleHeader("Content-Range","bytes " + std::to_string(begin) + "-" + std::to_string(end) + "/" + std::to_string(len));
|
this->WithSingleHeader("Content-Range","bytes " + std::to_string(begin) + "-" + std::to_string(end) + "/" + std::to_string(len));
|
||||||
this->statusCode = PartialContent;
|
this->statusCode = PartialContent;
|
||||||
this->WriteHeaders();
|
this->WriteHeaders();
|
||||||
strm->Seek(begin,SeekOrigin::Begin);
|
if(this->method != "HEAD")
|
||||||
|
{
|
||||||
|
strm->Seek(begin,SeekOrigin::Begin);
|
||||||
|
|
||||||
uint8_t buffer[1024];
|
uint8_t buffer[1024];
|
||||||
|
|
||||||
size_t read=0;
|
size_t read=0;
|
||||||
do {
|
|
||||||
read = sizeof(buffer);
|
do {
|
||||||
myLen = (end - begin)+1;
|
read = sizeof(buffer);
|
||||||
if(myLen < read) read = (size_t)myLen;
|
myLen = (end - begin)+1;
|
||||||
if(read == 0) break;
|
if(myLen < read) read = (size_t)myLen;
|
||||||
read = strm->Read(buffer,read);
|
if(read == 0) break;
|
||||||
if(read == 0) break;
|
read = strm->Read(buffer,read);
|
||||||
this->strm->WriteBlock(buffer,read);
|
if(read == 0) break;
|
||||||
|
this->strm->WriteBlock(buffer,read);
|
||||||
begin += read;
|
|
||||||
} while(read > 0 && !this->strm->EndOfStream());
|
|
||||||
|
|
||||||
|
begin += read;
|
||||||
|
} while(read > 0 && !this->strm->EndOfStream());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -821,6 +829,7 @@ namespace Tesses::Framework::Http
|
|||||||
this->WithSingleHeader("Accept-Range","bytes");
|
this->WithSingleHeader("Accept-Range","bytes");
|
||||||
this->WithSingleHeader("Content-Length",std::to_string(len));
|
this->WithSingleHeader("Content-Length",std::to_string(len));
|
||||||
this->WriteHeaders();
|
this->WriteHeaders();
|
||||||
|
if(this->method != "HEAD")
|
||||||
strm->CopyTo(this->strm);
|
strm->CopyTo(this->strm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -828,8 +837,10 @@ namespace Tesses::Framework::Http
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
||||||
auto chunkedStream = this->OpenResponseStream();
|
auto chunkedStream = this->OpenResponseStream();
|
||||||
|
|
||||||
|
if(method != "HEAD")
|
||||||
strm->CopyTo(chunkedStream);
|
strm->CopyTo(chunkedStream);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ namespace Tesses::Framework::Http
|
|||||||
}
|
}
|
||||||
bool HttpStream::CanWrite()
|
bool HttpStream::CanWrite()
|
||||||
{
|
{
|
||||||
|
if(this->done) return false;
|
||||||
if(this->recv) return false;
|
if(this->recv) return false;
|
||||||
return this->strm->CanWrite();
|
return this->strm->CanWrite();
|
||||||
}
|
}
|
||||||
@@ -118,6 +119,7 @@ namespace Tesses::Framework::Http
|
|||||||
}
|
}
|
||||||
size_t HttpStream::Write(const uint8_t* buff, size_t len)
|
size_t HttpStream::Write(const uint8_t* buff, size_t len)
|
||||||
{
|
{
|
||||||
|
if(this->done) return 0;
|
||||||
if(this->recv) return 0;
|
if(this->recv) return 0;
|
||||||
if(this->length == 0) return 0;
|
if(this->length == 0) return 0;
|
||||||
if(this->length > 0)
|
if(this->length > 0)
|
||||||
@@ -153,9 +155,20 @@ namespace Tesses::Framework::Http
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
void HttpStream::Close()
|
||||||
|
{
|
||||||
|
if(this->length == -1 && this->http1_1 && !done && !this->recv)
|
||||||
|
{
|
||||||
|
this->done=true;
|
||||||
|
StreamWriter writer(this->strm);
|
||||||
|
writer.newline = "\r\n";
|
||||||
|
writer.WriteLine("0");
|
||||||
|
writer.WriteLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
HttpStream::~HttpStream()
|
HttpStream::~HttpStream()
|
||||||
{
|
{
|
||||||
if(this->length == -1 && this->http1_1)
|
if(this->length == -1 && this->http1_1 && !done && !this->recv)
|
||||||
{
|
{
|
||||||
StreamWriter writer(this->strm);
|
StreamWriter writer(this->strm);
|
||||||
writer.newline = "\r\n";
|
writer.newline = "\r\n";
|
||||||
|
|||||||
97
src/Http/RouteServer.cpp
Normal file
97
src/Http/RouteServer.cpp
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
#include "TessesFramework/Http/RouteServer.hpp"
|
||||||
|
|
||||||
|
namespace Tesses::Framework::Http
|
||||||
|
{
|
||||||
|
|
||||||
|
RouteServer::RouteServerRoute::RouteServerRoute(std::string route, std::string method, ServerRequestHandler handler) : method(method), handler(handler)
|
||||||
|
{
|
||||||
|
auto path = Tesses::Framework::Filesystem::VFSPath::ParseUriPath(route);
|
||||||
|
for(auto item : path.path)
|
||||||
|
{
|
||||||
|
if(item.size() > 2 && item[0] == '{' && item[item.size()-1] == '}')
|
||||||
|
{
|
||||||
|
this->parts.emplace_back( item.substr(1,item.size()-2),true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this->parts.emplace_back(item,false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool RouteServer::RouteServerRoute::Equals(Tesses::Framework::Filesystem::VFSPath& path, HttpDictionary& args)
|
||||||
|
{
|
||||||
|
if(path.path.size() != this->parts.size()) return false;
|
||||||
|
|
||||||
|
|
||||||
|
for(size_t i = 0; i < this->parts.size(); i++)
|
||||||
|
{
|
||||||
|
auto& part = this->parts[i];
|
||||||
|
if(part.second)
|
||||||
|
args.SetValue(part.first, Tesses::Framework::Http::HttpUtils::UrlPathDecode(path.path[i]));
|
||||||
|
else if(part.first != path.path[i]) return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
RouteServer::RouteServer(std::shared_ptr<IHttpServer> root) : root(root)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouteServer::Add(std::string method, std::string pattern, ServerRequestHandler handler)
|
||||||
|
{
|
||||||
|
this->routes.emplace_back(pattern,method,handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RouteServer::Handle(ServerContext& ctx)
|
||||||
|
{
|
||||||
|
auto pathArgs = ctx.pathArguments;
|
||||||
|
auto path = Tesses::Framework::Filesystem::VFSPath::ParseUriPath(ctx.path);
|
||||||
|
for(auto& svr : this->routes)
|
||||||
|
{
|
||||||
|
if(svr.method != ctx.method) continue;
|
||||||
|
ctx.pathArguments = pathArgs;
|
||||||
|
if(svr.Equals(path, ctx.pathArguments) && svr.handler && svr.handler(ctx))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
ctx.pathArguments = pathArgs;
|
||||||
|
|
||||||
|
if(this->root)
|
||||||
|
return this->root->Handle(ctx);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouteServer::Get(std::string pattern, ServerRequestHandler handler)
|
||||||
|
{
|
||||||
|
Add("GET",pattern,handler);
|
||||||
|
}
|
||||||
|
void RouteServer::Post(std::string pattern, ServerRequestHandler handler)
|
||||||
|
{
|
||||||
|
Add("POST",pattern,handler);
|
||||||
|
}
|
||||||
|
void RouteServer::Put(std::string pattern, ServerRequestHandler handler)
|
||||||
|
{
|
||||||
|
Add("PUT",pattern,handler);
|
||||||
|
}
|
||||||
|
void RouteServer::Patch(std::string pattern, ServerRequestHandler handler)
|
||||||
|
{
|
||||||
|
Add("PATCH",pattern,handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouteServer::Delete(std::string pattern, ServerRequestHandler handler)
|
||||||
|
{
|
||||||
|
Add("DELETE",pattern,handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RouteServer::Trace(std::string pattern, ServerRequestHandler handler)
|
||||||
|
{
|
||||||
|
Add("TRACE",pattern,handler);
|
||||||
|
}
|
||||||
|
void RouteServer::Options(std::string pattern, ServerRequestHandler handler)
|
||||||
|
{
|
||||||
|
Add("OPTIONS",pattern,handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -48,8 +48,13 @@ namespace Tesses::Framework::Streams {
|
|||||||
if(len < 1024)
|
if(len < 1024)
|
||||||
read = len;
|
read = len;
|
||||||
if(read > 0)
|
if(read > 0)
|
||||||
|
{
|
||||||
read=this->Write(buffer,read);
|
read=this->Write(buffer,read);
|
||||||
|
if(read == 0)
|
||||||
|
{
|
||||||
|
throw std::out_of_range("Failed to write!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
buffer += read;
|
buffer += read;
|
||||||
@@ -109,7 +114,6 @@ namespace Tesses::Framework::Streams {
|
|||||||
read = (size_t)std::min(len-offset,(uint64_t)buffSize);
|
read = (size_t)std::min(len-offset,(uint64_t)buffSize);
|
||||||
|
|
||||||
read = this->Read(buffer,read);
|
read = this->Read(buffer,read);
|
||||||
|
|
||||||
strm->WriteBlock(buffer, read);
|
strm->WriteBlock(buffer, read);
|
||||||
|
|
||||||
offset += read;
|
offset += read;
|
||||||
|
|||||||
Reference in New Issue
Block a user