From fc1ff16c6880e2db89476caee33ea0a490e42176 Mon Sep 17 00:00:00 2001 From: Mark Elliot Date: Wed, 25 May 2016 18:18:21 -0700 Subject: [PATCH] Use default encoder when body type is text/plain --- .../palantir/remoting/http/FeignClients.java | 16 +++-- .../main/java/feign/TextDelegateEncoder.java | 55 +++++++++++++++ .../remoting/http/TextEncoderTest.java | 70 +++++++++++++++++++ 3 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 http-clients/src/main/java/feign/TextDelegateEncoder.java create mode 100644 http-clients/src/test/java/com/palantir/remoting/http/TextEncoderTest.java diff --git a/http-clients/src/main/java/com/palantir/remoting/http/FeignClients.java b/http-clients/src/main/java/com/palantir/remoting/http/FeignClients.java index 33df804ff..c20716faf 100644 --- a/http-clients/src/main/java/com/palantir/remoting/http/FeignClients.java +++ b/http-clients/src/main/java/com/palantir/remoting/http/FeignClients.java @@ -25,6 +25,7 @@ import feign.Request; import feign.Request.Options; import feign.TextDelegateDecoder; +import feign.TextDelegateEncoder; import feign.jackson.JacksonDecoder; import feign.jackson.JacksonEncoder; import feign.jaxrs.JaxRsWithHeaderAndQueryMapContract; @@ -55,7 +56,9 @@ public static FeignClientFactory standard() { public static FeignClientFactory standard(Request.Options timeoutOptions) { return FeignClientFactory.of( new GuavaOptionalAwareContract(new JaxRsWithHeaderAndQueryMapContract()), - new InputStreamDelegateEncoder(new JacksonEncoder(ObjectMappers.guavaJdk7())), + new InputStreamDelegateEncoder( + new TextDelegateEncoder( + new JacksonEncoder(ObjectMappers.guavaJdk7()))), new OptionalAwareDecoder( new InputStreamDelegateDecoder( new TextDelegateDecoder( @@ -79,7 +82,9 @@ public static FeignClientFactory standardJackson24() { public static FeignClientFactory standardJackson24(Request.Options timeoutOptions) { return FeignClientFactory.of( new GuavaOptionalAwareContract(new JaxRsWithHeaderAndQueryMapContract()), - new InputStreamDelegateEncoder(new Jackson24Encoder(ObjectMappers.guavaJdk7())), + new InputStreamDelegateEncoder( + new TextDelegateEncoder( + new Jackson24Encoder(ObjectMappers.guavaJdk7()))), new OptionalAwareDecoder( new InputStreamDelegateDecoder( new TextDelegateDecoder( @@ -103,7 +108,9 @@ public static FeignClientFactory vanilla() { public static FeignClientFactory vanilla(Request.Options timeoutOptions) { return FeignClientFactory.of( new GuavaOptionalAwareContract(new JaxRsWithHeaderAndQueryMapContract()), - new InputStreamDelegateEncoder(new JacksonEncoder(ObjectMappers.vanilla())), + new InputStreamDelegateEncoder( + new TextDelegateEncoder( + new JacksonEncoder(ObjectMappers.vanilla()))), new OptionalAwareDecoder( new InputStreamDelegateDecoder( new TextDelegateDecoder( @@ -127,7 +134,8 @@ public static FeignClientFactory withMapper(ObjectMapper mapper) { public static FeignClientFactory withMapper(ObjectMapper mapper, Request.Options timeoutOptions) { return FeignClientFactory.of( new GuavaOptionalAwareContract(new JaxRsWithHeaderAndQueryMapContract()), - new InputStreamDelegateEncoder(new JacksonEncoder(mapper)), + new InputStreamDelegateEncoder( + new TextDelegateEncoder(new JacksonEncoder(mapper))), new OptionalAwareDecoder( new InputStreamDelegateDecoder( new TextDelegateDecoder( diff --git a/http-clients/src/main/java/feign/TextDelegateEncoder.java b/http-clients/src/main/java/feign/TextDelegateEncoder.java new file mode 100644 index 000000000..6f7267000 --- /dev/null +++ b/http-clients/src/main/java/feign/TextDelegateEncoder.java @@ -0,0 +1,55 @@ +/* + * Copyright 2016 Palantir Technologies, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package feign; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.net.HttpHeaders; +import feign.codec.EncodeException; +import feign.codec.Encoder; +import java.lang.reflect.Type; +import java.util.Collection; +import javax.ws.rs.core.MediaType; + +/** + * Delegates to a {@link Encoder.Default} if the response has a Content-Type of text/plain, or falls back to the given + * delegate otherwise. + */ +public final class TextDelegateEncoder implements Encoder { + private static final Encoder defaultEncoder = new Encoder.Default(); + + private final Encoder delegate; + + public TextDelegateEncoder(Encoder delegate) { + this.delegate = delegate; + } + + @Override + public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException { + Collection contentTypes = template.headers().get(HttpHeaders.CONTENT_TYPE); + if (contentTypes == null) { + contentTypes = ImmutableSet.of(); + } + + // In the case of multiple content types, or an unknown content type, we'll use the delegate instead. + if (contentTypes.size() == 1 && Iterables.getOnlyElement(contentTypes, "").equals(MediaType.TEXT_PLAIN)) { + defaultEncoder.encode(object, bodyType, template); + } else { + delegate.encode(object, bodyType, template); + } + } +} diff --git a/http-clients/src/test/java/com/palantir/remoting/http/TextEncoderTest.java b/http-clients/src/test/java/com/palantir/remoting/http/TextEncoderTest.java new file mode 100644 index 000000000..06eb6dcbd --- /dev/null +++ b/http-clients/src/test/java/com/palantir/remoting/http/TextEncoderTest.java @@ -0,0 +1,70 @@ +/* + * Copyright 2016 Palantir Technologies, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.palantir.remoting.http; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import com.google.common.base.Optional; +import com.squareup.okhttp.mockwebserver.MockResponse; +import com.squareup.okhttp.mockwebserver.MockWebServer; +import com.squareup.okhttp.mockwebserver.RecordedRequest; +import javax.net.ssl.SSLSocketFactory; +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.core.MediaType; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +public final class TextEncoderTest { + + @Rule + public final MockWebServer server = new MockWebServer(); + + private TextEncoderService service; + + @Before + public void before() { + String endpointUri = "http://localhost:" + server.getPort(); + + service = FeignClients.standard().createProxy( + Optional.absent(), + endpointUri, + TextEncoderService.class); + + server.enqueue(new MockResponse().setBody("{}")); + } + + @Test + public void testTextEncoder_doesNotEscapeAsJson() throws InterruptedException { + String testString = "{\"key\": \"value\"}"; + service.post(testString); + + RecordedRequest request = server.takeRequest(); + assertThat(request.getBody().readUtf8(), is(testString)); + } + + @Path("/") + public interface TextEncoderService { + @POST + @Consumes(MediaType.TEXT_PLAIN) + Object post(String test); + } + +}