SNode.C
Loading...
Searching...
No Matches
clients.h
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
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#ifndef APPS_HTTP_MODEL_CLIENTS_H
21#define APPS_HTTP_MODEL_CLIENTS_H
22
23#define QUOTE_INCLUDE(a) STR_INCLUDE(a)
24#define STR_INCLUDE(a) #a
25
26// clang-format off
27#define CLIENT_INCLUDE QUOTE_INCLUDE(web/http/STREAM/NET/Client.h)
28// clang-format on
29
30#include CLIENT_INCLUDE // IWYU pragma: export
31
32#ifndef DOXYGEN_SHOULD_SKIP_THIS
33
34#include "log/Logger.h"
35#include "web/http/http_utils.h"
36
37#if (STREAM_TYPE == TLS) // tls
38#include <cstddef>
39#include <openssl/ssl.h>
40#include <openssl/x509v3.h>
41#endif
42
43#endif /* DOXYGEN_SHOULD_SKIP_THIS */
44
45static void logResponse(const std::shared_ptr<web::http::client::Request>& req, const std::shared_ptr<web::http::client::Response>& res) {
46 VLOG(1) << req->getSocketContext()->getSocketConnection()->getConnectionName() << " HTTP response: " << req->method << " " << req->url
47 << " HTTP/" << req->httpMajor << "." << req->httpMinor << "\n"
48 << httputils::toString(req->method,
49 req->url,
50 "HTTP/" + std::to_string(req->httpMajor) + "." + std::to_string(req->httpMinor),
51 req->getQueries(),
52 req->getHeaders(),
53 req->getCookies(),
54 {})
55 << "\n"
56 << httputils::toString(res->httpVersion, res->statusCode, res->reason, res->headers, res->cookies, res->body);
57}
58
59#if (STREAM_TYPE == LEGACY) // legacy
60
61namespace apps::http::legacy {
62
67
68 static Client getClient() {
69 Client client(
70 "httpclient",
71 [](const std::shared_ptr<Request>& req) {
72 VLOG(1) << req->getSocketContext()->getSocketConnection()->getConnectionName() << ": OnRequestStart";
73
74 req->httpMinor = 0;
75 req->url = "/";
76 req->set("Connection", "keep-alive");
77 req->setTrailer("MyTrailer",
78 "MyTrailerValue"); // The "Trailer" header field should be populated but the Trailer itself should not be
79 // send here because there is no content which is send using "Transfer-Encoding:chunked"
80 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
81 logResponse(req, res);
82 });
83#define LONG
84#ifdef LONG
85 req->url = "/hihihih";
86 req->set("Connection", "keep-alive");
87 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
88 logResponse(req, res);
89 });
90
91 req->httpMinor = 1;
92 req->url = "/index.html";
93 // req->set("Connection", "keep-alive");
94 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
95 logResponse(req, res);
96 });
97 req->url = "/";
98 req->set("Connection", "keep-alive");
99 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
100 logResponse(req, res);
101 });
102 req->url = "/index.html";
103 req->set("Connection", "keep-alive");
104 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
105 logResponse(req, res);
106 });
107 req->url = "/";
108 req->set("Connection", "keep-alive");
109 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
110 logResponse(req, res);
111 });
112 req->url = "/index.html";
113 req->set("Connection", "keep-alive");
114 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
115 logResponse(req, res);
116 });
117 req->url = "/";
118 req->set("Connection", "keep-alive");
119 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
120 logResponse(req, res);
121 });
122 req->url = "/index.html";
123 req->set("Connection", "keep-alive");
124 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
125 logResponse(req, res);
126 });
127 req->url = "/";
128 req->set("Connection", "keep-alive");
129 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
130 logResponse(req, res);
131 });
132 req->url = "/index.html";
133 req->set("Connection", "keep-alive");
134 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
135 logResponse(req, res);
136 });
137 req->url = "/";
138 req->set("Connection", "keep-alive");
139 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
140 logResponse(req, res);
141 });
142 req->url = "/index.html";
143 req->set("Connection", "keep-alive");
144 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
145 logResponse(req, res);
146 });
147 req->url = "/";
148 req->set("Connection", "keep-alive");
149 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
150 logResponse(req, res);
151 });
152 req->url = "/index.html";
153 req->set("Connection", "keep-alive");
154 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
155 logResponse(req, res);
156 });
157 req->url = "/";
158 req->set("Connection", "keep-alive");
159 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
160 logResponse(req, res);
161 });
162 req->url = "/index.html";
163 req->set("Connection", "keep-alive");
164 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
165 logResponse(req, res);
166 });
167 req->url = "/";
168 req->set("Connection", "keep-alive");
169 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
170 logResponse(req, res);
171 });
172
173 req->httpMinor = 1;
174 req->method = "POST";
175 req->url = "/";
176 req->set("Test", "aaa");
177 req->setTrailer("MyTrailer1",
178 "MyTrailerValue1"); // Full trailer processing. Header field "Trailer" set and the Trailer itself is also
179 // sent because here content will be sent with "Transfer-Encoding:chunked"
180 req->setTrailer("MyTrailer2", "MyTrailerValue2");
181 req->setTrailer("MyTrailer3", "MyTrailerValue3");
182 req->setTrailer("MyTrailer4", "MyTrailerValue4");
183 req->setTrailer("MyTrailer5", "MyTrailerValue5");
184 req->setTrailer("MyTrailer6", "MyTrailerValue6");
185 req->query("Query1", "QueryValue1");
186 req->query("Query2", "QueryValue2");
187 req->sendFile(
188 "/home/voc/projects/snodec/snode.c/CMakeLists.txt",
189 [req](int ret) {
190 if (ret == 0) {
191 VLOG(1) << req->getSocketContext()->getSocketConnection()->getConnectionName()
192 << " HTTP: Request accepted: POST / HTTP/" << req->httpMajor << "." << req->httpMinor;
193 VLOG(1) << " /home/voc/projects/snodec/snode.c/CMakeLists.txt";
194 } else {
195 LOG(ERROR) << req->getSocketContext()->getSocketConnection()->getConnectionName()
196 << " HTTP: Request failed: POST / HTTP/" << req->httpMajor << "." << req->httpMinor;
197 PLOG(ERROR) << " /home/voc/projects/snodec/snode.c/CMakeLists.txt";
198
199 req->set("Connection", "close");
200 req->end([]([[maybe_unused]] const std::shared_ptr<Request>& req,
201 [[maybe_unused]] const std::shared_ptr<Response>& res) {
202 });
203 }
204 },
205 [](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
206 logResponse(req, res);
207
208 req->init();
209 req->method = "POST";
210 req->url = "/";
211 req->set("Connection", "keep-alive");
212 req->set("Test", "bbb");
213 req->sendFile(
214 "/home/voc/projects/snodec/snode.c/CMakeLists.tt",
215 [req](int ret) {
216 if (ret == 0) {
217 VLOG(1) << req->getSocketContext()->getSocketConnection()->getConnectionName()
218 << " HTTP: Request accepted: POST / HTTP/" << req->httpMajor << "." << req->httpMinor;
219 VLOG(1) << " /home/voc/projects/snodec/snode.c/CMakeLists.tt";
220 } else {
221 LOG(ERROR) << req->getSocketContext()->getSocketConnection()->getConnectionName()
222 << " HTTP: Request failed: POST / HTTP/" << req->httpMajor << "." << req->httpMinor;
223 PLOG(ERROR) << " /home/voc/projects/snodec/snode.c/CMakeLists.tt";
224
225 req->init();
226 req->method = "GET";
227 req->url = "/";
228 req->set("Connection", "close");
229 req->set("Test", "ccc");
230 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
231 logResponse(req, res);
232 });
233 }
234 },
235 [](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
236 logResponse(req, res);
237 });
238 });
239 req->init();
240 req->method = "GET";
241 req->url = "/";
242 req->set("Connection", "close");
243 req->set("Test", "xxx");
244 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
245 logResponse(req, res);
246 });
247 core::EventReceiver::atNextTick([req]() {
248 req->method = "POST";
249 req->url = "/";
250 req->set("Connection", "keep-alive");
251 req->set("Test", "ddd");
252 req->sendFile(
253 "/home/voc/projects/snodec/snode.c/CMakeLists.txt",
254 [req](int ret) {
255 if (ret == 0) {
256 VLOG(1) << req->getSocketContext()->getSocketConnection()->getConnectionName()
257 << " HTTP: Request accepted: POST / HTTP/" << req->httpMajor << "." << req->httpMinor;
258 VLOG(1) << " /home/voc/projects/snodec/snode.c/CMakeLists.txt";
259 } else {
260 LOG(ERROR) << req->getSocketContext()->getSocketConnection()->getConnectionName()
261 << " HTTP: Request failed: POST / HTTP/" << req->httpMajor << "." << req->httpMinor;
262 PLOG(ERROR) << " /home/voc/projects/snodec/snode.c/CMakeLists.txt";
263
264 req->set("Connection", "close");
265 req->end([]([[maybe_unused]] const std::shared_ptr<Request>& req,
266 [[maybe_unused]] const std::shared_ptr<Response>& res) {
267 });
268 }
269 },
270 [](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
271 logResponse(req, res);
272 });
273
274 req->method = "POST";
275 req->url = "/";
276 req->set("Connection", "keep-alive");
277 req->set("Test", "eee");
278 req->setTrailer("MyTrailer1", "MyTrailerValue1");
279 req->setTrailer("MyTrailer2", "MyTrailerValue2");
280 req->sendFile(
281 "/home/voc/projects/snodec/snode.c/CMakeLists.txt",
282 [req](int ret) {
283 if (ret == 0) {
284 VLOG(1) << req->getSocketContext()->getSocketConnection()->getConnectionName()
285 << " HTTP: Request accepted: POST / HTTP/" << req->httpMajor << "." << req->httpMinor;
286 VLOG(1) << " /home/voc/projects/snodec/snode.c/CMakeLists.txt";
287 } else {
288 LOG(ERROR) << req->getSocketContext()->getSocketConnection()->getConnectionName()
289 << " HTTP: Request failed: POST / HTTP/" << req->httpMajor << "." << req->httpMinor;
290 PLOG(ERROR) << " /home/voc/projects/snodec/snode.c/CMakeLists.txt";
291
292 req->set("Connection", "close");
293 req->end([]([[maybe_unused]] const std::shared_ptr<Request>& req,
294 [[maybe_unused]] const std::shared_ptr<Response>& res) {
295 });
296 }
297 },
298 [](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
299 logResponse(req, res);
300 });
301 });
302#endif
303 },
304 []([[maybe_unused]] const std::shared_ptr<Request>& req) {
305 VLOG(1) << req->getSocketContext()->getSocketConnection()->getConnectionName() << ": OnRequestEnd";
306 });
307
308 client.setOnConnect([](SocketConnection* socketConnection) { // onConnect
309 VLOG(1) << socketConnection->getConnectionName() << ": OnConnect";
310
311 VLOG(1) << "\tLocal: " << socketConnection->getLocalAddress().toString();
312 VLOG(1) << "\tPeer: " << socketConnection->getRemoteAddress().toString();
313 });
314
315 client.setOnDisconnect([](SocketConnection* socketConnection) { // onDisconnect
316 VLOG(1) << socketConnection->getConnectionName() << ": OnDisconnect";
317
318 VLOG(1) << "\tLocal: " << socketConnection->getLocalAddress().toString();
319 VLOG(1) << "\tPeer: " << socketConnection->getRemoteAddress().toString();
320 });
321
322 return client;
323 }
324
325} // namespace apps::http::legacy
326
327#endif // (STREAM_TYPE == LEGACY) // legacy
328
329#if (STREAM_TYPE == TLS) // tls
330
331namespace apps::http::tls {
332
337
338 static Client getClient() {
339 Client client(
340 "httpclient",
341 [](const std::shared_ptr<Request>& req) {
342 VLOG(1) << req->getSocketContext()->getSocketConnection()->getConnectionName() << ": OnRequestStart";
343
344 req->url = "/";
345 req->set("Connection", "keep-alive");
346 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
347 logResponse(req, res);
348 });
349 req->url = "/";
350 req->set("Connection", "keep-alive");
351 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
352 logResponse(req, res);
353 });
354 req->url = "/index.html";
355 req->set("Connection", "keep-alive");
356 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
357 logResponse(req, res);
358 });
359 req->url = "/";
360 req->set("Connection", "keep-alive");
361 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
362 logResponse(req, res);
363 });
364 req->url = "/index.html";
365 req->set("Connection", "keep-alive");
366 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
367 logResponse(req, res);
368 });
369 req->url = "/";
370 req->set("Connection", "keep-alive");
371 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
372 logResponse(req, res);
373 });
374 req->url = "/index.html";
375 req->set("Connection", "keep-alive");
376 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
377 logResponse(req, res);
378 });
379 req->url = "/";
380 req->set("Connection", "keep-alive");
381 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
382 logResponse(req, res);
383 });
384 req->url = "/index.html";
385 req->set("Connection", "keep-alive");
386 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
387 logResponse(req, res);
388 });
389 req->url = "/";
390 req->set("Connection", "keep-alive");
391 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
392 logResponse(req, res);
393 });
394 req->url = "/index.html";
395 req->set("Connection", "keep-alive");
396 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
397 logResponse(req, res);
398 });
399 req->url = "/";
400 req->set("Connection", "keep-alive");
401 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
402 logResponse(req, res);
403 });
404 req->url = "/index.html";
405 req->set("Connection", "keep-alive");
406 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
407 logResponse(req, res);
408 });
409 req->url = "/";
410 req->set("Connection", "keep-alive");
411 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
412 logResponse(req, res);
413 });
414 req->url = "/index.html";
415 req->set("Connection", "keep-alive");
416 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
417 logResponse(req, res);
418 });
419 req->url = "/";
420 req->set("Connection", "keep-alive");
421 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
422 logResponse(req, res);
423 });
424 req->url = "/index.html";
425 req->set("Connection", "keep-alive");
426 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
427 logResponse(req, res);
428 });
429 req->url = "/";
430 req->set("Connection", "close");
431 req->end([](const std::shared_ptr<Request>& req, const std::shared_ptr<Response>& res) {
432 logResponse(req, res);
433 });
434 },
435 []([[maybe_unused]] const std::shared_ptr<Request>& req) {
436 VLOG(1) << req->getSocketContext()->getSocketConnection()->getConnectionName() << ": OnRequestEnd";
437 });
438
439 client.setOnConnect([](SocketConnection* socketConnection) { // onConnect
440 VLOG(1) << "OnConnect " << socketConnection->getConnectionName();
441
442 VLOG(1) << "\tLocal: " << socketConnection->getLocalAddress().toString();
443 VLOG(1) << "\tPeer: " << socketConnection->getRemoteAddress().toString();
444
445 /* Enable automatic hostname checks */
446 // X509_VERIFY_PARAM* param = SSL_get0_param(socketConnection->getSSL());
447
448 // X509_VERIFY_PARAM_set_hostflags(param, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
449 // if (!X509_VERIFY_PARAM_set1_host(param, "localhost", sizeof("localhost") - 1)) {
450 // // handle error
451 // socketConnection->close();
452 // }
453 });
454
455 client.setOnConnected([](SocketConnection* socketConnection) { // onConnected
456 VLOG(1) << socketConnection->getConnectionName() << ": OnConnected";
457 X509* server_cert = SSL_get_peer_certificate(socketConnection->getSSL());
458 if (server_cert != nullptr) {
459 long verifyErr = SSL_get_verify_result(socketConnection->getSSL());
460
461 VLOG(1) << "\tPeer certificate verifyErr = " + std::to_string(verifyErr) + ": " +
462 std::string(X509_verify_cert_error_string(verifyErr));
463
464 char* str = X509_NAME_oneline(X509_get_subject_name(server_cert), nullptr, 0);
465 VLOG(1) << "\t Subject: " + std::string(str);
466 OPENSSL_free(str);
467
468 str = X509_NAME_oneline(X509_get_issuer_name(server_cert), nullptr, 0);
469 VLOG(1) << "\t Issuer: " + std::string(str);
470 OPENSSL_free(str);
471
472 // We could do all sorts of certificate verification stuff here before deallocating the certificate.
473
474 GENERAL_NAMES* subjectAltNames =
475 static_cast<GENERAL_NAMES*>(X509_get_ext_d2i(server_cert, NID_subject_alt_name, nullptr, nullptr));
476#ifdef __GNUC__
477#pragma GCC diagnostic push
478#ifdef __has_warning
479#if __has_warning("-Wused-but-marked-unused")
480#pragma GCC diagnostic ignored "-Wused-but-marked-unused"
481#endif
482#endif
483#endif
484 int32_t altNameCount = sk_GENERAL_NAME_num(subjectAltNames);
485#ifdef __GNUC_
486#pragma GCC diagnostic pop
487#endif
488 VLOG(1) << "\t Subject alternative name count: " << altNameCount;
489 for (int32_t i = 0; i < altNameCount; ++i) {
490#ifdef __GNUC__
491#pragma GCC diagnostic push
492#ifdef __has_warning
493#if __has_warning("-Wused-but-marked-unused")
494#pragma GCC diagnostic ignored "-Wused-but-marked-unused"
495#endif
496#endif
497#endif
498 GENERAL_NAME* generalName = sk_GENERAL_NAME_value(subjectAltNames, i);
499#ifdef __GNUC_
500#pragma GCC diagnostic pop
501#endif
502 if (generalName->type == GEN_URI) {
503 std::string subjectAltName =
504 std::string(reinterpret_cast<const char*>(ASN1_STRING_get0_data(generalName->d.uniformResourceIdentifier)),
505 static_cast<std::size_t>(ASN1_STRING_length(generalName->d.uniformResourceIdentifier)));
506 VLOG(1) << "\t SAN (URI): '" + subjectAltName;
507 } else if (generalName->type == GEN_DNS) {
508 std::string subjectAltName =
509 std::string(reinterpret_cast<const char*>(ASN1_STRING_get0_data(generalName->d.dNSName)),
510 static_cast<std::size_t>(ASN1_STRING_length(generalName->d.dNSName)));
511 VLOG(1) << "\t SAN (DNS): '" + subjectAltName;
512 } else {
513 VLOG(1) << "\t SAN (Type): '" + std::to_string(generalName->type);
514 }
515 }
516#ifdef __GNUC__
517#pragma GCC diagnostic push
518#ifdef __has_warning
519#if __has_warning("-Wused-but-marked-unused")
520#pragma GCC diagnostic ignored "-Wused-but-marked-unused"
521#endif
522#endif
523#endif
524 sk_GENERAL_NAME_pop_free(subjectAltNames, GENERAL_NAME_free);
525#ifdef __GNUC_
526#pragma GCC diagnostic pop
527#endif
528 X509_free(server_cert);
529 } else {
530 VLOG(1) << "\tPeer certificate: no certificate";
531 }
532 });
533
534 client.setOnDisconnect([](SocketConnection* socketConnection) { // onDisconnect
535 VLOG(1) << socketConnection->getConnectionName() << ": OnDisconnect";
536
537 VLOG(1) << "\tLocal: " << socketConnection->getLocalAddress().toString();
538 VLOG(1) << "\tPeer: " << socketConnection->getRemoteAddress().toString();
539 });
540
541 return client;
542 }
543
544} // namespace apps::http::tls
545
546#endif // (STREAM_TYPE == TLS) // tls
547
548#endif // APPS_HTTP_MODEL_CLIENTS_H
int main(int argc, char *argv[])
#define QUOTE_INCLUDE(a)
Definition clients.h:25
#define STR_INCLUDE(a)
Definition servers.h:26
static void logResponse(const std::shared_ptr< web::http::client::Request > &req, const std::shared_ptr< web::http::client::Response > &res)
Definition clients.h:45
#define LONG
static Client getClient()
Definition clients.h:68
static Client getClient()
Definition clients.h:338