Add server sent events, change vfs structure, dark mode error pages and dark mode anonydrop
Some checks failed
Build and Deploy on Tag / build-arch (push) Failing after 8s
Build and Deploy on Tag / update-tap (push) Failing after 4s

This commit is contained in:
2026-04-23 02:23:22 -05:00
parent 3807bc5f36
commit 4cdfbc9542
12 changed files with 392 additions and 85 deletions

View File

@@ -6,6 +6,7 @@ on:
env:
PACKAGE_AND_BREW: ${{ secrets.PACKAGE_AND_BREW }}
VERSION: ${{ gitea.ref_name }}
jobs:
build-arch:
@@ -20,3 +21,22 @@ jobs:
- run: chown build:build /home/build/PKGBUILD
- run: chown build:build /home/build/build-arch.sh
- run: su build -c /home/build/build-arch.sh
update-tap:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
ref: "master"
path: "tapdir"
repository: "tesses50/tesses-tap.git"
token: ${{ env.PACKAGE_AND_BREW }}
- run: |
cd tapdir
bash ../Packaging/edit-formula.sh
git config user.name "Tesses Gitea Bot"
git config user.email "noreply@tesses.net"
git add .
git commit -m "Push tesses-framework=${{ env.VERSION }}"
git push

19
Packaging/edit-formula.sh Normal file
View File

@@ -0,0 +1,19 @@
export HASH=`curl https://git.tesses.org/tesses50/tessesframework/archive/$VERSION.tar.gz 2> /dev/null | shasum -a 256 | awk '{print $1}'`
echo "class Tessesframework < Formula" > "Formula/tessesframework.rb"
echo " desc \"\"" >> "Formula/tessesframework.rb"
echo " homepage \"\"" >> "Formula/tessesframework.rb"
echo " url \"https://git.tesses.org/tesses50/tessesframework/archive/$VERSION.tar.gz\"" >> "Formula/tessesframework.rb"
echo " sha256 \"$HASH\"" >> "Formula/tessesframework.rb"
echo " license \"MIT\"" >> "Formula/tessesframework.rb"
echo " depends_on \"cmake\" => :build" >> "Formula/tessesframework.rb"
echo " depends_on \"mbedtls@3\"" >> "Formula/tessesframework.rb"
echo " def install" >> "Formula/tessesframework.rb"
echo " system \"cmake\", \"-S\", \".\", \"-B\", \"build\", \"-DTESSESFRAMEWORK_FETCHCONTENT=OFF\", *std_cmake_args" >> "Formula/tessesframework.rb"
echo " system \"cmake\", \"--build\", \"build\"" >> "Formula/tessesframework.rb"
echo " system \"cmake\", \"--install\", \"build\"" >> "Formula/tessesframework.rb"
echo " end" >> "Formula/tessesframework.rb"
echo " test do" >> "Formula/tessesframework.rb"
echo " system \"true\"" >> "Formula/tessesframework.rb"
echo " end" >> "Formula/tessesframework.rb"
echo "end" >> "Formula/tessesframework.rb"

View File

@@ -1,5 +1,8 @@
# Changelog
## 0.0.3
Add server sent events, change vfs structure, dark mode error pages and dark mode anonydrop
## 0.0.2
Add UUIDs

View File

