HTTP boundry
This commit is contained in:
@@ -34,7 +34,8 @@ Primary source areas:
|
|||||||
- `src/runtime/shader`: background Slang build bridge and prepared shader artifact types
|
- `src/runtime/shader`: background Slang build bridge and prepared shader artifact types
|
||||||
- `src/runtime/state`: runtime JSON helpers, parameter normalization, and debounced runtime-state persistence
|
- `src/runtime/state`: runtime JSON helpers, parameter normalization, and debounced runtime-state persistence
|
||||||
- `src/runtime/text`: MSDF/MTSDF font atlas build and CPU-side prepared text texture composition
|
- `src/runtime/text`: MSDF/MTSDF font atlas build and CPU-side prepared text texture composition
|
||||||
- `src/control`: HTTP routing, command parsing, OpenAPI state JSON
|
- `src/control`: command parsing, HTTP/WebSocket transport helpers, OpenAPI state JSON
|
||||||
|
- `src/app/RenderCadenceHttpRoutes.*`: this app's current HTTP endpoint map
|
||||||
- `src/preview`: optional non-consuming preview window
|
- `src/preview`: optional non-consuming preview window
|
||||||
- `src/telemetry` and `src/logging`: runtime observation and logging
|
- `src/telemetry` and `src/logging`: runtime observation and logging
|
||||||
|
|
||||||
@@ -149,7 +150,7 @@ Screenshot routes are present in the UI/OpenAPI surface but are not implemented
|
|||||||
|
|
||||||
## Control Surface
|
## Control Surface
|
||||||
|
|
||||||
The HTTP server runs on its own thread. It serves:
|
The HTTP server runs on its own thread. `HttpControlServer` owns socket lifetime, HTTP parsing, static asset helpers, OpenAPI/Swagger helper serving, and WebSocket state transport. `RenderCadenceHttpRoutes` owns this app's current endpoint map:
|
||||||
|
|
||||||
- UI assets
|
- UI assets
|
||||||
- OpenAPI/Swagger docs
|
- OpenAPI/Swagger docs
|
||||||
@@ -167,6 +168,8 @@ Known but not implemented in the current native command path:
|
|||||||
|
|
||||||
Unsupported routes return an action response with `ok: false`.
|
Unsupported routes return an action response with `ok: false`.
|
||||||
|
|
||||||
|
Forks can reuse the HTTP/WebSocket shell without keeping these endpoints by installing a different route callback.
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
Native tests cover the main non-GL contracts:
|
Native tests cover the main non-GL contracts:
|
||||||
@@ -177,7 +180,7 @@ Native tests cover the main non-GL contracts:
|
|||||||
- supported shader catalog
|
- supported shader catalog
|
||||||
- runtime layer restore/reload behavior
|
- runtime layer restore/reload behavior
|
||||||
- runtime-state persistence writer
|
- runtime-state persistence writer
|
||||||
- HTTP command parsing
|
- HTTP transport and app-route dispatch
|
||||||
- frame exchange and input mailbox behavior
|
- frame exchange and input mailbox behavior
|
||||||
- video format and scheduling helpers
|
- video format and scheduling helpers
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ These parts are the useful base for the fork:
|
|||||||
- `src/render/readback`: BGRA8/UYVY8 PBO readback and completed-frame publication.
|
- `src/render/readback`: BGRA8/UYVY8 PBO readback and completed-frame publication.
|
||||||
- `src/platform`: hidden GL window/context support.
|
- `src/platform`: hidden GL window/context support.
|
||||||
- `src/app`: startup, config, video backend factory, runtime layer orchestration, preview, telemetry, and HTTP server hookup.
|
- `src/app`: startup, config, video backend factory, runtime layer orchestration, preview, telemetry, and HTTP server hookup.
|
||||||
- `src/control`, `src/telemetry`, `src/logging`, and `ui`: useful if the new repo still wants a local control surface.
|
- `src/control/http`, `src/telemetry`, `src/logging`, and `ui`: useful if the new repo still wants a local control surface.
|
||||||
|
- `src/app/RenderCadenceHttpRoutes.*`: useful only if the new repo keeps this app's current `/api/...` control surface.
|
||||||
|
|
||||||
## Replace Or Rework
|
## Replace Or Rework
|
||||||
|
|
||||||
@@ -32,10 +33,13 @@ These are most likely to change when the fork renders something other than shade
|
|||||||
- `src/shader`: shader package manifest parsing and Slang wrapper generation, unless the new renderer keeps the same shader package contract.
|
- `src/shader`: shader package manifest parsing and Slang wrapper generation, unless the new renderer keeps the same shader package contract.
|
||||||
- `shaders/`: bundled shader package library.
|
- `shaders/`: bundled shader package library.
|
||||||
- `runtime/templates/shader_wrapper.slang.in`: only needed for the current Slang package pipeline.
|
- `runtime/templates/shader_wrapper.slang.in`: only needed for the current Slang package pipeline.
|
||||||
|
- `src/app/RenderCadenceHttpRoutes.*`: replace this with a fork-owned route module if the new renderer has different controls, while keeping `src/control/http/HttpControlServer.*` as the socket/static/WebSocket shell.
|
||||||
- Shader-specific UI affordances in `ui`, if the new renderer has a different control model.
|
- Shader-specific UI affordances in `ui`, if the new renderer has a different control model.
|
||||||
|
|
||||||
The first fork step is now in place: `RenderThread` preserves the cadence/readback shell and calls a narrow render-content interface behind the draw call. A new repo can swap that implementation without touching video I/O scheduling.
|
The first fork step is now in place: `RenderThread` preserves the cadence/readback shell and calls a narrow render-content interface behind the draw call. A new repo can swap that implementation without touching video I/O scheduling.
|
||||||
|
|
||||||
|
The HTTP control boundary is also split now. `HttpControlServer` owns transport, static-file helpers, OpenAPI helper serving, and WebSocket state transport; `RenderCadenceHttpRoutes` owns this app's REST endpoint map. A fork that wants the same browser/server plumbing can provide its own route callback and leave the Render Cadence-specific endpoints behind.
|
||||||
|
|
||||||
## Current Swap Point
|
## Current Swap Point
|
||||||
|
|
||||||
The render cadence loop now calls `IRenderContent` inside the readback queue call in `src/render/thread/RenderThread.cpp`:
|
The render cadence loop now calls `IRenderContent` inside the readback queue call in `src/render/thread/RenderThread.cpp`:
|
||||||
|
|||||||
@@ -261,7 +261,7 @@ Current endpoints:
|
|||||||
- `POST /api/reload`: rescans the shader library, refreshes manifests, and queues recompilation for every catalog-valid layer in the active stack
|
- `POST /api/reload`: rescans the shader library, refreshes manifests, and queues recompilation for every catalog-valid layer in the active stack
|
||||||
- other OpenAPI POST routes are present but return `{ "ok": false, "error": "Endpoint is not implemented in RenderCadenceCompositor yet." }`
|
- other OpenAPI POST routes are present but return `{ "ok": false, "error": "Endpoint is not implemented in RenderCadenceCompositor yet." }`
|
||||||
|
|
||||||
The HTTP server runs on its own thread. It serves static UI/docs files, samples/copies telemetry through callbacks, and translates POST bodies into runtime control commands. Command execution is app-owned, so future OSC ingress can create the same commands without depending on HTTP route code. Control commands may update the display layer model, request debounced runtime-state persistence, start background shader builds, or publish an already-built render-layer snapshot, but they do not call render work or DeckLink scheduling directly.
|
The HTTP server runs on its own thread. `control/http/HttpControlServer` owns socket lifetime, HTTP parsing, static UI/docs helpers, and WebSocket transport. The Render Cadence endpoint map lives in `app/RenderCadenceHttpRoutes`, which samples/copies telemetry through callbacks and translates POST bodies into runtime control commands. A fork can keep the HTTP/WebSocket shell and install a different route callback without inheriting this app's `/api/...` surface. Command execution is app-owned, so future OSC ingress can create the same commands without depending on HTTP route code. Control commands may update the display layer model, request debounced runtime-state persistence, start background shader builds, or publish an already-built render-layer snapshot, but they do not call render work or DeckLink scheduling directly.
|
||||||
|
|
||||||
## Optional DeckLink Output
|
## Optional DeckLink Output
|
||||||
|
|
||||||
@@ -463,7 +463,8 @@ This app keeps the same core behavior but splits it into modules that can grow:
|
|||||||
- `runtime/state/`: runtime JSON helpers, parameter normalization, and debounced runtime-state persistence
|
- `runtime/state/`: runtime JSON helpers, parameter normalization, and debounced runtime-state persistence
|
||||||
- `runtime/text/`: font atlas build and prepared text texture composition
|
- `runtime/text/`: font atlas build and prepared text texture composition
|
||||||
- `control/`: control action results and runtime-state JSON presentation
|
- `control/`: control action results and runtime-state JSON presentation
|
||||||
- `control/http/`: local HTTP API, static UI serving, OpenAPI serving, and WebSocket updates
|
- `control/http/`: local HTTP transport, static UI/OpenAPI serving helpers, and WebSocket updates
|
||||||
|
- `app/RenderCadenceHttpRoutes`: this app's `/api/...` endpoint map behind the reusable HTTP server route callback
|
||||||
- `json/`: compact JSON serialization helpers
|
- `json/`: compact JSON serialization helpers
|
||||||
- `video/`: DeckLink output wrapper and scheduling thread
|
- `video/`: DeckLink output wrapper and scheduling thread
|
||||||
- `telemetry/`: cadence telemetry
|
- `telemetry/`: cadence telemetry
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "AppConfigJson.h"
|
#include "AppConfigJson.h"
|
||||||
#include "AppConfigProvider.h"
|
#include "AppConfigProvider.h"
|
||||||
#include "AppRestart.h"
|
#include "AppRestart.h"
|
||||||
|
#include "RenderCadenceHttpRoutes.h"
|
||||||
#include "RuntimeLayerController.h"
|
#include "RuntimeLayerController.h"
|
||||||
#include "../logging/Logger.h"
|
#include "../logging/Logger.h"
|
||||||
#include "../control/RuntimeStateJson.h"
|
#include "../control/RuntimeStateJson.h"
|
||||||
@@ -246,23 +247,23 @@ private:
|
|||||||
|
|
||||||
void StartHttpServer()
|
void StartHttpServer()
|
||||||
{
|
{
|
||||||
HttpControlServerCallbacks callbacks;
|
RenderCadenceHttpRouteCallbacks routeCallbacks;
|
||||||
callbacks.getStateJson = [this]() {
|
routeCallbacks.getStateJson = [this]() {
|
||||||
return BuildStateJson();
|
return BuildStateJson();
|
||||||
};
|
};
|
||||||
callbacks.getConfigJson = [this]() {
|
routeCallbacks.getConfigJson = [this]() {
|
||||||
return BuildConfigJson();
|
return BuildConfigJson();
|
||||||
};
|
};
|
||||||
callbacks.getNdiSourcesJson = [this]() {
|
routeCallbacks.getNdiSourcesJson = [this]() {
|
||||||
return BuildNdiSourcesJson();
|
return BuildNdiSourcesJson();
|
||||||
};
|
};
|
||||||
callbacks.addLayer = [this](const std::string& body) {
|
routeCallbacks.addLayer = [this](const std::string& body) {
|
||||||
return mRuntimeLayers.HandleAddLayer(body);
|
return mRuntimeLayers.HandleAddLayer(body);
|
||||||
};
|
};
|
||||||
callbacks.removeLayer = [this](const std::string& body) {
|
routeCallbacks.removeLayer = [this](const std::string& body) {
|
||||||
return mRuntimeLayers.HandleRemoveLayer(body);
|
return mRuntimeLayers.HandleRemoveLayer(body);
|
||||||
};
|
};
|
||||||
callbacks.executePost = [this](const std::string& path, const std::string& body) {
|
routeCallbacks.executePost = [this](const std::string& path, const std::string& body) {
|
||||||
if (path == "/api/config/save")
|
if (path == "/api/config/save")
|
||||||
return HandleConfigSave(body);
|
return HandleConfigSave(body);
|
||||||
if (path == "/api/app/restart")
|
if (path == "/api/app/restart")
|
||||||
@@ -275,6 +276,12 @@ private:
|
|||||||
return mRuntimeLayers.HandleControlCommand(command);
|
return mRuntimeLayers.HandleControlCommand(command);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
HttpControlServerCallbacks callbacks;
|
||||||
|
callbacks.getWebSocketStateJson = routeCallbacks.getStateJson;
|
||||||
|
callbacks.routeRequest = [this, routeCallbacks](const HttpRequest& request) {
|
||||||
|
return RouteRenderCadenceHttpRequest(request, mHttpServer, routeCallbacks);
|
||||||
|
};
|
||||||
|
|
||||||
std::string error;
|
std::string error;
|
||||||
if (!mHttpServer.Start(
|
if (!mHttpServer.Start(
|
||||||
FindRepoPath("ui/dist"),
|
FindRepoPath("ui/dist"),
|
||||||
|
|||||||
120
src/app/RenderCadenceHttpRoutes.cpp
Normal file
120
src/app/RenderCadenceHttpRoutes.cpp
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
#include "RenderCadenceHttpRoutes.h"
|
||||||
|
|
||||||
|
#include "../json/JsonWriter.h"
|
||||||
|
|
||||||
|
namespace RenderCadenceCompositor
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
bool IsKnownPostEndpoint(const std::string& path)
|
||||||
|
{
|
||||||
|
return path == "/api/layers/add"
|
||||||
|
|| path == "/api/layers/remove"
|
||||||
|
|| path == "/api/layers/move"
|
||||||
|
|| path == "/api/layers/reorder"
|
||||||
|
|| path == "/api/layers/set-bypass"
|
||||||
|
|| path == "/api/layers/set-shader"
|
||||||
|
|| path == "/api/layers/update-parameter"
|
||||||
|
|| path == "/api/layers/reset-parameters"
|
||||||
|
|| path == "/api/stack-presets/save"
|
||||||
|
|| path == "/api/stack-presets/load"
|
||||||
|
|| path == "/api/config/save"
|
||||||
|
|| path == "/api/app/restart"
|
||||||
|
|| path == "/api/reload"
|
||||||
|
|| path == "/api/screenshot";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ActionResponse(bool ok, const std::string& error = std::string())
|
||||||
|
{
|
||||||
|
JsonWriter writer;
|
||||||
|
writer.BeginObject();
|
||||||
|
writer.KeyBool("ok", ok);
|
||||||
|
if (!error.empty())
|
||||||
|
writer.KeyString("error", error);
|
||||||
|
writer.EndObject();
|
||||||
|
return writer.StringValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpResponse ServeRenderCadenceGet(
|
||||||
|
const HttpRequest& request,
|
||||||
|
const HttpControlServer& server,
|
||||||
|
const RenderCadenceHttpRouteCallbacks& callbacks)
|
||||||
|
{
|
||||||
|
if (request.path == "/api/state")
|
||||||
|
return HttpControlServer::JsonResponse("200 OK", callbacks.getStateJson ? callbacks.getStateJson() : "{}");
|
||||||
|
if (request.path == "/api/config")
|
||||||
|
return HttpControlServer::JsonResponse("200 OK", callbacks.getConfigJson ? callbacks.getConfigJson() : "{}");
|
||||||
|
if (request.path == "/api/ndi/sources")
|
||||||
|
return HttpControlServer::JsonResponse(
|
||||||
|
"200 OK",
|
||||||
|
callbacks.getNdiSourcesJson
|
||||||
|
? callbacks.getNdiSourcesJson()
|
||||||
|
: "{\"ok\":false,\"sources\":[],\"error\":\"NDI source discovery is not available.\"}");
|
||||||
|
if (request.path == "/openapi.yaml" || request.path == "/docs/openapi.yaml")
|
||||||
|
return server.ServeOpenApiSpec();
|
||||||
|
if (request.path == "/docs" || request.path == "/docs/")
|
||||||
|
return server.ServeSwaggerDocs();
|
||||||
|
if (request.path == "/" || request.path == "/index.html")
|
||||||
|
return server.ServeUiAsset("index.html");
|
||||||
|
if (request.path.rfind("/assets/", 0) == 0)
|
||||||
|
return server.ServeUiAsset(request.path.substr(1));
|
||||||
|
if (request.path.size() > 1)
|
||||||
|
{
|
||||||
|
const HttpResponse asset = server.ServeUiAsset(request.path.substr(1));
|
||||||
|
if (asset.status != "404 Not Found")
|
||||||
|
return asset;
|
||||||
|
}
|
||||||
|
return server.ServeUiAsset("index.html");
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpResponse ServeRenderCadencePost(
|
||||||
|
const HttpRequest& request,
|
||||||
|
const RenderCadenceHttpRouteCallbacks& callbacks)
|
||||||
|
{
|
||||||
|
if (!IsKnownPostEndpoint(request.path))
|
||||||
|
return HttpControlServer::TextResponse("404 Not Found", "Not Found");
|
||||||
|
|
||||||
|
if (callbacks.executePost)
|
||||||
|
{
|
||||||
|
const ControlActionResult result = callbacks.executePost(request.path, request.body);
|
||||||
|
return HttpControlServer::JsonResponse(
|
||||||
|
result.ok ? "200 OK" : "400 Bad Request",
|
||||||
|
ActionResponse(result.ok, result.error));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.path == "/api/layers/add" && callbacks.addLayer)
|
||||||
|
{
|
||||||
|
const ControlActionResult result = callbacks.addLayer(request.body);
|
||||||
|
return HttpControlServer::JsonResponse(
|
||||||
|
result.ok ? "200 OK" : "400 Bad Request",
|
||||||
|
ActionResponse(result.ok, result.error));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.path == "/api/layers/remove" && callbacks.removeLayer)
|
||||||
|
{
|
||||||
|
const ControlActionResult result = callbacks.removeLayer(request.body);
|
||||||
|
return HttpControlServer::JsonResponse(
|
||||||
|
result.ok ? "200 OK" : "400 Bad Request",
|
||||||
|
ActionResponse(result.ok, result.error));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"400 Bad Request",
|
||||||
|
"application/json",
|
||||||
|
ActionResponse(false, "Endpoint is not implemented in RenderCadenceCompositor yet.")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpResponse RouteRenderCadenceHttpRequest(
|
||||||
|
const HttpRequest& request,
|
||||||
|
const HttpControlServer& server,
|
||||||
|
const RenderCadenceHttpRouteCallbacks& callbacks)
|
||||||
|
{
|
||||||
|
if (request.method == "GET")
|
||||||
|
return ServeRenderCadenceGet(request, server, callbacks);
|
||||||
|
if (request.method == "POST")
|
||||||
|
return ServeRenderCadencePost(request, callbacks);
|
||||||
|
return HttpControlServer::TextResponse("404 Not Found", "Not Found");
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/app/RenderCadenceHttpRoutes.h
Normal file
25
src/app/RenderCadenceHttpRoutes.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../control/ControlActionResult.h"
|
||||||
|
#include "../control/http/HttpControlServer.h"
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace RenderCadenceCompositor
|
||||||
|
{
|
||||||
|
struct RenderCadenceHttpRouteCallbacks
|
||||||
|
{
|
||||||
|
std::function<std::string()> getStateJson;
|
||||||
|
std::function<std::string()> getConfigJson;
|
||||||
|
std::function<std::string()> getNdiSourcesJson;
|
||||||
|
std::function<ControlActionResult(const std::string&)> addLayer;
|
||||||
|
std::function<ControlActionResult(const std::string&)> removeLayer;
|
||||||
|
std::function<ControlActionResult(const std::string&, const std::string&)> executePost;
|
||||||
|
};
|
||||||
|
|
||||||
|
HttpResponse RouteRenderCadenceHttpRequest(
|
||||||
|
const HttpRequest& request,
|
||||||
|
const HttpControlServer& server,
|
||||||
|
const RenderCadenceHttpRouteCallbacks& callbacks);
|
||||||
|
}
|
||||||
@@ -231,12 +231,10 @@ bool HttpControlServer::SendResponse(SOCKET clientSocket, const HttpResponse& re
|
|||||||
|
|
||||||
HttpControlServer::HttpResponse HttpControlServer::RouteRequest(const HttpRequest& request) const
|
HttpControlServer::HttpResponse HttpControlServer::RouteRequest(const HttpRequest& request) const
|
||||||
{
|
{
|
||||||
if (request.method == "GET")
|
|
||||||
return ServeGet(request);
|
|
||||||
if (request.method == "POST")
|
|
||||||
return ServePost(request);
|
|
||||||
if (request.method == "OPTIONS")
|
if (request.method == "OPTIONS")
|
||||||
return TextResponse("204 No Content", std::string());
|
return TextResponse("204 No Content", std::string());
|
||||||
|
if (mCallbacks.routeRequest)
|
||||||
|
return mCallbacks.routeRequest(request);
|
||||||
return TextResponse("404 Not Found", "Not Found");
|
return TextResponse("404 Not Found", "Not Found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ControlActionResult.h"
|
|
||||||
|
|
||||||
#include <winsock2.h>
|
#include <winsock2.h>
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
@@ -23,14 +21,25 @@ struct HttpControlServerConfig
|
|||||||
std::chrono::milliseconds idleSleep = std::chrono::milliseconds(10);
|
std::chrono::milliseconds idleSleep = std::chrono::milliseconds(10);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct HttpRequest
|
||||||
|
{
|
||||||
|
std::string method;
|
||||||
|
std::string path;
|
||||||
|
std::map<std::string, std::string> headers;
|
||||||
|
std::string body;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HttpResponse
|
||||||
|
{
|
||||||
|
std::string status;
|
||||||
|
std::string contentType;
|
||||||
|
std::string body;
|
||||||
|
};
|
||||||
|
|
||||||
struct HttpControlServerCallbacks
|
struct HttpControlServerCallbacks
|
||||||
{
|
{
|
||||||
std::function<std::string()> getStateJson;
|
std::function<HttpResponse(const HttpRequest&)> routeRequest;
|
||||||
std::function<std::string()> getConfigJson;
|
std::function<std::string()> getWebSocketStateJson;
|
||||||
std::function<std::string()> getNdiSourcesJson;
|
|
||||||
std::function<ControlActionResult(const std::string&)> addLayer;
|
|
||||||
std::function<ControlActionResult(const std::string&)> removeLayer;
|
|
||||||
std::function<ControlActionResult(const std::string&, const std::string&)> executePost;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class UniqueSocket
|
class UniqueSocket
|
||||||
@@ -57,20 +66,8 @@ private:
|
|||||||
class HttpControlServer
|
class HttpControlServer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
struct HttpRequest
|
using HttpRequest = RenderCadenceCompositor::HttpRequest;
|
||||||
{
|
using HttpResponse = RenderCadenceCompositor::HttpResponse;
|
||||||
std::string method;
|
|
||||||
std::string path;
|
|
||||||
std::map<std::string, std::string> headers;
|
|
||||||
std::string body;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct HttpResponse
|
|
||||||
{
|
|
||||||
std::string status;
|
|
||||||
std::string contentType;
|
|
||||||
std::string body;
|
|
||||||
};
|
|
||||||
|
|
||||||
HttpControlServer() = default;
|
HttpControlServer() = default;
|
||||||
~HttpControlServer();
|
~HttpControlServer();
|
||||||
@@ -95,6 +92,13 @@ public:
|
|||||||
|
|
||||||
static bool ParseHttpRequest(const std::string& rawRequest, HttpRequest& request);
|
static bool ParseHttpRequest(const std::string& rawRequest, HttpRequest& request);
|
||||||
static std::string WebSocketAcceptKey(const std::string& clientKey);
|
static std::string WebSocketAcceptKey(const std::string& clientKey);
|
||||||
|
HttpResponse ServeOpenApiSpec() const;
|
||||||
|
HttpResponse ServeSwaggerDocs() const;
|
||||||
|
HttpResponse ServeUiAsset(const std::string& relativePath) const;
|
||||||
|
|
||||||
|
static HttpResponse JsonResponse(const std::string& status, const std::string& body);
|
||||||
|
static HttpResponse TextResponse(const std::string& status, const std::string& body);
|
||||||
|
static HttpResponse HtmlResponse(const std::string& status, const std::string& body);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void ThreadMain();
|
void ThreadMain();
|
||||||
@@ -105,17 +109,8 @@ private:
|
|||||||
void JoinFinishedClientThreads();
|
void JoinFinishedClientThreads();
|
||||||
bool SendResponse(SOCKET clientSocket, const HttpResponse& response) const;
|
bool SendResponse(SOCKET clientSocket, const HttpResponse& response) const;
|
||||||
HttpResponse RouteRequest(const HttpRequest& request) const;
|
HttpResponse RouteRequest(const HttpRequest& request) const;
|
||||||
HttpResponse ServeGet(const HttpRequest& request) const;
|
|
||||||
HttpResponse ServePost(const HttpRequest& request) const;
|
|
||||||
HttpResponse ServeOpenApiSpec() const;
|
|
||||||
HttpResponse ServeSwaggerDocs() const;
|
|
||||||
HttpResponse ServeUiAsset(const std::string& relativePath) const;
|
|
||||||
std::string LoadTextFile(const std::filesystem::path& path) const;
|
std::string LoadTextFile(const std::filesystem::path& path) const;
|
||||||
|
|
||||||
static HttpResponse JsonResponse(const std::string& status, const std::string& body);
|
|
||||||
static HttpResponse TextResponse(const std::string& status, const std::string& body);
|
|
||||||
static HttpResponse HtmlResponse(const std::string& status, const std::string& body);
|
|
||||||
static std::string ActionResponse(bool ok, const std::string& error = std::string());
|
|
||||||
static bool SendWebSocketText(SOCKET clientSocket, const std::string& text);
|
static bool SendWebSocketText(SOCKET clientSocket, const std::string& text);
|
||||||
static std::string GuessContentType(const std::filesystem::path& path);
|
static std::string GuessContentType(const std::filesystem::path& path);
|
||||||
static bool IsSafeRelativePath(const std::filesystem::path& path);
|
static bool IsSafeRelativePath(const std::filesystem::path& path);
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
#include "HttpControlServer.h"
|
#include "HttpControlServer.h"
|
||||||
|
|
||||||
#include "../json/JsonWriter.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
@@ -9,86 +7,6 @@
|
|||||||
|
|
||||||
namespace RenderCadenceCompositor
|
namespace RenderCadenceCompositor
|
||||||
{
|
{
|
||||||
namespace
|
|
||||||
{
|
|
||||||
bool IsKnownPostEndpoint(const std::string& path)
|
|
||||||
{
|
|
||||||
return path == "/api/layers/add"
|
|
||||||
|| path == "/api/layers/remove"
|
|
||||||
|| path == "/api/layers/move"
|
|
||||||
|| path == "/api/layers/reorder"
|
|
||||||
|| path == "/api/layers/set-bypass"
|
|
||||||
|| path == "/api/layers/set-shader"
|
|
||||||
|| path == "/api/layers/update-parameter"
|
|
||||||
|| path == "/api/layers/reset-parameters"
|
|
||||||
|| path == "/api/stack-presets/save"
|
|
||||||
|| path == "/api/stack-presets/load"
|
|
||||||
|| path == "/api/config/save"
|
|
||||||
|| path == "/api/app/restart"
|
|
||||||
|| path == "/api/reload"
|
|
||||||
|| path == "/api/screenshot";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpControlServer::HttpResponse HttpControlServer::ServeGet(const HttpRequest& request) const
|
|
||||||
{
|
|
||||||
if (request.path == "/api/state")
|
|
||||||
return JsonResponse("200 OK", mCallbacks.getStateJson ? mCallbacks.getStateJson() : "{}");
|
|
||||||
if (request.path == "/api/config")
|
|
||||||
return JsonResponse("200 OK", mCallbacks.getConfigJson ? mCallbacks.getConfigJson() : "{}");
|
|
||||||
if (request.path == "/api/ndi/sources")
|
|
||||||
return JsonResponse(
|
|
||||||
"200 OK",
|
|
||||||
mCallbacks.getNdiSourcesJson
|
|
||||||
? mCallbacks.getNdiSourcesJson()
|
|
||||||
: "{\"ok\":false,\"sources\":[],\"error\":\"NDI source discovery is not available.\"}");
|
|
||||||
if (request.path == "/openapi.yaml" || request.path == "/docs/openapi.yaml")
|
|
||||||
return ServeOpenApiSpec();
|
|
||||||
if (request.path == "/docs" || request.path == "/docs/")
|
|
||||||
return ServeSwaggerDocs();
|
|
||||||
if (request.path == "/" || request.path == "/index.html")
|
|
||||||
return ServeUiAsset("index.html");
|
|
||||||
if (request.path.rfind("/assets/", 0) == 0)
|
|
||||||
return ServeUiAsset(request.path.substr(1));
|
|
||||||
if (request.path.size() > 1)
|
|
||||||
{
|
|
||||||
const HttpResponse asset = ServeUiAsset(request.path.substr(1));
|
|
||||||
if (asset.status != "404 Not Found")
|
|
||||||
return asset;
|
|
||||||
}
|
|
||||||
return ServeUiAsset("index.html");
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpControlServer::HttpResponse HttpControlServer::ServePost(const HttpRequest& request) const
|
|
||||||
{
|
|
||||||
if (!IsKnownPostEndpoint(request.path))
|
|
||||||
return TextResponse("404 Not Found", "Not Found");
|
|
||||||
|
|
||||||
if (mCallbacks.executePost)
|
|
||||||
{
|
|
||||||
const ControlActionResult result = mCallbacks.executePost(request.path, request.body);
|
|
||||||
return JsonResponse(result.ok ? "200 OK" : "400 Bad Request", ActionResponse(result.ok, result.error));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.path == "/api/layers/add" && mCallbacks.addLayer)
|
|
||||||
{
|
|
||||||
const ControlActionResult result = mCallbacks.addLayer(request.body);
|
|
||||||
return JsonResponse(result.ok ? "200 OK" : "400 Bad Request", ActionResponse(result.ok, result.error));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.path == "/api/layers/remove" && mCallbacks.removeLayer)
|
|
||||||
{
|
|
||||||
const ControlActionResult result = mCallbacks.removeLayer(request.body);
|
|
||||||
return JsonResponse(result.ok ? "200 OK" : "400 Bad Request", ActionResponse(result.ok, result.error));
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
"400 Bad Request",
|
|
||||||
"application/json",
|
|
||||||
ActionResponse(false, "Endpoint is not implemented in RenderCadenceCompositor yet.")
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpControlServer::HttpResponse HttpControlServer::ServeOpenApiSpec() const
|
HttpControlServer::HttpResponse HttpControlServer::ServeOpenApiSpec() const
|
||||||
{
|
{
|
||||||
const std::filesystem::path path = mDocsRoot / "openapi.yaml";
|
const std::filesystem::path path = mDocsRoot / "openapi.yaml";
|
||||||
@@ -153,17 +71,6 @@ HttpControlServer::HttpResponse HttpControlServer::HtmlResponse(const std::strin
|
|||||||
return { status, "text/html", body };
|
return { status, "text/html", body };
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string HttpControlServer::ActionResponse(bool ok, const std::string& error)
|
|
||||||
{
|
|
||||||
JsonWriter writer;
|
|
||||||
writer.BeginObject();
|
|
||||||
writer.KeyBool("ok", ok);
|
|
||||||
if (!error.empty())
|
|
||||||
writer.KeyString("error", error);
|
|
||||||
writer.EndObject();
|
|
||||||
return writer.StringValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string HttpControlServer::GuessContentType(const std::filesystem::path& path)
|
std::string HttpControlServer::GuessContentType(const std::filesystem::path& path)
|
||||||
{
|
{
|
||||||
const std::string extension = ToLower(path.extension().string());
|
const std::string extension = ToLower(path.extension().string());
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ void HttpControlServer::WebSocketClientMain(UniqueSocket clientSocket)
|
|||||||
std::string previousState;
|
std::string previousState;
|
||||||
while (mRunning.load(std::memory_order_acquire))
|
while (mRunning.load(std::memory_order_acquire))
|
||||||
{
|
{
|
||||||
const std::string state = mCallbacks.getStateJson ? mCallbacks.getStateJson() : "{}";
|
const std::string state = mCallbacks.getWebSocketStateJson ? mCallbacks.getWebSocketStateJson() : "{}";
|
||||||
if (state != previousState)
|
if (state != previousState)
|
||||||
{
|
{
|
||||||
if (!SendWebSocketText(clientSocket.get(), state))
|
if (!SendWebSocketText(clientSocket.get(), state))
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ add_video_shader_test(RenderCadenceCompositorRuntimeStateJsonTests
|
|||||||
)
|
)
|
||||||
|
|
||||||
add_video_shader_test(RenderCadenceCompositorHttpControlServerTests
|
add_video_shader_test(RenderCadenceCompositorHttpControlServerTests
|
||||||
|
"${SRC_DIR}/app/RenderCadenceHttpRoutes.cpp"
|
||||||
"${SRC_DIR}/control/RuntimeControlCommand.cpp"
|
"${SRC_DIR}/control/RuntimeControlCommand.cpp"
|
||||||
"${SRC_DIR}/control/http/HttpControlServer.cpp"
|
"${SRC_DIR}/control/http/HttpControlServer.cpp"
|
||||||
"${SRC_DIR}/control/http/HttpControlServerRoutes.cpp"
|
"${SRC_DIR}/control/http/HttpControlServerRoutes.cpp"
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "HttpControlServer.h"
|
#include "HttpControlServer.h"
|
||||||
|
#include "RenderCadenceHttpRoutes.h"
|
||||||
#include "RuntimeControlCommand.h"
|
#include "RuntimeControlCommand.h"
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
@@ -45,20 +46,41 @@ void TestParsesHttpRequest()
|
|||||||
ExpectEquals(request.headers["host"], "127.0.0.1", "headers are lower-cased and trimmed");
|
ExpectEquals(request.headers["host"], "127.0.0.1", "headers are lower-cased and trimmed");
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestStateEndpointUsesCallback()
|
void TestServerDelegatesToRouteCallback()
|
||||||
{
|
{
|
||||||
using namespace RenderCadenceCompositor;
|
using namespace RenderCadenceCompositor;
|
||||||
|
|
||||||
HttpControlServer server;
|
HttpControlServer server;
|
||||||
HttpControlServerCallbacks callbacks;
|
HttpControlServerCallbacks callbacks;
|
||||||
callbacks.getStateJson = []() { return std::string("{\"ok\":true}"); };
|
callbacks.routeRequest = [](const HttpRequest& request) {
|
||||||
|
ExpectEquals(request.path, "/custom", "generic route callback receives request path");
|
||||||
|
return HttpControlServer::JsonResponse("200 OK", "{\"custom\":true}");
|
||||||
|
};
|
||||||
server.SetCallbacksForTest(callbacks);
|
server.SetCallbacksForTest(callbacks);
|
||||||
|
|
||||||
|
HttpControlServer::HttpRequest request;
|
||||||
|
request.method = "GET";
|
||||||
|
request.path = "/custom";
|
||||||
|
|
||||||
|
const HttpControlServer::HttpResponse response = server.RouteRequestForTest(request);
|
||||||
|
ExpectEquals(response.status, "200 OK", "server delegates HTTP request to route callback");
|
||||||
|
ExpectEquals(response.contentType, "application/json", "route callback controls content type");
|
||||||
|
ExpectEquals(response.body, "{\"custom\":true}", "route callback controls response body");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestStateEndpointUsesCallback()
|
||||||
|
{
|
||||||
|
using namespace RenderCadenceCompositor;
|
||||||
|
|
||||||
|
HttpControlServer server;
|
||||||
|
RenderCadenceHttpRouteCallbacks callbacks;
|
||||||
|
callbacks.getStateJson = []() { return std::string("{\"ok\":true}"); };
|
||||||
|
|
||||||
HttpControlServer::HttpRequest request;
|
HttpControlServer::HttpRequest request;
|
||||||
request.method = "GET";
|
request.method = "GET";
|
||||||
request.path = "/api/state";
|
request.path = "/api/state";
|
||||||
|
|
||||||
const HttpControlServer::HttpResponse response = server.RouteRequestForTest(request);
|
const HttpControlServer::HttpResponse response = RouteRenderCadenceHttpRequest(request, server, callbacks);
|
||||||
ExpectEquals(response.status, "200 OK", "state endpoint succeeds");
|
ExpectEquals(response.status, "200 OK", "state endpoint succeeds");
|
||||||
ExpectEquals(response.contentType, "application/json", "state endpoint is JSON");
|
ExpectEquals(response.contentType, "application/json", "state endpoint is JSON");
|
||||||
ExpectEquals(response.body, "{\"ok\":true}", "state endpoint returns callback JSON");
|
ExpectEquals(response.body, "{\"ok\":true}", "state endpoint returns callback JSON");
|
||||||
@@ -69,15 +91,14 @@ void TestConfigEndpointUsesCallback()
|
|||||||
using namespace RenderCadenceCompositor;
|
using namespace RenderCadenceCompositor;
|
||||||
|
|
||||||
HttpControlServer server;
|
HttpControlServer server;
|
||||||
HttpControlServerCallbacks callbacks;
|
RenderCadenceHttpRouteCallbacks callbacks;
|
||||||
callbacks.getConfigJson = []() { return std::string("{\"diskLoaded\":true}"); };
|
callbacks.getConfigJson = []() { return std::string("{\"diskLoaded\":true}"); };
|
||||||
server.SetCallbacksForTest(callbacks);
|
|
||||||
|
|
||||||
HttpControlServer::HttpRequest request;
|
HttpControlServer::HttpRequest request;
|
||||||
request.method = "GET";
|
request.method = "GET";
|
||||||
request.path = "/api/config";
|
request.path = "/api/config";
|
||||||
|
|
||||||
const HttpControlServer::HttpResponse response = server.RouteRequestForTest(request);
|
const HttpControlServer::HttpResponse response = RouteRenderCadenceHttpRequest(request, server, callbacks);
|
||||||
ExpectEquals(response.status, "200 OK", "config endpoint succeeds");
|
ExpectEquals(response.status, "200 OK", "config endpoint succeeds");
|
||||||
ExpectEquals(response.contentType, "application/json", "config endpoint is JSON");
|
ExpectEquals(response.contentType, "application/json", "config endpoint is JSON");
|
||||||
ExpectEquals(response.body, "{\"diskLoaded\":true}", "config endpoint returns callback JSON");
|
ExpectEquals(response.body, "{\"diskLoaded\":true}", "config endpoint returns callback JSON");
|
||||||
@@ -88,15 +109,14 @@ void TestNdiSourcesEndpointUsesCallback()
|
|||||||
using namespace RenderCadenceCompositor;
|
using namespace RenderCadenceCompositor;
|
||||||
|
|
||||||
HttpControlServer server;
|
HttpControlServer server;
|
||||||
HttpControlServerCallbacks callbacks;
|
RenderCadenceHttpRouteCallbacks callbacks;
|
||||||
callbacks.getNdiSourcesJson = []() { return std::string("{\"ok\":true,\"sources\":[{\"name\":\"DESKTOP (Camera)\"}]}"); };
|
callbacks.getNdiSourcesJson = []() { return std::string("{\"ok\":true,\"sources\":[{\"name\":\"DESKTOP (Camera)\"}]}"); };
|
||||||
server.SetCallbacksForTest(callbacks);
|
|
||||||
|
|
||||||
HttpControlServer::HttpRequest request;
|
HttpControlServer::HttpRequest request;
|
||||||
request.method = "GET";
|
request.method = "GET";
|
||||||
request.path = "/api/ndi/sources";
|
request.path = "/api/ndi/sources";
|
||||||
|
|
||||||
const HttpControlServer::HttpResponse response = server.RouteRequestForTest(request);
|
const HttpControlServer::HttpResponse response = RouteRenderCadenceHttpRequest(request, server, callbacks);
|
||||||
ExpectEquals(response.status, "200 OK", "NDI sources endpoint succeeds");
|
ExpectEquals(response.status, "200 OK", "NDI sources endpoint succeeds");
|
||||||
ExpectEquals(response.contentType, "application/json", "NDI sources endpoint is JSON");
|
ExpectEquals(response.contentType, "application/json", "NDI sources endpoint is JSON");
|
||||||
Expect(response.body.find("DESKTOP (Camera)") != std::string::npos, "NDI sources endpoint returns callback JSON");
|
Expect(response.body.find("DESKTOP (Camera)") != std::string::npos, "NDI sources endpoint returns callback JSON");
|
||||||
@@ -127,7 +147,8 @@ void TestRootServesUiIndex()
|
|||||||
request.path = "/";
|
request.path = "/";
|
||||||
|
|
||||||
server.SetRootsForTest(root, std::filesystem::path());
|
server.SetRootsForTest(root, std::filesystem::path());
|
||||||
const HttpControlServer::HttpResponse response = server.RouteRequestForTest(request);
|
RenderCadenceHttpRouteCallbacks callbacks;
|
||||||
|
const HttpControlServer::HttpResponse response = RouteRenderCadenceHttpRequest(request, server, callbacks);
|
||||||
|
|
||||||
ExpectEquals(response.status, "200 OK", "root endpoint serves UI index");
|
ExpectEquals(response.status, "200 OK", "root endpoint serves UI index");
|
||||||
ExpectEquals(response.contentType, "text/html", "UI index content type is html");
|
ExpectEquals(response.contentType, "text/html", "UI index content type is html");
|
||||||
@@ -146,7 +167,8 @@ void TestKnownPostEndpointReturnsActionError()
|
|||||||
request.path = "/api/layers/add";
|
request.path = "/api/layers/add";
|
||||||
request.body = "{\"shaderId\":\"happy-accident\"}";
|
request.body = "{\"shaderId\":\"happy-accident\"}";
|
||||||
|
|
||||||
const HttpControlServer::HttpResponse response = server.RouteRequestForTest(request);
|
RenderCadenceHttpRouteCallbacks callbacks;
|
||||||
|
const HttpControlServer::HttpResponse response = RouteRenderCadenceHttpRequest(request, server, callbacks);
|
||||||
ExpectEquals(response.status, "400 Bad Request", "unimplemented post returns OpenAPI action error status");
|
ExpectEquals(response.status, "400 Bad Request", "unimplemented post returns OpenAPI action error status");
|
||||||
ExpectEquals(response.contentType, "application/json", "unimplemented post returns JSON");
|
ExpectEquals(response.contentType, "application/json", "unimplemented post returns JSON");
|
||||||
Expect(response.body.find("\"ok\":false") != std::string::npos, "unimplemented post reports ok false");
|
Expect(response.body.find("\"ok\":false") != std::string::npos, "unimplemented post reports ok false");
|
||||||
@@ -158,7 +180,7 @@ void TestLayerPostEndpointsUseCallbacks()
|
|||||||
using namespace RenderCadenceCompositor;
|
using namespace RenderCadenceCompositor;
|
||||||
|
|
||||||
HttpControlServer server;
|
HttpControlServer server;
|
||||||
HttpControlServerCallbacks callbacks;
|
RenderCadenceHttpRouteCallbacks callbacks;
|
||||||
callbacks.addLayer = [](const std::string& body) {
|
callbacks.addLayer = [](const std::string& body) {
|
||||||
Expect(body.find("solid") != std::string::npos, "add callback receives request body");
|
Expect(body.find("solid") != std::string::npos, "add callback receives request body");
|
||||||
return ControlActionResult{ true, std::string() };
|
return ControlActionResult{ true, std::string() };
|
||||||
@@ -167,13 +189,12 @@ void TestLayerPostEndpointsUseCallbacks()
|
|||||||
Expect(body.find("runtime-layer-1") != std::string::npos, "remove callback receives request body");
|
Expect(body.find("runtime-layer-1") != std::string::npos, "remove callback receives request body");
|
||||||
return ControlActionResult{ false, "Unknown layer id." };
|
return ControlActionResult{ false, "Unknown layer id." };
|
||||||
};
|
};
|
||||||
server.SetCallbacksForTest(callbacks);
|
|
||||||
|
|
||||||
HttpControlServer::HttpRequest addRequest;
|
HttpControlServer::HttpRequest addRequest;
|
||||||
addRequest.method = "POST";
|
addRequest.method = "POST";
|
||||||
addRequest.path = "/api/layers/add";
|
addRequest.path = "/api/layers/add";
|
||||||
addRequest.body = "{\"shaderId\":\"solid\"}";
|
addRequest.body = "{\"shaderId\":\"solid\"}";
|
||||||
const HttpControlServer::HttpResponse addResponse = server.RouteRequestForTest(addRequest);
|
const HttpControlServer::HttpResponse addResponse = RouteRenderCadenceHttpRequest(addRequest, server, callbacks);
|
||||||
ExpectEquals(addResponse.status, "200 OK", "add layer callback success returns 200");
|
ExpectEquals(addResponse.status, "200 OK", "add layer callback success returns 200");
|
||||||
Expect(addResponse.body.find("\"ok\":true") != std::string::npos, "add layer callback returns action success");
|
Expect(addResponse.body.find("\"ok\":true") != std::string::npos, "add layer callback returns action success");
|
||||||
|
|
||||||
@@ -181,7 +202,7 @@ void TestLayerPostEndpointsUseCallbacks()
|
|||||||
removeRequest.method = "POST";
|
removeRequest.method = "POST";
|
||||||
removeRequest.path = "/api/layers/remove";
|
removeRequest.path = "/api/layers/remove";
|
||||||
removeRequest.body = "{\"layerId\":\"runtime-layer-1\"}";
|
removeRequest.body = "{\"layerId\":\"runtime-layer-1\"}";
|
||||||
const HttpControlServer::HttpResponse removeResponse = server.RouteRequestForTest(removeRequest);
|
const HttpControlServer::HttpResponse removeResponse = RouteRenderCadenceHttpRequest(removeRequest, server, callbacks);
|
||||||
ExpectEquals(removeResponse.status, "400 Bad Request", "remove layer callback failure returns 400");
|
ExpectEquals(removeResponse.status, "400 Bad Request", "remove layer callback failure returns 400");
|
||||||
Expect(removeResponse.body.find("Unknown layer id.") != std::string::npos, "remove layer callback returns diagnostic");
|
Expect(removeResponse.body.find("Unknown layer id.") != std::string::npos, "remove layer callback returns diagnostic");
|
||||||
}
|
}
|
||||||
@@ -191,20 +212,19 @@ void TestGenericPostCallbackHandlesControlRoutes()
|
|||||||
using namespace RenderCadenceCompositor;
|
using namespace RenderCadenceCompositor;
|
||||||
|
|
||||||
HttpControlServer server;
|
HttpControlServer server;
|
||||||
HttpControlServerCallbacks callbacks;
|
RenderCadenceHttpRouteCallbacks callbacks;
|
||||||
callbacks.executePost = [](const std::string& path, const std::string& body) {
|
callbacks.executePost = [](const std::string& path, const std::string& body) {
|
||||||
ExpectEquals(path, "/api/layers/set-bypass", "generic callback receives route path");
|
ExpectEquals(path, "/api/layers/set-bypass", "generic callback receives route path");
|
||||||
Expect(body.find("runtime-layer-1") != std::string::npos, "generic callback receives request body");
|
Expect(body.find("runtime-layer-1") != std::string::npos, "generic callback receives request body");
|
||||||
return ControlActionResult{ true, std::string() };
|
return ControlActionResult{ true, std::string() };
|
||||||
};
|
};
|
||||||
server.SetCallbacksForTest(callbacks);
|
|
||||||
|
|
||||||
HttpControlServer::HttpRequest request;
|
HttpControlServer::HttpRequest request;
|
||||||
request.method = "POST";
|
request.method = "POST";
|
||||||
request.path = "/api/layers/set-bypass";
|
request.path = "/api/layers/set-bypass";
|
||||||
request.body = "{\"layerId\":\"runtime-layer-1\",\"bypass\":true}";
|
request.body = "{\"layerId\":\"runtime-layer-1\",\"bypass\":true}";
|
||||||
|
|
||||||
const HttpControlServer::HttpResponse response = server.RouteRequestForTest(request);
|
const HttpControlServer::HttpResponse response = RouteRenderCadenceHttpRequest(request, server, callbacks);
|
||||||
ExpectEquals(response.status, "200 OK", "generic control callback success returns 200");
|
ExpectEquals(response.status, "200 OK", "generic control callback success returns 200");
|
||||||
Expect(response.body.find("\"ok\":true") != std::string::npos, "generic control callback returns action success");
|
Expect(response.body.find("\"ok\":true") != std::string::npos, "generic control callback returns action success");
|
||||||
}
|
}
|
||||||
@@ -214,20 +234,19 @@ void TestGenericPostCallbackHandlesConfigRoutes()
|
|||||||
using namespace RenderCadenceCompositor;
|
using namespace RenderCadenceCompositor;
|
||||||
|
|
||||||
HttpControlServer server;
|
HttpControlServer server;
|
||||||
HttpControlServerCallbacks callbacks;
|
RenderCadenceHttpRouteCallbacks callbacks;
|
||||||
callbacks.executePost = [](const std::string& path, const std::string& body) {
|
callbacks.executePost = [](const std::string& path, const std::string& body) {
|
||||||
ExpectEquals(path, "/api/config/save", "generic callback receives config route path");
|
ExpectEquals(path, "/api/config/save", "generic callback receives config route path");
|
||||||
Expect(body.find("runtimeShaderId") != std::string::npos, "generic callback receives config request body");
|
Expect(body.find("runtimeShaderId") != std::string::npos, "generic callback receives config request body");
|
||||||
return ControlActionResult{ true, std::string() };
|
return ControlActionResult{ true, std::string() };
|
||||||
};
|
};
|
||||||
server.SetCallbacksForTest(callbacks);
|
|
||||||
|
|
||||||
HttpControlServer::HttpRequest request;
|
HttpControlServer::HttpRequest request;
|
||||||
request.method = "POST";
|
request.method = "POST";
|
||||||
request.path = "/api/config/save";
|
request.path = "/api/config/save";
|
||||||
request.body = "{\"runtimeShaderId\":\"solid-color\"}";
|
request.body = "{\"runtimeShaderId\":\"solid-color\"}";
|
||||||
|
|
||||||
const HttpControlServer::HttpResponse response = server.RouteRequestForTest(request);
|
const HttpControlServer::HttpResponse response = RouteRenderCadenceHttpRequest(request, server, callbacks);
|
||||||
ExpectEquals(response.status, "200 OK", "config save callback success returns 200");
|
ExpectEquals(response.status, "200 OK", "config save callback success returns 200");
|
||||||
Expect(response.body.find("\"ok\":true") != std::string::npos, "config save callback returns action success");
|
Expect(response.body.find("\"ok\":true") != std::string::npos, "config save callback returns action success");
|
||||||
}
|
}
|
||||||
@@ -251,7 +270,8 @@ void TestUnknownEndpointReturns404()
|
|||||||
request.method = "GET";
|
request.method = "GET";
|
||||||
request.path = "/api/nope";
|
request.path = "/api/nope";
|
||||||
|
|
||||||
const HttpControlServer::HttpResponse response = server.RouteRequestForTest(request);
|
RenderCadenceHttpRouteCallbacks callbacks;
|
||||||
|
const HttpControlServer::HttpResponse response = RouteRenderCadenceHttpRequest(request, server, callbacks);
|
||||||
ExpectEquals(response.status, "404 Not Found", "unknown endpoint returns 404");
|
ExpectEquals(response.status, "404 Not Found", "unknown endpoint returns 404");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -259,6 +279,7 @@ void TestUnknownEndpointReturns404()
|
|||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
TestParsesHttpRequest();
|
TestParsesHttpRequest();
|
||||||
|
TestServerDelegatesToRouteCallback();
|
||||||
TestStateEndpointUsesCallback();
|
TestStateEndpointUsesCallback();
|
||||||
TestConfigEndpointUsesCallback();
|
TestConfigEndpointUsesCallback();
|
||||||
TestNdiSourcesEndpointUsesCallback();
|
TestNdiSourcesEndpointUsesCallback();
|
||||||
|
|||||||
Submodule video-io-3rdParty updated: 6325492f59...4755423da6
Reference in New Issue
Block a user