-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
Copy pathconnections_and_errors.cpp
500 lines (413 loc) · 16.5 KB
/
connections_and_errors.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
/***
* Copyright (C) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
*
* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
*
* Tests cases for covering issues dealing with http_client lifetime, underlying TCP connections, and general connection
*errors.
*
* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
****/
#include "stdafx.h"
#ifndef __cplusplus_winrt
#include "cpprest/http_listener.h"
#endif
#include <chrono>
#include <thread>
using namespace web;
using namespace utility;
using namespace concurrency;
using namespace web::http;
using namespace web::http::client;
using namespace tests::functional::http::utilities;
namespace tests
{
namespace functional
{
namespace http
{
namespace client
{
// Test implementation for pending_requests_after_client.
static void pending_requests_after_client_impl(const uri& address)
{
std::vector<pplx::task<void>> completed_requests;
{
test_http_server::scoped_server scoped(address);
const method mtd = methods::GET;
const size_t num_requests = 10;
std::vector<pplx::task<test_request*>> requests = scoped.server()->next_requests(num_requests);
std::vector<pplx::task<http_response>> responses;
{
http_client client(address);
// send requests.
for (size_t i = 0; i < num_requests; ++i)
{
responses.push_back(client.request(mtd));
}
}
// send responses.
for (size_t i = 0; i < num_requests; ++i)
{
completed_requests.push_back(requests[i].then([&](test_request* request) {
http_asserts::assert_test_request_equals(request, mtd, U("/"));
VERIFY_ARE_EQUAL(0u, request->reply(status_codes::OK));
}));
}
// verify responses.
for (size_t i = 0; i < num_requests; ++i)
{
try
{
http_asserts::assert_response_equals(responses[i].get(), status_codes::OK);
}
catch (...)
{
VERIFY_IS_TRUE(false);
}
}
}
for (auto&& req : completed_requests)
req.get();
}
SUITE(connections_and_errors)
{
// Tests requests still outstanding after the http_client has been destroyed.
TEST_FIXTURE(uri_address, pending_requests_after_client) { pending_requests_after_client_impl(m_uri); }
TEST_FIXTURE(uri_address, server_doesnt_exist)
{
http_client_config config;
config.set_timeout(std::chrono::seconds(1));
http_client client(m_uri, config);
VERIFY_THROWS(client.request(methods::GET).wait(), web::http::http_exception);
}
TEST_FIXTURE(uri_address, open_failure)
{
http_client client(U("http://localhost323:-1"));
// This API should not throw. The exception should be surfaced
// during task.wait/get
auto t = client.request(methods::GET);
VERIFY_THROWS(t.wait(), web::http::http_exception);
}
TEST_FIXTURE(uri_address, server_close_without_responding)
{
http_client_config config;
config.set_timeout(utility::seconds(1));
http_client client(m_uri, config);
test_http_server::scoped_server server(m_uri);
auto t = server.server()->next_request();
// Send request.
auto response = client.request(methods::PUT);
// Wait for request
VERIFY_NO_THROWS(t.get());
// Close server connection.
server.server()->close();
VERIFY_THROWS_HTTP_ERROR_CODE(response.wait(), std::errc::connection_aborted);
// Try sending another request.
VERIFY_THROWS(client.request(methods::GET).wait(), web::http::http_exception);
}
TEST_FIXTURE(uri_address, send_request_to_reopened_connection)
{
http_client_config config;
config.set_timeout(utility::seconds(1));
http_client client(m_uri, config);
{
// 1st request.
test_http_server::scoped_server server(m_uri);
auto t = server.server()->next_request().then([](test_request* p_request) {
p_request->reply(200);
});
// Send request.
auto response = client.request(methods::PUT);
// Wait for request
VERIFY_NO_THROWS(t.get());
// Wait for reply.
VERIFY_NO_THROWS(response.wait());
// Close server connection. For example, keep-alive timeout reason.
server.server()->close();
}
{
// 2nd. Send request to connection has been closed.
utility::string_t str_body(U("Hi, I'm reused connection"));
std::vector<unsigned char> raw_body(str_body.size() * sizeof(utility::char_t));
memcpy(&raw_body[0], &str_body[0], str_body.size() * sizeof(utility::char_t));
// Reopen connection.
test_http_server::scoped_server server(m_uri);
auto t = server.server()->next_request().then([&](test_request* p_request) {
VERIFY_ARE_EQUAL(p_request->m_body, raw_body);
p_request->reply(200);
});
// Try to send request with payload.
auto msg = http_request(methods::POST);
msg.set_body(str_body);
auto response = client.request(msg);
// Wait for request
VERIFY_NO_THROWS(t.get());
http_asserts::assert_response_equals(response.get(), status_codes::OK);
}
}
TEST_FIXTURE(uri_address, request_timeout)
{
test_http_server::scoped_server scoped(m_uri);
auto t = scoped.server()->next_request();
http_client_config config;
config.set_timeout(utility::seconds(1));
http_client client(m_uri, config);
auto responseTask = client.request(methods::GET);
#ifdef __APPLE__
// CodePlex 295
VERIFY_THROWS(responseTask.get(), http_exception);
#else
VERIFY_THROWS_HTTP_ERROR_CODE(responseTask.get(), std::errc::timed_out);
#endif
t.get();
}
TEST_FIXTURE(uri_address, request_timeout_microsecond)
{
pplx::task<test_request*> t;
{
test_http_server::scoped_server scoped(m_uri);
t = scoped.server()->next_request();
http_client_config config;
config.set_timeout(std::chrono::microseconds(900));
http_client client(m_uri, config);
auto responseTask = client.request(methods::GET);
#ifdef __APPLE__
// CodePlex 295
VERIFY_THROWS(responseTask.get(), http_exception);
#else
VERIFY_THROWS_HTTP_ERROR_CODE(responseTask.get(), std::errc::timed_out);
#endif
}
try
{
t.get();
}
catch (...)
{
}
}
TEST_FIXTURE(uri_address, invalid_method)
{
web::http::uri uri(U("http://www.bing.com/"));
http_client client(uri);
string_t invalid_chars = U("\a\b\f\v\n\r\t\x20\x7f");
for (auto iter = invalid_chars.begin(); iter < invalid_chars.end(); iter++)
{
string_t method = U("my method");
method[2] = *iter;
VERIFY_THROWS(client.request(method).get(), http_exception);
}
}
// This test sends an SSL request to a non-SSL server and should fail on handshaking
TEST_FIXTURE(uri_address, handshake_fail)
{
web::http::uri ssl_uri(U("https://localhost:34568/"));
test_http_server::scoped_server scoped(m_uri);
http_client client(ssl_uri);
auto request = client.request(methods::GET);
VERIFY_THROWS(request.get(), http_exception);
}
#if !defined(__cplusplus_winrt)
TEST_FIXTURE(uri_address, content_ready_timeout)
{
web::http::experimental::listener::http_listener listener(m_uri);
listener.open().wait();
streams::producer_consumer_buffer<uint8_t> buf;
listener.support([buf](http_request request) {
http_response response(200);
response.set_body(streams::istream(buf), U("text/plain"));
response.headers().add(header_names::connection, U("close"));
request.reply(response);
});
{
http_client_config config;
config.set_timeout(utility::seconds(1));
http_client client(m_uri, config);
http_request msg(methods::GET);
http_response rsp = client.request(msg).get();
// The response body should timeout and we should receive an exception
VERIFY_THROWS_HTTP_ERROR_CODE(rsp.content_ready().wait(), std::errc::timed_out);
}
buf.close(std::ios_base::out).wait();
listener.close().wait();
}
TEST_FIXTURE(uri_address, stream_timeout)
{
web::http::experimental::listener::http_listener listener(m_uri);
listener.open().wait();
streams::producer_consumer_buffer<uint8_t> buf;
listener.support([buf](http_request request) {
http_response response(200);
response.set_body(streams::istream(buf), U("text/plain"));
response.headers().add(header_names::connection, U("close"));
request.reply(response);
});
{
http_client_config config;
config.set_timeout(utility::seconds(1));
http_client client(m_uri, config);
http_request msg(methods::GET);
http_response rsp = client.request(msg).get();
// The response body should timeout and we should receive an exception
auto readTask = rsp.body().read_to_end(streams::producer_consumer_buffer<uint8_t>());
VERIFY_THROWS_HTTP_ERROR_CODE(readTask.wait(), std::errc::timed_out);
}
buf.close(std::ios_base::out).wait();
listener.close().wait();
}
#endif
TEST_FIXTURE(uri_address, cancel_before_request)
{
test_http_server::scoped_server scoped(m_uri);
http_client c(m_uri);
pplx::cancellation_token_source source;
source.cancel();
auto responseTask = c.request(methods::PUT, U("/"), source.get_token());
VERIFY_THROWS_HTTP_ERROR_CODE(responseTask.get(), std::errc::operation_canceled);
}
// This test can't be implemented with our test server so isn't available on WinRT.
#ifndef __cplusplus_winrt
TEST_FIXTURE(uri_address, cancel_after_headers)
{
web::http::experimental::listener::http_listener listener(m_uri);
listener.open().wait();
http_client c(m_uri);
pplx::cancellation_token_source source;
pplx::extensibility::event_t ev;
listener.support([&](http_request request) {
streams::producer_consumer_buffer<uint8_t> buf;
http_response response(200);
response.set_body(streams::istream(buf), U("text/plain"));
request.reply(response);
ev.wait();
buf.putc('a').wait();
buf.putc('b').wait();
buf.putc('c').wait();
buf.putc('d').wait();
buf.close(std::ios::out).wait();
});
auto responseTask = c.request(methods::GET, source.get_token());
http_response response = responseTask.get();
source.cancel();
ev.set();
VERIFY_THROWS_HTTP_ERROR_CODE(response.extract_string().get(), std::errc::operation_canceled);
// Codeplex 328.
#if !defined(_WIN32)
tests::common::utilities::os_utilities::sleep(1000);
#endif
listener.close().wait();
}
#endif
TEST_FIXTURE(uri_address, cancel_after_body)
{
test_http_server::scoped_server scoped(m_uri);
test_http_server* p_server = scoped.server();
http_client c(m_uri);
pplx::cancellation_token_source source;
std::map<utility::string_t, utility::string_t> headers;
headers[U("Content-Type")] = U("text/plain; charset=utf-8");
std::string bodyData("Hello");
p_server->next_request().then(
[&](test_request* r) { VERIFY_ARE_EQUAL(0u, r->reply(status_codes::OK, U("OK"), headers, bodyData)); });
auto response = c.request(methods::PUT, U("/"), U("data"), source.get_token()).get();
VERIFY_ARE_EQUAL(utility::conversions::to_string_t(bodyData), response.extract_string().get());
source.cancel();
response.content_ready().wait();
}
TEST_FIXTURE(uri_address, cancel_with_error)
{
http_client c(m_uri);
pplx::task<http_response> responseTask;
{
test_http_server::scoped_server server(m_uri);
pplx::cancellation_token_source source;
const auto r = server.server()->next_request();
responseTask = c.request(methods::GET, U("/"), source.get_token());
r.wait();
source.cancel();
}
// All errors after cancellation are ignored.
VERIFY_THROWS_HTTP_ERROR_CODE(responseTask.get(), std::errc::operation_canceled);
}
TEST_FIXTURE(uri_address, cancel_while_uploading_data)
{
test_http_server::scoped_server scoped(m_uri);
http_client c(m_uri);
pplx::cancellation_token_source source;
auto buf = streams::producer_consumer_buffer<uint8_t>();
buf.putc('A').wait();
auto responseTask = c.request(methods::PUT, U("/"), buf.create_istream(), 2, source.get_token());
source.cancel();
buf.putc('B').wait();
buf.close(std::ios::out).wait();
VERIFY_THROWS_HTTP_ERROR_CODE(responseTask.get(), std::errc::operation_canceled);
}
// This test can't be implemented with our test server since it doesn't stream data so isn't avaliable on WinRT.
#ifndef __cplusplus_winrt
TEST_FIXTURE(uri_address, cancel_while_downloading_data)
{
web::http::experimental::listener::http_listener listener(m_uri);
listener.open().wait();
http_client c(m_uri);
pplx::cancellation_token_source source;
pplx::extensibility::event_t ev;
pplx::extensibility::event_t ev2;
listener.support([&](http_request request) {
streams::producer_consumer_buffer<uint8_t> buf;
http_response response(200);
response.set_body(streams::istream(buf), U("text/plain"));
request.reply(response);
buf.putc('a').wait();
buf.putc('b').wait();
ev.set();
ev2.wait();
buf.putc('c').wait();
buf.putc('d').wait();
buf.close(std::ios::out).wait();
});
auto response = c.request(methods::GET, source.get_token()).get();
ev.wait();
source.cancel();
ev2.set();
VERIFY_THROWS_HTTP_ERROR_CODE(response.extract_string().get(), std::errc::operation_canceled);
// Codeplex 328.
#if !defined(_WIN32)
tests::common::utilities::os_utilities::sleep(1000);
#endif
listener.close().wait();
}
#endif
// Try to connect to a server on a closed port and cancel the operation.
TEST_FIXTURE(uri_address, cancel_bad_port)
{
// http_client_asio had a bug where, when canceled, it would cancel only the
// current connection but then go and try the next address from the list of
// resolved addresses, i.e., it wouldn't actually cancel as long as there
// are more addresses to try. Consequently, it would not report the task as
// being canceled. This was easiest to observe when trying to connect to a
// server that does not respond on a certain port, otherwise the timing
// might be tricky.
// We need to connect to a URI for which there are multiple addresses
// associated (i.e., multiple A records).
web::http::uri uri(U("https://microsoft.com:442/"));
// Send request.
http_client_config config;
config.set_timeout(std::chrono::milliseconds(1000));
http_client c(uri, config);
web::http::http_request r;
auto cts = pplx::cancellation_token_source();
auto ct = cts.get_token();
auto t = c.request(r, ct);
// Make sure that the client already finished resolving before canceling,
// otherwise the bug might not be triggered.
std::this_thread::sleep_for(std::chrono::milliseconds(400));
cts.cancel();
VERIFY_THROWS_HTTP_ERROR_CODE(t.get(), std::errc::operation_canceled);
}
} // SUITE(connections_and_errors)
} // namespace client
} // namespace http
} // namespace functional
} // namespace tests