OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "net/http/http_response_body_drainer.h" | |
6 | |
7 #include <cstring> | |
8 | |
9 #include "base/bind.h" | |
10 #include "base/compiler_specific.h" | |
11 #include "base/memory/weak_ptr.h" | |
12 #include "base/message_loop/message_loop.h" | |
13 #include "net/base/io_buffer.h" | |
14 #include "net/base/net_errors.h" | |
15 #include "net/base/test_completion_callback.h" | |
16 #include "net/http/http_network_session.h" | |
17 #include "net/http/http_server_properties_impl.h" | |
18 #include "net/http/http_stream.h" | |
19 #include "net/http/transport_security_state.h" | |
20 #include "net/proxy/proxy_service.h" | |
21 #include "net/ssl/ssl_config_service_defaults.h" | |
22 #include "testing/gtest/include/gtest/gtest.h" | |
23 | |
24 namespace net { | |
25 | |
26 namespace { | |
27 | |
28 const int kMagicChunkSize = 1024; | |
29 static_assert((HttpResponseBodyDrainer::kDrainBodyBufferSize % | |
30 kMagicChunkSize) == 0, | |
31 "chunk size needs to divide evenly into buffer size"); | |
32 | |
33 class CloseResultWaiter { | |
34 public: | |
35 CloseResultWaiter() | |
36 : result_(false), | |
37 have_result_(false), | |
38 waiting_for_result_(false) {} | |
39 | |
40 int WaitForResult() { | |
41 CHECK(!waiting_for_result_); | |
42 while (!have_result_) { | |
43 waiting_for_result_ = true; | |
44 base::MessageLoop::current()->Run(); | |
45 waiting_for_result_ = false; | |
46 } | |
47 return result_; | |
48 } | |
49 | |
50 void set_result(bool result) { | |
51 result_ = result; | |
52 have_result_ = true; | |
53 if (waiting_for_result_) | |
54 base::MessageLoop::current()->Quit(); | |
55 } | |
56 | |
57 private: | |
58 int result_; | |
59 bool have_result_; | |
60 bool waiting_for_result_; | |
61 | |
62 DISALLOW_COPY_AND_ASSIGN(CloseResultWaiter); | |
63 }; | |
64 | |
65 class MockHttpStream : public HttpStream { | |
66 public: | |
67 MockHttpStream(CloseResultWaiter* result_waiter) | |
68 : result_waiter_(result_waiter), | |
69 buf_len_(0), | |
70 closed_(false), | |
71 stall_reads_forever_(false), | |
72 num_chunks_(0), | |
73 is_sync_(false), | |
74 is_last_chunk_zero_size_(false), | |
75 is_complete_(false), | |
76 weak_factory_(this) {} | |
77 ~MockHttpStream() override {} | |
78 | |
79 // HttpStream implementation. | |
80 int InitializeStream(const HttpRequestInfo* request_info, | |
81 RequestPriority priority, | |
82 const BoundNetLog& net_log, | |
83 const CompletionCallback& callback) override { | |
84 return ERR_UNEXPECTED; | |
85 } | |
86 int SendRequest(const HttpRequestHeaders& request_headers, | |
87 HttpResponseInfo* response, | |
88 const CompletionCallback& callback) override { | |
89 return ERR_UNEXPECTED; | |
90 } | |
91 UploadProgress GetUploadProgress() const override { return UploadProgress(); } | |
92 int ReadResponseHeaders(const CompletionCallback& callback) override { | |
93 return ERR_UNEXPECTED; | |
94 } | |
95 | |
96 bool CanFindEndOfResponse() const override { return true; } | |
97 bool IsConnectionReused() const override { return false; } | |
98 void SetConnectionReused() override {} | |
99 bool IsConnectionReusable() const override { return false; } | |
100 int64 GetTotalReceivedBytes() const override { return 0; } | |
101 void GetSSLInfo(SSLInfo* ssl_info) override {} | |
102 void GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info) override {} | |
103 | |
104 // Mocked API | |
105 int ReadResponseBody(IOBuffer* buf, | |
106 int buf_len, | |
107 const CompletionCallback& callback) override; | |
108 void Close(bool not_reusable) override { | |
109 CHECK(!closed_); | |
110 closed_ = true; | |
111 result_waiter_->set_result(not_reusable); | |
112 } | |
113 | |
114 HttpStream* RenewStreamForAuth() override { return NULL; } | |
115 | |
116 bool IsResponseBodyComplete() const override { return is_complete_; } | |
117 | |
118 bool IsSpdyHttpStream() const override { return false; } | |
119 | |
120 bool GetLoadTimingInfo(LoadTimingInfo* load_timing_info) const override { | |
121 return false; | |
122 } | |
123 | |
124 void Drain(HttpNetworkSession*) override {} | |
125 | |
126 void SetPriority(RequestPriority priority) override {} | |
127 | |
128 // Methods to tweak/observer mock behavior: | |
129 void set_stall_reads_forever() { stall_reads_forever_ = true; } | |
130 | |
131 void set_num_chunks(int num_chunks) { num_chunks_ = num_chunks; } | |
132 | |
133 void set_sync() { is_sync_ = true; } | |
134 | |
135 void set_is_last_chunk_zero_size() { is_last_chunk_zero_size_ = true; } | |
136 | |
137 private: | |
138 int ReadResponseBodyImpl(IOBuffer* buf, int buf_len); | |
139 void CompleteRead(); | |
140 | |
141 bool closed() const { return closed_; } | |
142 | |
143 CloseResultWaiter* const result_waiter_; | |
144 scoped_refptr<IOBuffer> user_buf_; | |
145 CompletionCallback callback_; | |
146 int buf_len_; | |
147 bool closed_; | |
148 bool stall_reads_forever_; | |
149 int num_chunks_; | |
150 bool is_sync_; | |
151 bool is_last_chunk_zero_size_; | |
152 bool is_complete_; | |
153 base::WeakPtrFactory<MockHttpStream> weak_factory_; | |
154 }; | |
155 | |
156 int MockHttpStream::ReadResponseBody(IOBuffer* buf, | |
157 int buf_len, | |
158 const CompletionCallback& callback) { | |
159 CHECK(!callback.is_null()); | |
160 CHECK(callback_.is_null()); | |
161 CHECK(buf); | |
162 | |
163 if (stall_reads_forever_) | |
164 return ERR_IO_PENDING; | |
165 | |
166 if (is_complete_) | |
167 return ERR_UNEXPECTED; | |
168 | |
169 if (!is_sync_) { | |
170 user_buf_ = buf; | |
171 buf_len_ = buf_len; | |
172 callback_ = callback; | |
173 base::MessageLoop::current()->PostTask( | |
174 FROM_HERE, | |
175 base::Bind(&MockHttpStream::CompleteRead, weak_factory_.GetWeakPtr())); | |
176 return ERR_IO_PENDING; | |
177 } else { | |
178 return ReadResponseBodyImpl(buf, buf_len); | |
179 } | |
180 } | |
181 | |
182 int MockHttpStream::ReadResponseBodyImpl(IOBuffer* buf, int buf_len) { | |
183 if (is_last_chunk_zero_size_ && num_chunks_ == 1) { | |
184 buf_len = 0; | |
185 } else { | |
186 if (buf_len > kMagicChunkSize) | |
187 buf_len = kMagicChunkSize; | |
188 std::memset(buf->data(), 1, buf_len); | |
189 } | |
190 num_chunks_--; | |
191 if (!num_chunks_) | |
192 is_complete_ = true; | |
193 | |
194 return buf_len; | |
195 } | |
196 | |
197 void MockHttpStream::CompleteRead() { | |
198 int result = ReadResponseBodyImpl(user_buf_.get(), buf_len_); | |
199 user_buf_ = NULL; | |
200 CompletionCallback callback = callback_; | |
201 callback_.Reset(); | |
202 callback.Run(result); | |
203 } | |
204 | |
205 class HttpResponseBodyDrainerTest : public testing::Test { | |
206 protected: | |
207 HttpResponseBodyDrainerTest() | |
208 : proxy_service_(ProxyService::CreateDirect()), | |
209 ssl_config_service_(new SSLConfigServiceDefaults), | |
210 http_server_properties_(new HttpServerPropertiesImpl()), | |
211 transport_security_state_(new TransportSecurityState()), | |
212 session_(CreateNetworkSession()), | |
213 mock_stream_(new MockHttpStream(&result_waiter_)), | |
214 drainer_(new HttpResponseBodyDrainer(mock_stream_)) {} | |
215 | |
216 ~HttpResponseBodyDrainerTest() override {} | |
217 | |
218 HttpNetworkSession* CreateNetworkSession() const { | |
219 HttpNetworkSession::Params params; | |
220 params.proxy_service = proxy_service_.get(); | |
221 params.ssl_config_service = ssl_config_service_.get(); | |
222 params.http_server_properties = http_server_properties_->GetWeakPtr(); | |
223 params.transport_security_state = transport_security_state_.get(); | |
224 return new HttpNetworkSession(params); | |
225 } | |
226 | |
227 scoped_ptr<ProxyService> proxy_service_; | |
228 scoped_refptr<SSLConfigService> ssl_config_service_; | |
229 scoped_ptr<HttpServerPropertiesImpl> http_server_properties_; | |
230 scoped_ptr<TransportSecurityState> transport_security_state_; | |
231 const scoped_refptr<HttpNetworkSession> session_; | |
232 CloseResultWaiter result_waiter_; | |
233 MockHttpStream* const mock_stream_; // Owned by |drainer_|. | |
234 HttpResponseBodyDrainer* const drainer_; // Deletes itself. | |
235 }; | |
236 | |
237 TEST_F(HttpResponseBodyDrainerTest, DrainBodySyncSingleOK) { | |
238 mock_stream_->set_num_chunks(1); | |
239 mock_stream_->set_sync(); | |
240 drainer_->Start(session_.get()); | |
241 EXPECT_FALSE(result_waiter_.WaitForResult()); | |
242 } | |
243 | |
244 TEST_F(HttpResponseBodyDrainerTest, DrainBodySyncOK) { | |
245 mock_stream_->set_num_chunks(3); | |
246 mock_stream_->set_sync(); | |
247 drainer_->Start(session_.get()); | |
248 EXPECT_FALSE(result_waiter_.WaitForResult()); | |
249 } | |
250 | |
251 TEST_F(HttpResponseBodyDrainerTest, DrainBodyAsyncOK) { | |
252 mock_stream_->set_num_chunks(3); | |
253 drainer_->Start(session_.get()); | |
254 EXPECT_FALSE(result_waiter_.WaitForResult()); | |
255 } | |
256 | |
257 // Test the case when the final chunk is 0 bytes. This can happen when | |
258 // the final 0-byte chunk of a chunk-encoded http response is read in a last | |
259 // call to ReadResponseBody, after all data were returned from HttpStream. | |
260 TEST_F(HttpResponseBodyDrainerTest, DrainBodyAsyncEmptyChunk) { | |
261 mock_stream_->set_num_chunks(4); | |
262 mock_stream_->set_is_last_chunk_zero_size(); | |
263 drainer_->Start(session_.get()); | |
264 EXPECT_FALSE(result_waiter_.WaitForResult()); | |
265 } | |
266 | |
267 TEST_F(HttpResponseBodyDrainerTest, DrainBodySyncEmptyChunk) { | |
268 mock_stream_->set_num_chunks(4); | |
269 mock_stream_->set_sync(); | |
270 mock_stream_->set_is_last_chunk_zero_size(); | |
271 drainer_->Start(session_.get()); | |
272 EXPECT_FALSE(result_waiter_.WaitForResult()); | |
273 } | |
274 | |
275 TEST_F(HttpResponseBodyDrainerTest, DrainBodySizeEqualsDrainBuffer) { | |
276 mock_stream_->set_num_chunks( | |
277 HttpResponseBodyDrainer::kDrainBodyBufferSize / kMagicChunkSize); | |
278 drainer_->Start(session_.get()); | |
279 EXPECT_FALSE(result_waiter_.WaitForResult()); | |
280 } | |
281 | |
282 TEST_F(HttpResponseBodyDrainerTest, DrainBodyTimeOut) { | |
283 mock_stream_->set_num_chunks(2); | |
284 mock_stream_->set_stall_reads_forever(); | |
285 drainer_->Start(session_.get()); | |
286 EXPECT_TRUE(result_waiter_.WaitForResult()); | |
287 } | |
288 | |
289 TEST_F(HttpResponseBodyDrainerTest, CancelledBySession) { | |
290 mock_stream_->set_num_chunks(2); | |
291 mock_stream_->set_stall_reads_forever(); | |
292 drainer_->Start(session_.get()); | |
293 // HttpNetworkSession should delete |drainer_|. | |
294 } | |
295 | |
296 TEST_F(HttpResponseBodyDrainerTest, DrainBodyTooLarge) { | |
297 int too_many_chunks = | |
298 HttpResponseBodyDrainer::kDrainBodyBufferSize / kMagicChunkSize; | |
299 too_many_chunks += 1; // Now it's too large. | |
300 | |
301 mock_stream_->set_num_chunks(too_many_chunks); | |
302 drainer_->Start(session_.get()); | |
303 EXPECT_TRUE(result_waiter_.WaitForResult()); | |
304 } | |
305 | |
306 } // namespace | |
307 | |
308 } // namespace net | |
OLD | NEW |