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 #ifndef FCP_CLIENT_HTTP_JAVA_JAVA_HTTP_CLIENT_H_ 17 #define FCP_CLIENT_HTTP_JAVA_JAVA_HTTP_CLIENT_H_ 18 19 #include <jni.h> 20 21 #include <cstdint> 22 #include <memory> 23 #include <optional> 24 #include <string> 25 #include <utility> 26 #include <vector> 27 28 #include "absl/base/attributes.h" 29 #include "absl/status/status.h" 30 #include "absl/status/statusor.h" 31 #include "absl/strings/string_view.h" 32 #include "absl/synchronization/mutex.h" 33 #include "fcp/client/http/http_client.h" 34 #include "fcp/client/http/java/jni.pb.h" 35 36 namespace fcp { 37 namespace client { 38 namespace http { 39 namespace java { 40 41 // An `HttpClient` implementation that performs HTTP requests by calling back 42 // into Java over JNI, and lets the Java code issue the actual requests. 43 class JavaHttpClient : public HttpClient { 44 public: 45 JavaHttpClient(JavaVM* jvm, jobject java_http_client); 46 ~JavaHttpClient() override; 47 48 ABSL_MUST_USE_RESULT 49 std::unique_ptr<fcp::client::http::HttpRequestHandle> EnqueueRequest( 50 std::unique_ptr<fcp::client::http::HttpRequest> request) override; 51 52 absl::Status PerformRequests( 53 std::vector<std::pair<fcp::client::http::HttpRequestHandle*, 54 fcp::client::http::HttpRequestCallback*>> 55 requests) override; 56 57 private: 58 JavaVM* const jvm_; 59 jobject jthis_; 60 jmethodID enqueue_request_id_; 61 jmethodID perform_requests_id_; 62 jmethodID close_id_; 63 }; 64 65 // An HttpResponse implementation that is based on data provided via the JNI 66 // callbacks. 67 class JavaHttpResponse : public fcp::client::http::HttpResponse { 68 public: JavaHttpResponse()69 JavaHttpResponse() {} 70 71 // Populates the response data based on the given JNI proto. PopulateFromProto(const JniHttpResponse & response_proto)72 void PopulateFromProto(const JniHttpResponse& response_proto) { 73 code_ = response_proto.code(); 74 headers_.clear(); 75 for (const JniHttpHeader& header : response_proto.headers()) { 76 headers_.push_back(Header(header.name(), header.value())); 77 } 78 } 79 code()80 int code() const override { return code_; } headers()81 const HeaderList& headers() const override { return headers_; } 82 83 private: 84 int code_ = -1; 85 HeaderList headers_; 86 }; 87 88 // An `HttpRequestHandle` implementation that performs HTTP requests by calling 89 // back into Java over JNI, and lets the Java code issue the actual requests. 90 class JavaHttpRequestHandle : public fcp::client::http::HttpRequestHandle { 91 public: 92 // Utility for extracting a JavaHttpRequestHandle pointer from a Java jlong. 93 // When a JavaHttpRequestHandle object is constructed, it will 'attach' itself 94 // to the corresponding Java object by writing its address to its 95 // `nativeHandle` field. When Java then calls back into our C++ code, it 96 // passes that address back to us, and this function can then be used to turn 97 // the address into a proper pointer. 98 static JavaHttpRequestHandle* FromJlong(jlong ptr); 99 100 JavaHttpRequestHandle( 101 JavaVM* jvm, jobject java_http_request_handle, 102 std::unique_ptr<fcp::client::http::HttpRequest> request); 103 104 ~JavaHttpRequestHandle() override ABSL_LOCKS_EXCLUDED(lock_); 105 106 // --- HttpRequestHandle methods 107 fcp::client::http::HttpRequestHandle::SentReceivedBytes 108 TotalSentReceivedBytes() const override; 109 void Cancel() override ABSL_LOCKS_EXCLUDED(lock_); 110 111 // --- JNI handling methods 112 jboolean ReadRequestBody(JNIEnv* env, jbyteArray buffer, 113 jlong requested_bytes, jintArray actual_bytes_read) 114 ABSL_LOCKS_EXCLUDED(lock_); 115 116 jboolean OnResponseStarted(JNIEnv* env, jbyteArray response_proto) 117 ABSL_LOCKS_EXCLUDED(lock_); 118 void OnResponseError(JNIEnv* env, jbyteArray status_proto) 119 ABSL_LOCKS_EXCLUDED(lock_); 120 121 jboolean OnResponseBody(JNIEnv* env, jbyteArray buffer, jint bytes_available) 122 ABSL_LOCKS_EXCLUDED(lock_); 123 void OnResponseBodyError(JNIEnv* env, jbyteArray status_proto) 124 ABSL_LOCKS_EXCLUDED(lock_); 125 void OnResponseCompleted() ABSL_LOCKS_EXCLUDED(lock_); 126 127 // --- Internal methods. 128 // Returns the (possibly empty/default-constructed) response object for this 129 // handle. This object is populated with actual data after `OnResponseStarted` 130 // is called. 131 const JavaHttpResponse& response() const ABSL_LOCKS_EXCLUDED(lock_); 132 133 // Returns the `HttpRequestCallback` associated with this handle, or a nullptr 134 // if no callback is associated with it yet (i.e. no `PerformRequests` call 135 // was made yet with this handle). 136 fcp::client::http::HttpRequestCallback* callback() const 137 ABSL_LOCKS_EXCLUDED(lock_); 138 139 // Associates an `HttpRequestCallback` with this handle, or returns an 140 // `INVALID_ARGUMENT` error if one was already associated with it (indicating 141 // the handle is being used with more than one `PerformRequests` call, which 142 // is not allowed). 143 absl::Status SetCallback(fcp::client::http::HttpRequestCallback* callback) 144 ABSL_LOCKS_EXCLUDED(lock_); 145 146 // Returns the JNI reference for the Java handle object that corresponds to 147 // this C++ handle object. GetJobject()148 jobject GetJobject() { return jthis_; } 149 150 private: 151 JavaVM* const jvm_; 152 jobject jthis_; 153 jmethodID get_total_sent_received_bytes_id_; 154 jmethodID close_id_; 155 jfieldID native_handle_id_; 156 157 const std::unique_ptr<fcp::client::http::HttpRequest> request_; 158 159 // A note on synchronization: most of JavaHttpRequestHandle's methods may be 160 // called from any thread (incl. the JNI callback methods, which are 161 // guaranteed to never be called concurrently from more than one thread, but 162 // which for which subsequent calls may occur on different threads). This 163 // object also has mutable state (e.g. `response_` which only gets populated 164 // with data once the `OnResponseStarted` callback is invoked). 165 // 166 // We use this mutex to ensure that each JNI callback invocation observes the 167 // effects of every prior callback invocation. This means we don't have to 168 // rely on the Java side of the implementation for thread safety (even though 169 // the Java side likely also implements its own synchronization as well). 170 mutable absl::Mutex lock_; 171 JavaHttpResponse response_ ABSL_GUARDED_BY(lock_); 172 fcp::client::http::HttpRequestCallback* callback_ ABSL_GUARDED_BY(lock_) = 173 nullptr; 174 bool performed_ ABSL_GUARDED_BY(lock_) = false; 175 }; 176 177 } // namespace java 178 } // namespace http 179 } // namespace client 180 } // namespace fcp 181 #endif // FCP_CLIENT_HTTP_JAVA_JAVA_HTTP_CLIENT_H_ 182