spring cloud gateway 熔断 304 处理方案

改进:这是gateway的老版本遗留,我以前是2.04,后升级到2.17后bug已经被解决

老版本或者无法升级的可以尝试一下方案

 

前提:这里只针对304处理,302等其他请使用其他方案,如果有小伙伴有更好的方案请提出讨论

首先我们必须知道今静态资源304状态,是有两个http 头部信息决定的,Last-Modified  和If-Modified-Since

Last-Modified 是由服务器发送给客户端的HTTP请求头标签

If-Modified-Since 则是由客户端发送给服务器的HTTP请求头标签

第一次浏览器请求的时候是没有If-Modified-Since,当请求成功服务器会把该静态资源的Last-Modified(最后一次修改时间)返回

而后浏览器再次请求静态资源的时候会携带该头部信息的If-Modified-Since ,服务器会把If-Modified-Since和Last-Modified 进行比对,如果一致,则返回304状态码告诉浏览器,你本地已经缓存,直接使用即可;

 

问题就在于返回304后,服务端是没有body内容的,而gateway 在处理熔断的时候是查看后台是否有body进行判断的。且熔断机制在gateway中优先级非常高;从而导致gateway在对304进行处理的时候以为服务器熔断而降级;使得这种类型的静态资源 无法加载;

解决路程:

    1.我暂时关闭了熔断,使用全局异常处理方式获得后台返回,如果为空,则设置状态码为304;并设置body 为随意,(浏览器会根据状态码来进行业务处理,而不会根据body内容:(切记:后端服务请进行全局异常处理,非304状态下的异常最好不要返回null,否则会会增加gateway的逻辑复杂度))代码如下:

/**
 *
 * @author Administrator
 * @version $Id: JsonExceptionHandler, v 0.1 2020/3/16 22:48 Administrator Exp$
 */
public class JsonExceptionHandler implements ErrorWebExceptionHandler {
    private static final Logger log = LoggerFactory.getLogger(JsonExceptionHandler.class);

    /**
     * MessageReader
     */
    private List> messageReaders = Collections.emptyList();

    /**
     * MessageWriter
     */
    private List> messageWriters = Collections.emptyList();

    /**
     * ViewResolvers
     */
    private List viewResolvers = Collections.emptyList();

    /**
     * 存储处理异常后的信息
     */
    private ThreadLocal> exceptionHandlerResult = new ThreadLocal<>();

    /**
     * 参考AbstractErrorWebExceptionHandler
     *
     * @param messageReaders
     */
    public void setMessageReaders(List> messageReaders) {
        Assert.notNull(messageReaders, "'messageReaders' must not be null");
        this.messageReaders = messageReaders;
    }

    /**
     * 参考AbstractErrorWebExceptionHandler
     *
     * @param viewResolvers
     */
    public void setViewResolvers(List viewResolvers) {
        this.viewResolvers = viewResolvers;
    }

    /**
     * 参考AbstractErrorWebExceptionHandler
     *
     * @param messageWriters
     */
    public void setMessageWriters(List> messageWriters) {
        Assert.notNull(messageWriters, "'messageWriters' must not be null");
        this.messageWriters = messageWriters;
    }

