SNode.C
Loading...
Searching...
No Matches
Request.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 "web/http/client/Request.h"
21
22#include "core/file/FileReader.h"
23#include "core/socket/stream/SocketConnection.h"
24#include "web/http/MimeTypes.h"
25#include "web/http/client/SocketContext.h"
26#include "web/http/client/SocketContextUpgradeFactorySelector.h"
27
28//
29
30#include "commands/EndCommand.h"
31#include "commands/SendFileCommand.h"
32#include "commands/SendFragmentCommand.h"
33#include "commands/SendHeaderCommand.h"
34#include "commands/UpgradeCommand.h"
35
36#ifndef DOXYGEN_SHOULD_SKIP_THIS
37
38#include "log/Logger.h"
39#include "web/http/http_utils.h"
40
41#include <cerrno>
42#include <filesystem>
43#include <sstream>
44#include <system_error>
45#include <utility>
46
47#endif /* DOXYGEN_SHOULD_SKIP_THIS */
48
49#define to_hex_str(int_val) (static_cast<std::ostringstream const&>(std::ostringstream() << std::uppercase << std::hex << int_val)).str()
50
51namespace web::http::client {
52
53 Request::Request(web::http::client::SocketContext* socketContext, const std::string& host)
54 : hostFieldValue(host)
55 , socketContext(socketContext) {
56 this->host(hostFieldValue);
57 }
58
80
82 for (const RequestCommand* requestCommand : requestCommands) {
83 delete requestCommand;
84 }
85
86 if (!masterRequest.expired() && Sink::isStreaming()) {
87 socketContext->streamEof();
88 }
89 }
90
91 void Request::setMasterRequest(const std::shared_ptr<Request>& masterRequest) {
92 this->masterRequest = masterRequest;
93 }
94
95 void Request::init() {
96 method = "GET";
97 url = "/";
98 httpMajor = 1;
99 httpMinor = 1;
100 queries.clear();
101 headers.clear();
102 cookies.clear();
103 trailer.clear();
104 for (const RequestCommand* requestCommand : requestCommands) {
105 delete requestCommand;
106 }
107 requestCommands.clear();
108 transferEncoding = TransferEncoding::HTTP10;
109 contentLength = 0;
110 contentLengthSent = 0;
111 connectionState = ConnectionState::Default;
112 onResponseReceived = nullptr;
113 onResponseParseError = nullptr;
114
115 this->host(hostFieldValue);
116 set("X-Powered-By", "snode.c");
117 }
118
119 Request& Request::host(const std::string& hostFieldValue) {
120 set("Host", hostFieldValue);
121
122 return *this;
123 }
124
125 Request& Request::append(const std::string& field, const std::string& value) {
126 const std::map<std::string, std::string>::iterator it = headers.find(field);
127
128 if (it != headers.end()) {
129 set(field, it->second.append(", ").append(value));
130 } else {
131 set(field, value);
132 }
133
134 return *this;
135 }
136
137 Request& Request::set(const std::string& field, const std::string& value, bool overwrite) {
138 if (!value.empty()) {
139 if (overwrite) {
140 headers.insert_or_assign(field, value);
141 } else {
142 headers.insert({field, value});
143 }
144
145 if (web::http::ciEquals(field, "Connection")) {
146 if (web::http::ciContains(headers[field], "close")) {
147 connectionState = ConnectionState::Close;
148 } else if (web::http::ciContains(headers[field], "keep-alive")) {
149 connectionState = ConnectionState::Keep;
150 }
151 } else if (web::http::ciEquals(field, "Content-Length")) {
152 contentLength = std::stoul(value);
153 transferEncoding = TransferEncoding::Identity;
154 headers.erase("Transfer-Encoding");
155 } else if (web::http::ciEquals(field, "Transfer-Encoding")) {
156 if (web::http::ciContains(headers[field], "chunked")) {
157 transferEncoding = TransferEncoding::Chunked;
158 headers.erase("Content-Length");
159 }
160 if (web::http::ciContains(headers[field], "compressed")) {
161 }
162 if (web::http::ciContains(headers[field], "deflate")) {
163 }
164 if (web::http::ciContains(headers[field], "gzip")) {
165 }
166 } else if (web::http::ciEquals(field, "Content-Encoding")) {
167 if (web::http::ciContains(headers[field], "compressed")) {
168 }
169 if (web::http::ciContains(headers[field], "deflate")) {
170 }
171 if (web::http::ciContains(headers[field], "gzip")) {
172 }
173 if (web::http::ciContains(headers[field], "br")) {
174 }
175 }
176 } else {
177 headers.erase(field);
178 }
179
180 return *this;
181 }
182
183 Request& Request::set(const std::map<std::string, std::string>& headers, bool overwrite) {
184 for (const auto& [field, value] : headers) {
185 set(field, value, overwrite);
186 }
187
188 return *this;
189 }
190
191 Request& Request::type(const std::string& type) {
192 headers.insert({"Content-Type", type});
193
194 return *this;
195 }
196
197 Request& Request::cookie(const std::string& name, const std::string& value) {
198 cookies.insert({name, value});
199
200 return *this;
201 }
202
203 Request& Request::cookie(const std::map<std::string, std::string>& cookies) {
204 for (const auto& [name, value] : cookies) {
205 cookie(name, value);
206 }
207
208 return *this;
209 }
210
211 Request& Request::query(const std::string& key, const std::string& value) {
212 queries.insert({key, value});
213
214 return *this;
215 }
216
217 Request& Request::setTrailer(const std::string& field, const std::string& value, bool overwrite) {
218 if (!value.empty()) {
219 if (overwrite) {
220 trailer.insert_or_assign(field, value);
221 } else {
222 trailer.insert({field, value});
223 }
224 if (!headers.contains("Trailer")) {
225 set("Trailer", field);
226 } else {
227 headers["Trailer"].append("," + field);
228 }
229 } else {
230 trailer.erase(field);
231 }
232
233 return *this;
234 }
235
236 void Request::responseParseError(const std::shared_ptr<Request>& request, const std::string& message) {
237 LOG(WARNING) << request->getSocketContext()->getSocketConnection()->getConnectionName()
238 << " HTTP: Response parse error: " << request->method << " " << request->url << " "
239 << "HTTP/" << request->httpMajor << "." << request->httpMinor << ": " << message;
240 }
241
242 bool Request::send(const char* chunk,
243 std::size_t chunkLen,
244 const std::function<void(const std::shared_ptr<Request>&, const std::shared_ptr<Response>&)>& onResponseReceived,
245 const std::function<void(const std::shared_ptr<Request>&, const std::string&)>& onResponseParseError) {
246 bool queued = true;
247
248 if (!masterRequest.expired()) {
249 this->onResponseReceived = onResponseReceived;
250 this->onResponseParseError = onResponseParseError;
251
252 if (chunkLen > 0) {
253 set("Content-Type", "application/octet-stream", false);
254 }
255
257 sendFragment(chunk, chunkLen);
258
259 requestCommands.push_back(new commands::EndCommand());
260
262 } else {
263 queued = false;
264 }
265
266 return queued;
267 }
268
269 bool Request::send(const std::string& chunk,
270 const std::function<void(const std::shared_ptr<Request>&, const std::shared_ptr<Response>&)>& onResponseReceived,
271 const std::function<void(const std::shared_ptr<Request>&, const std::string&)>& onResponseParseError) {
272 if (!chunk.empty()) {
273 set("Content-Type", "text/html; charset=utf-8", false);
274 }
275
276 return send(chunk.data(), chunk.size(), onResponseReceived, onResponseParseError);
277 }
278
279 bool Request::upgrade(const std::string& url,
280 const std::string& protocols,
281 const std::function<void(const std::shared_ptr<Request>&, const std::shared_ptr<Response>&)>& onResponseReceived,
282 const std::function<void(const std::shared_ptr<Request>&, const std::string&)>& onResponseParseError) {
283 bool success = true;
284
285 if (!masterRequest.expired()) {
286 this->onResponseReceived = onResponseReceived;
287 this->onResponseParseError = onResponseParseError;
288
289 requestCommands.push_back(new commands::UpgradeCommand(url, protocols));
290
292 } else {
293 success = false;
294 }
295
296 return success;
297 }
298
299 void Request::upgrade(const std::shared_ptr<Response>& response, const std::function<void(const std::string&)>& status) {
300 std::string name;
301
302 if (!masterRequest.expired()) {
303 if (response != nullptr) {
304 if (web::http::ciContains(response->get("connection"), "Upgrade")) {
305 web::http::client::SocketContextUpgradeFactory* socketContextUpgradeFactory =
306 web::http::client::SocketContextUpgradeFactorySelector::instance()->select(*this, *response);
307
308 if (socketContextUpgradeFactory != nullptr) {
309 name = socketContextUpgradeFactory->name();
310
311 LOG(DEBUG) << socketContext->getSocketConnection()->getConnectionName()
312 << " HTTP upgrade: SocketContextUpgradeFactory created successful: " << name;
313
314 core::socket::stream::SocketContext* socketContextUpgrade =
315 socketContextUpgradeFactory->create(socketContext->getSocketConnection());
316
317 if (socketContextUpgrade != nullptr) {
318 LOG(DEBUG) << socketContext->getSocketConnection()->getConnectionName()
319 << " HTTP upgrade: SocketContextUpgrade created successful: " << name;
320
321 socketContext->switchSocketContext(socketContextUpgrade);
322 } else {
323 LOG(DEBUG) << socketContext->getSocketConnection()->getConnectionName()
324 << " HTTP upgrade: Create SocketContextUpgrade failed: " << name;
325
326 socketContext->close();
327 }
328 } else {
329 LOG(DEBUG) << socketContext->getSocketConnection()->getConnectionName()
330 << " HTTP upgrade: SocketContextUpgradeFactory not supported by server: " << header("upgrade");
331
332 socketContext->close();
333 }
334 } else {
335 LOG(DEBUG) << socketContext->getSocketConnection()->getConnectionName()
336 << " HTTP upgrade: Not any protocol supported by server: " << header("upgrade");
337
338 socketContext->close();
339 }
340 } else {
341 LOG(ERROR) << socketContext->getSocketConnection()->getConnectionName() << " HTTP upgrade: Response has gone away";
342
343 socketContext->close();
344 }
345 } else {
346 LOG(ERROR) << socketContext->getSocketConnection()->getConnectionName() << " HTTP upgrade: SocketContext has gone away";
347 }
348
349 status(name);
350 }
351
352 bool Request::sendFile(const std::string& file,
353 const std::function<void(int)>& onStatus,
354 const std::function<void(const std::shared_ptr<Request>&, const std::shared_ptr<Response>&)>& onResponseReceived,
355 const std::function<void(const std::shared_ptr<Request>&, const std::string&)>& onResponseParseError) {
356 bool queued = true;
357
358 if (!masterRequest.expired()) {
359 this->onResponseReceived = onResponseReceived;
360 this->onResponseParseError = onResponseParseError;
361
362 requestCommands.push_back(new commands::SendFileCommand(file, onStatus));
363
365 } else {
366 queued = false;
367 }
368
369 return queued;
370 }
371
373 if (!masterRequest.expired()) {
374 requestCommands.push_back(new commands::SendHeaderCommand());
375 }
376
377 return *this;
378 }
379
380 Request& Request::sendFragment(const char* chunk, std::size_t chunkLen) {
381 if (!masterRequest.expired()) {
382 contentLength += chunkLen;
383
384 requestCommands.push_back(new commands::SendFragmentCommand(chunk, chunkLen));
385 }
386
387 return *this;
388 }
389
390 Request& Request::sendFragment(const std::string& data) {
391 return sendFragment(data.data(), data.size());
392 }
393
394 bool Request::end(const std::function<void(const std::shared_ptr<Request>&, const std::shared_ptr<Response>&)>& onResponseReceived,
395 const std::function<void(const std::shared_ptr<Request>&, const std::string&)>& onResponseParseError) {
396 bool queued = true;
397
398 if (!masterRequest.expired()) {
399 this->onResponseReceived = onResponseReceived;
400 this->onResponseParseError = onResponseParseError;
401
403
404 requestCommands.push_back(new commands::EndCommand());
405
407 } else {
408 queued = false;
409 }
410
411 return queued;
412 }
413
415 bool error = false;
416 bool atomar = true;
417
418 for (RequestCommand* requestCommand : requestCommands) {
419 if (!error) {
420 const bool atomarCommand = requestCommand->execute(this);
421 if (atomar) {
422 atomar = atomarCommand;
423 }
424
425 error = requestCommand->getError();
426 }
427
428 delete requestCommand;
429 }
430 requestCommands.clear();
431
432 if (atomar && (!error || contentLengthSent != 0)) {
434 }
435
436 return !error || contentLengthSent != 0;
437 }
438
439 bool Request::executeSendFile(const std::string& file, const std::function<void(int)>& onStatus) {
440 bool atomar = true;
441
442 std::string absolutFileName = file;
443
444 if (std::filesystem::exists(absolutFileName)) {
445 std::error_code ec;
446 absolutFileName = std::filesystem::canonical(absolutFileName);
447
448 if (std::filesystem::is_regular_file(absolutFileName, ec) && !ec) {
449 core::file::FileReader::open(absolutFileName)->pipe(this, [this, &atomar, &absolutFileName, &onStatus](int errnum) {
450 errno = errnum;
451 onStatus(errnum);
452
453 if (errnum == 0) {
454 if (httpMajor == 1) {
455 atomar = false;
456
457 set("Content-Type", web::http::MimeTypes::contentType(absolutFileName), false);
458 set("Last-Modified", httputils::file_mod_http_date(absolutFileName), false);
459 if (httpMinor == 1 && contentLength == 0) {
460 set("Transfer-Encoding", "chunked");
461 } else {
462 set("Content-Length", std::to_string(std::filesystem::file_size(absolutFileName) + contentLength));
463 }
464
465 executeSendHeader();
466 }
467 }
468 });
469 } else {
470 errno = EINVAL;
471 onStatus(errno);
472 }
473 } else {
474 errno = ENOENT;
475 onStatus(errno);
476 }
477
478 return atomar;
479 }
480
481 bool Request::executeUpgrade(const std::string& url, const std::string& protocols) {
482 this->url = url;
483
484 set("Connection", "Upgrade", true);
485 set("Upgrade", protocols, true);
486
487 web::http::client::SocketContextUpgradeFactory* socketContextUpgradeFactory =
488 web::http::client::SocketContextUpgradeFactorySelector::instance()->select(protocols, *this);
489
490 if (socketContextUpgradeFactory != nullptr) {
491 socketContextUpgradeFactory->checkRefCount();
492
494 } else {
495 socketContext->close();
496 }
497
498 return true;
499 }
500
501 bool Request::executeEnd() { // NOLINT
502 return true;
503 }
504
506 const std::string httpVersion = "HTTP/" + std::to_string(httpMajor) + "." + std::to_string(httpMinor);
507
508 std::string queryString;
509 if (!queries.empty()) {
510 queryString += "?";
511 for (auto& [key, value] : queries) {
512 queryString += httputils::url_encode(key) + "=" + httputils::url_encode(value) + "&";
513 }
514 queryString.pop_back();
515 }
516
517 socketContext->sendToPeer(method + " " + url + queryString + " " + httpVersion + "\r\n");
518 socketContext->sendToPeer("Date: " + httputils::to_http_date() + "\r\n");
519
520 if (!headers.contains("Transfer-Encoding") && contentLength > 0) {
521 set("Content-Length", std::to_string(contentLength));
522 }
523
524 for (const auto& [field, value] : headers) {
525 socketContext->sendToPeer(std::string(field).append(":").append(value).append("\r\n"));
526 }
527
528 for (const auto& [name, value] : cookies) {
529 socketContext->sendToPeer(std::string("Cookie:").append(name).append("=").append(value).append("\r\n"));
530 }
531
532 socketContext->sendToPeer("\r\n");
533
534 return true;
535 }
536
537 bool Request::executeSendFragment(const char* chunk, std::size_t chunkLen) {
538 if (transferEncoding == TransferEncoding::Chunked) {
539 socketContext->sendToPeer(to_hex_str(chunkLen).append("\r\n"));
540 }
541
542 socketContext->sendToPeer(chunk, chunkLen);
543 contentLengthSent += chunkLen;
544
545 if (transferEncoding == TransferEncoding::Chunked) {
546 socketContext->sendToPeer("\r\n");
547 contentLength += chunkLen;
548 }
549
550 return true;
551 }
552
554 socketContext->requestPrepared(std::move(*this));
555 init();
556 }
557
558 void Request::deliverResponse(const std::shared_ptr<Request>& request, const std::shared_ptr<Response>& response) {
559 onResponseReceived(request, response);
560 }
561
562 void Request::deliverResponseParseError(const std::shared_ptr<Request>& request, const std::string& message) {
563 onResponseParseError(request, message);
564 }
565
567 if (!masterRequest.expired()) {
568 if (transferEncoding == TransferEncoding::Chunked) {
569 executeSendFragment("", 0); // For transfer encoding chunked. Terminate the chunk sequence.
570
571 if (!trailer.empty()) {
572 for (auto& [field, value] : trailer) {
573 socketContext->sendToPeer(std::string(field).append(":").append(value).append("\r\n"));
574 }
575 socketContext->sendToPeer("\r\n");
576 }
577 }
578
579 socketContext->requestDelivered(std::move(*this), contentLengthSent == contentLength);
580 }
581 }
582
583 void Request::onSourceConnect(core::pipe::Source* source) {
584 if (!masterRequest.expired()) {
585 if (socketContext->streamToPeer(source)) {
586 source->start();
587 }
588 } else {
589 source->stop();
590 }
591 }
592
593 void Request::onSourceData(const char* chunk, std::size_t chunkLen) {
594 executeSendFragment(chunk, chunkLen);
595 }
596
598 if (!masterRequest.expired()) {
599 socketContext->streamEof();
600
602 }
603 }
604
605 void Request::onSourceError(int errnum) {
606 errno = errnum;
607
608 if (!masterRequest.expired()) {
609 socketContext->streamEof();
610 socketContext->close();
611
613 }
614 }
615
617 return headers.contains(field) ? headers[field] : "";
618 }
619
621 return queries;
622 }
623
625 return headers;
626 }
627
629 return cookies;
630 }
631
633 return socketContext;
634 }
635
636} // namespace web::http::client
virtual void stop()=0
virtual void start()=0
Request & set(const std::string &field, const std::string &value, bool overwrite=true)
Definition Request.cpp:137
void onSourceEof() override
Definition Request.cpp:597
Request & cookie(const std::string &name, const std::string &value)
Definition Request.cpp:197
Request(Request &&) noexcept
Definition Request.cpp:59
void onSourceError(int errnum) override
Definition Request.cpp:605
std::string header(const std::string &field)
Definition Request.cpp:616
Request & sendFragment(const std::string &data)
Definition Request.cpp:390
bool sendFile(const std::string &file, const std::function< void(int errnum)> &onStatus, 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=responseParseError)
Definition Request.cpp:352
Request & query(const std::string &key, const std::string &value)
Definition Request.cpp:211
bool upgrade(const std::string &url, const std::string &protocols, 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=responseParseError)
Definition Request.cpp:279
void onSourceData(const char *chunk, std::size_t chunkLen) override
Definition Request.cpp:593
bool executeSendFragment(const char *chunk, std::size_t chunkLen)
Definition Request.cpp:537
const CiStringMap< std::string > & getQueries() const
Definition Request.cpp:620
const CiStringMap< std::string > & getCookies() const
Definition Request.cpp:628
Request & setTrailer(const std::string &field, const std::string &value, bool overwrite=true)
Definition Request.cpp:217
Request & cookie(const std::map< std::string, std::string > &cookies)
Definition Request.cpp:203
web::http::client::SocketContext * getSocketContext() const
Definition Request.cpp:632
void setMasterRequest(const std::shared_ptr< Request > &masterRequest)
Definition Request.cpp:91
Request & set(const std::map< std::string, std::string > &headers, bool overwrite=true)
Definition Request.cpp:183
Request & append(const std::string &field, const std::string &value)
Definition Request.cpp:125
Request & sendFragment(const char *chunk, std::size_t chunkLen)
Definition Request.cpp:380
bool send(const char *chunk, std::size_t chunkLen, 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=responseParseError)
Definition Request.cpp:242
const CiStringMap< std::string > & getHeaders() const
Definition Request.cpp:624
Request & host(const std::string &hostFieldValue)
Definition Request.cpp:119
bool send(const std::string &chunk, 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=responseParseError)
Definition Request.cpp:269
Request & type(const std::string &type)
Definition Request.cpp:191
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=responseParseError)
Definition Request.cpp:394
void deliverResponseParseError(const std::shared_ptr< Request > &request, const std::string &message)
Definition Request.cpp:562
bool executeSendFile(const std::string &file, const std::function< void(int)> &onStatus)
Definition Request.cpp:439
bool executeUpgrade(const std::string &url, const std::string &protocols)
Definition Request.cpp:481
#define to_hex_str(int_val)
Definition Request.cpp:49