如何在 Spring Cloud Gateway 的 filter 中返回 JSON

Last Modified: 2023/12/10

概述

在 gateway GlobalFilter 或者 GatewayFilter 中往往可能需要拦截请求并提前返回一个 JSON 给客户端,就拿鉴权来说,如果请求未携带 token 或者携带的 token 有误,那么就要拦截请求并给客户端返回 401,并根据业务需要返回一段 JSON 给客户端。本文将讲述在 filter 中如何返回 JSON 给客户端。

具体实现

一个 Http 响应通常涉及以下三个方面内容:

  • 状态码
  • 响应头
  • 响应体

只要将这三部分搞定了,我们就能够完成任何响应,至于 JSON 响应只不过是 Content-Type 这个响应头的值必须是 application/json 而已。

下面我们就以 AuthFilter 为例,假设请求没有携带 token,我们需要给客户端响应 401,响应内容为:

{ "msg": "token required" }

AuthFilter 需要实现的如下:

@Component
public class AuthFilter implements GlobalFilter {

  @Override
  public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    // 设置状态码
    // 设置响应头
    // 设置响应体
  }
}

1、设置状态码

不同的状态码代表不同的含义,一般用 401(unauthorized)来告诉客户端的凭证不正确或者缺失凭证。这个过程很简单,我们只需要从 exchange 中获取 response 对象,然后给 response 设置状态码即可:

ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);

2、设置响应头

response.getHeaders().add("Content-Type", MediaType.APPLICATION_JSON_VALUE);

3、设置响应体

ObjectMapper mapper = new ObjectMapper();
Map<String, Object> data = new HashMap<>();
data.put("msg", "token required");
        
String body = "";
try {
    body = mapper.writeValueAsString(data);
} catch (JsonProcessingException e) {
    e.printStackTrace();
}
DataBuffer buf = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(buf));

抽象一个方法来处理 JSON 响应

上面三步都很简单,但是如果组合在一起代码量却不少,这些都是样板化的代码,所以很有必要抽象一个方法来干这个事情,我们可以将状态码和响应数据作为参数传进来就可以了。

public static Mono<Void> jsonResponse(ServerHttpResponse resp, HttpStatus status, Object data) {
    String body;
    try {
        body = new ObjectMapper().writeValueAsString(data);
    } catch (JsonProcessingException e) {
        throw new IllegalArgumentException(e);
    }
    resp.setStatusCode(status);
    resp.getHeaders().add("Content-Type", MediaType.APPLICATION_JSON_VALUE);
    DataBuffer buffer = resp.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));
    return resp.writeWith(Flux.just(buffer));
}

有了这个方法后,我们再来实现 AuthFilter 就很简单了:

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    Map<String, Object> data = new HashMap<>();
    data.put("msg", "token required");
    return jsonResponse(exchange.getResponse(), HttpStatus.UNAUTHORIZED, data);
}

当你把代码 copy 到 idea 中之后,发现 writeValueAsString 上面有个扎眼的黄色,鼠标移上去还有一个提示“Inappropriate blocking method call ”,啥意思呢,难道将一个对象序列化为 json 字符串还会阻塞吗?

~~这个我会在后面的文章中讲解,暂时就留个坑在这吧。~~先提前说明一下,这个黄色并无实际影响,可以放心使用。另外如果要说这个实现有瑕疵,那就是每次都创建一个 ObjectMapper,大家可以将 ObjectMapper 传进来,或者通过容器获取 ObjectMapper。

欲知详情,可以参考:idea: Inappropriate blocking method call

有问题吗?点此反馈!

温馨提示:反馈需要登录