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 && !HttpMethod.permitsRequestBody(method)) {
throw new IllegalArgumentException("method " + method + " must not have a request body.");
}
if (body == null && 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.