1 // Copyright 2022 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 package com.google.fcp.client.http; 15 16 import com.google.fcp.client.CallFromNativeWrapper; 17 import com.google.protobuf.ExtensionRegistryLite; 18 import com.google.protobuf.InvalidProtocolBufferException; 19 import com.google.rpc.Code; 20 import com.google.rpc.Status; 21 import java.net.HttpURLConnection; 22 import java.util.ArrayList; 23 24 /** 25 * An implementation of {@link HttpClientForNativeImpl} that uses {@link HttpURLConnection} for 26 * issuing network requests. 27 */ 28 public final class HttpClientForNativeImpl extends HttpClientForNative { 29 30 /** Used to bubble up unexpected errors and exceptions. */ 31 static final class UncheckedHttpClientForNativeException extends RuntimeException { UncheckedHttpClientForNativeException(String message)32 UncheckedHttpClientForNativeException(String message) { 33 super(message); 34 } 35 UncheckedHttpClientForNativeException(String message, Throwable cause)36 UncheckedHttpClientForNativeException(String message, Throwable cause) { 37 super(message, cause); 38 } 39 } 40 41 /** A factory for creating an {@link HttpRequestHandleImpl} for a given {@link JniHttpRequest}. */ 42 public interface HttpRequestHandleImplFactory { 43 /** 44 * Creates a new request handle, which must be an instance of {@link HttpRequestHandleImpl} or 45 * one of its subclasses. This indirection is used to provide a different subclass in unit 46 * tests. 47 */ create(JniHttpRequest request)48 HttpRequestHandleImpl create(JniHttpRequest request); 49 } 50 51 private final CallFromNativeWrapper callFromNativeWrapper; 52 private final HttpRequestHandleImplFactory requestHandleFactory; 53 54 /** 55 * Creates a new instance, configured with the provided parameters. 56 * 57 * @param callFromNativeWrapper the wrapper to use for all calls that arrive over JNI, to ensure 58 * uncaught exceptions are handled correctly. 59 * @param requestHandleFactory the factory to use to create new {@link HttpRequestHandleImpl} for 60 * a given {@link JniHttpRequest}. 61 */ HttpClientForNativeImpl( CallFromNativeWrapper callFromNativeWrapper, HttpRequestHandleImplFactory requestHandleFactory)62 public HttpClientForNativeImpl( 63 CallFromNativeWrapper callFromNativeWrapper, 64 HttpRequestHandleImplFactory requestHandleFactory) { 65 this.callFromNativeWrapper = callFromNativeWrapper; 66 this.requestHandleFactory = requestHandleFactory; 67 } 68 69 @Override enqueueRequest(byte[] requestProto)70 public HttpRequestHandleImpl enqueueRequest(byte[] requestProto) { 71 return callFromNativeWrapper.wrapCall( 72 () -> { 73 // Parse the request given to us over JNI. 74 JniHttpRequest request; 75 try { 76 request = 77 JniHttpRequest.parseFrom(requestProto, ExtensionRegistryLite.getEmptyRegistry()); 78 } catch (InvalidProtocolBufferException e) { 79 // If parsing failed then the native code did something horribly wrong, just let the 80 // exception bubble up to the unchecked exception handler. 81 throw new UncheckedHttpClientForNativeException("invalid JniHttpRequest", e); 82 } 83 return requestHandleFactory.create(request); 84 }); 85 } 86 87 @Override performRequests(Object[] requestsParam)88 public byte[] performRequests(Object[] requestsParam) { 89 return callFromNativeWrapper.wrapCall( 90 () -> { 91 ArrayList<HttpRequestHandleImpl> handles = new ArrayList<>(requestsParam.length); 92 for (Object requestHandle : requestsParam) { 93 // Note: if this cast fails, then it means that the native layer has somehow passed us a 94 // different object than we returned from enqueueRequest, which would indicate a bug. In 95 // those cases we just let the exception bubble up to create a crash report. 96 HttpRequestHandleImpl handle = (HttpRequestHandleImpl) requestHandle; 97 handles.add(handle); 98 // Handle each request on the ExecutorService (i.e. on background threads). 99 handle.performRequest(); 100 } 101 // Wait for each request to finish. 102 for (HttpRequestHandleImpl handle : handles) { 103 handle.waitForRequestCompletion(); 104 } 105 106 return Status.newBuilder().setCode(Code.OK_VALUE).build().toByteArray(); 107 }); 108 } 109 110 @Override 111 public void close() { 112 // Nothing to do here. 113 } 114 } 115