Skip to content

Commit 0dd9782

Browse files
davidmc24adriancole
authored andcommitted
default client: add support for gzip-encoded request bodies (OpenFeign#52)
Enhances the default client to GZIP-encode request bodies when the appropriate content-encoding header is set in the interface's method definition. OpenFeign#52
1 parent f1cea1c commit 0dd9782

5 files changed

Lines changed: 98 additions & 3 deletions

File tree

CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
### Version 4.4
2+
* Support overriding default HostnameVerifier
3+
* Support GZIP content encoding for request bodies
4+
15
### Version 4.3
26
* Add ability to configure zero or more RequestInterceptors.
37
* Remove `overrides = true` on codec modules.

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.LinkedHashMap;
2727
import java.util.List;
2828
import java.util.Map;
29+
import java.util.zip.GZIPOutputStream;
2930

3031
import javax.inject.Inject;
3132
import javax.net.ssl.HostnameVerifier;
@@ -35,7 +36,9 @@
3536
import dagger.Lazy;
3637
import feign.Request.Options;
3738

39+
import static feign.Util.CONTENT_ENCODING;
3840
import static feign.Util.CONTENT_LENGTH;
41+
import static feign.Util.ENCODING_GZIP;
3942
import static feign.Util.UTF_8;
4043

4144
/**
@@ -81,13 +84,20 @@ HttpURLConnection convertAndSend(Request request, Options options) throws IOExce
8184
connection.setInstanceFollowRedirects(true);
8285
connection.setRequestMethod(request.method());
8386

87+
Collection<String> contentEncodingValues = request.headers().get(CONTENT_ENCODING);
88+
boolean gzipEncodedRequest = contentEncodingValues != null && contentEncodingValues.contains(ENCODING_GZIP);
89+
8490
Integer contentLength = null;
8591
for (String field : request.headers().keySet()) {
8692
for (String value : request.headers().get(field)) {
8793
if (field.equals(CONTENT_LENGTH)) {
88-
contentLength = Integer.valueOf(value);
94+
if (!gzipEncodedRequest) {
95+
contentLength = Integer.valueOf(value);
96+
connection.addRequestProperty(field, value);
97+
}
98+
} else {
99+
connection.addRequestProperty(field, value);
89100
}
90-
connection.addRequestProperty(field, value);
91101
}
92102
}
93103

@@ -99,6 +109,9 @@ HttpURLConnection convertAndSend(Request request, Options options) throws IOExce
99109
}
100110
connection.setDoOutput(true);
101111
OutputStream out = connection.getOutputStream();
112+
if (gzipEncodedRequest) {
113+
out = new GZIPOutputStream(out);
114+
}
102115
try {
103116
out.write(request.body().getBytes(UTF_8));
104117
} finally {

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,18 @@ private Util() { // no instances
3939
* The HTTP Content-Length header field name.
4040
*/
4141
public static final String CONTENT_LENGTH = "Content-Length";
42+
/**
43+
* The HTTP Content-Encoding header field name.
44+
*/
45+
public static final String CONTENT_ENCODING = "Content-Encoding";
4246
/**
4347
* The HTTP Retry-After header field name.
4448
*/
4549
public static final String RETRY_AFTER = "Retry-After";
50+
/**
51+
* Value for the Content-Encoding header that indicates that GZIP encoding is in use.
52+
*/
53+
public static final String ENCODING_GZIP = "gzip";
4654

4755
// com.google.common.base.Charsets
4856
/**

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

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
package feign;
1717

1818
import com.google.common.base.Joiner;
19+
import com.google.common.io.ByteStreams;
20+
import com.google.common.io.CharStreams;
1921
import com.google.mockwebserver.MockResponse;
2022
import com.google.mockwebserver.MockWebServer;
2123
import com.google.mockwebserver.RecordedRequest;
@@ -47,7 +49,9 @@
4749
import java.util.concurrent.atomic.AtomicBoolean;
4850

4951
import static dagger.Provides.Type.SET;
52+
import static feign.Util.UTF_8;
5053
import static org.testng.Assert.assertEquals;
54+
import static org.testng.Assert.assertNull;
5155
import static org.testng.Assert.assertTrue;
5256
import static org.testng.Assert.fail;
5357

@@ -80,6 +84,8 @@ void login(
8084

8185
@RequestLine("POST /") void body(List<String> contents);
8286

87+
@RequestLine("POST /") @Headers("Content-Encoding: gzip") void gzipBody(List<String> contents);
88+
8389
@RequestLine("POST /") void form(
8490
@Named("customer_name") String customer, @Named("user_name") String user, @Named("password") String password);
8591

@@ -310,7 +316,30 @@ public void postBodyParam() throws IOException, InterruptedException {
310316
TestInterface api = Feign.create(TestInterface.class, "http://localhost:" + server.getPort(), new TestInterface.Module());
311317

312318
api.body(Arrays.asList("netflix", "denominator", "password"));
313-
assertEquals(new String(server.takeRequest().getBody()), "[netflix, denominator, password]");
319+
RecordedRequest request = server.takeRequest();
320+
assertEquals(request.getHeader("Content-Length"), "32");
321+
assertEquals(new String(request.getBody()), "[netflix, denominator, password]");
322+
} finally {
323+
server.shutdown();
324+
}
325+
}
326+
327+
@Test
328+
public void postGZIPEncodedBodyParam() throws IOException, InterruptedException {
329+
final MockWebServer server = new MockWebServer();
330+
server.enqueue(new MockResponse().setResponseCode(200).setBody("foo"));
331+
server.play();
332+
333+
try {
334+
TestInterface api = Feign.create(TestInterface.class, "http://localhost:" + server.getPort(), new TestInterface.Module());
335+
336+
api.gzipBody(Arrays.asList("netflix", "denominator", "password"));
337+
RecordedRequest request = server.takeRequest();
338+
assertNull(request.getHeader("Content-Length"));
339+
byte[] compressedBody = request.getBody();
340+
String uncompressedBody = CharStreams.toString(CharStreams.newReaderSupplier(
341+
GZIPStreams.newInputStreamSupplier(ByteStreams.newInputStreamSupplier(compressedBody)), UTF_8));
342+
assertEquals(uncompressedBody, "[netflix, denominator, password]");
314343
} finally {
315344
server.shutdown();
316345
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2013 Netflix, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package feign;
17+
18+
import com.google.common.io.InputSupplier;
19+
20+
import java.io.IOException;
21+
import java.io.InputStream;
22+
import java.util.zip.GZIPInputStream;
23+
24+
class GZIPStreams {
25+
static InputSupplier<GZIPInputStream> newInputStreamSupplier(InputSupplier<? extends InputStream> supplier) {
26+
return new GZIPInputStreamSupplier(supplier);
27+
}
28+
29+
private static class GZIPInputStreamSupplier implements InputSupplier<GZIPInputStream> {
30+
private final InputSupplier<? extends InputStream> supplier;
31+
32+
GZIPInputStreamSupplier(InputSupplier<? extends InputStream> supplier) {
33+
this.supplier = supplier;
34+
}
35+
36+
@Override
37+
public GZIPInputStream getInput() throws IOException {
38+
return new GZIPInputStream(supplier.getInput());
39+
}
40+
}
41+
}

0 commit comments

Comments
 (0)