Retrofitを試す
Retrofitを試してみたメモです。
Javaのhttp clientはずっとapache commonsのHttpClientを使用していたのですが、
Retrofitが便利らしいので試してみました。
下記のバージョンで試してみます。
- java8
- retrofit 2.4.0
- okhttp 3.10.0
- jackson-databind 2.9.5
Retrofit
Retrofitはインターフェースにアノテーションでリクエストを定義して使用するJava, Android用のhttp clientです。
apache commons HttpClientよりもリクエスト/レスポンスの処理が感覚的で簡潔に書けます。
http clientの実装としてokhttpを使用してます。
Dependency
gradleプロジェクトで試してみます。
dependencyには下記を追加しました。
build.gradle
dependencies { compile 'com.squareup.retrofit2:retrofit:2.4.0' compile 'com.squareup.retrofit2:converter-jackson:2.4.0' compile 'com.fasterxml.jackson.core:jackson-databind:2.9.5' compile 'com.squareup.okhttp3:logging-interceptor:3.10.0' compileOnly 'org.projectlombok:lombok:1.16.22' testCompile 'junit:junit:4.12' testCompile 'org.assertj:assertj-core:3.10.0' }
Retrofitではconverterをプラグインとして適用できます。
今回はjson用にconverter-jacksonを追加しました。
(Gsonなど他のconverterもあります)
https://github.com/square/retrofit/wiki/Converters
テスト用の簡易サーバをGoで立ち上げて試してみます。
API Interface
RetrofitではAPIを定義したインターフェースを作成します。
MyServiceというインターフェースを定義します。
MyService.java
public interface MyService { @GET("dog") Call<Dog> getDog(); @GET("dogs") Call<List<Dog>> getDogList(); @GET("dog?aaa=bbb") Call<Dog> getDogWithParam(); @GET("dog") Call<Dog> getDogWithParam(@Query("aaa") String value); @GET("dog") Call<Dog> getDogWithParams(@QueryMap Map<String, String> params); @Headers({ "User-Agent: my-service:0.1", "my-header: zzz" }) @GET("dog") Call<Dog> getDogWithHeaders(); @GET("dog") Call<Dog> getDogWithDynamicHeader(@Header("User-Agent") String userAgent); @GET("dogs/{id}") Call<Dog> getDogById(@Path("id") int id); @POST("dogs") Call<Void> create(@Body Dog dog); @FormUrlEncoded @POST("dogs/form") Call<Void> form(@Field("id") int id, @Field("name") String name); @PUT("dogs/{id}") Call<Void> update(@Path("id") int id, @Body Dog dog); @DELETE("dogs/{id}") Call<Void> delete(@Path("id") int id); }
インターフェースで使用するmodelとしてDogクラスを定義します。
@AllArgsConstructor @NoArgsConstructor @Data public class Dog { private int id; private String name; }
Retrofit Client
Retrofit.Builderクラスを使用してRetrofitインスタンスを生成します。
baseUrl()でサーバのURLを指定し、JacksonConverterFactoryを指定してRetrofitインスタンスを生成します。
create()でAPIを定義したインターフェースを指定します。
Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://localhost:8080/") .addConverterFactory(JacksonConverterFactory.create()) .build(); MyService myService = retrofit.create(MyService.class);
Retrofitではhttp clientの実装としてokhttpを使用するのですが、
自分でokhttpのインスタンスを指定することが出来ます。
logging interceptorを追加したokhttpを設定してみます。
(okhttpのinterceptorについてはこちらで記事を書いてます)
下記をdependencyに追加。
build.gradle
compile 'com.squareup.okhttp3:logging-interceptor:3.10.0'
Retrofitのインスタンス生成を下記のように修正します。
OkHttpClient okHttpClient = new OkHttpClient.Builder() .addInterceptor(new HttpLoggingInterceptor().setLevel(Level.BODY)) .build(); Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://localhost:8080/") .client(okHttpClient) .addConverterFactory(JacksonConverterFactory.create()) .build(); myService = retrofit.create(MyService.class);
GET
単純にGETしてみます。
@GETアノテーションでエンドポイントを指定します。
CallにレスポンスのDogクラスを指定します。
MyService.java
@GET("dog") Call<Dog> getDog();
テスト。
Call.execute()でhttp requestが同期実行されます。
Response.body()でDogへデシリアライズされて取得する事が出来ます。
MainTest.java
@Test public void getDogTest() throws Exception { Response<Dog> response = myService.getDog().execute(); if (!response.isSuccessful()) { String url = response.raw().request().url().toString(); System.out.println("error!!" + response.errorBody() + " url=" + url); } Dog dog = response.body(); System.out.println("dog: " + dog); }
クライントログ
dog: Dog(id=1, name=pochi)
サーバ側ログ
[method] GET [header] Connection: Keep-Alive [header] Accept-Encoding: gzip [header] User-Agent: okhttp/3.10.0 [path] /dog
GET(List)
GETのレスポンスをListで取得してみます
Call<List<Dog>>でListとして指定するだけです。
MyService.java
@GET("dogs") Call<List<Dog>> getDogList();
MainTest.java
@Test public void getDogList() throws Exception { Response<List<Dog>> response = myService.getDogList().execute(); if (!response.isSuccessful()) { String url = response.raw().request().url().toString(); System.out.println("error!!" + response.errorBody() + " url=" + url); } List<Dog> dogs = response.body(); System.out.println("dogs: " + dogs); }
クライントログ
dogs: [Dog(id=1, name=pochi), Dog(id=2, name=john)]
サーバ側ログ
[method] GET [header] Connection: Keep-Alive [header] Accept-Encoding: gzip [header] User-Agent: okhttp/3.10.0 [path] /dogs
GET(request parameter)
固定のrequest parameterを指定する場合はURLとしてそのまま記述出来ます。
MyService.java
@GET("dog?aaa=bbb") Call<Dog> getDogWithParam();
MainTest.java
@Test public void getDogWithParamTest() throws Exception { Response<Dog> response = myService.getDogWithParam().execute(); if (!response.isSuccessful()) { String url = response.raw().request().url().toString(); System.out.println("error!!" + response.errorBody() + " url=" + url); } Dog dog = response.body(); System.out.println("dog: " + dog); }
クライントログ
dog: Dog(id=1, name=pochi)
サーバ側ログ
[method] GET [header] Accept-Encoding: gzip [header] User-Agent: okhttp/3.10.0 [header] Connection: Keep-Alive [path] /dog [param] aaa: bbb
GET(@Query)
@Queryでrequest parameterのkey, valueを指定できます。
MyService.java
@GET("dog") Call<Dog> getDogWithParam(@Query("aaa") String value);
MainTest.java
@Test public void getDogWithParamQueryTest() throws Exception { Response<Dog> response = myService.getDogWithParam("ccc").execute(); if (!response.isSuccessful()) { String url = response.raw().request().url().toString(); System.out.println("error!!" + response.errorBody() + " url=" + url); } Dog dog = response.body(); System.out.println("dog: " + dog); }
クライントログ
dog: Dog(id=1, name=pochi)
サーバ側ログ
[method] GET [header] Connection: Keep-Alive [header] Accept-Encoding: gzip [header] User-Agent: okhttp/3.10.0 [path] /dog [param] aaa: ccc
GET(@QueryMap)
@QueryMapで複数のrequest parameterをまとめて指定できます。
Mapで指定します。
MyService.java
@GET("dog") Call<Dog> getDogWithParams(@QueryMap Map<String, String> params);
MainTest.java
@Test public void getDogWithParamsTest() throws Exception { Map<String, String> params = new HashMap<>(); params.put("aaa", "bbb"); params.put("size", "100"); Response<Dog> response = myService.getDogWithParams(params).execute(); if (!response.isSuccessful()) { String url = response.raw().request().url().toString(); System.out.println("error!!" + response.errorBody() + " url=" + url); } Dog dog = response.body(); System.out.println("dog: " + dog); }
クライントログ
dog: Dog(id=1, name=pochi)
サーバ側ログ
[method] GET [header] Connection: Keep-Alive [header] Accept-Encoding: gzip [header] User-Agent: okhttp/3.10.0 [path] /dog [param] aaa: bbb [param] size: 100
GET(@Headers)
@Headersで固定http headerを指定できます。
okhttpのinterceptorでも固定http headerを指定することも出来ます。
(例:OkHttp3を試す - abcdefg.....)
MyService.java
@Headers({ "User-Agent: my-service:0.1", "my-header: zzz" }) @GET("dog") Call<Dog> getDogWithHeaders();
MainTest.java
@Test public void getDogWithHeadersTest() throws Exception { Response<Dog> response = myService.getDogWithHeaders().execute(); if (!response.isSuccessful()) { String url = response.raw().request().url().toString(); System.out.println("error!!" + response.errorBody() + " url=" + url); } Dog dog = response.body(); System.out.println("dog: " + dog); }
クライントログ
dog: Dog(id=1, name=pochi)
サーバ側ログ
[method] GET [header] User-Agent: my-service:0.1 [header] My-Header: zzz [header] Connection: Keep-Alive [header] Accept-Encoding: gzip [path] /dog
GET(@Header)
@Headerでhttp headerを指定出来ます。
MyService.java
@GET("dog") Call<Dog> getDogWithDynamicHeader(@Header("User-Agent") String userAgent);
MainTest.java
@Test public void getDogWithDynamicHeaderTest() throws Exception { Response<Dog> response = myService.getDogWithDynamicHeader("my-header:0.2").execute(); if (!response.isSuccessful()) { String url = response.raw().request().url().toString(); System.out.println("error!!" + response.errorBody() + " url=" + url); } Dog dog = response.body(); System.out.println("dog: " + dog); }
クライントログ
dog: Dog(id=1, name=pochi)
サーバ側ログ
[method] GET [header] User-Agent: my-header:0.2 [header] Connection: Keep-Alive [header] Accept-Encoding: gzip [path] /dog
GET(@Path)
@Pathでrequest pathを指定出来ます。
@GETのエンドポイントのpathに{name}の形式で指定し、@Path("name")で値を指定します。
MyService.java
@GET("dogs/{id}") Call<Dog> getDogById(@Path("id") int id);
MainTest.java
@Test public void getDogByIdTest() throws Exception { Response<Dog> response = myService.getDogById(1).execute(); if (!response.isSuccessful()) { String url = response.raw().request().url().toString(); System.out.println("error!!" + response.errorBody() + " url=" + url); } Dog dog = response.body(); System.out.println("dog: " + dog); }
クライントログ
dog: Dog(id=1, name=pochi)
サーバ側ログ
[method] GET [header] Connection: Keep-Alive [header] Accept-Encoding: gzip [header] User-Agent: okhttp/3.10.0 [path] /dogs/1
POST(json)
jsonでPOSTしてみます。
@POST()でエンドポイントを指定して、@Bodyでrequest bodyを指定します。
レスポンスは特に取得しないのでVoidを指定しています。
MyService.java
@POST("dogs") Call<Void> create(@Body Dog dog);
MainTest.java
@Test public void createTest() throws Exception { Dog dog = new Dog(100, "pudding"); Response<Void> response = myService.create(dog).execute(); if (!response.isSuccessful()) { String url = response.raw().request().url().toString(); System.out.println("error!!" + response.errorBody() + " url=" + url); } }
サーバ側ログ
[method] POST [header] Content-Type: application/json; charset=UTF-8 [header] Content-Length: 27 [header] Connection: Keep-Alive [header] Accept-Encoding: gzip [header] User-Agent: okhttp/3.10.0 [path] /dogs [request body row] {"id":100,"name":"pudding"} [request body decoded] {ID:100 Name:pudding}
POST(form)
formでPOSTしてみます。
form形式で送信するために@FormUrlEncodedを指定し、@Fieldでformのkeyとvalueを指定します。
MyService.java
@FormUrlEncoded @POST("dogs/form") Call<Void> form(@Field("id") int id, @Field("name") String name);
MainTest.java
@Test public void formTest() throws Exception { Response<Void> response = myService.form(200, "太郎").execute(); if (!response.isSuccessful()) { String url = response.raw().request().url().toString(); System.out.println("error!!" + response.errorBody() + " url=" + url); } }
サーバ側ログ
[method] POST [header] Content-Type: application/x-www-form-urlencoded [header] Content-Length: 30 [header] Connection: Keep-Alive [header] Accept-Encoding: gzip [header] User-Agent: okhttp/3.10.0 [path] /dogs/form [request body row] id=200&name=%E5%A4%AA%E9%83%8E [request body decoded] id=200&name=太郎
PUT
PUTは@PUTでエンドポイントを指定します。
MyService.java
@PUT("dogs/{id}") Call<Void> update(@Path("id") int id, @Body Dog dog);
MainTest.java
@Test public void updateTest() throws Exception { Dog dog = new Dog(300, "santa"); Response<Void> response = myService.update(2, dog).execute(); if (!response.isSuccessful()) { String url = response.raw().request().url().toString(); System.out.println("error!!" + response.errorBody() + " url=" + url); } }
サーバ側ログ
[method] PUT [header] Content-Length: 25 [header] Connection: Keep-Alive [header] Accept-Encoding: gzip [header] User-Agent: okhttp/3.10.0 [header] Content-Type: application/json; charset=UTF-8 [path] /dogs/2 [request body row] {"id":300,"name":"santa"} [request body decoded] {ID:300 Name:santa}
DELETE
DELETEは@DELETEでエンドポイントを指定します。
MyService.java
@DELETE("dogs/{id}") Call<Void> delete(@Path("id") int id);
MainTest.java
@Test public void deleteTest() throws Exception { Response<Void> response = myService.delete(3).execute(); if (!response.isSuccessful()) { String url = response.raw().request().url().toString(); System.out.println("error!!" + response.errorBody() + " url=" + url); } }
サーバ側ログ
[method] DELETE [header] Accept-Encoding: gzip [header] User-Agent: okhttp/3.10.0 [header] Connection: Keep-Alive [path] /dogs/3
ソースコードは下記にあげました。
github.com
おわり。