SNode.C
Loading...
Searching...
No Matches
express_compat_server.cpp
Go to the documentation of this file.
1/*
2 * Express compatibility server for SNode.C/Express
3 *
4 * Intended to be used alongside the Node.js/Express reference server in this suite.
5 */
6#include "core/SNodeC.h"
7#include "express/legacy/in/WebApp.h"
8#include "log/Logger.h"
9
10#include <nlohmann/json.hpp>
11
12// IWYU pragma: no_include <nlohmann/detail/json_ref.hpp>
13// IWYU pragma: no_include <nlohmann/json_fwd.hpp>
14
15using express::Router;
16using nlohmann::json;
17
18using Trace = std::vector<json>;
19
20static json snapshot(const std::shared_ptr<express::Request>& req, const std::string& label) {
21 json out = json::object();
22 out["label"] = label;
23 out["method"] = req->method;
24 out["url"] = req->url;
25 out["originalUrl"] = req->originalUrl;
26 out["baseUrl"] = req->baseUrl;
27 out["path"] = req->path;
28
29 json params = json::object();
30 for (const auto& [k, v] : req->params) {
31 params[k] = v;
32 }
33 out["params"] = params;
34
35 json query = json::object();
36 for (const auto& [k, v] : req->queries) {
37 query[k] = v;
38 }
39 out["query"] = query;
40
41 json headers = json::object();
42 headers["x-test"] = req->get("X-Test");
43 out["headers"] = headers;
44
45 return out;
46}
47
48static void ensureTrace(const std::shared_ptr<express::Request>& req) {
49 // Create the trace attribute if it doesn't exist yet.
50 req->setAttribute<Trace>(Trace{}, "trace");
51}
52
53static void tracePush(const std::shared_ptr<express::Request>& req, const std::string& label) {
56 [&](Trace& t) {
57 t.emplace_back(snapshot(req, label));
58 },
59 "trace");
60}
61
62static json traceGet(const std::shared_ptr<express::Request>& req) {
63 json arr = json::array();
65 [&](const Trace& t) {
66 for (auto const& e : t) {
67 arr.push_back(e);
68 }
69 },
70 "trace");
71 return arr;
72}
73
74int main(int argc, char* argv[]) {
75 core::SNodeC::init(argc, argv);
76
77 const express::legacy::in::WebApp app("express-compat");
78
79 // Meta
80 app.get("/__meta", [] APPLICATION(req, res) {
81 res->json({{"ok", true}, {"server", "snodec"}, {"express", true}});
82 });
83
84 // Basic
85 app.get("/health", [] APPLICATION(req, res) {
86 res->json({{"ok", true}, {"label", "health"}});
87 });
88
89 // Case-insensitive routing demo
90 app.get("/Case/Path", [] APPLICATION(req, res) {
91 res->json(snapshot(req, "case"));
92 });
93
94 // Trailing slash demo (strictRouting off => matches both)
95 app.get("/trail/", [] APPLICATION(req, res) {
96 res->json(snapshot(req, "trail"));
97 });
98
99 // Wildcards
100 app.get("/file/*", [] APPLICATION(req, res) {
101 res->json(snapshot(req, "file"));
102 });
103 app.get("/a/*/b/*", [] APPLICATION(req, res) {
104 res->json(snapshot(req, "multi_wild"));
105 });
106
107 // Param decoding
108 app.get("/p/:x", [] APPLICATION(req, res) {
109 res->json(snapshot(req, "param"));
110 });
111
112 // Query decoding/casing
113 app.get("/query/echo", [] APPLICATION(req, res) {
114 res->json(snapshot(req, "query_echo"));
115 });
116
117 // HEAD -> GET fallback (handled by dispatcher/response code)
118 app.get("/head-demo", [] APPLICATION(req, res) {
119 res->set("X-Demo", "1");
120 res->send("head body");
121 });
122
123 // next('route') demo
124 app.get(
125 "/nr/:id(\\d+)",
126 [] MIDDLEWARE(req, res, next) {
127 if (req->params["id"] == "0") {
128 next("route");
129 return;
130 }
131 next();
132 },
133 [] APPLICATION(req, res) {
134 res->json(snapshot(req, "nr_primary"));
135 });
136 app.get("/nr/:id(\\d+)", [] APPLICATION(req, res) {
137 res->json(snapshot(req, "nr_fallback"));
138 });
139
140 // next('router') demo
141 const Router guarded;
142 guarded.use([] MIDDLEWARE(req, res, next) {
143 if (req->queries["allow"] != "true") {
144 next("router");
145 return;
146 }
147 next();
148 });
149 guarded.get("/stats", [] APPLICATION(req, res) {
150 res->json(snapshot(req, "guarded_stats"));
151 });
152 app.use("/guarded", guarded);
153
154 // Fallback when router skipped
155 app.get("/guarded/:rest(.*)", [] APPLICATION(req, res) {
156 res->status(403).json({{"label", "guarded_fallback"}, {"status", 403}});
157 });
158
159 // Root-mounted router (baseUrl should be empty string)
160 const Router root;
161 root.get("/root/test", [] APPLICATION(req, res) {
162 res->json(snapshot(req, "root_mount"));
163 });
164 app.use("/", root);
165
166 // Nested routers trace: req.url / baseUrl stripping
167 const Router api;
168 api.use([] MIDDLEWARE(req, res, next) {
169 tracePush(req, "api.use");
170 next();
171 });
172
173 const Router v1;
174 v1.use([] MIDDLEWARE(req, res, next) {
175 tracePush(req, "v1.use");
176 next();
177 });
178 v1.get("/users/:id", [] APPLICATION(req, res) {
179 tracePush(req, "handler");
180 res->json({{"label", "nested_trace"}, {"trace", traceGet(req)}});
181 });
182
183 api.use("/v1", v1);
184 app.use("/api", api);
185
186 // mergeParams demo (requires your mergeParams patch-set)
187 const Router mpMerge;
188 mpMerge.setMergeParams(true);
189 mpMerge.get("/users/:id", [] APPLICATION(req, res) {
190 res->json(snapshot(req, "mp_merge"));
191 });
192 app.use("/mp/merge/t/:tenant", mpMerge);
193
194 const Router mpNoMerge;
195 mpNoMerge.get("/users/:id", [] APPLICATION(req, res) {
196 res->json(snapshot(req, "mp_nomerge"));
197 });
198 app.use("/mp/nomerge/t/:tenant", mpNoMerge);
199
200 // Params scoping trace: merge vs no-merge
201 auto makeScope = [](bool merge) {
202 const Router parent;
203 parent.setMergeParams();
204 parent.use([merge](auto const& req, auto const&, auto& next) {
205 tracePush(req, merge ? "scopeMerge.parent" : "scopeNoMerge.parent");
206 next();
207 });
208
209 const Router child;
210 child.setMergeParams(merge);
211 child.use([merge](auto const& req, auto const&, auto& next) {
212 tracePush(req, merge ? "scopeMerge.child" : "scopeNoMerge.child");
213 next();
214 });
215 child.get("/end", [merge](auto const& req, auto const& res) {
216 tracePush(req, merge ? "scopeMerge.handler" : "scopeNoMerge.handler");
217 res->json({{"label", merge ? "scope_merge" : "scope_nomerge"}, {"trace", traceGet(req)}});
218 });
219
220 parent.use("/b/:b", child);
221 return parent;
222 };
223
224 app.use("/scope/nomerge/:a", makeScope(false));
225 app.use("/scope/merge/:a", makeScope(true));
226
227 // Param decoding (valid)
228 app.get("/decode/:p", [] APPLICATION(req, res) {
229 res->json(snapshot(req, "decode_ok"));
230 });
231
232 // 404 (GET only, enough for this suite)
233 app.get("/:rest(.*)", [] APPLICATION(req, res) {
234 res->status(404).json({{"label", "not_found"}, {"path", req->path}});
235 });
236
237 app.listen(8080, [](const express::legacy::in::WebApp::SocketAddress& socketAddress, const core::socket::State& state) {
238 switch (state) {
239 case core::socket::State::OK:
240 VLOG(1) << "express-compat listening on '" << socketAddress.toString() << "'";
241 break;
242 case core::socket::State::DISABLED:
243 VLOG(1) << "express-compat disabled";
244 break;
245 case core::socket::State::ERROR:
246 LOG(ERROR) << "express-compat " << socketAddress.toString() << ": " << state.what();
247 break;
248 case core::socket::State::FATAL:
249 LOG(FATAL) << "express-compat " << socketAddress.toString() << ": " << state.what();
250 break;
251 }
252 });
253
254 return core::SNodeC::start();
255}
#define LOG(level)
Definition Logger.h:148
#define VLOG(level)
Definition Logger.h:164
#define APPLICATION(req, res)
Definition Router.h:68
#define MIDDLEWARE(req, res, next)
Definition Router.h:63
static void init(int argc, char *argv[])
Definition SNodeC.cpp:54
static int start(const utils::Timeval &timeOut={LONG_MAX, 0})
Definition SNodeC.cpp:60
static constexpr int DISABLED
Definition State.h:56
static constexpr int ERROR
Definition State.h:57
std::string what() const
Definition State.cpp:114
static constexpr int FATAL
Definition State.h:58
static constexpr int OK
Definition State.h:55
void operator()(const std::string &how="") const
Definition Next.cpp:56
std::string originalUrl
Definition Request.h:77
std::string url
Definition Request.h:98
std::map< std::string, std::string > params
Definition Request.h:81
const std::string & get(const std::string &key, int i=0) const
Definition Request.cpp:95
std::string method
Definition Request.h:97
std::string baseUrl
Definition Request.h:76
std::map< std::string, std::string > queries
Definition Request.h:103
std::string path
Definition Request.h:79
Response & set(const std::string &field, const std::string &value, bool overwrite=true)
Definition Response.cpp:129
void send(const std::string &chunk)
Definition Response.cpp:169
Response & status(int status)
Definition Response.cpp:117
void json(const nlohmann::json &json)
Definition Response.cpp:72
Route & get(const Router &router) const
Definition Router.cpp:102
const Router & setMergeParams(bool mergeParams=true) const
Definition Router.cpp:90
WebAppT(const std::string &name)
Definition WebAppT.h:74
LogMessage(Level level, int verboseLevel=-1, bool withErrno=false)
Definition Logger.cpp:280
std::string toString(bool expanded=true) const override
const Super & listen(uint16_t port, const std::function< void(const SocketAddress &, core::socket::State)> &onStatus) const
constexpr bool setAttribute(const Attribute &&attribute, const std::string &subKey="", bool overwrite=false)
bool getAttribute(const std::function< void(Attribute &)> &onFound, const std::string &subKey="") const
int main(int argc, char *argv[])
std::vector< json > Trace
static void ensureTrace(const std::shared_ptr< express::Request > &req)
static void tracePush(const std::shared_ptr< express::Request > &req, const std::string &label)
static json snapshot(const std::shared_ptr< express::Request > &req, const std::string &label)
static json traceGet(const std::shared_ptr< express::Request > &req)