apply(Config config) {
+ return new GatewayPredicate() {
+ @Override
+ public boolean test(ServerWebExchange exchange) {
+ // get gray value
+ String patternValue = patternParamResolve(exchange, config.getFieldPattern());
+ if (ObjectUtils.isEmpty(patternValue)) {
+ return config.isMatchEmptyField();
+ }
+
+
+ int weightStart = Math.max(0, config.getStart());
+ int weightEnd = Math.min(MAX_WEIGHT_RANGE, config.getEnd());
+
+ if (weightEnd < weightStart) {
+ // invalid match range
+ return false;
+ }
+
+
+ int hash = Math.abs(MurmurHash3.hash32x86(patternValue.getBytes(StandardCharsets.UTF_8))% MAX_WEIGHT_RANGE) ;
+
+ return hash >= weightStart && hash < weightEnd;
+ }
+
+ @Override
+ public Object getConfig() {
+ return config;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("field: %s ,start=%s ,end=%s", config.getFieldPattern(), config.getStart(), config.getEnd());
+ }
+ };
+ }
+
+ private String patternParamResolve(ServerWebExchange exchange, String singlePattern){
+ if (ObjectUtils.isEmpty(singlePattern)) {
+ return "";
+ }
+ if (singlePattern.startsWith(REQUEST_QUERY_PATTERN)) {
+ String fieldName = singlePattern.substring(REQUEST_QUERY_PATTERN.length());
+ return Optional.ofNullable(exchange.getRequest().getQueryParams().getFirst(fieldName)).orElse("");
+ } else if (singlePattern.startsWith(REQUEST_HEADER_PATTERN)) {
+ String headerName = singlePattern.substring(REQUEST_HEADER_PATTERN.length());
+ return Optional.ofNullable(exchange.getRequest().getHeaders().getFirst(headerName)).orElse("");
+ }else if (singlePattern.startsWith(REQUEST_COOKIE_PATTERN)) {
+ String cookieName = singlePattern.substring(REQUEST_COOKIE_PATTERN.length());
+ return Optional.ofNullable(exchange.getRequest().getCookies().getFirst(cookieName))
+ .map(HttpCookie::getValue)
+ .orElse("");
+ }else if (Objects.equals(singlePattern, REQUEST_PATH_PATTERN)) {
+ return Optional.ofNullable(exchange.getRequest().getURI().getPath()).orElse("");
+ }else {
+ // unsupported
+ return "";
+ }
+ }
+
+ /**
+ * match range [start, end) range. [0, 1000) full match range
+ */
+ @Validated
+ public static class Config {
+
+ /**
+ * field pattern to specify which field weight calculate base on
+ *
+ * request.header.xx
+ * request.query.xx
+ * request.cookie.xx
+ * request.path
+ *
+ */
+
+ private String fieldPattern;
+
+ /**
+ * how to handle empty field value. false miss-match, true always match
+ */
+ private boolean matchEmptyField = false;
+
+ /**
+ * weight range include start, valid input [0, 999]
+ */
+ private int start;
+ /**
+ * weight range exclude end, valid input [1, 1000]
+ */
+ private int end;
+
+ public String getFieldPattern() {
+ return fieldPattern;
+ }
+
+ public void setFieldPattern(String fieldPattern) {
+ this.fieldPattern = fieldPattern;
+ }
+
+ public int getStart() {
+ return start;
+ }
+
+ public void setStart(int start) {
+ this.start = start;
+ }
+
+ public int getEnd() {
+ return end;
+ }
+
+ public void setEnd(int end) {
+ this.end = end;
+ }
+
+ public boolean isMatchEmptyField() {
+ return matchEmptyField;
+ }
+
+ public void setMatchEmptyField(boolean matchEmptyField) {
+ this.matchEmptyField = matchEmptyField;
+ }
+ }
+
+}