package com.linecorp.armeria.server.thrift; import static com.linecorp.armeria.common.MediaType.parse; import static com.linecorp.armeria.common.SerializationFormat.find; import static com.linecorp.armeria.common.thrift.ThriftSerializationFormats.BINARY; import static com.linecorp.armeria.common.thrift.ThriftSerializationFormats.COMPACT; import static com.linecorp.armeria.common.thrift.ThriftSerializationFormats.JSON; import static com.linecorp.armeria.common.thrift.ThriftSerializationFormats.TEXT; import static org.assertj.core.api.Assertions.assertThat; import java.nio.charset.StandardCharsets; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import com.linecorp.armeria.client.ClientOption; import com.linecorp.armeria.client.Clients; import com.linecorp.armeria.client.InvalidResponseException; import com.linecorp.armeria.common.SerializationFormat; import com.linecorp.armeria.common.http.HttpHeaderNames; import com.linecorp.armeria.common.http.HttpHeaders; import com.linecorp.armeria.server.ServerBuilder; import com.linecorp.armeria.service.test.thrift.main.HelloService; import com.linecorp.armeria.testing.server.ServerRule; /** * Test of serialization format validation / detection based on HTTP headers. */ public class ThriftSerializationFormatsTest { private static final HelloService.Iface HELLO_SERVICE = name -> "Hello, " + name + '!'; @ClassRule public static final ServerRule server = new ServerRule() { @Override protected void configure(ServerBuilder sb) throws Exception { sb.serviceAt("/hello", THttpService.of(HELLO_SERVICE)) .serviceAt("/hellobinaryonly", THttpService.ofFormats(HELLO_SERVICE, BINARY)) .serviceAt("/hellotextonly", THttpService.ofFormats(HELLO_SERVICE, TEXT)); } }; @Rule public ExpectedException thrown = ExpectedException.none(); @Test public void findByMediaType() { // The 'protocol' parameter has to be case-insensitive. assertThat(find(parse("application/x-thrift; protocol=tbinary"))).containsSame(BINARY); assertThat(find(parse("application/x-thrift;protocol=TCompact"))).containsSame(COMPACT); assertThat(find(parse("application/x-thrift ; protocol=\"TjSoN\""))).containsSame(JSON); // An unknown parameter ('version' in this case) should not be accepted. assertThat(find(parse("application/x-thrift ; version=3;protocol=ttext"))).isEmpty(); // 'charset=utf-8' parameter should be accepted for TJSON and TTEXT. assertThat(find(parse("application/x-thrift; protocol=tjson; charset=utf-8"))).containsSame(JSON); assertThat(find(parse("application/vnd.apache.thrift.json; charset=utf-8"))).containsSame(JSON); assertThat(find(parse("application/x-thrift; protocol=ttext; charset=utf-8"))).containsSame(TEXT); assertThat(find(parse("application/vnd.apache.thrift.text; charset=utf-8"))).containsSame(TEXT); // .. but neither non-UTF-8 charsets: assertThat(find(parse("application/x-thrift; protocol=tjson; charset=us-ascii"))).isEmpty(); assertThat(find(parse("application/vnd.apache.thrift.json; charset=us-ascii"))).isEmpty(); assertThat(find(parse("application/x-thrift; protocol=ttext; charset=us-ascii"))).isEmpty(); assertThat(find(parse("application/vnd.apache.thrift.text; charset=us-ascii"))).isEmpty(); // .. nor binary/compact formats: assertThat(find(parse("application/x-thrift; protocol=tbinary; charset=utf-8"))).isEmpty(); assertThat(find(parse("application/vnd.apache.thrift.binary; charset=utf-8"))).isEmpty(); assertThat(find(parse("application/x-thrift; protocol=tcompact; charset=utf-8"))).isEmpty(); assertThat(find(parse("application/vnd.apache.thrift.compact; charset=utf-8"))).isEmpty(); } @Test @SuppressWarnings("deprecation") public void backwardCompatibility() { assertThat(SerializationFormat.ofThrift()).containsExactlyInAnyOrder(BINARY, COMPACT, JSON, TEXT); assertThat(SerializationFormat.THRIFT_BINARY).isNotNull(); assertThat(SerializationFormat.THRIFT_COMPACT).isNotNull(); assertThat(SerializationFormat.THRIFT_JSON).isNotNull(); assertThat(SerializationFormat.THRIFT_TEXT).isNotNull(); } @Test public void defaults() throws Exception { HelloService.Iface client = Clients.newClient(server.uri(BINARY, "/hello"), HelloService.Iface.class); assertThat(client.hello("Trustin")).isEqualTo("Hello, Trustin!"); } @Test public void notDefault() throws Exception { HelloService.Iface client = Clients.newClient(server.uri(TEXT, "/hello"), HelloService.Iface.class); assertThat(client.hello("Trustin")).isEqualTo("Hello, Trustin!"); } @Test public void notAllowed() throws Exception { HelloService.Iface client = Clients.newClient(server.uri(TEXT, "/hellobinaryonly"), HelloService.Iface.class); thrown.expect(InvalidResponseException.class); thrown.expectMessage("415 Unsupported Media Type"); client.hello("Trustin"); } @Test public void contentTypeNotThrift() throws Exception { // Browser clients often send a non-thrift content type. HttpHeaders headers = HttpHeaders.of(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=utf-8"); HelloService.Iface client = Clients.newClient(server.uri(BINARY, "/hello"), HelloService.Iface.class, ClientOption.HTTP_HEADERS.newValue(headers)); assertThat(client.hello("Trustin")).isEqualTo("Hello, Trustin!"); } @Test public void acceptNotSameAsContentType() throws Exception { HttpHeaders headers = HttpHeaders.of(HttpHeaderNames.ACCEPT, "application/x-thrift; protocol=TBINARY"); HelloService.Iface client = Clients.newClient(server.uri(TEXT, "/hello"), HelloService.Iface.class, ClientOption.HTTP_HEADERS.newValue(headers)); thrown.expect(InvalidResponseException.class); thrown.expectMessage("406 Not Acceptable"); client.hello("Trustin"); } @Test public void defaultSerializationFormat() throws Exception { try (CloseableHttpClient hc = HttpClients.createMinimal()) { // Send a TTEXT request with content type 'application/x-thrift' without 'protocol' parameter. HttpPost req = new HttpPost(server.uri("/hellotextonly")); req.setHeader("Content-type", "application/x-thrift"); req.setEntity(new StringEntity( '{' + " \"method\": \"hello\"," + " \"type\":\"CALL\"," + " \"args\": { \"name\": \"trustin\"}" + '}', StandardCharsets.UTF_8)); try (CloseableHttpResponse res = hc.execute(req)) { assertThat(res.getStatusLine().toString()).isEqualTo("HTTP/1.1 200 OK"); } } } }