SNode.C
Loading...
Searching...
No Matches
httplowlevelclient.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
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#include "core/SNodeC.h"
21#include "core/socket/stream/SocketContext.h"
22#include "core/socket/stream/SocketContextFactory.h"
23#include "net/in/stream/legacy/SocketClient.h"
24#include "net/in/stream/tls/SocketClient.h"
25#include "web/http/client/ResponseParser.h"
26
27#ifndef DOXYGEN_SHOULD_SKIP_THIS
28
29#include "log/Logger.h"
30
31#include <cstddef>
32#include <openssl/ssl.h>
33#include <openssl/x509v3.h>
34#include <string>
35
36// IWYU pragma: no_include <openssl/ssl3.h>
37// IWYU pragma: no_include <openssl/x509.h>
38// IWYU pragma: no_include <openssl/types.h>
39// IWYU pragma: no_include <openssl/asn1.h>
40// IWYU pragma: no_include <openssl/obj_mac.h>
41// IWYU pragma: no_include <openssl/crypto.h>
42
43#endif /* DOXYGEN_SHOULD_SKIP_THIS */
44
45namespace apps::http {
46
47 static web::http::client::ResponseParser* getResponseParser(core::socket::stream::SocketContext* socketContext) {
48 web::http::client::ResponseParser* responseParser = new web::http::client::ResponseParser(
49 socketContext,
50 []() {
51 VLOG(1) << "++ OnStarted";
52 },
53 []([[maybe_unused]] web::http::client::Response&& res) {
54 VLOG(1) << "++ OnParsed";
55 },
56 [](int status, const std::string& reason) {
57 VLOG(1) << "++ OnError: " + std::to_string(status) + " - " + reason;
58 });
59
60 return responseParser;
61 }
62
64 public:
65 explicit SimpleSocketProtocol(core::socket::stream::SocketConnection* socketConnection)
67 responseParser = getResponseParser(this);
68 }
69
71
73 VLOG(1) << "SimpleSocketProtocol connected";
74 }
76 VLOG(1) << "SimpleSocketProtocol disconnected";
77 }
78
79 bool onSignal([[maybe_unused]] int signum) override {
80 return true;
81 }
82
84 return responseParser->parse();
85 }
86
87 void onWriteError(int errnum) override {
88 core::socket::stream::SocketContext::onWriteError(errnum);
89 shutdownRead();
90 }
91
92 void onReadError(int errnum) override {
93 core::socket::stream::SocketContext::onReadError(errnum);
94 shutdownWrite();
95 }
96
97 private:
99 };
100
104
106 public:
108
109 private:
110 core::socket::stream::SocketContext* create(core::socket::stream::SocketConnection* socketConnection) override {
111 return new SimpleSocketProtocol(socketConnection);
112 }
113 };
114
117
118} // namespace apps::http
119
120namespace tls {
121
125
127 SocketClient tlsClient(
128 "tls",
129 [](SocketConnection* socketConnection) { // onConnect
130 VLOG(1) << "OnConnect";
131
132 VLOG(1) << "\tServer: " << socketConnection->getRemoteAddress().toString();
133 VLOG(1) << "\tClient: " << socketConnection->getLocalAddress().toString();
134
135 /* Enable automatic hostname checks */
136 // X509_VERIFY_PARAM* param = SSL_get0_param(socketConnection->getSSL());
137
138 // X509_VERIFY_PARAM_set_hostflags(param, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
139 // if (!X509_VERIFY_PARAM_set1_host(param, "localhost", sizeof("localhost") - 1)) {
140 // // handle error
141 // socketConnection->close();
142 // }
143 },
144 [](SocketConnection* socketConnection) { // onConnected
145 VLOG(1) << "OnConnected";
146
147 X509* server_cert = SSL_get_peer_certificate(socketConnection->getSSL());
148 if (server_cert != nullptr) {
149 const long verifyErr = SSL_get_verify_result(socketConnection->getSSL());
150
151 VLOG(1) << " Server certificate: " + std::string(X509_verify_cert_error_string(verifyErr));
152
153 char* str = X509_NAME_oneline(X509_get_subject_name(server_cert), nullptr, 0);
154 VLOG(1) << " Subject: " + std::string(str);
155 OPENSSL_free(str);
156
157 str = X509_NAME_oneline(X509_get_issuer_name(server_cert), nullptr, 0);
158 VLOG(1) << " Issuer: " + std::string(str);
159 OPENSSL_free(str);
160
161 // We could do all sorts of certificate verification stuff here before deallocating the certificate.
162
163 GENERAL_NAMES* subjectAltNames =
164 static_cast<GENERAL_NAMES*>(X509_get_ext_d2i(server_cert, NID_subject_alt_name, nullptr, nullptr));
165#ifdef __GNUC__
166#pragma GCC diagnostic push
167#ifdef __has_warning
168#if __has_warning("-Wused-but-marked-unused")
169#pragma GCC diagnostic ignored "-Wused-but-marked-unused"
170#endif
171#endif
172#endif
173 const int32_t altNameCount = sk_GENERAL_NAME_num(subjectAltNames);
174#ifdef __GNUC_
175#pragma GCC diagnostic pop
176#endif
177 VLOG(1) << "\t Subject alternative name count: " << altNameCount;
178 for (int32_t i = 0; i < altNameCount; ++i) {
179#ifdef __GNUC__
180#pragma GCC diagnostic push
181#ifdef __has_warning
182#if __has_warning("-Wused-but-marked-unused")
183#pragma GCC diagnostic ignored "-Wused-but-marked-unused"
184#endif
185#endif
186#endif
187 GENERAL_NAME* generalName = sk_GENERAL_NAME_value(subjectAltNames, i);
188#ifdef __GNUC_
189#pragma GCC diagnostic pop
190#endif
191 if (generalName->type == GEN_URI) {
192 const std::string subjectAltName =
193 std::string(reinterpret_cast<const char*>(ASN1_STRING_get0_data(generalName->d.uniformResourceIdentifier)),
194 static_cast<std::size_t>(ASN1_STRING_length(generalName->d.uniformResourceIdentifier)));
195 VLOG(1) << "\t SAN (URI): '" + subjectAltName;
196 } else if (generalName->type == GEN_DNS) {
197 const std::string subjectAltName =
198 std::string(reinterpret_cast<const char*>(ASN1_STRING_get0_data(generalName->d.dNSName)),
199 static_cast<std::size_t>(ASN1_STRING_length(generalName->d.dNSName)));
200 VLOG(1) << "\t SAN (DNS): '" + subjectAltName;
201 } else {
202 VLOG(1) << "\t SAN (Type): '" + std::to_string(generalName->type);
203 }
204 }
205#ifdef __GNUC__
206#pragma GCC diagnostic push
207#ifdef __has_warning
208#if __has_warning("-Wused-but-marked-unused")
209#pragma GCC diagnostic ignored "-Wused-but-marked-unused"
210#endif
211#endif
212#endif
213 sk_GENERAL_NAME_pop_free(subjectAltNames, GENERAL_NAME_free);
214#ifdef __GNUC_
215#pragma GCC diagnostic pop
216#endif
217 X509_free(server_cert);
218 } else {
219 VLOG(1) << " Server certificate: no certificate";
220 }
221
222 socketConnection->sendToPeer("GET /index.html HTTP/1.1\r\nConnection: close\r\n\r\n"); // Connection: close\r\n\r\n");
223 },
224 [](SocketConnection* socketConnection) { // onDisconnect
225 VLOG(1) << "OnDisconnect";
226
227 VLOG(1) << "\tServer: " + socketConnection->getRemoteAddress().toString();
228 VLOG(1) << "\tClient: " + socketConnection->getLocalAddress().toString();
229
230 });
231
232 const SocketAddress remoteAddress("localhost", 8088);
233
234 tlsClient.connect(remoteAddress,
235 [instanceName = tlsClient.getConfig().getInstanceName()](
236 const SocketAddress& socketAddress,
237 const core::socket::State& state) { // example.com:81 simulate connnect timeout
238 switch (state) {
239 case core::socket::State::OK:
240 VLOG(1) << instanceName << ": connected to '" << socketAddress.toString() << "'";
241 break;
242 case core::socket::State::DISABLED:
243 VLOG(1) << instanceName << ": disabled";
244 break;
245 case core::socket::State::ERROR:
246 LOG(ERROR) << instanceName << ": " << socketAddress.toString() << ": " << state.what();
247 break;
248 case core::socket::State::FATAL:
249 LOG(FATAL) << instanceName << ": " << socketAddress.toString() << ": " << state.what();
250 break;
251 }
252 });
253 return tlsClient;
254 }
255
256} // namespace tls
257
258namespace legacy {
259
263
265 SocketClient legacyClient(
266 "legacy",
267 [](SocketConnection* socketConnection) { // OnConnect
268 VLOG(1) << "OnConnect";
269
270 VLOG(1) << "\tServer: " << socketConnection->getRemoteAddress().toString();
271 VLOG(1) << "\tClient: " << socketConnection->getLocalAddress().toString();
272 },
273 [](SocketConnection* socketConnection) { // onConnected
274 VLOG(1) << "OnConnected";
275
276 socketConnection->sendToPeer("GET /index.html HTTP/1.1\r\nConnection: close\r\n\r\n"); // Connection: close\r\n\r\n");
277 },
278 [](SocketConnection* socketConnection) { // onDisconnect
279 VLOG(1) << "OnDisconnect";
280
281 VLOG(1) << "\tServer: " << socketConnection->getRemoteAddress().toString();
282 VLOG(1) << "\tClient: " << socketConnection->getLocalAddress().toString();
283 });
284
285 SocketAddress remoteAddress("localhost", 8080);
286
287 remoteAddress.init();
288
289 VLOG(1) << "###############': " << remoteAddress.getCanonName();
290 VLOG(1) << "###############': " << remoteAddress.toString();
291
292 legacyClient.connect(remoteAddress,
293 [instanceName = legacyClient.getConfig().getInstanceName()](
294 const tls::SocketAddress& socketAddress,
295 const core::socket::State& state) { // example.com:81 simulate connnect timeout
296 switch (state) {
297 case core::socket::State::OK:
298 VLOG(1) << instanceName << ": connected to '" << socketAddress.toString() << "'";
299 break;
300 case core::socket::State::DISABLED:
301 VLOG(1) << instanceName << ": disabled";
302 break;
303 case core::socket::State::ERROR:
304 LOG(ERROR) << instanceName << ": " << socketAddress.toString() << ": " << state.what();
305 break;
306 case core::socket::State::FATAL:
307 LOG(FATAL) << instanceName << ": " << socketAddress.toString() << ": " << state.what();
308 break;
309 }
310 });
311
312 return legacyClient;
313 }
314
315} // namespace legacy
316
317int main(int argc, char* argv[]) {
318 core::SNodeC::init(argc, argv);
319
320 {
321 const legacy::SocketAddress legacyRemoteAddress("localhost", 8080);
322
323 const legacy::SocketClient legacyClient = legacy::getLegacyClient();
324
325 legacyClient.connect(legacyRemoteAddress,
326 [instanceName = legacyClient.getConfig().getInstanceName()](
327 const tls::SocketAddress& socketAddress,
328 const core::socket::State& state) { // example.com:81 simulate connnect timeout
329 switch (state) {
330 case core::socket::State::OK:
331 VLOG(1) << instanceName << ": connected to '" << socketAddress.toString() << "'";
332 break;
333 case core::socket::State::DISABLED:
334 VLOG(1) << instanceName << ": disabled";
335 break;
336 case core::socket::State::ERROR:
337 LOG(ERROR) << instanceName << ": " << socketAddress.toString() << ": " << state.what();
338 break;
339 case core::socket::State::FATAL:
340 LOG(FATAL) << instanceName << ": " << socketAddress.toString() << ": " << state.what();
341 break;
342 }
343 });
344
345 const tls::SocketAddress tlsRemoteAddress = tls::SocketAddress("localhost", 8088);
346
347 const tls::SocketClient tlsClient = tls::getClient();
348
349 tlsClient.connect(tlsRemoteAddress,
350 [instanceName = tlsClient.getConfig().getInstanceName()](
351 const tls::SocketAddress& socketAddress,
352 const core::socket::State& state) { // example.com:81 simulate connnect timeout
353 switch (state) {
354 case core::socket::State::OK:
355 VLOG(1) << instanceName << ": connected to '" << socketAddress.toString() << "'";
356 break;
357 case core::socket::State::DISABLED:
358 VLOG(1) << instanceName << ": disabled";
359 break;
360 case core::socket::State::ERROR:
361 LOG(ERROR) << instanceName << ": " << socketAddress.toString() << ": " << state.what();
362 break;
363 case core::socket::State::FATAL:
364 LOG(FATAL) << instanceName << ": " << socketAddress.toString() << ": " << state.what();
365 break;
366 }
367 });
368 }
369
370 return core::SNodeC::start();
371}
core::socket::stream::SocketContext * create(core::socket::stream::SocketConnection *socketConnection) override
web::http::client::ResponseParser * responseParser
SimpleSocketProtocol(core::socket::stream::SocketConnection *socketConnection)
bool onSignal(int signum) override
void onReadError(int errnum) override
void onWriteError(int errnum) override
std::size_t onReceivedFromPeer() override
int main(int argc, char *argv[])
static web::http::client::ResponseParser * getResponseParser(core::socket::stream::SocketContext *socketContext)
SocketClient getLegacyClient()
SocketClient getClient()