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