关于云计算:Spring-Cloud-Gateway过滤器精确控制异常返回实战完全定制返回body

3次阅读

共计 8600 个字符,预计需要花费 22 分钟才能阅读完成。

欢送拜访我的 GitHub

这里分类和汇总了欣宸的全副原创 (含配套源码):https://github.com/zq2599/blog_demos

本篇概览

  • Spring Cloud Gateway 利用中,解决申请时若产生异样未被捕捉,申请方收到的响应是零碎默认的内容,无奈满足理论业务需要
  • 因而,从前一篇文章《Spring Cloud Gateway 过滤器准确管制异样返回 (剖析篇)》开始,咱们深入分析了 Spring Cloud Gateway 的相干源码,理解到全局异样的解决细节,而后,通过前文《Spring Cloud Gateway 过滤器准确管制异样返回 (实战,管制 http 返回码和 message 字段)》的实战,咱们曾经能随便设置 http 返回码,以及 body 中的 message 字段,也就是管制下图两个红框中的内容:

  • 正如上图所示,异样产生时零碎固定返回 8 个字段,这就有些不够灵便了,在一些对格局和内容有严格要求的场景下,咱们须要可能齐全管制返回码和返回 body 的内容,如下所示,只返回三个字段,每个字段都是齐全为业务服务的:
{
    # 这是有具体业务含意的返回码
    "code": "010020003",

    # 这是能准确形容谬误起因的文本信息
    "message": "请确保申请参数中的 user-id 字段是无效的",
    
    # 这是惯例的业务数据,产生异样时该字段为空
    "data": null
}
  • 明天咱们的指标就是通过编码定制异样产生时的返回信息,具体内容就是上述 JSON 数据:只有 code、message、data 三个字段

源码下载

  • 本篇实战中的残缺源码可在 GitHub 下载到,地址和链接信息如下表所示 (https://github.com/zq2599/blo…):
名称 链接 备注
我的项目主页 https://github.com/zq2599/blo… 该我的项目在 GitHub 上的主页
git 仓库地址 (https) https://github.com/zq2599/blo… 该我的项目源码的仓库地址,https 协定
git 仓库地址 (ssh) git@github.com:zq2599/blog_demos.git 该我的项目源码的仓库地址,ssh 协定
  • 这个 git 我的项目中有多个文件夹,本篇的源码在 <font color=”blue”>spring-cloud-tutorials</font> 文件夹下,如下图红框所示:

  • <font color=”blue”>spring-cloud-tutorials</font> 文件夹下有多个子工程,本篇的代码是 <font color=”red”>gateway-change-body</font>,如下图红框所示:

为何不必惯例伎俩

  • 提到全局异样解决,经验丰富的您应该想到了罕用的 ControllerAdvice 和 ExceptionHandler 注解润饰的全局异样解决类,然而 Spring Cloud Gateway 是基于 WebFlux 的,咱们之前解决异样时用到的 HttpServletRequest 在 Spring Cloud Gateway 中并不实用,因而,不能用 ControllerAdvice 和 ExceptionHandler 的伎俩来解决全局异样

基本思路

  • 在入手前做好短缺的实践剖析,写出的代码能力失常工作
  • 关上 DefaultErrorWebExceptionHandler.java,找到 renderErrorResponse 办法,来看看 Spring Cloud Gateway 本来是如何结构异样返回内容的:

  • 此刻聪慧的您应该想到怎么做了:做个新的类继承 DefaultErrorWebExceptionHandler,笼罩其 renderErrorResponse 办法,新的 renderErrorResponse 办法中,依照理论业务须要来设置返回内容,没错,这就是咱们的思路,不过还要细化一下,最终具体的步骤如下:
  1. 新增一个异样类 <font color=”blue”>CustomizeInfoException.java</font>,该类有三个字段:http 返回码、业务返回码、业务形容信息
  2. 在返回异样的代码地位,应用 CustomizeInfoException 类来抛出异样,依照理论业务场景设置 CustomizeInfoException 实例的各个字段
  3. 新增 MyErrorWebExceptionHandler.java,继承自 DefaultErrorWebExceptionHandler,重写了 renderErrorResponse 办法,这外面查看异样实例是否是 CustomizeInfoException 类型,如果是,就从其中取出 http 返回码、业务返回码、业务形容信息等字段,结构返回 body 的内容,异样实例若不是 CustomizeInfoException 类型,就放弃之前的解决逻辑不变;
  4. 新增 configuration 类,用于将 MyErrorWebExceptionHandler 实例注册到 spring 环境
  • 剖析结束,开始编码吧,为了简略起见,本篇不再新增 maven 子工程,而是基于前文创立的子工程 <font color=”red”>gateway-change-body</font>,在这外面持续写代码;

编码

  • 新增异样类 <font color=”blue”>CustomizeInfoException.java</font>:
package com.bolingcavalry.changebody.exception;

import lombok.Data;
import org.springframework.http.HttpStatus;

@Data
public class CustomizeInfoException extends Exception {
    /**
     * http 返回码
     */
    private HttpStatus httpStatus;

    /**
     * body 中的 code 字段 (业务返回码)
     */
    private String code;

    /**
     * body 中的 message 字段 (业务返回信息)
     */
    private String message;
}
  • 批改 RequestBodyRewrite.java 的 apply 办法,这外面是在解决申请 body,如果查看到没有 <font color=”blue”>user-id</font> 字段,就不将申请转发到服务提供方 <font color=”blue”>provider-hello</font>,而是返回谬误,这里的谬误就用 CustomizeInfoException 类来解决:
@Override
    public Publisher<String> apply(ServerWebExchange exchange, String body) {
        try {Map<String, Object> map = objectMapper.readValue(body, Map.class);

            // 如果申请参数中不含 user-id,就返回异样
            if (!map.containsKey("user-id")) {CustomizeInfoException customizeInfoException = new CustomizeInfoException();
                // 这里返回 406,您能够依照业务须要自行调整
                customizeInfoException.setHttpStatus(HttpStatus.NOT_ACCEPTABLE);

                // 这里依照业务须要自行设置 code
                customizeInfoException.setCode("010020003");

                // 这里依照业务须要自行设置返回的 message
                customizeInfoException.setMessage("请确保申请参数中的 user-id 字段是无效的");

                return Mono.error(customizeInfoException);
            }

            // 获得 id
            int userId = (Integer)map.get("user-id");

            // 失去 nanme 后写入 map
            map.put("user-name", mockUserName(userId));

            return Mono.just(objectMapper.writeValueAsString(map));
        } catch (Exception ex) {log.error("1. json process fail", ex);
            return Mono.error(new Exception("1. json process fail", ex));
        }
    }
  • 异样解决类 MyErrorWebExceptionHandler.java,这里有一处须要 <font color=”red”> 重点关注的是:</font> 上面的代码仅是参考而已,您无需拘泥于 CustomizeInfoException 无关的逻辑,齐全能依照业务需要自在设置返回的状态码和 body:
package com.bolingcavalry.changebody.handler;

import com.bolingcavalry.changebody.exception.CustomizeInfoException;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;

public class MyErrorWebExceptionHandler extends DefaultErrorWebExceptionHandler {public MyErrorWebExceptionHandler(ErrorAttributes errorAttributes, WebProperties.Resources resources, ErrorProperties errorProperties, ApplicationContext applicationContext) {super(errorAttributes, resources, errorProperties, applicationContext);
    }

    @Override
    protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
        // 返回码
        int status;
        // 最终是用 responseBodyMap 来生成响应 body 的
        Map<String, Object> responseBodyMap = new HashMap<>();

        // 这里和父类的做法一样,获得 DefaultErrorAttributes 整理出来的所有异样信息
        Map<String, Object> error = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));

        // 原始的异样信息能够用 getError 办法获得
        Throwable throwable = getError(request);

        // 如果异样类是咱们定制的,就定制
        if (throwable instanceof CustomizeInfoException) {CustomizeInfoException myGatewayException = (CustomizeInfoException) throwable;
            // http 返回码、body 的 code 字段、body 的 message 字段,这三个信息都从 CustomizeInfoException 实例中获取
            status = myGatewayException.getHttpStatus().value();
            responseBodyMap.put("code", myGatewayException.getCode());
            responseBodyMap.put("message", myGatewayException.getMessage());
            responseBodyMap.put("data", null);
        } else {
            // 如果不是咱们定制的异样,就维持和父类一样的逻辑
            // 返回码
            status = getHttpStatus(error);
            // body 内容
            responseBodyMap.putAll(error);
        }

        return ServerResponse
                // http 返回码
                .status(status)
                // 类型和以前一样
                .contentType(MediaType.APPLICATION_JSON)
                // 响应 body 的内容
                .body(BodyInserters.fromValue(responseBodyMap));
    }
}
  • 最初是配置类 MyErrorWebFluxAutoConfiguration.java:
