CppServer 1.0.5.0
C++ Server Library
Loading...
Searching...
No Matches
http_response.cpp
Go to the documentation of this file.
1
10
11#include "errors/exceptions.h"
12#include "string/format.h"
13#include "string/string_utils.h"
14#include "utility/countof.h"
15
16#include <cassert>
17
18namespace CppServer {
19namespace HTTP {
20
21const std::unordered_map<std::string, std::string> HTTPResponse::_mime_table =
22{
23 // Base content types
24 { ".html", "text/html" },
25 { ".css", "text/css" },
26 { ".js", "text/javascript" },
27 { ".vue", "text/html" },
28 { ".xml", "text/xml" },
29
30 // Application content types
31 { ".atom", "application/atom+xml" },
32 { ".fastsoap", "application/fastsoap" },
33 { ".gzip", "application/gzip" },
34 { ".json", "application/json" },
35 { ".map", "application/json" },
36 { ".pdf", "application/pdf" },
37 { ".ps", "application/postscript" },
38 { ".soap", "application/soap+xml" },
39 { ".sql", "application/sql" },
40 { ".xslt", "application/xslt+xml" },
41 { ".zip", "application/zip" },
42 { ".zlib", "application/zlib" },
43
44 // Audio content types
45 { ".aac", "audio/aac" },
46 { ".ac3", "audio/ac3" },
47 { ".mp3", "audio/mpeg" },
48 { ".ogg", "audio/ogg" },
49
50 // Font content types
51 { ".ttf", "font/ttf" },
52
53 // Image content types
54 { ".bmp", "image/bmp" },
55 { ".emf", "image/emf" },
56 { ".gif", "image/gif" },
57 { ".jpg", "image/jpeg" },
58 { ".jpm", "image/jpm" },
59 { ".jpx", "image/jpx" },
60 { ".jrx", "image/jrx" },
61 { ".png", "image/png" },
62 { ".svg", "image/svg+xml" },
63 { ".tiff", "image/tiff" },
64 { ".wmf", "image/wmf" },
65
66 // Message content types
67 { ".http", "message/http" },
68 { ".s-http", "message/s-http" },
69
70 // Model content types
71 { ".mesh", "model/mesh" },
72 { ".vrml", "model/vrml" },
73
74 // Text content types
75 { ".csv", "text/csv" },
76 { ".plain", "text/plain" },
77 { ".richtext", "text/richtext" },
78 { ".rtf", "text/rtf" },
79 { ".rtx", "text/rtx" },
80 { ".sgml", "text/sgml" },
81 { ".strings", "text/strings" },
82 { ".url", "text/uri-list" },
83
84 // Video content types
85 { ".H264", "video/H264" },
86 { ".H265", "video/H265" },
87 { ".mp4", "video/mp4" },
88 { ".mpeg", "video/mpeg" },
89 { ".raw", "video/raw" }
90};
91
92std::tuple<std::string_view, std::string_view> HTTPResponse::header(size_t i) const noexcept
93{
94 assert((i < _headers.size()) && "Index out of bounds!");
95 if (i >= _headers.size())
96 return std::make_tuple(std::string_view(), std::string_view());
97
98 auto item = _headers[i];
99
100 return std::make_tuple(std::string_view(_cache.data() + std::get<0>(item), std::get<1>(item)), std::string_view(_cache.data() + std::get<2>(item), std::get<3>(item)));
101}
102
104{
105 _error = false;
106 _status = 0;
107 _status_phrase_index = 0;
108 _status_phrase_size = 0;
109 _protocol_index = 0;
110 _protocol_size = 0;
111 _headers.clear();
112 _body_index = 0;
113 _body_size = 0;
114 _body_length = 0;
115 _body_length_provided = false;
116
117 _cache.clear();
118 _cache_size = 0;
119 return *this;
120}
121
122HTTPResponse& HTTPResponse::SetBegin(int status, std::string_view protocol)
123{
124 std::string status_phrase;
125
126 switch (status)
127 {
128 case 100: status_phrase = "Continue"; break;
129 case 101: status_phrase = "Switching Protocols"; break;
130 case 102: status_phrase = "Processing"; break;
131 case 103: status_phrase = "Early Hints"; break;
132
133 case 200: status_phrase = "OK"; break;
134 case 201: status_phrase = "Created"; break;
135 case 202: status_phrase = "Accepted"; break;
136 case 203: status_phrase = "Non-Authoritative Information"; break;
137 case 204: status_phrase = "No Content"; break;
138 case 205: status_phrase = "Reset Content"; break;
139 case 206: status_phrase = "Partial Content"; break;
140 case 207: status_phrase = "Multi-Status"; break;
141 case 208: status_phrase = "Already Reported"; break;
142
143 case 226: status_phrase = "IM Used"; break;
144
145 case 300: status_phrase = "Multiple Choices"; break;
146 case 301: status_phrase = "Moved Permanently"; break;
147 case 302: status_phrase = "Found"; break;
148 case 303: status_phrase = "See Other"; break;
149 case 304: status_phrase = "Not Modified"; break;
150 case 305: status_phrase = "Use Proxy"; break;
151 case 306: status_phrase = "Switch Proxy"; break;
152 case 307: status_phrase = "Temporary Redirect"; break;
153 case 308: status_phrase = "Permanent Redirect"; break;
154
155 case 400: status_phrase = "Bad Request"; break;
156 case 401: status_phrase = "Unauthorized"; break;
157 case 402: status_phrase = "Payment Required"; break;
158 case 403: status_phrase = "Forbidden"; break;
159 case 404: status_phrase = "Not Found"; break;
160 case 405: status_phrase = "Method Not Allowed"; break;
161 case 406: status_phrase = "Not Acceptable"; break;
162 case 407: status_phrase = "Proxy Authentication Required"; break;
163 case 408: status_phrase = "Request Timeout"; break;
164 case 409: status_phrase = "Conflict"; break;
165 case 410: status_phrase = "Gone"; break;
166 case 411: status_phrase = "Length Required"; break;
167 case 412: status_phrase = "Precondition Failed"; break;
168 case 413: status_phrase = "Payload Too Large"; break;
169 case 414: status_phrase = "URI Too Long"; break;
170 case 415: status_phrase = "Unsupported Media Type"; break;
171 case 416: status_phrase = "Range Not Satisfiable"; break;
172 case 417: status_phrase = "Expectation Failed"; break;
173
174 case 421: status_phrase = "Misdirected Request"; break;
175 case 422: status_phrase = "Unprocessable Entity"; break;
176 case 423: status_phrase = "Locked"; break;
177 case 424: status_phrase = "Failed Dependency"; break;
178 case 425: status_phrase = "Too Early"; break;
179 case 426: status_phrase = "Upgrade Required"; break;
180 case 427: status_phrase = "Unassigned"; break;
181 case 428: status_phrase = "Precondition Required"; break;
182 case 429: status_phrase = "Too Many Requests"; break;
183 case 431: status_phrase = "Request Header Fields Too Large"; break;
184
185 case 451: status_phrase = "Unavailable For Legal Reasons"; break;
186
187 case 500: status_phrase = "Internal Server Error"; break;
188 case 501: status_phrase = "Not Implemented"; break;
189 case 502: status_phrase = "Bad Gateway"; break;
190 case 503: status_phrase = "Service Unavailable"; break;
191 case 504: status_phrase = "Gateway Timeout"; break;
192 case 505: status_phrase = "HTTP Version Not Supported"; break;
193 case 506: status_phrase = "Variant Also Negotiates"; break;
194 case 507: status_phrase = "Insufficient Storage"; break;
195 case 508: status_phrase = "Loop Detected"; break;
196
197 case 510: status_phrase = "Not Extended"; break;
198 case 511: status_phrase = "Network Authentication Required"; break;
199
200 default: status_phrase = "Unknown"; break;
201 }
202
204 return *this;
205}
206
207HTTPResponse& HTTPResponse::SetBegin(int status, std::string_view status_phrase, std::string_view protocol)
208{
209 // Clear the HTTP response cache
210 Clear();
211
212 size_t index = 0;
213
214 // Append the HTTP response protocol version
215 _cache.append(protocol);
216 _protocol_index = index;
217 _protocol_size = protocol.size();
218
219 _cache.append(" ");
220 index = _cache.size();
221
222 // Append the HTTP response status
223 char buffer[32];
224 _cache.append(FastConvert(status, buffer, CppCommon::countof(buffer)));
225 _status = status;
226
227 _cache.append(" ");
228 index = _cache.size();
229
230 // Append the HTTP response status phrase
231 _cache.append(status_phrase);
232 _status_phrase_index = index;
233 _status_phrase_size = status_phrase.size();
234
235 _cache.append("\r\n");
236 return *this;
237}
238
239HTTPResponse& HTTPResponse::SetContentType(std::string_view extension)
240{
241 // Try to lookup the content type in mime table
242 const auto& mime = _mime_table.find(std::string(extension));
243 if (mime != _mime_table.end())
244 return SetHeader("Content-Type", mime->second);
245
246 return *this;
247}
248
249HTTPResponse& HTTPResponse::SetHeader(std::string_view key, std::string_view value)
250{
251 size_t index = _cache.size();
252
253 // Append the HTTP response header's key
254 _cache.append(key);
255 size_t key_index = index;
256 size_t key_size = key.size();
257
258 _cache.append(": ");
259 index = _cache.size();
260
261 // Append the HTTP response header's value
262 _cache.append(value);
263 size_t value_index = index;
264 size_t value_size = value.size();
265
266 _cache.append("\r\n");
267
268 // Add the header to the corresponding collection
269 _headers.emplace_back(key_index, key_size, value_index, value_size);
270 return *this;
271}
272
273HTTPResponse& HTTPResponse::SetCookie(std::string_view name, std::string_view value, size_t max_age, std::string_view path, std::string_view domain, bool secure, bool strict, bool http_only)
274{
275 size_t index = _cache.size();
276
277 // Append the HTTP response header's key
278 _cache.append("Set-Cookie");
279 size_t key_index = index;
280 size_t key_size = 10;
281
282 _cache.append(": ");
283 index = _cache.size();
284
285 // Append the HTTP response header's value
286 size_t value_index = index;
287
288 char buffer[32];
289
290 // Append cookie
291 _cache.append(name);
292 _cache.append("=");
293 _cache.append(value);
294 _cache.append("; Max-Age=");
295 _cache.append(FastConvert(max_age, buffer, CppCommon::countof(buffer)));
296 if (!domain.empty())
297 {
298 _cache.append("; Domain=");
299 _cache.append(domain);
300 }
301 if (!path.empty())
302 {
303 _cache.append("; Path=");
304 _cache.append(path);
305 }
306 if (secure)
307 _cache.append("; Secure");
308 if (strict)
309 _cache.append("; SameSite=Strict");
310 if (http_only)
311 _cache.append("; HttpOnly");
312
313 size_t value_size = _cache.size() - value_index;
314
315 _cache.append("\r\n");
316
317 // Add the header to the corresponding collection
318 _headers.emplace_back(key_index, key_size, value_index, value_size);
319 return *this;
320}
321
322HTTPResponse& HTTPResponse::SetBody(std::string_view body)
323{
324 // Append non empty content length header
325 char buffer[32];
326 SetHeader("Content-Length", FastConvert(body.size(), buffer, CppCommon::countof(buffer)));
327
328 _cache.append("\r\n");
329
330 size_t index = _cache.size();
331
332 // Append the HTTP response body
333 _cache.append(body);
334 _body_index = index;
335 _body_size = body.size();
336 _body_length = body.size();
337 _body_length_provided = true;
338 return *this;
339}
340
342{
343 // Append content length header
344 char buffer[32];
345 SetHeader("Content-Length", FastConvert(length, buffer, CppCommon::countof(buffer)));
346
347 _cache.append("\r\n");
348
349 size_t index = _cache.size();
350
351 // Clear the HTTP response body
352 _body_index = index;
353 _body_size = 0;
354 _body_length = length;
355 _body_length_provided = true;
356 return *this;
357}
358
360{
361 Clear();
363 SetBody();
364 return *this;
365}
366
367HTTPResponse& HTTPResponse::MakeErrorResponse(int status, std::string_view content, std::string_view content_type)
368{
369 Clear();
371 if (!content_type.empty())
372 SetHeader("Content-Type", content_type);
373 SetBody(content);
374 return *this;
375}
376
378{
379 Clear();
380 SetBegin(200);
381 SetBody();
382 return *this;
383}
384
385HTTPResponse& HTTPResponse::MakeGetResponse(std::string_view content, std::string_view content_type)
386{
387 Clear();
388 SetBegin(200);
389 if (!content_type.empty())
390 SetHeader("Content-Type", content_type);
391 SetBody(content);
392 return *this;
393}
394
396{
397 Clear();
398 SetBegin(200);
399 SetHeader("Allow", allow);
400 SetBody();
401 return *this;
402}
403
405{
406 Clear();
407 SetBegin(200);
408 SetHeader("Content-Type", "message/http");
409 SetBody(request);
410 return *this;
411}
412
413bool HTTPResponse::IsPendingHeader() const
414{
415 return (!_error && (_body_index == 0));
416}
417
418bool HTTPResponse::IsPendingBody() const
419{
420 return (!_error && (_body_index > 0) && (_body_size > 0));
421}
422
423bool HTTPResponse::ReceiveHeader(const void* buffer, size_t size)
424{
425 // Update the response cache
426 _cache.insert(_cache.end(), (const char*)buffer, (const char*)buffer + size);
427
428 // Try to seek for HTTP header separator
429 for (size_t i = _cache_size; i < _cache.size(); ++i)
430 {
431 // Check for the response cache out of bounds
432 if ((i + 3) >= _cache.size())
433 break;
434
435 // Check for the header separator
436 if ((_cache[i + 0] == '\r') && (_cache[i + 1] == '\n') && (_cache[i + 2] == '\r') && (_cache[i + 3] == '\n'))
437 {
438 size_t index = 0;
439
440 // Set the error flag for a while...
441 _error = true;
442
443 // Parse protocol version
444 _protocol_index = index;
445 _protocol_size = 0;
446 while (_cache[index] != ' ')
447 {
448 ++_protocol_size;
449 ++index;
450 if (index >= _cache.size())
451 return false;
452 }
453 ++index;
454 if (index >= _cache.size())
455 return false;
456
457 // Parse status code
458 size_t status_index = index;
459 size_t status_size = 0;
460 while (_cache[index] != ' ')
461 {
462 if ((_cache[index] < '0') || (_cache[index] > '9'))
463 return false;
464 ++status_size;
465 ++index;
466 if (index >= _cache.size())
467 return false;
468 }
469 _status = 0;
470 for (size_t j = status_index; j < (status_index + status_size); ++j)
471 {
472 _status *= 10;
473 _status += _cache[j] - '0';
474 }
475 ++index;
476 if (index >= _cache.size())
477 return false;
478
479 // Parse status phrase
480 _status_phrase_index = index;
481 _status_phrase_size = 0;
482 while (_cache[index] != '\r')
483 {
484 ++_status_phrase_size;
485 ++index;
486 if (index >= _cache.size())
487 return false;
488 }
489 ++index;
490 if ((index >= _cache.size()) || (_cache[index] != '\n'))
491 return false;
492 ++index;
493 if (index >= _cache.size())
494 return false;
495
496 // Parse headers
497 while ((index < _cache.size()) && (index < i))
498 {
499 // Parse header name
500 size_t header_name_index = index;
501 size_t header_name_size = 0;
502 while (_cache[index] != ':')
503 {
504 ++header_name_size;
505 ++index;
506 if (index >= i)
507 break;
508 if (index >= _cache.size())
509 return false;
510 }
511 ++index;
512 if (index >= i)
513 break;
514 if (index >= _cache.size())
515 return false;
516
517 // Skip all prefix space characters
518 while (std::isspace(_cache[index]))
519 {
520 ++index;
521 if (index >= i)
522 break;
523 if (index >= _cache.size())
524 return false;
525 }
526
527 // Parse header value
528 size_t header_value_index = index;
529 size_t header_value_size = 0;
530 while (_cache[index] != '\r')
531 {
532 ++header_value_size;
533 ++index;
534 if (index >= i)
535 break;
536 if (index >= _cache.size())
537 return false;
538 }
539 ++index;
540 if ((index >= _cache.size()) || (_cache[index] != '\n'))
541 return false;
542 ++index;
543 if (index >= _cache.size())
544 return false;
545
546 // Validate header name and value (sometimes value can be empty)
547 if (header_name_size == 0)
548 return false;
549
550 // Add a new header
551 _headers.emplace_back(header_name_index, header_name_size, header_value_index, header_value_size);
552
553 // Try to find the body content length
554 if (CppCommon::StringUtils::CompareNoCase(std::string_view(_cache.data() + header_name_index, header_name_size), "Content-Length"))
555 {
556 _body_length = 0;
557 for (size_t j = header_value_index; j < (header_value_index + header_value_size); ++j)
558 {
559 if ((_cache[j] < '0') || (_cache[j] > '9'))
560 return false;
561 _body_length *= 10;
562 _body_length += _cache[j] - '0';
563 _body_length_provided = true;
564 }
565 }
566 }
567
568 // Reset the error flag
569 _error = false;
570
571 // Update the body index and size
572 _body_index = i + 4;
573 _body_size = _cache.size() - i - 4;
574
575 // Update the parsed cache size
576 _cache_size = _cache.size();
577
578 return true;
579 }
580 }
581
582 // Update the parsed cache size
583 _cache_size = (_cache.size() >= 3) ? (_cache.size() - 3) : 0;
584
585 return false;
586}
587
588bool HTTPResponse::ReceiveBody(const void* buffer, size_t size)
589{
590 // Update HTTP response cache
591 _cache.insert(_cache.end(), (const char*)buffer, (const char*)buffer + size);
592
593 // Update the parsed cache size
594 _cache_size = _cache.size();
595
596 // Update body size
597 _body_size += size;
598
599 // Check if the body length was provided
600 if (_body_length_provided)
601 {
602 // Was the body fully received?
603 if (_body_size >= _body_length)
604 {
605 _body_size = _body_length;
606 return true;
607 }
608 }
609 else
610 {
611 // Check the body content to find the response body end
612 if (_body_size >= 4)
613 {
614 size_t index = _body_index + _body_size - 4;
615
616 // Was the body fully received?
617 if ((_cache[index + 0] == '\r') && (_cache[index + 1] == '\n') && (_cache[index + 2] == '\r') && (_cache[index + 3] == '\n'))
618 {
619 _body_length = _body_size;
620 return true;
621 }
622 }
623 }
624
625 // Body was received partially...
626 return false;
627}
628
629std::string_view HTTPResponse::FastConvert(size_t value, char* buffer, size_t size)
630{
631 size_t index = size;
632 do
633 {
634 buffer[--index] = '0' + (value % 10);
635 value /= 10;
636 }
637 while (value > 0);
638 return std::string_view(buffer + index, size - index);
639}
640
641std::ostream& operator<<(std::ostream& os, const HTTPResponse& response)
642{
643 os << "Status: " << response.status() << std::endl;
644 os << "Status phrase: " << response.status_phrase() << std::endl;
645 os << "Protocol: " << response.protocol() << std::endl;
646 os << "Headers: " << response.headers() << std::endl;
647 for (size_t i = 0; i < response.headers(); ++i)
648 {
649 auto header = response.header(i);
650 os << std::get<0>(header) << ": " << std::get<1>(header) << std::endl;
651 }
652 os << "Body:" << response.body_length() << std::endl;
653 os << response.body() << std::endl;
654 return os;
655}
656
657void HTTPResponse::swap(HTTPResponse& response) noexcept
658{
659 using std::swap;
660 swap(_error, response._error);
661 swap(_status, response._status);
662 swap(_status_phrase_index, response._status_phrase_index);
663 swap(_status_phrase_size, response._status_phrase_size);
664 swap(_protocol_index, response._protocol_index);
665 swap(_protocol_size, response._protocol_size);
666 swap(_headers, response._headers);
667 swap(_body_index, response._body_index);
668 swap(_body_size, response._body_size);
669 swap(_body_length, response._body_length);
670 swap(_body_length_provided, response._body_length_provided);
671 swap(_cache, response._cache);
672 swap(_cache_size, response._cache_size);
673}
674
675} // namespace HTTP
676} // namespace CppServer
HTTPResponse & SetBody(std::string_view body="")
Set the HTTP response body.
HTTPResponse & MakeHeadResponse()
Make HEAD response.
HTTPResponse & MakeOptionsResponse(std::string_view allow="HEAD,GET,POST,PUT,DELETE,OPTIONS,TRACE")
Make OPTIONS response.
HTTPResponse & MakeGetResponse(std::string_view content="", std::string_view content_type="text/plain; charset=UTF-8")
Make GET response.
size_t body_length() const noexcept
Get the HTTP response body length.
HTTPResponse & SetHeader(std::string_view key, std::string_view value)
Set the HTTP response header.
HTTPResponse & MakeOKResponse(int status=200)
Make OK response.
HTTPResponse & SetCookie(std::string_view name, std::string_view value, size_t max_age=86400, std::string_view path="", std::string_view domain="", bool secure=true, bool strict=true, bool http_only=true)
Set the HTTP response cookie.
HTTPResponse & Clear()
Clear the HTTP response cache.
size_t headers() const noexcept
Get the HTTP response headers count.
int status() const noexcept
Get the HTTP response status.
HTTPResponse & SetBodyLength(size_t length)
Set the HTTP response body length.
std::string_view protocol() const noexcept
Get the HTTP response protocol version.
HTTPResponse & MakeErrorResponse(std::string_view content="", std::string_view content_type="text/plain; charset=UTF-8")
Make ERROR response.
HTTPResponse & MakeTraceResponse(std::string_view request)
Make TRACE response.
std::string_view status_phrase() const noexcept
Get the HTTP response status phrase.
HTTPResponse & SetBegin(int status, std::string_view protocol="HTTP/1.1")
Set the HTTP response begin with a given status and protocol.
HTTPResponse & SetContentType(std::string_view extension)
Set the HTTP response content type.
friend void swap(HTTPResponse &response1, HTTPResponse &response2) noexcept
std::tuple< std::string_view, std::string_view > header(size_t i) const noexcept
Get the HTTP response header by index.
std::string_view body() const noexcept
Get the HTTP response body.
HTTP response definition.
std::ostream & operator<<(std::ostream &os, const HTTPRequest &request)
C++ Server project definitions.
Definition asio.h:56