1 /*
2 * Copyright 2022 Google LLC
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 #include "fcp/client/http/java/java_http_client.h"
17
18 #include <jni.h>
19
20 #include <cstdint>
21 #include <functional>
22 #include <memory>
23 #include <optional>
24 #include <string>
25 #include <utility>
26 #include <vector>
27
28 // #include "google/rpc/status.pb.h"
29 #include "absl/cleanup/cleanup.h"
30 #include "absl/status/status.h"
31 #include "absl/status/statusor.h"
32 #include "absl/strings/numbers.h"
33 #include "absl/strings/str_cat.h"
34 #include "absl/strings/string_view.h"
35 #include "absl/synchronization/mutex.h"
36 #include "fcp/base/monitoring.h"
37 #include "fcp/client/http/http_client.h"
38 #include "fcp/client/http/http_client_util.h"
39 #include "fcp/client/http/java/jni.pb.h"
40 #include "fcp/jni/jni_util.h"
41
42 namespace fcp {
43 namespace client {
44 namespace http {
45 namespace java {
46
47 using fcp::client::http::HttpRequestCallback;
48 using fcp::client::http::HttpRequestHandle;
49 using fcp::jni::JavaFieldSig;
50 using fcp::jni::JavaMethodSig;
51 using fcp::jni::LocalRefDeleter;
52 using fcp::jni::ParseProtoFromJByteArray;
53 using fcp::jni::ScopedJniEnv;
54 using fcp::jni::SerializeProtoToJByteArray;
55
56 namespace {
57
58 // The Java method signatures for the Java class corresponding to the C++
59 // `JavaHttpClient` class.
60 struct JavaHttpClientClassDesc {
61 static constexpr JavaMethodSig kEnqueueRequest = {
62 "enqueueRequest",
63 "([B)Lcom/google/fcp/client/http/HttpClientForNative$HttpRequestHandle;"};
64 static constexpr JavaMethodSig kPerformRequests = {"performRequests",
65 "([Ljava/lang/Object;)[B"};
66 static constexpr JavaMethodSig kClose = {"close", "()V"};
67 };
68
69 // The Java method and field signatures for the Java class corresponding to the
70 // C++ `JavaHttpRequestHandle` class.
71 struct JavaHttpRequestHandleClassDesc {
72 static constexpr JavaMethodSig kGetTotalSentReceivedBytes = {
73 "getTotalSentReceivedBytes", "()[B"};
74 static constexpr JavaMethodSig kClose = {"close", "()V"};
75 static constexpr JavaFieldSig kNativeHandle = {"nativeHandle", "J"};
76 };
77
ConvertHttpClientMethodToProtoMethod(fcp::client::http::HttpRequest::Method method)78 JniHttpMethod ConvertHttpClientMethodToProtoMethod(
79 fcp::client::http::HttpRequest::Method method) {
80 switch (method) {
81 case fcp::client::http::HttpRequest::Method::kHead:
82 return JniHttpMethod::HTTP_METHOD_HEAD;
83 case fcp::client::http::HttpRequest::Method::kGet:
84 return JniHttpMethod::HTTP_METHOD_GET;
85 case fcp::client::http::HttpRequest::Method::kPost:
86 return JniHttpMethod::HTTP_METHOD_POST;
87 case fcp::client::http::HttpRequest::Method::kPut:
88 return JniHttpMethod::HTTP_METHOD_PUT;
89 case fcp::client::http::HttpRequest::Method::kPatch:
90 return JniHttpMethod::HTTP_METHOD_PATCH;
91 case fcp::client::http::HttpRequest::Method::kDelete:
92 return JniHttpMethod::HTTP_METHOD_DELETE;
93 default:
94 return JniHttpMethod::HTTP_METHOD_UNKNOWN;
95 }
96 }
97
98 // Calls JNIEnv::GetMethodID and ensures that its return value is valid.
GetMethodIdOrAbort(JNIEnv & env,jclass clazz,JavaMethodSig method)99 jmethodID GetMethodIdOrAbort(JNIEnv& env, jclass clazz, JavaMethodSig method) {
100 jmethodID id = env.GetMethodID(clazz, method.name, method.signature);
101 FCP_CHECK(id != nullptr);
102 return id;
103 }
104 } // namespace
105
JavaHttpClient(JavaVM * jvm,jobject java_http_client)106 JavaHttpClient::JavaHttpClient(JavaVM* jvm, jobject java_http_client)
107 : jvm_(jvm) {
108 ScopedJniEnv scoped_env(jvm_);
109 JNIEnv* env = scoped_env.env();
110 jthis_ = env->NewGlobalRef(java_http_client);
111 FCP_CHECK(jthis_ != nullptr);
112 // We get the class from the jobject here instead of looking it up by name in
113 // the classloader because we may be using this class from a non java thread
114 // that has been attached to the jvm, and thus has a classloader with only
115 // "system" classes.
116 jclass java_http_client_class = env->GetObjectClass(java_http_client);
117 FCP_CHECK(java_http_client_class != nullptr);
118 LocalRefDeleter java_http_client_class_deleter(env, java_http_client_class);
119
120 // Look up the method IDs for the Java methods we'll call later on.
121 enqueue_request_id_ = GetMethodIdOrAbort(
122 *env, java_http_client_class, JavaHttpClientClassDesc::kEnqueueRequest);
123 perform_requests_id_ = GetMethodIdOrAbort(
124 *env, java_http_client_class, JavaHttpClientClassDesc::kPerformRequests);
125 close_id_ = GetMethodIdOrAbort(*env, java_http_client_class,
126 JavaHttpClientClassDesc::kClose);
127 }
128
~JavaHttpClient()129 JavaHttpClient::~JavaHttpClient() {
130 ScopedJniEnv scoped_env(jvm_);
131 JNIEnv* env = scoped_env.env();
132
133 // We call the Java close() method when the destructor is invoked. This gives
134 // the Java code a chance to clean things up on its side, if it needs to.
135 env->CallVoidMethod(jthis_, close_id_);
136 FCP_CHECK(!env->ExceptionCheck());
137
138 // Delete the global reference to the Java object.
139 env->DeleteGlobalRef(jthis_);
140 }
141
EnqueueRequest(std::unique_ptr<fcp::client::http::HttpRequest> request)142 std::unique_ptr<HttpRequestHandle> JavaHttpClient::EnqueueRequest(
143 std::unique_ptr<fcp::client::http::HttpRequest> request) {
144 // Convert the `HttpRequest`'s info into a proto we can serialize and pass
145 // over the JNI boundary.
146 JniHttpRequest request_proto;
147 request_proto.set_uri(std::string(request->uri()));
148 request_proto.set_method(
149 ConvertHttpClientMethodToProtoMethod(request->method()));
150 for (auto request_header : request->extra_headers()) {
151 JniHttpHeader* header = request_proto.add_extra_headers();
152 header->set_name(request_header.first);
153 header->set_value(request_header.second);
154 }
155 request_proto.set_has_body(request->HasBody());
156
157 // Call into Java to create the Java request handle object that will cooperate
158 // with our C++ `JavaHttpRequestHandle` object.
159 ScopedJniEnv scoped_env(jvm_);
160 JNIEnv* env = scoped_env.env();
161 jbyteArray serialized_request_proto =
162 SerializeProtoToJByteArray(env, request_proto);
163 LocalRefDeleter serialized_request_proto_deleter(env,
164 serialized_request_proto);
165 jobject java_http_request_handle = env->CallObjectMethod(
166 jthis_, enqueue_request_id_, serialized_request_proto);
167 FCP_CHECK(java_http_request_handle != nullptr);
168 FCP_CHECK(!env->ExceptionCheck());
169
170 // Create the C++ `JavaHttpRequestHandle` object (which will 'attach' itself
171 // to the Java object by writing its address to the `nativeHandle` Java
172 // field).
173 auto result = std::make_unique<JavaHttpRequestHandle>(
174 jvm_, java_http_request_handle, std::move(request));
175 return result;
176 }
177
PerformRequests(std::vector<std::pair<HttpRequestHandle *,HttpRequestCallback * >> generic_requests)178 absl::Status JavaHttpClient::PerformRequests(
179 std::vector<std::pair<HttpRequestHandle*, HttpRequestCallback*>>
180 generic_requests) {
181 // We're about to kick off a group of requests. Each request has a matching
182 // callback, as well as a corresponding Java object. To prepare for the
183 // `performRequests` call into Java:
184 // 1. Create an Object[] array, consisting of each request's corresponding
185 // Java object.
186 // 2. For each request, register the callback that should be used for it.
187 ScopedJniEnv scoped_env(jvm_);
188 JNIEnv* env = scoped_env.env();
189
190 // The object array we'll create will just be of type Object[]. The Java
191 // code/JNI runtime will be in charge of downcasting the individual elements
192 // back to its concrete Java HttpRequestHandle implementation class. This
193 // avoids us having to try and look up the Java class (which can be difficult,
194 // since we may be running on a thread with a ClassLoader containing only Java
195 // system classes).
196 jclass object_class = env->FindClass("java/lang/Object");
197 FCP_CHECK(object_class != nullptr);
198 FCP_CHECK(!env->ExceptionCheck());
199 LocalRefDeleter object_class_deleter(env, object_class);
200
201 // Create the Object[] array.
202 jobjectArray request_handle_array = env->NewObjectArray(
203 static_cast<jsize>(generic_requests.size()), object_class, nullptr);
204 FCP_CHECK(request_handle_array != nullptr);
205 FCP_CHECK(!env->ExceptionCheck());
206 LocalRefDeleter request_handle_array_deleter(env, request_handle_array);
207
208 // Populate the Object[] array with the Java objects corresponding to each
209 // request, and register each callback with the `JavaHttpRequestHandle`.
210 int i = 0;
211 for (const auto& [generic_handle, callback] : generic_requests) {
212 auto request_handle = static_cast<JavaHttpRequestHandle*>(generic_handle);
213 env->SetObjectArrayElement(request_handle_array, i++,
214 request_handle->GetJobject());
215 FCP_CHECK(!env->ExceptionCheck());
216 FCP_RETURN_IF_ERROR(request_handle->SetCallback(callback));
217 }
218
219 // Call the Java `performRequests` method over JNI, passing it the Object[]
220 // array.
221 jbyteArray perform_requests_result =
222 static_cast<jbyteArray>(env->CallObjectMethod(
223 jthis_, perform_requests_id_, request_handle_array));
224 FCP_CHECK(!env->ExceptionCheck());
225 FCP_CHECK(perform_requests_result != nullptr);
226 LocalRefDeleter perform_requests_result_deleter(env, perform_requests_result);
227
228 // Convert the return value from Java to an absl::Status.
229 return ConvertRpcStatusToAbslStatus(
230 ParseProtoFromJByteArray<
231 ::google::internal::federatedcompute::v1::Status>(
232 env, perform_requests_result));
233 }
234
FromJlong(jlong ptr)235 JavaHttpRequestHandle* JavaHttpRequestHandle::FromJlong(jlong ptr) {
236 // If the Java code erroneously calls a JNI callback with a handle that has
237 // already been destroyed, then `ptr` will be 0. We want to catch such bugs
238 // early.
239 FCP_CHECK(ptr != 0)
240 << "cannot call JNI callback before enqueueRequest has been called";
241 return reinterpret_cast<JavaHttpRequestHandle*>(ptr);
242 }
243
JavaHttpRequestHandle(JavaVM * jvm,jobject java_http_request_handle,std::unique_ptr<fcp::client::http::HttpRequest> request)244 JavaHttpRequestHandle::JavaHttpRequestHandle(
245 JavaVM* jvm, jobject java_http_request_handle,
246 std::unique_ptr<fcp::client::http::HttpRequest> request)
247 : jvm_(jvm), request_(std::move(request)) {
248 ScopedJniEnv scoped_env(jvm_);
249 JNIEnv* env = scoped_env.env();
250 jthis_ = env->NewGlobalRef(java_http_request_handle);
251 FCP_CHECK(jthis_ != nullptr);
252
253 // We get the class from the jobject here instead of looking up by name in
254 // the classloader because we may be using this class from a non java thread
255 // that has been attached to the jvm, and thus has a classloader with only
256 // "system" classes.
257 jclass java_http_request_handle_class =
258 env->GetObjectClass(java_http_request_handle);
259 LocalRefDeleter java_http_request_handle_class_deleter(
260 env, java_http_request_handle_class);
261
262 get_total_sent_received_bytes_id_ = GetMethodIdOrAbort(
263 *env, java_http_request_handle_class,
264 JavaHttpRequestHandleClassDesc::kGetTotalSentReceivedBytes);
265
266 close_id_ = GetMethodIdOrAbort(*env, java_http_request_handle_class,
267 JavaHttpRequestHandleClassDesc::kClose);
268
269 native_handle_id_ =
270 env->GetFieldID(java_http_request_handle_class,
271 JavaHttpRequestHandleClassDesc::kNativeHandle.name,
272 JavaHttpRequestHandleClassDesc::kNativeHandle.signature);
273
274 // Register this object's address inside the `nativeHandle` field, so we can
275 // look this object up during later calls back into native.
276 env->SetLongField(jthis_, native_handle_id_, reinterpret_cast<jlong>(this));
277 FCP_CHECK(!env->ExceptionCheck());
278 }
279
~JavaHttpRequestHandle()280 JavaHttpRequestHandle::~JavaHttpRequestHandle() {
281 ScopedJniEnv scoped_env(jvm_);
282 JNIEnv* env = scoped_env.env();
283
284 absl::MutexLock locked(&lock_);
285 // We call the Java close() method when the destructor is invoked, to let the
286 // Java code know the request's resources (if any) can be released. The
287 // close() method may not have been invoked yet if the JavaHttpRequestHandle
288 // never ended up being passed to `performRequests`.
289 env->CallVoidMethod(jthis_, close_id_);
290 FCP_CHECK(!env->ExceptionCheck());
291
292 // Unset the native handle (this is an additional safety check, so that if the
293 // Java object erroneously calls back into the native layer again, we will be
294 // able to detect it, rather than us accidentally accessing a destructed
295 // object).
296 env->SetLongField(jthis_, native_handle_id_, 0);
297
298 // Delete the reference to the Java object.
299 env->DeleteGlobalRef(jthis_);
300 }
301
Cancel()302 void JavaHttpRequestHandle::Cancel() {
303 {
304 absl::MutexLock locked(&lock_);
305 // We mark the request 'performed'. This way if the handle is subsequently
306 // still erroneously passed to `PerformRequests`, we can detect the error.
307 performed_ = true;
308 }
309 // Note that we release the lock before calling into Java, to ensure that if
310 // the Java call itself calls back into the native layer (e.g. by calling on
311 // of the request handle callbacks), we don't accidentally try to acquire
312 // the same mutex twice.
313
314 ScopedJniEnv scoped_env(jvm_);
315 JNIEnv* env = scoped_env.env();
316
317 // We call the Java close() method to indicate that the request should be
318 // cancelled. If `PerformRequests` wasn't called yet, then this will be a
319 // no-op.
320 env->CallVoidMethod(jthis_, close_id_);
321 FCP_CHECK(!env->ExceptionCheck());
322 }
323
324 HttpRequestHandle::SentReceivedBytes
TotalSentReceivedBytes() const325 JavaHttpRequestHandle::TotalSentReceivedBytes() const {
326 ScopedJniEnv scoped_env(jvm_);
327 JNIEnv* env = scoped_env.env();
328
329 jbyteArray sent_received_bytes_result = static_cast<jbyteArray>(
330 env->CallObjectMethod(jthis_, get_total_sent_received_bytes_id_));
331 FCP_CHECK(!env->ExceptionCheck());
332 FCP_CHECK(sent_received_bytes_result != nullptr);
333 LocalRefDeleter sent_received_bytes_result_deleter(
334 env, sent_received_bytes_result);
335
336 // Convert the return value from a Java byte[] to the expected proto.
337 auto sent_received_bytes = ParseProtoFromJByteArray<JniHttpSentReceivedBytes>(
338 env, sent_received_bytes_result);
339 return {.sent_bytes = sent_received_bytes.sent_bytes(),
340 .received_bytes = sent_received_bytes.received_bytes()};
341 }
342
callback() const343 fcp::client::http::HttpRequestCallback* JavaHttpRequestHandle::callback()
344 const {
345 // This method acquires and immediately releases the lock to ensure that all
346 // JNI callback invocations observe the effects of any prior JNI callback
347 // invocation, prior to invoking another `HttpRequestCallback` method.
348 //
349 // We don't hold the lock while invoking the actual `HttpRequestCallback`
350 // method though, to ensure that if the `HttpRequestCallback` invocation
351 // ultimately causes another JNI callback to be invoked, we don't attempt to
352 // acquire the lock twice on the same thread.
353 absl::MutexLock _(&lock_);
354 return callback_;
355 }
356
response() const357 const JavaHttpResponse& JavaHttpRequestHandle::response() const {
358 // We synchronize for the same purpose as in callback().
359 absl::MutexLock _(&lock_);
360 return response_;
361 }
362
SetCallback(fcp::client::http::HttpRequestCallback * callback)363 absl::Status JavaHttpRequestHandle::SetCallback(
364 fcp::client::http::HttpRequestCallback* callback) {
365 absl::MutexLock locked(&lock_);
366 // If the request was already 'performed' then we should detect that error.
367 if (performed_) {
368 return absl::InvalidArgumentError(
369 "can't perform a request twice, or perform an already-cancelled "
370 "request");
371 }
372 performed_ = true;
373 callback_ = callback;
374 return absl::OkStatus();
375 }
376
ReadRequestBody(JNIEnv * env,jbyteArray buffer,jlong requested_bytes,jintArray actual_bytes_read)377 jboolean JavaHttpRequestHandle::ReadRequestBody(JNIEnv* env, jbyteArray buffer,
378 jlong requested_bytes,
379 jintArray actual_bytes_read) {
380 // Get a pointer to the output buffer's raw data. Note that this may make a
381 // copy of the Java data, but depending on JVM implementation it may also
382 // return a direct pointer to it, avoiding the copy (on Android, ART will
383 // generally avoid copying if the array is large enough).
384 jbyte* raw_buffer = env->GetByteArrayElements(buffer, nullptr);
385 FCP_CHECK(raw_buffer != nullptr);
386 FCP_CHECK(!env->ExceptionCheck());
387 // Ask the `HttpRequest` to write the request body data into the buffer.
388 absl::StatusOr<int64_t> read_body_result =
389 request_->ReadBody(reinterpret_cast<char*>(raw_buffer), requested_bytes);
390 // Release the raw buffer pointer (we must always do this, even if we hit an
391 // error and didn't write anything to the buffer).
392 //
393 // This ensures that the data in raw_buffer is now visible via the Java buffer
394 // (as noted above, this may result in copying the data into the Java heap,
395 // but if a direct pointer was returned earlier on then this will be a no-op).
396 env->ReleaseByteArrayElements(buffer, raw_buffer, 0);
397 FCP_CHECK(!env->ExceptionCheck());
398
399 // Out of range is expected, and marks the end of the body. Any other error is
400 // unrecoverable, and should result in the OnResponseError being called.
401 if (!read_body_result.ok() &&
402 read_body_result.status().code() != absl::StatusCode::kOutOfRange) {
403 // If we receive an error during the reading of the request body, we
404 // immediately forward that error to the HttpRequestCallback (i.e. the Java
405 // layer will not need to call this callback method anymore). This ensures
406 // that we can forward the original error back to the callback (without
407 // having to convert it to a Java representation and back to a Status
408 // again).
409 callback()->OnResponseError(
410 *request_,
411 absl::Status(read_body_result.status().code(),
412 absl::StrCat("failed to read request body",
413 read_body_result.status().message())));
414 return JNI_FALSE;
415 }
416
417 // Otherwise, if everything went successfully, then we still need to write the
418 // actual amount of data we read (or -1 if we hit the end of the data) to the
419 // `actual_bytes_read` output array (the output array provides a convenient
420 // way to return something in addition to the return value, while still using
421 // only primitive Java types to keep the JNI boilerplate to a minimum).
422 //
423 // Note: we know that casting from int64_t to jint (aka a 32 bit int) should
424 // be safe, since `requested_bytes` is a jint, and the actual bytes read can
425 // never be larger than that number.
426 jint actual_bytes_read_result[] = {
427 static_cast<jint>(read_body_result.value_or(-1))};
428 env->SetIntArrayRegion(actual_bytes_read, 0, 1, actual_bytes_read_result);
429 return JNI_TRUE;
430 }
431
OnResponseStarted(JNIEnv * env,jbyteArray response_proto)432 jboolean JavaHttpRequestHandle::OnResponseStarted(JNIEnv* env,
433 jbyteArray response_proto) {
434 // Populate the response_ field based on the serialized response proto. This
435 // will allow us to access it in subsequent callbacks as well.
436 {
437 absl::MutexLock _(&lock_);
438 response_.PopulateFromProto(
439 ParseProtoFromJByteArray<JniHttpResponse>(env, response_proto));
440 }
441 return callback()->OnResponseStarted(*request_, response()).ok() ? JNI_TRUE
442 : JNI_FALSE;
443 }
444
OnResponseError(JNIEnv * env,jbyteArray status_proto)445 void JavaHttpRequestHandle::OnResponseError(JNIEnv* env,
446 jbyteArray status_proto) {
447 absl::Status status = ConvertRpcStatusToAbslStatus(
448 ParseProtoFromJByteArray<
449 ::google::internal::federatedcompute::v1::Status>(env, status_proto));
450 callback()->OnResponseError(*request_, status);
451 }
452
OnResponseBody(JNIEnv * env,jbyteArray buffer,jint bytes_available)453 jboolean JavaHttpRequestHandle::OnResponseBody(JNIEnv* env, jbyteArray buffer,
454 jint bytes_available) {
455 // Get a pointer to the input buffer's raw data. Note that this may make a
456 // copy of the Java data, but depending on JVM implementation it may also
457 // return a direct pointer to it, avoiding the copy (on Android, ART will
458 // generally avoid copying if the array is large enough).
459 jbyte* raw_buffer = env->GetByteArrayElements(buffer, nullptr);
460 FCP_CHECK(raw_buffer != nullptr);
461 FCP_CHECK(!env->ExceptionCheck());
462 absl::string_view buffer_view(reinterpret_cast<char*>(raw_buffer),
463 bytes_available);
464 // Pass the response body data to the HttpRequestCallback.
465 auto result = callback()->OnResponseBody(*request_, response(), buffer_view);
466
467 // JNI_ABORT ensures that we don't copy the bytes in the raw buffer back to
468 // the main buffer (since we know they weren't modified).
469 env->ReleaseByteArrayElements(buffer, raw_buffer, JNI_ABORT);
470
471 return result.ok() ? JNI_TRUE : JNI_FALSE;
472 }
473
OnResponseBodyError(JNIEnv * env,jbyteArray status_proto)474 void JavaHttpRequestHandle::OnResponseBodyError(JNIEnv* env,
475 jbyteArray status_proto) {
476 absl::Status status = ConvertRpcStatusToAbslStatus(
477 ParseProtoFromJByteArray<
478 ::google::internal::federatedcompute::v1::Status>(env, status_proto));
479 callback()->OnResponseBodyError(*request_, response(), status);
480 }
481
OnResponseCompleted()482 void JavaHttpRequestHandle::OnResponseCompleted() {
483 callback()->OnResponseCompleted(*request_, response());
484 }
485
486 // JNI functions. These are called from Java. We just forward them to the
487 // appropriate JavaHttpRequestHandle instance's member function.
488 #define JFUN(METHOD_NAME) \
489 Java_com_google_fcp_client_http_HttpClientForNative_##METHOD_NAME // NOLINT
490
JFUN(readRequestBody)491 extern "C" JNIEXPORT jboolean JNICALL JFUN(readRequestBody)(
492 JNIEnv* env, jclass, jlong request_handle_ptr, jbyteArray buffer,
493 jlong requested_bytes, jintArray actual_bytes_read) {
494 return JavaHttpRequestHandle::FromJlong(request_handle_ptr)
495 ->ReadRequestBody(env, buffer, requested_bytes, actual_bytes_read);
496 }
497
JFUN(onResponseStarted)498 extern "C" JNIEXPORT jboolean JNICALL JFUN(onResponseStarted)(
499 JNIEnv* env, jclass, jlong request_handle_ptr, jbyteArray response_proto) {
500 return JavaHttpRequestHandle::FromJlong(request_handle_ptr)
501 ->OnResponseStarted(env, response_proto);
502 }
503
JFUN(onResponseError)504 extern "C" JNIEXPORT void JNICALL JFUN(onResponseError)(
505 JNIEnv* env, jclass, jlong request_handle_ptr, jbyteArray status_proto) {
506 return JavaHttpRequestHandle::FromJlong(request_handle_ptr)
507 ->OnResponseError(env, status_proto);
508 }
509
510 extern "C" JNIEXPORT jboolean JNICALL
JFUN(onResponseBody)511 JFUN(onResponseBody)(JNIEnv* env, jclass, jlong request_handle_ptr,
512 jbyteArray buffer, jint bytes_available) {
513 return JavaHttpRequestHandle::FromJlong(request_handle_ptr)
514 ->OnResponseBody(env, buffer, bytes_available);
515 }
516
JFUN(onResponseBodyError)517 extern "C" JNIEXPORT void JNICALL JFUN(onResponseBodyError)(
518 JNIEnv* env, jclass, jlong request_handle_ptr, jbyteArray status_proto) {
519 JavaHttpRequestHandle::FromJlong(request_handle_ptr)
520 ->OnResponseBodyError(env, status_proto);
521 }
522
523 extern "C" JNIEXPORT void JNICALL
JFUN(onResponseCompleted)524 JFUN(onResponseCompleted)(JNIEnv* env, jclass, jlong request_handle_ptr) {
525 JavaHttpRequestHandle::FromJlong(request_handle_ptr)->OnResponseCompleted();
526 }
527
528 } // namespace java
529 } // namespace http
530 } // namespace client
531 } // namespace fcp
532