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 JerseyLoggingFilter
【讨论】:
效果很好。我只需要将过滤器注释为@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.<init>(ConstructorInjectorImpl.java:45) [resteasy-jaxrs-3.0.10.Final.jar:]
更新:这似乎是使用内部类的问题,然后当我将构造函数设置为常规类时意外地使构造函数不公开。以上是关于JAX-RS 2 打印 JSON 请求的主要内容,如果未能解决你的问题,请参考以下文章