CppServer  1.0.4.0
C++ Server Library
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 
18 namespace CppServer {
19 namespace HTTP {
20 
21 const 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 
92 std::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 
122 HTTPResponse& 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 
207 HTTPResponse& 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 
239 HTTPResponse& 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 
249 HTTPResponse& 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 
273 HTTPResponse& 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 
322 HTTPResponse& 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();
362  SetBegin(status);
363  SetBody();
364  return *this;
365 }
366 
367 HTTPResponse& HTTPResponse::MakeErrorResponse(int status, std::string_view content, std::string_view content_type)
368 {
369  Clear();
370  SetBegin(status);
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 
385 HTTPResponse& 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 
413 bool HTTPResponse::IsPendingHeader() const
414 {
415  return (!_error && (_body_index == 0));
416 }
417 
418 bool HTTPResponse::IsPendingBody() const
419 {
420  return (!_error && (_body_index > 0) && (_body_size > 0));
421 }
422 
423 bool 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 
588 bool 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 
629 std::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 
641 std::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 
657 void 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.
Definition: http_response.h:79
HTTPResponse & SetHeader(std::string_view key, std::string_view value)
Set the HTTP response header.
void swap(HTTPResponse &response) noexcept
Swap two instances.
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.
Definition: http_response.h:73
int status() const noexcept
Get the HTTP response status.
Definition: http_response.h:67
HTTPResponse & SetBodyLength(size_t length)
Set the HTTP response body length.
std::string_view protocol() const noexcept
Get the HTTP response protocol version.
Definition: http_response.h:71
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.
Definition: http_response.h:69
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.
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.
Definition: http_response.h:77
HTTP response definition.
std::ostream & operator<<(std::ostream &os, const HTTPRequest &request)
C++ Server project definitions.
Definition: asio.h:56