1+ #pragma once
2+
3+ #include < string>
4+ #include < system_error>
5+ #include < utility>
6+ #include < vector>
7+ #include < algorithm>
8+
9+ namespace netlib ::http {
10+
11+ using http_header_entry = std::pair<std::string, std::string>;
12+ using http_headers = std::vector<http_header_entry>;
13+
14+ struct http_response {
15+ http_headers headers;
16+ uint32_t response_code;
17+ std::pair<uint32_t , uint32_t > version;
18+ std::string body;
19+ inline std::error_condition from_raw_response (const std::string& raw_response) {
20+ // a very rudimentary http response parser
21+ if (raw_response.empty ()) {
22+ return std::errc::no_message;
23+ }
24+
25+ /* strategy:
26+ * split into multiple (at least two) parts, delimited by \r\n\r\n"
27+ * within the first part:
28+ * first, parse the status line
29+ * second, parse the header fields, until we arrive at an empty line (only CR LF)
30+ * last, an optional body
31+ * then, for the rest of the parts, concat into body
32+ */
33+
34+ auto split = [](const std::string& str, const std::string& delimiter) -> std::vector<std::string> {
35+ std::vector<std::string> split_tokens;
36+ std::size_t start;
37+ std::size_t end = 0 ;
38+ while ((start = str.find_first_not_of (delimiter, end)) != std::string::npos)
39+ {
40+ end = str.find (delimiter, start);
41+ split_tokens.push_back (str.substr (start, end - start));
42+ }
43+ return split_tokens;
44+ };
45+
46+ std::vector<std::string> header_body_split = split (raw_response, " \r\n\r\n " );
47+ // split header part of response into response_header_lines
48+ std::vector<std::string> response_header_lines = split (header_body_split.front (), " \r\n " );
49+ // first line should start with "HTTP"
50+ if (!response_header_lines.front ().starts_with (" HTTP" )) {
51+ return std::errc::result_out_of_range;
52+ }
53+ // attempt to parse status line
54+ // split into parts by space
55+ auto status_parts = split (response_header_lines.front (), " " );
56+ if (status_parts.size () < 3 ) {
57+ return std::errc::bad_message;
58+ }
59+ // parse "HTTP/x.x"
60+ auto version_parts = split (status_parts.front (), " /" );
61+ if (version_parts.size () != 2 ) {
62+ return std::errc::bad_message;
63+ }
64+ // parse "x.x"
65+ auto version_components = split (version_parts.back (), " ." );
66+ version.first = std::stoi (version_components.front ());
67+ version.second = std::stoi (version_components.back ());
68+ // parse response code
69+ response_code = std::stoi (status_parts[1 ]);
70+ // there can be an optional code description in the first line, but we ignore that here
71+ // parse the response header lines until the end
72+ // start at second line, first is status
73+ std::for_each (response_header_lines.begin (), response_header_lines.end (), [&](const std::string& header_component){
74+ auto component_parts = split (header_component, " :" );
75+ if (component_parts.size () == 2 ) {
76+ headers.emplace_back (component_parts.front (), component_parts.back ());
77+ }
78+ });
79+
80+ // now, take the body part(s) and concat them
81+ std::for_each (header_body_split.begin () + 1 , header_body_split.end (), [&](const std::string& body_line){
82+ body += body_line;
83+ });
84+
85+ return {};
86+
87+ };
88+ };
89+
90+ }
0 commit comments