blob: 9fbe97f5c40d32be8fbc6f9d6a802f603f6711dc [file] [log] [blame]
adlr@google.com3defe6a2009-12-04 20:57:17 +00001// 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 <netinet/in.h>
14#include <sys/socket.h>
15#include <sys/types.h>
16#include <errno.h>
Andrew de los Reyes08c4e272010-04-15 14:02:17 -070017#include <inttypes.h>
adlr@google.com3defe6a2009-12-04 20:57:17 +000018#include <stdio.h>
19#include <stdlib.h>
20#include <string.h>
21#include <unistd.h>
22#include <algorithm>
23#include <string>
24#include <vector>
25#include "chromeos/obsolete_logging.h"
26
27using std::min;
28using std::string;
29using std::vector;
30
31namespace chromeos_update_engine {
32
33struct HttpRequest {
34 string url;
35 off_t offset;
36};
37
38namespace {
39const int kPort = 8080; // hardcoded to 8080 for now
40const int kBigLength = 100000;
41}
42
43bool ParseRequest(int fd, HttpRequest* request) {
44 string headers;
45 while(headers.find("\r\n\r\n") == string::npos) {
46 vector<char> buf(1024);
47 memset(&buf[0], 0, buf.size());
48 ssize_t r = read(fd, &buf[0], buf.size() - 1);
49 if (r < 0) {
50 perror("read");
51 exit(1);
52 }
53 buf.resize(r);
54
55 headers.insert(headers.end(), buf.begin(), buf.end());
56 }
57 LOG(INFO) << "got headers: " << headers;
58
59 string::size_type url_start, url_end;
60 CHECK_NE(headers.find("GET "), string::npos);
61 url_start = headers.find("GET ") + strlen("GET ");
62 url_end = headers.find(' ', url_start);
63 CHECK_NE(string::npos, url_end);
64 string url = headers.substr(url_start, url_end - url_start);
65 LOG(INFO) << "URL: " << url;
66
67 string::size_type range_start, range_end;
68 if (headers.find("\r\nRange: ") == string::npos) {
69 request->offset = 0;
70 } else {
71 range_start = headers.find("\r\nRange: ") + strlen("\r\nRange: ");
72 range_end = headers.find('\r', range_start);
73 CHECK_NE(string::npos, range_end);
74 string range_header = headers.substr(range_start, range_end - range_start);
75
76 LOG(INFO) << "Range: " << range_header;
77 CHECK(*range_header.rbegin() == '-');
78 request->offset = atoll(range_header.c_str() + strlen("bytes="));
79 LOG(INFO) << "Offset: " << request->offset;
80 }
81 request->url = url;
82 return true;
83}
84
85void WriteString(int fd, const string& str) {
86 unsigned int bytes_written = 0;
87 while (bytes_written < str.size()) {
88 ssize_t r = write(fd, str.c_str() + bytes_written,
89 str.size() - bytes_written);
90 LOG(INFO) << "write() wrote " << r << " bytes";
91 if (r < 0) {
92 perror("write");
93 return;
94 }
95 bytes_written += r;
96 }
97 LOG(INFO) << "WriteString wrote " << bytes_written << " bytes";
98}
99
100string Itoa(off_t num) {
101 char buf[100] = {0};
Andrew de los Reyes08c4e272010-04-15 14:02:17 -0700102 snprintf(buf, sizeof(buf), "%" PRIi64, num);
adlr@google.com3defe6a2009-12-04 20:57:17 +0000103 return buf;
104}
105
106void WriteHeaders(int fd, bool support_range, off_t full_size,
107 off_t start_offset) {
108 LOG(INFO) << "writing headers";
109 WriteString(fd, "HTTP/1.1 200 OK\r\n");
110 WriteString(fd, "Content-Type: application/octet-stream\r\n");
111 if (support_range) {
112 WriteString(fd, "Accept-Ranges: bytes\r\n");
113 WriteString(fd, string("Content-Range: bytes ") + Itoa(start_offset) +
114 "-" + Itoa(full_size - 1) + "/" + Itoa(full_size) + "\r\n");
115 }
116 off_t content_length = full_size;
117 if (support_range)
118 content_length -= start_offset;
119 WriteString(fd, string("Content-Length: ") + Itoa(content_length) + "\r\n");
120 WriteString(fd, "\r\n");
121}
122
123void HandleQuitQuitQuit(int fd) {
Andrew de los Reyes08c4e272010-04-15 14:02:17 -0700124 WriteHeaders(fd, true, 0, 0);
adlr@google.com3defe6a2009-12-04 20:57:17 +0000125 exit(0);
126}
127
128void HandleBig(int fd, const HttpRequest& request) {
129 const off_t full_length = kBigLength;
130 WriteHeaders(fd, true, full_length, request.offset);
131 const off_t content_length = full_length - request.offset;
132 int i = request.offset;
133 for (; i % 10; i++)
134 WriteString(fd, string(1, 'a' + (i % 10)));
135 CHECK_EQ(i % 10, 0);
136 for (; i < content_length; i += 10)
137 WriteString(fd, "abcdefghij");
138 CHECK_EQ(i, full_length);
139}
140
141// This is like /big, but it writes at most 9000 bytes. Also,
142// half way through it sleeps for 70 seconds
143// (technically, when (offset % (9000 * 7)) == 0).
144void HandleFlaky(int fd, const HttpRequest& request) {
145 const off_t full_length = kBigLength;
146 WriteHeaders(fd, true, full_length, request.offset);
Andrew de los Reyes08c4e272010-04-15 14:02:17 -0700147 const off_t content_length =
148 min(static_cast<off_t>(9000), full_length - request.offset);
adlr@google.com3defe6a2009-12-04 20:57:17 +0000149 const bool should_sleep = (request.offset % (9000 * 7)) == 0;
150
151 string buf;
152
153 for (int i = request.offset; i % 10; i++)
154 buf.append(1, 'a' + (i % 10));
Andrew de los Reyes08c4e272010-04-15 14:02:17 -0700155 while (static_cast<off_t>(buf.size()) < content_length)
adlr@google.com3defe6a2009-12-04 20:57:17 +0000156 buf.append("abcdefghij");
157 buf.resize(content_length);
158
159 if (!should_sleep) {
160 LOG(INFO) << "writing data blob of size " << buf.size();
161 WriteString(fd, buf);
162 } else {
163 string::size_type half_way_point = buf.size() / 2;
164 LOG(INFO) << "writing small data blob of size " << half_way_point;
165 WriteString(fd, buf.substr(0, half_way_point));
166 sleep(10);
167 LOG(INFO) << "writing small data blob of size "
168 << (buf.size() - half_way_point);
169 WriteString(fd, buf.substr(half_way_point, buf.size() - half_way_point));
170 }
171}
172
173void HandleDefault(int fd, const HttpRequest& request) {
174 const string data("unhandled path");
175 WriteHeaders(fd, true, data.size(), request.offset);
176 const string data_to_write(data.substr(request.offset,
177 data.size() - request.offset));
178 WriteString(fd, data_to_write);
179}
180
181void HandleConnection(int fd) {
182 HttpRequest request;
183 ParseRequest(fd, &request);
184
185 if (request.url == "/quitquitquit")
186 HandleQuitQuitQuit(fd);
187 else if (request.url == "/big")
188 HandleBig(fd, request);
189 else if (request.url == "/flaky")
190 HandleFlaky(fd, request);
191 else
192 HandleDefault(fd, request);
193
194 close(fd);
195}
196
197} // namespace chromeos_update_engine
198
199using namespace chromeos_update_engine;
200
201int main(int argc, char** argv) {
202 socklen_t clilen;
203 struct sockaddr_in server_addr;
204 struct sockaddr_in client_addr;
205 memset(&server_addr, 0, sizeof(server_addr));
206 memset(&client_addr, 0, sizeof(client_addr));
207
208 int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
209 if (listen_fd < 0)
210 LOG(FATAL) << "socket() failed";
211
212 server_addr.sin_family = AF_INET;
213 server_addr.sin_addr.s_addr = INADDR_ANY;
214 server_addr.sin_port = htons(kPort);
215
216 {
217 // Get rid of "Address in use" error
218 int tr = 1;
219 if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &tr,
220 sizeof(int)) == -1) {
221 perror("setsockopt");
222 exit(1);
223 }
224 }
225
226 if (bind(listen_fd, reinterpret_cast<struct sockaddr *>(&server_addr),
227 sizeof(server_addr)) < 0) {
228 perror("bind");
229 exit(1);
230 }
231 CHECK_EQ(listen(listen_fd,5), 0);
232 while (1) {
233 clilen = sizeof(client_addr);
234 int client_fd = accept(listen_fd,
235 (struct sockaddr *) &client_addr,
236 &clilen);
237 LOG(INFO) << "got past accept";
238 if (client_fd < 0)
239 LOG(FATAL) << "ERROR on accept";
240 HandleConnection(client_fd);
241 }
242 return 0;
243}