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