1 | /* |
2 | * IXHttp.cpp |
3 | * Author: Benjamin Sergeant |
4 | * Copyright (c) 2019 Machine Zone, Inc. All rights reserved. |
5 | */ |
6 | |
7 | #include "IXHttp.h" |
8 | |
9 | #include "IXCancellationRequest.h" |
10 | #include "IXGzipCodec.h" |
11 | #include "IXSocket.h" |
12 | #include <sstream> |
13 | #include <vector> |
14 | |
15 | namespace ix |
16 | { |
17 | std::string Http::trim(const std::string& str) |
18 | { |
19 | std::string out; |
20 | for (auto c : str) |
21 | { |
22 | if (c != ' ' && c != '\n' && c != '\r') |
23 | { |
24 | out += c; |
25 | } |
26 | } |
27 | |
28 | return out; |
29 | } |
30 | |
31 | std::pair<std::string, int> Http::parseStatusLine(const std::string& line) |
32 | { |
33 | // Request-Line = Method SP Request-URI SP HTTP-Version CRLF |
34 | std::string token; |
35 | std::stringstream tokenStream(line); |
36 | std::vector<std::string> tokens; |
37 | |
38 | // Split by ' ' |
39 | while (std::getline(tokenStream, token, ' ')) |
40 | { |
41 | tokens.push_back(token); |
42 | } |
43 | |
44 | std::string httpVersion; |
45 | if (tokens.size() >= 1) |
46 | { |
47 | httpVersion = trim(tokens[0]); |
48 | } |
49 | |
50 | int statusCode = -1; |
51 | if (tokens.size() >= 2) |
52 | { |
53 | std::stringstream ss; |
54 | ss << trim(tokens[1]); |
55 | ss >> statusCode; |
56 | } |
57 | |
58 | return std::make_pair(httpVersion, statusCode); |
59 | } |
60 | |
61 | std::tuple<std::string, std::string, std::string> Http::parseRequestLine( |
62 | const std::string& line) |
63 | { |
64 | // Request-Line = Method SP Request-URI SP HTTP-Version CRLF |
65 | std::string token; |
66 | std::stringstream tokenStream(line); |
67 | std::vector<std::string> tokens; |
68 | |
69 | // Split by ' ' |
70 | while (std::getline(tokenStream, token, ' ')) |
71 | { |
72 | tokens.push_back(token); |
73 | } |
74 | |
75 | std::string method; |
76 | if (tokens.size() >= 1) |
77 | { |
78 | method = trim(tokens[0]); |
79 | } |
80 | |
81 | std::string requestUri; |
82 | if (tokens.size() >= 2) |
83 | { |
84 | requestUri = trim(tokens[1]); |
85 | } |
86 | |
87 | std::string httpVersion; |
88 | if (tokens.size() >= 3) |
89 | { |
90 | httpVersion = trim(tokens[2]); |
91 | } |
92 | |
93 | return std::make_tuple(method, requestUri, httpVersion); |
94 | } |
95 | |
96 | std::tuple<bool, std::string, HttpRequestPtr> Http::parseRequest( |
97 | std::unique_ptr<Socket>& socket, int timeoutSecs) |
98 | { |
99 | HttpRequestPtr httpRequest; |
100 | |
101 | std::atomic<bool> requestInitCancellation(false); |
102 | |
103 | auto isCancellationRequested = |
104 | makeCancellationRequestWithTimeout(timeoutSecs, requestInitCancellation); |
105 | |
106 | // Read first line |
107 | auto lineResult = socket->readLine(isCancellationRequested); |
108 | auto lineValid = lineResult.first; |
109 | auto line = lineResult.second; |
110 | |
111 | if (!lineValid) |
112 | { |
113 | return std::make_tuple(false, "Error reading HTTP request line" , httpRequest); |
114 | } |
115 | |
116 | // Parse request line (GET /foo HTTP/1.1\r\n) |
117 | auto requestLine = Http::parseRequestLine(line); |
118 | auto method = std::get<0>(requestLine); |
119 | auto uri = std::get<1>(requestLine); |
120 | auto httpVersion = std::get<2>(requestLine); |
121 | |
122 | // Retrieve and validate HTTP headers |
123 | auto result = parseHttpHeaders(socket, isCancellationRequested); |
124 | auto = result.first; |
125 | auto = result.second; |
126 | |
127 | if (!headersValid) |
128 | { |
129 | return std::make_tuple(false, "Error parsing HTTP headers" , httpRequest); |
130 | } |
131 | |
132 | std::string body; |
133 | if (headers.find("Content-Length" ) != headers.end()) |
134 | { |
135 | int contentLength = 0; |
136 | try |
137 | { |
138 | contentLength = std::stoi(headers["Content-Length" ]); |
139 | } |
140 | catch (const std::exception&) |
141 | { |
142 | return std::make_tuple( |
143 | false, "Error parsing HTTP Header 'Content-Length'" , httpRequest); |
144 | } |
145 | |
146 | if (contentLength < 0) |
147 | { |
148 | return std::make_tuple( |
149 | false, "Error: 'Content-Length' should be a positive integer" , httpRequest); |
150 | } |
151 | |
152 | auto res = socket->readBytes(contentLength, nullptr, nullptr, isCancellationRequested); |
153 | if (!res.first) |
154 | { |
155 | return std::make_tuple( |
156 | false, std::string("Error reading request: " ) + res.second, httpRequest); |
157 | } |
158 | body = res.second; |
159 | } |
160 | |
161 | // If the content was compressed with gzip, decode it |
162 | if (headers["Content-Encoding" ] == "gzip" ) |
163 | { |
164 | #ifdef IXWEBSOCKET_USE_ZLIB |
165 | std::string decompressedPayload; |
166 | if (!gzipDecompress(body, decompressedPayload)) |
167 | { |
168 | return std::make_tuple( |
169 | false, std::string("Error during gzip decompression of the body" ), httpRequest); |
170 | } |
171 | body = decompressedPayload; |
172 | #else |
173 | std::string errorMsg("ixwebsocket was not compiled with gzip support on" ); |
174 | return std::make_tuple(false, errorMsg, httpRequest); |
175 | #endif |
176 | } |
177 | |
178 | httpRequest = std::make_shared<HttpRequest>(uri, method, httpVersion, body, headers); |
179 | return std::make_tuple(true, "" , httpRequest); |
180 | } |
181 | |
182 | bool Http::sendResponse(HttpResponsePtr response, std::unique_ptr<Socket>& socket) |
183 | { |
184 | // Write the response to the socket |
185 | std::stringstream ss; |
186 | ss << "HTTP/1.1 " ; |
187 | ss << response->statusCode; |
188 | ss << " " ; |
189 | ss << response->description; |
190 | ss << "\r\n" ; |
191 | |
192 | if (!socket->writeBytes(ss.str(), nullptr)) |
193 | { |
194 | return false; |
195 | } |
196 | |
197 | // Write headers |
198 | ss.str("" ); |
199 | ss << "Content-Length: " << response->body.size() << "\r\n" ; |
200 | for (auto&& it : response->headers) |
201 | { |
202 | ss << it.first << ": " << it.second << "\r\n" ; |
203 | } |
204 | ss << "\r\n" ; |
205 | |
206 | if (!socket->writeBytes(ss.str(), nullptr)) |
207 | { |
208 | return false; |
209 | } |
210 | |
211 | return response->body.empty() ? true : socket->writeBytes(response->body, nullptr); |
212 | } |
213 | } // namespace ix |
214 | |