annotate src/capnproto-0.6.0/c++/src/kj/compat/http-test.c++ @ 147:45360b968bf4

Cap'n Proto v0.6 + build for OSX
author Chris Cannam <cannam@all-day-breakfast.com>
date Mon, 22 May 2017 10:01:37 +0100
parents
children
rev   line source
cannam@147 1 // Copyright (c) 2017 Sandstorm Development Group, Inc. and contributors
cannam@147 2 // Licensed under the MIT License:
cannam@147 3 //
cannam@147 4 // Permission is hereby granted, free of charge, to any person obtaining a copy
cannam@147 5 // of this software and associated documentation files (the "Software"), to deal
cannam@147 6 // in the Software without restriction, including without limitation the rights
cannam@147 7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
cannam@147 8 // copies of the Software, and to permit persons to whom the Software is
cannam@147 9 // furnished to do so, subject to the following conditions:
cannam@147 10 //
cannam@147 11 // The above copyright notice and this permission notice shall be included in
cannam@147 12 // all copies or substantial portions of the Software.
cannam@147 13 //
cannam@147 14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
cannam@147 15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
cannam@147 16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
cannam@147 17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
cannam@147 18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
cannam@147 19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
cannam@147 20 // THE SOFTWARE.
cannam@147 21
cannam@147 22 #include "http.h"
cannam@147 23 #include <kj/debug.h>
cannam@147 24 #include <kj/test.h>
cannam@147 25 #include <map>
cannam@147 26
cannam@147 27 namespace kj {
cannam@147 28 namespace {
cannam@147 29
cannam@147 30 KJ_TEST("HttpMethod parse / stringify") {
cannam@147 31 #define TRY(name) \
cannam@147 32 KJ_EXPECT(kj::str(HttpMethod::name) == #name); \
cannam@147 33 KJ_IF_MAYBE(parsed, tryParseHttpMethod(#name)) { \
cannam@147 34 KJ_EXPECT(*parsed == HttpMethod::name); \
cannam@147 35 } else { \
cannam@147 36 KJ_FAIL_EXPECT("couldn't parse \"" #name "\" as HttpMethod"); \
cannam@147 37 }
cannam@147 38
cannam@147 39 KJ_HTTP_FOR_EACH_METHOD(TRY)
cannam@147 40 #undef TRY
cannam@147 41
cannam@147 42 KJ_EXPECT(tryParseHttpMethod("FOO") == nullptr);
cannam@147 43 KJ_EXPECT(tryParseHttpMethod("") == nullptr);
cannam@147 44 KJ_EXPECT(tryParseHttpMethod("G") == nullptr);
cannam@147 45 KJ_EXPECT(tryParseHttpMethod("GE") == nullptr);
cannam@147 46 KJ_EXPECT(tryParseHttpMethod("GET ") == nullptr);
cannam@147 47 KJ_EXPECT(tryParseHttpMethod("get") == nullptr);
cannam@147 48 }
cannam@147 49
cannam@147 50 KJ_TEST("HttpHeaderTable") {
cannam@147 51 HttpHeaderTable::Builder builder;
cannam@147 52
cannam@147 53 auto host = builder.add("Host");
cannam@147 54 auto host2 = builder.add("hOsT");
cannam@147 55 auto fooBar = builder.add("Foo-Bar");
cannam@147 56 auto bazQux = builder.add("baz-qux");
cannam@147 57 auto bazQux2 = builder.add("Baz-Qux");
cannam@147 58
cannam@147 59 auto table = builder.build();
cannam@147 60
cannam@147 61 uint builtinHeaderCount = 0;
cannam@147 62 #define INCREMENT(id, name) ++builtinHeaderCount;
cannam@147 63 KJ_HTTP_FOR_EACH_BUILTIN_HEADER(INCREMENT)
cannam@147 64 #undef INCREMENT
cannam@147 65
cannam@147 66 KJ_EXPECT(table->idCount() == builtinHeaderCount + 2);
cannam@147 67
cannam@147 68 KJ_EXPECT(host == HttpHeaderId::HOST);
cannam@147 69 KJ_EXPECT(host != HttpHeaderId::DATE);
cannam@147 70 KJ_EXPECT(host2 == host);
cannam@147 71
cannam@147 72 KJ_EXPECT(host != fooBar);
cannam@147 73 KJ_EXPECT(host != bazQux);
cannam@147 74 KJ_EXPECT(fooBar != bazQux);
cannam@147 75 KJ_EXPECT(bazQux == bazQux2);
cannam@147 76
cannam@147 77 KJ_EXPECT(kj::str(host) == "Host");
cannam@147 78 KJ_EXPECT(kj::str(host2) == "Host");
cannam@147 79 KJ_EXPECT(kj::str(fooBar) == "Foo-Bar");
cannam@147 80 KJ_EXPECT(kj::str(bazQux) == "baz-qux");
cannam@147 81 KJ_EXPECT(kj::str(HttpHeaderId::HOST) == "Host");
cannam@147 82
cannam@147 83 KJ_EXPECT(table->idToString(HttpHeaderId::DATE) == "Date");
cannam@147 84 KJ_EXPECT(table->idToString(fooBar) == "Foo-Bar");
cannam@147 85
cannam@147 86 KJ_EXPECT(KJ_ASSERT_NONNULL(table->stringToId("Date")) == HttpHeaderId::DATE);
cannam@147 87 KJ_EXPECT(KJ_ASSERT_NONNULL(table->stringToId("dATE")) == HttpHeaderId::DATE);
cannam@147 88 KJ_EXPECT(KJ_ASSERT_NONNULL(table->stringToId("Foo-Bar")) == fooBar);
cannam@147 89 KJ_EXPECT(KJ_ASSERT_NONNULL(table->stringToId("foo-BAR")) == fooBar);
cannam@147 90 KJ_EXPECT(table->stringToId("foobar") == nullptr);
cannam@147 91 KJ_EXPECT(table->stringToId("barfoo") == nullptr);
cannam@147 92 }
cannam@147 93
cannam@147 94 KJ_TEST("HttpHeaders::parseRequest") {
cannam@147 95 HttpHeaderTable::Builder builder;
cannam@147 96
cannam@147 97 auto fooBar = builder.add("Foo-Bar");
cannam@147 98 auto bazQux = builder.add("baz-qux");
cannam@147 99
cannam@147 100 auto table = builder.build();
cannam@147 101
cannam@147 102 HttpHeaders headers(*table);
cannam@147 103 auto text = kj::heapString(
cannam@147 104 "POST /some/path \t HTTP/1.1\r\n"
cannam@147 105 "Foo-BaR: Baz\r\n"
cannam@147 106 "Host: example.com\r\n"
cannam@147 107 "Content-Length: 123\r\n"
cannam@147 108 "DATE: early\r\n"
cannam@147 109 "other-Header: yep\r\n"
cannam@147 110 "\r\n");
cannam@147 111 auto result = KJ_ASSERT_NONNULL(headers.tryParseRequest(text.asArray()));
cannam@147 112
cannam@147 113 KJ_EXPECT(result.method == HttpMethod::POST);
cannam@147 114 KJ_EXPECT(result.url == "/some/path");
cannam@147 115 KJ_EXPECT(KJ_ASSERT_NONNULL(headers.get(HttpHeaderId::HOST)) == "example.com");
cannam@147 116 KJ_EXPECT(KJ_ASSERT_NONNULL(headers.get(HttpHeaderId::DATE)) == "early");
cannam@147 117 KJ_EXPECT(KJ_ASSERT_NONNULL(headers.get(fooBar)) == "Baz");
cannam@147 118 KJ_EXPECT(headers.get(bazQux) == nullptr);
cannam@147 119 KJ_EXPECT(headers.get(HttpHeaderId::CONTENT_TYPE) == nullptr);
cannam@147 120 KJ_EXPECT(result.connectionHeaders.contentLength == "123");
cannam@147 121 KJ_EXPECT(result.connectionHeaders.transferEncoding == nullptr);
cannam@147 122
cannam@147 123 std::map<kj::StringPtr, kj::StringPtr> unpackedHeaders;
cannam@147 124 headers.forEach([&](kj::StringPtr name, kj::StringPtr value) {
cannam@147 125 KJ_EXPECT(unpackedHeaders.insert(std::make_pair(name, value)).second);
cannam@147 126 });
cannam@147 127 KJ_EXPECT(unpackedHeaders.size() == 4);
cannam@147 128 KJ_EXPECT(unpackedHeaders["Host"] == "example.com");
cannam@147 129 KJ_EXPECT(unpackedHeaders["Date"] == "early");
cannam@147 130 KJ_EXPECT(unpackedHeaders["Foo-Bar"] == "Baz");
cannam@147 131 KJ_EXPECT(unpackedHeaders["other-Header"] == "yep");
cannam@147 132
cannam@147 133 KJ_EXPECT(headers.serializeRequest(result.method, result.url, result.connectionHeaders) ==
cannam@147 134 "POST /some/path HTTP/1.1\r\n"
cannam@147 135 "Content-Length: 123\r\n"
cannam@147 136 "Host: example.com\r\n"
cannam@147 137 "Date: early\r\n"
cannam@147 138 "Foo-Bar: Baz\r\n"
cannam@147 139 "other-Header: yep\r\n"
cannam@147 140 "\r\n");
cannam@147 141 }
cannam@147 142
cannam@147 143 KJ_TEST("HttpHeaders::parseResponse") {
cannam@147 144 HttpHeaderTable::Builder builder;
cannam@147 145
cannam@147 146 auto fooBar = builder.add("Foo-Bar");
cannam@147 147 auto bazQux = builder.add("baz-qux");
cannam@147 148
cannam@147 149 auto table = builder.build();
cannam@147 150
cannam@147 151 HttpHeaders headers(*table);
cannam@147 152 auto text = kj::heapString(
cannam@147 153 "HTTP/1.1\t\t 418\t I'm a teapot\r\n"
cannam@147 154 "Foo-BaR: Baz\r\n"
cannam@147 155 "Host: example.com\r\n"
cannam@147 156 "Content-Length: 123\r\n"
cannam@147 157 "DATE: early\r\n"
cannam@147 158 "other-Header: yep\r\n"
cannam@147 159 "\r\n");
cannam@147 160 auto result = KJ_ASSERT_NONNULL(headers.tryParseResponse(text.asArray()));
cannam@147 161
cannam@147 162 KJ_EXPECT(result.statusCode == 418);
cannam@147 163 KJ_EXPECT(result.statusText == "I'm a teapot");
cannam@147 164 KJ_EXPECT(KJ_ASSERT_NONNULL(headers.get(HttpHeaderId::HOST)) == "example.com");
cannam@147 165 KJ_EXPECT(KJ_ASSERT_NONNULL(headers.get(HttpHeaderId::DATE)) == "early");
cannam@147 166 KJ_EXPECT(KJ_ASSERT_NONNULL(headers.get(fooBar)) == "Baz");
cannam@147 167 KJ_EXPECT(headers.get(bazQux) == nullptr);
cannam@147 168 KJ_EXPECT(headers.get(HttpHeaderId::CONTENT_TYPE) == nullptr);
cannam@147 169 KJ_EXPECT(result.connectionHeaders.contentLength == "123");
cannam@147 170 KJ_EXPECT(result.connectionHeaders.transferEncoding == nullptr);
cannam@147 171
cannam@147 172 std::map<kj::StringPtr, kj::StringPtr> unpackedHeaders;
cannam@147 173 headers.forEach([&](kj::StringPtr name, kj::StringPtr value) {
cannam@147 174 KJ_EXPECT(unpackedHeaders.insert(std::make_pair(name, value)).second);
cannam@147 175 });
cannam@147 176 KJ_EXPECT(unpackedHeaders.size() == 4);
cannam@147 177 KJ_EXPECT(unpackedHeaders["Host"] == "example.com");
cannam@147 178 KJ_EXPECT(unpackedHeaders["Date"] == "early");
cannam@147 179 KJ_EXPECT(unpackedHeaders["Foo-Bar"] == "Baz");
cannam@147 180 KJ_EXPECT(unpackedHeaders["other-Header"] == "yep");
cannam@147 181
cannam@147 182 KJ_EXPECT(headers.serializeResponse(
cannam@147 183 result.statusCode, result.statusText, result.connectionHeaders) ==
cannam@147 184 "HTTP/1.1 418 I'm a teapot\r\n"
cannam@147 185 "Content-Length: 123\r\n"
cannam@147 186 "Host: example.com\r\n"
cannam@147 187 "Date: early\r\n"
cannam@147 188 "Foo-Bar: Baz\r\n"
cannam@147 189 "other-Header: yep\r\n"
cannam@147 190 "\r\n");
cannam@147 191 }
cannam@147 192
cannam@147 193 KJ_TEST("HttpHeaders parse invalid") {
cannam@147 194 auto table = HttpHeaderTable::Builder().build();
cannam@147 195 HttpHeaders headers(*table);
cannam@147 196
cannam@147 197 // NUL byte in request.
cannam@147 198 KJ_EXPECT(headers.tryParseRequest(kj::heapString(
cannam@147 199 "POST \0 /some/path \t HTTP/1.1\r\n"
cannam@147 200 "Foo-BaR: Baz\r\n"
cannam@147 201 "Host: example.com\r\n"
cannam@147 202 "DATE: early\r\n"
cannam@147 203 "other-Header: yep\r\n"
cannam@147 204 "\r\n")) == nullptr);
cannam@147 205
cannam@147 206 // Control character in header name.
cannam@147 207 KJ_EXPECT(headers.tryParseRequest(kj::heapString(
cannam@147 208 "POST /some/path \t HTTP/1.1\r\n"
cannam@147 209 "Foo-BaR: Baz\r\n"
cannam@147 210 "Cont\001ent-Length: 123\r\n"
cannam@147 211 "DATE: early\r\n"
cannam@147 212 "other-Header: yep\r\n"
cannam@147 213 "\r\n")) == nullptr);
cannam@147 214
cannam@147 215 // Separator character in header name.
cannam@147 216 KJ_EXPECT(headers.tryParseRequest(kj::heapString(
cannam@147 217 "POST /some/path \t HTTP/1.1\r\n"
cannam@147 218 "Foo-BaR: Baz\r\n"
cannam@147 219 "Host: example.com\r\n"
cannam@147 220 "DATE/: early\r\n"
cannam@147 221 "other-Header: yep\r\n"
cannam@147 222 "\r\n")) == nullptr);
cannam@147 223
cannam@147 224 // Response status code not numeric.
cannam@147 225 KJ_EXPECT(headers.tryParseResponse(kj::heapString(
cannam@147 226 "HTTP/1.1\t\t abc\t I'm a teapot\r\n"
cannam@147 227 "Foo-BaR: Baz\r\n"
cannam@147 228 "Host: example.com\r\n"
cannam@147 229 "DATE: early\r\n"
cannam@147 230 "other-Header: yep\r\n"
cannam@147 231 "\r\n")) == nullptr);
cannam@147 232 }
cannam@147 233
cannam@147 234 // =======================================================================================
cannam@147 235
cannam@147 236 class ReadFragmenter final: public kj::AsyncIoStream {
cannam@147 237 public:
cannam@147 238 ReadFragmenter(AsyncIoStream& inner, size_t limit): inner(inner), limit(limit) {}
cannam@147 239
cannam@147 240 Promise<size_t> read(void* buffer, size_t minBytes, size_t maxBytes) override {
cannam@147 241 return inner.read(buffer, minBytes, kj::max(minBytes, kj::min(limit, maxBytes)));
cannam@147 242 }
cannam@147 243 Promise<size_t> tryRead(void* buffer, size_t minBytes, size_t maxBytes) override {
cannam@147 244 return inner.tryRead(buffer, minBytes, kj::max(minBytes, kj::min(limit, maxBytes)));
cannam@147 245 }
cannam@147 246
cannam@147 247 Maybe<uint64_t> tryGetLength() override { return inner.tryGetLength(); }
cannam@147 248
cannam@147 249 Promise<uint64_t> pumpTo(AsyncOutputStream& output, uint64_t amount) override {
cannam@147 250 return inner.pumpTo(output, amount);
cannam@147 251 }
cannam@147 252
cannam@147 253 Promise<void> write(const void* buffer, size_t size) override {
cannam@147 254 return inner.write(buffer, size);
cannam@147 255 }
cannam@147 256 Promise<void> write(ArrayPtr<const ArrayPtr<const byte>> pieces) override {
cannam@147 257 return inner.write(pieces);
cannam@147 258 }
cannam@147 259
cannam@147 260 Maybe<Promise<uint64_t>> tryPumpFrom(AsyncInputStream& input, uint64_t amount) override {
cannam@147 261 return inner.tryPumpFrom(input, amount);
cannam@147 262 }
cannam@147 263
cannam@147 264 void shutdownWrite() override {
cannam@147 265 return inner.shutdownWrite();
cannam@147 266 }
cannam@147 267
cannam@147 268 void abortRead() override { return inner.abortRead(); }
cannam@147 269
cannam@147 270 void getsockopt(int level, int option, void* value, uint* length) override {
cannam@147 271 return inner.getsockopt(level, option, value, length);
cannam@147 272 }
cannam@147 273 void setsockopt(int level, int option, const void* value, uint length) override {
cannam@147 274 return inner.setsockopt(level, option, value, length);
cannam@147 275 }
cannam@147 276
cannam@147 277 void getsockname(struct sockaddr* addr, uint* length) override {
cannam@147 278 return inner.getsockname(addr, length);
cannam@147 279 }
cannam@147 280 void getpeername(struct sockaddr* addr, uint* length) override {
cannam@147 281 return inner.getsockname(addr, length);
cannam@147 282 }
cannam@147 283
cannam@147 284 private:
cannam@147 285 kj::AsyncIoStream& inner;
cannam@147 286 size_t limit;
cannam@147 287 };
cannam@147 288
cannam@147 289 template <typename T>
cannam@147 290 class InitializeableArray: public Array<T> {
cannam@147 291 public:
cannam@147 292 InitializeableArray(std::initializer_list<T> init)
cannam@147 293 : Array<T>(kj::heapArray(init)) {}
cannam@147 294 };
cannam@147 295
cannam@147 296 enum Side { BOTH, CLIENT_ONLY, SERVER_ONLY };
cannam@147 297
cannam@147 298 struct HeaderTestCase {
cannam@147 299 HttpHeaderId id;
cannam@147 300 kj::StringPtr value;
cannam@147 301 };
cannam@147 302
cannam@147 303 struct HttpRequestTestCase {
cannam@147 304 kj::StringPtr raw;
cannam@147 305
cannam@147 306 HttpMethod method;
cannam@147 307 kj::StringPtr path;
cannam@147 308 InitializeableArray<HeaderTestCase> requestHeaders;
cannam@147 309 kj::Maybe<uint64_t> requestBodySize;
cannam@147 310 InitializeableArray<kj::StringPtr> requestBodyParts;
cannam@147 311
cannam@147 312 Side side = BOTH;
cannam@147 313
cannam@147 314 // TODO(cleanup): Delete this constructor if/when we move to C++14.
cannam@147 315 HttpRequestTestCase(kj::StringPtr raw, HttpMethod method, kj::StringPtr path,
cannam@147 316 InitializeableArray<HeaderTestCase> requestHeaders,
cannam@147 317 kj::Maybe<uint64_t> requestBodySize,
cannam@147 318 InitializeableArray<kj::StringPtr> requestBodyParts,
cannam@147 319 Side side = BOTH)
cannam@147 320 : raw(raw), method(method), path(path), requestHeaders(kj::mv(requestHeaders)),
cannam@147 321 requestBodySize(requestBodySize), requestBodyParts(kj::mv(requestBodyParts)),
cannam@147 322 side(side) {}
cannam@147 323 };
cannam@147 324
cannam@147 325 struct HttpResponseTestCase {
cannam@147 326 kj::StringPtr raw;
cannam@147 327
cannam@147 328 uint64_t statusCode;
cannam@147 329 kj::StringPtr statusText;
cannam@147 330 InitializeableArray<HeaderTestCase> responseHeaders;
cannam@147 331 kj::Maybe<uint64_t> responseBodySize;
cannam@147 332 InitializeableArray<kj::StringPtr> responseBodyParts;
cannam@147 333
cannam@147 334 HttpMethod method = HttpMethod::GET;
cannam@147 335
cannam@147 336 Side side = BOTH;
cannam@147 337
cannam@147 338 // TODO(cleanup): Delete this constructor if/when we move to C++14.
cannam@147 339 HttpResponseTestCase(kj::StringPtr raw, uint64_t statusCode, kj::StringPtr statusText,
cannam@147 340 InitializeableArray<HeaderTestCase> responseHeaders,
cannam@147 341 kj::Maybe<uint64_t> responseBodySize,
cannam@147 342 InitializeableArray<kj::StringPtr> responseBodyParts,
cannam@147 343 HttpMethod method = HttpMethod::GET,
cannam@147 344 Side side = BOTH)
cannam@147 345 : raw(raw), statusCode(statusCode), statusText(statusText),
cannam@147 346 responseHeaders(kj::mv(responseHeaders)), responseBodySize(responseBodySize),
cannam@147 347 responseBodyParts(kj::mv(responseBodyParts)), method(method), side(side) {}
cannam@147 348 };
cannam@147 349
cannam@147 350 struct HttpTestCase {
cannam@147 351 HttpRequestTestCase request;
cannam@147 352 HttpResponseTestCase response;
cannam@147 353 };
cannam@147 354
cannam@147 355 kj::Promise<void> writeEach(kj::AsyncOutputStream& out, kj::ArrayPtr<const kj::StringPtr> parts) {
cannam@147 356 if (parts.size() == 0) return kj::READY_NOW;
cannam@147 357
cannam@147 358 return out.write(parts[0].begin(), parts[0].size())
cannam@147 359 .then([&out,parts]() {
cannam@147 360 return writeEach(out, parts.slice(1, parts.size()));
cannam@147 361 });
cannam@147 362 }
cannam@147 363
cannam@147 364 kj::Promise<void> expectRead(kj::AsyncInputStream& in, kj::StringPtr expected) {
cannam@147 365 if (expected.size() == 0) return kj::READY_NOW;
cannam@147 366
cannam@147 367 auto buffer = kj::heapArray<char>(expected.size());
cannam@147 368
cannam@147 369 auto promise = in.tryRead(buffer.begin(), 1, buffer.size());
cannam@147 370 return promise.then(kj::mvCapture(buffer, [&in,expected](kj::Array<char> buffer, size_t amount) {
cannam@147 371 if (amount == 0) {
cannam@147 372 KJ_FAIL_ASSERT("expected data never sent", expected);
cannam@147 373 }
cannam@147 374
cannam@147 375 auto actual = buffer.slice(0, amount);
cannam@147 376 if (memcmp(actual.begin(), expected.begin(), actual.size()) != 0) {
cannam@147 377 KJ_FAIL_ASSERT("data from stream doesn't match expected", expected, actual);
cannam@147 378 }
cannam@147 379
cannam@147 380 return expectRead(in, expected.slice(amount));
cannam@147 381 }));
cannam@147 382 }
cannam@147 383
cannam@147 384 void testHttpClientRequest(kj::AsyncIoContext& io, const HttpRequestTestCase& testCase) {
cannam@147 385 auto pipe = io.provider->newTwoWayPipe();
cannam@147 386
cannam@147 387 auto serverTask = expectRead(*pipe.ends[1], testCase.raw).then([&]() {
cannam@147 388 static const char SIMPLE_RESPONSE[] =
cannam@147 389 "HTTP/1.1 200 OK\r\n"
cannam@147 390 "Content-Length: 0\r\n"
cannam@147 391 "\r\n";
cannam@147 392 return pipe.ends[1]->write(SIMPLE_RESPONSE, strlen(SIMPLE_RESPONSE));
cannam@147 393 }).then([&]() -> kj::Promise<void> {
cannam@147 394 return kj::NEVER_DONE;
cannam@147 395 });
cannam@147 396
cannam@147 397 HttpHeaderTable table;
cannam@147 398 auto client = newHttpClient(table, *pipe.ends[0]);
cannam@147 399
cannam@147 400 HttpHeaders headers(table);
cannam@147 401 for (auto& header: testCase.requestHeaders) {
cannam@147 402 headers.set(header.id, header.value);
cannam@147 403 }
cannam@147 404
cannam@147 405 auto request = client->request(testCase.method, testCase.path, headers, testCase.requestBodySize);
cannam@147 406 if (testCase.requestBodyParts.size() > 0) {
cannam@147 407 writeEach(*request.body, testCase.requestBodyParts).wait(io.waitScope);
cannam@147 408 }
cannam@147 409 request.body = nullptr;
cannam@147 410 auto clientTask = request.response
cannam@147 411 .then([&](HttpClient::Response&& response) {
cannam@147 412 auto promise = response.body->readAllText();
cannam@147 413 return promise.attach(kj::mv(response.body));
cannam@147 414 }).ignoreResult();
cannam@147 415
cannam@147 416 serverTask.exclusiveJoin(kj::mv(clientTask)).wait(io.waitScope);
cannam@147 417
cannam@147 418 // Verify no more data written by client.
cannam@147 419 client = nullptr;
cannam@147 420 pipe.ends[0]->shutdownWrite();
cannam@147 421 KJ_EXPECT(pipe.ends[1]->readAllText().wait(io.waitScope) == "");
cannam@147 422 }
cannam@147 423
cannam@147 424 void testHttpClientResponse(kj::AsyncIoContext& io, const HttpResponseTestCase& testCase,
cannam@147 425 size_t readFragmentSize) {
cannam@147 426 auto pipe = io.provider->newTwoWayPipe();
cannam@147 427 ReadFragmenter fragmenter(*pipe.ends[0], readFragmentSize);
cannam@147 428
cannam@147 429 auto expectedReqText = testCase.method == HttpMethod::GET || testCase.method == HttpMethod::HEAD
cannam@147 430 ? kj::str(testCase.method, " / HTTP/1.1\r\n\r\n")
cannam@147 431 : kj::str(testCase.method, " / HTTP/1.1\r\nContent-Length: 0\r\n");
cannam@147 432
cannam@147 433 auto serverTask = expectRead(*pipe.ends[1], expectedReqText).then([&]() {
cannam@147 434 return pipe.ends[1]->write(testCase.raw.begin(), testCase.raw.size());
cannam@147 435 }).then([&]() -> kj::Promise<void> {
cannam@147 436 pipe.ends[1]->shutdownWrite();
cannam@147 437 return kj::NEVER_DONE;
cannam@147 438 });
cannam@147 439
cannam@147 440 HttpHeaderTable table;
cannam@147 441 auto client = newHttpClient(table, fragmenter);
cannam@147 442
cannam@147 443 HttpHeaders headers(table);
cannam@147 444 auto request = client->request(testCase.method, "/", headers, uint64_t(0));
cannam@147 445 request.body = nullptr;
cannam@147 446 auto clientTask = request.response
cannam@147 447 .then([&](HttpClient::Response&& response) {
cannam@147 448 KJ_EXPECT(response.statusCode == testCase.statusCode);
cannam@147 449 KJ_EXPECT(response.statusText == testCase.statusText);
cannam@147 450
cannam@147 451 for (auto& header: testCase.responseHeaders) {
cannam@147 452 KJ_EXPECT(KJ_ASSERT_NONNULL(response.headers->get(header.id)) == header.value);
cannam@147 453 }
cannam@147 454 auto promise = response.body->readAllText();
cannam@147 455 return promise.attach(kj::mv(response.body));
cannam@147 456 }).then([&](kj::String body) {
cannam@147 457 KJ_EXPECT(body == kj::strArray(testCase.responseBodyParts, ""), body);
cannam@147 458 });
cannam@147 459
cannam@147 460 serverTask.exclusiveJoin(kj::mv(clientTask)).wait(io.waitScope);
cannam@147 461
cannam@147 462 // Verify no more data written by client.
cannam@147 463 client = nullptr;
cannam@147 464 pipe.ends[0]->shutdownWrite();
cannam@147 465 KJ_EXPECT(pipe.ends[1]->readAllText().wait(io.waitScope) == "");
cannam@147 466 }
cannam@147 467
cannam@147 468 class TestHttpService final: public HttpService {
cannam@147 469 public:
cannam@147 470 TestHttpService(const HttpRequestTestCase& expectedRequest,
cannam@147 471 const HttpResponseTestCase& response,
cannam@147 472 HttpHeaderTable& table)
cannam@147 473 : singleExpectedRequest(&expectedRequest),
cannam@147 474 singleResponse(&response),
cannam@147 475 responseHeaders(table) {}
cannam@147 476 TestHttpService(kj::ArrayPtr<const HttpTestCase> testCases,
cannam@147 477 HttpHeaderTable& table)
cannam@147 478 : singleExpectedRequest(nullptr),
cannam@147 479 singleResponse(nullptr),
cannam@147 480 testCases(testCases),
cannam@147 481 responseHeaders(table) {}
cannam@147 482
cannam@147 483 uint getRequestCount() { return requestCount; }
cannam@147 484
cannam@147 485 kj::Promise<void> request(
cannam@147 486 HttpMethod method, kj::StringPtr url, const HttpHeaders& headers,
cannam@147 487 kj::AsyncInputStream& requestBody, Response& responseSender) override {
cannam@147 488 auto& expectedRequest = testCases == nullptr ? *singleExpectedRequest :
cannam@147 489 testCases[requestCount % testCases.size()].request;
cannam@147 490 auto& response = testCases == nullptr ? *singleResponse :
cannam@147 491 testCases[requestCount % testCases.size()].response;
cannam@147 492
cannam@147 493 ++requestCount;
cannam@147 494
cannam@147 495 KJ_EXPECT(method == expectedRequest.method, method);
cannam@147 496 KJ_EXPECT(url == expectedRequest.path, url);
cannam@147 497
cannam@147 498 for (auto& header: expectedRequest.requestHeaders) {
cannam@147 499 KJ_EXPECT(KJ_ASSERT_NONNULL(headers.get(header.id)) == header.value);
cannam@147 500 }
cannam@147 501
cannam@147 502 auto size = requestBody.tryGetLength();
cannam@147 503 KJ_IF_MAYBE(expectedSize, expectedRequest.requestBodySize) {
cannam@147 504 KJ_IF_MAYBE(s, size) {
cannam@147 505 KJ_EXPECT(*s == *expectedSize, *s);
cannam@147 506 } else {
cannam@147 507 KJ_FAIL_EXPECT("tryGetLength() returned nullptr; expected known size");
cannam@147 508 }
cannam@147 509 } else {
cannam@147 510 KJ_EXPECT(size == nullptr);
cannam@147 511 }
cannam@147 512
cannam@147 513 return requestBody.readAllText()
cannam@147 514 .then([this,&expectedRequest,&response,&responseSender](kj::String text) {
cannam@147 515 KJ_EXPECT(text == kj::strArray(expectedRequest.requestBodyParts, ""), text);
cannam@147 516
cannam@147 517 responseHeaders.clear();
cannam@147 518 for (auto& header: response.responseHeaders) {
cannam@147 519 responseHeaders.set(header.id, header.value);
cannam@147 520 }
cannam@147 521
cannam@147 522 auto stream = responseSender.send(response.statusCode, response.statusText,
cannam@147 523 responseHeaders, response.responseBodySize);
cannam@147 524 auto promise = writeEach(*stream, response.responseBodyParts);
cannam@147 525 return promise.attach(kj::mv(stream));
cannam@147 526 });
cannam@147 527 }
cannam@147 528
cannam@147 529 private:
cannam@147 530 const HttpRequestTestCase* singleExpectedRequest;
cannam@147 531 const HttpResponseTestCase* singleResponse;
cannam@147 532 kj::ArrayPtr<const HttpTestCase> testCases;
cannam@147 533 HttpHeaders responseHeaders;
cannam@147 534 uint requestCount = 0;
cannam@147 535 };
cannam@147 536
cannam@147 537 void testHttpServerRequest(kj::AsyncIoContext& io,
cannam@147 538 const HttpRequestTestCase& requestCase,
cannam@147 539 const HttpResponseTestCase& responseCase) {
cannam@147 540 auto pipe = io.provider->newTwoWayPipe();
cannam@147 541
cannam@147 542 HttpHeaderTable table;
cannam@147 543 TestHttpService service(requestCase, responseCase, table);
cannam@147 544 HttpServer server(io.provider->getTimer(), table, service);
cannam@147 545
cannam@147 546 auto listenTask = server.listenHttp(kj::mv(pipe.ends[0]));
cannam@147 547
cannam@147 548 pipe.ends[1]->write(requestCase.raw.begin(), requestCase.raw.size()).wait(io.waitScope);
cannam@147 549 pipe.ends[1]->shutdownWrite();
cannam@147 550
cannam@147 551 expectRead(*pipe.ends[1], responseCase.raw).wait(io.waitScope);
cannam@147 552
cannam@147 553 listenTask.wait(io.waitScope);
cannam@147 554
cannam@147 555 KJ_EXPECT(service.getRequestCount() == 1);
cannam@147 556 }
cannam@147 557
cannam@147 558 kj::ArrayPtr<const HttpRequestTestCase> requestTestCases() {
cannam@147 559 static const auto HUGE_STRING = kj::strArray(kj::repeat("abcdefgh", 4096), "");
cannam@147 560 static const auto HUGE_REQUEST = kj::str(
cannam@147 561 "GET / HTTP/1.1\r\n"
cannam@147 562 "Host: ", HUGE_STRING, "\r\n"
cannam@147 563 "\r\n");
cannam@147 564
cannam@147 565 static const HttpRequestTestCase REQUEST_TEST_CASES[] {
cannam@147 566 {
cannam@147 567 "GET /foo/bar HTTP/1.1\r\n"
cannam@147 568 "Host: example.com\r\n"
cannam@147 569 "\r\n",
cannam@147 570
cannam@147 571 HttpMethod::GET,
cannam@147 572 "/foo/bar",
cannam@147 573 {{HttpHeaderId::HOST, "example.com"}},
cannam@147 574 nullptr, {},
cannam@147 575 },
cannam@147 576
cannam@147 577 {
cannam@147 578 "HEAD /foo/bar HTTP/1.1\r\n"
cannam@147 579 "Host: example.com\r\n"
cannam@147 580 "\r\n",
cannam@147 581
cannam@147 582 HttpMethod::HEAD,
cannam@147 583 "/foo/bar",
cannam@147 584 {{HttpHeaderId::HOST, "example.com"}},
cannam@147 585 nullptr, {},
cannam@147 586 },
cannam@147 587
cannam@147 588 {
cannam@147 589 "POST / HTTP/1.1\r\n"
cannam@147 590 "Content-Length: 9\r\n"
cannam@147 591 "Host: example.com\r\n"
cannam@147 592 "Content-Type: text/plain\r\n"
cannam@147 593 "\r\n"
cannam@147 594 "foobarbaz",
cannam@147 595
cannam@147 596 HttpMethod::POST,
cannam@147 597 "/",
cannam@147 598 {
cannam@147 599 {HttpHeaderId::HOST, "example.com"},
cannam@147 600 {HttpHeaderId::CONTENT_TYPE, "text/plain"},
cannam@147 601 },
cannam@147 602 9, { "foo", "bar", "baz" },
cannam@147 603 },
cannam@147 604
cannam@147 605 {
cannam@147 606 "POST / HTTP/1.1\r\n"
cannam@147 607 "Transfer-Encoding: chunked\r\n"
cannam@147 608 "Host: example.com\r\n"
cannam@147 609 "Content-Type: text/plain\r\n"
cannam@147 610 "\r\n"
cannam@147 611 "3\r\n"
cannam@147 612 "foo\r\n"
cannam@147 613 "6\r\n"
cannam@147 614 "barbaz\r\n"
cannam@147 615 "0\r\n"
cannam@147 616 "\r\n",
cannam@147 617
cannam@147 618 HttpMethod::POST,
cannam@147 619 "/",
cannam@147 620 {
cannam@147 621 {HttpHeaderId::HOST, "example.com"},
cannam@147 622 {HttpHeaderId::CONTENT_TYPE, "text/plain"},
cannam@147 623 },
cannam@147 624 nullptr, { "foo", "barbaz" },
cannam@147 625 },
cannam@147 626
cannam@147 627 {
cannam@147 628 "POST / HTTP/1.1\r\n"
cannam@147 629 "Transfer-Encoding: chunked\r\n"
cannam@147 630 "Host: example.com\r\n"
cannam@147 631 "Content-Type: text/plain\r\n"
cannam@147 632 "\r\n"
cannam@147 633 "1d\r\n"
cannam@147 634 "0123456789abcdef0123456789abc\r\n"
cannam@147 635 "0\r\n"
cannam@147 636 "\r\n",
cannam@147 637
cannam@147 638 HttpMethod::POST,
cannam@147 639 "/",
cannam@147 640 {
cannam@147 641 {HttpHeaderId::HOST, "example.com"},
cannam@147 642 {HttpHeaderId::CONTENT_TYPE, "text/plain"},
cannam@147 643 },
cannam@147 644 nullptr, { "0123456789abcdef0123456789abc" },
cannam@147 645 },
cannam@147 646
cannam@147 647 {
cannam@147 648 HUGE_REQUEST,
cannam@147 649
cannam@147 650 HttpMethod::GET,
cannam@147 651 "/",
cannam@147 652 {{HttpHeaderId::HOST, HUGE_STRING}},
cannam@147 653 nullptr, {}
cannam@147 654 },
cannam@147 655 };
cannam@147 656
cannam@147 657 // TODO(cleanup): A bug in GCC 4.8, fixed in 4.9, prevents REQUEST_TEST_CASES from implicitly
cannam@147 658 // casting to our return type.
cannam@147 659 return kj::arrayPtr(REQUEST_TEST_CASES, kj::size(REQUEST_TEST_CASES));
cannam@147 660 }
cannam@147 661
cannam@147 662 kj::ArrayPtr<const HttpResponseTestCase> responseTestCases() {
cannam@147 663 static const HttpResponseTestCase RESPONSE_TEST_CASES[] {
cannam@147 664 {
cannam@147 665 "HTTP/1.1 200 OK\r\n"
cannam@147 666 "Content-Type: text/plain\r\n"
cannam@147 667 "Connection: close\r\n"
cannam@147 668 "\r\n"
cannam@147 669 "baz qux",
cannam@147 670
cannam@147 671 200, "OK",
cannam@147 672 {{HttpHeaderId::CONTENT_TYPE, "text/plain"}},
cannam@147 673 nullptr, {"baz qux"},
cannam@147 674
cannam@147 675 HttpMethod::GET,
cannam@147 676 CLIENT_ONLY, // Server never sends connection: close
cannam@147 677 },
cannam@147 678
cannam@147 679 {
cannam@147 680 "HTTP/1.1 200 OK\r\n"
cannam@147 681 "Content-Length: 123\r\n"
cannam@147 682 "Content-Type: text/plain\r\n"
cannam@147 683 "\r\n",
cannam@147 684
cannam@147 685 200, "OK",
cannam@147 686 {{HttpHeaderId::CONTENT_TYPE, "text/plain"}},
cannam@147 687 123, {},
cannam@147 688
cannam@147 689 HttpMethod::HEAD,
cannam@147 690 },
cannam@147 691
cannam@147 692 {
cannam@147 693 "HTTP/1.1 200 OK\r\n"
cannam@147 694 "Content-Length: 8\r\n"
cannam@147 695 "Content-Type: text/plain\r\n"
cannam@147 696 "\r\n"
cannam@147 697 "quxcorge",
cannam@147 698
cannam@147 699 200, "OK",
cannam@147 700 {{HttpHeaderId::CONTENT_TYPE, "text/plain"}},
cannam@147 701 8, { "qux", "corge" }
cannam@147 702 },
cannam@147 703
cannam@147 704 {
cannam@147 705 "HTTP/1.1 200 OK\r\n"
cannam@147 706 "Transfer-Encoding: chunked\r\n"
cannam@147 707 "Content-Type: text/plain\r\n"
cannam@147 708 "\r\n"
cannam@147 709 "3\r\n"
cannam@147 710 "qux\r\n"
cannam@147 711 "5\r\n"
cannam@147 712 "corge\r\n"
cannam@147 713 "0\r\n"
cannam@147 714 "\r\n",
cannam@147 715
cannam@147 716 200, "OK",
cannam@147 717 {{HttpHeaderId::CONTENT_TYPE, "text/plain"}},
cannam@147 718 nullptr, { "qux", "corge" }
cannam@147 719 },
cannam@147 720 };
cannam@147 721
cannam@147 722 // TODO(cleanup): A bug in GCC 4.8, fixed in 4.9, prevents RESPONSE_TEST_CASES from implicitly
cannam@147 723 // casting to our return type.
cannam@147 724 return kj::arrayPtr(RESPONSE_TEST_CASES, kj::size(RESPONSE_TEST_CASES));
cannam@147 725 }
cannam@147 726
cannam@147 727 KJ_TEST("HttpClient requests") {
cannam@147 728 auto io = kj::setupAsyncIo();
cannam@147 729
cannam@147 730 for (auto& testCase: requestTestCases()) {
cannam@147 731 if (testCase.side == SERVER_ONLY) continue;
cannam@147 732 KJ_CONTEXT(testCase.raw);
cannam@147 733 testHttpClientRequest(io, testCase);
cannam@147 734 }
cannam@147 735 }
cannam@147 736
cannam@147 737 KJ_TEST("HttpClient responses") {
cannam@147 738 auto io = kj::setupAsyncIo();
cannam@147 739 size_t FRAGMENT_SIZES[] = { 1, 2, 3, 4, 5, 6, 7, 8, 16, 31, kj::maxValue };
cannam@147 740
cannam@147 741 for (auto& testCase: responseTestCases()) {
cannam@147 742 if (testCase.side == SERVER_ONLY) continue;
cannam@147 743 for (size_t fragmentSize: FRAGMENT_SIZES) {
cannam@147 744 KJ_CONTEXT(testCase.raw, fragmentSize);
cannam@147 745 testHttpClientResponse(io, testCase, fragmentSize);
cannam@147 746 }
cannam@147 747 }
cannam@147 748 }
cannam@147 749
cannam@147 750 KJ_TEST("HttpServer requests") {
cannam@147 751 HttpResponseTestCase RESPONSE = {
cannam@147 752 "HTTP/1.1 200 OK\r\n"
cannam@147 753 "Content-Length: 3\r\n"
cannam@147 754 "\r\n"
cannam@147 755 "foo",
cannam@147 756
cannam@147 757 200, "OK",
cannam@147 758 {},
cannam@147 759 3, {"foo"}
cannam@147 760 };
cannam@147 761
cannam@147 762 HttpResponseTestCase HEAD_RESPONSE = {
cannam@147 763 "HTTP/1.1 200 OK\r\n"
cannam@147 764 "Content-Length: 3\r\n"
cannam@147 765 "\r\n",
cannam@147 766
cannam@147 767 200, "OK",
cannam@147 768 {},
cannam@147 769 3, {"foo"}
cannam@147 770 };
cannam@147 771
cannam@147 772 auto io = kj::setupAsyncIo();
cannam@147 773
cannam@147 774 for (auto& testCase: requestTestCases()) {
cannam@147 775 if (testCase.side == CLIENT_ONLY) continue;
cannam@147 776 KJ_CONTEXT(testCase.raw);
cannam@147 777 testHttpServerRequest(io, testCase,
cannam@147 778 testCase.method == HttpMethod::HEAD ? HEAD_RESPONSE : RESPONSE);
cannam@147 779 }
cannam@147 780 }
cannam@147 781
cannam@147 782 KJ_TEST("HttpServer responses") {
cannam@147 783 HttpRequestTestCase REQUEST = {
cannam@147 784 "GET / HTTP/1.1\r\n"
cannam@147 785 "\r\n",
cannam@147 786
cannam@147 787 HttpMethod::GET,
cannam@147 788 "/",
cannam@147 789 {},
cannam@147 790 nullptr, {},
cannam@147 791 };
cannam@147 792
cannam@147 793 HttpRequestTestCase HEAD_REQUEST = {
cannam@147 794 "HEAD / HTTP/1.1\r\n"
cannam@147 795 "\r\n",
cannam@147 796
cannam@147 797 HttpMethod::HEAD,
cannam@147 798 "/",
cannam@147 799 {},
cannam@147 800 nullptr, {},
cannam@147 801 };
cannam@147 802
cannam@147 803 auto io = kj::setupAsyncIo();
cannam@147 804
cannam@147 805 for (auto& testCase: responseTestCases()) {
cannam@147 806 if (testCase.side == CLIENT_ONLY) continue;
cannam@147 807 KJ_CONTEXT(testCase.raw);
cannam@147 808 testHttpServerRequest(io,
cannam@147 809 testCase.method == HttpMethod::HEAD ? HEAD_REQUEST : REQUEST, testCase);
cannam@147 810 }
cannam@147 811 }
cannam@147 812
cannam@147 813 // -----------------------------------------------------------------------------
cannam@147 814
cannam@147 815 kj::ArrayPtr<const HttpTestCase> pipelineTestCases() {
cannam@147 816 static const HttpTestCase PIPELINE_TESTS[] = {
cannam@147 817 {
cannam@147 818 {
cannam@147 819 "GET / HTTP/1.1\r\n"
cannam@147 820 "\r\n",
cannam@147 821
cannam@147 822 HttpMethod::GET, "/", {}, nullptr, {},
cannam@147 823 },
cannam@147 824 {
cannam@147 825 "HTTP/1.1 200 OK\r\n"
cannam@147 826 "Content-Length: 7\r\n"
cannam@147 827 "\r\n"
cannam@147 828 "foo bar",
cannam@147 829
cannam@147 830 200, "OK", {}, 7, { "foo bar" }
cannam@147 831 },
cannam@147 832 },
cannam@147 833
cannam@147 834 {
cannam@147 835 {
cannam@147 836 "POST /foo HTTP/1.1\r\n"
cannam@147 837 "Content-Length: 6\r\n"
cannam@147 838 "\r\n"
cannam@147 839 "grault",
cannam@147 840
cannam@147 841 HttpMethod::POST, "/foo", {}, 6, { "grault" },
cannam@147 842 },
cannam@147 843 {
cannam@147 844 "HTTP/1.1 404 Not Found\r\n"
cannam@147 845 "Content-Length: 13\r\n"
cannam@147 846 "\r\n"
cannam@147 847 "baz qux corge",
cannam@147 848
cannam@147 849 404, "Not Found", {}, 13, { "baz qux corge" }
cannam@147 850 },
cannam@147 851 },
cannam@147 852
cannam@147 853 {
cannam@147 854 {
cannam@147 855 "POST /bar HTTP/1.1\r\n"
cannam@147 856 "Transfer-Encoding: chunked\r\n"
cannam@147 857 "\r\n"
cannam@147 858 "6\r\n"
cannam@147 859 "garply\r\n"
cannam@147 860 "5\r\n"
cannam@147 861 "waldo\r\n"
cannam@147 862 "0\r\n"
cannam@147 863 "\r\n",
cannam@147 864
cannam@147 865 HttpMethod::POST, "/bar", {}, nullptr, { "garply", "waldo" },
cannam@147 866 },
cannam@147 867 {
cannam@147 868 "HTTP/1.1 200 OK\r\n"
cannam@147 869 "Transfer-Encoding: chunked\r\n"
cannam@147 870 "\r\n"
cannam@147 871 "4\r\n"
cannam@147 872 "fred\r\n"
cannam@147 873 "5\r\n"
cannam@147 874 "plugh\r\n"
cannam@147 875 "0\r\n"
cannam@147 876 "\r\n",
cannam@147 877
cannam@147 878 200, "OK", {}, nullptr, { "fred", "plugh" }
cannam@147 879 },
cannam@147 880 },
cannam@147 881
cannam@147 882 {
cannam@147 883 {
cannam@147 884 "HEAD / HTTP/1.1\r\n"
cannam@147 885 "\r\n",
cannam@147 886
cannam@147 887 HttpMethod::HEAD, "/", {}, nullptr, {},
cannam@147 888 },
cannam@147 889 {
cannam@147 890 "HTTP/1.1 200 OK\r\n"
cannam@147 891 "Content-Length: 7\r\n"
cannam@147 892 "\r\n",
cannam@147 893
cannam@147 894 200, "OK", {}, 7, { "foo bar" }
cannam@147 895 },
cannam@147 896 },
cannam@147 897 };
cannam@147 898
cannam@147 899 // TODO(cleanup): A bug in GCC 4.8, fixed in 4.9, prevents RESPONSE_TEST_CASES from implicitly
cannam@147 900 // casting to our return type.
cannam@147 901 return kj::arrayPtr(PIPELINE_TESTS, kj::size(PIPELINE_TESTS));
cannam@147 902 }
cannam@147 903
cannam@147 904 KJ_TEST("HttpClient pipeline") {
cannam@147 905 auto PIPELINE_TESTS = pipelineTestCases();
cannam@147 906
cannam@147 907 auto io = kj::setupAsyncIo();
cannam@147 908 auto pipe = io.provider->newTwoWayPipe();
cannam@147 909
cannam@147 910 kj::Promise<void> writeResponsesPromise = kj::READY_NOW;
cannam@147 911 for (auto& testCase: PIPELINE_TESTS) {
cannam@147 912 writeResponsesPromise = writeResponsesPromise
cannam@147 913 .then([&]() {
cannam@147 914 return expectRead(*pipe.ends[1], testCase.request.raw);
cannam@147 915 }).then([&]() {
cannam@147 916 return pipe.ends[1]->write(testCase.response.raw.begin(), testCase.response.raw.size());
cannam@147 917 });
cannam@147 918 }
cannam@147 919
cannam@147 920 HttpHeaderTable table;
cannam@147 921 auto client = newHttpClient(table, *pipe.ends[0]);
cannam@147 922
cannam@147 923 for (auto& testCase: PIPELINE_TESTS) {
cannam@147 924 KJ_CONTEXT(testCase.request.raw, testCase.response.raw);
cannam@147 925
cannam@147 926 HttpHeaders headers(table);
cannam@147 927 for (auto& header: testCase.request.requestHeaders) {
cannam@147 928 headers.set(header.id, header.value);
cannam@147 929 }
cannam@147 930
cannam@147 931 auto request = client->request(
cannam@147 932 testCase.request.method, testCase.request.path, headers, testCase.request.requestBodySize);
cannam@147 933 for (auto& part: testCase.request.requestBodyParts) {
cannam@147 934 request.body->write(part.begin(), part.size()).wait(io.waitScope);
cannam@147 935 }
cannam@147 936 request.body = nullptr;
cannam@147 937
cannam@147 938 auto response = request.response.wait(io.waitScope);
cannam@147 939
cannam@147 940 KJ_EXPECT(response.statusCode == testCase.response.statusCode);
cannam@147 941 auto body = response.body->readAllText().wait(io.waitScope);
cannam@147 942 if (testCase.request.method == HttpMethod::HEAD) {
cannam@147 943 KJ_EXPECT(body == "");
cannam@147 944 } else {
cannam@147 945 KJ_EXPECT(body == kj::strArray(testCase.response.responseBodyParts, ""), body);
cannam@147 946 }
cannam@147 947 }
cannam@147 948
cannam@147 949 client = nullptr;
cannam@147 950 pipe.ends[0]->shutdownWrite();
cannam@147 951
cannam@147 952 writeResponsesPromise.wait(io.waitScope);
cannam@147 953 }
cannam@147 954
cannam@147 955 KJ_TEST("HttpClient parallel pipeline") {
cannam@147 956 auto PIPELINE_TESTS = pipelineTestCases();
cannam@147 957
cannam@147 958 auto io = kj::setupAsyncIo();
cannam@147 959 auto pipe = io.provider->newTwoWayPipe();
cannam@147 960
cannam@147 961 kj::Promise<void> writeResponsesPromise = kj::READY_NOW;
cannam@147 962 for (auto& testCase: PIPELINE_TESTS) {
cannam@147 963 writeResponsesPromise = writeResponsesPromise
cannam@147 964 .then([&]() {
cannam@147 965 return expectRead(*pipe.ends[1], testCase.request.raw);
cannam@147 966 }).then([&]() {
cannam@147 967 return pipe.ends[1]->write(testCase.response.raw.begin(), testCase.response.raw.size());
cannam@147 968 });
cannam@147 969 }
cannam@147 970
cannam@147 971 HttpHeaderTable table;
cannam@147 972 auto client = newHttpClient(table, *pipe.ends[0]);
cannam@147 973
cannam@147 974 auto responsePromises = KJ_MAP(testCase, PIPELINE_TESTS) {
cannam@147 975 KJ_CONTEXT(testCase.request.raw, testCase.response.raw);
cannam@147 976
cannam@147 977 HttpHeaders headers(table);
cannam@147 978 for (auto& header: testCase.request.requestHeaders) {
cannam@147 979 headers.set(header.id, header.value);
cannam@147 980 }
cannam@147 981
cannam@147 982 auto request = client->request(
cannam@147 983 testCase.request.method, testCase.request.path, headers, testCase.request.requestBodySize);
cannam@147 984 for (auto& part: testCase.request.requestBodyParts) {
cannam@147 985 request.body->write(part.begin(), part.size()).wait(io.waitScope);
cannam@147 986 }
cannam@147 987
cannam@147 988 return kj::mv(request.response);
cannam@147 989 };
cannam@147 990
cannam@147 991 for (auto i: kj::indices(PIPELINE_TESTS)) {
cannam@147 992 auto& testCase = PIPELINE_TESTS[i];
cannam@147 993 auto response = responsePromises[i].wait(io.waitScope);
cannam@147 994
cannam@147 995 KJ_EXPECT(response.statusCode == testCase.response.statusCode);
cannam@147 996 auto body = response.body->readAllText().wait(io.waitScope);
cannam@147 997 if (testCase.request.method == HttpMethod::HEAD) {
cannam@147 998 KJ_EXPECT(body == "");
cannam@147 999 } else {
cannam@147 1000 KJ_EXPECT(body == kj::strArray(testCase.response.responseBodyParts, ""), body);
cannam@147 1001 }
cannam@147 1002 }
cannam@147 1003
cannam@147 1004 client = nullptr;
cannam@147 1005 pipe.ends[0]->shutdownWrite();
cannam@147 1006
cannam@147 1007 writeResponsesPromise.wait(io.waitScope);
cannam@147 1008 }
cannam@147 1009
cannam@147 1010 KJ_TEST("HttpServer pipeline") {
cannam@147 1011 auto PIPELINE_TESTS = pipelineTestCases();
cannam@147 1012
cannam@147 1013 auto io = kj::setupAsyncIo();
cannam@147 1014 auto pipe = io.provider->newTwoWayPipe();
cannam@147 1015
cannam@147 1016 HttpHeaderTable table;
cannam@147 1017 TestHttpService service(PIPELINE_TESTS, table);
cannam@147 1018 HttpServer server(io.provider->getTimer(), table, service);
cannam@147 1019
cannam@147 1020 auto listenTask = server.listenHttp(kj::mv(pipe.ends[0]));
cannam@147 1021
cannam@147 1022 for (auto& testCase: PIPELINE_TESTS) {
cannam@147 1023 KJ_CONTEXT(testCase.request.raw, testCase.response.raw);
cannam@147 1024
cannam@147 1025 pipe.ends[1]->write(testCase.request.raw.begin(), testCase.request.raw.size())
cannam@147 1026 .wait(io.waitScope);
cannam@147 1027
cannam@147 1028 expectRead(*pipe.ends[1], testCase.response.raw).wait(io.waitScope);
cannam@147 1029 }
cannam@147 1030
cannam@147 1031 pipe.ends[1]->shutdownWrite();
cannam@147 1032 listenTask.wait(io.waitScope);
cannam@147 1033
cannam@147 1034 KJ_EXPECT(service.getRequestCount() == kj::size(PIPELINE_TESTS));
cannam@147 1035 }
cannam@147 1036
cannam@147 1037 KJ_TEST("HttpServer parallel pipeline") {
cannam@147 1038 auto PIPELINE_TESTS = pipelineTestCases();
cannam@147 1039
cannam@147 1040 auto io = kj::setupAsyncIo();
cannam@147 1041 auto pipe = io.provider->newTwoWayPipe();
cannam@147 1042
cannam@147 1043 auto allRequestText =
cannam@147 1044 kj::strArray(KJ_MAP(testCase, PIPELINE_TESTS) { return testCase.request.raw; }, "");
cannam@147 1045 auto allResponseText =
cannam@147 1046 kj::strArray(KJ_MAP(testCase, PIPELINE_TESTS) { return testCase.response.raw; }, "");
cannam@147 1047
cannam@147 1048 HttpHeaderTable table;
cannam@147 1049 TestHttpService service(PIPELINE_TESTS, table);
cannam@147 1050 HttpServer server(io.provider->getTimer(), table, service);
cannam@147 1051
cannam@147 1052 auto listenTask = server.listenHttp(kj::mv(pipe.ends[0]));
cannam@147 1053
cannam@147 1054 pipe.ends[1]->write(allRequestText.begin(), allRequestText.size()).wait(io.waitScope);
cannam@147 1055 pipe.ends[1]->shutdownWrite();
cannam@147 1056
cannam@147 1057 auto rawResponse = pipe.ends[1]->readAllText().wait(io.waitScope);
cannam@147 1058 KJ_EXPECT(rawResponse == allResponseText, rawResponse);
cannam@147 1059
cannam@147 1060 listenTask.wait(io.waitScope);
cannam@147 1061
cannam@147 1062 KJ_EXPECT(service.getRequestCount() == kj::size(PIPELINE_TESTS));
cannam@147 1063 }
cannam@147 1064
cannam@147 1065 KJ_TEST("HttpClient <-> HttpServer") {
cannam@147 1066 auto PIPELINE_TESTS = pipelineTestCases();
cannam@147 1067
cannam@147 1068 auto io = kj::setupAsyncIo();
cannam@147 1069 auto pipe = io.provider->newTwoWayPipe();
cannam@147 1070
cannam@147 1071 HttpHeaderTable table;
cannam@147 1072 TestHttpService service(PIPELINE_TESTS, table);
cannam@147 1073 HttpServer server(io.provider->getTimer(), table, service);
cannam@147 1074
cannam@147 1075 auto listenTask = server.listenHttp(kj::mv(pipe.ends[1]));
cannam@147 1076 auto client = newHttpClient(table, *pipe.ends[0]);
cannam@147 1077
cannam@147 1078 for (auto& testCase: PIPELINE_TESTS) {
cannam@147 1079 KJ_CONTEXT(testCase.request.raw, testCase.response.raw);
cannam@147 1080
cannam@147 1081 HttpHeaders headers(table);
cannam@147 1082 for (auto& header: testCase.request.requestHeaders) {
cannam@147 1083 headers.set(header.id, header.value);
cannam@147 1084 }
cannam@147 1085
cannam@147 1086 auto request = client->request(
cannam@147 1087 testCase.request.method, testCase.request.path, headers, testCase.request.requestBodySize);
cannam@147 1088 for (auto& part: testCase.request.requestBodyParts) {
cannam@147 1089 request.body->write(part.begin(), part.size()).wait(io.waitScope);
cannam@147 1090 }
cannam@147 1091 request.body = nullptr;
cannam@147 1092
cannam@147 1093 auto response = request.response.wait(io.waitScope);
cannam@147 1094
cannam@147 1095 KJ_EXPECT(response.statusCode == testCase.response.statusCode);
cannam@147 1096 auto body = response.body->readAllText().wait(io.waitScope);
cannam@147 1097 if (testCase.request.method == HttpMethod::HEAD) {
cannam@147 1098 KJ_EXPECT(body == "");
cannam@147 1099 } else {
cannam@147 1100 KJ_EXPECT(body == kj::strArray(testCase.response.responseBodyParts, ""), body);
cannam@147 1101 }
cannam@147 1102 }
cannam@147 1103
cannam@147 1104 client = nullptr;
cannam@147 1105 pipe.ends[0]->shutdownWrite();
cannam@147 1106 listenTask.wait(io.waitScope);
cannam@147 1107 KJ_EXPECT(service.getRequestCount() == kj::size(PIPELINE_TESTS));
cannam@147 1108 }
cannam@147 1109
cannam@147 1110 // -----------------------------------------------------------------------------
cannam@147 1111
cannam@147 1112 KJ_TEST("HttpServer request timeout") {
cannam@147 1113 auto PIPELINE_TESTS = pipelineTestCases();
cannam@147 1114
cannam@147 1115 auto io = kj::setupAsyncIo();
cannam@147 1116 auto pipe = io.provider->newTwoWayPipe();
cannam@147 1117
cannam@147 1118 HttpHeaderTable table;
cannam@147 1119 TestHttpService service(PIPELINE_TESTS, table);
cannam@147 1120 HttpServerSettings settings;
cannam@147 1121 settings.headerTimeout = 1 * kj::MILLISECONDS;
cannam@147 1122 HttpServer server(io.provider->getTimer(), table, service, settings);
cannam@147 1123
cannam@147 1124 // Shouldn't hang! Should time out.
cannam@147 1125 server.listenHttp(kj::mv(pipe.ends[0])).wait(io.waitScope);
cannam@147 1126
cannam@147 1127 // Sends back 408 Request Timeout.
cannam@147 1128 KJ_EXPECT(pipe.ends[1]->readAllText().wait(io.waitScope)
cannam@147 1129 .startsWith("HTTP/1.1 408 Request Timeout"));
cannam@147 1130 }
cannam@147 1131
cannam@147 1132 KJ_TEST("HttpServer pipeline timeout") {
cannam@147 1133 auto PIPELINE_TESTS = pipelineTestCases();
cannam@147 1134
cannam@147 1135 auto io = kj::setupAsyncIo();
cannam@147 1136 auto pipe = io.provider->newTwoWayPipe();
cannam@147 1137
cannam@147 1138 HttpHeaderTable table;
cannam@147 1139 TestHttpService service(PIPELINE_TESTS, table);
cannam@147 1140 HttpServerSettings settings;
cannam@147 1141 settings.pipelineTimeout = 1 * kj::MILLISECONDS;
cannam@147 1142 HttpServer server(io.provider->getTimer(), table, service, settings);
cannam@147 1143
cannam@147 1144 auto listenTask = server.listenHttp(kj::mv(pipe.ends[0]));
cannam@147 1145
cannam@147 1146 // Do one request.
cannam@147 1147 pipe.ends[1]->write(PIPELINE_TESTS[0].request.raw.begin(), PIPELINE_TESTS[0].request.raw.size())
cannam@147 1148 .wait(io.waitScope);
cannam@147 1149 expectRead(*pipe.ends[1], PIPELINE_TESTS[0].response.raw).wait(io.waitScope);
cannam@147 1150
cannam@147 1151 // Listen task should time out even though we didn't shutdown the socket.
cannam@147 1152 listenTask.wait(io.waitScope);
cannam@147 1153
cannam@147 1154 // In this case, no data is sent back.
cannam@147 1155 KJ_EXPECT(pipe.ends[1]->readAllText().wait(io.waitScope) == "");
cannam@147 1156 }
cannam@147 1157
cannam@147 1158 class BrokenHttpService final: public HttpService {
cannam@147 1159 // HttpService that doesn't send a response.
cannam@147 1160 public:
cannam@147 1161 BrokenHttpService() = default;
cannam@147 1162 explicit BrokenHttpService(kj::Exception&& exception): exception(kj::mv(exception)) {}
cannam@147 1163
cannam@147 1164 kj::Promise<void> request(
cannam@147 1165 HttpMethod method, kj::StringPtr url, const HttpHeaders& headers,
cannam@147 1166 kj::AsyncInputStream& requestBody, Response& responseSender) override {
cannam@147 1167 return requestBody.readAllBytes().then([this](kj::Array<byte>&&) -> kj::Promise<void> {
cannam@147 1168 KJ_IF_MAYBE(e, exception) {
cannam@147 1169 return kj::cp(*e);
cannam@147 1170 } else {
cannam@147 1171 return kj::READY_NOW;
cannam@147 1172 }
cannam@147 1173 });
cannam@147 1174 }
cannam@147 1175
cannam@147 1176 private:
cannam@147 1177 kj::Maybe<kj::Exception> exception;
cannam@147 1178 };
cannam@147 1179
cannam@147 1180 KJ_TEST("HttpServer no response") {
cannam@147 1181 auto PIPELINE_TESTS = pipelineTestCases();
cannam@147 1182
cannam@147 1183 auto io = kj::setupAsyncIo();
cannam@147 1184 auto pipe = io.provider->newTwoWayPipe();
cannam@147 1185
cannam@147 1186 HttpHeaderTable table;
cannam@147 1187 BrokenHttpService service;
cannam@147 1188 HttpServer server(io.provider->getTimer(), table, service);
cannam@147 1189
cannam@147 1190 auto listenTask = server.listenHttp(kj::mv(pipe.ends[0]));
cannam@147 1191
cannam@147 1192 // Do one request.
cannam@147 1193 pipe.ends[1]->write(PIPELINE_TESTS[0].request.raw.begin(), PIPELINE_TESTS[0].request.raw.size())
cannam@147 1194 .wait(io.waitScope);
cannam@147 1195 auto text = pipe.ends[1]->readAllText().wait(io.waitScope);
cannam@147 1196
cannam@147 1197 KJ_EXPECT(text ==
cannam@147 1198 "HTTP/1.1 500 Internal Server Error\r\n"
cannam@147 1199 "Connection: close\r\n"
cannam@147 1200 "Content-Length: 51\r\n"
cannam@147 1201 "Content-Type: text/plain\r\n"
cannam@147 1202 "\r\n"
cannam@147 1203 "ERROR: The HttpService did not generate a response.", text);
cannam@147 1204 }
cannam@147 1205
cannam@147 1206 KJ_TEST("HttpServer disconnected") {
cannam@147 1207 auto PIPELINE_TESTS = pipelineTestCases();
cannam@147 1208
cannam@147 1209 auto io = kj::setupAsyncIo();
cannam@147 1210 auto pipe = io.provider->newTwoWayPipe();
cannam@147 1211
cannam@147 1212 HttpHeaderTable table;
cannam@147 1213 BrokenHttpService service(KJ_EXCEPTION(DISCONNECTED, "disconnected"));
cannam@147 1214 HttpServer server(io.provider->getTimer(), table, service);
cannam@147 1215
cannam@147 1216 auto listenTask = server.listenHttp(kj::mv(pipe.ends[0]));
cannam@147 1217
cannam@147 1218 // Do one request.
cannam@147 1219 pipe.ends[1]->write(PIPELINE_TESTS[0].request.raw.begin(), PIPELINE_TESTS[0].request.raw.size())
cannam@147 1220 .wait(io.waitScope);
cannam@147 1221 auto text = pipe.ends[1]->readAllText().wait(io.waitScope);
cannam@147 1222
cannam@147 1223 KJ_EXPECT(text == "", text);
cannam@147 1224 }
cannam@147 1225
cannam@147 1226 KJ_TEST("HttpServer overloaded") {
cannam@147 1227 auto PIPELINE_TESTS = pipelineTestCases();
cannam@147 1228
cannam@147 1229 auto io = kj::setupAsyncIo();
cannam@147 1230 auto pipe = io.provider->newTwoWayPipe();
cannam@147 1231
cannam@147 1232 HttpHeaderTable table;
cannam@147 1233 BrokenHttpService service(KJ_EXCEPTION(OVERLOADED, "overloaded"));
cannam@147 1234 HttpServer server(io.provider->getTimer(), table, service);
cannam@147 1235
cannam@147 1236 auto listenTask = server.listenHttp(kj::mv(pipe.ends[0]));
cannam@147 1237
cannam@147 1238 // Do one request.
cannam@147 1239 pipe.ends[1]->write(PIPELINE_TESTS[0].request.raw.begin(), PIPELINE_TESTS[0].request.raw.size())
cannam@147 1240 .wait(io.waitScope);
cannam@147 1241 auto text = pipe.ends[1]->readAllText().wait(io.waitScope);
cannam@147 1242
cannam@147 1243 KJ_EXPECT(text.startsWith("HTTP/1.1 503 Service Unavailable"), text);
cannam@147 1244 }
cannam@147 1245
cannam@147 1246 KJ_TEST("HttpServer unimplemented") {
cannam@147 1247 auto PIPELINE_TESTS = pipelineTestCases();
cannam@147 1248
cannam@147 1249 auto io = kj::setupAsyncIo();
cannam@147 1250 auto pipe = io.provider->newTwoWayPipe();
cannam@147 1251
cannam@147 1252 HttpHeaderTable table;
cannam@147 1253 BrokenHttpService service(KJ_EXCEPTION(UNIMPLEMENTED, "unimplemented"));
cannam@147 1254 HttpServer server(io.provider->getTimer(), table, service);
cannam@147 1255
cannam@147 1256 auto listenTask = server.listenHttp(kj::mv(pipe.ends[0]));
cannam@147 1257
cannam@147 1258 // Do one request.
cannam@147 1259 pipe.ends[1]->write(PIPELINE_TESTS[0].request.raw.begin(), PIPELINE_TESTS[0].request.raw.size())
cannam@147 1260 .wait(io.waitScope);
cannam@147 1261 auto text = pipe.ends[1]->readAllText().wait(io.waitScope);
cannam@147 1262
cannam@147 1263 KJ_EXPECT(text.startsWith("HTTP/1.1 501 Not Implemented"), text);
cannam@147 1264 }
cannam@147 1265
cannam@147 1266 KJ_TEST("HttpServer threw exception") {
cannam@147 1267 auto PIPELINE_TESTS = pipelineTestCases();
cannam@147 1268
cannam@147 1269 auto io = kj::setupAsyncIo();
cannam@147 1270 auto pipe = io.provider->newTwoWayPipe();
cannam@147 1271
cannam@147 1272 HttpHeaderTable table;
cannam@147 1273 BrokenHttpService service(KJ_EXCEPTION(FAILED, "failed"));
cannam@147 1274 HttpServer server(io.provider->getTimer(), table, service);
cannam@147 1275
cannam@147 1276 auto listenTask = server.listenHttp(kj::mv(pipe.ends[0]));
cannam@147 1277
cannam@147 1278 // Do one request.
cannam@147 1279 pipe.ends[1]->write(PIPELINE_TESTS[0].request.raw.begin(), PIPELINE_TESTS[0].request.raw.size())
cannam@147 1280 .wait(io.waitScope);
cannam@147 1281 auto text = pipe.ends[1]->readAllText().wait(io.waitScope);
cannam@147 1282
cannam@147 1283 KJ_EXPECT(text.startsWith("HTTP/1.1 500 Internal Server Error"), text);
cannam@147 1284 }
cannam@147 1285
cannam@147 1286 class PartialResponseService final: public HttpService {
cannam@147 1287 // HttpService that sends a partial response then throws.
cannam@147 1288 public:
cannam@147 1289 kj::Promise<void> request(
cannam@147 1290 HttpMethod method, kj::StringPtr url, const HttpHeaders& headers,
cannam@147 1291 kj::AsyncInputStream& requestBody, Response& response) override {
cannam@147 1292 return requestBody.readAllBytes()
cannam@147 1293 .then([this,&response](kj::Array<byte>&&) -> kj::Promise<void> {
cannam@147 1294 HttpHeaders headers(table);
cannam@147 1295 auto body = response.send(200, "OK", headers, 32);
cannam@147 1296 auto promise = body->write("foo", 3);
cannam@147 1297 return promise.attach(kj::mv(body)).then([]() -> kj::Promise<void> {
cannam@147 1298 return KJ_EXCEPTION(FAILED, "failed");
cannam@147 1299 });
cannam@147 1300 });
cannam@147 1301 }
cannam@147 1302
cannam@147 1303 private:
cannam@147 1304 kj::Maybe<kj::Exception> exception;
cannam@147 1305 HttpHeaderTable table;
cannam@147 1306 };
cannam@147 1307
cannam@147 1308 KJ_TEST("HttpServer threw exception after starting response") {
cannam@147 1309 auto PIPELINE_TESTS = pipelineTestCases();
cannam@147 1310
cannam@147 1311 auto io = kj::setupAsyncIo();
cannam@147 1312 auto pipe = io.provider->newTwoWayPipe();
cannam@147 1313
cannam@147 1314 HttpHeaderTable table;
cannam@147 1315 PartialResponseService service;
cannam@147 1316 HttpServer server(io.provider->getTimer(), table, service);
cannam@147 1317
cannam@147 1318 auto listenTask = server.listenHttp(kj::mv(pipe.ends[0]));
cannam@147 1319
cannam@147 1320 KJ_EXPECT_LOG(ERROR, "HttpService threw exception after generating a partial response");
cannam@147 1321
cannam@147 1322 // Do one request.
cannam@147 1323 pipe.ends[1]->write(PIPELINE_TESTS[0].request.raw.begin(), PIPELINE_TESTS[0].request.raw.size())
cannam@147 1324 .wait(io.waitScope);
cannam@147 1325 auto text = pipe.ends[1]->readAllText().wait(io.waitScope);
cannam@147 1326
cannam@147 1327 KJ_EXPECT(text ==
cannam@147 1328 "HTTP/1.1 200 OK\r\n"
cannam@147 1329 "Content-Length: 32\r\n"
cannam@147 1330 "\r\n"
cannam@147 1331 "foo", text);
cannam@147 1332 }
cannam@147 1333
cannam@147 1334 // -----------------------------------------------------------------------------
cannam@147 1335
cannam@147 1336 KJ_TEST("HttpClient to capnproto.org") {
cannam@147 1337 auto io = kj::setupAsyncIo();
cannam@147 1338
cannam@147 1339 auto maybeConn = io.provider->getNetwork().parseAddress("capnproto.org", 80)
cannam@147 1340 .then([](kj::Own<kj::NetworkAddress> addr) {
cannam@147 1341 auto promise = addr->connect();
cannam@147 1342 return promise.attach(kj::mv(addr));
cannam@147 1343 }).then([](kj::Own<kj::AsyncIoStream>&& connection) -> kj::Maybe<kj::Own<kj::AsyncIoStream>> {
cannam@147 1344 return kj::mv(connection);
cannam@147 1345 }, [](kj::Exception&& e) -> kj::Maybe<kj::Own<kj::AsyncIoStream>> {
cannam@147 1346 KJ_LOG(WARNING, "skipping test because couldn't connect to capnproto.org");
cannam@147 1347 return nullptr;
cannam@147 1348 }).wait(io.waitScope);
cannam@147 1349
cannam@147 1350 KJ_IF_MAYBE(conn, maybeConn) {
cannam@147 1351 // Successfully connected to capnproto.org. Try doing GET /. We expect to get a redirect to
cannam@147 1352 // HTTPS, because what kind of horrible web site would serve in plaintext, really?
cannam@147 1353
cannam@147 1354 HttpHeaderTable table;
cannam@147 1355 auto client = newHttpClient(table, **conn);
cannam@147 1356
cannam@147 1357 HttpHeaders headers(table);
cannam@147 1358 headers.set(HttpHeaderId::HOST, "capnproto.org");
cannam@147 1359
cannam@147 1360 auto response = client->request(HttpMethod::GET, "/", headers).response.wait(io.waitScope);
cannam@147 1361 KJ_EXPECT(response.statusCode / 100 == 3);
cannam@147 1362 auto location = KJ_ASSERT_NONNULL(response.headers->get(HttpHeaderId::LOCATION));
cannam@147 1363 KJ_EXPECT(location == "https://capnproto.org/");
cannam@147 1364
cannam@147 1365 auto body = response.body->readAllText().wait(io.waitScope);
cannam@147 1366 }
cannam@147 1367 }
cannam@147 1368
cannam@147 1369 } // namespace
cannam@147 1370 } // namespace kj