java - 如何调试 Quarkus/SmallRye 客户端请求

标签 java quarkus microprofile

我有一个如下所示的请求:

@Path("/v1")
@RegisterRestClient
@Produces("application/json")
public interface VaultClient {
    @POST
    @Path("/auth/jwt/login")
    @Consumes("application/json")
    String getVaultToken(LoginPayload loginPayload);
}

LoginPayload 它只是一个简单的 POJO:

public class LoginPayload {
    private String jwt;
    final private String role = "some-service";

    public void setJwt(String _jwt) {
        this.jwt = _jwt;
    }
}

当我尝试通过服务调用此端点时:

public String getServiceJwt() {
    String loginJwt = getLoginJwt();
    LoginPayload loginPayload = new LoginPayload();
    loginPayload.setJwt(loginJwt);
    try {
        System.out.println(loginPayload.toString());
        String tokenResponse = vaultClient.getVaultToken(loginPayload);
        System.out.println("##################");
        System.out.println(tokenResponse);
    } catch (Exception e) {
        System.out.println(e);
    }
    return vaultJwt;
}

我得到了 400:

javax.ws.rs.WebApplicationException: Unknown error, status code 400
java.lang.RuntimeException: method call not supported

但是我不知道如何解决这个问题。我可以通过 PostMan/Insomnia 执行相同的请求,它会很好地返回响应。有没有办法让我更好地反射(reflection)传出的响应是什么样的?也许它没有正确地将 POJO 序列化为 JSON?我无从得知。

***更新 我在该请求的另一端放置了一个节点服务器并注销了主体。里面是空的。所以有些东西没有序列化 POJO 并通过 POST 请求发送它。但这并不是一个很好的调试故事。有什么方法可以让我在不在此请求的另一端登录的情况下获得此信息?

另外,为什么 POJO 不序列化?它非常严格地遵循所有文档。

最佳答案

我使用过滤器和拦截器作为异常处理程序来解决这个问题:

打印日志的过滤器:

import lombok.extern.java.Log;
import org.glassfish.jersey.message.MessageUtils;

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.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.WriterInterceptor;
import javax.ws.rs.ext.WriterInterceptorContext;
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.net.URI;
import java.nio.charset.Charset;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Based on org.glassfish.jersey.filter.LoggingFilter
 */
