test_http_server.cc revision 39910dcd1d68987ccee7c3031dc269233a8490bb
1// 2// Copyright (C) 2012 The Android Open Source Project 3// 4// Licensed under the Apache License, Version 2.0 (the "License"); 5// you may not use this file except in compliance with the License. 6// You may obtain a copy of the License at 7// 8// http://www.apache.org/licenses/LICENSE-2.0 9// 10// Unless required by applicable law or agreed to in writing, software 11// distributed under the License is distributed on an "AS IS" BASIS, 12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13// See the License for the specific language governing permissions and 14// limitations under the License. 15// 16 17// This file implements a simple HTTP server. It can exhibit odd behavior 18// that's useful for testing. For example, it's useful to test that 19// the updater can continue a connection if it's dropped, or that it 20// handles very slow data transfers. 21 22// To use this, simply make an HTTP connection to localhost:port and 23// GET a url. 24 25#include <err.h> 26#include <errno.h> 27#include <fcntl.h> 28#include <inttypes.h> 29#include <netinet/in.h> 30#include <signal.h> 31#include <stdio.h> 32#include <stdlib.h> 33#include <string.h> 34#include <sys/socket.h> 35#include <sys/stat.h> 36#include <sys/types.h> 37#include <unistd.h> 38 39#include <algorithm> 40#include <string> 41#include <vector> 42 43#include <base/logging.h> 44#include <base/posix/eintr_wrapper.h> 45#include <base/strings/string_split.h> 46#include <base/strings/string_util.h> 47#include <base/strings/stringprintf.h> 48 49#include "update_engine/common/http_common.h" 50 51 52// HTTP end-of-line delimiter; sorry, this needs to be a macro. 53#define EOL "\r\n" 54 55using std::string; 56using std::vector; 57 58 59namespace chromeos_update_engine { 60 61static const char* kListeningMsgPrefix = "listening on port "; 62 63enum { 64 RC_OK = 0, 65 RC_BAD_ARGS, 66 RC_ERR_READ, 67 RC_ERR_SETSOCKOPT, 68 RC_ERR_BIND, 69 RC_ERR_LISTEN, 70 RC_ERR_GETSOCKNAME, 71 RC_ERR_REPORT, 72}; 73 74struct HttpRequest { 75 HttpRequest() 76 : start_offset(0), end_offset(0), return_code(kHttpResponseOk) {} 77 string host; 78 string url; 79 off_t start_offset; 80 off_t end_offset; // non-inclusive, zero indicates unspecified. 81 HttpResponseCode return_code; 82}; 83 84bool ParseRequest(int fd, HttpRequest* request) { 85 string headers; 86 do { 87 char buf[1024]; 88 ssize_t r = read(fd, buf, sizeof(buf)); 89 if (r < 0) { 90 perror("read"); 91 exit(RC_ERR_READ); 92 } 93 headers.append(buf, r); 94 } while (!base::EndsWith(headers, EOL EOL, true)); 95 96 LOG(INFO) << "got headers:\n--8<------8<------8<------8<----\n" 97 << headers 98 << "\n--8<------8<------8<------8<----"; 99 100 // Break header into lines. 101 vector<string> lines; 102 base::SplitStringUsingSubstr( 103 headers.substr(0, headers.length() - strlen(EOL EOL)), EOL, &lines); 104 105 // Decode URL line. 106 vector<string> terms; 107 base::SplitStringAlongWhitespace(lines[0], &terms); 108 CHECK_EQ(terms.size(), static_cast<vector<string>::size_type>(3)); 109 CHECK_EQ(terms[0], "GET"); 110 request->url = terms[1]; 111 LOG(INFO) << "URL: " << request->url; 112 113 // Decode remaining lines. 114 size_t i; 115 for (i = 1; i < lines.size(); i++) { 116 vector<string> terms; 117 base::SplitStringAlongWhitespace(lines[i], &terms); 118 119 if (terms[0] == "Range:") { 120 CHECK_EQ(terms.size(), static_cast<vector<string>::size_type>(2)); 121 string &range = terms[1]; 122 LOG(INFO) << "range attribute: " << range; 123 CHECK(base::StartsWithASCII(range, "bytes=", true) && 124 range.find('-') != string::npos); 125 request->start_offset = atoll(range.c_str() + strlen("bytes=")); 126 // Decode end offset and increment it by one (so it is non-inclusive). 127 if (range.find('-') < range.length() - 1) 128 request->end_offset = atoll(range.c_str() + range.find('-') + 1) + 1; 129 request->return_code = kHttpResponsePartialContent; 130 string tmp_str = base::StringPrintf("decoded range offsets: " 131 "start=%jd end=", 132 (intmax_t)request->start_offset); 133 if (request->end_offset > 0) 134 base::StringAppendF(&tmp_str, "%jd (non-inclusive)", 135 (intmax_t)request->end_offset); 136 else 137 base::StringAppendF(&tmp_str, "unspecified"); 138 LOG(INFO) << tmp_str; 139 } else if (terms[0] == "Host:") { 140 CHECK_EQ(terms.size(), static_cast<vector<string>::size_type>(2)); 141 request->host = terms[1]; 142 LOG(INFO) << "host attribute: " << request->host; 143 } else { 144 LOG(WARNING) << "ignoring HTTP attribute: `" << lines[i] << "'"; 145 } 146 } 147 148 return true; 149} 150 151string Itoa(off_t num) { 152 char buf[100] = {0}; 153 snprintf(buf, sizeof(buf), "%" PRIi64, num); 154 return buf; 155} 156 157// Writes a string into a file. Returns total number of bytes written or -1 if a 158// write error occurred. 159ssize_t WriteString(int fd, const string& str) { 160 const size_t total_size = str.size(); 161 size_t remaining_size = total_size; 162 char const *data = str.data(); 163 164 while (remaining_size) { 165 ssize_t written = write(fd, data, remaining_size); 166 if (written < 0) { 167 perror("write"); 168 LOG(INFO) << "write failed"; 169 return -1; 170 } 171 data += written; 172 remaining_size -= written; 173 } 174 175 return total_size; 176} 177 178// Writes the headers of an HTTP response into a file. 179ssize_t WriteHeaders(int fd, const off_t start_offset, const off_t end_offset, 180 HttpResponseCode return_code) { 181 ssize_t written = 0, ret; 182 183 ret = WriteString(fd, 184 string("HTTP/1.1 ") + Itoa(return_code) + " " + 185 GetHttpResponseDescription(return_code) + 186 EOL 187 "Content-Type: application/octet-stream" EOL); 188 if (ret < 0) 189 return -1; 190 written += ret; 191 192 // Compute content legnth. 193 const off_t content_length = end_offset - start_offset;; 194 195 // A start offset that equals the end offset indicates that the response 196 // should contain the full range of bytes in the requested resource. 197 if (start_offset || start_offset == end_offset) { 198 ret = WriteString(fd, 199 string("Accept-Ranges: bytes" EOL 200 "Content-Range: bytes ") + 201 Itoa(start_offset == end_offset ? 0 : start_offset) + 202 "-" + Itoa(end_offset - 1) + "/" + Itoa(end_offset) + 203 EOL); 204 if (ret < 0) 205 return -1; 206 written += ret; 207 } 208 209 ret = WriteString(fd, string("Content-Length: ") + Itoa(content_length) + 210 EOL EOL); 211 if (ret < 0) 212 return -1; 213 written += ret; 214 215 return written; 216} 217 218// Writes a predetermined payload of lines of ascending bytes to a file. The 219// first byte of output is appropriately offset with respect to the request line 220// length. Returns the number of successfully written bytes. 221size_t WritePayload(int fd, const off_t start_offset, const off_t end_offset, 222 const char first_byte, const size_t line_len) { 223 CHECK_LE(start_offset, end_offset); 224 CHECK_GT(line_len, static_cast<size_t>(0)); 225 226 LOG(INFO) << "writing payload: " << line_len << "-byte lines starting with `" 227 << first_byte << "', offset range " << start_offset << " -> " 228 << end_offset; 229 230 // Populate line of ascending characters. 231 string line; 232 line.reserve(line_len); 233 char byte = first_byte; 234 size_t i; 235 for (i = 0; i < line_len; i++) 236 line += byte++; 237 238 const size_t total_len = end_offset - start_offset; 239 size_t remaining_len = total_len; 240 bool success = true; 241 242 // If start offset is not aligned with line boundary, output partial line up 243 // to the first line boundary. 244 size_t start_modulo = start_offset % line_len; 245 if (start_modulo) { 246 string partial = line.substr(start_modulo, remaining_len); 247 ssize_t ret = WriteString(fd, partial); 248 if ((success = (ret >= 0 && (size_t) ret == partial.length()))) 249 remaining_len -= partial.length(); 250 } 251 252 // Output full lines up to the maximal line boundary below the end offset. 253 while (success && remaining_len >= line_len) { 254 ssize_t ret = WriteString(fd, line); 255 if ((success = (ret >= 0 && (size_t) ret == line_len))) 256 remaining_len -= line_len; 257 } 258 259 // Output a partial line up to the end offset. 260 if (success && remaining_len) { 261 string partial = line.substr(0, remaining_len); 262 ssize_t ret = WriteString(fd, partial); 263 if ((success = (ret >= 0 && (size_t) ret == partial.length()))) 264 remaining_len -= partial.length(); 265 } 266 267 return (total_len - remaining_len); 268} 269 270// Write default payload lines of the form 'abcdefghij'. 271inline size_t WritePayload(int fd, const off_t start_offset, 272 const off_t end_offset) { 273 return WritePayload(fd, start_offset, end_offset, 'a', 10); 274} 275 276// Send an empty response, then kill the server. 277void HandleQuit(int fd) { 278 WriteHeaders(fd, 0, 0, kHttpResponseOk); 279 LOG(INFO) << "pid(" << getpid() << "): HTTP server exiting ..."; 280 exit(RC_OK); 281} 282 283 284// Generates an HTTP response with payload corresponding to requested offsets 285// and length. Optionally, truncate the payload at a given length and add a 286// pause midway through the transfer. Returns the total number of bytes 287// delivered or -1 for error. 288ssize_t HandleGet(int fd, const HttpRequest& request, const size_t total_length, 289 const size_t truncate_length, const int sleep_every, 290 const int sleep_secs) { 291 ssize_t ret; 292 size_t written = 0; 293 294 // Obtain start offset, make sure it is within total payload length. 295 const size_t start_offset = request.start_offset; 296 if (start_offset >= total_length) { 297 LOG(WARNING) << "start offset (" << start_offset 298 << ") exceeds total length (" << total_length 299 << "), generating error response (" 300 << kHttpResponseReqRangeNotSat << ")"; 301 return WriteHeaders(fd, total_length, total_length, 302 kHttpResponseReqRangeNotSat); 303 } 304 305 // Obtain end offset, adjust to fit in total payload length and ensure it does 306 // not preceded the start offset. 307 size_t end_offset = (request.end_offset > 0 ? 308 request.end_offset : total_length); 309 if (end_offset < start_offset) { 310 LOG(WARNING) << "end offset (" << end_offset << ") precedes start offset (" 311 << start_offset << "), generating error response"; 312 return WriteHeaders(fd, 0, 0, kHttpResponseBadRequest); 313 } 314 if (end_offset > total_length) { 315 LOG(INFO) << "requested end offset (" << end_offset 316 << ") exceeds total length (" << total_length << "), adjusting"; 317 end_offset = total_length; 318 } 319 320 // Generate headers 321 LOG(INFO) << "generating response header: range=" << start_offset << "-" 322 << (end_offset - 1) << "/" << (end_offset - start_offset) 323 << ", return code=" << request.return_code; 324 if ((ret = WriteHeaders(fd, start_offset, end_offset, 325 request.return_code)) < 0) 326 return -1; 327 LOG(INFO) << ret << " header bytes written"; 328 written += ret; 329 330 // Compute payload length, truncate as necessary. 331 size_t payload_length = end_offset - start_offset; 332 if (truncate_length > 0 && truncate_length < payload_length) { 333 LOG(INFO) << "truncating request payload length (" << payload_length 334 << ") at " << truncate_length; 335 payload_length = truncate_length; 336 end_offset = start_offset + payload_length; 337 } 338 339 LOG(INFO) << "generating response payload: range=" << start_offset << "-" 340 << (end_offset - 1) << "/" << (end_offset - start_offset); 341 342 // Decide about optional midway delay. 343 if (truncate_length > 0 && sleep_every > 0 && sleep_secs >= 0 && 344 start_offset % (truncate_length * sleep_every) == 0) { 345 const off_t midway_offset = start_offset + payload_length / 2; 346 347 if ((ret = WritePayload(fd, start_offset, midway_offset)) < 0) 348 return -1; 349 LOG(INFO) << ret << " payload bytes written (first chunk)"; 350 written += ret; 351 352 LOG(INFO) << "sleeping for " << sleep_secs << " seconds..."; 353 sleep(sleep_secs); 354 355 if ((ret = WritePayload(fd, midway_offset, end_offset)) < 0) 356 return -1; 357 LOG(INFO) << ret << " payload bytes written (second chunk)"; 358 written += ret; 359 } else { 360 if ((ret = WritePayload(fd, start_offset, end_offset)) < 0) 361 return -1; 362 LOG(INFO) << ret << " payload bytes written"; 363 written += ret; 364 } 365 366 LOG(INFO) << "response generation complete, " << written 367 << " total bytes written"; 368 return written; 369} 370 371ssize_t HandleGet(int fd, const HttpRequest& request, 372 const size_t total_length) { 373 return HandleGet(fd, request, total_length, 0, 0, 0); 374} 375 376// Handles /redirect/<code>/<url> requests by returning the specified 377// redirect <code> with a location pointing to /<url>. 378void HandleRedirect(int fd, const HttpRequest& request) { 379 LOG(INFO) << "Redirecting..."; 380 string url = request.url; 381 CHECK_EQ(static_cast<size_t>(0), url.find("/redirect/")); 382 url.erase(0, strlen("/redirect/")); 383 string::size_type url_start = url.find('/'); 384 CHECK_NE(url_start, string::npos); 385 HttpResponseCode code = StringToHttpResponseCode(url.c_str()); 386 url.erase(0, url_start); 387 url = "http://" + request.host + url; 388 const char *status = GetHttpResponseDescription(code); 389 if (!status) 390 CHECK(false) << "Unrecognized redirection code: " << code; 391 LOG(INFO) << "Code: " << code << " " << status; 392 LOG(INFO) << "New URL: " << url; 393 394 ssize_t ret; 395 if ((ret = WriteString(fd, "HTTP/1.1 " + Itoa(code) + " " + 396 status + EOL)) < 0) 397 return; 398 WriteString(fd, "Location: " + url + EOL); 399} 400 401// Generate a page not found error response with actual text payload. Return 402// number of bytes written or -1 for error. 403ssize_t HandleError(int fd, const HttpRequest& request) { 404 LOG(INFO) << "Generating error HTTP response"; 405 406 ssize_t ret; 407 size_t written = 0; 408 409 const string data("This is an error page."); 410 411 if ((ret = WriteHeaders(fd, 0, data.size(), kHttpResponseNotFound)) < 0) 412 return -1; 413 written += ret; 414 415 if ((ret = WriteString(fd, data)) < 0) 416 return -1; 417 written += ret; 418 419 return written; 420} 421 422// Generate an error response if the requested offset is nonzero, up to a given 423// maximal number of successive failures. The error generated is an "Internal 424// Server Error" (500). 425ssize_t HandleErrorIfOffset(int fd, const HttpRequest& request, 426 size_t end_offset, int max_fails) { 427 static int num_fails = 0; 428 429 if (request.start_offset > 0 && num_fails < max_fails) { 430 LOG(INFO) << "Generating error HTTP response"; 431 432 ssize_t ret; 433 size_t written = 0; 434 435 const string data("This is an error page."); 436 437 if ((ret = WriteHeaders(fd, 0, data.size(), 438 kHttpResponseInternalServerError)) < 0) 439 return -1; 440 written += ret; 441 442 if ((ret = WriteString(fd, data)) < 0) 443 return -1; 444 written += ret; 445 446 num_fails++; 447 return written; 448 } else { 449 num_fails = 0; 450 return HandleGet(fd, request, end_offset); 451 } 452} 453 454void HandleHang(int fd) { 455 LOG(INFO) << "Hanging until the other side of the connection is closed."; 456 char c; 457 while (HANDLE_EINTR(read(fd, &c, 1)) > 0) {} 458} 459 460void HandleDefault(int fd, const HttpRequest& request) { 461 const off_t start_offset = request.start_offset; 462 const string data("unhandled path"); 463 const size_t size = data.size(); 464 ssize_t ret; 465 466 if ((ret = WriteHeaders(fd, start_offset, size, request.return_code)) < 0) 467 return; 468 WriteString(fd, (start_offset < static_cast<off_t>(size) ? 469 data.substr(start_offset) : "")); 470} 471 472 473// Break a URL into terms delimited by slashes. 474class UrlTerms { 475 public: 476 UrlTerms(const string &url, size_t num_terms) { 477 // URL must be non-empty and start with a slash. 478 CHECK_GT(url.size(), static_cast<size_t>(0)); 479 CHECK_EQ(url[0], '/'); 480 481 // Split it into terms delimited by slashes, omitting the preceeding slash. 482 base::SplitStringDontTrim(url.substr(1), '/', &terms); 483 484 // Ensure expected length. 485 CHECK_EQ(terms.size(), num_terms); 486 } 487 488 inline string Get(const off_t index) const { 489 return terms[index]; 490 } 491 inline const char *GetCStr(const off_t index) const { 492 return Get(index).c_str(); 493 } 494 inline int GetInt(const off_t index) const { 495 return atoi(GetCStr(index)); 496 } 497 inline size_t GetSizeT(const off_t index) const { 498 return static_cast<size_t>(atol(GetCStr(index))); 499 } 500 501 private: 502 vector<string> terms; 503}; 504 505void HandleConnection(int fd) { 506 HttpRequest request; 507 ParseRequest(fd, &request); 508 509 string &url = request.url; 510 LOG(INFO) << "pid(" << getpid() << "): handling url " << url; 511 if (url == "/quitquitquit") { 512 HandleQuit(fd); 513 } else if (base::StartsWithASCII(url, "/download/", true)) { 514 const UrlTerms terms(url, 2); 515 HandleGet(fd, request, terms.GetSizeT(1)); 516 } else if (base::StartsWithASCII(url, "/flaky/", true)) { 517 const UrlTerms terms(url, 5); 518 HandleGet(fd, request, terms.GetSizeT(1), terms.GetSizeT(2), 519 terms.GetInt(3), terms.GetInt(4)); 520 } else if (url.find("/redirect/") == 0) { 521 HandleRedirect(fd, request); 522 } else if (url == "/error") { 523 HandleError(fd, request); 524 } else if (base::StartsWithASCII(url, "/error-if-offset/", true)) { 525 const UrlTerms terms(url, 3); 526 HandleErrorIfOffset(fd, request, terms.GetSizeT(1), terms.GetInt(2)); 527 } else if (url == "/hang") { 528 HandleHang(fd); 529 } else { 530 HandleDefault(fd, request); 531 } 532 533 close(fd); 534} 535 536} // namespace chromeos_update_engine 537 538using namespace chromeos_update_engine; // NOLINT(build/namespaces) 539 540 541void usage(const char *prog_arg) { 542 fprintf( 543 stderr, 544 "Usage: %s [ FILE ]\n" 545 "Once accepting connections, the following is written to FILE (or " 546 "stdout):\n" 547 "\"%sN\" (where N is an integer port number)\n", 548 basename(prog_arg), kListeningMsgPrefix); 549} 550 551int main(int argc, char** argv) { 552 // Check invocation. 553 if (argc > 2) 554 errx(RC_BAD_ARGS, "unexpected number of arguments (use -h for usage)"); 555 556 // Parse (optional) argument. 557 int report_fd = STDOUT_FILENO; 558 if (argc == 2) { 559 if (!strcmp(argv[1], "-h")) { 560 usage(argv[0]); 561 exit(RC_OK); 562 } 563 564 report_fd = open(argv[1], O_WRONLY | O_CREAT, 00644); 565 } 566 567 // Ignore SIGPIPE on write() to sockets. 568 signal(SIGPIPE, SIG_IGN); 569 570 int listen_fd = socket(AF_INET, SOCK_STREAM, 0); 571 if (listen_fd < 0) 572 LOG(FATAL) << "socket() failed"; 573 574 struct sockaddr_in server_addr = sockaddr_in(); 575 server_addr.sin_family = AF_INET; 576 server_addr.sin_addr.s_addr = INADDR_ANY; 577 server_addr.sin_port = 0; 578 579 { 580 // Get rid of "Address in use" error 581 int tr = 1; 582 if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &tr, 583 sizeof(int)) == -1) { 584 perror("setsockopt"); 585 exit(RC_ERR_SETSOCKOPT); 586 } 587 } 588 589 // Bind the socket and set for listening. 590 if (bind(listen_fd, reinterpret_cast<struct sockaddr *>(&server_addr), 591 sizeof(server_addr)) < 0) { 592 perror("bind"); 593 exit(RC_ERR_BIND); 594 } 595 if (listen(listen_fd, 5) < 0) { 596 perror("listen"); 597 exit(RC_ERR_LISTEN); 598 } 599 600 // Check the actual port. 601 struct sockaddr_in bound_addr = sockaddr_in(); 602 socklen_t bound_addr_len = sizeof(bound_addr); 603 if (getsockname(listen_fd, reinterpret_cast<struct sockaddr*>(&bound_addr), 604 &bound_addr_len) < 0) { 605 perror("getsockname"); 606 exit(RC_ERR_GETSOCKNAME); 607 } 608 in_port_t port = ntohs(bound_addr.sin_port); 609 610 // Output the listening port, indicating that the server is processing 611 // requests. IMPORTANT! (a) the format of this message is as expected by some 612 // unit tests, avoid unilateral changes; (b) it is necessary to flush/sync the 613 // file to prevent the spawning process from waiting indefinitely for this 614 // message. 615 string listening_msg = base::StringPrintf("%s%hu", kListeningMsgPrefix, port); 616 LOG(INFO) << listening_msg; 617 CHECK_EQ(write(report_fd, listening_msg.c_str(), listening_msg.length()), 618 static_cast<int>(listening_msg.length())); 619 CHECK_EQ(write(report_fd, "\n", 1), 1); 620 if (report_fd == STDOUT_FILENO) 621 fsync(report_fd); 622 else 623 close(report_fd); 624 625 while (1) { 626 LOG(INFO) << "pid(" << getpid() << "): waiting to accept new connection"; 627 int client_fd = accept(listen_fd, nullptr, nullptr); 628 LOG(INFO) << "got past accept"; 629 if (client_fd < 0) 630 LOG(FATAL) << "ERROR on accept"; 631 HandleConnection(client_fd); 632 } 633 return 0; 634} 635