test_http_server.cc revision 48085ba58516e94f045d3ab7e26c8f36e6a6936f
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
30using std::min;
31using std::string;
32using std::vector;
33
34namespace chromeos_update_engine {
35
36struct HttpRequest {
37  HttpRequest() : offset(0), return_code(200) {}
38  string host;
39  string url;
40  off_t offset;
41  int return_code;
42};
43
44namespace {
45const int kPort = 8088;  // hardcoded for now
46const int kBigLength = 100000;
47const int kMediumLength = 1000;
48}
49
50bool ParseRequest(int fd, HttpRequest* request) {
51  string headers;
52  while(headers.find("\r\n\r\n") == string::npos) {
53    vector<char> buf(1024);
54    memset(&buf[0], 0, buf.size());
55    ssize_t r = read(fd, &buf[0], buf.size() - 1);
56    if (r < 0) {
57      perror("read");
58      exit(1);
59    }
60    buf.resize(r);
61
62    headers.insert(headers.end(), buf.begin(), buf.end());
63  }
64  LOG(INFO) << "got headers: " << headers;
65
66  string::size_type url_start, url_end;
67  CHECK_NE(headers.find("GET "), string::npos);
68  url_start = headers.find("GET ") + strlen("GET ");
69  url_end = headers.find(' ', url_start);
70  CHECK_NE(string::npos, url_end);
71  string url = headers.substr(url_start, url_end - url_start);
72  LOG(INFO) << "URL: " << url;
73  request->url = url;
74
75  string::size_type range_start, range_end;
76  if (headers.find("\r\nRange: ") == string::npos) {
77    request->offset = 0;
78  } else {
79    range_start = headers.find("\r\nRange: ") + strlen("\r\nRange: ");
80    range_end = headers.find('\r', range_start);
81    CHECK_NE(string::npos, range_end);
82    string range_header = headers.substr(range_start, range_end - range_start);
83
84    LOG(INFO) << "Range: " << range_header;
85    CHECK(*range_header.rbegin() == '-');
86    request->offset = atoll(range_header.c_str() + strlen("bytes="));
87    request->return_code = 206;  // Success for Range: request
88    LOG(INFO) << "Offset: " << request->offset;
89  }
90
91  if (headers.find("\r\nHost: ") == string::npos) {
92    request->host = "";
93  } else {
94    string::size_type host_start =
95        headers.find("\r\nHost: ") + strlen("\r\nHost: ");
96    string::size_type host_end = headers.find('\r', host_start);
97    CHECK_NE(string::npos, host_end);
98    string host = headers.substr(host_start, host_end - host_start);
99
100    LOG(INFO) << "Host: " << host;
101    request->host = host;
102  }
103
104  return true;
105}
106
107bool WriteString(int fd, const string& str) {
108  unsigned int bytes_written = 0;
109  while (bytes_written < str.size()) {
110    ssize_t r = write(fd, str.data() + bytes_written,
111                      str.size() - bytes_written);
112    if (r < 0) {
113      perror("write");
114      LOG(INFO) << "write failed";
115      return false;
116    }
117    bytes_written += r;
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// Return a string description for common HTTP response codes.
129const char *GetHttpStatusLine(int code_id) {
130  struct HttpStatusCode {
131    int id;
132    const char* description;
133  } http_status_codes[] = {
134    { 200, "OK" },
135    { 201, "Created" },
136    { 202, "Accepted" },
137    { 203, "Non-Authoritative Information" },
138    { 204, "No Content" },
139    { 205, "Reset Content" },
140    { 206, "Partial Content" },
141    { 300, "Multiple Choices" },
142    { 301, "Moved Permanently" },
143    { 302, "Found" },
144    { 303, "See Other" },
145    { 304, "Not Modified" },
146    { 305, "Use Proxy" },
147    { 307, "Temporary Redirect" },
148    { 400, "Bad Request" },
149    { 401, "Unauthorized" },
150    { 403, "Forbidden" },
151    { 404, "Not Found" },
152    { 408, "Request Timeout" },
153    { 500, "Internal Server Error" },
154    { 501, "Not Implemented" },
155    { 503, "Service Unavailable" },
156    { 505, "HTTP Version Not Supported" },
157    { 0, "Undefined" },
158  };
159
160  int i;
161  for (i = 0; http_status_codes[i].id; ++i)
162    if (http_status_codes[i].id == code_id)
163      break;
164
165  return http_status_codes[i].description;
166}
167
168
169void WriteHeaders(int fd, bool support_range, off_t full_size,
170                  off_t start_offset, int return_code) {
171  LOG(INFO) << "writing headers";
172  WriteString(fd, string("HTTP/1.1 ") + Itoa(return_code) + " " +
173              GetHttpStatusLine(return_code) + "\r\n");
174  WriteString(fd, "Content-Type: application/octet-stream\r\n");
175  if (support_range) {
176    WriteString(fd, "Accept-Ranges: bytes\r\n");
177    WriteString(fd, string("Content-Range: bytes ") + Itoa(start_offset) +
178                "-" + Itoa(full_size - 1) + "/" + Itoa(full_size) + "\r\n");
179  }
180  off_t content_length = full_size;
181  if (support_range)
182    content_length -= start_offset;
183  WriteString(fd, string("Content-Length: ") + Itoa(content_length) + "\r\n");
184  WriteString(fd, "\r\n");
185}
186
187void HandleQuitQuitQuit(int fd) {
188  WriteHeaders(fd, true, 0, 0, 200);
189  exit(0);
190}
191
192void HandleBig(int fd, const HttpRequest& request, int big_length) {
193  LOG(INFO) << "starting big";
194  const off_t full_length = big_length;
195  WriteHeaders(fd, true, full_length, request.offset, request.return_code);
196  int i = request.offset;
197  bool success = true;
198  for (; (i % 10) && success; i++)
199    success = WriteString(fd, string(1, 'a' + (i % 10)));
200  if (success)
201    CHECK_EQ(i % 10, 0);
202  for (; (i < full_length) && success; i += 10) {
203    success = WriteString(fd, "abcdefghij");
204  }
205  if (success)
206    CHECK_EQ(i, full_length);
207  LOG(INFO) << "Done w/ big";
208}
209
210// This is like /big, but it writes at most 9000 bytes. Also,
211// half way through it sleeps for 70 seconds
212// (technically, when (offset % (9000 * 7)) == 0).
213void HandleFlaky(int fd, const HttpRequest& request) {
214  const off_t full_length = kBigLength;
215  WriteHeaders(fd, true, full_length, request.offset, request.return_code);
216  const off_t content_length =
217      min(static_cast<off_t>(9000), full_length - request.offset);
218  const bool should_sleep = (request.offset % (9000 * 7)) == 0;
219
220  string buf;
221
222  for (int i = request.offset; i % 10; i++)
223    buf.append(1, 'a' + (i % 10));
224  while (static_cast<off_t>(buf.size()) < content_length)
225    buf.append("abcdefghij");
226  buf.resize(content_length);
227
228  if (!should_sleep) {
229    LOG(INFO) << "writing data blob of size " << buf.size();
230    WriteString(fd, buf);
231  } else {
232    string::size_type half_way_point = buf.size() / 2;
233    LOG(INFO) << "writing small data blob of size " << half_way_point;
234    WriteString(fd, buf.substr(0, half_way_point));
235    sleep(10);
236    LOG(INFO) << "writing small data blob of size "
237              << (buf.size() - half_way_point);
238    WriteString(fd, buf.substr(half_way_point, buf.size() - half_way_point));
239  }
240}
241
242// Handles /redirect/<code>/<url> requests by returning the specified
243// redirect <code> with a location pointing to /<url>.
244void HandleRedirect(int fd, const HttpRequest& request) {
245  LOG(INFO) << "Redirecting...";
246  string url = request.url;
247  CHECK_EQ(0, url.find("/redirect/"));
248  url.erase(0, strlen("/redirect/"));
249  string::size_type url_start = url.find('/');
250  CHECK_NE(url_start, string::npos);
251  string code = url.substr(0, url_start);
252  url.erase(0, url_start);
253  url = "http://" + request.host + url;
254  string status;
255  if (code == "301") {
256    status = "Moved Permanently";
257  } else if (code == "302") {
258    status = "Found";
259  } else if (code == "303") {
260    status = "See Other";
261  } else if (code == "307") {
262    status = "Temporary Redirect";
263  } else {
264    CHECK(false) << "Unrecognized redirection code: " << code;
265  }
266  LOG(INFO) << "Code: " << code << " " << status;
267  LOG(INFO) << "New URL: " << url;
268  WriteString(fd, "HTTP/1.1 " + code + " " + status + "\r\n");
269  WriteString(fd, "Location: " + url + "\r\n");
270}
271
272void HandleDefault(int fd, const HttpRequest& request) {
273  const string data("unhandled path");
274  WriteHeaders(fd, true, data.size(), request.offset, request.return_code);
275  const string data_to_write(data.substr(request.offset,
276                                         data.size() - request.offset));
277  WriteString(fd, data_to_write);
278}
279
280// Handle an error response from server.
281void HandleError(int fd, const HttpRequest& request) {
282  LOG(INFO) << "Generating error HTTP response";
283
284  // Generate a Not Found" error page with some text.
285  const string data("This is an error page.");
286  WriteHeaders(fd, false, data.size(), 0, 404);
287  WriteString(fd, data);
288}
289
290void HandleConnection(int fd) {
291  HttpRequest request;
292  ParseRequest(fd, &request);
293
294  if (request.url == "/quitquitquit")
295    HandleQuitQuitQuit(fd);
296  else if (request.url == "/big")
297    HandleBig(fd, request, kBigLength);
298  else if (request.url == "/medium")
299    HandleBig(fd, request, kMediumLength);
300  else if (request.url == "/flaky")
301    HandleFlaky(fd, request);
302  else if (request.url.find("/redirect/") == 0)
303    HandleRedirect(fd, request);
304  else if (request.url.find("/error") == 0)
305    HandleError(fd, request);
306  else
307    HandleDefault(fd, request);
308
309  close(fd);
310}
311
312}  // namespace chromeos_update_engine
313
314using namespace chromeos_update_engine;
315
316int main(int argc, char** argv) {
317  // Ignore SIGPIPE on write() to sockets.
318  signal(SIGPIPE, SIG_IGN);
319
320  socklen_t clilen;
321  struct sockaddr_in server_addr;
322  struct sockaddr_in client_addr;
323  memset(&server_addr, 0, sizeof(server_addr));
324  memset(&client_addr, 0, sizeof(client_addr));
325
326  int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
327  if (listen_fd < 0)
328    LOG(FATAL) << "socket() failed";
329
330  server_addr.sin_family = AF_INET;
331  server_addr.sin_addr.s_addr = INADDR_ANY;
332  server_addr.sin_port = htons(kPort);
333
334  {
335    // Get rid of "Address in use" error
336    int tr = 1;
337    if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &tr,
338                   sizeof(int)) == -1) {
339      perror("setsockopt");
340      exit(1);
341    }
342  }
343
344  if (bind(listen_fd, reinterpret_cast<struct sockaddr *>(&server_addr),
345           sizeof(server_addr)) < 0) {
346    perror("bind");
347    exit(1);
348  }
349  CHECK_EQ(listen(listen_fd,5), 0);
350  while (1) {
351    clilen = sizeof(client_addr);
352    int client_fd = accept(listen_fd,
353                           (struct sockaddr *) &client_addr,
354                           &clilen);
355    LOG(INFO) << "got past accept";
356    if (client_fd < 0)
357      LOG(FATAL) << "ERROR on accept";
358    HandleConnection(client_fd);
359  }
360  return 0;
361}
362