API SDK: Retrofit

We have a Java SDK to do easier use of Fexco api that you can find here, and here you have API documentation.

The API has a lot of characteristics but I want to mention quickly how we have adapted Retrofit to Event Batch. Basically we need a body with the http verb GET, but this is not allow in REST API. Let me show what we did.

Retrofit

Before we begin I want to say something about Retrofit, you can see the project in the web site, this client uses OkHttp to do the http request, and it supports http2. Another interesting thing is that you can use a lot of converters or a custom converter.

  • Gson: com.squareup.retrofit2:converter-gson
  • Jackson: com.squareup.retrofit2:converter-jackson
  • Moshi: com.squareup.retrofit2:converter-moshi
  • Protobuf: com.squareup.retrofit2:converter-protobuf
  • Wire: com.squareup.retrofit2:converter-wire
  • Simple XML: com.squareup.retrofit2:converter-simplexml
  • Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars

A converter to ProtoBuf, I think we will write a new post about this.

The most important feature of Retrofit is how you use it. You only have to define an interface for your services. Here it is an example from their website:

public interface GitHubService {
  @GET("users/{user}/repos")
  Call<List<Repo>> listRepos(@Path("user") String user);
}

Only with this code you have a very good client to use with your website.

RxJava2

Another add-on was using reactivex for the SDK client. It is very easy to include Observables. From the previous example

public interface GitHubService {
  @GET("users/{user}/repos")
  Observable<List<Repo>> listRepos(@Path("user") String user);
}

And you can do it by just adding the adapter dependency

compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'

The Issue

You can define your custom verb with HTTP annotation and you can set hasBody like true.

    @HTTP(method = "GET", path = "{event}", hasBody = true)
    Observable<EventBatch> getEvent(@Header(Constants.APIKEY) String apiKey, @Path("event") String type,
            @Body EventBatch eb);

But when you use this method to do a request you get an exception

Caused by: java.lang.IllegalArgumentException: method GET must not have a request body.
    at okhttp3.Request$Builder.method(Request.java:236)
    at retrofit2.RequestBuilder.build(RequestBuilder.java:228)

The Request class doesn’t allow to build a Request with GET and body. You can see the code in the Request Builder

    public Builder method(String method, @Nullable RequestBody body) {
      if (method == null) throw new NullPointerException("method == null");
      if (method.length() == 0) throw new IllegalArgumentException("method.length() == 0");
      if (body != null &amp;&amp; !HttpMethod.permitsRequestBody(method)) {
        throw new IllegalArgumentException("method " + method + " must not have a request body.");
      }
      if (body == null &amp;&amp; HttpMethod.requiresRequestBody(method)) {
        throw new IllegalArgumentException("method " + method + " must have a request body.");
      }
      this.method = method;
      this.body = body;
      return this;
    }

The options we have:

  • Changing all the GETs to POST.
  • Custom verb in server side
  • Workaround with retrofit and OKHttp to do a GET with body

The Workaround

Under this circumstances the only solution is forcing a GET with body. It is made using a custom HTTP and using a interceptor to change the verb to GET before doing the request.

Before we need to declare the custom HTTP with the name GET-EVENT and hasBody as true

    @HTTP(method = "GET-EVENT", path = "{event}", hasBody = true)
    Observable<EventBatch> getEvent(@Header(Constants.APIKEY) String apiKey, @Path("event") String type,
            @Body EventBatch eb);

The second part is to add the interceptor to the client that do the change from GET-EVENT to GET. The only way was using java reflection.

    OkHttpClient client=new OkHttpClient.Builder().addInterceptor(new Interceptor() {
        @Override
        public okhttp3.Response intercept(Chain chain) throws IOException {
            Request originalRequest = chain.request();
            if ("GET-EVENT".equals(originalRequest.method())) {
                try {
                    Field field = originalRequest.getClass().getDeclaredField("method");
                    field.setAccessible(true);
                    field.set(originalRequest, "GET");
                } catch (NoSuchFieldException | SecurityException | IllegalArgumentException
                        | IllegalAccessException e) {
                    System.err.println("error transform get");
                }

            }
            return chain.proceed(originalRequest);
        }
        }).build();
    }

With reflection we access to Request to change method the from GET-EVENT to GET

Retrofit retrofit = new Retrofit.Builder().baseUrl(baseUrl)
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create()).client(client).build();
//Ready prepare to use
OurInterface service=retrofit.create(OurInterface.calls);

Conclusion

We like retrofit, although it doesn’t allow body in GET, and we don’t want to stop using it. Then we decide to do a hack to continue using it and get all benefits of this library.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.