JAX-RS 2 распечатать запрос JSON

Я хотел бы иметь возможность печатать JAX-RS 2 JSON с помощью запроса, независимо от фактической реализации на моем сервере приложений.

Я пробовал предлагаемые решения для SO, но все они включают двоичные файлы из реальной реализации (например, Джерси и т.д.), и мне разрешено использовать только 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, а затем переписать эти байты после этого. Вот как справляется Джерси LoggingFilter.

  • Для ответа в нашем фильтре нам нужно извлечь данные из потока ответов, но мы также должны убедиться, что у потока все еще есть данные, поскольку он еще не был десериализован для клиента. Для этого mark() и reset() поток, предполагающий, что маркировка поддерживается. Если нет, оберните его в BufferedOutputStream. Опять же, именно так Джерси LoggingFilter справляется с этим.

Ниже приведена полная простая реализация. Большая часть из них берется прямо с Джерси LoggingFilter, хотя она убирается только для вашего варианта использования. Джерси LoggingFilter записывает много другой информации, кроме только сущности. Одна вещь, которую я оставил, - это проверка кодировки. Я просто использовал жестко кодированный UTF-8, поскольку класс MessageUtil, используемый Джерси, специфичен для Джерси. Если вы хотите сделать фильтр более универсальным для других кодировок, вам может понадобиться изучить это.

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

См. также: