Crow  1.1
A C++ microframework for the web
 
Loading...
Searching...
No Matches
http_response.h
1#pragma once
2#include <string>
3#include <unordered_map>
4#include <ios>
5#include <fstream>
6#include <sstream>
7// S_ISREG is not defined for windows
8// This defines it like suggested in https://stackoverflow.com/a/62371749
9#if defined(_MSC_VER)
10#define _CRT_INTERNAL_NONSTDC_NAMES 1
11#endif
12#include <sys/stat.h>
13#if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG)
14#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
15#endif
16
17#include "crow/http_request.h"
18#include "crow/ci_map.h"
19#include "crow/socket_adaptors.h"
20#include "crow/logging.h"
21#include "crow/mime_types.h"
22#include "crow/returnable.h"
23
24
25namespace crow
26{
27 template<typename Adaptor, typename Handler, typename... Middlewares>
28 class Connection;
29
30 namespace websocket
31 {
32 template<typename Adaptor, typename Handler>
33 class Connection;
34 }
35
36 class Router;
37
38 /// HTTP response
39 struct response
40 {
41 template<typename Adaptor, typename Handler, typename... Middlewares>
42 friend class crow::Connection;
43
44 template<typename Adaptor, typename Handler>
45 friend class websocket::Connection;
46
47 friend class Router;
48
49 int code{200}; ///< The Status code for the response.
50 std::string body; ///< The actual payload containing the response data.
51 ci_map headers; ///< HTTP headers.
52
53#ifdef CROW_ENABLE_COMPRESSION
54 bool compressed = true; ///< If compression is enabled and this is false, the individual response will not be compressed.
55#endif
56 bool skip_body = false; ///< Whether this is a response to a HEAD request.
57 bool manual_length_header = false; ///< Whether Crow should automatically add a "Content-Length" header.
58
59 /// Set the value of an existing header in the response.
60 void set_header(std::string key, std::string value)
61 {
62 headers.erase(key);
63 headers.emplace(std::move(key), std::move(value));
64 }
65
66 /// Add a new header to the response.
67 void add_header(std::string key, std::string value)
68 {
69 headers.emplace(std::move(key), std::move(value));
70 }
71
72 const std::string& get_header_value(const std::string& key)
73 {
75 }
76
77 // naive validation of a mime-type string
78 static bool validate_mime_type(const std::string& candidate) noexcept
79 {
80 // Here we simply check that the candidate type starts with
81 // a valid parent type, and has at least one character afterwards.
82 std::array<std::string, 10> valid_parent_types = {
83 "application/", "audio/", "font/", "example/",
84 "image/", "message/", "model/", "multipart/",
85 "text/", "video/"};
86 for (const std::string& parent : valid_parent_types)
87 {
88 // ensure the candidate is *longer* than the parent,
89 // to avoid unnecessary string comparison and to
90 // reject zero-length subtypes.
91 if (candidate.size() <= parent.size())
92 {
93 continue;
94 }
95 // strncmp is used rather than substr to avoid allocation,
96 // but a string_view approach would be better if Crow
97 // migrates to C++17.
98 if (strncmp(parent.c_str(), candidate.c_str(), parent.size()) == 0)
99 {
100 return true;
101 }
102 }
103 return false;
104 }
105
106 // Find the mime type from the content type either by lookup,
107 // or by the content type itself, if it is a valid a mime type.
108 // Defaults to text/plain.
109 static std::string get_mime_type(const std::string& contentType)
110 {
111 const auto mimeTypeIterator = mime_types.find(contentType);
112 if (mimeTypeIterator != mime_types.end())
113 {
114 return mimeTypeIterator->second;
115 }
116 else if (validate_mime_type(contentType))
117 {
118 return contentType;
119 }
120 else
121 {
122 CROW_LOG_WARNING << "Unable to interpret mime type for content type '" << contentType << "'. Defaulting to text/plain.";
123 return "text/plain";
124 }
125 }
126
127
128 // clang-format off
129 response() {}
130 explicit response(int code_) : code(code_) {}
131 response(std::string body_) : body(std::move(body_)) {}
132 response(int code_, std::string body_) : code(code_), body(std::move(body_)) {}
133 // clang-format on
134 response(returnable&& value)
135 {
136 body = value.dump();
137 set_header("Content-Type", value.content_type);
138 }
139 response(returnable& value)
140 {
141 body = value.dump();
142 set_header("Content-Type", value.content_type);
143 }
144 response(int code_, returnable& value):
145 code(code_)
146 {
147 body = value.dump();
148 set_header("Content-Type", value.content_type);
149 }
150 response(int code_, returnable&& value):
151 code(code_), body(value.dump())
152 {
153 set_header("Content-Type", std::move(value.content_type));
154 }
155
156 response(response&& r)
157 {
158 *this = std::move(r);
159 }
160
161 response(std::string contentType, std::string body_):
162 body(std::move(body_))
163 {
164 set_header("Content-Type", get_mime_type(contentType));
165 }
166
167 response(int code_, std::string contentType, std::string body_):
168 code(code_), body(std::move(body_))
169 {
170 set_header("Content-Type", get_mime_type(contentType));
171 }
172
173 response& operator=(const response& r) = delete;
174
175 response& operator=(response&& r) noexcept
176 {
177 body = std::move(r.body);
178 code = r.code;
179 headers = std::move(r.headers);
180 completed_ = r.completed_;
181 file_info = std::move(r.file_info);
182 return *this;
183 }
184
185 /// Check if the response has completed (whether response.end() has been called)
186 bool is_completed() const noexcept
187 {
188 return completed_;
189 }
190
191 void clear()
192 {
193 body.clear();
194 code = 200;
195 headers.clear();
196 completed_ = false;
197 file_info = static_file_info{};
198 }
199
200 /// Return a "Temporary Redirect" response.
201
202 ///
203 /// Location can either be a route or a full URL.
204 void redirect(const std::string& location)
205 {
206 code = 307;
207 set_header("Location", location);
208 }
209
210 /// Return a "Permanent Redirect" response.
211
212 ///
213 /// Location can either be a route or a full URL.
214 void redirect_perm(const std::string& location)
215 {
216 code = 308;
217 set_header("Location", location);
218 }
219
220 /// Return a "Found (Moved Temporarily)" response.
221
222 ///
223 /// Location can either be a route or a full URL.
224 void moved(const std::string& location)
225 {
226 code = 302;
227 set_header("Location", location);
228 }
229
230 /// Return a "Moved Permanently" response.
231
232 ///
233 /// Location can either be a route or a full URL.
234 void moved_perm(const std::string& location)
235 {
236 code = 301;
237 set_header("Location", location);
238 }
239
240 void write(const std::string& body_part)
241 {
242 body += body_part;
243 }
244
245 /// Set the response completion flag and call the handler (to send the response).
246 void end()
247 {
248 if (!completed_)
249 {
250 completed_ = true;
251 if (skip_body)
252 {
253 set_header("Content-Length", std::to_string(body.size()));
254 body = "";
256 }
257 if (complete_request_handler_)
258 {
259 complete_request_handler_();
260 manual_length_header = false;
261 skip_body = false;
262 }
263 }
264 }
265
266 /// Same as end() except it adds a body part right before ending.
267 void end(const std::string& body_part)
268 {
269 body += body_part;
270 end();
271 }
272
273 /// Check if the connection is still alive (usually by checking the socket status).
274 bool is_alive()
275 {
276 return is_alive_helper_ && is_alive_helper_();
277 }
278
279 /// Check whether the response has a static file defined.
281 {
282 return file_info.path.size();
283 }
284
285 /// This constains metadata (coming from the `stat` command) related to any static files associated with this response.
286
287 ///
288 /// Either a static file or a string body can be returned as 1 response.
290 {
291 std::string path = "";
292 struct stat statbuf;
293 int statResult;
294 };
295
296 /// Return a static file as the response body, the content_type may be specified explicitly.
297 void set_static_file_info(std::string path, std::string content_type = "")
298 {
299 utility::sanitize_filename(path);
300 set_static_file_info_unsafe(path, content_type);
301 }
302
303 /// Return a static file as the response body without sanitizing the path (use set_static_file_info instead),
304 /// the content_type may be specified explicitly.
305 void set_static_file_info_unsafe(std::string path, std::string content_type = "")
306 {
307 file_info.path = path;
308 file_info.statResult = stat(file_info.path.c_str(), &file_info.statbuf);
309#ifdef CROW_ENABLE_COMPRESSION
310 compressed = false;
311#endif
312 if (file_info.statResult == 0 && S_ISREG(file_info.statbuf.st_mode))
313 {
314 code = 200;
315 this->add_header("Content-Length", std::to_string(file_info.statbuf.st_size));
316
317 if (content_type.empty())
318 {
319 std::size_t last_dot = path.find_last_of('.');
320 std::string extension = path.substr(last_dot + 1);
321
322 if (!extension.empty())
323 {
324 this->add_header("Content-Type", get_mime_type(extension));
325 }
326 }
327 else
328 {
329 this->add_header("Content-Type", content_type);
330 }
331 }
332 else
333 {
334 code = 404;
335 file_info.path.clear();
336 }
337 }
338
339 private:
340 void write_header_into_buffer(std::vector<asio::const_buffer>& buffers, std::string& content_length_buffer, bool add_keep_alive, const std::string& server_name)
341 {
342 // TODO(EDev): HTTP version in status codes should be dynamic
343 // Keep in sync with common.h/status
344 static std::unordered_map<int, std::string> statusCodes = {
345 {status::CONTINUE, "HTTP/1.1 100 Continue\r\n"},
346 {status::SWITCHING_PROTOCOLS, "HTTP/1.1 101 Switching Protocols\r\n"},
347
348 {status::OK, "HTTP/1.1 200 OK\r\n"},
349 {status::CREATED, "HTTP/1.1 201 Created\r\n"},
350 {status::ACCEPTED, "HTTP/1.1 202 Accepted\r\n"},
351 {status::NON_AUTHORITATIVE_INFORMATION, "HTTP/1.1 203 Non-Authoritative Information\r\n"},
352 {status::NO_CONTENT, "HTTP/1.1 204 No Content\r\n"},
353 {status::RESET_CONTENT, "HTTP/1.1 205 Reset Content\r\n"},
354 {status::PARTIAL_CONTENT, "HTTP/1.1 206 Partial Content\r\n"},
355
356 {status::MULTIPLE_CHOICES, "HTTP/1.1 300 Multiple Choices\r\n"},
357 {status::MOVED_PERMANENTLY, "HTTP/1.1 301 Moved Permanently\r\n"},
358 {status::FOUND, "HTTP/1.1 302 Found\r\n"},
359 {status::SEE_OTHER, "HTTP/1.1 303 See Other\r\n"},
360 {status::NOT_MODIFIED, "HTTP/1.1 304 Not Modified\r\n"},
361 {status::TEMPORARY_REDIRECT, "HTTP/1.1 307 Temporary Redirect\r\n"},
362 {status::PERMANENT_REDIRECT, "HTTP/1.1 308 Permanent Redirect\r\n"},
363
364 {status::BAD_REQUEST, "HTTP/1.1 400 Bad Request\r\n"},
365 {status::UNAUTHORIZED, "HTTP/1.1 401 Unauthorized\r\n"},
366 {status::FORBIDDEN, "HTTP/1.1 403 Forbidden\r\n"},
367 {status::NOT_FOUND, "HTTP/1.1 404 Not Found\r\n"},
368 {status::METHOD_NOT_ALLOWED, "HTTP/1.1 405 Method Not Allowed\r\n"},
369 {status::NOT_ACCEPTABLE, "HTTP/1.1 406 Not Acceptable\r\n"},
370 {status::PROXY_AUTHENTICATION_REQUIRED, "HTTP/1.1 407 Proxy Authentication Required\r\n"},
371 {status::CONFLICT, "HTTP/1.1 409 Conflict\r\n"},
372 {status::GONE, "HTTP/1.1 410 Gone\r\n"},
373 {status::PAYLOAD_TOO_LARGE, "HTTP/1.1 413 Payload Too Large\r\n"},
374 {status::UNSUPPORTED_MEDIA_TYPE, "HTTP/1.1 415 Unsupported Media Type\r\n"},
375 {status::RANGE_NOT_SATISFIABLE, "HTTP/1.1 416 Range Not Satisfiable\r\n"},
376 {status::EXPECTATION_FAILED, "HTTP/1.1 417 Expectation Failed\r\n"},
377 {status::PRECONDITION_REQUIRED, "HTTP/1.1 428 Precondition Required\r\n"},
378 {status::TOO_MANY_REQUESTS, "HTTP/1.1 429 Too Many Requests\r\n"},
379 {status::UNAVAILABLE_FOR_LEGAL_REASONS, "HTTP/1.1 451 Unavailable For Legal Reasons\r\n"},
380
381 {status::INTERNAL_SERVER_ERROR, "HTTP/1.1 500 Internal Server Error\r\n"},
382 {status::NOT_IMPLEMENTED, "HTTP/1.1 501 Not Implemented\r\n"},
383 {status::BAD_GATEWAY, "HTTP/1.1 502 Bad Gateway\r\n"},
384 {status::SERVICE_UNAVAILABLE, "HTTP/1.1 503 Service Unavailable\r\n"},
385 {status::GATEWAY_TIMEOUT, "HTTP/1.1 504 Gateway Timeout\r\n"},
386 {status::VARIANT_ALSO_NEGOTIATES, "HTTP/1.1 506 Variant Also Negotiates\r\n"},
387 };
388
389 static const std::string seperator = ": ";
390
391 buffers.clear();
392 buffers.reserve(4 * (headers.size() + 5) + 3);
393
394 if (!statusCodes.count(code))
395 {
396 CROW_LOG_WARNING << this << " status code "
397 << "(" << code << ")"
398 << " not defined, returning 500 instead";
399 code = 500;
400 }
401
402 auto& status = statusCodes.find(code)->second;
403 buffers.emplace_back(status.data(), status.size());
404
405 if (code >= 400 && body.empty())
406 body = statusCodes[code].substr(9);
407
408 for (auto& kv : headers)
409 {
410 buffers.emplace_back(kv.first.data(), kv.first.size());
411 buffers.emplace_back(seperator.data(), seperator.size());
412 buffers.emplace_back(kv.second.data(), kv.second.size());
413 buffers.emplace_back(crlf.data(), crlf.size());
414 }
415
416 if (!manual_length_header && !headers.count("content-length"))
417 {
418 content_length_buffer = std::to_string(body.size());
419 static std::string content_length_tag = "Content-Length: ";
420 buffers.emplace_back(content_length_tag.data(), content_length_tag.size());
421 buffers.emplace_back(content_length_buffer.data(), content_length_buffer.size());
422 buffers.emplace_back(crlf.data(), crlf.size());
423 }
424 if (!headers.count("server") && !server_name.empty())
425 {
426 static std::string server_tag = "Server: ";
427 buffers.emplace_back(server_tag.data(), server_tag.size());
428 buffers.emplace_back(server_name.data(), server_name.size());
429 buffers.emplace_back(crlf.data(), crlf.size());
430 }
431 /*if (!headers.count("date"))
432 {
433 static std::string date_tag = "Date: ";
434 date_str_ = get_cached_date_str();
435 buffers.emplace_back(date_tag.data(), date_tag.size());
436 buffers.emplace_back(date_str_.data(), date_str_.size());
437 buffers.emplace_back(crlf.data(), crlf.size());
438 }*/
439 if (add_keep_alive)
440 {
441 static std::string keep_alive_tag = "Connection: Keep-Alive";
442 buffers.emplace_back(keep_alive_tag.data(), keep_alive_tag.size());
443 buffers.emplace_back(crlf.data(), crlf.size());
444 }
445
446 buffers.emplace_back(crlf.data(), crlf.size());
447 }
448
449 bool completed_{};
450 std::function<void()> complete_request_handler_;
451 std::function<bool()> is_alive_helper_;
452 static_file_info file_info;
453 };
454} // namespace crow
An HTTP connection.
Definition http_connection.h:48
Handles matching requests to existing rules and upgrade requests.
Definition routing.h:1268
A websocket connection.
Definition websocket.h:111
The main namespace of the library. In this namespace is defined the most important classes and functi...
const std::string & get_header_value(const T &headers, const std::string &key)
Find and return the value associated with the key. (returns an empty string if nothing is found)
Definition http_request.h:24
This constains metadata (coming from the stat command) related to any static files associated with th...
Definition http_response.h:290
HTTP response.
Definition http_response.h:40
void end(const std::string &body_part)
Same as end() except it adds a body part right before ending.
Definition http_response.h:267
void add_header(std::string key, std::string value)
Add a new header to the response.
Definition http_response.h:67
void set_static_file_info(std::string path, std::string content_type="")
Return a static file as the response body, the content_type may be specified explicitly.
Definition http_response.h:297
bool skip_body
Whether this is a response to a HEAD request.
Definition http_response.h:56
void set_static_file_info_unsafe(std::string path, std::string content_type="")
Definition http_response.h:305
void moved(const std::string &location)
Return a "Found (Moved Temporarily)" response.
Definition http_response.h:224
bool manual_length_header
Whether Crow should automatically add a "Content-Length" header.
Definition http_response.h:57
void redirect_perm(const std::string &location)
Return a "Permanent Redirect" response.
Definition http_response.h:214
void moved_perm(const std::string &location)
Return a "Moved Permanently" response.
Definition http_response.h:234
bool is_completed() const noexcept
Check if the response has completed (whether response.end() has been called)
Definition http_response.h:186
int code
The Status code for the response.
Definition http_response.h:49
void set_header(std::string key, std::string value)
Set the value of an existing header in the response.
Definition http_response.h:60
void end()
Set the response completion flag and call the handler (to send the response).
Definition http_response.h:246
void redirect(const std::string &location)
Return a "Temporary Redirect" response.
Definition http_response.h:204
bool is_alive()
Check if the connection is still alive (usually by checking the socket status).
Definition http_response.h:274
ci_map headers
HTTP headers.
Definition http_response.h:51
std::string body
The actual payload containing the response data.
Definition http_response.h:50
bool compressed
If compression is enabled and this is false, the individual response will not be compressed.
Definition http_response.h:54
bool is_static_type()
Check whether the response has a static file defined.
Definition http_response.h:280