简体   繁体   English

如何处理 ContainerRequestFilter 中的阻塞操作 Quarkus/Vert.x

[英]How should Blocking Operations in a ContainerRequestFilter be handled Quarkus/Vert.x

Background :背景

We are implementing a signed request mechanism for communication between services.我们正在实现服务之间通信的签名请求机制。 Part of that process generates a digest on the contents of the request body.该过程的一部分会生成关于请求正文内容的摘要。 To validate the body on receipt, we re-generate the digest at the receiver and compare.为了在接收时验证正文,我们在接收器处重新生成摘要并进行比较。 It's pretty straight-forward stuff.这是非常直接的东西。

@PreMatching
@Priority(Priorities.ENTITY_CODER)
public class DigestValidationFilter implements ContainerRequestFilter {

   private final DigestGenerator generator;

   @Inject
   public DigestValidationFilter(DigestGenerator generator) {
      this.generator = generator;
   }

   @Override
   public void filter(ContainerRequestContext context) throws IOException {
      if (context.hasEntity() && context.getHeaderString(Headers.DIGEST) != null) {
         String digest = context.getHeaderString(Headers.DIGEST);

         ByteArrayOutputStream body = new ByteArrayOutputStream();
         try (InputStream stream = context.getEntityStream()) {
             stream.transferTo(body); // <-- This is line 36 from the provided stack-trace
         }

         String algorithm = digest.split("=", 2)[0];
         try {
             String calculated = generator.generate(algorithm, body.toByteArray());

             if (digest.equals(calculated)) {
                 context.setEntityStream(new ByteArrayInputStream(body.toByteArray()));
             } else {
                 throw new InvalidDigestException("Calculated digest does not match supplied digest. Request body may have been tampered with.");
             }
         } catch (NoSuchAlgorithmException e) {
             throw new InvalidDigestException(String.format("Unsupported hash algorithm: %s", algorithm), e);
         }
      }
   }
}

The above filter is made available to services as a java-lib.上述过滤器作为 java-lib 提供给服务。 We also supply a set of RequestFilters that can be used with various Http clients, ie, okhttp3, apache-httpclient, etc. These clients only generate digests when the body is "repeatable", ie, not streaming.我们还提供了一组 RequestFilters,可用于各种 Http 客户端,即 okhttp3、apache-httpclient 等。这些客户端仅在正文“可重复”时生成摘要,即不流式传输。

The Issue :问题

In Jersey services and Spring Boot services, we do not run into issues.在 Jersey 服务和 Spring 引导服务中,我们没有遇到问题。 However, when we use Quarkus, we receive the following stack-trace:但是,当我们使用 Quarkus 时,我们会收到以下堆栈跟踪:

2022-09-02 15:18:25 5.13.0 ERROR A blocking operation occurred on the IO thread. This likely means you need to use the @io.smallrye.common.annotation.Blocking annotation on the Resource method, class or javax.ws.rs.core.Application class. 
2022-09-02 15:18:25 5.13.0 ERROR HTTP Request to /v1/policy/internal/policies/72575947-45ac-4358-bc40-b5c7ffbd3f35/target-resources failed, error id: c79aa557-c742-43d7-93d9-0e362b2dff79-1 
org.jboss.resteasy.reactive.common.core.BlockingNotAllowedException: Attempting a blocking read on io thread
    at org.jboss.resteasy.reactive.server.vertx.VertxInputStream$VertxBlockingInput.readBlocking(VertxInputStream.java:242)
    at org.jboss.resteasy.reactive.server.vertx.VertxInputStream.readIntoBuffer(VertxInputStream.java:120)
    at org.jboss.resteasy.reactive.server.vertx.VertxInputStream.read(VertxInputStream.java:82)
    at java.base/java.io.InputStream.transferTo(InputStream.java:782)
    at com.###.ciam.jaxrs.DigestValidationFilter.filter(DigestValidationFilter.java:36)
    at org.jboss.resteasy.reactive.server.handlers.ResourceRequestFilterHandler.handle(ResourceRequestFilterHandler.java:47)
    at org.jboss.resteasy.reactive.server.handlers.ResourceRequestFilterHandler.handle(ResourceRequestFilterHandler.java:8)
    at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:141)
    at org.jboss.resteasy.reactive.server.handlers.RestInitialHandler.beginProcessing(RestInitialHandler.java:49)
    at org.jboss.resteasy.reactive.server.vertx.ResteasyReactiveVertxHandler.handle(ResteasyReactiveVertxHandler.java:17)
    at org.jboss.resteasy.reactive.server.vertx.ResteasyReactiveVertxHandler.handle(ResteasyReactiveVertxHandler.java:7)
    at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1212)
    at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:163)
    at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:141)
    at io.quarkus.vertx.http.runtime.StaticResourcesRecorder$2.handle(StaticResourcesRecorder.java:67) ... elided ...

