yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
http_server.cc
Go to the documentation of this file.
2
3#include <string>
4
6#include "util/log.h"
7
8#include "httplib.h"
9
10namespace yaze {
11namespace cli {
12namespace api {
13
14HttpServer::HttpServer() : server_(std::make_unique<httplib::Server>()) {}
15
19
20absl::Status HttpServer::Start(int port, bool loopback_only) {
21 if (is_running_) {
22 return absl::AlreadyExistsError("Server is already running");
23 }
24
25 if (!server_->is_valid()) {
26 return absl::InternalError("HttpServer is not valid");
27 }
28
29 editor_control_only_ = loopback_only && static_cast<bool>(editor_control_);
31
32 const std::string address = loopback_only ? "127.0.0.1" : bind_address_;
33 if (!server_->bind_to_port(address, port)) {
34 return absl::UnavailableError("HTTP API port is already in use");
35 }
36
37 port_ = port;
38 is_running_ = true;
39 server_thread_ = std::make_unique<std::thread>([this, port]() {
40 LOG_INFO("HttpServer", "Starting API server on port %d", port);
41 bool ret = server_->listen_after_bind();
42 if (!ret) {
43 LOG_ERROR("HttpServer", "Failed to listen on port %d", port);
44 }
45 is_running_ = false;
46 });
47
48 // Publish Bonjour service for LAN discovery by iOS clients.
49 if (loopback_only) {
50 return absl::OkStatus();
51 }
53 if (bonjour_.IsAvailable()) {
54 if (bonjour_.IsPublished()) {
55 LOG_INFO("HttpServer", "Bonjour discovery published on port %d", port);
56 } else {
57 LOG_ERROR("HttpServer", "Bonjour discovery failed to publish on port %d",
58 port);
59 }
60 } else {
61 LOG_INFO("HttpServer",
62 "Bonjour discovery not available on this platform, skipping");
63 }
64
65 return absl::OkStatus();
66}
67
70 server_->stop();
71 if (server_thread_ && server_thread_->joinable()) {
72 server_thread_->join();
73 }
74 is_running_ = false;
75}
76
78 return is_running_;
79}
80
82 if (editor_control_) {
83 server_->Post("/api/v1/editor/control",
84 [this](const httplib::Request& req, httplib::Response& res) {
86 });
87 }
89 // Do not expose the offline CLI/ROM handlers in a live editor session.
90 // They are not marshalled to the UI thread and may retain stale ROM data.
91 server_->Get("/api/v1/health", [](const httplib::Request&,
92 httplib::Response& res) {
93 res.set_content(R"({"status":"ok","service":"yaze-editor-control/v1"})",
94 "application/json");
95 });
96 return;
97 }
98 server_->set_error_handler(
99 [](const httplib::Request& req, httplib::Response& res) {
100 (void)req;
101 ApplyCorsHeaders(res);
102 if (!res.body.empty()) {
103 return;
104 }
105 const char* message = "Request failed";
106 switch (res.status) {
107 case 400:
108 message = "Bad request";
109 break;
110 case 404:
111 message = "Not found";
112 break;
113 case 405:
114 message = "Method not allowed";
115 break;
116 case 501:
117 message = "Not implemented";
118 break;
119 case 503:
120 message = "Service unavailable";
121 break;
122 case 500:
123 message = "Internal server error";
124 break;
125 default:
126 break;
127 }
128 std::string body = std::string("{\"error\":\"") + message + "\"}";
129 res.set_content(body, "application/json");
130 });
131
132 server_->Get("/api/v1/health",
133 [this](const httplib::Request& req, httplib::Response& res) {
134 HandleHealth(req, res, &bonjour_);
135 });
136 server_->Get("/api/v1/models", HandleListModels);
137 server_->Options("/api/v1/health", HandleCorsPreflight);
138 server_->Options("/api/v1/models", HandleCorsPreflight);
139
140 server_->Get("/api/v1/symbols",
141 [this](const httplib::Request& req, httplib::Response& res) {
142 emu::debug::SymbolProvider* symbols = nullptr;
143 if (symbol_source_) {
144 symbols = symbol_source_();
145 }
146 HandleGetSymbols(req, res, symbols);
147 });
148 server_->Options("/api/v1/symbols", HandleCorsPreflight);
149
150 server_->Post("/api/v1/navigate", HandleNavigate);
151 server_->Post("/api/v1/breakpoint/hit", HandleBreakpointHit);
152 server_->Post("/api/v1/state/update", HandleStateUpdate);
153 server_->Options("/api/v1/navigate", HandleCorsPreflight);
154 server_->Options("/api/v1/breakpoint/hit", HandleCorsPreflight);
155 server_->Options("/api/v1/state/update", HandleCorsPreflight);
156
157 server_->Post("/api/v1/window/show",
158 [this](const httplib::Request& req, httplib::Response& res) {
160 });
161 server_->Options("/api/v1/window/show", HandleCorsPreflight);
162
163 server_->Post("/api/v1/window/hide",
164 [this](const httplib::Request& req, httplib::Response& res) {
166 });
167 server_->Options("/api/v1/window/hide", HandleCorsPreflight);
168
169 server_->Get("/api/v1/render/dungeon",
170 [this](const httplib::Request& req, httplib::Response& res) {
172 render_source_ ? render_source_() : nullptr;
173 HandleRenderDungeon(req, res, rs);
174 });
175 server_->Options("/api/v1/render/dungeon", HandleCorsPreflight);
176
177 server_->Get("/api/v1/render/dungeon/metadata",
178 [this](const httplib::Request& req, httplib::Response& res) {
180 render_source_ ? render_source_() : nullptr;
181 HandleRenderDungeonMetadata(req, res, rs);
182 });
183 server_->Options("/api/v1/render/dungeon/metadata", HandleCorsPreflight);
184
185 // Command execution endpoints (Phase 3)
187 server_->Post("/api/v1/command/execute",
188 [this](const httplib::Request& req, httplib::Response& res) {
189 Rom* rom = rom_source_ ? rom_source_() : nullptr;
190 HandleCommandExecute(req, res, rom);
191 });
192 } else {
193 server_->Post("/api/v1/command/execute", HandleCommandExecuteDisabled);
194 }
195 server_->Get("/api/v1/command/list", HandleCommandList);
196 server_->Options("/api/v1/command/execute", HandleCorsPreflight);
197 server_->Options("/api/v1/command/list", HandleCorsPreflight);
198
199 // Annotation CRUD endpoints (Phase 4)
200 auto project_path_lambda = [this]() -> std::string {
202 };
203 server_->Get("/api/v1/annotations",
204 [project_path_lambda](const httplib::Request& req,
205 httplib::Response& res) {
206 HandleAnnotationList(req, res, project_path_lambda());
207 });
208 server_->Post("/api/v1/annotations",
209 [project_path_lambda](const httplib::Request& req,
210 httplib::Response& res) {
211 HandleAnnotationCreate(req, res, project_path_lambda());
212 });
213 server_->Put(R"(/api/v1/annotations/(.+))",
214 [project_path_lambda](const httplib::Request& req,
215 httplib::Response& res) {
216 HandleAnnotationUpdate(req, res, project_path_lambda());
217 });
218 server_->Delete(R"(/api/v1/annotations/(.+))",
219 [project_path_lambda](const httplib::Request& req,
220 httplib::Response& res) {
221 HandleAnnotationDelete(req, res, project_path_lambda());
222 });
223 server_->Options("/api/v1/annotations", HandleCorsPreflight);
224 server_->Options(R"(/api/v1/annotations/(.+))", HandleCorsPreflight);
225}
226
227} // namespace api
228} // namespace cli
229} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:52
void Unpublish()
Stop advertising. Safe to call multiple times.
void Publish(int port, const std::string &rom_title="")
Start advertising. Call after the HTTP server socket is bound.
std::unique_ptr< httplib::Server > server_
BonjourPublisher bonjour_
RenderServiceSource render_source_
SymbolProviderSource symbol_source_
absl::Status Start(int port, bool loopback_only=false)
ProjectPathSource project_path_source_
std::unique_ptr< std::thread > server_thread_
std::atomic< bool > is_running_
EditorControl editor_control_
Provider for symbol (label) resolution in disassembly.
#define LOG_ERROR(category, format,...)
Definition log.h:140
#define LOG_INFO(category, format,...)
Definition log.h:136
void HandleStateUpdate(const httplib::Request &req, httplib::Response &res)
void ApplyCorsHeaders(httplib::Response &res)
void HandleCorsPreflight(const httplib::Request &req, httplib::Response &res)
void HandleEditorControl(const httplib::Request &req, httplib::Response &res, const std::function< absl::StatusOr< std::string >(const std::string &)> &action)
void HandleBreakpointHit(const httplib::Request &req, httplib::Response &res)
void HandleListModels(const httplib::Request &req, httplib::Response &res)
void HandleGetSymbols(const httplib::Request &req, httplib::Response &res, yaze::emu::debug::SymbolProvider *symbols)
void HandleNavigate(const httplib::Request &req, httplib::Response &res)
void HandleWindowShow(const httplib::Request &req, httplib::Response &res, const std::function< bool()> &action)
void HandleAnnotationUpdate(const httplib::Request &req, httplib::Response &res, const std::string &project_path)
void HandleRenderDungeonMetadata(const httplib::Request &req, httplib::Response &res, yaze::app::service::RenderService *render_service)
void HandleRenderDungeon(const httplib::Request &req, httplib::Response &res, yaze::app::service::RenderService *render_service)
void HandleCommandExecute(const httplib::Request &req, httplib::Response &res, yaze::Rom *rom)
void HandleAnnotationDelete(const httplib::Request &req, httplib::Response &res, const std::string &project_path)
void HandleAnnotationList(const httplib::Request &req, httplib::Response &res, const std::string &project_path)
void HandleCommandExecuteDisabled(const httplib::Request &req, httplib::Response &res)
void HandleAnnotationCreate(const httplib::Request &req, httplib::Response &res, const std::string &project_path)
void HandleCommandList(const httplib::Request &req, httplib::Response &res)
void HandleWindowHide(const httplib::Request &req, httplib::Response &res, const std::function< bool()> &action)
void HandleHealth(const httplib::Request &req, httplib::Response &res, const BonjourPublisher *bonjour)