Spring Cloud Gateway 動態修改請求引數解決 # URL 編碼錯誤傳參問題

乾貨滿滿張雜湊發表於2021-10-05

Spring Cloud Gateway 動態修改請求引數解決 # URL 編碼錯誤傳參問題

繼實現動態修改請求 Body 以及重試帶 Body 的請求之後,我們又遇到了一個小問題。最近很多介面,收到了錯誤的引數,在介面層報的錯是:

class org.springframework.web.method.annotation.MethodArgumentTypeMismatchException, Failed to convert value of type 'java.lang.String' to required type 'java.lang.Integer'; nested exception is java.lang.NumberFormatException: For input string: "10#scrollTop=8178" 

例如上面這個報錯即本來應該是一個數字,結果收到的是 10#scrollTop=8178 導致轉換異常。

正常的請求,是可以帶 # 的,# 後面的部分屬於 fragment。一個 URI 包括:
image

但是對於這些報錯的請求,我們發現,傳送的請求的原始 URI 中, # 被錯誤的 URL 編碼了,變成了 %23,例如上面的請求,發到後端的是:

https://zhxhash@example.com:8081/test/service?id=test&number=10%23segment1

這樣,後端解析到的 number 的值,就是 number=10#segment1,這樣就會發生開頭提到的報錯。

由於前端沒能復現這個問題,並且問題集中於某幾個系統的瀏覽器版本,這個問題只能通過後臺閘道器做修改解決。

我們的閘道器使用的是 Spring Cloud Gateway,我們可以針對全域性請求新增全域性 Filter,動態修正 URI,解決這個問題,程式碼如下:

@Log4j2
@Component
public class QueryNormalizationFilter implements GlobalFilter, Ordered {
    @Override
    @SneakyThrows
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String originUriString = exchange.getRequest().getURI().toString();
        if (originUriString.contains("%23")) {
            //將編碼後的 %23 替換為 #,重新用這個字串生成 URI
            URI replaced = new URI(originUriString.replace("%23", "#"));
            return chain.filter(
                    exchange.mutate()
                            .request(
                                    new ServerHttpRequestDecorator(exchange.getRequest()) {
                                        /**
                                         * 這個是影響轉發到後臺服務的 uri
                                         *
                                         * @return
                                         */
                                        @Override
                                        public URI getURI() {
                                            return replaced;
                                        }

                                        /**
                                         * 修改這個主要為了後面的 Filter 獲取查詢引數是準確的
                                         *
                                         * @return
                                         */
                                        @Override
                                        public MultiValueMap<String, String> getQueryParams() {
                                            return UriComponentsBuilder.fromUri(replaced).build().getQueryParams();
                                        }
                                    }
                            ).build()
            );
        } else {
            return chain.filter(exchange);
        }
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}

注意點是:

  1. 我們需要將這個 Filter 放在最開始的位置,保證後續的 Filter 的 URI 是正確的,以免有的 Filter 拿 Fragment 做文章。
  2. 如果我們只關心轉發的請求是正確的,那我們只替換 URI 即可,即覆蓋 getURI 方法。
  3. 連 getQueryParams 也覆蓋的原因,是後續的 Filter 可能也會對 QueryParams 做一些操作,我們要保證準確性。
  4. 只覆蓋 getQueryParams,並不會修改後續轉發到具體的微服務的請求的 QueryParams,這個只能通過覆蓋 getURI 修改。

微信搜尋“我的程式設計喵”關注公眾號,每日一刷,輕鬆提升技術,斬獲各種offer

相關文章