    @Override
    public Mono handle(ServerWebExchange exchange, Throwable ex) {
        /**
         * 按照异常类型进行处理
         */
        ServerHttpRequest request = exchange.getRequest();
        HttpStatus httpStatus;
        String body;
        if (ex instanceof NotFoundException) {
            httpStatus = HttpStatus.NOT_FOUND;
            body = "Service Not Found";
        } else if (ex instanceof ResponseStatusException) {
            ResponseStatusException responseStatusException = (ResponseStatusException) ex;
            httpStatus = responseStatusException.getStatus();
            body = responseStatusException.getMessage();
        } else if (ex instanceof NullPointerException) {
            if (testLast(request.getPath().value())) {
                body = "Internal Server Error";//这里即使设置body,去前端也会进行304
                httpStatus = HttpStatus.NOT_MODIFIED;//状态码设置
            } else {
                body = null;//这里留个口子,其他状态下依旧可以降级
                httpStatus = HttpStatus.OK;
            }
            
        } else {
            httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
            body = "Internal Server Error";
        }
        /**
         * 封装响应体,此body可修改为自己的jsonBody
         */
        Map result = new HashMap<>(2, 1);
        result.put("httpStatus", httpStatus);
        result.put("body", body);
        /**
         * 错误记录
         */

        log.error("[全局异常处理]异常请求路径:{},记录异常信息:{}", request.getPath(), ex.getMessage());
        /**
         * 参考AbstractErrorWebExceptionHandler
         */
        if (exchange.getResponse().isCommitted()) {
            return Mono.error(ex);
        }
        exceptionHandlerResult.set(result);
        ServerRequest newRequest = ServerRequest.create(exchange, this.messageReaders);
        return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse).route(newRequest)
                .switchIfEmpty(Mono.error(ex))
                .flatMap((handler) -> handler.handle(newRequest))
                .flatMap((response) -> write(exchange, response));

    }
    //根据文件后缀进行判断放行
    public static boolean testLast(String url) {
        if (url.lastIndexOf(".js") >= 0 || url.lastIndexOf(".css") >= 0
                || url.lastIndexOf(".jpg") >= 0 || url.lastIndexOf(".png") >= 0) {
            return true;
        }
        return false;
    }

    /**
     * 参考DefaultErrorWebExceptionHandler
     *
     * @param request
     * @return
     */
    protected Mono renderErrorResponse(ServerRequest request) {
        Map result = exceptionHandlerResult.get();
        return ServerResponse.status((HttpStatus) result.get("httpStatus"))
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .body(BodyInserters.fromObject(result.get("body")));
    }

    /**
     * 参考AbstractErrorWebExceptionHandler
     *
     * @param exchange
     * @param response
     * @return
     */
    private Mono write(ServerWebExchange exchange,
                                       ServerResponse response) {
        exchange.getResponse().getHeaders()
                .setContentType(response.headers().getContentType());
        return response.writeTo(exchange, new ResponseContext());
    }

    /**
     * 参考AbstractErrorWebExceptionHandler
     */
    private class ResponseContext implements ServerResponse.Context {

        @Override
        public List> messageWriters() {
            return JsonExceptionHandler.this.messageWriters;
        }

        @Override
        public List viewResolvers() {
            return JsonExceptionHandler.this.viewResolvers;
        }

    }
}

 

/**
 * 在启动类中加入
 * 自定义异常处理[@@]注册Bean时依赖的Bean,会从容器中直接获取,所以直接注入即可
 *
 * @param viewResolversProvider
 * @param serverCodecConfigurer
 * @return
 */
@Primary
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public ErrorWebExceptionHandler errorWebExceptionHandler(ObjectProvider> viewResolversProvider,
                                                         ServerCodecConfigurer serverCodecConfigurer) {

    JsonExceptionHandler jsonExceptionHandler = new JsonExceptionHandler();
    jsonExceptionHandler.setViewResolvers(viewResolversProvider.getIfAvailable(Collections::emptyList));
    jsonExceptionHandler.setMessageWriters(serverCodecConfigurer.getWriters());
    jsonExceptionHandler.setMessageReaders(serverCodecConfigurer.getReaders());
    return jsonExceptionHandler;
}

这样当304返回空body的时候就直接对body进行赋值,从而使得浏览器正常304;但是如果加上了熔断后(熔断的优先级高于全局异常),会导致全局异常不起作用直接熔断,于是我在熔断代码里加了以下代码:

@RequestMapping("/fallbackSimple")
@ResponseBody
public LinkedHashMap fallbackSimple(ServerWebExchange exchange) {
    ServerHttpResponse response = exchange.getResponse();
    DataBufferFactory bufferFactory = response.bufferFactory();
    bufferFactory.allocateBuffer().read();//读取返回流的时候会异常,从而进入全局异常,全局异常根据文件后缀设置状态码
    log.info("服务熔断了----------fallbackSimple");
    return fallback;
}

这样既能使用全局异常,又能使用熔断;

以上方案为一个取巧方案;

还有另一个方案就是修改gateway的源代码重新打包(这种方案本人没有实际测试过,debug调试下看了可以获取到304状态码

spring cloud gateway 熔断 304 处理方案_第1张图片

状态码那一行提前,然后进行状态码判断,理论上这种方案更科学;

你可能感兴趣的:(个人)