JAX-RS 2 打印 JSON 请求

Posted

技术标签:

【中文标题】JAX-RS 2 打印 JSON 请求【英文标题】:JAX-RS 2 print JSON request 【发布时间】:2016-08-09 03:08:15 【问题描述】:

我希望能够从请求中打印 JAX-RS 2 JSON 有效负载,而不管我的应用程序服务器上的实际实现如何。

我在 SO 上尝试过建议的解决方案,但都包括来自实际实现的二进制文件(如 Jersey 和类似的),并且我只能在我的应用程序中使用 javaee-api v 7.0。

我尝试在我的客户端上实现 ClientRequestFilter 和 ClientResponseFilter,但它们不包含序列化实体。

这是一个客户端示例:

WebTarget target = ClientBuilder.newClient().register(MyLoggingFilter.class).target("http://localhost:8080/loggingtest/resources/accounts");
Account acc = target.request().accept(MediaType.APPLICATION_JSON).get(account.Account.class);

这是 MyLoggingFilter 的实现:

@Provider
public class MyLoggingFilter implements ClientRequestFilter, ClientResponseFilter 

    private static final Logger LOGGER = Logger.getLogger(MyLoggingFilter.class.getName());

    @Override
    public void filter(ClientRequestContext requestContext) throws IOException 

        LOGGER.log(Level.SEVERE, "Request method: 0", requestContext.getMethod());

    

    @Override
    public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException 
        LOGGER.log(Level.SEVERE, "Response status: 0", responseContext.getStatus());
            

【问题讨论】:

【参考方案1】:

因此,在尝试实现此功能时需要考虑几件事

    对于请求实体,您将希望由框架处理序列化,这意味着您想要做类似的事情

    @Override
    public void filter(ClientRequestContext requestContext) 
        Object entity = requestContext.getEntity();
        String serialized = serializeEntity(entity);
        log(serialized);
    

    在这里你自己序列化它,也许使用 Jackson ObjectMapper 或其他东西。您可以这样做,但它可以处理的类型有点有限。如果您让对象按照框架已经处理的方式进行序列化,那么框架将能够支持更多的类型,而不仅仅是 JSON。

    为了让框架处理序列化,并且仍然能够获取序列化数据,我们需要使用WriterInterceptor。我们可以做的是将实体输出流设置为ByteArrayOutputStream,然后让框架将请求对象序列化为我们的ByteArrayOutputStream,然后在后面记录这些字节。这就是 Jersey LoggingFilter 的处理方式。

    对于响应,在我们的过滤器中,我们需要从响应流中提取数据,但是另外我们需要确保流仍然有数据,因为它还没有为客户端反序列化。要做到这一点mark()reset() 流,假设支持标记。如果没有,请将其包装在 BufferedOutputStream 中。同样,这就是 Jersey LoggingFilter 的处理方式。

下面是一个完整的简单实现。其中大部分是直接取自 Jersey LoggingFilter,尽管它只是为了您的用例而被剥离。 Jersey LoggingFilter 记录了许多其他信息,除了实体。我遗漏的一件事是检查字符集。我只是使用了硬编码的 UTF-8,因为 Jersey 使用的 MessageUtil 类是 Jersey 特定的。如果您想让过滤器对其他字符集更通用,您可能需要考虑修复它。

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.logging.Logger;
import javax.annotation.Priority;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.client.ClientRequestContext;
import javax.ws.rs.client.ClientRequestFilter;
import javax.ws.rs.client.ClientResponseContext;
import javax.ws.rs.client.ClientResponseFilter;
import javax.ws.rs.ext.WriterInterceptor;
import javax.ws.rs.ext.WriterInterceptorContext;