package com.bolingcavalry.changebody.config;

import com.bolingcavalry.changebody.handler.MyErrorWebExceptionHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;
import java.util.stream.Collectors;

@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(WebFluxAutoConfiguration.class)
public class MyErrorWebFluxAutoConfiguration {

    private final ServerProperties serverProperties;

    public MyErrorWebFluxAutoConfiguration(ServerProperties serverProperties) {this.serverProperties = serverProperties;}

    @Bean
    @Order(-1)
    public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes,
                                                             org.springframework.boot.autoconfigure.web.ResourceProperties resourceProperties,
                                                             WebProperties webProperties, ObjectProvider<ViewResolver> viewResolvers,
                                                             ServerCodecConfigurer serverCodecConfigurer, ApplicationContext applicationContext) {

        MyErrorWebExceptionHandler exceptionHandler = new MyErrorWebExceptionHandler(errorAttributes,
                resourceProperties.hasBeenCustomized() ? resourceProperties : webProperties.getResources(),
                this.serverProperties.getError(), applicationContext);
        exceptionHandler.setViewResolvers(viewResolvers.orderedStream().collect(Collectors.toList()));
        exceptionHandler.setMessageWriters(serverCodecConfigurer.getWriters());
        exceptionHandler.setMessageReaders(serverCodecConfigurer.getReaders());
        return exceptionHandler;
    }
}
  • 编码实现,该把程序运行起来验证成果了;

验证

  • 启动利用 gateway-change-body
  • 用 postman 发动 POST 申请,地址是 <font color=”blue”>http://localhost:8081/hello/c…</font>,如下图,红框 2 中的 http 返回码是咱们代码里设置的,红框 3 显示返回的内容就是咱们定制的那三个字段:

  • 至此,管制 Spring Cloud Gateway 利用异样返回的实战曾经全副实现,从源码剖析联合实战演练,心愿欣宸的文章能陪伴您深刻理解 Spring Cloud Gateway,打造出更加弱小的网关利用;

你不孤独,欣宸原创一路相伴

  1. Java 系列
  2. Spring 系列
  3. Docker 系列
  4. kubernetes 系列
  5. 数据库 + 中间件系列
  6. DevOps 系列

欢送关注公众号:程序员欣宸

微信搜寻「程序员欣宸」,我是欣宸,期待与您一起畅游 Java 世界 …
https://github.com/zq2599/blog_demos

正文完
 0