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