SNode.C
Loading...
Searching...
No Matches
testexpressnext.cpp
Go to the documentation of this file.
1/*
2 * SNode.C - A Slim Toolkit for Network Communication
3 * Copyright (C) Volker Christian <me@vchrist.at>
4 * 2020, 2021, 2022, 2023, 2024, 2025, 2026
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published
8 * by the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20/*
21 * MIT License
22 *
23 * Permission is hereby granted, free of charge, to any person obtaining a copy
24 * of this software and associated documentation files (the "Software"), to deal
25 * in the Software without restriction, including without limitation the rights
26 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
27 * copies of the Software, and to permit persons to whom the Software is
28 * furnished to do so, subject to the following conditions:
29 *
30 * The above copyright notice and this permission notice shall be included in
31 * all copies or substantial portions of the Software.
32 *
33 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
39 * THE SOFTWARE.
40 */
41
42#include "core/SNodeC.h"
43#include "core/timer/Timer.h"
44#include "express/legacy/in/WebApp.h"
45#include "log/Logger.h"
46#include "web/http/legacy/in/Client.h"
47
48#include <cstddef>
49#include <deque>
50
51using express::Router;
52
53struct TestCase {
54 std::string name;
55 std::string url;
57 std::string expectedBody;
58 std::string connection;
59};
60
61// "next(async route) fallback for id=0"
62
64public:
66 : testCases(
67 {{"next() middleware chain", "/next/basic", 200, "next() reached final handler", "keep-alive"},
68 {"next() asynchronous middleware chain", "/next/async/basic", 200, "next(async) reached final handler", "keep-alive"},
69 {"next('route') fallback", "/next/route/0", 200, "next(route) fallback for id=0", "keep-alive"},
70 {"next('route') primary", "/next/route/7", 200, "next(route) primary for id=7", "keep-alive"},
71 {"next('route') asynchronous fallback", "/next/async/route/0", 200, "next(async route) fallback for id=0", "keep-alive"},
72 {"next('route') asynchronous primary", "/next/async/route/7", 200, "next(async route) primary for id=7", "keep-alive"},
73 {"next('router') fallback", "/next/router/resource?allow=false", 403, "next(router) denied by fallback", "keep-alive"},
74 {"next('router') inside router", "/next/router/resource?allow=true", 200, "next(router) router handler", "keep-alive"},
75 {"next('router') asynchronous fallback",
76 "/next/async/router/resource?allow=false",
77 403,
78 "next(async router) denied by fallback",
79 "keep-alive"},
80 {"next('router') asynchronous inside router",
81 "/next/async/router/resource?allow=true",
82 200,
83 "next(async router) router handler",
84 "close"}}) {
85 }
86
87 void run() {
88 client = std::make_shared<Client>( //
89 [this](const std::shared_ptr<MasterRequest>& connectedRequest) {
90 masterRequest = connectedRequest;
92 },
93 [](const std::shared_ptr<MasterRequest>&) {
94 });
95
97 "localhost",
98 18080,
99 [this](const Client::SocketAddress&, const core::socket::State& state) {
100 if (state != core::socket::State::OK) {
101 ++failures;
102 LOG(ERROR) << "FAIL: connect failed: " << state.what();
104 [] {
106 },
107 1);
108 }
109 });
110 }
111
112 std::size_t getFailures() const {
113 return failures;
114 }
115
116private:
117 using Client = web::http::legacy::in::Client;
118 using MasterRequest = Client::MasterRequest;
119 using Request = Client::Request;
120 using Response = Client::Response;
121
123 if (testCases.empty()) {
124 LOG(INFO) << "All express next() tests executed. failures=" << failures;
127 }
129 [] {
131 },
132 1);
133 return;
134 }
135
137 ++failures;
138 LOG(ERROR) << "FAIL: master request not connected";
140 [] {
142 },
143 1);
144 return;
145 }
146
147 const TestCase current = testCases.front();
148 testCases.pop_front();
149
151 masterRequest->method = "GET";
152 masterRequest->url = current.url;
153 masterRequest->set("Connection", current.connection);
155 [this, current]([[maybe_unused]] const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
156 const std::string body(res->body.begin(), res->body.end());
157 const bool statusOk = std::stoi(res->statusCode) == current.expectedStatus;
158 const bool bodyOk = body.find(current.expectedBody) != std::string::npos;
159
160 if (statusOk && bodyOk) {
161 LOG(INFO) << "PASS: " << current.name << " status=" << res->statusCode << " body='" << body << "'";
162 } else {
163 ++failures;
164 LOG(ERROR) << "FAIL: " << current.name << " expected status=" << current.expectedStatus << " expected body fragment='"
165 << current.expectedBody << "'"
166 << " got status=" << res->statusCode << " body='" << body << "'";
167 }
168
170 },
171 [this, current]([[maybe_unused]] const std::shared_ptr<Request>& req, const std::string& reason) {
172 ++failures;
173 LOG(ERROR) << "FAIL: " << current.name << " parse-error: " << reason;
175 });
176 }
177
178 std::deque<TestCase> testCases;
179 std::shared_ptr<Client> client;
180 std::shared_ptr<MasterRequest> masterRequest;
181 std::size_t failures = 0;
182};
183
184int main(int argc, char* argv[]) {
185 core::SNodeC::init(argc, argv);
186
187 const express::legacy::in::WebApp app("testexpressnext-server");
188
189 app.get(
190 "/next/basic",
191 [] MIDDLEWARE(req, res, next) {
192 req->setAttribute<std::string>("middleware-seen", "yes");
193 next();
194 },
195 [] APPLICATION(req, res) {
196 std::string marker = "no";
197 req->getAttribute<std::string>(
198 [&](const std::string& value) {
199 marker = value;
200 },
201 "middleware-seen");
202 res->status(200).send("next() reached final handler (middleware=" + marker + ")");
203 });
204
205 app.get(
206 "/next/async/basic",
207 [] MIDDLEWARE(req, res, next) {
208 req->setAttribute<std::string>("middleware-seen-async", "yes");
210 next();
211 });
212 },
213 [] APPLICATION(req, res) {
214 std::string marker = "no";
215 req->getAttribute<std::string>(
216 [&](const std::string& value) {
217 marker = value;
218 },
219 "middleware-seen-async");
220 res->status(200).send("next(async) reached final handler (middleware=" + marker + ")");
221 });
222
223 app.get(
224 "/next/route/:id(\\d+)",
225 [] MIDDLEWARE(req, res, next) {
226 if (req->params["id"] == "0") {
227 next("route");
228 return;
229 }
230 next();
231 },
232 [] APPLICATION(req, res) {
233 res->status(200).send("next(route) primary for id=" + req->params["id"]);
234 });
235
236 app.get("/next/route/:id(\\d+)", [] APPLICATION(req, res) {
237 res->status(200).send("next(route) fallback for id=" + req->params["id"]);
238 });
239
240 app.get(
241 "/next/async/route/:id(\\d+)",
242 [] MIDDLEWARE(req, res, next) {
243 if (req->params["id"] == "0") {
245 next("route");
246 });
247 return;
248 }
250 next();
251 });
252 },
253 [] APPLICATION(req, res) {
254 res->status(200).send("next(async route) primary for id=" + req->params["id"]);
255 });
256
257 app.get("/next/async/route/:id(\\d+)", [] APPLICATION(req, res) {
258 res->status(200).send("next(async route) fallback for id=" + req->params["id"]);
259 });
260
261 const Router guarded;
262 guarded.use([] MIDDLEWARE(req, res, next) {
263 if (req->queries["allow"] != "true") {
264 next("router");
265 return;
266 }
267 next();
268 });
269 guarded.get("/resource", [] APPLICATION(req, res) {
270 res->status(200).send("next(router) router handler");
271 });
272
273 app.use("/next/router", guarded);
274 app.get("/next/router/:rest(.*)", [] APPLICATION(req, res) {
275 res->status(403).send("next(router) denied by fallback");
276 });
277
278 const Router guardedAsync;
279 guardedAsync.use([] MIDDLEWARE(req, res, next) {
280 if (req->queries["allow"] != "true") {
282 next("router");
283 });
284 return;
285 }
287 next();
288 });
289 });
290 guardedAsync.get("/resource", [] APPLICATION(req, res) {
291 res->status(200).send("next(async router) router handler");
292 });
293
294 app.use("/next/async/router", guardedAsync);
295 app.get("/next/async/router/:rest(.*)", [] APPLICATION(req, res) {
296 res->status(403).send("next(async router) denied by fallback");
297 });
298
300
301 app.listen(18080, [](const express::legacy::in::WebApp::SocketAddress& socketAddress, const core::socket::State& state) {
302 switch (state) {
303 case core::socket::State::OK:
304 VLOG(1) << "testexpressnext listening on '" << socketAddress.toString() << "'";
305 break;
306 case core::socket::State::DISABLED:
307 VLOG(1) << "testexpressnext disabled";
308 break;
309 case core::socket::State::ERROR:
310 LOG(ERROR) << "testexpressnext " << socketAddress.toString() << ": " << state.what();
311 break;
312 case core::socket::State::FATAL:
313 LOG(FATAL) << "testexpressnext " << socketAddress.toString() << ": " << state.what();
314 break;
315 }
316 });
317
318 NextTester nextTester;
320 [&nextTester] {
321 nextTester.run();
322 },
323 1);
324
325 const int rc = core::SNodeC::start();
326 if (nextTester.getFailures() > 0) {
327 LOG(ERROR) << "testexpressnext finished with failures=" << nextTester.getFailures();
328 return 1;
329 }
330
331 LOG(INFO) << "testexpressnext finished successfully";
332 return rc;
333}
#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
Client::MasterRequest MasterRequest
std::deque< TestCase > testCases
void dispatchNextRequest()
std::shared_ptr< MasterRequest > masterRequest
Client::Request Request
web::http::legacy::in::Client Client
std::shared_ptr< Client > client
Client::Response Response
std::size_t failures
std::size_t getFailures() const
static void atNextTick(const std::function< void(void)> &callBack)
static void init(int argc, char *argv[])
Definition SNodeC.cpp:54
static void stop()
Definition SNodeC.cpp:64
static int start(const utils::Timeval &timeOut={LONG_MAX, 0})
Definition SNodeC.cpp:60
Config * getConfig() const
Definition Socket.hpp:65
static constexpr int DISABLED
Definition State.h:56
bool operator==(const int &state) const
Definition State.cpp:74
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
static Timer singleshotTimer(const std::function< void()> &dispatcher, const utils::Timeval &timeout)
Definition Timer.cpp:57
void operator()(const std::string &how="") const
Definition Next.cpp:56
std::map< std::string, std::string > params
Definition Request.h:81
void send(const std::string &chunk)
Definition Response.cpp:169
Response & status(int status)
Definition Response.cpp:117
Route & get(const Router &router) const
Definition Router.cpp:102
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 & connect(const std::string &ipOrHostname, uint16_t port, const std::function< void(const SocketAddress &, core::socket::State)> &onStatus) const
const Super & listen(uint16_t port, const std::function< void(const SocketAddress &, core::socket::State)> &onStatus) const
ConfigSocketServer * setReuseAddress(bool reuseAddress=true)
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
bool end(const std::function< void(const std::shared_ptr< Request > &, const std::shared_ptr< Response > &)> &onResponseReceived, const std::function< void(const std::shared_ptr< Request > &, const std::string &)> &onResponseParseError)
Definition Request.cpp:525
Request & set(const std::string &field, const std::string &value, bool overwrite=true)
Definition Request.cpp:143
std::vector< char > body
Definition Response.h:85
int main(int argc, char *argv[])
Definition Timer.h:59
web::http::client::Client< net::in::stream::legacy::SocketClient > Client
Definition Client.h:54
std::string name
std::string connection
std::string expectedBody
std::string url