【问题标题】:JAX-RS 2 print JSON requestJAX-RS 2 打印 JSON 请求
【发布时间】: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());
    }        
}

【问题讨论】:

    标签: java json jakarta-ee logging jax-rs


    【解决方案1】:

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

    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 的处理方式。

    2. 对于响应,在我们的过滤器中,我们需要从响应流中提取数据,但是另外我们需要确保流仍然有数据,因为它还没有为客户端反序列化。要做到这一点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);
            }
        }
    }
    

    另请参阅:

    【讨论】:

    • 效果很好。我只需要将过滤器注释为@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:]
    • 更新:这似乎是使用内部类的问题,然后当我将构造函数设置为常规类时意外地使构造函数不公开。
    猜你喜欢
    • 1970-01-01
    • 2017-09-06
    • 1970-01-01
    • 2016-03-23
    • 1970-01-01
    • 1970-01-01
    • 2015-04-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多