Browse Source

default client: add support for gzip-encoded request bodies (#52)

Enhances the default client to GZIP-encode request bodies when the appropriate
content-encoding header is set in the interface's method definition.

https://github.com/Netflix/feign/issues/52
4.x
David M. Carr 11 years ago committed by adriancole
parent
commit
0dd978275b
  1. 4
      CHANGES.md
  2. 17
      core/src/main/java/feign/Client.java
  3. 8
      core/src/main/java/feign/Util.java
  4. 31
      core/src/test/java/feign/FeignTest.java
  5. 41
      core/src/test/java/feign/GZIPStreams.java

4
CHANGES.md

@ -1,3 +1,7 @@ @@ -1,3 +1,7 @@
### Version 4.4
* Support overriding default HostnameVerifier
* Support GZIP content encoding for request bodies
### Version 4.3
* Add ability to configure zero or more RequestInterceptors.
* Remove `overrides = true` on codec modules.

17
core/src/main/java/feign/Client.java

@ -26,6 +26,7 @@ import java.util.Collection; @@ -26,6 +26,7 @@ import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPOutputStream;
import javax.inject.Inject;
import javax.net.ssl.HostnameVerifier;
@ -35,7 +36,9 @@ import javax.net.ssl.SSLSocketFactory; @@ -35,7 +36,9 @@ import javax.net.ssl.SSLSocketFactory;
import dagger.Lazy;
import feign.Request.Options;
import static feign.Util.CONTENT_ENCODING;
import static feign.Util.CONTENT_LENGTH;
import static feign.Util.ENCODING_GZIP;
import static feign.Util.UTF_8;
/**
@ -81,13 +84,20 @@ public interface Client { @@ -81,13 +84,20 @@ public interface Client {
connection.setInstanceFollowRedirects(true);
connection.setRequestMethod(request.method());
Collection<String> contentEncodingValues = request.headers().get(CONTENT_ENCODING);
boolean gzipEncodedRequest = contentEncodingValues != null && contentEncodingValues.contains(ENCODING_GZIP);
Integer contentLength = null;
for (String field : request.headers().keySet()) {
for (String value : request.headers().get(field)) {
if (field.equals(CONTENT_LENGTH)) {
contentLength = Integer.valueOf(value);
if (!gzipEncodedRequest) {
contentLength = Integer.valueOf(value);
connection.addRequestProperty(field, value);
}
} else {
connection.addRequestProperty(field, value);
}
connection.addRequestProperty(field, value);
}
}
@ -99,6 +109,9 @@ public interface Client { @@ -99,6 +109,9 @@ public interface Client {
}
connection.setDoOutput(true);
OutputStream out = connection.getOutputStream();
if (gzipEncodedRequest) {
out = new GZIPOutputStream(out);
}
try {
out.write(request.body().getBytes(UTF_8));
} finally {

8
core/src/main/java/feign/Util.java

@ -39,10 +39,18 @@ public class Util { @@ -39,10 +39,18 @@ public class Util {
* The HTTP Content-Length header field name.
*/
public static final String CONTENT_LENGTH = "Content-Length";
/**
* The HTTP Content-Encoding header field name.
*/
public static final String CONTENT_ENCODING = "Content-Encoding";
/**
* The HTTP Retry-After header field name.
*/
public static final String RETRY_AFTER = "Retry-After";
/**
* Value for the Content-Encoding header that indicates that GZIP encoding is in use.
*/
public static final String ENCODING_GZIP = "gzip";
// com.google.common.base.Charsets
/**

31
core/src/test/java/feign/FeignTest.java

@ -16,6 +16,8 @@ @@ -16,6 +16,8 @@
package feign;
import com.google.common.base.Joiner;
import com.google.common.io.ByteStreams;
import com.google.common.io.CharStreams;
import com.google.mockwebserver.MockResponse;
import com.google.mockwebserver.MockWebServer;
import com.google.mockwebserver.RecordedRequest;
@ -47,7 +49,9 @@ import java.util.concurrent.Executors; @@ -47,7 +49,9 @@ import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import static dagger.Provides.Type.SET;
import static feign.Util.UTF_8;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
@ -80,6 +84,8 @@ public class FeignTest { @@ -80,6 +84,8 @@ public class FeignTest {
@RequestLine("POST /") void body(List<String> contents);
@RequestLine("POST /") @Headers("Content-Encoding: gzip") void gzipBody(List<String> contents);
@RequestLine("POST /") void form(
@Named("customer_name") String customer, @Named("user_name") String user, @Named("password") String password);
@ -310,7 +316,30 @@ public class FeignTest { @@ -310,7 +316,30 @@ public class FeignTest {
TestInterface api = Feign.create(TestInterface.class, "http://localhost:" + server.getPort(), new TestInterface.Module());
api.body(Arrays.asList("netflix", "denominator", "password"));
assertEquals(new String(server.takeRequest().getBody()), "[netflix, denominator, password]");
RecordedRequest request = server.takeRequest();
assertEquals(request.getHeader("Content-Length"), "32");
assertEquals(new String(request.getBody()), "[netflix, denominator, password]");
} finally {
server.shutdown();
}
}
@Test
public void postGZIPEncodedBodyParam() throws IOException, InterruptedException {
final MockWebServer server = new MockWebServer();
server.enqueue(new MockResponse().setResponseCode(200).setBody("foo"));
server.play();
try {
TestInterface api = Feign.create(TestInterface.class, "http://localhost:" + server.getPort(), new TestInterface.Module());
api.gzipBody(Arrays.asList("netflix", "denominator", "password"));
RecordedRequest request = server.takeRequest();
assertNull(request.getHeader("Content-Length"));
byte[] compressedBody = request.getBody();
String uncompressedBody = CharStreams.toString(CharStreams.newReaderSupplier(
GZIPStreams.newInputStreamSupplier(ByteStreams.newInputStreamSupplier(compressedBody)), UTF_8));
assertEquals(uncompressedBody, "[netflix, denominator, password]");
} finally {
server.shutdown();
}

41
core/src/test/java/feign/GZIPStreams.java

@ -0,0 +1,41 @@ @@ -0,0 +1,41 @@
/*
* Copyright 2013 Netflix, Inc.
*
* 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.io.InputSupplier;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.GZIPInputStream;
class GZIPStreams {
static InputSupplier<GZIPInputStream> newInputStreamSupplier(InputSupplier<? extends InputStream> supplier) {
return new GZIPInputStreamSupplier(supplier);
}
private static class GZIPInputStreamSupplier implements InputSupplier<GZIPInputStream> {
private final InputSupplier<? extends InputStream> supplier;
GZIPInputStreamSupplier(InputSupplier<? extends InputStream> supplier) {
this.supplier = supplier;
}
@Override
public GZIPInputStream getInput() throws IOException {
return new GZIPInputStream(supplier.getInput());
}
}
}
Loading…
Cancel
Save