14

How it is possible to read a response body while using Zuul as a proxy in post filter?

I am trying to call the code like this:

@Component
public class PostFilter extends ZuulFilter {

    private static final Logger log = LoggerFactory.getLogger(PostFilter.class);

    @Override
    public String filterType() {
        return "post";
    }

    @Override
    public int filterOrder() {
        return 2000;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        ctx.getResponseBody(); // null

        // cant't do this, cause input stream is used later in other filters and I got InputStream Closed exception
        // GZIPInputStream gzipInputStream = new GZIPInputStream(stream);
        return null;
    }

}
Dariusz Mydlarz
  • 2,940
  • 6
  • 31
  • 59

8 Answers8

13

I've managed to overcome this. The solution consists of 4 steps:

  1. Read ctx.getResponseDataStream() into a ByteArrayOutputStream
  2. Copy OutputStream to 2 InputStreams.
  3. Use one of it for your custom purposes.
  4. Use the second to reassign to context: context.setResponseBody(inputStream)
    • reading stream from point 1 would cause that the stream cannot be read again, so this way you're passing a new fresh stream that wasn't read yet
Dariusz Mydlarz
  • 2,940
  • 6
  • 31
  • 59
  • Don't remember exactly, but probably yes. Did you copied inputstream? – Dariusz Mydlarz Jan 16 '17 at 21:24
  • 1
    Copying the `InputStream` worked great, but I had to set my filter order before `org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter`. Using anything greater than 1000 leads to an "InputStream already closed" error because the response body has already been read and returned. – Michael Técourt Jan 17 '17 at 13:59
  • Ok, good. The number I used in example was probably not so important for my question ;) – Dariusz Mydlarz Jan 17 '17 at 14:17
  • 1
    @MichaelTecourt the filter number should be part of the response. I was trying for hours, just changed the filter number to 10 and it worked – Pedro Romão May 12 '17 at 14:43
11

If someone is struggling with compressed answer, here's the solution I used:

// Read the compressed response
RequestContext ctx = RequestContext.getCurrentContext();
InputStream compressedResponseDataStream = ctx.getResponseDataStream();
try {
    // Uncompress and transform the response
    InputStream responseDataStream = new GZIPInputStream(compressedResponseDataStream);
    String responseAsString = StreamUtils.copyToString(responseDataStream, Charset.forName("UTF-8"));
    // Do want you want with your String response
    ...
    // Replace the response with the modified object
    ctx.setResponseBody(responseAsString);
} catch (IOException e) {
    logger.warn("Error reading body", e);
}
VincentS
  • 602
  • 1
  • 10
  • 24
4

Thanks for suggestion, this is the code I used that works.

try (final InputStream responseDataStream = ctx.getResponseDataStream()) {
   final String responseData = CharStreams.toString(new InputStreamReader(responseDataStream, "UTF-8"));
   ctx.setResponseBody(responseData);
} catch (IOException e) {
   logger.warn("Error reading body",e);
}
codesalsa
  • 882
  • 5
  • 18
  • 2
    Nice use of Java 7's try-with-resources syntax here. In case someone is worried about `CharStreams.toString()` being `@Beta` (IntelliJ warns about it), one could also use Spring's `StreamUtils.copyToString()` if it's a dependency. This way, one can also omit the `InputStreamReader` instance. Also, imho it's a bit cleaner to use `StandardCharsets.UTF_8` instead of `"UTF-8"`. – Daniel K Nov 12 '18 at 15:17
3

As you can see in this example, you have two methods available to extract the response body:

1- ctx.getResponseBody();

2- ctx.getResponseDataStream();

You have to check which one is not null and use that one.

3

Be careful with the filterNumber

Using anything greater than 1000 leads to an "InputStream already closed" error because the response body has already been read and

I used the number 10 and worked fine

Pedro Romão
  • 2,285
  • 28
  • 22
1

None of the answers worked for me. 1) Order of the filter needs to be lower that 1000 (sending response filter)

2) Code:

 private String getResponseData(RequestContext ctx) throws IOException {
    String responseData = null;

    final InputStream responseDataStream = ctx.getResponseDataStream();
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ByteArrayOutputStream copy = new ByteArrayOutputStream();
    int read = 0;
    byte[] buff = new byte[1024];
    while ((read = responseDataStream.read(buff)) != -1) {
        bos.write(buff, 0, read);
        copy.write(buff, 0, read);
    }
    InputStream isFromFirstData = new ByteArrayInputStream(bos.toByteArray());

    boolean responseGZipped = ctx.getResponseGZipped();
    try {
        InputStream zin = null;
        if (responseGZipped) {
            zin = new GZIPInputStream(isFromFirstData);
        } else {
            zin = responseDataStream;
        }
        responseData = CharStreams.toString(new InputStreamReader(zin, "UTF-8"));
        ctx.setResponseDataStream(new ByteArrayInputStream(copy.toByteArray()));

    } catch (IOException e) {
        logger.warn("Error reading body {}", e.getMessage());
    }

    return responseData;
}
Damian
  • 2,930
  • 6
  • 39
  • 61
0

The simplest solution would be to get an input stream, read it with InputStreamReader, and finally create a new stream based on the read string and set it as a response data stream of context. Also, don't forget to set the order of the filter to number lower then 1000.

final InputStream responseDataStream = ctx.getResponseDataStream();
String response = CharStreams.toString(new InputStreamReader(responseDataStream, StandardCharsets.UTF_8));
InputStream stream = new ByteArrayInputStream(response.getBytes(StandardCharsets.UTF_8));
ctx.setResponseDataStream(stream);
Toni
  • 3,296
  • 2
  • 13
  • 34
0
//NOTE:filterOrder:999
RequestContext context = getCurrentContext();
InputStream stream = context.getResponseDataStream();
// uncompress
String body = StreamUtils.copyToString(new GZIPInputStream(stream),
    Charset.forName("UTF-8"));

// modify
body = "Modified gzipped response via setResponseBody(): " + body;

// compress again
ByteArrayOutputStream bos = new ByteArrayOutputStream(body.length());
GZIPOutputStream gzip = new GZIPOutputStream(bos);
gzip.write(body.getBytes("UTF-8"));
gzip.close();
byte[] compressed = bos.toByteArray();
bos.close();
context.setResponseDataStream(new ByteArrayInputStream(compressed));

https://github.com/spring-attic/sample-zuul-filters/issues/4

zy_sun
  • 175
  • 1
  • 11