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