/* * Copyright (c) 2016 LINE Corporation. All rights reserved. * LINE Corporation PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. */ package com.linecorp.armeria.client.http.retrofit2; import static com.linecorp.armeria.client.endpoint.EndpointSelectionStrategy.ROUND_ROBIN; import static com.linecorp.armeria.common.SerializationFormat.NONE; import static com.linecorp.armeria.common.util.Functions.voidFunction; import static org.assertj.core.api.Assertions.assertThat; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.junit.Before; import org.junit.ClassRule; import org.junit.Test; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.linecorp.armeria.client.ClientFactory; import com.linecorp.armeria.client.Clients; import com.linecorp.armeria.client.Endpoint; import com.linecorp.armeria.client.endpoint.EndpointGroupRegistry; import com.linecorp.armeria.client.endpoint.StaticEndpointGroup; import com.linecorp.armeria.client.http.HttpClient; import com.linecorp.armeria.common.MediaType; import com.linecorp.armeria.common.http.HttpRequest; import com.linecorp.armeria.common.http.HttpResponseWriter; import com.linecorp.armeria.common.http.HttpStatus; import com.linecorp.armeria.server.ServerBuilder; import com.linecorp.armeria.server.ServiceRequestContext; import com.linecorp.armeria.server.http.AbstractHttpService; import com.linecorp.armeria.testing.server.ServerRule; import io.netty.handler.codec.http.QueryStringDecoder; import okhttp3.HttpUrl; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; import retrofit2.adapter.java8.Java8CallAdapterFactory; import retrofit2.converter.jackson.JacksonConverterFactory; import retrofit2.http.Body; import retrofit2.http.Field; import retrofit2.http.FormUrlEncoded; import retrofit2.http.GET; import retrofit2.http.Header; import retrofit2.http.Headers; import retrofit2.http.POST; import retrofit2.http.Query; public class ArmeriaCallFactoryTest { public static class Pojo { @JsonProperty("name") String name; @JsonProperty("age") int age; @JsonCreator public Pojo(@JsonProperty("name") String name, @JsonProperty("age") int age) { this.name = name; this.age = age; } @Override public boolean equals(Object o) { if (o == this) { return true; } if (!(o instanceof Pojo)) { return false; } Pojo other = (Pojo) o; return name.equals(other.name) && age == other.age; } @Override public int hashCode() { int result = 1; result = result * 31 + (name == null ? 43 : name.hashCode()); result = result * 31 + age; return result; } @Override public String toString() { return "Pojo[name=" + name + ", age=" + age + ']'; } } interface Service { @GET("/pojo") CompletableFuture<Pojo> pojo(); @GET("/pojo") Call<Pojo> pojoReturnCall(); @GET("/pojos") CompletableFuture<List<Pojo>> pojos(); @GET("/queryString") CompletableFuture<Pojo> queryString(@Query("name") String name, @Query("age") int age); @POST("/post") @Headers("content-type: application/json; charset=UTF-8") CompletableFuture<Response<Void>> post(@Body Pojo pojo); @POST("/postForm") @FormUrlEncoded CompletableFuture<Response<Void>> postForm(@Field("name") String name, @Field("age") int age); @POST("/postCustomContentType") CompletableFuture<Response<Void>> postCustomContentType(@Header("Content-Type") String contentType); } private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); @ClassRule public static final ServerRule server = new ServerRule() { @Override protected void configure(ServerBuilder sb) throws Exception { sb.serviceAt("/pojo", new AbstractHttpService() { @Override protected void doGet(ServiceRequestContext ctx, HttpRequest req, HttpResponseWriter res) throws Exception { res.respond(HttpStatus.OK, MediaType.JSON_UTF_8, "{\"name\":\"Cony\", \"age\":26}"); } }) .serviceAt("/pojos", new AbstractHttpService() { @Override protected void doGet(ServiceRequestContext ctx, HttpRequest req, HttpResponseWriter res) throws Exception { res.respond(HttpStatus.OK, MediaType.JSON_UTF_8, "[{\"name\":\"Cony\", \"age\":26}," + "{\"name\":\"Leonard\", \"age\":21}]"); } }) .serviceAt("/queryString", new AbstractHttpService() { @Override protected void doGet(ServiceRequestContext ctx, HttpRequest req, HttpResponseWriter res) throws Exception { req.aggregate().handle(voidFunction((aReq, cause) -> { Map<String, List<String>> params = new QueryStringDecoder(aReq.path()) .parameters(); res.respond(HttpStatus.OK, MediaType.JSON_UTF_8, "{\"name\":\"" + params.get("name").get(0) + "\", " + "\"age\":" + params.get("age").get(0) + '}'); })); } }) .serviceAt("/post", new AbstractHttpService() { @Override protected void doPost(ServiceRequestContext ctx, HttpRequest req, HttpResponseWriter res) throws Exception { req.aggregate().handle(voidFunction((aReq, cause) -> { if (cause != null) { res.respond(HttpStatus.INTERNAL_SERVER_ERROR, MediaType.PLAIN_TEXT_UTF_8, Throwables.getStackTraceAsString(cause)); return; } String text = aReq.content().toStringUtf8(); final Pojo request; try { request = OBJECT_MAPPER.readValue(text, Pojo.class); } catch (IOException e) { res.respond(HttpStatus.INTERNAL_SERVER_ERROR, MediaType.PLAIN_TEXT_UTF_8, Throwables.getStackTraceAsString(e)); return; } assertThat(request).isEqualTo(new Pojo("Cony", 26)); res.respond(HttpStatus.OK); })); } }) .serviceAt("/postForm", new AbstractHttpService() { @Override protected void doPost(ServiceRequestContext ctx, HttpRequest req, HttpResponseWriter res) throws Exception { req.aggregate().handle(voidFunction((aReq, cause) -> { if (cause != null) { res.respond(HttpStatus.INTERNAL_SERVER_ERROR, MediaType.PLAIN_TEXT_UTF_8, Throwables.getStackTraceAsString(cause)); return; } Map<String, List<String>> params = new QueryStringDecoder( aReq.content().toStringUtf8(), false) .parameters(); assertThat(params).isEqualTo(ImmutableMap.of("name", ImmutableList.of("Cony"), "age", ImmutableList.of("26"))); res.respond(HttpStatus.OK); })); } }) .serviceAt("/postCustomContentType", new AbstractHttpService() { @Override protected void doPost(ServiceRequestContext ctx, HttpRequest req, HttpResponseWriter res) throws Exception { req.aggregate().handle(voidFunction((aReq, cause) -> { if (cause != null) { res.respond(HttpStatus.INTERNAL_SERVER_ERROR, MediaType.PLAIN_TEXT_UTF_8, Throwables.getStackTraceAsString(cause)); return; } Map<String, List<String>> params = new QueryStringDecoder( aReq.content().toStringUtf8(), false) .parameters(); assertThat(params).isEmpty(); res.respond(HttpStatus.OK); })); } }); } }; private Service service; @Before public void setUp() { service = ArmeriaRetrofit.builder(Clients.newClient(ClientFactory.DEFAULT, server.uri(NONE, "/"), HttpClient.class)) .addConverterFactory(JacksonConverterFactory.create(OBJECT_MAPPER)) .addCallAdapterFactory(Java8CallAdapterFactory.create()) .build() .create(Service.class); } @Test public void pojo() throws Exception { Pojo pojo = service.pojo().get(); assertThat(pojo).isEqualTo(new Pojo("Cony", 26)); } @Test public void pojos() throws Exception { List<Pojo> pojos = service.pojos().get(); assertThat(pojos.get(0)).isEqualTo(new Pojo("Cony", 26)); assertThat(pojos.get(1)).isEqualTo(new Pojo("Leonard", 21)); } @Test public void queryString() throws Exception { Pojo response = service.queryString("Cony", 26).get(); assertThat(response).isEqualTo(new Pojo("Cony", 26)); } @Test public void post() throws Exception { Response<Void> response = service.post(new Pojo("Cony", 26)).get(); assertThat(response.isSuccessful()).isTrue(); } @Test public void formEncoded() throws Exception { Response<Void> response = service.postForm("Cony", 26).get(); assertThat(response.isSuccessful()).isTrue(); } @Test public void pojo_returnCall() throws Exception { Pojo pojo = service.pojoReturnCall().execute().body(); assertThat(pojo).isEqualTo(new Pojo("Cony", 26)); } @Test public void pojo_returnCallCancelBeforeEnqueue() throws Exception { CountDownLatch countDownLatch = new CountDownLatch(1); Call<Pojo> pojoCall = service.pojoReturnCall(); pojoCall.cancel(); pojoCall.enqueue(new Callback<Pojo>() { @Override public void onResponse(Call<Pojo> call, Response<Pojo> response) { } @Override public void onFailure(Call<Pojo> call, Throwable t) { countDownLatch.countDown(); } }); assertThat(countDownLatch.await(3, TimeUnit.SECONDS)).isTrue(); } @Test public void pojo_returnCallCancelAfterComplete() throws Exception { CountDownLatch countDownLatch = new CountDownLatch(1); AtomicInteger failCount = new AtomicInteger(0); Call<Pojo> pojoCall = service.pojoReturnCall(); pojoCall.enqueue(new Callback<Pojo>() { @Override public void onResponse(Call<Pojo> call, Response<Pojo> response) { countDownLatch.countDown(); } @Override public void onFailure(Call<Pojo> call, Throwable t) { failCount.incrementAndGet(); } }); assertThat(countDownLatch.await(3, TimeUnit.SECONDS)).isTrue(); pojoCall.cancel(); assertThat(failCount.intValue()).isZero(); } @Test public void respectsHttpClientUri() throws Exception { Response<Void> response = service.postForm("Cony", 26).get(); assertThat(response.raw().request().url()).isEqualTo( new HttpUrl.Builder().scheme("http") .host("127.0.0.1") .port(server.httpPort()) .addPathSegment("postForm") .build()); } @Test public void respectsHttpClientUri_endpointGroup() throws Exception { EndpointGroupRegistry.register("foo", new StaticEndpointGroup(Endpoint.of("127.0.0.1", server.httpPort())), ROUND_ROBIN); Service service = ArmeriaRetrofit.builder(Clients.newClient(ClientFactory.DEFAULT, "none+http://group:foo/", HttpClient.class)) .addConverterFactory(JacksonConverterFactory.create(OBJECT_MAPPER)) .addCallAdapterFactory(Java8CallAdapterFactory.create()) .build() .create(Service.class); Response<Void> response = service.postForm("Cony", 26).get(); // TODO(ide) Use the actual `host:port`. See https://github.com/line/armeria/issues/379 assertThat(response.raw().request().url()).isEqualTo( new HttpUrl.Builder().scheme("http") .host("group_foo") .addPathSegment("postForm") .build()); } /** * Tests https://github.com/line/armeria/pull/386 */ @Test public void nullContentType() throws Exception { Response<Void> response = service.postCustomContentType(null).get(); assertThat(response.code()).isEqualTo(200); } }