test_http_server.cc revision e4ad2508de4d69d7a90d3ce441efe2c82c55bd1d
1// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// This file implements a simple HTTP server. It can exhibit odd behavior
6// that's useful for testing. For example, it's useful to test that
7// the updater can continue a connection if it's dropped, or that it
8// handles very slow data transfers.
9
10// To use this, simply make an HTTP connection to localhost:port and
11// GET a url.
12
13#include <errno.h>
14#include <inttypes.h>
15#include <netinet/in.h>
16#include <signal.h>
17#include <stdio.h>
18#include <stdlib.h>
19#include <string.h>
20#include <sys/socket.h>
21#include <sys/types.h>
22#include <unistd.h>
23
24#include <algorithm>
25#include <string>
26#include <vector>
27
28#include <base/logging.h>
29#include <base/string_split.h>
30#include <base/string_util.h>
31
32#include "update_engine/http_common.h"
33#include "update_engine/http_fetcher_unittest.h"
34
35
36// HTTP end-of-line delimiter; sorry, this needs to be a macro.
37#define EOL "\r\n"
38
39using std::min;
40using std::string;
41using std::vector;
42
43
44namespace chromeos_update_engine {
45
46struct HttpRequest {
47  HttpRequest()
48      : start_offset(0), end_offset(0), return_code(kHttpResponseOk) {}
49  string host;
50  string url;
51  off_t start_offset;
52  off_t end_offset;  // non-inclusive, zero indicates unspecified.
53  HttpResponseCode return_code;
54};
55
56bool ParseRequest(int fd, HttpRequest* request) {
57  string headers;
58  do {
59    char buf[1024];
60    ssize_t r = read(fd, buf, sizeof(buf));
61    if (r < 0) {
62      perror("read");
63      exit(1);
64    }
65    headers.append(buf, r);
66  } while (!EndsWith(headers, EOL EOL, true));
67
68  LOG(INFO) << "got headers:\n--8<------8<------8<------8<----\n"
69            << headers
70            << "\n--8<------8<------8<------8<----";
71
72  // Break header into lines.
73  std::vector<string> lines;
74  base::SplitStringUsingSubstr(
75      headers.substr(0, headers.length() - strlen(EOL EOL)), EOL, &lines);
76
77  // Decode URL line.
78  std::vector<string> terms;
79  base::SplitStringAlongWhitespace(lines[0], &terms);
80  CHECK_EQ(terms.size(), 3);
81  CHECK_EQ(terms[0], "GET");
82  request->url = terms[1];
83  LOG(INFO) << "URL: " << request->url;
84
85  // Decode remaining lines.
86  size_t i;
87  for (i = 1; i < lines.size(); i++) {
88    std::vector<string> terms;
89    base::SplitStringAlongWhitespace(lines[i], &terms);
90
91    if (terms[0] == "Range:") {
92      CHECK_EQ(terms.size(), 2);
93      string &range = terms[1];
94      LOG(INFO) << "range attribute: " << range;
95      CHECK(StartsWithASCII(range, "bytes=", true) &&
96            range.find('-') != string::npos);
97      request->start_offset = atoll(range.c_str() + strlen("bytes="));
98      // Decode end offset and increment it by one (so it is non-inclusive).
99      if (range.find('-') < range.length() - 1)
100        request->end_offset = atoll(range.c_str() + range.find('-') + 1) + 1;
101      request->return_code = kHttpResponsePartialContent;
102      std::string tmp_str = StringPrintf("decoded range offsets: start=%jd "
103                                         "end=", request->start_offset);
104      if (request->end_offset > 0)
105        base::StringAppendF(&tmp_str, "%jd (non-inclusive)",
106                            request->end_offset);
107      else
108        base::StringAppendF(&tmp_str, "unspecified");
109      LOG(INFO) << tmp_str;
110    } else if (terms[0] == "Host:") {
111      CHECK_EQ(terms.size(), 2);
112      request->host = terms[1];
113      LOG(INFO) << "host attribute: " << request->host;
114    } else {
115      LOG(WARNING) << "ignoring HTTP attribute: `" << lines[i] << "'";
116    }
117  }
118
119  return true;
120}
121
122string Itoa(off_t num) {
123  char buf[100] = {0};
124  snprintf(buf, sizeof(buf), "%" PRIi64, num);
125  return buf;
126}
127
128// Writes a string into a file. Returns total number of bytes written or -1 if a
129// write error occurred.
130ssize_t WriteString(int fd, const string& str) {
131  const size_t total_size = str.size();
132  size_t remaining_size = total_size;
133  char const *data = str.data();
134
135  while (remaining_size) {
136    ssize_t written = write(fd, data, remaining_size);
137    if (written < 0) {
138      perror("write");
139      LOG(INFO) << "write failed";
140      return -1;
141    }
142    data += written;
143    remaining_size -= written;
144  }
145
146  return total_size;
147}
148
149// Writes the headers of an HTTP response into a file.
150ssize_t WriteHeaders(int fd, const off_t start_offset, const off_t end_offset,
151                     HttpResponseCode return_code) {
152  ssize_t written = 0, ret;
153
154  ret = WriteString(fd,
155                    string("HTTP/1.1 ") + Itoa(return_code) + " " +
156                    GetHttpResponseDescription(return_code) +
157                    EOL
158                    "Content-Type: application/octet-stream" EOL);
159  if (ret < 0)
160    return -1;
161  written += ret;
162
163  // Compute content legnth.
164  const off_t content_length = end_offset - start_offset;;
165
166  // A start offset that equals the end offset indicates that the response
167  // should contain the full range of bytes in the requested resource.
168  if (start_offset || start_offset == end_offset) {
169    ret = WriteString(fd,
170                      string("Accept-Ranges: bytes" EOL
171                             "Content-Range: bytes ") +
172                      Itoa(start_offset == end_offset ? 0 : start_offset) +
173                      "-" + Itoa(end_offset - 1) + "/" + Itoa(end_offset) +
174                      EOL);
175    if (ret < 0)
176      return -1;
177    written += ret;
178  }
179
180  ret = WriteString(fd, string("Content-Length: ") + Itoa(content_length) +
181                    EOL EOL);
182  if (ret < 0)
183    return -1;
184  written += ret;
185
186  return written;
187}
188
189// Writes a predetermined payload of lines of ascending bytes to a file. The
190// first byte of output is appropriately offset with respect to the request line
191// length.  Returns the number of successfully written bytes.
192size_t WritePayload(int fd, const off_t start_offset, const off_t end_offset,
193                    const char first_byte, const size_t line_len) {
194  CHECK_LE(start_offset, end_offset);
195  CHECK_GT(line_len, 0);
196
197  LOG(INFO) << "writing payload: " << line_len << "-byte lines starting with `"
198            << first_byte << "', offset range " << start_offset << " -> "
199            << end_offset;
200
201  // Populate line of ascending characters.
202  string line;
203  line.reserve(line_len);
204  char byte = first_byte;
205  size_t i;
206  for (i = 0; i < line_len; i++)
207    line += byte++;
208
209  const size_t total_len = end_offset - start_offset;
210  size_t remaining_len = total_len;
211  bool success = true;
212
213  // If start offset is not aligned with line boundary, output partial line up
214  // to the first line boundary.
215  size_t start_modulo = start_offset % line_len;
216  if (start_modulo) {
217    string partial = line.substr(start_modulo, remaining_len);
218    ssize_t ret = WriteString(fd, partial);
219    if ((success = (ret >= 0 && (size_t) ret == partial.length())))
220      remaining_len -= partial.length();
221  }
222
223  // Output full lines up to the maximal line boundary below the end offset.
224  while (success && remaining_len >= line_len) {
225    ssize_t ret = WriteString(fd, line);
226    if ((success = (ret >= 0 && (size_t) ret == line_len)))
227      remaining_len -= line_len;
228  }
229
230  // Output a partial line up to the end offset.
231  if (success && remaining_len) {
232    string partial = line.substr(0, remaining_len);
233    ssize_t ret = WriteString(fd, partial);
234    if ((success = (ret >= 0 && (size_t) ret == partial.length())))
235      remaining_len -= partial.length();
236  }
237
238  return (total_len - remaining_len);
239}
240
241// Write default payload lines of the form 'abcdefghij'.
242inline size_t WritePayload(int fd, const off_t start_offset,
243                           const off_t end_offset) {
244  return WritePayload(fd, start_offset, end_offset, 'a', 10);
245}
246
247// Send an empty response, then kill the server.
248void HandleQuit(int fd) {
249  WriteHeaders(fd, 0, 0, kHttpResponseOk);
250  exit(0);
251}
252
253
254// Generates an HTTP response with payload corresponding to requested offsets
255// and length.  Optionally, truncate the payload at a given length and add a
256// pause midway through the transfer.  Returns the total number of bytes
257// delivered or -1 for error.
258ssize_t HandleGet(int fd, const HttpRequest& request, const size_t total_length,
259                  const size_t truncate_length, const int sleep_every,
260                  const int sleep_secs) {
261  ssize_t ret;
262  size_t written = 0;
263
264  // Obtain start offset, make sure it is within total payload length.
265  const size_t start_offset = request.start_offset;
266  if (start_offset >= total_length) {
267    LOG(WARNING) << "start offset (" << start_offset
268                 << ") exceeds total length (" << total_length
269                 << "), generating error response ("
270                 << kHttpResponseReqRangeNotSat << ")";
271    return WriteHeaders(fd, total_length, total_length,
272                        kHttpResponseReqRangeNotSat);
273  }
274
275  // Obtain end offset, adjust to fit in total payload length and ensure it does
276  // not preceded the start offset.
277  size_t end_offset = (request.end_offset > 0 ?
278                       request.end_offset : total_length);
279  if (end_offset < start_offset) {
280    LOG(WARNING) << "end offset (" << end_offset << ") precedes start offset ("
281                 << start_offset << "), generating error response";
282    return WriteHeaders(fd, 0, 0, kHttpResponseBadRequest);
283  }
284  if (end_offset > total_length) {
285    LOG(INFO) << "requested end offset (" << end_offset
286              << ") exceeds total length (" << total_length << "), adjusting";
287    end_offset = total_length;
288  }
289
290  // Generate headers
291  LOG(INFO) << "generating response header: range=" << start_offset << "-"
292            << (end_offset - 1) << "/" << (end_offset - start_offset)
293            << ", return code=" << request.return_code;
294  if ((ret = WriteHeaders(fd, start_offset, end_offset,
295                          request.return_code)) < 0)
296    return -1;
297  LOG(INFO) << ret << " header bytes written";
298  written += ret;
299
300  // Compute payload length, truncate as necessary.
301  size_t payload_length = end_offset - start_offset;
302  if (truncate_length > 0 && truncate_length < payload_length) {
303    LOG(INFO) << "truncating request payload length (" << payload_length
304              << ") at " << truncate_length;
305    payload_length = truncate_length;
306    end_offset = start_offset + payload_length;
307  }
308
309  LOG(INFO) << "generating response payload: range=" << start_offset << "-"
310            << (end_offset - 1) << "/" << (end_offset - start_offset);
311
312  // Decide about optional midway delay.
313  if (truncate_length > 0 && sleep_every > 0 && sleep_secs >= 0 &&
314      start_offset % (truncate_length * sleep_every) == 0) {
315    const off_t midway_offset = start_offset + payload_length / 2;
316
317    if ((ret = WritePayload(fd, start_offset, midway_offset)) < 0)
318      return -1;
319    LOG(INFO) << ret << " payload bytes written (first chunk)";
320    written += ret;
321
322    LOG(INFO) << "sleeping for " << sleep_secs << " seconds...";
323    sleep(sleep_secs);
324
325    if ((ret = WritePayload(fd, midway_offset, end_offset)) < 0)
326      return -1;
327    LOG(INFO) << ret << " payload bytes written (second chunk)";
328    written += ret;
329  } else {
330    if ((ret = WritePayload(fd, start_offset, end_offset)) < 0)
331      return -1;
332    LOG(INFO) << ret << " payload bytes written";
333    written += ret;
334  }
335
336  LOG(INFO) << "response generation complete, " << written
337            << " total bytes written";
338  return written;
339}
340
341ssize_t HandleGet(int fd, const HttpRequest& request,
342                  const size_t total_length) {
343  return HandleGet(fd, request, total_length, 0, 0, 0);
344}
345
346// Handles /redirect/<code>/<url> requests by returning the specified
347// redirect <code> with a location pointing to /<url>.
348void HandleRedirect(int fd, const HttpRequest& request) {
349  LOG(INFO) << "Redirecting...";
350  string url = request.url;
351  CHECK_EQ(0, url.find("/redirect/"));
352  url.erase(0, strlen("/redirect/"));
353  string::size_type url_start = url.find('/');
354  CHECK_NE(url_start, string::npos);
355  HttpResponseCode code = StringToHttpResponseCode(url.c_str());
356  url.erase(0, url_start);
357  url = "http://" + request.host + url;
358  const char *status = GetHttpResponseDescription(code);
359  if (!status)
360    CHECK(false) << "Unrecognized redirection code: " << code;
361  LOG(INFO) << "Code: " << code << " " << status;
362  LOG(INFO) << "New URL: " << url;
363
364  ssize_t ret;
365  if ((ret = WriteString(fd, "HTTP/1.1 " + Itoa(code) + " " +
366                         status + EOL)) < 0)
367    return;
368  WriteString(fd, "Location: " + url + EOL);
369}
370
371// Generate a page not found error response with actual text payload. Return
372// number of bytes written or -1 for error.
373ssize_t HandleError(int fd, const HttpRequest& request) {
374  LOG(INFO) << "Generating error HTTP response";
375
376  ssize_t ret;
377  size_t written = 0;
378
379  const string data("This is an error page.");
380
381  if ((ret = WriteHeaders(fd, 0, data.size(), kHttpResponseNotFound)) < 0)
382    return -1;
383  written += ret;
384
385  if ((ret = WriteString(fd, data)) < 0)
386    return -1;
387  written += ret;
388
389  return written;
390}
391
392// Generate an error response if the requested offset is nonzero, up to a given
393// maximal number of successive failures.  The error generated is an "Internal
394// Server Error" (500).
395ssize_t HandleErrorIfOffset(int fd, const HttpRequest& request,
396                            size_t end_offset, int max_fails) {
397  static int num_fails = 0;
398
399  if (request.start_offset > 0 && num_fails < max_fails) {
400    LOG(INFO) << "Generating error HTTP response";
401
402    ssize_t ret;
403    size_t written = 0;
404
405    const string data("This is an error page.");
406
407    if ((ret = WriteHeaders(fd, 0, data.size(),
408                            kHttpResponseInternalServerError)) < 0)
409      return -1;
410    written += ret;
411
412    if ((ret = WriteString(fd, data)) < 0)
413      return -1;
414    written += ret;
415
416    num_fails++;
417    return written;
418  } else {
419    num_fails = 0;
420    return HandleGet(fd, request, end_offset);
421  }
422}
423
424void HandleDefault(int fd, const HttpRequest& request) {
425  const off_t start_offset = request.start_offset;
426  const string data("unhandled path");
427  const size_t size = data.size();
428  ssize_t ret;
429
430  if ((ret = WriteHeaders(fd, start_offset, size, request.return_code)) < 0)
431    return;
432  WriteString(fd, (start_offset < static_cast<off_t>(size) ?
433                   data.substr(start_offset) : ""));
434}
435
436
437// Break a URL into terms delimited by slashes.
438class UrlTerms {
439 public:
440  UrlTerms(string &url, size_t num_terms) {
441    // URL must be non-empty and start with a slash.
442    CHECK_GT(url.size(), 0);
443    CHECK_EQ(url[0], '/');
444
445    // Split it into terms delimited by slashes, omitting the preceeding slash.
446    base::SplitStringDontTrim(url.substr(1), '/', &terms);
447
448    // Ensure expected length.
449    CHECK_EQ(terms.size(), num_terms);
450  }
451
452  inline string Get(const off_t index) const {
453    return terms[index];
454  }
455  inline const char *GetCStr(const off_t index) const {
456    return Get(index).c_str();
457  }
458  inline int GetInt(const off_t index) const {
459    return atoi(GetCStr(index));
460  }
461  inline long GetLong(const off_t index) const {
462    return atol(GetCStr(index));
463  }
464
465 private:
466  std::vector<string> terms;
467};
468
469void HandleConnection(int fd) {
470  HttpRequest request;
471  ParseRequest(fd, &request);
472
473  string &url = request.url;
474  if (url == "/quitquitquit") {
475    HandleQuit(fd);
476  } else if (StartsWithASCII(url, "/download/", true)) {
477    const UrlTerms terms(url, 2);
478    HandleGet(fd, request, terms.GetLong(1));
479  } else if (StartsWithASCII(url, "/flaky/", true)) {
480    const UrlTerms terms(url, 5);
481    HandleGet(fd, request, terms.GetLong(1), terms.GetLong(2), terms.GetLong(3),
482              terms.GetLong(4));
483  } else if (url.find("/redirect/") == 0) {
484    HandleRedirect(fd, request);
485  } else if (url == "/error") {
486    HandleError(fd, request);
487  } else if (StartsWithASCII(url, "/error-if-offset/", true)) {
488    const UrlTerms terms(url, 3);
489    HandleErrorIfOffset(fd, request, terms.GetLong(1), terms.GetInt(2));
490  } else {
491    HandleDefault(fd, request);
492  }
493
494  close(fd);
495}
496
497}  // namespace chromeos_update_engine
498
499using namespace chromeos_update_engine;
500
501int main(int argc, char** argv) {
502  // Ignore SIGPIPE on write() to sockets.
503  signal(SIGPIPE, SIG_IGN);
504
505  socklen_t clilen;
506  struct sockaddr_in server_addr;
507  struct sockaddr_in client_addr;
508  memset(&server_addr, 0, sizeof(server_addr));
509  memset(&client_addr, 0, sizeof(client_addr));
510
511  int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
512  if (listen_fd < 0)
513    LOG(FATAL) << "socket() failed";
514
515  server_addr.sin_family = AF_INET;
516  server_addr.sin_addr.s_addr = INADDR_ANY;
517  server_addr.sin_port = htons(kServerPort);
518
519  {
520    // Get rid of "Address in use" error
521    int tr = 1;
522    if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &tr,
523                   sizeof(int)) == -1) {
524      perror("setsockopt");
525      exit(1);
526    }
527  }
528
529  if (bind(listen_fd, reinterpret_cast<struct sockaddr *>(&server_addr),
530           sizeof(server_addr)) < 0) {
531    perror("bind");
532    exit(1);
533  }
534  CHECK_EQ(listen(listen_fd,5), 0);
535  while (1) {
536    clilen = sizeof(client_addr);
537    int client_fd = accept(listen_fd,
538                           (struct sockaddr *) &client_addr,
539                           &clilen);
540    LOG(INFO) << "got past accept";
541    if (client_fd < 0)
542      LOG(FATAL) << "ERROR on accept";
543    HandleConnection(client_fd);
544  }
545  return 0;
546}
547