• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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