Spring Cloud: Feign request/response compression

If you happen to read one of the previous posts you probably guessing that we had been using a lot of Netflix Feign in our projects. In fact we had created a couple of extensions for OAuth support, custom error decoders and one additional for handling GZIP compression of both requests and responses.

https://github.com/jmnarloch/feign-encoding-spring-cloud-starter

Yet another Spring Cloud starter that you need to simply drop to your project dependencies:

Whether you are using Maven

<dependency>
  <groupId>com.github.jmnarloch</groupId>
  <artifactId>feign-encoding-spring-cloud-starter</artifactId>
  <version>1.1.1</version>
</dependency>

or Gradle:

compile 'com.github.jmnarloch:feign-encoding-spring-cloud-starter:1.1.1'

Feign setup

Then you need to decide whether you want to support compression of your request, responses or both. You do this by annotating your configuration class with @EnableFeignAcceptGzipEncoding and @EnableFeignContentGzipEncoding.

@EnableFeignAcceptGzipEncoding
@EnableFeignContentGzipEncoding
@EnableFeignClients
@Configuration
public class Application {

}

Simple isn’t it?

This will register Feign interceptors that will enrich the outgoing requests and set the Accept-Encoding and Content-Encoding HTTP headers. You need to be aware that this still doesn’t make automaticaly every your request/response to be compressed. For instance the server might be configured in way that it’s going to compress only specific media types or response above specifc threshold length.

Corresponding Feign client side options exists:

feign.compression.min-request-size=2048 # the minimum request size
feign.compression.mime-types=text/xml,application/xml,application/json # the request media types

Allowing to specify which supported media types and request should use GZIP compression.

Disclaimer:

If you run this plugin with Spring Cloud Netflix 1.0.1 you will run into fallowing error:

feign.codec.DecodeException: Could not read JSON: Illegal character ((CTRL-CHAR, code 31)): only regular white space (\r, \n, \t) is allowed between tokens
 at [Source: java.io.PushbackInputStream@c6b2dd9; line: 1, column: 2]; nested exception is com.fasterxml.jackson.core.JsonParseException: Illegal character ((CTRL-CHAR, code 31)): only regular white space (\r, \n, \t) is allowed between tokens
 at [Source: java.io.PushbackInputStream@c6b2dd9; line: 1, column: 2]
    at feign.SynchronousMethodHandler.decode(SynchronousMethodHandler.java:150)
    at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:118)
    at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:71)
    at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:94)
    at com.sun.proxy.$Proxy64.getInvoices(Unknown Source)
    at com.github.jmnarloch.spring.cloud.feign.FeignAcceptEncodingTest.compressedResponse(FeignAcceptEncodingTest.java:64)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:73)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:82)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:73)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:224)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:83)
    ... 40 more
Caused by: org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON: Illegal character ((CTRL-CHAR, code 31)): only regular white space (\r, \n, \t) is allowed between tokens
 at [Source: java.io.PushbackInputStream@c6b2dd9; line: 1, column: 2]; nested exception is com.fasterxml.jackson.core.JsonParseException: Illegal character ((CTRL-CHAR, code 31)): only regular white space (\r, \n, \t) is allowed between tokens
 at [Source: java.io.PushbackInputStream@c6b2dd9; line: 1, column: 2]
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:208)
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:200)
    at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:97)
    at org.springframework.cloud.netflix.feign.support.SpringDecoder.decode(SpringDecoder.java:57)
    at org.springframework.cloud.netflix.feign.support.ResponseEntityDecoder.decode(ResponseEntityDecoder.java:40)
    at feign.SynchronousMethodHandler.decode(SynchronousMethodHandler.java:146)
    ... 45 more
Caused by: com.fasterxml.jackson.core.JsonParseException: Illegal character ((CTRL-CHAR, code 31)): only regular white space (\r, \n, \t) is allowed between tokens
 at [Source: java.io.PushbackInputStream@c6b2dd9; line: 1, column: 2]
    at com.fasterxml.jackson.core.JsonParser._constructError(JsonParser.java:1419)
    at com.fasterxml.jackson.core.base.ParserMinimalBase._reportError(ParserMinimalBase.java:508)
    at com.fasterxml.jackson.core.base.ParserMinimalBase._throwInvalidSpace(ParserMinimalBase.java:459)
    at com.fasterxml.jackson.core.json.UTF8StreamJsonParser._skipWSOrEnd(UTF8StreamJsonParser.java:2625)
    at com.fasterxml.jackson.core.json.UTF8StreamJsonParser.nextToken(UTF8StreamJsonParser.java:645)
    at com.fasterxml.jackson.databind.ObjectMapper._initForReading(ObjectMapper.java:3105)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3051)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2221)
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:205)
    ... 50 more

To fix this you will have to enable Feign HttpClient support. Fallow the instruction: https://github.com/jmnarloch/feign-encoding-spring-cloud-starter#know-issues on how to do this.
In Spring Cloud Netflix 1.1 this problem does not exist.

Sneak peaking Spring Cloud 1.1

Starting from Spring Cloud 1.1, you will have control over the request/response compression and be able to enable it by specifying one of the properties:

feign.compression.request.enabled=true
feign.compression.response.enabled=true

While in case of responses you entirely have to rely on the server settings (the server may not support payload compression at all, or may compress only selected kind of responses). On contrary on the client side you have more fine grained control over which requests can be compressed, by specifying the supported media types and threshold content length.

Note: request compression enforces that your server must understand the Content-Encoding header and be able to decompress the incoming request, therefor it’s advise to use this feature with caution (it’s perfect for system internal communication where you have full control over each individual subsystem).

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s