java - Jersey 2 日志记录和 gzip

标签 java rest logging jersey

我为 ResourceConfig 注册了 LogginFilter 过滤器。

// Enable logging of requests
registerInstances(new LoggingFilter(java.util.logging.Logger.getLogger(this.getClass().getName()),
        true));

我还启用了 GZIPEncoding 过滤器,因此将为支持它的客户端压缩响应。

// Encode gzip responses if request header supports it
EncodingFilter.enableFor(this, GZipEncoder.class);

但现在的问题是日志过滤器正在输出压缩的响应实体,即。在应用压缩之后而不是之前,所以输出看起来像这样使得它非常无用并且不可能看到返回的内容。

INFO: 78 * Server responded with a response on thread http-bio-8443-exec-5 78 < 200 78 < Content-Type: application/json ^_^@^@^@^@^@^@^@

我可以禁用 GZIPEncoder 并以未压缩的方式记录响应,然后响应实体也以未压缩的方式发送...

有没有人建议如何避免这个问题并使服务器端 GZIP 支持成为可能,同时以纯文本而不是压缩的形式调试响应数据。

我将不胜感激。

谢谢 问候

最佳答案

我们也有同样的要求,在压缩时记录明文有效负载。这就是我最终的做法。 此示例适用于 http 客户端请求 Accept-Encoding: gzip 的情况。

您将拥有 GZIPWriterFilterInterceptor 类,它实现了 Jersey Filter 和 Interceptor 接口(interface)。

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Map.Entry;
import java.util.UUID;

import javax.annotation.Priority;
import javax.ws.rs.Priorities;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Request;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.WriterInterceptor;
import javax.ws.rs.ext.WriterInterceptorContext;

import org.apache.commons.io.output.TeeOutputStream;
@Provider
@Priority(Priorities.ENTITY_CODER)
@Compress
public class GZIPWriterFilterInterceptor implements ContainerResponseFilter, WriterInterceptor
{
/**
 * http header value for Accept-Encoding
 */
public static final String GZIP = "gzip";



protected GZIPOutputStream getGZIPOutputStream(final OutputStream outputStream) throws IOException
{
    return new GZIPOutputStream(outputStream);
}

@Override
public void filter(final ContainerRequestContext requestContext,
        final ContainerResponseContext responseContext) throws IOException
{
    final MultivaluedMap<String,String> headers = requestContext.getHeaders();
    //if client has requested with HttpHeader of Accept-Encoding: gzip, 
    //then do gzip encoding and put Content-Coding: gzip as part of response
    if( headers != null && headers.get(HttpHeaders.ACCEPT_ENCODING) != null )
    {
        for(final String header : headers.get(HttpHeaders.ACCEPT_ENCODING))
        {
            if(header.contains(GZIP))
            {
responseContext.getHeaders().add(HttpHeaders.CONTENT_ENCODING, GZIP);
                    break;
            }
        }
    }
}

@Override
public void aroundWriteTo(final WriterInterceptorContext context)
        throws IOException, WebApplicationException
{
    if( context.getHeaders() != null && context.getHeaders().containsKey(HttpHeaders.CONTENT_ENCODING)
            && context.getHeaders().get(HttpHeaders.CONTENT_ENCODING).contains(GZIP) )
    {
        // for the response, there is only one outputstream i.e. written to by jersey after all the
        // interceptors have invoked context.proceed() in their aroundWriteTo method.
        // so when we wrap the base outputstream in a GZIPOutputStream, the response will be GZIP encoded.
        // but we also want to log the payload. Hence we use TeeOutputStream to "clone" the base outputstream.
        // As a result, jersey will write directly to TeeOutputStream, which in turn will forward the writes to
        // both GZIPOutputStream and ByteArrayOutputStream. We also store the ByteArrayOutputStream in context
        // so that it can be accessed by APILoggingFilter.
        final ByteArrayOutputStream boas = new ByteArrayOutputStream();

        final OutputStream outputStream = context.getOutputStream();
        context.setOutputStream(new TeeOutputStream(getGZIPOutputStream(outputStream)
                , boas) );
        context.setProperty(Constants.ENTITY_LOGGER_PROPERTY, boas);

    }
    context.proceed();
}
}

请注意,我们如何使用 Apache 的 TeeOutputStream 克隆输出流。

接下来您需要的只是 Compress Annotation 定义,然后您可以使用 @Compress 注释您的 Resource 方法。还要在您的 web.xml 中包含此类的包名称。

@NameBinding
@Retention(RetentionPolicy.RUNTIME)
public @interface Compress 
{

}

这是 web.xml 的相关片段

 <servlet>
    <servlet-name>MY Web Application</servlet-name>
    <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
    <init-param>
        <param-name>jersey.config.server.provider.packages</param-name>
        <param-value>com.my.gzip.logging.jaxrs.filters.package</param-value>
    </init-param>

    <load-on-startup>1</load-on-startup>
</servlet>

如果你想进行负载日志记录,你可以添加另一个 Jersey 过滤器/拦截器。这是它的 aroundWriteTo 方法的样子。

    public void aroundWriteTo(final WriterInterceptorContext context) throws IOException, WebApplicationException
{
    if (context != null)
    {
        ByteArrayOutputStream stream = (ByteArrayOutputStream) context.getProperty(ENTITY_LOGGER_PROPERTY);
        if( stream == null)
        {
            //it means the GZIPWriter is not hooked in.
            stream = new ByteArrayOutputStream();

            final OutputStream outputStream = context.getOutputStream();
            context.setOutputStream(new TeeOutputStream(outputStream, stream) );
        }
        context.proceed();

        final String message = stream == null ? "no entity" : stream.toString();
        Logger.end(context, message);


    }
}

关于java - Jersey 2 日志记录和 gzip,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28091959/

相关文章:

java - Spring Rest 编码的最佳实践是什么?基于 API 和基于名称的 API

java - 无法获得特定国家/地区的本地化响应

c++ - 如何配置 boost 日志,用于旋转和附加写入?

java - 如何在记录 log4j2 消息之前对其进行修改?

python - 简单的调试级 Python 日志记录失败?

java - 在 JdbcCursorItemReader 中使用作业参数作为准备好的语句参数

java logback 自定义记录器名称

java - 如何使用 Java 获取给定证书文件的私钥

java - 从JAVA中的sql查询中获取JSON的所有行

java - 在云环境中,使用哪些技术让客户端上传要处理的文件有什么好方法?