1
1
#include < string>
2
+ #include < string_view>
2
3
#include < unordered_map>
3
- #include < sstream>
4
- #include < vector>
5
-
6
- struct HttpRequest {
7
- std::string method;
8
- std::string path;
9
- std::string version;
10
- std::unordered_map<std::string, std::string> headers;
11
- std::string body;
4
+ #include < optional>
5
+ #include < stdexcept>
6
+ #include < algorithm>
7
+ #include < memory>
8
+
9
+ class HttpParserError : public std ::runtime_error {
10
+ using std::runtime_error::runtime_error;
12
11
};
13
12
14
- class HttpParser {
13
+ class HttpRequest final {
14
+ public:
15
+ using Headers = std::unordered_map<std::string, std::string, std::hash<std::string>, std::equal_to<>>;
16
+
17
+ private:
18
+ std::string method_;
19
+ std::string path_;
20
+ std::string version_;
21
+ Headers headers_;
22
+ std::string body_;
23
+
15
24
public:
16
- HttpRequest parse (const std::string& raw_request) {
25
+ // Use string_view for efficient read-only access
26
+ [[nodiscard]] std::string_view method () const noexcept { return method_; }
27
+ [[nodiscard]] std::string_view path () const noexcept { return path_; }
28
+ [[nodiscard]] std::string_view version () const noexcept { return version_; }
29
+ [[nodiscard]] const Headers& headers () const noexcept { return headers_; }
30
+ [[nodiscard]] std::string_view body () const noexcept { return body_; }
31
+
32
+ // Friend declaration for the parser
33
+ friend class HttpParser ;
34
+ };
35
+
36
+ class HttpParser final {
37
+ public:
38
+ // Use string_view for efficient parsing without copying
39
+ [[nodiscard]] std::optional<HttpRequest> parse (std::string_view raw_request) const {
40
+ try {
41
+ return parse_impl (raw_request);
42
+ } catch (const std::exception &) {
43
+ return std::nullopt;
44
+ }
45
+ }
46
+
47
+ private:
48
+ [[nodiscard]] HttpRequest parse_impl (std::string_view raw_request) const {
17
49
HttpRequest request;
18
- std::istringstream stream (raw_request) ;
19
- std::string line ;
50
+ size_t pos = 0 ;
51
+ size_t end_pos ;
20
52
21
- if (std::getline (stream, line)) {
22
- parseRequestLine (line, request);
53
+ // Parse request line
54
+ if ((end_pos = raw_request.find (" \r\n " , pos)) == std::string_view::npos) {
55
+ throw HttpParserError (" Invalid request line" );
23
56
}
57
+ parse_request_line (raw_request.substr (pos, end_pos - pos), request);
58
+ pos = end_pos + 2 ;
59
+
60
+ // Parse headers
61
+ while (pos < raw_request.size ()) {
62
+ end_pos = raw_request.find (" \r\n " , pos);
63
+ if (end_pos == std::string_view::npos) {
64
+ throw HttpParserError (" Invalid header format" );
65
+ }
24
66
25
- while (std::getline (stream, line) && line != " \r " ) {
26
- if (line.empty ()) break ;
27
- parseHeader (line, request);
67
+ // Check for end of headers
68
+ if (pos == end_pos) {
69
+ pos += 2 ;
70
+ break ;
71
+ }
72
+
73
+ parse_header (raw_request.substr (pos, end_pos - pos), request);
74
+ pos = end_pos + 2 ;
28
75
}
29
76
30
- std::string body;
31
- while ( std::getline (stream, line )) {
32
- body += line + " \n " ;
77
+ // Parse body
78
+ if (pos < raw_request. size ( )) {
79
+ request. body_ = std::string (raw_request. substr (pos)) ;
33
80
}
34
- request.body = body;
35
81
36
82
return request;
37
83
}
38
84
39
- private:
40
- void parseRequestLine (const std::string& line, HttpRequest& request) {
41
- std::istringstream lineStream (line);
42
- lineStream >> request.method >> request.path >> request.version ;
85
+ static void parse_request_line (std::string_view line, HttpRequest& request) {
86
+ size_t method_end = line.find (' ' );
87
+ if (method_end == std::string_view::npos) {
88
+ throw HttpParserError (" Invalid request line format" );
89
+ }
90
+
91
+ size_t path_end = line.find (' ' , method_end + 1 );
92
+ if (path_end == std::string_view::npos) {
93
+ throw HttpParserError (" Invalid request line format" );
94
+ }
95
+
96
+ request.method_ = std::string (line.substr (0 , method_end));
97
+ request.path_ = std::string (line.substr (method_end + 1 , path_end - method_end - 1 ));
98
+ request.version_ = std::string (line.substr (path_end + 1 ));
99
+
100
+ // Validate HTTP method
101
+ if (!is_valid_method (request.method_ )) {
102
+ throw HttpParserError (" Invalid HTTP method" );
103
+ }
43
104
}
44
105
45
- void parseHeader (const std::string& line, HttpRequest& request) {
46
- size_t colonPos = line.find (' :' );
47
- if (colonPos == std::string::npos) return ;
106
+ static void parse_header (std::string_view line, HttpRequest& request) {
107
+ size_t colon_pos = line.find (' :' );
108
+ if (colon_pos == std::string_view::npos) {
109
+ throw HttpParserError (" Invalid header format" );
110
+ }
111
+
112
+ std::string_view key = line.substr (0 , colon_pos);
113
+ std::string_view value = line.substr (colon_pos + 1 );
114
+
115
+ // Trim whitespace
116
+ value = trim (value);
117
+
118
+ if (!key.empty () && !value.empty ()) {
119
+ request.headers_ .emplace (
120
+ std::string (key),
121
+ std::string (value)
122
+ );
123
+ }
124
+ }
48
125
49
- std::string key = line.substr (0 , colonPos);
50
- std::string value = line.substr (colonPos + 1 );
51
- value.erase (0 , value.find_first_not_of (" " ));
52
- value.erase (value.find_last_not_of (" \r " ) + 1 );
126
+ [[nodiscard]] static std::string_view trim (std::string_view str) noexcept {
127
+ const auto first = str.find_first_not_of (" \t\r\n " );
128
+ if (first == std::string_view::npos) return {};
129
+
130
+ const auto last = str.find_last_not_of (" \t\r\n " );
131
+ return str.substr (first, last - first + 1 );
132
+ }
53
133
54
- request.headers [key] = value;
134
+ [[nodiscard]] static bool is_valid_method (const std::string& method) noexcept {
135
+ static const auto valid_methods = std::array{
136
+ " GET" , " POST" , " PUT" , " DELETE" , " HEAD" ,
137
+ " OPTIONS" , " PATCH" , " TRACE" , " CONNECT"
138
+ };
139
+
140
+ return std::find (valid_methods.begin (), valid_methods.end (), method)
141
+ != valid_methods.end ();
55
142
}
56
143
};
0 commit comments