我想装饰部署在 AWS Lambda 上的 Quarkus RESTEasy Reactive 应用程序中的所有 HTTP 请求处理。
Quarkus Amazon Lambda HTTP 扩展可帮助用户通过 AWS API Gateway 集成在 AWS Lambda 上提供 RESTEasy Reactive 路由。 RESTEasy Reactive 允许用户使用标准的 ContainerResponseFilter 来装饰请求处理。但是,在资源抛出异常的情况下,不会调用响应过滤器。在我的用例中,我需要知道请求何时完成,即使它完成时出现错误。
如果在 servlet 容器上部署 RESTEasy Reactive,则可以使用 servlet 过滤器来装饰 RESTEasy 处理的所有请求。但是,在使用 Quarkus Amazon Lambda HTTP 扩展的部署中,如何装饰所有请求处理?
filters 和 interceptors 来处理修改传入请求或传出响应,以及 ExceptionMapper 接口来捕获异常并将异常映射到正确的 Response
对象。不幸的是,当抛出的异常未由
ContainerResponseFilter
WriterInterceptor
和 ExceptionMapper
都不会被调用
幸运的是,RestEasy Reactive 扩展提供了一个很好的拦截所有请求和响应的方法。示例 REST 端点是:
@Path("/api")
public class ExampleResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "Hello from RESTEasy Reactive";
}
@GET
@Path("/mapped")
public String mapped(@NotBlank @QueryParam("param") String param) {
return "Hello " + param + "!";
}
@GET
@Path("/unmapped")
public String error(@QueryParam("param") String param) {
if (null == param) {
throw new IllegalArgumentException("Param problem");
}
return "Hello " + param + "!";
}
@DELETE
public void noContent() {
System.out.println("Got request");
}
}
hello
方法仅返回带有静态响应正文的 200-OK 响应。第二种方法称为
mapped
接受查询参数。该参数由 Hibernate Validator 进行验证。无效的输入(空白或缺少参数)将引发由ResteasyReactiveViolationExceptionMapper 映射的异常
unmapped
方法会抛出
IllegalArgumentExpceiton
,但没有内置的
ExceptionMapper
来处理该问题。最后说一个特殊情况。
noContent
方法将返回204-No Content,因此没有任何响应正文。还有另外两个提供商:
@Provider
public class SampleResponseFilter implements ContainerResponseFilter {
@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
responseContext.getHeaders().add("X-ResponseFilter", "accepted");
}
}
和
@Provider
public class SampleWriterInterceptor implements WriterInterceptor {
@Override
public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
context.getHeaders().add("X-Interceptor", "intercepted");
context.proceed();
}
}
调用 hello(/api
端点)两个提供者都被调用
spinner@bistromath:~$ curl -v http://localhost:8080/api
* Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /api HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Content-Type: text/plain;charset=UTF-8
< X-Interceptor: intercepted
< X-ResponseFilter: accepted
< content-length: 28
<
* Connection #0 to host localhost left intact
调用 /mapped
端点:两个提供程序都被调用,因为前面提到的
ExceptionMapper
捕获了该异常并映射到
Response
对象。
spinner@bistromath:~$ curl -v http://localhost:8080/api/mapped
* Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /api/mapped HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 400 Bad Request
< Content-Type: text/plain;charset=UTF-8
< validation-exception: true
< X-Interceptor: intercepted
< X-ResponseFilter: accepted
< content-length: 131
<
* Connection #0 to host localhost left intact
调用 /unmapped
没有调用任何已注册的提供程序。
spinner@bistromath:~$ curl -v http://localhost:8080/api/unmapped
* Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /api/unmapped HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 500 Internal Server Error
< content-type: application/json; charset=utf-8
< content-length: 1337
<
response body omitted for brevity
仅使用 /api
调用
DELETE
HTTP 方法
SampleResponseFilter
,因为没有提供响应正文。
spinner@bistromath:~$ curl -v -X DELETE http://localhost:8080/api/silent
* Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> DELETE /api/silent HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 204 No Content
< X-ResponseFilter: accepted
<
* Connection #0 to host localhost left intact
Resteasy Reactive 允许注册自定义拦截器。这些拦截器将在每个请求和响应时被调用。拦截器示例是:
import io.quarkus.vertx.http.runtime.filters.Filters;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
@ApplicationScoped
public class SampleRouteFilter {
void responseFilter(@Observes Filters filters) {
filters.register(rc -> {
rc.response().putHeader("X-RouteFilter", "filtered");
rc.next();
}, 1000);
}
}
从现在开始,每个响应都将包含 X-RouteFilter
标头键,无论该响应是否成功处理,是否由某个休息端点处理(例如,未找到或不允许方法)。例如:
spinner@bistromath:~$ curl -v http://localhost:8080/api/unmapped
* Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /api/unmapped HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 500 Internal Server Error
< X-RouteFilter: filtered
< content-type: application/json; charset=utf-8
< content-length: 1337
<
或:
spinner@bistromath:~$ curl -v -X DELETE http://localhost:8080/api/unmapped
* Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> DELETE /api/unmapped HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 405 Method Not Allowed
< X-RouteFilter: filtered
< X-ResponseFilter: accepted
< content-length: 0
<
* Connection #0 to host localhost left intact
注意:反应式过滤器注册到所有请求和响应。注册为/q/dev-ui
或
/q/health
等。所以在修改任何内容之前请小心。