SNode.C
Loading...
Searching...
No Matches
Response.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/server/Response.h"
21
22#include "SocketContext.h"
23#include "core/file/FileReader.h"
24#include "core/socket/stream/SocketConnection.h"
25#include "web/http/MimeTypes.h"
26#include "web/http/StatusCodes.h"
27#include "web/http/server/SocketContextUpgradeFactorySelector.h"
28
29#ifndef DOXYGEN_SHOULD_SKIP_THIS
30
31#include "log/Logger.h"
32#include "utils/system/time.h"
33#include "web/http/CiStringMap.h"
34#include "web/http/http_utils.h"
35
36#include <cerrno>
37#include <filesystem>
38#include <iterator>
39#include <numeric>
40#include <sstream>
41#include <system_error>
42#include <utility>
43
44#endif /* DOXYGEN_SHOULD_SKIP_THIS */
45
46#define to_hex_str(int_val) (static_cast<std::ostringstream const&>(std::ostringstream() << std::uppercase << std::hex << int_val)).str()
47
48namespace web::http::server {
49
53
55 if (socketContext != nullptr && Sink::isStreaming()) {
56 socketContext->streamEof();
57 }
58
59 delete socketContextUpgrade; // delete of nullptr is valid since C++14!
60 }
61
63 stop();
64 }
65
66 void Response::init() {
67 statusCode = 200;
68 httpMajor = 1;
69 httpMinor = 1;
70 headers.clear();
71 cookies.clear();
72 trailer.clear();
73 contentLength = 0;
74 contentSent = 0;
75 socketContextUpgrade = nullptr;
76 connectionState = ConnectionState::Default;
77 transferEncoding = TransferEncoding::HTTP10;
78 }
79
80 Response& Response::status(int statusCode) {
81 this->statusCode = statusCode;
82
83 return *this;
84 }
85
86 Response& Response::append(const std::string& field, const std::string& value) {
87 const std::map<std::string, std::string>::iterator it = headers.find(field);
88
89 if (it != headers.end()) {
90 set(field, it->second.append(", ").append(value));
91 } else {
92 set(field, value);
93 }
94
95 return *this;
96 }
97
98 Response& Response::set(const std::map<std::string, std::string>& headers, bool overwrite) {
99 for (const auto& [field, value] : headers) {
100 set(field, value, overwrite);
101 }
102
103 return *this;
104 }
105
106 Response& Response::set(const std::string& field, const std::string& value, bool overwrite) {
107 if (!value.empty()) {
108 if (overwrite) {
109 headers.insert_or_assign(field, value);
110 } else {
111 headers.insert({field, value});
112 }
113
114 if (web::http::ciEquals(field, "Connection")) {
115 if (web::http::ciContains(headers[field], "keep-alive")) {
116 connectionState = ConnectionState::Keep;
117 } else if (web::http::ciContains(headers[field], "close")) {
118 connectionState = ConnectionState::Close;
119 }
120 } else if (web::http::ciEquals(field, "Content-Length")) {
121 contentLength = std::stoul(value);
122 transferEncoding = TransferEncoding::Identity;
123 headers.erase("Transfer-Encoding");
124 } else if (web::http::ciEquals(field, "Transfer-Encoding")) {
125 if (web::http::ciContains(headers[field], "chunked")) {
126 transferEncoding = TransferEncoding::Chunked;
127 headers.erase("Content-Length");
128 }
129 if (web::http::ciContains(headers[field], "compressed")) {
130 }
131 if (web::http::ciContains(headers[field], "deflate")) {
132 }
133 if (web::http::ciContains(headers[field], "gzip")) {
134 }
135 } else if (web::http::ciEquals(field, "Content-Encoding")) {
136 if (web::http::ciContains(headers[field], "compressed")) {
137 }
138 if (web::http::ciContains(headers[field], "deflate")) {
139 }
140 if (web::http::ciContains(headers[field], "gzip")) {
141 }
142 if (web::http::ciContains(headers[field], "br")) {
143 }
144 }
145 } else {
146 headers.erase(field);
147 }
148
149 return *this;
150 }
151
152 Response& Response::type(const std::string& type) {
153 return set("Content-Type", type);
154 }
155
156 Response& Response::cookie(const std::string& name, const std::string& value, const std::map<std::string, std::string>& options) {
157 cookies.insert({name, CookieOptions(value, options)});
158
159 return *this;
160 }
161
162 Response& Response::clearCookie(const std::string& name, const std::map<std::string, std::string>& options) {
163 std::map<std::string, std::string> opts = options;
164
165 opts.erase("Max-Age");
166 const time_t time = 0;
167 opts["Expires"] = httputils::to_http_date(utils::system::gmtime(&time));
168
169 return cookie(name, "", opts);
170 }
171
172 Response& Response::setTrailer(const std::string& field, const std::string& value, bool overwrite) {
173 if (!value.empty()) {
174 if (overwrite) {
175 trailer.insert_or_assign(field, value);
176 } else {
177 trailer.insert({field, value});
178 }
179 if (!headers.contains("Trailer")) {
180 set("Trailer", field);
181 } else {
182 headers["Trailer"].append("," + field);
183 }
184 } else {
185 trailer.erase(field);
186 }
187
188 return *this;
189 }
190
191 void Response::send(const char* chunk, std::size_t chunkLen) {
192 if (chunkLen > 0) {
193 set("Content-Type", "application/octet-stream", false);
194 }
195 set("Content-Length", std::to_string(chunkLen));
196
198 sendFragment(chunk, chunkLen);
200 }
201
202 void Response::send(const std::string& chunk) {
203 if (!chunk.empty()) {
204 set("Content-Type", "text/html; charset=utf-8", false);
205 }
206
207 send(chunk.data(), chunk.size());
208 }
209
210 void Response::sendStatus(int statusCode) {
211 status(statusCode);
212
213 send(StatusCode::reason(statusCode));
214 }
215
216 /* Just an UML-Sequence diagram test */
217 /** Sequence diagram of res.upgrade(req).
218@startuml
219!include web/http/server/pu/response_upgrade.pu
220@enduml
221 */
222 void Response::upgrade(const std::shared_ptr<Request>& request, const std::function<void(const std::string&)>& status) {
223 std::string name;
224
225 if (socketContext != nullptr) {
226 if (request != nullptr) {
227 if (web::http::ciContains(request->get("connection"), "Upgrade")) {
228 web::http::server::SocketContextUpgradeFactory* socketContextUpgradeFactory =
229 web::http::server::SocketContextUpgradeFactorySelector::instance()->select(*request, *this);
230
231 if (socketContextUpgradeFactory != nullptr) {
232 name = socketContextUpgradeFactory->name();
233 LOG(DEBUG) << socketContext->getSocketConnection()->getConnectionName()
234 << " HTTP upgrade: SocketContextUpgradeFactory created successful: " << name;
235
236 socketContextUpgrade = socketContextUpgradeFactory->create(socketContext->getSocketConnection());
237
238 if (socketContextUpgrade != nullptr) {
239 LOG(DEBUG) << socketContext->getSocketConnection()->getConnectionName()
240 << " HTTP upgrade: SocketContextUpgrade created successful: " << name;
241 } else {
242 LOG(DEBUG) << socketContext->getSocketConnection()->getConnectionName()
243 << " HTTP upgrade: Create SocketContextUpgrade failed: " << name;
244
245 set("Connection", "close").status(404);
246 }
247 } else {
248 LOG(DEBUG) << socketContext->getSocketConnection()->getConnectionName()
249 << " HTTP upgrade: SocketContextUpgradeFactory not supported: " << request->get("upgrade");
250
251 set("Connection", "close").status(404);
252 }
253 } else {
254 LOG(DEBUG) << socketContext->getSocketConnection()->getConnectionName() << " HTTP upgrade: No upgrade requested";
255
256 set("Connection", "close").status(400);
257 }
258 } else {
259 LOG(ERROR) << socketContext->getSocketConnection()->getConnectionName() << " HTTP upgrade: Request has gone away";
260
261 set("Connection", "close").status(500);
262 }
263 } else {
264 LOG(ERROR) << socketContext->getSocketConnection()->getConnectionName() << " HTTP upgrade: SocketContext has gone away";
265 }
266
267 status(name);
268 }
269
270 void Response::sendFile(const std::string& file, const std::function<void(int)>& callback) {
271 if (socketContext != nullptr) {
272 std::string absolutFileName = file;
273
274 if (std::filesystem::exists(absolutFileName)) {
275 std::error_code ec;
276 absolutFileName = std::filesystem::canonical(absolutFileName);
277
278 if (std::filesystem::is_regular_file(absolutFileName, ec) && !ec) {
279 core::file::FileReader::open(absolutFileName)->pipe(this, [this, &absolutFileName, &callback](int errnum) {
280 callback(errnum);
281
282 if (errnum == 0) {
283 set("Content-Type", web::http::MimeTypes::contentType(absolutFileName), false);
284 set("Last-Modified", httputils::file_mod_http_date(absolutFileName), false);
285 if (httpMajor == 1) {
286 if (httpMinor == 1) {
287 set("Transfer-Encoding", "chunked");
288 } else {
289 set("Content-Length", std::to_string(std::filesystem::file_size(absolutFileName)));
290 }
291 }
292 }
293 });
294 } else {
295 errno = EEXIST;
296 callback(errno);
297 }
298 } else {
299 errno = ENOENT;
300 callback(errno);
301 }
302 }
303 }
304
305 void Response::end() {
306 send("");
307 }
308
310 if (socketContext != nullptr) {
311 socketContext->responseStarted();
312
313 socketContext->sendToPeer("HTTP/" + std::to_string(httpMajor)
314 .append(".")
315 .append(std::to_string(httpMinor))
316 .append(" ")
317 .append(std::to_string(statusCode))
318 .append(" ")
319 .append(StatusCode::reason(statusCode))
320 .append("\r\n"));
321 socketContext->sendToPeer("Date: " + httputils::to_http_date() + "\r\n");
322
323 set("X-Powered-By", "snode.c");
324
325 for (const auto& [field, value] : headers) {
326 socketContext->sendToPeer(std::string(field).append(": ").append(value).append("\r\n"));
327 }
328
329 for (const auto& [cookie, cookieValue] : cookies) {
330 const std::string cookieString = std::accumulate(
331 cookieValue.getOptions().begin(),
332 cookieValue.getOptions().end(),
333 cookie + "=" + cookieValue.getValue(),
334 [](const std::string& str, const std::pair<const std::string&, const std::string&> option) -> std::string {
335 return str + "; " + option.first + (!option.second.empty() ? "=" + option.second : "");
336 });
337 socketContext->sendToPeer("Set-Cookie: " + cookieString + "\r\n");
338 }
339
340 socketContext->sendToPeer("\r\n");
341 }
342
343 return *this;
344 }
345
346 Response& Response::sendFragment(const char* chunk, std::size_t chunkLen) {
347 if (socketContext != nullptr) {
348 if (transferEncoding == TransferEncoding::Chunked) {
349 socketContext->sendToPeer(to_hex_str(chunkLen).append("\r\n"));
350 }
351
352 socketContext->sendToPeer(chunk, chunkLen);
353 contentSent += chunkLen;
354
355 if (transferEncoding == TransferEncoding::Chunked) {
356 socketContext->sendToPeer("\r\n");
357 contentLength += chunkLen;
358 }
359 }
360
361 return *this;
362 }
363
364 Response& Response::sendFragment(const std::string& chunk) {
365 return sendFragment(chunk.data(), chunk.size());
366 }
367
369 if (transferEncoding == TransferEncoding::Chunked) {
370 sendFragment(""); // For transfer encoding chunked. Terminate the chunk sequence.
371
372 if (!trailer.empty()) {
373 for (auto& [field, value] : trailer) {
374 socketContext->sendToPeer(std::string(field).append(":").append(value).append("\r\n"));
375 }
376
377 socketContext->sendToPeer("\r\n");
378 }
379 }
380
381 if (socketContext != nullptr) {
382 socketContext->responseCompleted(contentSent == contentLength || (httpMajor == 1 && httpMinor == 0));
383
384 if (socketContextUpgrade != nullptr) {
385 socketContext->switchSocketContext(socketContextUpgrade);
386 socketContextUpgrade = nullptr;
387 }
388 }
389 }
390
391 void Response::onSourceConnect(core::pipe::Source* source) {
392 if (socketContext != nullptr) {
393 if (socketContext->streamToPeer(source)) {
395
396 source->start();
397 } else {
398 source->stop();
399 }
400 } else {
401 source->stop();
402 }
403 }
404
405 void Response::onSourceData(const char* chunk, std::size_t chunkLen) {
406 sendFragment(chunk, chunkLen);
407 }
408
410 if (socketContext != nullptr) {
411 socketContext->streamEof();
412 }
413
415 }
416
417 void Response::onSourceError(int errnum) {
418 errno = errnum;
419
420 if (socketContext != nullptr) {
421 socketContext->streamEof();
422 socketContext->close();
423 }
424
426 }
427
429 return headers[field];
430 }
431
433 return socketContext;
434 }
435
436} // namespace web::http::server
virtual void stop()=0
virtual void start()=0
void onSourceData(const char *chunk, std::size_t chunkLen) override
Definition Response.cpp:405
Response(SocketContext *socketContext)
Definition Response.cpp:50
SocketContext * getSocketContext() const
Definition Response.cpp:432
void onSourceConnect(core::pipe::Source *source) override
Definition Response.cpp:391
Response & status(int statusCode)
Definition Response.cpp:80
void send(const std::string &chunk)
Definition Response.cpp:202
void send(const char *chunk, std::size_t chunkLen)
Definition Response.cpp:191
Response & setTrailer(const std::string &field, const std::string &value, bool overwrite=true)
Definition Response.cpp:172
Response & set(const std::map< std::string, std::string > &headers, bool overwrite=true)
Definition Response.cpp:98
Response & set(const std::string &field, const std::string &value, bool overwrite=true)
Definition Response.cpp:106
void upgrade(const std::shared_ptr< Request > &request, const std::function< void(const std::string &)> &status)
Definition Response.cpp:222
Response & type(const std::string &type)
Definition Response.cpp:152
void onSourceError(int errnum) override
Definition Response.cpp:417
Response & sendFragment(const std::string &chunk)
Definition Response.cpp:364
Response & cookie(const std::string &name, const std::string &value, const std::map< std::string, std::string > &options={})
Definition Response.cpp:156
Response & append(const std::string &field, const std::string &value)
Definition Response.cpp:86
void sendStatus(int statusCode)
Definition Response.cpp:210
Response & sendFragment(const char *chunk, std::size_t chunkLen)
Definition Response.cpp:346
void onSourceEof() override
Definition Response.cpp:409
void sendFile(const std::string &file, const std::function< void(int)> &callback)
Definition Response.cpp:270
Response & clearCookie(const std::string &name, const std::map< std::string, std::string > &options={})
Definition Response.cpp:162
const std::string & header(const std::string &field)
Definition Response.cpp:428
#define to_hex_str(int_val)
Definition Request.cpp:49