@Priority(Integer.MIN_VALUE)
public class EntityLoggingFilter implements ClientRequestFilter, ClientResponseFilter, WriterInterceptor 

    private static final Logger logger = Logger.getLogger(EntityLoggingFilter.class.getName());
    private static final String ENTITY_STREAM_PROPERTY = "EntityLoggingFilter.entityStream";
    private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
    private final int maxEntitySize = 1024 * 8;

    private void log(StringBuilder sb) 
        logger.info(sb.toString());
    

    private InputStream logInboundEntity(final StringBuilder b, InputStream stream, final Charset charset) throws IOException 
        if (!stream.markSupported()) 
            stream = new BufferedInputStream(stream);
        
        stream.mark(maxEntitySize + 1);
        final byte[] entity = new byte[maxEntitySize + 1];
        final int entitySize = stream.read(entity);
        b.append(new String(entity, 0, Math.min(entitySize, maxEntitySize), charset));
        if (entitySize > maxEntitySize) 
            b.append("...more...");
        
        b.append('\n');
        stream.reset();
        return stream;
    

    @Override
    public void filter(ClientRequestContext requestContext) throws IOException 
        if (requestContext.hasEntity()) 
            final OutputStream stream = new LoggingStream(requestContext.getEntityStream());
            requestContext.setEntityStream(stream);
            requestContext.setProperty(ENTITY_STREAM_PROPERTY, stream);
        
    

    @Override
    public void filter(ClientRequestContext requestContext,
            ClientResponseContext responseContext) throws IOException 
        final StringBuilder sb = new StringBuilder();
        if (responseContext.hasEntity()) 
            responseContext.setEntityStream(logInboundEntity(sb, responseContext.getEntityStream(),
                    DEFAULT_CHARSET));
            log(sb);
        

    

    @Override
    public void aroundWriteTo(WriterInterceptorContext context)
            throws IOException, WebApplicationException 
        final LoggingStream stream = (LoggingStream) context.getProperty(ENTITY_STREAM_PROPERTY);
        context.proceed();
        if (stream != null) 
            log(stream.getStringBuilder(DEFAULT_CHARSET));
        
    

    private class LoggingStream extends FilterOutputStream 

        private final StringBuilder sb = new StringBuilder();
        private final ByteArrayOutputStream baos = new ByteArrayOutputStream();

        LoggingStream(OutputStream out) 
            super(out);
        

        StringBuilder getStringBuilder(Charset charset) 
            // write entity to the builder
            final byte[] entity = baos.toByteArray();

            sb.append(new String(entity, 0, entity.length, charset));
            if (entity.length > maxEntitySize) 
                sb.append("...more...");
            
            sb.append('\n');

            return sb;
        

        @Override
        public void write(final int i) throws IOException 
            if (baos.size() <= maxEntitySize) 
                baos.write(i);
            
            out.write(i);
        
    

另请参阅:

Source code for Jersey LoggingFilter

【讨论】:

效果很好。我只需要将过滤器注释为@Provider 并将其注册到我的客户端,它会从 POSTed Entity 记录 JSON。顺便说一句,我正在使用 JAX-B 来序列化实体。感谢您的宝贵时间,请接受我的支持和接受。 非常适合调试! 我试图创建一个子类来覆盖 maxEntitySize,因为客户端返回了大量数据。但是,当我注册子类而不是EntityLoggingFilter 时,我在Response response = target.request(MediaType.APPLICATION_JSON).post(Entity.json(path)); 上得到一个NullPointerException。传递子类不起作用有什么明显的原因吗?它甚至没有调用我的子类构造函数,并且 NPE 发生在 at org.jboss.resteasy.core.ConstructorInjectorImpl.&lt;init&gt;(ConstructorInjectorImpl.java:45) [resteasy-jaxrs-3.0.10.Final.jar:] 更新:这似乎是使用内部类的问题,然后当我将构造函数设置为常规类时意外地使构造函数不公开。

以上是关于JAX-RS 2 打印 JSON 请求的主要内容,如果未能解决你的问题,请参考以下文章

JAX-RS @CookieParam值从请求变为另一个

使用 JAX-RS Jersey 2.2 获取带有 Content-Type 和 Accept 标头的请求

Jax-RS 和 Xmlhttp 通信

使用 JAX-RS 将 JSON 查询参数转换为对象

JAX-RS 处理具有相同路径的多个请求

如何在 JAX-RS 客户端中记录请求正文