@@ -8,6 +8,9 @@ using namespace Tesses::Framework::Http;
using namespace Tesses::Framework::Streams;
using namespace Tesses::Framework::TextStreams;
using namespace Tesses::Framework::Threading;
std::shared_ptr<ServerSentEvents> sse = std::make_shared<ServerSentEvents>();
class Johnny : public ServerContextData
{
public:
@@ -65,7 +68,7 @@ class MyWebServer : public IHttpServer {
for(size_t i=0;i<10000; i++)
{
writer.WriteLine("<li>" + std::to_string(i) + "</li>");
TF_Sleep(10);
}
writer.WriteLine("</ul>");
@@ -74,6 +77,15 @@ class MyWebServer : public IHttpServer {
writer.WriteLine("</html>");
return true;
}
else if(ctx.path == "/ssetest.html")
{
}
else if(ctx.path == "/sse")
{
ctx.SendServerSentEvents(sse);
return true;
}
else if(ctx.path == "/main.js")
{
@@ -162,6 +174,13 @@ class MyOtherWebServer : public IHttpServer
int main(int argc, char** argv)
{
TF_InitWithConsole();
int64_t timer = 0;
auto timerHDL= TF_Timer([&timer]()->void{
timer++;
sse->SendData("Timer has ticked " + std::to_string(timer) + " times now");
});
std::shared_ptr<RouteServer> routeSvr = std::make_shared<RouteServer>();
routeSvr->Get("/name/{name}/greeting",[](ServerContext& ctx)->bool{
std::string name;

View File

@@ -76,6 +76,7 @@ namespace Tesses::Framework
void TF_RunEventLoop();
void TF_RunEventLoopItteration();
bool TF_IsRunning();
void TF_Sleep(uint32_t sleepMS);
void TF_SetIsRunning(bool _isRunning);
void TF_Quit();
bool TF_GetConsoleEventsEnabled();

View File

@@ -13,12 +13,30 @@ namespace Tesses::Framework::Http
public:
virtual ~ServerContextData();
};
class ServerContext;
class ServerSentEvents {
std::vector<std::shared_ptr<Tesses::Framework::Streams::Stream>> strms;
Tesses::Framework::Threading::Mutex mtx;
private:
void SendEventRaw(const std::string& evt);
public:
void SendRetry(uint32_t ms);
void SendRetry(std::chrono::milliseconds ms);
void SendRetry(Tesses::Framework::Date::TimeSpan ts);
void SendData(const std::string& message);
void SendData(const std::string& message, const std::string& dataType);
void SendId(const std::string& idVal);
void SendCustomEvent(const std::string& type, const std::string& value);
void SendComment(const std::string& comment);
friend class ServerContext;
};
class ServerContext {
bool sent;
bool debug;
std::vector<std::shared_ptr<ServerSentEvents>> sse;
std::shared_ptr<Tesses::Framework::Streams::Stream> strm;
std::map<std::string,ServerContextData*> data;
std::queue<std::function<bool(ServerContext& ctx)>> headerhandlers;
@@ -54,6 +72,7 @@ namespace Tesses::Framework::Http
void SendNotFound();
void SendBadRequest();
void SendException(std::exception& ex);
void SendServerSentEvents(std::shared_ptr<ServerSentEvents> sse);
std::shared_ptr<Tesses::Framework::Streams::Stream> OpenResponseStream();
std::shared_ptr<Tesses::Framework::Streams::Stream> OpenRequestStream();
ServerContext& WithStatusCode(StatusCode code);
@@ -87,6 +106,9 @@ namespace Tesses::Framework::Http
data[name] = item;
return item;
}
friend class ServerSentEvents;
};
class IHttpServer {

View File

@@ -46,12 +46,17 @@ namespace Tesses::Framework::Streams
void Listen(int32_t backlog);
void Bind(std::string ip, uint16_t port);
void SetBroadcast(bool bC);
void SetReuseAddress(bool reuse);
void SetReusePort(bool reuse);
void SetMulticastTTL(uint8_t ttl);
void SetMulticastMembership(std::string multicastAddress, std::string ifaceIP="0.0.0.0");
std::shared_ptr<NetworkStream> Accept(std::string& ip, uint16_t& port);
size_t Read(uint8_t* buff, size_t sz);
size_t Write(const uint8_t* buff, size_t sz);
size_t ReadFrom(uint8_t* buff, size_t sz, std::string& ip, uint16_t& port);
size_t WriteTo(const uint8_t* buff, size_t sz, std::string ip, uint16_t port);
static std::vector<std::pair<std::string,std::string>> GetIPs(bool ipV6=false);
~NetworkStream();
void SetNoDelay(bool noDelay);
void Close();

View File

@@ -27,6 +27,79 @@ using namespace Tesses::Framework::TextStreams;
namespace Tesses::Framework::Http
{
void ServerSentEvents::SendEventRaw(const std::string& evt)
{
this->mtx.Lock();
for(auto& item : this->strms)
{
try {
StreamWriter writer(item);
writer.newline = "\r\n";
writer.WriteLine(evt);
}catch(...) {
}
}
this->mtx.Unlock();
}
void ServerSentEvents::SendId(const std::string& id)
{
SendCustomEvent("id",id);
}
void ServerSentEvents::SendData(const std::string& message)
{
SendCustomEvent("data", message);
}
void ServerSentEvents::SendComment(const std::string& comment)
{
SendCustomEvent("", comment);
}
void ServerSentEvents::SendCustomEvent(const std::string& etype, const std::string& message)
{
std::string text = etype + ": ";
for(auto item : message)
{
if(item == '\r') continue;
if(item == '\n') {
text += "\r\n" + etype + ": ";
}
else text += item;
}
SendEventRaw(text);
}
void ServerSentEvents::SendData(const std::string& message, const std::string& mtype)
{
std::string text = "event: " + mtype + "\r\ndata: ";
for(auto item : message)
{
if(item == '\r') continue;
if(item == '\n') {
text += "\r\ndata: ";
}
else text += item;
}
SendEventRaw(text);
}
void ServerSentEvents::SendRetry(uint32_t ms)
{
SendCustomEvent("retry", std::to_string(ms));
}
void ServerSentEvents::SendRetry(std::chrono::milliseconds ms)
{
SendCustomEvent("retry", std::to_string(ms.count()));
}
void ServerSentEvents::SendRetry(Tesses::Framework::Date::TimeSpan ts)
{
SendRetry((uint32_t)ts.TotalSeconds() * 1000);
}
class WSServer
{
public:
@@ -282,6 +355,7 @@ namespace Tesses::Framework::Http
this->conn->OnClose(false);
}
};
/*
static int _header_field(multipart_parser* p, const char *at, size_t length)
{
@@ -344,6 +418,7 @@ namespace Tesses::Framework::Http
data->currentHeaders.Clear();
return 0;
}*/
std::string ServerContext::GetUrlWithQuery()
{
if(this->queryParams.kvp.empty()) return this->path;
@@ -364,6 +439,34 @@ namespace Tesses::Framework::Http
}
}
void ServerContext::SendServerSentEvents(std::shared_ptr<ServerSentEvents> sse)
{
this->responseHeaders.SetValue("X-Accel-Buffering","no");
this->responseHeaders.SetValue("Content-Type","text/event-stream");
this->responseHeaders.SetValue("Cache-Control","no-cache");
auto strm = this->OpenResponseStream();
if(strm == nullptr || this->method == "HEAD") return;
sse->mtx.Lock();
sse->strms.push_back(strm);
sse->mtx.Unlock();
while(!this->strm->EndOfStream())
{
TF_Sleep(10);
}
sse->mtx.Lock();
for(auto index = sse->strms.begin(); index != sse->strms.end(); index++)
{
if(*index == strm)
{
sse->strms.erase(index);
break;
}
}
sse->mtx.Unlock();
}
std::string ServerContext::ReadString()
{
if(strm == nullptr) return {};

View File

@@ -160,20 +160,30 @@ namespace Tesses::Framework::Http
if(this->length == -1 && this->http1_1 && !done && !this->recv)
{
this->done=true;
try {
StreamWriter writer(this->strm);
writer.newline = "\r\n";
writer.WriteLine("0");
writer.WriteLine();
}catch(...){
}
}
}
HttpStream::~HttpStream()
{
if(this->length == -1 && this->http1_1 && !done && !this->recv)
{
try {
StreamWriter writer(this->strm);
writer.newline = "\r\n";
writer.WriteLine("0");
writer.WriteLine();
}catch(...) {
}
}
}
}

View File

@@ -1,8 +1,10 @@
#include "TessesFramework/Streams/NetworkStream.hpp"
#include "TessesFramework/Http/HttpUtils.hpp"
#include <TessesFramework/Streams/NetworkStream.hpp>
#include <iostream>
#include <cstring>
using HttpUtils = Tesses::Framework::Http::HttpUtils;
#if defined(TESSESFRAMEWORK_ENABLE_NETWORKING)
@@ -34,6 +36,8 @@ using HttpUtils = Tesses::Framework::Http::HttpUtils;
#endif
#undef min
#pragma comment(lib, "ws2_32.lib")
#else
extern "C" {
@@ -47,6 +51,7 @@ extern "C" {
#if defined(AF_UNIX) && !defined(GEKKO) && !defined(__SWITCH__) && !defined(__PS2__)
#include <sys/un.h>
#endif
#include <poll.h>
}
#endif
@@ -720,10 +725,75 @@ namespace Tesses::Framework::Streams {
return;
}
}
void NetworkStream::SetReuseAddress(bool reuse)
{
if(!this->success) return;
int no = reuse ? 1 : 0;
if (NETWORK_SETSOCKOPT(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&no, sizeof(no)) != 0)
{
this->success=false;
if(this->owns)
NETWORK_CLOSE(this->sock);
}
}
void NetworkStream::SetReusePort(bool reuse)
{
if(!this->success) return;
int no = reuse ? 1 : 0;
if (NETWORK_SETSOCKOPT(sock, SOL_SOCKET, SO_REUSEPORT, (const char*)&no, sizeof(no)) != 0)
{
this->success=false;
if(this->owns)
NETWORK_CLOSE(this->sock);
}
}
void NetworkStream::SetMulticastTTL(uint8_t ttl)
{
if(!this->success) return;
#if defined(IPPROTO_IP) && defined(IP_MULTICAST_TTL)
if (NETWORK_SETSOCKOPT(sock, IPPROTO_IP, IP_MULTICAST_TTL, (const char*)&ttl, sizeof(ttl)) != 0)
{
this->success=false;
if(this->owns)
NETWORK_CLOSE(this->sock);
}
#endif
}
void NetworkStream::SetMulticastMembership(std::string multicastAddress, std::string ifaceIP)
{
if(!this->success) return;
#if defined(IPPROTO_IP) && defined(IP_MULTICAST_TTL)
struct sockaddr_storage maddr;
struct sockaddr_storage iaddr;
bool success = IPParse(multicastAddress, &maddr) && IPParse(ifaceIP, &iaddr);
if(success && maddr.ss_family == AF_INET && iaddr.ss_family == AF_INET)
{
struct ip_mreq req;
req.imr_multiaddr = ((struct sockaddr_in*)&maddr)->sin_addr;
req.imr_interface = ((struct sockaddr_in*)&iaddr)->sin_addr;
if(NETWORK_SETSOCKOPT(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const char*)&req, sizeof(req)) != 0)
{
this->success=false;
if(this->owns)
NETWORK_CLOSE(this->sock);
}
}
#endif
}
void NetworkStream::SetBroadcast(bool bC)
{
if(!this->success) return;
int broadcast = 1;
int broadcast = bC ? 1 : 0;
if (NETWORK_SETSOCKOPT(sock, SOL_SOCKET, SO_BROADCAST, (const char*)&broadcast, sizeof(broadcast)) != 0)
{
this->success=false;
@@ -906,6 +976,23 @@ void NetworkStream::SetBroadcast(bool bC)
{
}
void NetworkStream::SetReuseAddress(bool reuse)
{
}
void NetworkStream::SetReusePort(bool reuse)
{
}
void NetworkStream::SetMulticastTTL(uint8_t ttl)
{
}
void NetworkStream::SetMulticastMembership(std::string multicastAddress, std::string ifaceIP="0.0.0.0")
{
}
std::shared_ptr<NetworkStream> NetworkStream::Accept(std::string& ip, uint16_t& port)
{
return nullptr;

View File

@@ -49,7 +49,9 @@ namespace Tesses::Framework::Streams {
read = len;
if(read > 0)
{
size_t r0=read;
read=this->Write(buffer,read);
if(read == 0)
{
throw std::out_of_range("Failed to write!");

View File

@@ -53,6 +53,10 @@ static GXRModeObj *rmode = NULL;
#endif
#if !defined(_WIN32)
#include <unistd.h>
#endif
namespace Tesses::Framework
{
@@ -85,7 +89,19 @@ namespace Tesses::Framework
cb();
#endif
}
void TF_Sleep(uint32_t sleepMS)
{
#if defined(_WIN32)
Sleep((DWORD)sleepMS);
#else
struct timespec ts;
ts.tv_sec = (time_t)(sleepMS / 1000);
ts.tv_nsec = (sleepMS % 1000) * 1000000;
nanosleep(&ts,NULL);
#endif
}
void TF_ConnectToSelf(uint16_t port)
{
Tesses::Framework::Streams::NetworkStream ns("127.0.0.1",port,false,false,false);