Apollo-android: Retrieve Response Http Headers and avoid multiple instance creation

Created on 23 Jun 2020  路  6Comments  路  Source: apollographql/apollo-android

Hi Everyone!
I have a graphQL API to query that requires HTTP request headers for each request and that returns the relative HTTP response headers
I found the response to half of the problem that I am trying to solve from https://github.com/apollographql/apollo-android/issues/2030#issuecomment-647873074
It looks like I can use the method provided by https://github.com/apollographql/apollo-android/blob/master/apollo-runtime/src/main/java/com/apollographql/apollo/internal/RealApolloCall.java#L178-L183 to add the request header for each request and now I was wondering how I can map the HTTP response headers to the POJO class that implements an Operation.Data. https://github.com/apollographql/apollo-android/blob/master/apollo-api/src/commonMain/kotlin/com/apollographql/apollo/api/Operation.kt#L40
Maybe I am missing something and it is already there in some bean property?

Question

All 6 comments

Hi! You can get the HTTP response headers from response.executionContext:

    val response: Response<YourQuery.Data> = //... get a response using ApolloClient
    val httpContext = response.executionContext[OkHttpExecutionContext.KEY]
    httpContext?.response?.headers?.forEach {
        println("${it.first}=${it.second}")
    }

This is a fairly new API and it will most likely require a recent version. Also, it's not documented yet but it should certainly be added somewhere. Let me know if it worked and I'll add it.

Screen Shot 2020-06-23 at 11 36 44 AM
I am using apollo-api-jvm:2.2.1 Is it only in Kotlin such support?

public <T extends Query, V extends Operation.Data> Optional<V> execute(Query operation, Locale locale) {
    ApolloCall<V> apolloCall = ApolloClient.builder()
        .serverUrl(String.format(myProperties.getServerUrl(), locale.getCountry(), locale.toLanguageTag()))
        .okHttpClient(okHttpClient)
        .build()).query(operation).requestHeaders(
        RequestHeaders.builder()
            .addHeader("Authorization", "blablabla")
            .build());
/*  Code to understand how to get the response headers  
  ApolloClient apolloClient = ApolloClient.builder().build();
    Response<Operation.Data> r = apolloClient.query(operation);
    r.getExecutionContext().plus()
*/
    Object objectResponse = Rx2Apollo.from(apolloCall)
        .onErrorReturn(e -> {
          Builder<V> builder = Response.builder(operation);
          builder.errors(Lists.newArrayList(new Error(e.getMessage(), Collections.EMPTY_LIST, Collections.EMPTY_MAP)));
          logger.error("It was not possible to process the request for {}", e.getMessage());
          return builder.build();
        })
        .blockingFirst();
    if (objectResponse instanceof Response) {
      Response response = (Response) objectResponse;
      if (response.hasErrors()) {
        List<String> errors = (List<String>) response.getErrors().parallelStream()
                                                     .filter(i-> i instanceof Error)
                                                     .map(i-> ((Error) i).getMessage())
                                                     .collect(Collectors.toList());
        return Optional.of((V) new OperationDataError(errors));
      } else {
        return Optional.of((V) response.getData());
      }
    }
    logger.error("Not request processed for {}", operation.queryDocument());
    return Optional.empty();
  }

The interceptors are not thread-safe like the headers. If I add an interceptor to the apolloClient such interceptor is going to kick-in for all the requests dispatched by such apolloClient.
It would be also nice to embed the errors inside the Operation so I can avoid writing such horrible code :)

      final OkHttpExecutionContext context = response.getExecutionContext().get(OkHttpExecutionContext.KEY);
      Map<String,List<String>> responseHeaders = Optional.ofNullable(context)
            .map(OkHttpExecutionContext::getResponse)
            .map(okhttp3.Response::headers)
            .map(Headers::toMultimap)
            .orElse(Collections.emptyMap());

This is the solution to retrieve the headers. I upgraded the apollo-runtime to 2.2.1.
It looks like the ApolloClient hides some capabilities of the OkHttpClient and it looks to don't be trivial
avoid keeping a map of beans without dealing with OKHttpInterceptors when you need to change only requestParams in the URL

  ApolloCall<V> apolloCall = ApolloClient.builder()
        .serverUrl(String.format(myProperties.getServerUrl(), locale.getCountry(), locale.toLanguageTag()))
        .okHttpClient(okHttpClient)
        .build()).query(operation).requestHeaders(
        RequestHeaders.builder()
            .addHeader("Authorization", "blablabla")
            .build());

Hi!

Glad to hear that retrieving the HTTP response headers is working 馃憤

Regarding OKHttpInterceptors, I'm not sure to understand what you are using them for. Can you elaborate a bit more? To change the headers on an ApolloCall, you can use ApolloCall.requestHeaders() without a OkHttpInterceptor

Yes, the headers problem is solved.

ApolloCall<V> apolloCall = ApolloClient.builder()
        .serverUrl(String.format(myProperties.getServerUrl(), locale.getCountry(), locale.toLanguageTag()))
        .okHttpClient(okHttpClient)
        .build()).query(operation);

I am referring to this snippet as you can see I would need to change the serverUrl for each request to add the java.util.Locale depending on the user request.
myProperties.getServerUrl is something like https://mygreatgraphqlserver/blablabla?country=%s&language=%s and I wanted to avoid to generate an ApolloClient Bean for each request, or the ApolloClient bean is designed by taking in consideration that you can have a new one for each request?

Ah yes, there's no way to change the serverUrl per-request. What you can do is create a "generic" ApolloClient and use ApolloClient.newBuilder() to change the serverUrl without instantiating a new OkHttpClient every time:

// Initial generic client
ApolloClient genericClient = ApolloClient.builder()
        .serverUrl("dummyUrl")
        .okHttpClient(okHttpClient)
        .build()

// When you want to change the serverUrl:
ApolloClient apolloClient = genericClient.newBuilder()
        .serverUrl("https://your.real/url")
        .build()

This way, the underlying OkHttpClient instance and underlying threadpool will be reused.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jamesweb1 picture jamesweb1  路  3Comments

rnitame picture rnitame  路  3Comments

gmrandom picture gmrandom  路  4Comments

AOrobator picture AOrobator  路  3Comments

TonnyL picture TonnyL  路  3Comments