I completely understand why Vert.x would like to prevent long-running I/O operations on the request processing threads.我完全理解为什么 Vert.x 想要阻止请求处理线程上长时间运行的 I/O 操作。 That said, the advice provided in the exception only accounts for I/O operations at the end of the request processing, ie, it assumes the I/O is happening in the endpoint.也就是说,异常中提供的建议仅考虑请求处理结束时的 I/O 操作,即它假设 I/O 发生在端点中。 Although we do control the filter code, it is in an external library, making it almost like a 3rd party library.尽管我们确实控制了过滤器代码,但它位于外部库中,使其几乎就像一个第三方库。

My Question :我的问题

What is the right way to handle this?处理这个问题的正确方法是什么?

I've been scouring documentation, but haven't stumbled on the answer yet (or haven't recognized the answer).我一直在搜索文档,但还没有偶然发现答案(或者没有认识到答案)。 Is there a set of recommended docs I should review?我应该查看一组推荐的文档吗?

https://quarkus.io/guides/resteasy-reactive#request-or-response-filters https://quarkus.io/guides/resteasy-reactive#request-or-response-filters

https://smallrye.io/smallrye-mutiny/1.7.0/guides/framework-integration/ https://smallrye.io/smallrye-mutiny/1.7.0/guides/framework-integration/

    @RequestScoped
    class Filter(
        private val vertx: Vertx
    ) {
    
    //  you can run blocking code on mutiny's Infrastructure defaultWorkerPool
        @ServerRequestFilter
        fun filter(requestContext: ContainerRequestContext): Uni<RestResponse<*>> {
            return Uni.createFrom().item { work() }
                .map<RestResponse<*>> { null }
                .runSubscriptionOn(Infrastructure.getDefaultWorkerPool())
        }
    // or use vertx.executeBlocking api
        @ServerRequestFilter
        fun filter(requestContext: ContainerRequestContext): Uni<RestResponse<*>> {
            return vertx.executeBlocking(
                Uni.createFrom().item { work() }
                    .map { null }
            )
        }
    
    
    
        private fun work(){
            Log.info("filter")
            Thread.sleep(3000)
        }
    }

In the end, the advice in the exception lead me to simply annotating a delegate ContainerRequestFilter:最后,异常中的建议让我简单地注释了一个委托 ContainerRequestFilter:

public class DigestValidationFilterBlocking implements ContainerRequestFilter {

    private final DigestValidationFilter delegate;

    public DigestValidationFilterBlocking(DigestValidationFilter delegate) {
        this.delegate = delegate;
    }

    @Blocking // <-- This annotation allowed Vert.x to accept the I/O operation
    @Override
    public void filter(ContainerRequestContext context) throws IOException {
        delegate.filter(context);
    }
}

I had the same problem.我有同样的问题。 You can try using this in your @ServerRequestFilter :您可以尝试在@ServerRequestFilter中使用它:

@Context
HttpServerRequest request;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM