WebExceptionHandler:如何使用Spring Webflux编写响应体

24

我想通过添加WebExceptionHandler来处理我的API的异常。我可以更改状态码,但是当我想要更改响应体(例如添加异常消息或自定义对象)时,我陷入困境。

有人有例子吗?

我如何添加我的WebExceptionHandler:

HttpHandler httpHandler = WebHttpHandlerBuilder.webHandler(toHttpHandler(routerFunction))
  .prependExceptionHandler((serverWebExchange, exception) -> {

      exchange.getResponse().setStatusCode(myStatusGivenTheException);
      exchange.getResponse().writeAndFlushWith(??)
      return Mono.empty();

  }).build();
5个回答

30

WebExceptionHandler较为低级,因此您必须直接处理请求/响应交换。

请注意:

  • Mono<Void>返回类型应该表示响应处理结束,因此它应连接到写入响应的Publisher
  • 在这个层面上,您将直接处理数据缓冲区(不支持序列化)

您的WebExceptionHandler可能如下所示:

(serverWebExchange, exception) -> {

  exchange.getResponse().setStatusCode(myStatusGivenTheException);
  byte[] bytes = "Some text".getBytes(StandardCharsets.UTF_8);
  DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
  return exchange.getResponse().writeWith(Flux.just(buffer));
}

当我尝试运行这个例子时,IDE会提示Flux.just(buffer)与writeAndFlushWith参数不兼容。它期望的是:Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body); 这个签名改变了吗? - Ellis
1
我在我的回答中修正了一个拼写错误。谢谢! - Brian Clozel

5

对于序列化对象,我使用以下方式:

 Mono<DataBuffer> db = commonsException.getErrorsResponse().map(errorsResponse -> {

     ObjectMapper objectMapper = new ObjectMapper();
     try {
         return objectMapper.writeValueAsBytes(errorsResponse);
     } catch (JsonProcessingException e) {
          return e.getMessage().getBytes();
     }
}).map(s -> exchange.getResponse().bufferFactory().wrap(s));

exchange.getResponse().getHeaders().add("Content-Type", "application/json");
exchange.getResponse().setStatusCode(commonsException.getHttpStatus());
return exchange.getResponse().writeWith(db);

5

ServerResponse有一个writeTo方法,可以用于将您的正文写入到ServerExchange中(Spring框架是这样做的)。唯一的问题是您必须提供Context作为第二个参数,因此我只是从框架实现中复制了HandlerStrategiesResponseContext

请确保您至少使用Spring Boot 2.0.0 M2版本,在此版本之前,使用RouterFunctionsWebExceptionHandler未注册。

import org.springframework.http.HttpStatus
import org.springframework.http.HttpStatus.*
import org.springframework.http.codec.HttpMessageWriter
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.server.HandlerStrategies
import org.springframework.web.reactive.function.server.ServerResponse
import org.springframework.web.reactive.result.view.ViewResolver
import org.springframework.web.server.ServerWebExchange
import org.springframework.web.server.WebExceptionHandler


@Component
class GlobalErrorHandler() : WebExceptionHandler {

    override fun handle(exchange: ServerWebExchange, ex: Throwable): Mono<Void> =
        handle(ex)
                .flatMap {
                    it.writeTo(exchange, HandlerStrategiesResponseContext(HandlerStrategies.withDefaults()))
                }
                .flatMap {
                    Mono.empty<Void>()
                }

    fun handle(throwable: Throwable): Mono<ServerResponse> {

        return when (throwable) {
            is EntityNotFoundException -> {
                createResponse(NOT_FOUND, "NOT_FOUND", "Entity not found, details: ${throwable.message}")
            }
            else -> {
                createResponse(INTERNAL_SERVER_ERROR, "GENERIC_ERROR", "Unhandled exception")
            }
        }
    }

    fun createResponse(httpStatus: HttpStatus, code: String, message: String): Mono<ServerResponse> =
        ServerResponse.status(httpStatus).syncBody(ApiError(code, message))
}

private class HandlerStrategiesResponseContext(val strategies: HandlerStrategies) : ServerResponse.Context {

    override fun messageWriters(): List<HttpMessageWriter<*>> {
        return this.strategies.messageWriters()
    }

    override fun viewResolvers(): List<ViewResolver> {
        return this.strategies.viewResolvers()
    }
}

2

如果有人正在寻找编写 JSON 响应体的方法,这里有一个 Kotlin 代码示例:

fun writeBodyJson(body: Any, exchange: ServerWebExchange) =
    exchange.response.writeWith(
        Jackson2JsonEncoder().encode(
            Mono.just(body),
            exchange.response.bufferFactory(),
            ResolvableType.forInstance(body),
            MediaType.APPLICATION_JSON_UTF8,
            Hints.from(Hints.LOG_PREFIX_HINT, exchange.logPrefix)
        )
    )

不完全确定这是正确的方法,希望能得到一些意见。


0

我需要呈现一个视图,但是在异常处理程序中找不到方法来实现这一点(只找到了使用控制器的示例,这将需要不必要的重定向),因此我使用了ServerResponseResultHandler从处理程序中呈现它。这提供了所需的Mono<Void>响应。

/** Used when creating the {@link HandlerResult}
 *  Taken from https://github.com/spring-projects/spring-framework/blob/5.3.x/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/support/HandlerFunctionAdapter.java
 */
private static final MethodParameter RETURN_TYPE;

static {
    try {
        Method method = ServerAccessDeniedHandler.class.getMethod("handle", ServerWebExchange.class, AccessDeniedException.class);
        RETURN_TYPE = new MethodParameter(method, -1);
    }
    catch (NoSuchMethodException ex) {
        throw new IllegalStateException(ex);
    }
}

@Autowired
private ServerResponseResultHandler responseHandler;

public class CustomAccessDeniedHandler implements ServerAccessDeniedHandler {
    @Override
    public Mono<Void> handle(ServerWebExchange exchange, AccessDeniedException ex) {
        return ReactiveSecurityContextHolder.getContext()
            .map(SecurityContext::getAuthentication)
            .filter(auth -> auth != null && auth.isAuthenticated())
            .switchIfEmpty(Mono.empty())
            .flatMap(auth -> ServerResponse
                .status(HttpStatus.FORBIDDEN)
                // Give name of view and model attributes it requires
                .render("unauthorized", Map.of("varName", varValue))
                .flatMap(response -> responseHandler.handleResult(exchange,
                    new HandlerResult(this, response, RETURN_TYPE)))
            );
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接