-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathreverse_proxy_handler.cc
331 lines (282 loc) · 12.6 KB
/
reverse_proxy_handler.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
#include <boost/algorithm/string/replace.hpp>
#include <boost/asio.hpp>
#include <boost/tokenizer.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/bind.hpp>
#include <iostream>
#include "reverse_proxy_handler.h"
using boost::asio::ip::tcp;
// Helper function for tokenizing HTTP requests
// From: https://github.com/UCLA-CS130/Mr.-Robot-et-al./blob/c9b064c68cd4bc1ae6b5c012db59eae9cb8b946d/request.cc#L7
boost::tokenizer<boost::char_separator<char>>
tokenGenerator(const std::string s, const char* sep) {
// Tokenize the rest_lines
boost::char_separator<char> separator(sep);
boost::tokenizer<boost::char_separator<char>> tokens(s, separator);
return tokens;
}
// Initializes the handler. Returns OK if successful
// uri_prefix is the value in the config file that this handler will run for
// config is the contents of the child block for this handler ONLY
RequestHandler::Status ReverseProxyHandler::Init(const std::string& uri_prefix,
const NginxConfig& config) {
// Check config child block for settings
for (size_t i = 0; i < config.statements.size(); i++) {
// Only attempt to read statements with a first token we care about
if (config.statements[i]->tokens.size() > 0) {
if (config.statements[i]->tokens[0] == "remote_host") {
// remote_host must have 2 tokens
if (config.statements[i]->tokens.size() > 1) {
remote_host = config.statements[i]->tokens[1];
} else {
printf("\"remote_host\" statement in config needs more tokens\n");
}
}
if (config.statements[i]->tokens[0] == "remote_port") {
// remote_port must have 2 tokens
if (config.statements[i]->tokens.size() > 1) {
remote_port = config.statements[i]->tokens[1];
} else {
printf("\"remote_port\" statement in config needs more tokens\n");
}
}
}
}
if (!remote_host.empty() && !remote_port.empty()) {
original_uri_prefix = uri_prefix;
return RequestHandler::OK;
} else {
printf("Missing remote_port or remote_host provided to ReverseProxyHandler\n");
return RequestHandler::Error;
}
}
// Helper for 302-redirects
// Returns the value of the Location header,
// e.g., "www.exmple.com" for "Location: www.example.com"
std::string ReverseProxyHandler::getLocationHeaderValue(const std::string& response_buffer_string) {
// Grab the Location header line
int start_location = response_buffer_string.find("Location:");
int end_location = response_buffer_string.find_first_of("\r\n", start_location);
std::string location_line = response_buffer_string.substr(start_location, end_location);
boost::tokenizer<boost::char_separator<char>> tokens
= tokenGenerator(location_line, " ");
// Split the location line to get the value
std::string location_value;
int i = 0;
for (auto cur_token = tokens.begin();
cur_token != tokens.end();
cur_token++,
i++) {
if (i == 1) {
location_value = *cur_token;
break;
}
}
return location_value;
}
// Helper for parsing response code from origin responses
std::string ReverseProxyHandler::getRemoteResponseCode(const std::string& response_buffer_string) {
// Response has the form:
// HTTP/1.0 200 OK
// Content-Length: 3497
// Content-Type: text/plain
//
// <body contents>
//
// We check just the first line
size_t end_first_line = response_buffer_string.find_first_of("\r\n");
std::string first_line = response_buffer_string.substr(0, end_first_line);
// TODO: Move this response-code checking into a helper function
// Tokenize and check second token for ResponseCode
// Based on: https://github.com/UCLA-CS130/Mr.-Robot-et-al./blob/c9b064c68cd4bc1ae6b5c012db59eae9cb8b946d/request.cc#L46
boost::tokenizer<boost::char_separator<char>> tokens
= tokenGenerator(first_line, " ");
// Handle response code from remote_host.
// remote_host's ResponseCode => our ResponseCode cases:
//
// 200 => 200
// 302 => fetch-loop to 200 or 404 (upon not-found or max-retries)
// 404 => 404
// 500 => 404
std::string return_response_code;
int i = 0;
for (auto cur_token = tokens.begin();
cur_token != tokens.end();
cur_token++,
i++) {
if (i == 1) {
std::string remote_response_code = *cur_token;
// std::cerr << "remote_response_code: " << remote_response_code << std::endl;
if (remote_response_code == "200") {
return_response_code = "200";
} else if (remote_response_code == "302") {
return_response_code = "302";
} else if (remote_response_code == "404") {
return_response_code = "404";
} else if (remote_response_code == "500") {
return_response_code = "404";
} else {
printf("ReverseProxyHandler does not understand the remote_host's ResponseCode\n");
return_response_code = "500";
}
break;
}
}
return return_response_code;
}
// Helper for sending proxy requests
// Returns the string representation of the response received from the remote_host
// Origin is the remote_host that is the original source of the content
// we're serving on their behalf
std::string ReverseProxyHandler::sendRequestToOrigin(Request request, std::string remote_uri) {
boost::asio::io_service io_service;
// Some set up for ssl;
// Based off of:
// http://www.boost.org/doc/libs/1_47_0/doc/html/boost_asio/example/ssl/client.cpp
// http://www.boost.org/doc/libs/1_47_0/doc/html/boost_asio/overview/ssl.html
boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23);
ctx.set_default_verify_paths();
boost::asio::ssl::stream<tcp::socket> socket(io_service, ctx);
tcp::resolver resolver(io_service);
// Construct new request to send to remote host
// Example: Modify request from: /reverse_proxy/static/file1.txt
// to: /static/file1.txt
// Note: exception to this rule: when prefix is "/" (hence, the check)
if (original_uri_prefix != "/") {
remote_uri.erase(0, remote_uri.find("/", 1));
}
if (remote_uri.empty()) {
remote_uri = "/";
}
request.SetUri(remote_uri);
// Use resolver to handle DNS lookup if query is not an IP address
boost::system::error_code ec;
std::string new_response = "";
bool got_302 = false;
do {
// Avoid "host not found" error
// See: https://stackoverflow.com/questions/12542460/boost-asio-host-not-found-authorative
tcp::resolver::query query(remote_host,
remote_port,
boost::asio::ip::resolver_query_base::numeric_service);
// If HTTPS, verify certifcate of remote host
if (std::stoi(remote_port) == 443) {
// socket.set_verify_mode(boost::asio::ssl::verify_peer);
// socket.set_verify_callback(boost::asio::ssl::rfc2818_verification(remote_host));
}
// A hostname could resolve to multiple endpoints to try;
// connect() will try all of them
// See: http://www.boost.org/doc/libs/1_62_0/boost/asio/connect.hpp
boost::asio::connect(socket.next_layer(), resolver.resolve(query), ec);
if (ec == boost::asio::error::not_found) {
printf("Reverse proxy connection attempt to remote host failed. Check hostname and port number.\n");
return "RequestHandler::Error";
}
// If HTTPS, need to do SSL handshake
if (std::stoi(remote_port) == 443) {
socket.handshake(boost::asio::ssl::stream_base::client, ec);
if (!ec) {
std::cout << "Handshake succeeded" << std::endl;
} else {
std::cout << "Handshake failed!" << std::endl;
return "RequestHandler::Error";
}
}
std::string remote_request = request.raw_request();
size_t host_pos = remote_request.find("Host");
size_t host_end = remote_request.find("\r\n", host_pos);
size_t host_len = host_end - host_pos;
remote_request.replace(host_pos, host_len, "Host: " + remote_host);
// Not done on other ports otherwise could break other tests
if (std::stoi(remote_port) == 443) {
//make it so we look for eof
size_t con_pos = remote_request.find("Connection");
if (con_pos != std::string::npos) {
size_t con_end = remote_request.find("\r\n", con_pos);
size_t con_len = con_end - con_pos;
remote_request.replace(con_pos, con_len, "Connection: close");
}
// Replace HTTP/1.1 by HTTP/1.0 so server shouldn't send us transfer chunk encoding
size_t http_pos = remote_request.find("HTTP/1");
size_t http_end = remote_request.find("\r\n", http_pos);
size_t http_len = http_end - http_pos;
remote_request.replace(http_pos, http_len, "HTTP/1.0");
}
// socket.next_layer().send(boost::asio::buffer(remote_request));
if (std::stoi(remote_port) == 443) {
boost::asio::write(socket, boost::asio::buffer(remote_request));
} else {
boost::asio::write(socket.next_layer(), boost::asio::buffer(remote_request));
}
const int MAX_BUFFER_LENGTH = 1024;
size_t bytes_received = 0;
do {
char response_buffer[MAX_BUFFER_LENGTH];
// bytes_received = socket.next_layer().receive(boost::asio::buffer(response_buffer), {}, ec);
if (std::stoi(remote_port) == 443) {
bytes_received = boost::asio::read(socket, boost::asio::buffer(response_buffer), ec);
} else {
bytes_received = boost::asio::read(socket.next_layer(), boost::asio::buffer(response_buffer), ec);
}
new_response.append(response_buffer, bytes_received);
} while (!ec);
if (new_response.find("HTTP/1.1 302 Found") != std::string::npos) {
got_302 = true;
size_t location = new_response.find("Location");
size_t www = new_response.find("www", location);
size_t slash = new_response.find("/", www);
remote_host = new_response.substr(www, slash - www);
new_response = "";
}
else {
got_302 = false;
}
} while (got_302);
// Checking ec set when reading response from remote host
switch (ec.value()) {
case boost::asio::error::eof:
case boost::system::errc::success:
// SSL error for SSL_R_SHORT_READ
// Expected behavior if handshake works according to:
// http://stackoverflow.com/questions/8467277/boostasio-and-async-ssl-stream-how-to-detect-end-of-data-connection-close
case 335544539:
break;
default:
return "RequestHandler::Error";
}
return new_response;
}
// Helper for editing the path of relative URIs
// When HTML elements require resources with a path relative to their origin, for
// example a stylesheet for www.foobar.com/home which has the path:
// 'href="../css/style.css"', we need to change the '="../"' to '="/home/../"'.
void ReverseProxyHandler::rerouteRelativeUris(std::string& response_body) {
if (original_uri_prefix != "/") {
std::string new_href = "href=\"" + original_uri_prefix + "/";
boost::replace_all(response_body, "href=\"/", new_href);
std::string new_src = "src=\"" + original_uri_prefix + "/";
boost::replace_all(response_body, "src=\"/", new_src);
}
}
// Handles an HTTP request, and generates a response. Returns a response code
// indicating success or failure condition. If ResponseCode is not OK, the
// contents of the response object are undefined, and the server will return
// HTTP code 500
RequestHandler::Status ReverseProxyHandler::HandleRequest(const Request& request,
Response* response) {
std::string response_buffer_string = sendRequestToOrigin(request, request.uri());
if (response_buffer_string == "RequestHandler::Error") {
response->SetStatus(Response::not_found);
return RequestHandler::Error;
}
std::string return_response_code = getRemoteResponseCode(response_buffer_string);
if (return_response_code == "200") {
response->SetStatus(Response::ok);
} else if (return_response_code == "404") {
response->SetStatus(Response::not_found);
} else if (return_response_code == "500") {
response->SetStatus(Response::internal_server_error);
}
response->SetFullResponse(response_buffer_string);
return RequestHandler::OK;
}