blob: 6dab0c9acd21abdfc9760822b8dc0acdb893a945 [file] [log] [blame]
henrike@webrtc.orgf7795df2014-05-13 18:00:26 +00001/*
2 * Copyright 2004 The WebRTC Project Authors. All rights reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11#include "webrtc/base/gunit.h"
12#include "webrtc/base/httpbase.h"
13#include "webrtc/base/testutils.h"
14
15namespace rtc {
16
17const char* const kHttpResponse =
18 "HTTP/1.1 200\r\n"
19 "Connection: Keep-Alive\r\n"
20 "Content-Type: text/plain\r\n"
21 "Proxy-Authorization: 42\r\n"
22 "Transfer-Encoding: chunked\r\n"
23 "\r\n"
24 "00000008\r\n"
25 "Goodbye!\r\n"
26 "0\r\n\r\n";
27
28const char* const kHttpEmptyResponse =
29 "HTTP/1.1 200\r\n"
30 "Connection: Keep-Alive\r\n"
31 "Content-Length: 0\r\n"
32 "Proxy-Authorization: 42\r\n"
33 "\r\n";
34
35const char* const kHttpResponsePrefix =
36 "HTTP/1.1 200\r\n"
37 "Connection: Keep-Alive\r\n"
38 "Content-Type: text/plain\r\n"
39 "Proxy-Authorization: 42\r\n"
40 "Transfer-Encoding: chunked\r\n"
41 "\r\n"
42 "8\r\n"
43 "Goodbye!\r\n";
44
45class HttpBaseTest : public testing::Test, public IHttpNotify {
46public:
47 enum EventType { E_HEADER_COMPLETE, E_COMPLETE, E_CLOSED };
48 struct Event {
49 EventType event;
50 bool chunked;
51 size_t data_size;
52 HttpMode mode;
53 HttpError err;
54 };
55 HttpBaseTest() : mem(NULL), obtain_stream(false), http_stream(NULL) { }
56
57 virtual void SetUp() { }
58 virtual void TearDown() {
59 delete http_stream;
60 // Avoid an ASSERT, in case a test doesn't clean up properly
61 base.abort(HE_NONE);
62 }
63
64 virtual HttpError onHttpHeaderComplete(bool chunked, size_t& data_size) {
65 LOG_F(LS_VERBOSE) << "chunked: " << chunked << " size: " << data_size;
66 Event e = { E_HEADER_COMPLETE, chunked, data_size, HM_NONE, HE_NONE};
67 events.push_back(e);
68 if (obtain_stream) {
69 ObtainDocumentStream();
70 }
71 return HE_NONE;
72 }
73 virtual void onHttpComplete(HttpMode mode, HttpError err) {
74 LOG_F(LS_VERBOSE) << "mode: " << mode << " err: " << err;
75 Event e = { E_COMPLETE, false, 0, mode, err };
76 events.push_back(e);
77 }
78 virtual void onHttpClosed(HttpError err) {
79 LOG_F(LS_VERBOSE) << "err: " << err;
80 Event e = { E_CLOSED, false, 0, HM_NONE, err };
81 events.push_back(e);
82 }
83
84 void SetupSource(const char* response);
85
86 void VerifyHeaderComplete(size_t event_count, bool empty_doc);
87 void VerifyDocumentContents(const char* expected_data,
88 size_t expected_length = SIZE_UNKNOWN);
89
90 void ObtainDocumentStream();
91 void VerifyDocumentStreamIsOpening();
92 void VerifyDocumentStreamOpenEvent();
93 void ReadDocumentStreamData(const char* expected_data);
94 void VerifyDocumentStreamIsEOS();
95
96 void SetupDocument(const char* response);
97 void VerifySourceContents(const char* expected_data,
98 size_t expected_length = SIZE_UNKNOWN);
99
100 void VerifyTransferComplete(HttpMode mode, HttpError error);
101
102 HttpBase base;
103 MemoryStream* mem;
104 HttpResponseData data;
105
106 // The source of http data, and source events
107 testing::StreamSource src;
108 std::vector<Event> events;
109
110 // Document stream, and stream events
111 bool obtain_stream;
112 StreamInterface* http_stream;
113 testing::StreamSink sink;
114};
115
116void HttpBaseTest::SetupSource(const char* http_data) {
117 LOG_F(LS_VERBOSE) << "Enter";
118
119 src.SetState(SS_OPENING);
120 src.QueueString(http_data);
121
122 base.notify(this);
123 base.attach(&src);
124 EXPECT_TRUE(events.empty());
125
126 src.SetState(SS_OPEN);
127 ASSERT_EQ(1U, events.size());
128 EXPECT_EQ(E_COMPLETE, events[0].event);
129 EXPECT_EQ(HM_CONNECT, events[0].mode);
130 EXPECT_EQ(HE_NONE, events[0].err);
131 events.clear();
132
133 mem = new MemoryStream;
134 data.document.reset(mem);
135 LOG_F(LS_VERBOSE) << "Exit";
136}
137
138void HttpBaseTest::VerifyHeaderComplete(size_t event_count, bool empty_doc) {
139 LOG_F(LS_VERBOSE) << "Enter";
140
141 ASSERT_EQ(event_count, events.size());
142 EXPECT_EQ(E_HEADER_COMPLETE, events[0].event);
143
144 std::string header;
145 EXPECT_EQ(HVER_1_1, data.version);
146 EXPECT_EQ(static_cast<uint32>(HC_OK), data.scode);
147 EXPECT_TRUE(data.hasHeader(HH_PROXY_AUTHORIZATION, &header));
148 EXPECT_EQ("42", header);
149 EXPECT_TRUE(data.hasHeader(HH_CONNECTION, &header));
150 EXPECT_EQ("Keep-Alive", header);
151
152 if (empty_doc) {
153 EXPECT_FALSE(events[0].chunked);
154 EXPECT_EQ(0U, events[0].data_size);
155
156 EXPECT_TRUE(data.hasHeader(HH_CONTENT_LENGTH, &header));
157 EXPECT_EQ("0", header);
158 } else {
159 EXPECT_TRUE(events[0].chunked);
160 EXPECT_EQ(SIZE_UNKNOWN, events[0].data_size);
161
162 EXPECT_TRUE(data.hasHeader(HH_CONTENT_TYPE, &header));
163 EXPECT_EQ("text/plain", header);
164 EXPECT_TRUE(data.hasHeader(HH_TRANSFER_ENCODING, &header));
165 EXPECT_EQ("chunked", header);
166 }
167 LOG_F(LS_VERBOSE) << "Exit";
168}
169
170void HttpBaseTest::VerifyDocumentContents(const char* expected_data,
171 size_t expected_length) {
172 LOG_F(LS_VERBOSE) << "Enter";
173
174 if (SIZE_UNKNOWN == expected_length) {
175 expected_length = strlen(expected_data);
176 }
177 EXPECT_EQ(mem, data.document.get());
178
179 size_t length;
180 mem->GetSize(&length);
181 EXPECT_EQ(expected_length, length);
182 EXPECT_TRUE(0 == memcmp(expected_data, mem->GetBuffer(), length));
183 LOG_F(LS_VERBOSE) << "Exit";
184}
185
186void HttpBaseTest::ObtainDocumentStream() {
187 LOG_F(LS_VERBOSE) << "Enter";
188 EXPECT_FALSE(http_stream);
189 http_stream = base.GetDocumentStream();
190 ASSERT_TRUE(NULL != http_stream);
191 sink.Monitor(http_stream);
192 LOG_F(LS_VERBOSE) << "Exit";
193}
194
195void HttpBaseTest::VerifyDocumentStreamIsOpening() {
196 LOG_F(LS_VERBOSE) << "Enter";
197 ASSERT_TRUE(NULL != http_stream);
198 EXPECT_EQ(0, sink.Events(http_stream));
199 EXPECT_EQ(SS_OPENING, http_stream->GetState());
200
201 size_t read = 0;
202 char buffer[5] = { 0 };
203 EXPECT_EQ(SR_BLOCK, http_stream->Read(buffer, sizeof(buffer), &read, NULL));
204 LOG_F(LS_VERBOSE) << "Exit";
205}
206
207void HttpBaseTest::VerifyDocumentStreamOpenEvent() {
208 LOG_F(LS_VERBOSE) << "Enter";
209
210 ASSERT_TRUE(NULL != http_stream);
211 EXPECT_EQ(SE_OPEN | SE_READ, sink.Events(http_stream));
212 EXPECT_EQ(SS_OPEN, http_stream->GetState());
213
214 // HTTP headers haven't arrived yet
215 EXPECT_EQ(0U, events.size());
216 EXPECT_EQ(static_cast<uint32>(HC_INTERNAL_SERVER_ERROR), data.scode);
217 LOG_F(LS_VERBOSE) << "Exit";
218}
219
220void HttpBaseTest::ReadDocumentStreamData(const char* expected_data) {
221 LOG_F(LS_VERBOSE) << "Enter";
222
223 ASSERT_TRUE(NULL != http_stream);
224 EXPECT_EQ(SS_OPEN, http_stream->GetState());
225
226 // Pump the HTTP I/O using Read, and verify the results.
227 size_t verified_length = 0;
228 const size_t expected_length = strlen(expected_data);
229 while (verified_length < expected_length) {
230 size_t read = 0;
231 char buffer[5] = { 0 };
232 size_t amt_to_read = _min(expected_length - verified_length, sizeof(buffer));
233 EXPECT_EQ(SR_SUCCESS, http_stream->Read(buffer, amt_to_read, &read, NULL));
234 EXPECT_EQ(amt_to_read, read);
235 EXPECT_TRUE(0 == memcmp(expected_data + verified_length, buffer, read));
236 verified_length += read;
237 }
238 LOG_F(LS_VERBOSE) << "Exit";
239}
240
241void HttpBaseTest::VerifyDocumentStreamIsEOS() {
242 LOG_F(LS_VERBOSE) << "Enter";
243
244 ASSERT_TRUE(NULL != http_stream);
245 size_t read = 0;
246 char buffer[5] = { 0 };
247 EXPECT_EQ(SR_EOS, http_stream->Read(buffer, sizeof(buffer), &read, NULL));
248 EXPECT_EQ(SS_CLOSED, http_stream->GetState());
249
250 // When EOS is caused by Read, we don't expect SE_CLOSE
251 EXPECT_EQ(0, sink.Events(http_stream));
252 LOG_F(LS_VERBOSE) << "Exit";
253}
254
255void HttpBaseTest::SetupDocument(const char* document_data) {
256 LOG_F(LS_VERBOSE) << "Enter";
257 src.SetState(SS_OPEN);
258
259 base.notify(this);
260 base.attach(&src);
261 EXPECT_TRUE(events.empty());
262
263 if (document_data) {
264 // Note: we could just call data.set_success("text/plain", mem), but that
265 // won't allow us to use the chunked transfer encoding.
266 mem = new MemoryStream(document_data);
267 data.document.reset(mem);
268 data.setHeader(HH_CONTENT_TYPE, "text/plain");
269 data.setHeader(HH_TRANSFER_ENCODING, "chunked");
270 } else {
271 data.setHeader(HH_CONTENT_LENGTH, "0");
272 }
273 data.scode = HC_OK;
274 data.setHeader(HH_PROXY_AUTHORIZATION, "42");
275 data.setHeader(HH_CONNECTION, "Keep-Alive");
276 LOG_F(LS_VERBOSE) << "Exit";
277}
278
279void HttpBaseTest::VerifySourceContents(const char* expected_data,
280 size_t expected_length) {
281 LOG_F(LS_VERBOSE) << "Enter";
282 if (SIZE_UNKNOWN == expected_length) {
283 expected_length = strlen(expected_data);
284 }
285 std::string contents = src.ReadData();
286 EXPECT_EQ(expected_length, contents.length());
287 EXPECT_TRUE(0 == memcmp(expected_data, contents.data(), expected_length));
288 LOG_F(LS_VERBOSE) << "Exit";
289}
290
291void HttpBaseTest::VerifyTransferComplete(HttpMode mode, HttpError error) {
292 LOG_F(LS_VERBOSE) << "Enter";
293 // Verify that http operation has completed
294 ASSERT_TRUE(events.size() > 0);
295 size_t last_event = events.size() - 1;
296 EXPECT_EQ(E_COMPLETE, events[last_event].event);
297 EXPECT_EQ(mode, events[last_event].mode);
298 EXPECT_EQ(error, events[last_event].err);
299 LOG_F(LS_VERBOSE) << "Exit";
300}
301
302//
303// Tests
304//
305
306TEST_F(HttpBaseTest, SupportsSend) {
307 // Queue response document
308 SetupDocument("Goodbye!");
309
310 // Begin send
311 base.send(&data);
312
313 // Send completed successfully
314 VerifyTransferComplete(HM_SEND, HE_NONE);
315 VerifySourceContents(kHttpResponse);
316}
317
318TEST_F(HttpBaseTest, SupportsSendNoDocument) {
319 // Queue response document
320 SetupDocument(NULL);
321
322 // Begin send
323 base.send(&data);
324
325 // Send completed successfully
326 VerifyTransferComplete(HM_SEND, HE_NONE);
327 VerifySourceContents(kHttpEmptyResponse);
328}
329
330TEST_F(HttpBaseTest, SignalsCompleteOnInterruptedSend) {
331 // This test is attempting to expose a bug that occurs when a particular
332 // base objects is used for receiving, and then used for sending. In
333 // particular, the HttpParser state is different after receiving. Simulate
334 // that here.
335 SetupSource(kHttpResponse);
336 base.recv(&data);
337 VerifyTransferComplete(HM_RECV, HE_NONE);
338
339 src.Clear();
340 data.clear(true);
341 events.clear();
342 base.detach();
343
344 // Queue response document
345 SetupDocument("Goodbye!");
346
347 // Prevent entire response from being sent
348 const size_t kInterruptedLength = strlen(kHttpResponse) - 1;
349 src.SetWriteBlock(kInterruptedLength);
350
351 // Begin send
352 base.send(&data);
353
354 // Document is mostly complete, but no completion signal yet.
355 EXPECT_TRUE(events.empty());
356 VerifySourceContents(kHttpResponse, kInterruptedLength);
357
358 src.SetState(SS_CLOSED);
359
360 // Send completed with disconnect error, and no additional data.
361 VerifyTransferComplete(HM_SEND, HE_DISCONNECTED);
362 EXPECT_TRUE(src.ReadData().empty());
363}
364
365TEST_F(HttpBaseTest, SupportsReceiveViaDocumentPush) {
366 // Queue response document
367 SetupSource(kHttpResponse);
368
369 // Begin receive
370 base.recv(&data);
371
372 // Document completed successfully
373 VerifyHeaderComplete(2, false);
374 VerifyTransferComplete(HM_RECV, HE_NONE);
375 VerifyDocumentContents("Goodbye!");
376}
377
378TEST_F(HttpBaseTest, SupportsReceiveViaStreamPull) {
379 // Switch to pull mode
380 ObtainDocumentStream();
381 VerifyDocumentStreamIsOpening();
382
383 // Queue response document
384 SetupSource(kHttpResponse);
385 VerifyDocumentStreamIsOpening();
386
387 // Begin receive
388 base.recv(&data);
389
390 // Pull document data
391 VerifyDocumentStreamOpenEvent();
392 ReadDocumentStreamData("Goodbye!");
393 VerifyDocumentStreamIsEOS();
394
395 // Document completed successfully
396 VerifyHeaderComplete(2, false);
397 VerifyTransferComplete(HM_RECV, HE_NONE);
398 VerifyDocumentContents("");
399}
400
401TEST_F(HttpBaseTest, DISABLED_AllowsCloseStreamBeforeDocumentIsComplete) {
402
403 // TODO: Remove extra logging once test failure is understood
404 int old_sev = rtc::LogMessage::GetLogToDebug();
405 rtc::LogMessage::LogToDebug(LS_VERBOSE);
406
407
408 // Switch to pull mode
409 ObtainDocumentStream();
410 VerifyDocumentStreamIsOpening();
411
412 // Queue response document
413 SetupSource(kHttpResponse);
414 VerifyDocumentStreamIsOpening();
415
416 // Begin receive
417 base.recv(&data);
418
419 // Pull some of the data
420 VerifyDocumentStreamOpenEvent();
421 ReadDocumentStreamData("Goodb");
422
423 // We've seen the header by now
424 VerifyHeaderComplete(1, false);
425
426 // Close the pull stream, this will transition back to push I/O.
427 http_stream->Close();
428 Thread::Current()->ProcessMessages(0);
429
430 // Remainder of document completed successfully
431 VerifyTransferComplete(HM_RECV, HE_NONE);
432 VerifyDocumentContents("ye!");
433
434 rtc::LogMessage::LogToDebug(old_sev);
435}
436
437TEST_F(HttpBaseTest, AllowsGetDocumentStreamInResponseToHttpHeader) {
438 // Queue response document
439 SetupSource(kHttpResponse);
440
441 // Switch to pull mode in response to header arrival
442 obtain_stream = true;
443
444 // Begin receive
445 base.recv(&data);
446
447 // We've already seen the header, but not data has arrived
448 VerifyHeaderComplete(1, false);
449 VerifyDocumentContents("");
450
451 // Pull the document data
452 ReadDocumentStreamData("Goodbye!");
453 VerifyDocumentStreamIsEOS();
454
455 // Document completed successfully
456 VerifyTransferComplete(HM_RECV, HE_NONE);
457 VerifyDocumentContents("");
458}
459
460TEST_F(HttpBaseTest, AllowsGetDocumentStreamWithEmptyDocumentBody) {
461 // Queue empty response document
462 SetupSource(kHttpEmptyResponse);
463
464 // Switch to pull mode in response to header arrival
465 obtain_stream = true;
466
467 // Begin receive
468 base.recv(&data);
469
470 // We've already seen the header, but not data has arrived
471 VerifyHeaderComplete(1, true);
472 VerifyDocumentContents("");
473
474 // The document is still open, until we attempt to read
475 ASSERT_TRUE(NULL != http_stream);
476 EXPECT_EQ(SS_OPEN, http_stream->GetState());
477
478 // Attempt to read data, and discover EOS
479 VerifyDocumentStreamIsEOS();
480
481 // Document completed successfully
482 VerifyTransferComplete(HM_RECV, HE_NONE);
483 VerifyDocumentContents("");
484}
485
486TEST_F(HttpBaseTest, SignalsDocumentStreamCloseOnUnexpectedClose) {
487 // Switch to pull mode
488 ObtainDocumentStream();
489 VerifyDocumentStreamIsOpening();
490
491 // Queue response document
492 SetupSource(kHttpResponsePrefix);
493 VerifyDocumentStreamIsOpening();
494
495 // Begin receive
496 base.recv(&data);
497
498 // Pull document data
499 VerifyDocumentStreamOpenEvent();
500 ReadDocumentStreamData("Goodbye!");
501
502 // Simulate unexpected close
503 src.SetState(SS_CLOSED);
504
505 // Observe error event on document stream
506 EXPECT_EQ(testing::SSE_ERROR, sink.Events(http_stream));
507
508 // Future reads give an error
509 int error = 0;
510 char buffer[5] = { 0 };
511 EXPECT_EQ(SR_ERROR, http_stream->Read(buffer, sizeof(buffer), NULL, &error));
512 EXPECT_EQ(HE_DISCONNECTED, error);
513
514 // Document completed with error
515 VerifyHeaderComplete(2, false);
516 VerifyTransferComplete(HM_RECV, HE_DISCONNECTED);
517 VerifyDocumentContents("");
518}
519
520} // namespace rtc