spring-boothttpurlconnectionresttemplateetagfirebase-remote-config

How to get "etag" in the implementation of Firebase Remote Config REST Api using the RestTemplate of springframework/spring-boot?


I am implementing firebase-remote-config rest api in a spring-boot server for an Android App for learning purpose. My spring boot version is 2.1.7.RELEASE. I am trying to get the etag value, so that I can update the key & values from the server side, without using firebase console. I was able to get the etag via httpconnection as per this link. But whenever I used RestTemplate I couldn't get the etag, all I got was null.

This is the build.gradle file

buildscript {
    ext {
        springBootVersion = '2.1.7.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = 'com.practice.shaikhalvee'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
    mavenCentral()
}

dependencies {
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-web'
    compile group: 'com.google.apis', name: 'google-api-services-firebaseremoteconfig', version: 'v1-rev14-1.23.0'
    compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.8.5'
    compile group: 'com.google.code.gson', name: 'gson', version: '2.8.6'
    testCompile group: 'junit', name: 'junit', version: '4.12'
}

And this is the Controller That I created to experiment with firebase-remote-config.

@RestController
@RequestMapping(value = "/api/remote-config")
public class FirebaseRestApiEndpoint {

    private final FirebaseRemoteConfigService firebaseRemoteConfigService;

    public FirebaseRestApiEndpoint(FirebaseRemoteConfigService firebaseRemoteConfigService) {
        this.firebaseRemoteConfigService = firebaseRemoteConfigService;
    }

    @GetMapping(value = "/get-template/{call-type}")
    public void getTemplate(@PathVariable("call-type") String callType) {
        switch (callType) {
            case "http":
                firebaseRemoteConfigService.getMetadataTemplateWithHttpCall();
                break;
            case "rest":
                firebaseRemoteConfigService.getMetadataTemplateWithRestCall();
                break;
            default:
                throw new RuntimeException("call-type is unknown. Should be rest or http");
        }
    }
}

This is the accessToken() process I used

// Constant File holds the url and other static final string values.
public static String getAccessToken() throws IOException {
    GoogleCredential googleCredential = GoogleCredential
            .fromStream(new FileInputStream(Constants.CERTIFICATE_FILE))
            .createScoped(Arrays.asList(Constants.SCOPES));
    googleCredential.refreshToken();
    return googleCredential.getAccessToken();
}

And for the service layer I am only including the methods that are called here for simplification.

This is the method that uses HttpUrlConnection.

public void getMetadataTemplateWithHttpCall() {
    try {
        URL url = new URL(Constants.BASE_URL + Constants.REMOTE_CONFIG_ENDPOINT);
        HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
        httpURLConnection.setRequestProperty("Authorization", "Bearer " + CommonConfig.getAccessToken());
        httpURLConnection.setRequestProperty("Content-Type", "application/json; UTF-8");
        httpURLConnection.setRequestMethod("GET");
        httpURLConnection.setRequestProperty("Accept-Encoding", "gzip");

        int code = httpURLConnection.getResponseCode();
        if (code == 200) {
            InputStream inputStream = new GZIPInputStream(httpURLConnection.getInputStream());
            JsonElement jsonElement = JsonParser.parseReader(new InputStreamReader(inputStream));

            Gson gson = new GsonBuilder()
                    .setPrettyPrinting()
                    .disableHtmlEscaping()
                    .enableComplexMapKeySerialization()
                    .serializeSpecialFloatingPointValues()
                    .create();
            String jsonStr = gson.toJson(jsonElement);
            RemoteConfig remoteConfig = gson.fromJson(jsonStr, RemoteConfig.class);
            String etag = httpURLConnection.getHeaderField("ETag");
            System.out.println(etag);
        }
    } catch (IOException e) {
        System.err.println(e.getMessage());
    }
}

Here proper etag is printed as per the documentation of Firebase-Remote-Config REST Api.

But if I try to implement the same http get connection with RestTemplate, I can't get the etag.

Now this is the method that utilizes RestTemplate. I gave pretty similar request property for this rest call. But still I can't get the etag

public void getMetadataTemplateWithRestCall() {
    ObjectMapper objectMapper = new ObjectMapper();
    HttpHeaders httpHeaders = new HttpHeaders();
    String url = "";
    try {
        httpHeaders.add(HttpHeaders.CONTENT_ENCODING, "gzip");
        httpHeaders.setBearerAuth(CommonConfig.getAccessToken());
        httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8);

        url = Constants.BASE_URL + Constants.REMOTE_CONFIG_ENDPOINT;

        HttpEntity<?> requestEntity = new HttpEntity(httpHeaders);
        ResponseEntity<RemoteConfig> baseResponse = restTemplate.exchange(url, HttpMethod.GET, requestEntity, RemoteConfig.class);

        String etag = baseResponse.getHeaders().getETag();
        System.out.println(etag);
    } catch (IOException e) {
        e.getMessage();
    }
}

