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 class Router;
31
32 /// HTTP response
33 struct response
34 {
35 template<typename Adaptor, typename Handler, typename... Middlewares>
36 friend class crow::Connection;
37
38 friend class Router;
39
40 int code{200}; ///< The Status code for the response.
41 std::string body; ///< The actual payload containing the response data.
42 ci_map headers; ///< HTTP headers.
43
44#ifdef CROW_ENABLE_COMPRESSION
45 bool compressed = true; ///< If compression is enabled and this is false, the individual response will not be compressed.
46#endif
47 bool skip_body = false; ///< Whether this is a response to a HEAD request.
48 bool manual_length_header = false; ///< Whether Crow should automatically add a "Content-Length" header.
49
50 /// Set the value of an existing header in the response.
51 void set_header(std::string key, std::string value)
52 {
53 headers.erase(key);
54 headers.emplace(std::move(key), std::move(value));
55 }
56
57 /// Add a new header to the response.
58 void add_header(std::string key, std::string value)
59 {
60 headers.emplace(std::move(key), std::move(value));
61 }
62
63 const std::string& get_header_value(const std::string& key)
64 {
66 }
67
68 // naive validation of a mime-type string
69 static bool validate_mime_type(const std::string& candidate) noexcept
70 {
71 // Here we simply check that the candidate type starts with
72 // a valid parent type, and has at least one character afterwards.
73 std::array<std::string, 10> valid_parent_types = {
74 "application/", "audio/", "font/", "example/",
75 "image/", "message/", "model/", "multipart/",
76 "text/", "video/"};
77 for (const std::string& parent : valid_parent_types)
78 {
79 // ensure the candidate is *longer* than the parent,
80 // to avoid unnecessary string comparison and to
81 // reject zero-length subtypes.
82 if (candidate.size() <= parent.size())
83 {
84 continue;
85 }
86 // strncmp is used rather than substr to avoid allocation,
87 // but a string_view approach would be better if Crow
88 // migrates to C++17.
89 if (strncmp(parent.c_str(), candidate.c_str(), parent.size()) == 0)
90 {
91 return true;
92 }
93 }
94 return false;
95 }
96
97 // Find the mime type from the content type either by lookup,
98 // or by the content type itself, if it is a valid a mime type.
99 // Defaults to text/plain.
100 static std::string get_mime_type(const std::string& contentType)
101 {
102 const auto mimeTypeIterator = mime_types.find(contentType);
103 if (mimeTypeIterator != mime_types.end())
104 {
105 return mimeTypeIterator->second;
106 }
107 else if (validate_mime_type(contentType))
108 {
109 return contentType;
110 }
111 else
112 {
113 CROW_LOG_WARNING << "Unable to interpret mime type for content type '" << contentType << "'. Defaulting to text/plain.";
114 return "text/plain";
115 }
116 }
117
118
119 // clang-format off
120 response() {}
121 explicit response(int code) : code(code) {}
122 response(std::string body) : body(std::move(body)) {}
123 response(int code, std::string body) : code(code), body(std::move(body)) {}
124 // clang-format on
125 response(returnable&& value)
126 {
127 body = value.dump();
128 set_header("Content-Type", value.content_type);
129 }
130 response(returnable& value)
131 {
132 body = value.dump();
133 set_header("Content-Type", value.content_type);
134 }
135 response(int code, returnable& value):
136 code(code)
137 {
138 body = value.dump();
139 set_header("Content-Type", value.content_type);
140 }
141 response(int code, returnable&& value):
142 code(code), body(value.dump())
143 {
144 set_header("Content-Type", std::move(value.content_type));
145 }
146
147 response(response&& r)
148 {
149 *this = std::move(r);
150 }
151
152 response(std::string contentType, std::string body):
153 body(std::move(body))
154 {
155 set_header("Content-Type", get_mime_type(contentType));
156 }
157
158 response(int code, std::string contentType, std::string body):
159 code(code), body(std::move(body))
160 {
161 set_header("Content-Type", get_mime_type(contentType));
162 }
163
164 response& operator=(const response& r) = delete;
165
166 response& operator=(response&& r) noexcept
167 {
168 body = std::move(r.body);
169 code = r.code;
170 headers = std::move(r.headers);
171 completed_ = r.completed_;
172 file_info = std::move(r.file_info);
173 return *this;
174 }
175
176 /// Check if the response has completed (whether response.end() has been called)
177 bool is_completed() const noexcept
178 {
179 return completed_;
180 }
181
182 void clear()
183 {
184 body.clear();
185 code = 200;
186 headers.clear();
187 completed_ = false;
188 file_info = static_file_info{};
189 }
190
191 /// Return a "Temporary Redirect" response.
192
193 ///
194 /// Location can either be a route or a full URL.
195 void redirect(const std::string& location)
196 {
197 code = 307;
198 set_header("Location", location);
199 }
200
201 /// Return a "Permanent Redirect" response.
202
203 ///
204 /// Location can either be a route or a full URL.
205 void redirect_perm(const std::string& location)
206 {
207 code = 308;
208 set_header("Location", location);
209 }
210
211 /// Return a "Found (Moved Temporarily)" response.
212
213 ///
214 /// Location can either be a route or a full URL.
215 void moved(const std::string& location)
216 {
217 code = 302;
218 set_header("Location", location);
219 }
220
221 /// Return a "Moved Permanently" response.
222
223 ///
224 /// Location can either be a route or a full URL.
225 void moved_perm(const std::string& location)
226 {
227 code = 301;
228 set_header("Location", location);
229 }
230
231 void write(const std::string& body_part)
232 {
233 body += body_part;
234 }
235
236 /// Set the response completion flag and call the handler (to send the response).
237 void end()
238 {
239 if (!completed_)
240 {
241 completed_ = true;
242 if (skip_body)
243 {
244 set_header("Content-Length", std::to_string(body.size()));
245 body = "";
247 }
248 if (complete_request_handler_)
249 {
250 complete_request_handler_();
251 manual_length_header = false;
252 skip_body = false;
253 }
254 }
255 }
256
257 /// Same as end() except it adds a body part right before ending.
258 void end(const std::string& body_part)
259 {
260 body += body_part;
261 end();
262 }
263
264 /// Check if the connection is still alive (usually by checking the socket status).
265 bool is_alive()
266 {
267 return is_alive_helper_ && is_alive_helper_();
268 }
269
270 /// Check whether the response has a static file defined.
272 {
273 return file_info.path.size();
274 }
275
276 /// This constains metadata (coming from the `stat` command) related to any static files associated with this response.
277
278 ///
279 /// Either a static file or a string body can be returned as 1 response.
281 {
282 std::string path = "";
283 struct stat statbuf;
284 int statResult;
285 };
286
287 /// Return a static file as the response body
288 void set_static_file_info(std::string path)
289 {
290 utility::sanitize_filename(path);
292 }
293
294 /// Return a static file as the response body without sanitizing the path (use set_static_file_info instead)
295 void set_static_file_info_unsafe(std::string path)
296 {
297 file_info.path = path;
298 file_info.statResult = stat(file_info.path.c_str(), &file_info.statbuf);
299#ifdef CROW_ENABLE_COMPRESSION
300 compressed = false;
301#endif
302 if (file_info.statResult == 0 && S_ISREG(file_info.statbuf.st_mode))
303 {
304 std::size_t last_dot = path.find_last_of(".");
305 std::string extension = path.substr(last_dot + 1);
306 code = 200;
307 this->add_header("Content-Length", std::to_string(file_info.statbuf.st_size));
308
309 if (!extension.empty())
310 {
311 this->add_header("Content-Type", get_mime_type(extension));
312 }
313 }
314 else
315 {
316 code = 404;
317 file_info.path.clear();
318 }
319 }
320
321 private:
322 bool completed_{};
323 std::function<void()> complete_request_handler_;
324 std::function<bool()> is_alive_helper_;
325 static_file_info file_info;
326 };
327} // namespace crow
An HTTP connection.
Definition http_connection.h:48
Handles matching requests to existing rules and upgrade requests.
Definition routing.h:1254
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:281
HTTP response.
Definition http_response.h:34
void end(const std::string &body_part)
Same as end() except it adds a body part right before ending.
Definition http_response.h:258
void add_header(std::string key, std::string value)
Add a new header to the response.
Definition http_response.h:58
bool skip_body
Whether this is a response to a HEAD request.
Definition http_response.h:47
void moved(const std::string &location)
Return a "Found (Moved Temporarily)" response.
Definition http_response.h:215
void set_static_file_info(std::string path)
Return a static file as the response body.
Definition http_response.h:288
bool manual_length_header
Whether Crow should automatically add a "Content-Length" header.
Definition http_response.h:48
void redirect_perm(const std::string &location)
Return a "Permanent Redirect" response.
Definition http_response.h:205
void moved_perm(const std::string &location)
Return a "Moved Permanently" response.
Definition http_response.h:225
bool is_completed() const noexcept
Check if the response has completed (whether response.end() has been called)
Definition http_response.h:177
int code
The Status code for the response.
Definition http_response.h:40
void set_header(std::string key, std::string value)
Set the value of an existing header in the response.
Definition http_response.h:51
void end()
Set the response completion flag and call the handler (to send the response).
Definition http_response.h:237
void redirect(const std::string &location)
Return a "Temporary Redirect" response.
Definition http_response.h:195
bool is_alive()
Check if the connection is still alive (usually by checking the socket status).
Definition http_response.h:265
void set_static_file_info_unsafe(std::string path)
Return a static file as the response body without sanitizing the path (use set_static_file_info inste...
Definition http_response.h:295
ci_map headers
HTTP headers.
Definition http_response.h:42
std::string body
The actual payload containing the response data.
Definition http_response.h:41
bool compressed
If compression is enabled and this is false, the individual response will not be compressed.
Definition http_response.h:45
bool is_static_type()
Check whether the response has a static file defined.
Definition http_response.h:271