OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2015 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 "cronet_bidirectional_stream_adapter.h" | |
6 | |
7 #include <string> | |
8 #include <vector> | |
9 | |
10 #include "base/bind.h" | |
11 #include "base/location.h" | |
12 #include "base/logging.h" | |
13 #include "base/strings/string_number_conversions.h" | |
14 #include "components/cronet/android/cronet_url_request_context_adapter.h" | |
15 #include "jni/CronetBidirectionalStream_jni.h" | |
16 #include "net/base/io_buffer.h" | |
17 #include "net/base/net_errors.h" | |
18 #include "net/base/request_priority.h" | |
19 #include "net/cert/cert_status_flags.h" | |
20 #include "net/http/bidirectional_stream_request_info.h" | |
21 #include "net/http/http_network_session.h" | |
22 #include "net/http/http_response_headers.h" | |
23 #include "net/http/http_status_code.h" | |
24 #include "net/http/http_transaction_factory.h" | |
25 #include "net/http/http_util.h" | |
26 #include "net/spdy/spdy_header_block.h" | |
27 #include "net/ssl/ssl_info.h" | |
28 #include "net/url_request/redirect_info.h" | |
29 #include "net/url_request/url_request_context.h" | |
30 | |
31 using base::android::ConvertUTF8ToJavaString; | |
32 using base::android::ConvertJavaStringToUTF8; | |
33 | |
34 namespace cronet { | |
35 | |
36 // Explicitly register static JNI functions. | |
37 bool CronetBidirectionalStreamAdapterRegisterJni(JNIEnv* env) { | |
38 return RegisterNativesImpl(env); | |
39 } | |
40 | |
41 static jlong CreateBidirectionalStream( | |
42 JNIEnv* env, | |
43 const JavaParamRef<jobject>& jbidi_stream, | |
44 jlong jurl_request_context_adapter) { | |
45 CronetURLRequestContextAdapter* context_adapter = | |
46 reinterpret_cast<CronetURLRequestContextAdapter*>( | |
47 jurl_request_context_adapter); | |
48 DCHECK(context_adapter); | |
49 | |
50 CronetBidirectionalStreamAdapter* adapter = | |
51 new CronetBidirectionalStreamAdapter(context_adapter, env, jbidi_stream); | |
52 | |
53 return reinterpret_cast<jlong>(adapter); | |
54 } | |
55 | |
56 // TODO(mef): Extract this and its original from cronet_url_request_adapter.cc | |
57 // into separate module. | |
58 // net::WrappedIOBuffer subclass for a buffer owned by a Java ByteBuffer. Keeps | |
59 // the ByteBuffer alive until destroyed. Uses WrappedIOBuffer because data() is | |
60 // owned by the embedder. | |
61 class CronetBidirectionalStreamAdapter::IOBufferWithByteBuffer | |
62 : public net::WrappedIOBuffer { | |
63 public: | |
64 // Creates a buffer wrapping the Java ByteBuffer |jbyte_buffer|. |data| points | |
65 // to the memory backed by the ByteBuffer, and position is the location to | |
66 // start writing. | |
67 IOBufferWithByteBuffer(JNIEnv* env, | |
68 const JavaParamRef<jobject>& jbyte_buffer, | |
69 void* data, | |
70 int position, | |
71 int limit) | |
72 : net::WrappedIOBuffer(static_cast<char*>(data) + position), | |
73 initial_position_(position), | |
74 initial_limit_(limit) { | |
75 DCHECK(data); | |
76 DCHECK_EQ(env->GetDirectBufferAddress(jbyte_buffer), data); | |
77 byte_buffer_.Reset(env, jbyte_buffer); | |
78 } | |
79 | |
80 int initial_position() const { return initial_position_; } | |
81 int initial_limit() const { return initial_limit_; } | |
82 | |
83 jobject byte_buffer() const { return byte_buffer_.obj(); } | |
84 | |
85 private: | |
86 ~IOBufferWithByteBuffer() override {} | |
87 | |
88 base::android::ScopedJavaGlobalRef<jobject> byte_buffer_; | |
89 | |
90 const int initial_position_; | |
91 const int initial_limit_; | |
92 }; | |
93 | |
94 CronetBidirectionalStreamAdapter::CronetBidirectionalStreamAdapter( | |
95 CronetURLRequestContextAdapter* context, | |
96 JNIEnv* env, | |
97 const JavaParamRef<jobject>& jbidi_stream) | |
98 : context_(context) { | |
99 DCHECK(!context_->IsOnNetworkThread()); | |
pauljensen
2016/01/12 16:55:41
This could fail if user implements a direct execut
mef
2016/01/14 21:07:54
Done.
| |
100 owner_.Reset(env, jbidi_stream); | |
101 } | |
102 | |
103 CronetBidirectionalStreamAdapter::~CronetBidirectionalStreamAdapter() { | |
104 DCHECK(context_->IsOnNetworkThread()); | |
105 } | |
106 | |
107 jint CronetBidirectionalStreamAdapter::Start( | |
108 JNIEnv* env, | |
109 const JavaParamRef<jobject>& jcaller, | |
110 const JavaParamRef<jstring>& jurl, | |
111 jint jpriority, | |
112 const JavaParamRef<jstring>& jmethod, | |
113 const JavaParamRef<jobjectArray>& jheaders, | |
114 jboolean jend_of_stream) { | |
115 DCHECK(!context_->IsOnNetworkThread()); | |
pauljensen
2016/01/12 16:55:41
This could fail if user implements a direct execut
mef
2016/01/14 21:07:54
Done.
| |
116 // Prepare request info here to be able to return the error. | |
117 scoped_ptr<net::BidirectionalStreamRequestInfo> request_info( | |
118 new net::BidirectionalStreamRequestInfo()); | |
119 request_info->url = GURL(ConvertJavaStringToUTF8(env, jurl)); | |
120 request_info->priority = static_cast<net::RequestPriority>(jpriority); | |
121 // Http method is a token, just as header name. | |
122 request_info->method = ConvertJavaStringToUTF8(env, jmethod); | |
123 if (!net::HttpUtil::IsValidHeaderName(request_info->method)) | |
124 return -1; | |
125 | |
126 std::vector<std::string> headers; | |
127 base::android::AppendJavaStringArrayToStringVector(env, jheaders, &headers); | |
128 for (size_t i = 0; i < headers.size(); i += 2) { | |
129 std::string name(headers[i]); | |
130 std::string value(headers[i + 1]); | |
131 if (!net::HttpUtil::IsValidHeaderName(name) || | |
132 !net::HttpUtil::IsValidHeaderValue(value)) { | |
133 return i + 1; | |
134 } | |
135 | |
136 request_info->extra_headers.SetHeader(name, value); | |
137 } | |
138 request_info->end_stream_on_headers = jend_of_stream; | |
139 | |
140 context_->PostTaskToNetworkThread( | |
141 FROM_HERE, | |
142 base::Bind(&CronetBidirectionalStreamAdapter::StartOnNetworkThread, | |
143 base::Unretained(this), base::Passed(&request_info))); | |
144 return 0; | |
145 } | |
146 | |
147 void CronetBidirectionalStreamAdapter::StartOnNetworkThread( | |
148 scoped_ptr<net::BidirectionalStreamRequestInfo> request_info) { | |
149 DCHECK(context_->IsOnNetworkThread()); | |
150 | |
151 VLOG(1) << "Starting bidirectional stream: " | |
152 << request_info->url.possibly_invalid_spec().c_str(); | |
153 | |
154 bidi_stream_.reset(new net::BidirectionalStream( | |
155 std::move(request_info), context_->GetURLRequestContext() | |
156 ->http_transaction_factory() | |
pauljensen
2016/01/12 16:55:41
the auto-formatting here is rather hideous. I'd r
mef
2016/01/14 21:07:54
Ack. I've tried, but cl format insists.
| |
157 ->GetSession(), | |
158 this)); | |
159 } | |
160 | |
161 jboolean CronetBidirectionalStreamAdapter::ReadData( | |
162 JNIEnv* env, | |
163 const JavaParamRef<jobject>& jcaller, | |
164 const JavaParamRef<jobject>& jbyte_buffer, | |
165 jint jposition, | |
166 jint jlimit) { | |
167 DCHECK(!context_->IsOnNetworkThread()); | |
pauljensen
2016/01/12 16:55:41
This could fail if user implements a direct execut
mef
2016/01/14 21:07:54
Done.
| |
168 DCHECK_LT(jposition, jlimit); | |
169 | |
170 void* data = env->GetDirectBufferAddress(jbyte_buffer); | |
171 if (!data) | |
172 return JNI_FALSE; | |
173 | |
174 scoped_refptr<IOBufferWithByteBuffer> read_buffer( | |
175 new IOBufferWithByteBuffer(env, jbyte_buffer, data, jposition, jlimit)); | |
176 | |
177 int remaining_capacity = jlimit - jposition; | |
178 | |
179 context_->PostTaskToNetworkThread( | |
180 FROM_HERE, | |
181 base::Bind(&CronetBidirectionalStreamAdapter::ReadDataOnNetworkThread, | |
182 base::Unretained(this), read_buffer, remaining_capacity)); | |
183 return JNI_TRUE; | |
184 } | |
185 | |
186 jboolean CronetBidirectionalStreamAdapter::WriteData( | |
187 JNIEnv* env, | |
188 const JavaParamRef<jobject>& jcaller, | |
189 const JavaParamRef<jobject>& jbyte_buffer, | |
190 jint jposition, | |
191 jint jlimit, | |
192 jboolean jend_of_stream) { | |
193 DCHECK(!context_->IsOnNetworkThread()); | |
pauljensen
2016/01/12 16:55:41
This could fail if user implements a direct execut
mef
2016/01/14 21:07:54
Done.
| |
194 DCHECK_LT(jposition, jlimit); | |
195 | |
196 void* data = env->GetDirectBufferAddress(jbyte_buffer); | |
197 if (!data) | |
198 return JNI_FALSE; | |
199 | |
200 scoped_refptr<IOBufferWithByteBuffer> write_buffer( | |
201 new IOBufferWithByteBuffer(env, jbyte_buffer, data, jposition, jlimit)); | |
202 | |
203 int remaining_capacity = jlimit - jposition; | |
204 | |
205 context_->PostTaskToNetworkThread( | |
206 FROM_HERE, | |
207 base::Bind(&CronetBidirectionalStreamAdapter::WriteDataOnNetworkThread, | |
208 base::Unretained(this), write_buffer, remaining_capacity, | |
209 jend_of_stream)); | |
210 return JNI_TRUE; | |
211 } | |
212 | |
213 void CronetBidirectionalStreamAdapter::Destroy( | |
214 JNIEnv* env, | |
215 const JavaParamRef<jobject>& jcaller, | |
216 jboolean jsend_on_canceled) { | |
217 // Destroy could be called from any thread, including network thread (if | |
218 // posting task to executor throws an exception), but is posted, so |this| | |
219 // is valid until calling task is complete. Destroy() is always called from | |
220 // within a synchronized java block that guarantees no future posts to the | |
221 // network thread with the adapter pointer. | |
222 context_->PostTaskToNetworkThread( | |
223 FROM_HERE, | |
224 base::Bind(&CronetBidirectionalStreamAdapter::DestroyOnNetworkThread, | |
225 base::Unretained(this), jsend_on_canceled)); | |
226 } | |
227 | |
228 base::android::ScopedJavaLocalRef<jstring> | |
229 CronetBidirectionalStreamAdapter::GetNegotiatedProtocol( | |
pauljensen
2016/01/12 16:55:41
can we get rid of this function and simply pass th
mef
2016/01/14 21:07:54
Done.
| |
230 JNIEnv* env, | |
231 const JavaParamRef<jobject>& jcaller) const { | |
232 DCHECK(context_->IsOnNetworkThread()); | |
233 switch (bidi_stream_->GetProtocol()) { | |
234 case net::kProtoHTTP2: | |
235 return ConvertUTF8ToJavaString(env, "h2"); | |
236 default: | |
237 break; | |
238 } | |
239 NOTREACHED(); | |
240 return ConvertUTF8ToJavaString(env, ""); | |
241 } | |
242 | |
243 // net::BidirectionalStream::Delegate overrides (called on network thread). | |
244 | |
245 void CronetBidirectionalStreamAdapter::OnHeadersSent() { | |
246 VLOG(1) << "OnHeadersSent"; | |
247 DCHECK(context_->IsOnNetworkThread()); | |
248 JNIEnv* env = base::android::AttachCurrentThread(); | |
249 cronet::Java_CronetBidirectionalStream_onRequestHeadersSent(env, | |
250 owner_.obj()); | |
251 } | |
252 | |
253 void CronetBidirectionalStreamAdapter::OnHeadersReceived( | |
254 const net::SpdyHeaderBlock& response_headers) { | |
255 VLOG(1) << "OnHeadersReceived"; | |
256 DCHECK(context_->IsOnNetworkThread()); | |
257 JNIEnv* env = base::android::AttachCurrentThread(); | |
258 // Get http status code from response headers. | |
259 jint http_status_code = 0; | |
260 const auto http_status_header = response_headers.find(":status"); | |
261 if (http_status_header != response_headers.end()) | |
262 base::StringToInt(http_status_header->second, &http_status_code); | |
263 | |
264 cronet::Java_CronetBidirectionalStream_onResponseHeadersReceived( | |
265 env, owner_.obj(), http_status_code, | |
266 GetHeadersArray(env, response_headers).obj()); | |
267 } | |
268 | |
269 void CronetBidirectionalStreamAdapter::OnDataRead(int bytes_read) { | |
270 VLOG(1) << "OnDataRead:" << bytes_read; | |
271 DCHECK(context_->IsOnNetworkThread()); | |
272 jlong received_bytes_count = bidi_stream_->GetTotalReceivedBytes(); | |
273 JNIEnv* env = base::android::AttachCurrentThread(); | |
274 cronet::Java_CronetBidirectionalStream_onReadCompleted( | |
275 env, owner_.obj(), read_buffer_->byte_buffer(), bytes_read, | |
276 read_buffer_->initial_position(), read_buffer_->initial_limit(), | |
277 received_bytes_count); | |
278 // Free the read buffer. This lets the Java ByteBuffer be freed, if the | |
279 // embedder releases it, too. | |
280 read_buffer_ = nullptr; | |
pauljensen
2016/01/12 16:55:41
nit: it'd be nice if we Release()'d the Java ByteB
mef
2016/01/14 21:07:54
Acknowledged. As you've said, callback only posts
| |
281 } | |
282 | |
283 void CronetBidirectionalStreamAdapter::OnDataSent() { | |
284 DCHECK(context_->IsOnNetworkThread()); | |
285 JNIEnv* env = base::android::AttachCurrentThread(); | |
286 cronet::Java_CronetBidirectionalStream_onWriteCompleted( | |
287 env, owner_.obj(), write_buffer_->byte_buffer(), | |
288 write_buffer_->initial_position(), write_buffer_->initial_limit()); | |
289 // Free the write buffer. This lets the Java ByteBuffer be freed, if the | |
290 // embedder releases it, too. | |
291 write_buffer_ = nullptr; | |
292 } | |
293 | |
294 void CronetBidirectionalStreamAdapter::OnTrailersReceived( | |
295 const net::SpdyHeaderBlock& response_trailers) { | |
296 DCHECK(context_->IsOnNetworkThread()); | |
297 JNIEnv* env = base::android::AttachCurrentThread(); | |
298 cronet::Java_CronetBidirectionalStream_onResponseTrailersReceived( | |
299 env, owner_.obj(), GetHeadersArray(env, response_trailers).obj()); | |
300 } | |
301 | |
302 void CronetBidirectionalStreamAdapter::OnFailed(int error) { | |
303 DCHECK(context_->IsOnNetworkThread()); | |
304 VLOG(1) << "OnFailed:" << error; | |
305 jlong received_bytes_count = bidi_stream_->GetTotalReceivedBytes(); | |
306 JNIEnv* env = base::android::AttachCurrentThread(); | |
307 cronet::Java_CronetBidirectionalStream_onError( | |
308 env, owner_.obj(), error, | |
309 ConvertUTF8ToJavaString(env, net::ErrorToString(error)).obj(), | |
310 received_bytes_count); | |
311 } | |
312 | |
313 base::android::ScopedJavaLocalRef<jobjectArray> | |
314 CronetBidirectionalStreamAdapter::GetHeadersArray( | |
315 JNIEnv* env, | |
316 const net::SpdyHeaderBlock& header_block) { | |
317 DCHECK(context_->IsOnNetworkThread()); | |
318 | |
319 std::vector<std::string> headers; | |
320 for (const auto& header : header_block) { | |
321 headers.push_back(header.first.as_string()); | |
322 headers.push_back(header.second.as_string()); | |
323 } | |
324 return base::android::ToJavaArrayOfStrings(env, headers); | |
325 } | |
326 | |
327 void CronetBidirectionalStreamAdapter::ReadDataOnNetworkThread( | |
328 scoped_refptr<IOBufferWithByteBuffer> read_buffer, | |
329 int buffer_size) { | |
330 DCHECK(context_->IsOnNetworkThread()); | |
331 DCHECK(read_buffer); | |
332 DCHECK(!read_buffer_); | |
333 | |
334 read_buffer_ = read_buffer; | |
335 | |
336 int bytes_read = bidi_stream_->ReadData(read_buffer_.get(), buffer_size); | |
337 // If IO is pending, wait for the BidirectionalStream to call OnDataRead. | |
338 if (bytes_read == net::ERR_IO_PENDING) | |
339 return; | |
340 | |
341 if (bytes_read < 0) { | |
342 OnFailed(bytes_read); | |
343 return; | |
344 } | |
345 OnDataRead(bytes_read); | |
346 } | |
347 | |
348 void CronetBidirectionalStreamAdapter::WriteDataOnNetworkThread( | |
349 scoped_refptr<IOBufferWithByteBuffer> write_buffer, | |
350 int buffer_size, | |
351 bool end_of_stream) { | |
352 DCHECK(context_->IsOnNetworkThread()); | |
353 DCHECK(write_buffer); | |
354 DCHECK(!write_buffer_); | |
355 | |
356 write_buffer_ = write_buffer; | |
357 bidi_stream_->SendData(write_buffer_.get(), buffer_size, end_of_stream); | |
358 } | |
359 | |
360 void CronetBidirectionalStreamAdapter::DestroyOnNetworkThread( | |
361 bool send_on_canceled) { | |
362 DCHECK(context_->IsOnNetworkThread()); | |
363 if (send_on_canceled) { | |
364 JNIEnv* env = base::android::AttachCurrentThread(); | |
365 cronet::Java_CronetBidirectionalStream_onCanceled(env, owner_.obj()); | |
366 } | |
367 delete this; | |
368 } | |
369 | |
370 } // namespace cronet | |
OLD | NEW |