As you have guessed it, this etag value is null. I am trying to Firebase Remote Config REST call, and yet if I can't properly process REST call, maybe I am missing something here.

For your convenience I am providing their header as etag resides in the header. Fun fact, in http call, I get 13 headers. And in rest call I get 11. And you guessed it, etag is missing

This the header for http connection call.

Transfer-Encoding=[chunked], 
null=[HTTP/1.1 200 OK],
Alt-Svc=[quic=":443"; ma=2592000; v="46,43",h3-Q050=":443"; ma=2592000,h3-Q049=":443"; ma=2592000,h3-Q048=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000], 
Server=[ESF], 
X-Content-Type-Options=[nosniff], 
Date=[Mon, 11 Nov 2019 04:59:04 GMT], 
X-Frame-Options=[SAMEORIGIN], 
Cache-Control=[private], 
ETag=[etag-111111311162-47],
Content-Encoding=[gzip],
Vary=[Referer, X-Origin, Origin], 
X-XSS-Protection=[0], 
Content-Type=[application/json; charset=UTF-8]

And this is the header for Rest Call

Content-Type:"application/json; charset=UTF-8", 
Vary:"X-Origin", "Referer", "Origin,Accept-Encoding", 
Date:"Mon, 11 Nov 2019 05:15:10 GMT", 
Server:"ESF", 
Cache-Control:"private", 
X-XSS-Protection:"0", 
X-Frame-Options:"SAMEORIGIN", 
X-Content-Type-Options:"nosniff", 
Alt-Svc:"quic=":443"; ma=2592000; v="46,43",h3-Q050=":443"; ma=2592000,h3-Q049=":443"; ma=2592000,h3-Q048=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000", 
Accept-Ranges:"none", 
Transfer-Encoding:"chunked"

Can anyone kindly help me. My goal is to get the etag value using rest call, more precisely using The RestTemplate of springframework, as it's a REST API.

Thank you.


Solution

  • After a long research & debugging I was able to find a workaround.

    In the documentation we were told to add header Accept-Encoding: gzip. That's why I used httpHeaders.add(HttpHeaders.CONTENT_ENCODING, "gzip"); in the getMetadataTemplateWithHttpCall() method. It doesn't work. I even added header like this, httpHeaders.add("Accept-Encoding", "gzip");. It throws exception, RestTemplate doesn't accept gzip as Accept-Encoding. What works is that if you intercept the rest call with a customized RestTemplate interceptor before the call and compress the request using gzip with the interceptor. That means in case of RestTemplate we need to compress the request before http call and then send it. Only then, in the ResponseEntity I get the "etag".

    I have created a @Bean for specified RestTemplate, which I have provided below. If you want to use RestTemplate of springframework, then you must add this type of interceptor to utilize Firebase Remote Config REST API.

    This is the @Bean

    @Configuration
    public class RestTemplateConfig {
        @Bean(value = "gzippedRestTemplate")
        public RestTemplate restTemplate() {
            HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(
                    HttpClientBuilder.create().build());
            clientHttpRequestFactory.setConnectTimeout(2000);
            clientHttpRequestFactory.setReadTimeout(10000);
            RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory);
            List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
            if (interceptors == null) {
                interceptors = new ArrayList<>();
                restTemplate.setInterceptors(interceptors);
            }
            interceptors.add(new GzipAcceptHeaderRequestInterceptor());
            return restTemplate;
        }
        public static class GzipAcceptHeaderRequestInterceptor implements ClientHttpRequestInterceptor {
            @Override
            public ClientHttpResponse intercept(HttpRequest request, @Nullable byte[] body, ClientHttpRequestExecution execution) throws IOException {
                request.getHeaders().set(HttpHeaders.ACCEPT_ENCODING, "gzip");
                return execution.execute(request, body);
            }
        }
    }
    

    Now you can get the "etag" value from the GET call using springframework's RestTemplate, publish your work.