如何修复 Vert.x 中的 'Request has already been read' 错误

How to fix 'Request has already been read' error in Vert.x

我正在设置 api 网关。我想在请求 BE 服务之前验证授权令牌。我得到 IllegalStateException:请求已被读取。请帮忙。

我把测试项目代码上传到GitHub。 https://github.com/EddyPan/test-demo

router.route().path("/user/admin").method(HttpMethod.POST)
        .handler(rct -> {

            HttpServerRequest request = rct.request().setExpectMultipart(true);

            MultiMap headers = request.headers();

            JsonObject param = new JsonObject().put("requestUrl", "http://localhost:18080/authorize")
                    .put("httpMethod", "POST");

            webClient.postAbs("http://localhost:18080/authorize")
                    .timeout(6000)
                    .putHeader("Content-Type", "application/json")
                    .putHeader("Authorization", headers.get("Authorization"))
                    .as(BodyCodec.jsonObject())
                    .sendJsonObject(param, ar -> authHandler(rct, ar));

        });

异常:

java.lang.IllegalStateException: Request has already been read
    at io.vertx.core.http.impl.HttpServerRequestImpl.checkEnded(HttpServerRequestImpl.java:599)
    at io.vertx.core.http.impl.HttpServerRequestImpl.setExpectMultipart(HttpServerRequestImpl.java:431)
    at io.vertx.ext.web.impl.HttpServerRequestWrapper.setExpectMultipart(HttpServerRequestWrapper.java:208)
    at com.demo.HttpServerVerticle.lambda$start(HttpServerVerticle.java:62)
    at io.vertx.ext.web.impl.RouteImpl.handleContext(RouteImpl.java:232)
    at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:121)
    at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:134)
    at com.demo.HttpServerVerticle.authHandler(HttpServerVerticle.java:132)
    at com.demo.HttpServerVerticle.lambda$null[=13=](HttpServerVerticle.java:53)
    at io.vertx.ext.web.client.impl.HttpContext.handleDispatchResponse(HttpContext.java:285)
    at io.vertx.ext.web.client.impl.HttpContext.execute(HttpContext.java:272)
    at io.vertx.ext.web.client.impl.HttpContext.next(HttpContext.java:250)
    at io.vertx.ext.web.client.impl.predicate.PredicateInterceptor.handle(PredicateInterceptor.java:69)
    at io.vertx.ext.web.client.impl.predicate.PredicateInterceptor.handle(PredicateInterceptor.java:32)
    at io.vertx.ext.web.client.impl.HttpContext.next(HttpContext.java:247)
    at io.vertx.ext.web.client.impl.HttpContext.fire(HttpContext.java:257)
    at io.vertx.ext.web.client.impl.HttpContext.dispatchResponse(HttpContext.java:218)
    at io.vertx.ext.web.client.impl.HttpContext.lambda$null(HttpContext.java:341)
    at io.vertx.core.impl.ContextImpl.executeTask(ContextImpl.java:320)
    at io.vertx.core.impl.EventLoopContext.lambda$executeAsync[=13=](EventLoopContext.java:38)
    at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:163)
    at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:404)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:495)
    at io.netty.util.concurrent.SingleThreadEventExecutor.run(SingleThreadEventExecutor.java:905)
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
    at java.lang.Thread.run(Thread.java:748)

我解决了这个问题。在调用 auth 之前 api 我暂停原始请求,并在授权完成后恢复缓冲区处理。

router.route().path("/user/admin").method(HttpMethod.POST)
        .handler(rct -> {

            HttpServerRequest request = rct.request().setExpectMultipart(true);

            request.pause(); // Here is to pasue the origin request.

            MultiMap headers = request.headers();

            JsonObject param = new JsonObject().put("requestUrl", "http://localhost:18080/authorize")
                    .put("httpMethod", "POST");

            webClient.postAbs("http://localhost:18080/authorize")
                    .timeout(6000)
                    .putHeader("Content-Type", "application/json")
                    .putHeader("Authorization", headers.get("Authorization"))
                    .as(BodyCodec.jsonObject())
                    .sendJsonObject(param, ar -> authHandler(rct, ar));

        });

您可能会遇到此错误的两个原因:

A) 在 BodyHandler

之前异步 Handler

如 Vert.X documentation 中所述 BodyHandler:

The usage of this handler requires that it is installed as soon as possible in the router since it needs to install handlers to consume the HTTP request body and this must be done before executing any async call.

修复 1:更改顺序:

router.post(endpoint)
   .consumes(contentType)
   .handler(bodyHandler)  <<<<<<<<< first this
   .handler(authHandler) <<<<<<<<  then this async handler;

修复 2:pause/resume 请求交付:

参见Vert.X documentation:

If an async call is required before, the HttpServerRequest should be paused and then resumed so that the request events are not delivered until the body handler is ready to process them.

router.post(endpoint)
   .consumes(contentType)
   .handler(authHandler)
   .handler(bodyHandler);
BodyHandler implements Handler<RoutingContext> {
  @Override
  public void handle(final RoutingContext ctx) {

     // pause request delivery
     ctx.request().pause();

     asyncCall(r -> {
        // e.g. check authorization or database here

        // resume request delivery
        ctx.request.resume();

        // call the next handler
        ctx.next();
     }
  }
}

B) 多次 request.body() 调用

假设您使用 Vert.X BodyHandler 并在其后安装自定义处理程序:

router.post(endpoint)
   .consumes(contentType)
   .handler(BodyHandler.create())
   .handler(customHandler);

您的自定义处理程序 不能调用 request.body()! 否则您会得到

403: body has already been read

修复:使用 ctx.getBody()

使用 ctx.getBody[/asJson/asString]() 获取已被 BodyHandler 读取的正文:

CustomHandler implements Handler<RoutingContext> {
    @Override
    public void handleAuthorizedRequest(RoutingContext ctx) {
        final var body = ctx.getBodyAsJson();

        // instead of: ctx.request().body();
        ...
    }
}