Repository: firebase/firebase-admin-java
Pull request #462 (2020-07-28 23:33:56)

fix: Handling JSON serialization/response interception at ErrorHandlingHttpClient
* Exposing `setInterceptor()` method on `ErrorHandlingHttpClient`. By handling it here, individual services no longer have to have a reference for `HttpResponseInterceptor`.
* Added support for serializing JSON request payloads at the `HttpRequestInfo`. This means individual services no longer have to handle this or even have a reference to a `JsonFactory`.
* Removing some leftover http method override code in `AuthHttpClient` (this became redundant as of #459)
* Upgraded Surefire and Jacoco test coverage dependencies.


Patch:
diff --git a/pom.xml b/pom.xml
index 33e235d..e61a57c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -198,7 +198,7 @@
                     <plugin>
                         <groupId>org.jacoco</groupId>
                         <artifactId>jacoco-maven-plugin</artifactId>
-                        <version>0.7.9</version>
+                        <version>0.8.5</version>
                         <executions>
                             <execution>
                                 <id>pre-unit-test</id>
@@ -289,7 +289,7 @@
             <!-- Test Phase -->
             <plugin>
                 <artifactId>maven-surefire-plugin</artifactId>
-                <version>2.19.1</version>
+                <version>2.22.0</version>
                 <configuration>
                     <skipTests>${skipUTs}</skipTests>
                 </configuration>
diff --git a/src/main/java/com/google/firebase/auth/FirebaseUserManager.java b/src/main/java/com/google/firebase/auth/FirebaseUserManager.java
index 55b854e..d00cd24 100644
--- a/src/main/java/com/google/firebase/auth/FirebaseUserManager.java
+++ b/src/main/java/com/google/firebase/auth/FirebaseUserManager.java
@@ -80,7 +80,7 @@ class FirebaseUserManager {
   private final JsonFactory jsonFactory;
   private final AuthHttpClient httpClient;
 
-  FirebaseUserManager(Builder builder) {
+  private FirebaseUserManager(Builder builder) {
     FirebaseApp app = checkNotNull(builder.app, "FirebaseApp must not be null");
     String projectId = ImplFirebaseTrampolines.getProjectId(app);
     checkArgument(!Strings.isNullOrEmpty(projectId),
diff --git a/src/main/java/com/google/firebase/auth/internal/AuthHttpClient.java b/src/main/java/com/google/firebase/auth/internal/AuthHttpClient.java
index fe1c820..cc6cfc9 100644
--- a/src/main/java/com/google/firebase/auth/internal/AuthHttpClient.java
+++ b/src/main/java/com/google/firebase/auth/internal/AuthHttpClient.java
@@ -17,10 +17,8 @@
 package com.google.firebase.auth.internal;
 
 import com.google.api.client.http.GenericUrl;
-import com.google.api.client.http.HttpContent;
 import com.google.api.client.http.HttpRequestFactory;
 import com.google.api.client.http.HttpResponseInterceptor;
-import com.google.api.client.http.json.JsonHttpContent;
 import com.google.api.client.json.JsonFactory;
 import com.google.common.collect.ImmutableSortedSet;
 import com.google.firebase.IncomingHttpResponse;
@@ -41,13 +39,9 @@ public final class AuthHttpClient {
 
   private static final String CLIENT_VERSION = "Java/Admin/" + SdkUtils.getVersion();
 
-  private final JsonFactory jsonFactory;
   private final ErrorHandlingHttpClient<FirebaseAuthException> httpClient;
 
-  private HttpResponseInterceptor interceptor;
-
   public AuthHttpClient(JsonFactory jsonFactory, HttpRequestFactory requestFactory) {
-    this.jsonFactory = jsonFactory;
     AuthErrorHandler authErrorHandler = new AuthErrorHandler(jsonFactory);
     this.httpClient = new ErrorHandlingHttpClient<>(requestFactory, jsonFactory, authErrorHandler);
   }
@@ -68,25 +62,21 @@ public final class AuthHttpClient {
   }
 
   public void setInterceptor(HttpResponseInterceptor interceptor) {
-    this.interceptor = interceptor;
+    this.httpClient.setInterceptor(interceptor);
   }
 
   public IncomingHttpResponse sendRequest(
       String method, GenericUrl url, @Nullable Object content) throws FirebaseAuthException {
-    HttpContent httpContent = content != null ? new JsonHttpContent(jsonFactory, content) : null;
-    HttpRequestInfo request = HttpRequestInfo.buildRequest(method, url, httpContent)
-        .addHeader(CLIENT_VERSION_HEADER, CLIENT_VERSION)
-        .setResponseInterceptor(interceptor);
-    if (method.equals("PATCH")) {
-      request.addHeader("X-HTTP-Method-Override", "PATCH");
-    }
-
+    HttpRequestInfo request = HttpRequestInfo.buildJsonRequest(method, url, content)
+        .addHeader(CLIENT_VERSION_HEADER, CLIENT_VERSION);
     return httpClient.send(request);
   }
 
   public <T> T sendRequest(
-      String method, GenericUrl url,
-      @Nullable Object content, Class<T> clazz) throws FirebaseAuthException {
+      String method,
+      GenericUrl url,
+      @Nullable Object content,
+      Class<T> clazz) throws FirebaseAuthException {
 
     IncomingHttpResponse response = this.sendRequest(method, url, content);
     return this.parse(response, clazz);
diff --git a/src/main/java/com/google/firebase/auth/internal/CryptoSigners.java b/src/main/java/com/google/firebase/auth/internal/CryptoSigners.java
index c20c82a..7bc54af 100644
--- a/src/main/java/com/google/firebase/auth/internal/CryptoSigners.java
+++ b/src/main/java/com/google/firebase/auth/internal/CryptoSigners.java
@@ -8,7 +8,6 @@ import com.google.api.client.http.HttpRequest;
 import com.google.api.client.http.HttpRequestFactory;
 import com.google.api.client.http.HttpResponse;
 import com.google.api.client.http.HttpResponseInterceptor;
-import com.google.api.client.http.json.JsonHttpContent;
 import com.google.api.client.json.GenericJson;
 import com.google.api.client.json.JsonFactory;
 import com.google.api.client.util.StringUtils;
@@ -75,9 +74,7 @@ public class CryptoSigners {
         "https://iam.googleapis.com/v1/projects/-/serviceAccounts/%s:signBlob";
 
     private final String serviceAccount;
-    private final JsonFactory jsonFactory;
     private final ErrorHandlingHttpClient<FirebaseAuthException> httpClient;
-    private HttpResponseInterceptor interceptor;
 
     IAMCryptoSigner(
         @NonNull HttpRequestFactory requestFactory,
@@ -85,7 +82,6 @@ public class CryptoSigners {
         @NonNull String serviceAccount) {
       checkArgument(!Strings.isNullOrEmpty(serviceAccount));
       this.serviceAccount = serviceAccount;
-      this.jsonFactory = checkNotNull(jsonFactory);
       this.httpClient = new ErrorHandlingHttpClient<>(
           requestFactory,
           jsonFactory,
@@ -93,7 +89,7 @@ public class CryptoSigners {
     }
 
     void setInterceptor(HttpResponseInterceptor interceptor) {
-      this.interceptor = interceptor;
+      httpClient.setInterceptor(interceptor);
     }
 
     @Override
@@ -101,9 +97,7 @@ public class CryptoSigners {
       String encodedPayload = BaseEncoding.base64().encode(payload);
       Map<String, String> content = ImmutableMap.of("bytesToSign", encodedPayload);
       String encodedUrl = String.format(IAM_SIGN_BLOB_URL, serviceAccount);
-      HttpRequestInfo requestInfo = HttpRequestInfo
-          .buildPostRequest(encodedUrl, new JsonHttpContent(jsonFactory, content))
-          .setResponseInterceptor(interceptor);
+      HttpRequestInfo requestInfo = HttpRequestInfo.buildJsonPostRequest(encodedUrl, content);
       GenericJson parsed = httpClient.sendAndParse(requestInfo, GenericJson.class);
       return BaseEncoding.base64().decode((String) parsed.get("signature"));
     }
diff --git a/src/main/java/com/google/firebase/iid/FirebaseInstanceId.java b/src/main/java/com/google/firebase/iid/FirebaseInstanceId.java
index 63c46f2..8226544 100644
--- a/src/main/java/com/google/firebase/iid/FirebaseInstanceId.java
+++ b/src/main/java/com/google/firebase/iid/FirebaseInstanceId.java
@@ -65,8 +65,6 @@ public class FirebaseInstanceId {
   private final String projectId;
   private final ErrorHandlingHttpClient<FirebaseInstanceIdException> httpClient;
 
-  private HttpResponseInterceptor interceptor;
-
   private FirebaseInstanceId(FirebaseApp app) {
     this(app, null);
   }
@@ -115,7 +113,7 @@ public class FirebaseInstanceId {
 
   @VisibleForTesting
   void setInterceptor(HttpResponseInterceptor interceptor) {
-    this.interceptor = interceptor;
+    httpClient.setInterceptor(interceptor);
   }
 
   /**
@@ -154,8 +152,7 @@ public class FirebaseInstanceId {
       protected Void execute() throws FirebaseInstanceIdException {
         String url = String.format(
             "%s/project/%s/instanceId/%s", IID_SERVICE_URL, projectId, instanceId);
-        HttpRequestInfo request = HttpRequestInfo.buildDeleteRequest(url)
-            .setResponseInterceptor(interceptor);
+        HttpRequestInfo request = HttpRequestInfo.buildDeleteRequest(url);
         httpClient.send(request);
         return null;
       }
diff --git a/src/main/java/com/google/firebase/internal/ErrorHandlingHttpClient.java b/src/main/java/com/google/firebase/internal/ErrorHandlingHttpClient.java
index f533f13..e449568 100644
--- a/src/main/java/com/google/firebase/internal/ErrorHandlingHttpClient.java
+++ b/src/main/java/com/google/firebase/internal/ErrorHandlingHttpClient.java
@@ -22,6 +22,7 @@ import com.google.api.client.http.HttpRequest;
 import com.google.api.client.http.HttpRequestFactory;
 import com.google.api.client.http.HttpResponse;
 import com.google.api.client.http.HttpResponseException;
+import com.google.api.client.http.HttpResponseInterceptor;
 import com.google.api.client.json.JsonFactory;
 import com.google.api.client.json.JsonObjectParser;
 import com.google.api.client.json.JsonParser;
@@ -41,6 +42,9 @@ public final class ErrorHandlingHttpClient<T extends FirebaseException> {
   private final HttpRequestFactory requestFactory;
   private final JsonFactory jsonFactory;
   private final HttpErrorHandler<T> errorHandler;
+  private final JsonObjectParser jsonParser;
+
+  private HttpResponseInterceptor interceptor;
 
   public ErrorHandlingHttpClient(
       HttpRequestFactory requestFactory,
@@ -49,6 +53,12 @@ public final class ErrorHandlingHttpClient<T extends FirebaseException> {
     this.requestFactory = checkNotNull(requestFactory, "requestFactory must not be null");
     this.jsonFactory = checkNotNull(jsonFactory, "jsonFactory must not be null");
     this.errorHandler = checkNotNull(errorHandler, "errorHandler must not be null");
+    this.jsonParser = new JsonObjectParser(jsonFactory);
+  }
+
+  public ErrorHandlingHttpClient<T> setInterceptor(HttpResponseInterceptor interceptor) {
+    this.interceptor = interceptor;
+    return this;
   }
 
   /**
@@ -127,8 +137,9 @@ public final class ErrorHandlingHttpClient<T extends FirebaseException> {
 
   private HttpRequest createHttpRequest(HttpRequestInfo requestInfo) throws T {
     try {
-      return requestInfo.newHttpRequest(requestFactory)
-          .setParser(new JsonObjectParser(jsonFactory));
+      return requestInfo.newHttpRequest(requestFactory, jsonFactory)
+          .setParser(jsonParser)
+          .setResponseInterceptor(interceptor);
     } catch (IOException e) {
       // Handle request initialization errors (credential loading and other config errors)
       throw errorHandler.handleIOException(e);
diff --git a/src/main/java/com/google/firebase/internal/HttpRequestInfo.java b/src/main/java/com/google/firebase/internal/HttpRequestInfo.java
index b7e00cc..cb687b7 100644
--- a/src/main/java/com/google/firebase/internal/HttpRequestInfo.java
+++ b/src/main/java/com/google/firebase/internal/HttpRequestInfo.java
@@ -24,7 +24,8 @@ import com.google.api.client.http.HttpContent;
 import com.google.api.client.http.HttpMethods;
 import com.google.api.client.http.HttpRequest;
 import com.google.api.client.http.HttpRequestFactory;
-import com.google.api.client.http.HttpResponseInterceptor;
+import com.google.api.client.http.json.JsonHttpContent;
+import com.google.api.client.json.JsonFactory;
 import com.google.common.base.Strings;
 import java.io.IOException;
 import java.util.HashMap;
@@ -39,14 +40,15 @@ public final class HttpRequestInfo {
   private final String method;
   private final GenericUrl url;
   private final HttpContent content;
+  private final Object jsonContent;
   private final Map<String, String> headers = new HashMap<>();
-  private HttpResponseInterceptor interceptor;
 
-  private HttpRequestInfo(String method, GenericUrl url, HttpContent content) {
+  private HttpRequestInfo(String method, GenericUrl url, HttpContent content, Object jsonContent) {
     checkArgument(!Strings.isNullOrEmpty(method), "method must not be null");
     this.method = method;
     this.url = checkNotNull(url, "url must not be null");
     this.content = content;
+    this.jsonContent = jsonContent;
   }
 
   public HttpRequestInfo addHeader(String name, String value) {
@@ -59,11 +61,6 @@ public final class HttpRequestInfo {
     return this;
   }
 
-  public HttpRequestInfo setResponseInterceptor(HttpResponseInterceptor interceptor) {
-    this.interceptor = interceptor;
-    return this;
-  }
-
   public static HttpRequestInfo buildGetRequest(String url) {
     return buildRequest(HttpMethods.GET, url, null);
   }
@@ -72,10 +69,6 @@ public final class HttpRequestInfo {
     return buildRequest(HttpMethods.DELETE, url, null);
   }
 
-  public static HttpRequestInfo buildPostRequest(String url, HttpContent content) {
-    return buildRequest(HttpMethods.POST, url, content);
-  }
-
   public static HttpRequestInfo buildRequest(
       String method, String url, @Nullable HttpContent content) {
     return buildRequest(method, new GenericUrl(url), content);
@@ -83,26 +76,53 @@ public final class HttpRequestInfo {
 
   public static HttpRequestInfo buildRequest(
       String method, GenericUrl url, @Nullable HttpContent content) {
-    return new HttpRequestInfo(method, url, content);
+    return new HttpRequestInfo(method, url, content, null);
+  }
+
+  public static HttpRequestInfo buildJsonPostRequest(String url, @Nullable Object content) {
+    return buildJsonRequest(HttpMethods.POST, url, content);
+  }
+
+  public static HttpRequestInfo buildJsonRequest(
+      String method, String url, @Nullable Object content) {
+    return buildJsonRequest(method, new GenericUrl(url), content);
   }
 
-  HttpRequest newHttpRequest(HttpRequestFactory factory) throws IOException {
+  public static HttpRequestInfo buildJsonRequest(
+      String method, GenericUrl url, @Nullable Object content) {
+    return new HttpRequestInfo(method, url, null, content);
+  }
+
+  HttpRequest newHttpRequest(
+      HttpRequestFactory factory, JsonFactory jsonFactory) throws IOException {
     HttpRequest request;
+    HttpContent httpContent = getContent(jsonFactory);
     if (factory.getTransport().supportsMethod(method)) {
-      request = factory.buildRequest(method, url, content);
+      request = factory.buildRequest(method, url, httpContent);
     } else {
       // Some HttpTransport implementations (notably NetHttpTransport) don't support new methods
       // like PATCH. We try to emulate such requests over POST by setting the method override
       // header, which is recognized by most Google backend APIs.
-      request = factory.buildPostRequest(url, content);
+      request = factory.buildPostRequest(url, httpContent);
       request.getHeaders().set("X-HTTP-Method-Override", method);
     }
 
     for (Map.Entry<String, String> entry : headers.entrySet()) {
       request.getHeaders().set(entry.getKey(), entry.getValue());
     }
-    request.setResponseInterceptor(interceptor);
 
     return request;
   }
+
+  private HttpContent getContent(JsonFactory jsonFactory) {
+    if (content != null) {
+      return content;
+    }
+
+    if (jsonContent != null) {
+      return new JsonHttpContent(jsonFactory, jsonContent);
+    }
+
+    return null;
+  }
 }
diff --git a/src/main/java/com/google/firebase/messaging/FirebaseMessagingClientImpl.java b/src/main/java/com/google/firebase/messaging/FirebaseMessagingClientImpl.java
index f954e2b..43b9b34 100644
--- a/src/main/java/com/google/firebase/messaging/FirebaseMessagingClientImpl.java
+++ b/src/main/java/com/google/firebase/messaging/FirebaseMessagingClientImpl.java
@@ -84,8 +84,8 @@ final class FirebaseMessagingClientImpl implements FirebaseMessagingClient {
     this.jsonFactory = checkNotNull(builder.jsonFactory);
     this.responseInterceptor = builder.responseInterceptor;
     this.errorHandler = new MessagingErrorHandler(this.jsonFactory);
-    this.httpClient = new ErrorHandlingHttpClient<>(
-        this.requestFactory, this.jsonFactory, this.errorHandler);
+    this.httpClient = new ErrorHandlingHttpClient<>(requestFactory, jsonFactory, errorHandler)
+      .setInterceptor(responseInterceptor);
     this.batchClient = new MessagingBatchClient(requestFactory.getTransport(), jsonFactory);
   }
 
@@ -121,10 +121,9 @@ final class FirebaseMessagingClientImpl implements FirebaseMessagingClient {
   private String sendSingleRequest(
       Message message, boolean dryRun) throws FirebaseMessagingException {
     HttpRequestInfo request =
-        HttpRequestInfo.buildPostRequest(
-            fcmSendUrl, new JsonHttpContent(jsonFactory, message.wrapForTransport(dryRun)))
-            .addAllHeaders(COMMON_HEADERS)
-            .setResponseInterceptor(responseInterceptor);
+        HttpRequestInfo.buildJsonPostRequest(
+            fcmSendUrl, message.wrapForTransport(dryRun))
+            .addAllHeaders(COMMON_HEADERS);
     MessagingServiceResponse parsed = httpClient.sendAndParse(
         request, MessagingServiceResponse.class);
     return parsed.getMessageId();
@@ -172,6 +171,8 @@ final class FirebaseMessagingClientImpl implements FirebaseMessagingClient {
     return new HttpRequestInitializer() {
       @Override
       public void initialize(HttpRequest request) throws IOException {
+        // Batch requests are not executed on the ErrorHandlingHttpClient. Therefore, they
+        // require some special handling at initialization.
         HttpRequestInitializer initializer = requestFactory.getInitializer();
         if (initializer != null) {
           initializer.initialize(request);
diff --git a/src/main/java/com/google/firebase/messaging/InstanceIdClientImpl.java b/src/main/java/com/google/firebase/messaging/InstanceIdClientImpl.java
index 81e953f..5648fcf 100644
--- a/src/main/java/com/google/firebase/messaging/InstanceIdClientImpl.java
+++ b/src/main/java/com/google/firebase/messaging/InstanceIdClientImpl.java
@@ -16,15 +16,11 @@
 
 package com.google.firebase.messaging;
 
-import static com.google.common.base.Preconditions.checkNotNull;
-
 import com.google.api.client.http.HttpRequestFactory;
 import com.google.api.client.http.HttpResponseInterceptor;
-import com.google.api.client.http.json.JsonHttpContent;
 import com.google.api.client.json.GenericJson;
 import com.google.api.client.json.JsonFactory;
 import com.google.api.client.util.Key;
-import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableMap;
 import com.google.firebase.FirebaseApp;
@@ -34,7 +30,6 @@ import com.google.firebase.internal.ApiClientUtils;
 import com.google.firebase.internal.ErrorHandlingHttpClient;
 import com.google.firebase.internal.HttpRequestInfo;
 import com.google.firebase.internal.Nullable;
-
 import java.io.IOException;
 import java.util.List;
 import java.util.Map;
@@ -52,8 +47,6 @@ final class InstanceIdClientImpl implements InstanceIdClient {
   private static final String IID_UNSUBSCRIBE_PATH = "iid/v1:batchRemove";
 
   private final ErrorHandlingHttpClient<FirebaseMessagingException> requestFactory;
-  private final JsonFactory jsonFactory;
-  private final HttpResponseInterceptor responseInterceptor;
 
   InstanceIdClientImpl(HttpRequestFactory requestFactory, JsonFactory jsonFactory) {
     this(requestFactory, jsonFactory, null);
@@ -63,10 +56,9 @@ final class InstanceIdClientImpl implements InstanceIdClient {
       HttpRequestFactory requestFactory,
       JsonFactory jsonFactory,
       @Nullable HttpResponseInterceptor responseInterceptor) {
-    this.requestFactory = new ErrorHandlingHttpClient<>(
-        requestFactory, jsonFactory, new InstanceIdErrorHandler(jsonFactory));
-    this.jsonFactory = checkNotNull(jsonFactory);
-    this.responseInterceptor = responseInterceptor;
+    InstanceIdErrorHandler errorHandler = new InstanceIdErrorHandler(jsonFactory);
+    this.requestFactory = new ErrorHandlingHttpClient<>(requestFactory, jsonFactory, errorHandler)
+      .setInterceptor(responseInterceptor);
   }
 
   static InstanceIdClientImpl fromApp(FirebaseApp app) {
@@ -75,11 +67,6 @@ final class InstanceIdClientImpl implements InstanceIdClient {
         app.getOptions().getJsonFactory());
   }
 
-  @VisibleForTesting
-  JsonFactory getJsonFactory() {
-    return jsonFactory;
-  }
-
   public TopicManagementResponse subscribeToTopic(
       String topic, List<String> registrationTokens) throws FirebaseMessagingException {
     return sendInstanceIdRequest(topic, registrationTokens, IID_SUBSCRIBE_PATH);
@@ -101,10 +88,8 @@ final class InstanceIdClientImpl implements InstanceIdClient {
         "registration_tokens", registrationTokens
     );
 
-    HttpRequestInfo request =
-        HttpRequestInfo.buildPostRequest(url, new JsonHttpContent(jsonFactory, payload))
-            .addHeader("access_token_auth", "true")
-            .setResponseInterceptor(responseInterceptor);
+    HttpRequestInfo request = HttpRequestInfo.buildJsonPostRequest(url, payload)
+        .addHeader("access_token_auth", "true");
     InstanceIdServiceResponse response = new InstanceIdServiceResponse();
     requestFactory.sendAndParse(request, response);
     return new TopicManagementResponse(response.results);
diff --git a/src/main/java/com/google/firebase/projectmanagement/HttpHelper.java b/src/main/java/com/google/firebase/projectmanagement/HttpHelper.java
index 788dde4..f9ec62a 100644
--- a/src/main/java/com/google/firebase/projectmanagement/HttpHelper.java
+++ b/src/main/java/com/google/firebase/projectmanagement/HttpHelper.java
@@ -19,7 +19,6 @@ package com.google.firebase.projectmanagement;
 import com.google.api.client.http.HttpMethods;
 import com.google.api.client.http.HttpRequestFactory;
 import com.google.api.client.http.HttpResponseInterceptor;
-import com.google.api.client.http.json.JsonHttpContent;
 import com.google.api.client.json.JsonFactory;
 import com.google.firebase.FirebaseException;
 import com.google.firebase.IncomingHttpResponse;
@@ -32,20 +31,17 @@ final class HttpHelper {
 
   private static final String CLIENT_VERSION_HEADER = "X-Client-Version";
 
-  private final String clientVersion = "Java/Admin/" + SdkUtils.getVersion();
-  private final JsonFactory jsonFactory;
-  private final ErrorHandlingHttpClient<FirebaseProjectManagementException> httpClient;
+  private static final String CLIENT_VERSION = "Java/Admin/" + SdkUtils.getVersion();
 
-  private HttpResponseInterceptor interceptor;
+  private final ErrorHandlingHttpClient<FirebaseProjectManagementException> httpClient;
 
   HttpHelper(JsonFactory jsonFactory, HttpRequestFactory requestFactory) {
-    this.jsonFactory = jsonFactory;
     ProjectManagementErrorHandler errorHandler = new ProjectManagementErrorHandler(jsonFactory);
     this.httpClient = new ErrorHandlingHttpClient<>(requestFactory, jsonFactory, errorHandler);
   }
 
   void setInterceptor(HttpResponseInterceptor interceptor) {
-    this.interceptor = interceptor;
+    httpClient.setInterceptor(interceptor);
   }
 
   <T> IncomingHttpResponse makeGetRequest(
@@ -67,7 +63,7 @@ final class HttpHelper {
       String requestIdentifier,
       String requestIdentifierDescription) throws FirebaseProjectManagementException {
     return makeRequest(
-        HttpRequestInfo.buildPostRequest(url, new JsonHttpContent(jsonFactory, payload)),
+        HttpRequestInfo.buildJsonPostRequest(url, payload),
         parsedResponseInstance,
         requestIdentifier,
         requestIdentifierDescription);
@@ -79,10 +75,11 @@ final class HttpHelper {
       T parsedResponseInstance,
       String requestIdentifier,
       String requestIdentifierDescription) throws FirebaseProjectManagementException {
-    HttpRequestInfo baseRequest = HttpRequestInfo.buildRequest(
-        HttpMethods.PATCH, url, new JsonHttpContent(jsonFactory, payload));
     makeRequest(
-        baseRequest, parsedResponseInstance, requestIdentifier, requestIdentifierDescription);
+        HttpRequestInfo.buildJsonRequest(HttpMethods.PATCH, url, payload),
+        parsedResponseInstance,
+        requestIdentifier,
+        requestIdentifierDescription);
   }
 
   <T> void makeDeleteRequest(
@@ -103,8 +100,7 @@ final class HttpHelper {
       String requestIdentifier,
       String requestIdentifierDescription) throws FirebaseProjectManagementException {
     try {
-      baseRequest.addHeader(CLIENT_VERSION_HEADER, clientVersion);
-      baseRequest.setResponseInterceptor(interceptor);
+      baseRequest.addHeader(CLIENT_VERSION_HEADER, CLIENT_VERSION);
       IncomingHttpResponse response = httpClient.send(baseRequest);
       httpClient.parse(response, parsedResponseInstance);
       return response;


Test patch:
diff --git a/src/test/java/com/google/firebase/internal/ErrorHandlingHttpClientTest.java b/src/test/java/com/google/firebase/internal/ErrorHandlingHttpClientTest.java
index 2f0490b..5919140 100644
--- a/src/test/java/com/google/firebase/internal/ErrorHandlingHttpClientTest.java
+++ b/src/test/java/com/google/firebase/internal/ErrorHandlingHttpClientTest.java
@@ -23,13 +23,14 @@ import static org.junit.Assert.assertSame;
 import static org.junit.Assert.fail;
 
 import com.google.api.client.googleapis.util.Utils;
+import com.google.api.client.http.ByteArrayContent;
+import com.google.api.client.http.HttpContent;
 import com.google.api.client.http.HttpMethods;
 import com.google.api.client.http.HttpRequest;
 import com.google.api.client.http.HttpRequestFactory;
 import com.google.api.client.http.HttpResponseException;
 import com.google.api.client.http.HttpStatusCodes;
 import com.google.api.client.http.LowLevelHttpRequest;
-import com.google.api.client.http.json.JsonHttpContent;
 import com.google.api.client.json.JsonFactory;
 import com.google.api.client.testing.http.MockHttpTransport;
 import com.google.api.client.testing.http.MockLowLevelHttpResponse;
@@ -109,15 +110,15 @@ public class ErrorHandlingHttpClientTest {
   public void testSuccessfulRequestWithHeadersAndBody() throws FirebaseException, IOException {
     MockLowLevelHttpResponse response = new MockLowLevelHttpResponse()
         .setContent("{\"foo\": \"bar\"}");
-    ErrorHandlingHttpClient<FirebaseException> client = createHttpClient(response);
-
-    HttpRequestInfo request = HttpRequestInfo.buildPostRequest(
-        "https://firebase.google.com",
-        new JsonHttpContent(DEFAULT_JSON_FACTORY, ImmutableMap.of("key", "value")));
     TestResponseInterceptor interceptor = new TestResponseInterceptor();
+    ErrorHandlingHttpClient<FirebaseException> client = createHttpClient(response)
+        .setInterceptor(interceptor);
+
+    HttpRequestInfo request = HttpRequestInfo.buildJsonPostRequest(
+        "https://firebase.google.com", ImmutableMap.of("key", "value"));
+
     request.addHeader("h1", "v1")
-        .addAllHeaders(ImmutableMap.of("h2", "v2", "h3", "v3"))
-        .setResponseInterceptor(interceptor);
+        .addAllHeaders(ImmutableMap.of("h2", "v2", "h3", "v3"));
     GenericData body = client.sendAndParse(request, GenericData.class);
 
     assertEquals(1, body.size());
@@ -133,20 +134,43 @@ public class ErrorHandlingHttpClientTest {
     assertEquals("{\"key\":\"value\"}", out.toString());
   }
 
+  @Test
+  public void testSuccessfulRequestWithNonJsonBody() throws FirebaseException, IOException {
+    MockLowLevelHttpResponse response = new MockLowLevelHttpResponse()
+        .setContent("{\"foo\": \"bar\"}");
+    TestResponseInterceptor interceptor = new TestResponseInterceptor();
+    ErrorHandlingHttpClient<FirebaseException> client = createHttpClient(response)
+        .setInterceptor(interceptor);
+    HttpContent content = new ByteArrayContent("text/plain", "Test".getBytes());
+
+    HttpRequestInfo request = HttpRequestInfo.buildRequest(
+        HttpMethods.POST, "https://firebase.google.com", content);
+
+    GenericData body = client.sendAndParse(request, GenericData.class);
+
+    assertEquals(1, body.size());
+    assertEquals("bar", body.get("foo"));
+    HttpRequest last = interceptor.getLastRequest();
+    assertEquals(HttpMethods.POST, last.getRequestMethod());
+    assertEquals("text/plain", last.getContent().getType());
+
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+    last.getContent().writeTo(out);
+    assertEquals("Test", out.toString());
+  }
+
   @Test
   public void testUnsupportedMethod() throws FirebaseException, IOException {
     MockHttpTransport transport = new MockHttpTransport.Builder()
         .setLowLevelHttpResponse(new MockLowLevelHttpResponse().setContent("{}"))
         .setSupportedMethods(ImmutableSet.of(HttpMethods.GET, HttpMethods.POST))
         .build();
+    TestResponseInterceptor interceptor = new TestResponseInterceptor();
     ErrorHandlingHttpClient<FirebaseException> client = new ErrorHandlingHttpClient<>(
         transport.createRequestFactory(), DEFAULT_JSON_FACTORY, new TestHttpErrorHandler());
-    HttpRequestInfo patchRequest = HttpRequestInfo.buildRequest(
-        HttpMethods.PATCH,
-        "https://firebase.google.com",
-        new JsonHttpContent(DEFAULT_JSON_FACTORY, ImmutableMap.of("key", "value")));
-    TestResponseInterceptor interceptor = new TestResponseInterceptor();
-    patchRequest.setResponseInterceptor(interceptor);
+    client.setInterceptor(interceptor);
+    HttpRequestInfo patchRequest = HttpRequestInfo.buildJsonRequest(
+        HttpMethods.PATCH, "https://firebase.google.com", ImmutableMap.of("key", "value"));
 
     client.sendAndParse(patchRequest, GenericData.class);
 
diff --git a/src/test/java/com/google/firebase/messaging/InstanceIdClientImplTest.java b/src/test/java/com/google/firebase/messaging/InstanceIdClientImplTest.java
index bac2a51..70bf9a2 100644
--- a/src/test/java/com/google/firebase/messaging/InstanceIdClientImplTest.java
+++ b/src/test/java/com/google/firebase/messaging/InstanceIdClientImplTest.java
@@ -378,7 +378,6 @@ public class InstanceIdClientImplTest {
     try {
       InstanceIdClientImpl client = InstanceIdClientImpl.fromApp(app);
 
-      assertSame(options.getJsonFactory(), client.getJsonFactory());
       client.subscribeToTopic("test-topic", ImmutableList.of("id1", "id2"));
       fail("No error thrown for error response");
     } catch (FirebaseMessagingException e) {