@Log
public class LoggingFilter implements ContainerRequestFilter, ClientRequestFilter, ContainerResponseFilter,
                                      ClientResponseFilter, WriterInterceptor {

    private static final String NOTIFICATION_PREFIX = "* ";

    private static final String REQUEST_PREFIX = "> ";

    private static final String RESPONSE_PREFIX = "< ";

    private static final String ENTITY_LOGGER_PROPERTY = LoggingFilter.class.getName() + ".entityLogger";

    private static final String LOGGING_ID_PROPERTY = LoggingFilter.class.getName() + ".id";

    private static final Comparator<Map.Entry<String, List<String>>> COMPARATOR = (o1, o2) -> o1.getKey().compareToIgnoreCase(o2.getKey());

    private static final int DEFAULT_MAX_ENTITY_SIZE = 8 * 1024;

    private final AtomicLong _id = new AtomicLong(0);

    private final int maxEntitySize;

    public LoggingFilter() {

        this.maxEntitySize = LoggingFilter.DEFAULT_MAX_ENTITY_SIZE;
    }


    private void log(final StringBuilder b) {

        LoggingFilter.log.info(b.toString());
    }

    private StringBuilder prefixId(final StringBuilder b, final long id) {

        b.append(id).append(" ");
        return b;
    }

    private void printRequestLine(final StringBuilder b, final String note, final long id, final String method, final URI uri) {

        this.prefixId(b, id).append(LoggingFilter.NOTIFICATION_PREFIX)
                .append(note)
                .append(" on thread ").append(Thread.currentThread().getName())
                .append("\n");
        this.prefixId(b, id).append(LoggingFilter.REQUEST_PREFIX).append(method).append(" ")
                .append(uri.toASCIIString()).append("\n");
    }

    private void printResponseLine(final StringBuilder b, final String note, final long id, final int status) {

        this.prefixId(b, id).append(LoggingFilter.NOTIFICATION_PREFIX)
                .append(note)
                .append(" on thread ").append(Thread.currentThread().getName()).append("\n");
        this.prefixId(b, id).append(LoggingFilter.RESPONSE_PREFIX)
                .append(status)
                .append("\n");
    }

    private void printPrefixedHeaders(final StringBuilder b,
                                      final long id,
                                      final String prefix,
                                      final MultivaluedMap<String, String> headers) {

        for (final Map.Entry<String, List<String>> headerEntry : this.getSortedHeaders(headers.entrySet())) {
            final List<?> val = headerEntry.getValue();
            final String header = headerEntry.getKey();

            if(val.size() == 1) {
                this.prefixId(b, id).append(prefix).append(header).append(": ").append(val.get(0)).append("\n");
            }
            else {
                final StringBuilder sb = new StringBuilder();
                boolean add = false;
                for (final Object s : val) {
                    if(add) {
                        sb.append(',');
                    }
                    add = true;
                    sb.append(s);
                }
                this.prefixId(b, id).append(prefix).append(header).append(": ").append(sb.toString()).append("\n");
            }
        }
    }

    private Set<Map.Entry<String, List<String>>> getSortedHeaders(final Set<Map.Entry<String, List<String>>> headers) {

        final TreeSet<Map.Entry<String, List<String>>> sortedHeaders = new TreeSet<>(LoggingFilter.COMPARATOR);
        sortedHeaders.addAll(headers);
        return sortedHeaders;
    }

    private InputStream logInboundEntity(final StringBuilder b, InputStream stream, final Charset charset) throws IOException {

        if(!stream.markSupported()) {
            stream = new BufferedInputStream(stream);
        }
        stream.mark(this.maxEntitySize + 1);
        final byte[] entity = new byte[this.maxEntitySize + 1];
        final int entitySize = stream.read(entity);
        b.append(new String(entity, 0, Math.min(entitySize, this.maxEntitySize), charset));
        if(entitySize > this.maxEntitySize) {
            b.append("...more...");
        }
        b.append('\n');
        stream.reset();
        return stream;
    }

    @Override
    public void filter(final ClientRequestContext context) throws IOException {

        final long id = this._id.incrementAndGet();
        context.setProperty(LoggingFilter.LOGGING_ID_PROPERTY, id);

        final StringBuilder b = new StringBuilder();

        this.printRequestLine(b, "Sending client request", id, context.getMethod(), context.getUri());
        this.printPrefixedHeaders(b, id, LoggingFilter.REQUEST_PREFIX, context.getStringHeaders());

        if(context.hasEntity()) {
            final OutputStream stream = new LoggingFilter.LoggingStream(b, context.getEntityStream());
            context.setEntityStream(stream);
            context.setProperty(LoggingFilter.ENTITY_LOGGER_PROPERTY, stream);
            // not calling log(b) here - it will be called by the interceptor
        }
        else {
            this.log(b);
        }
    }

    @Override
    public void filter(final ClientRequestContext requestContext, final ClientResponseContext responseContext)
    throws IOException {

        final Object requestId = requestContext.getProperty(LoggingFilter.LOGGING_ID_PROPERTY);
        final long id = requestId != null ? (Long) requestId : this._id.incrementAndGet();

        final StringBuilder b = new StringBuilder();

        this.printResponseLine(b, "Client response received", id, responseContext.getStatus());
        this.printPrefixedHeaders(b, id, LoggingFilter.RESPONSE_PREFIX, responseContext.getHeaders());

        if(responseContext.hasEntity()) {
            responseContext.setEntityStream(this.logInboundEntity(b, responseContext.getEntityStream(),
                    MessageUtils.getCharset(responseContext.getMediaType())));
        }

        this.log(b);
    }

    @Override
    public void filter(final ContainerRequestContext context) throws IOException {

        final long id = this._id.incrementAndGet();
        context.setProperty(LoggingFilter.LOGGING_ID_PROPERTY, id);

        final StringBuilder b = new StringBuilder();

        this.printRequestLine(b, "Server has received a request", id, context.getMethod(), context.getUriInfo().getRequestUri());
        this.printPrefixedHeaders(b, id, LoggingFilter.REQUEST_PREFIX, context.getHeaders());

        if(context.hasEntity()) {
            context.setEntityStream(
                    this.logInboundEntity(b, context.getEntityStream(), MessageUtils.getCharset(context.getMediaType())));
        }

        this.log(b);
    }

    @Override
    public void filter(final ContainerRequestContext requestContext, final ContainerResponseContext responseContext)
    throws IOException {

        final Object requestId = requestContext.getProperty(LoggingFilter.LOGGING_ID_PROPERTY);
        final long id = requestId != null ? (Long) requestId : this._id.incrementAndGet();

        final StringBuilder b = new StringBuilder();

        this.printResponseLine(b, "Server responded with a response", id, responseContext.getStatus());
        this.printPrefixedHeaders(b, id, LoggingFilter.RESPONSE_PREFIX, responseContext.getStringHeaders());

        if(responseContext.hasEntity()) {
            final OutputStream stream = new LoggingFilter.LoggingStream(b, responseContext.getEntityStream());
            responseContext.setEntityStream(stream);
            requestContext.setProperty(LoggingFilter.ENTITY_LOGGER_PROPERTY, stream);
            // not calling log(b) here - it will be called by the interceptor
        }
        else {
            this.log(b);
        }
    }

    @Override
    public void aroundWriteTo(final WriterInterceptorContext writerInterceptorContext)
    throws IOException, WebApplicationException {

        final LoggingFilter.LoggingStream stream = (LoggingFilter.LoggingStream) writerInterceptorContext.getProperty(LoggingFilter.ENTITY_LOGGER_PROPERTY);
        writerInterceptorContext.proceed();
        if(stream != null) {
            this.log(stream.getStringBuilder(MessageUtils.getCharset(writerInterceptorContext.getMediaType())));
        }
    }

    private class LoggingStream extends FilterOutputStream {

        private final StringBuilder b;

        private final ByteArrayOutputStream baos = new ByteArrayOutputStream();

        LoggingStream(final StringBuilder b, final OutputStream inner) {

            super(inner);

            this.b = b;
        }

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

            this.b.append(new String(entity, 0, Math.min(entity.length, LoggingFilter.this.maxEntitySize), charset));
            if(entity.length > LoggingFilter.this.maxEntitySize) {
                this.b.append("...more...");
            }
            this.b.append('\n');

            return this.b;
        }

        @Override
        public void write(final int i) throws IOException {

            if(this.baos.size() <= LoggingFilter.this.maxEntitySize) {
                this.baos.write(i);
            }
            this.out.write(i);
        }

    }

}

