如何在 Zuul 后置过滤器中获取响应正文?

Posted

技术标签:

【中文标题】如何在 Zuul 后置过滤器中获取响应正文?【英文标题】:How to get response body in Zuul post filter? 【发布时间】:2016-05-07 10:09:08 【问题描述】:

post 过滤器中使用 Zuul 作为代理时如何读取响应正文?

我正在尝试这样调用代码:

@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;
    


【问题讨论】:

【参考方案1】:

我已经设法克服了这个问题。解决方案包括 4 个步骤:

    ctx.getResponseDataStream()读入ByteArrayOutputStream 将 OutputStream 复制到 2 个 InputStream。 将其中一个用于您的自定义目的。 使用第二个重新分配给上下文:context.setResponseBody(inputStream) 从第 1 点读取流会导致无法再次读取流,因此这样您将传递一个尚未读取的新流

【讨论】:

不记得确切,但可能是的。你复制了输入流吗? 复制InputStream 效果很好,但我必须在org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter 之前设置过滤顺序。使用大于 1000 的值会导致“InputStream 已关闭”错误,因为响应正文已被读取并返回。 好的,很好。我在示例中使用的数字可能对我的问题不太重要;) @MichaelTcourt 过滤器编号应该是响应的一部分。我尝试了几个小时,只是将过滤器编号更改为 10 并且它起作用了【参考方案2】:

如果有人正在为压缩答案而苦苦挣扎,这是我使用的解决方案:

// 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);

【讨论】:

我错过了这条评论,花了 6 个小时在上面 @Cshah 在搜索 SO 中是否存在此问题之前,切勿尝试解决问题! ;) 说真的,很高兴它有帮助! 如果你使用 gzip 作为 html 压缩过程【参考方案3】:

感谢您的建议,这是我使用的有效代码。

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);

【讨论】:

在这里很好地使用了 Java 7 的 try-with-resources 语法。如果有人担心 CharStreams.toString()@Beta (IntelliJ 对此提出警告),如果它是依赖项,也可以使用 Spring 的 StreamUtils.copyToString()。这样,也可以省略InputStreamReader 实例。另外,恕我直言,使用 StandardCharsets.UTF_8 而不是 "UTF-8" 会更干净一些。【参考方案4】:

正如您在此example 中看到的,您有两种方法可用于提取响应正文:

1- ctx.getResponseBody();

2- ctx.getResponseDataStream();

你必须检查哪一个不为空并使用那个。

【讨论】:

谢谢,我已经做了 id,但有点棘手。我会发布一些代码 就我而言,ctx.getResponseBody();返回 null 所以我使用了 ctx.getResponseDataStream();而是 刚刚发现deletectx.getResponseBody()postputgetResponseDataStream()【参考方案5】:

注意过滤器编号

使用大于 1000 的值会导致“InputStream already 关闭”错误,因为响应正文已被读取并且

我用了 10 号,效果很好

【讨论】:

伙计,你让我开心!感谢您的回答!【参考方案6】:

没有一个答案对我有用。 1)过滤器的顺序需要低于1000(发送响应过滤器)

2) 代码:

 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;

【讨论】:

【参考方案7】:

最简单的解决方案是获取一个输入流,使用InputStreamReader 读取它,最后根据读取的字符串创建一个新流,并将其设置为上下文的响应数据流。另外,不要忘记将过滤器的顺序设置为低于 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);

【讨论】:

以上是关于如何在 Zuul 后置过滤器中获取响应正文?的主要内容,如果未能解决你的问题,请参考以下文章

sprignclou的zuul的实战

在zuul过滤器中修改请求正文无法正常工作

如何使用保留请求正文和响应正文的 servlet 过滤器记录请求和响应?

WebClient - 如何获取请求正文?

如何使用 java 过滤器修改响应正文?

在 WebFlux WebClient 中测试状态码时如何获取响应正文?