在其余客户端界面中使用过滤器:

@Path("/")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@RegisterRestClient
@RegisterProvider(LoggingFilter.class)
public interface Api {

    @GET
    @Path("/foo/bar")
    FooBar getFoorBar();

现在请求和响应负载已打印在日志中。

之后是处理异常的拦截器:

预选赛:

@InterceptorBinding
@Target({TYPE, METHOD})
@Retention(RUNTIME)
public @interface ExceptionHandler {

}

拦截器:

@Interceptor
@ExceptionHandler
public class ExceptionHandlerInterceptor  {

    @AroundInvoke
    public Object processRequest(final InvocationContext invocationContext) {

        try {
            return invocationContext.proceed();

        }
        catch (final WebApplicationException e) {

            final int status = e.getResponse().getStatus();
            final String errorJson = e.getResponse().readEntity(String.class);

            final Jsonb jsonb = JsonbBuilder.create();

            //"ErrorMessageDTO" is waited when a error occurs
            ErrorMessage errorMessage = jsonb.fromJson(errorJson, ErrorMessage.class);

            //isValid method verifies if the conversion was successful
            if(errorMessage.isValid()) {
                return Response
                        .status(status)
                        .entity(errorMessage)
                        .build();
            }

            errorMessage = ErrorMessage
                    .builder()
                    .statusCode(status)
                    .statusMessage(e.getMessage())
                    .success(false)
                    .build();

            return Response
                    .status(status)
                    .entity(errorMessage)
                    .build();
        }
        catch (final Exception e) {

            e.printStackTrace();

            return Response
                    .status(Status.INTERNAL_SERVER_ERROR)
                    .entity(ErrorMessage
                            .builder()
                            .statusCode(Status.INTERNAL_SERVER_ERROR.getStatusCode())
                            .statusMessage(e.getMessage())
                            .success(false)
                            .build())
                    .build();
        }
    }

}

使用拦截器:

@Path("/resource")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@ExceptionHandler
@Traced
@Log
public class ResourceEndpoint {

    @Inject
    @RestClient
    Api api;

    @GET
    @Path("/latest")
    public Response getFooBarLatest() {

        return Response.ok(this.api.getFoorBar()).build();
    }

ErrorMessage bean:

@RegisterForReflection
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Data
@Builder
public class ErrorMessage  {

    @JsonbProperty("status_message")
    private String statusMessage;

    @JsonbProperty("status_code")
    private Integer statusCode;

    @JsonbProperty("success")
    private boolean success = true;

    @JsonbTransient
    public boolean isValid() {

        return this.statusMessage != null && !this.statusMessage.isEmpty() && this.statusCode != null;
    }

}

PS:使用Lombok!

关于java - 如何调试 Quarkus/SmallRye 客户端请求,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55550652/

相关文章:

java - 通过同步方法访问的私有(private)类参数是否被视为 'synchronized object' ?

java - 当我将quarkus适配到一个简单的spring boot项目时出现“No Spring WebApplicationInitializer types detected on classpath”?

java - java中设置数组的方法

java - 使用 JSON-B/Yasson 将 JSON 反序列化为多态 POJO

java - 使用 aws lambda native 读取文本文件

rest - Quarkus 中的 MicroProfile Rest 客户端与 Vertx 客户端

maven - 如何在 maven 构建期间将外部依赖项复制到 Open Liberty

java - 如何在 REST API 中将驼峰式大小写转换为带下划线的小写?

java - Keymap、InputMap、ActionMap、KeyListener — 选择重载

java - GlassFish 禁止 403 错误页面