- Add `getCookieValue` method in RequestItemParser interface and update GatewayParamParser - Add cookie parsing logic for Spring Cloud Gateway and Zuul Signed-off-by: Eric Zhao <sczyh16@gmail.com>master
@@ -30,6 +30,7 @@ public final class SentinelGatewayConstants { | |||||
public static final int PARAM_PARSE_STRATEGY_HOST = 1; | public static final int PARAM_PARSE_STRATEGY_HOST = 1; | ||||
public static final int PARAM_PARSE_STRATEGY_HEADER = 2; | public static final int PARAM_PARSE_STRATEGY_HEADER = 2; | ||||
public static final int PARAM_PARSE_STRATEGY_URL_PARAM = 3; | public static final int PARAM_PARSE_STRATEGY_URL_PARAM = 3; | ||||
public static final int PARAM_PARSE_STRATEGY_COOKIE = 4; | |||||
public static final int PARAM_MATCH_STRATEGY_EXACT = 0; | public static final int PARAM_MATCH_STRATEGY_EXACT = 0; | ||||
public static final int PARAM_MATCH_STRATEGY_PREFIX = 1; | public static final int PARAM_MATCH_STRATEGY_PREFIX = 1; | ||||
@@ -42,8 +42,8 @@ public class GatewayParamParser<T> { | |||||
/** | /** | ||||
* Parse parameters for given resource from the request entity on condition of the rule predicate. | * Parse parameters for given resource from the request entity on condition of the rule predicate. | ||||
* | * | ||||
* @param resource valid resource name | |||||
* @param request valid request | |||||
* @param resource valid resource name | |||||
* @param request valid request | |||||
* @param rulePredicate rule predicate indicating the rules to refer | * @param rulePredicate rule predicate indicating the rules to refer | ||||
* @return the parameter array | * @return the parameter array | ||||
*/ | */ | ||||
@@ -92,6 +92,8 @@ public class GatewayParamParser<T> { | |||||
return parseHeader(item, request); | return parseHeader(item, request); | ||||
case SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM: | case SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM: | ||||
return parseUrlParameter(item, request); | return parseUrlParameter(item, request); | ||||
case SentinelGatewayConstants.PARAM_PARSE_STRATEGY_COOKIE: | |||||
return parseCookie(item, request); | |||||
default: | default: | ||||
return null; | return null; | ||||
} | } | ||||
@@ -139,6 +141,17 @@ public class GatewayParamParser<T> { | |||||
return parseWithMatchStrategyInternal(item.getMatchStrategy(), param, pattern); | return parseWithMatchStrategyInternal(item.getMatchStrategy(), param, pattern); | ||||
} | } | ||||
private String parseCookie(/*@Valid*/ GatewayParamFlowItem item, T request) { | |||||
String cookieName = item.getFieldName(); | |||||
String pattern = item.getPattern(); | |||||
String param = requestItemParser.getCookieValue(request, cookieName); | |||||
if (pattern == null) { | |||||
return param; | |||||
} | |||||
// Match value according to regex pattern or exact mode. | |||||
return parseWithMatchStrategyInternal(item.getMatchStrategy(), param, pattern); | |||||
} | |||||
private String parseWithMatchStrategyInternal(int matchStrategy, String value, String pattern) { | private String parseWithMatchStrategyInternal(int matchStrategy, String value, String pattern) { | ||||
// TODO: implement here. | // TODO: implement here. | ||||
if (value == null) { | if (value == null) { | ||||
@@ -41,7 +41,7 @@ public interface RequestItemParser<T> { | |||||
* Get the header associated with the header key. | * Get the header associated with the header key. | ||||
* | * | ||||
* @param request valid request | * @param request valid request | ||||
* @param key valid header key | |||||
* @param key valid header key | |||||
* @return the header | * @return the header | ||||
*/ | */ | ||||
String getHeader(T request, String key); | String getHeader(T request, String key); | ||||
@@ -49,9 +49,19 @@ public interface RequestItemParser<T> { | |||||
/** | /** | ||||
* Get the parameter value associated with the parameter name. | * Get the parameter value associated with the parameter name. | ||||
* | * | ||||
* @param request valid request | |||||
* @param request valid request | |||||
* @param paramName valid parameter name | * @param paramName valid parameter name | ||||
* @return the parameter value | * @return the parameter value | ||||
*/ | */ | ||||
String getUrlParam(T request, String paramName); | String getUrlParam(T request, String paramName); | ||||
/** | |||||
* Get the cookie value associated with the cookie name. | |||||
* | |||||
* @param request valid request | |||||
* @param cookieName valid cookie name | |||||
* @return the cookie value | |||||
* @since 1.7.0 | |||||
*/ | |||||
String getCookieValue(T request, String cookieName); | |||||
} | } |
@@ -16,6 +16,7 @@ | |||||
package com.alibaba.csp.sentinel.adapter.gateway.common.rule; | package com.alibaba.csp.sentinel.adapter.gateway.common.rule; | ||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.Arrays; | |||||
import java.util.HashMap; | import java.util.HashMap; | ||||
import java.util.HashSet; | import java.util.HashSet; | ||||
import java.util.List; | import java.util.List; | ||||
@@ -242,14 +243,18 @@ public final class GatewayRuleManager { | |||||
if (item.getParseStrategy() < 0) { | if (item.getParseStrategy() < 0) { | ||||
return false; | return false; | ||||
} | } | ||||
if (item.getParseStrategy() == SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM || | |||||
item.getParseStrategy() == SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER) { | |||||
if (StringUtil.isBlank(item.getFieldName())) { | |||||
return false; | |||||
} | |||||
// Check required field name for item types. | |||||
if (FIELD_REQUIRED_SET.contains(item.getParseStrategy()) && StringUtil.isBlank(item.getFieldName())) { | |||||
return false; | |||||
} | } | ||||
return StringUtil.isEmpty(item.getPattern()) || item.getMatchStrategy() >= 0; | return StringUtil.isEmpty(item.getPattern()) || item.getMatchStrategy() >= 0; | ||||
} | } | ||||
private static final Set<Integer> FIELD_REQUIRED_SET = new HashSet<>( | |||||
Arrays.asList(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM, | |||||
SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER, | |||||
SentinelGatewayConstants.PARAM_PARSE_STRATEGY_COOKIE) | |||||
); | |||||
private GatewayRuleManager() {} | private GatewayRuleManager() {} | ||||
} | } |
@@ -94,6 +94,7 @@ public class GatewayParamParserTest { | |||||
final String api1 = "my_test_route_B"; | final String api1 = "my_test_route_B"; | ||||
final String headerName = "X-Sentinel-Flag"; | final String headerName = "X-Sentinel-Flag"; | ||||
final String paramName = "p"; | final String paramName = "p"; | ||||
final String cookieName = "myCookie"; | |||||
GatewayFlowRule routeRuleNoParam = new GatewayFlowRule(routeId1) | GatewayFlowRule routeRuleNoParam = new GatewayFlowRule(routeId1) | ||||
.setCount(10) | .setCount(10) | ||||
.setIntervalSec(10); | .setIntervalSec(10); | ||||
@@ -128,6 +129,13 @@ public class GatewayParamParserTest { | |||||
.setParamItem(new GatewayParamFlowItem() | .setParamItem(new GatewayParamFlowItem() | ||||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HOST) | .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HOST) | ||||
); | ); | ||||
GatewayFlowRule routeRule5 = new GatewayFlowRule(routeId1) | |||||
.setCount(50) | |||||
.setIntervalSec(30) | |||||
.setParamItem(new GatewayParamFlowItem() | |||||
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_COOKIE) | |||||
.setFieldName(cookieName) | |||||
); | |||||
GatewayFlowRule apiRule1 = new GatewayFlowRule(api1) | GatewayFlowRule apiRule1 = new GatewayFlowRule(api1) | ||||
.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME) | .setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME) | ||||
.setCount(5) | .setCount(5) | ||||
@@ -140,6 +148,7 @@ public class GatewayParamParserTest { | |||||
rules.add(routeRule2); | rules.add(routeRule2); | ||||
rules.add(routeRule3); | rules.add(routeRule3); | ||||
rules.add(routeRule4); | rules.add(routeRule4); | ||||
rules.add(routeRule5); | |||||
rules.add(routeRuleNoParam); | rules.add(routeRuleNoParam); | ||||
rules.add(apiRule1); | rules.add(apiRule1); | ||||
GatewayRuleManager.loadRules(rules); | GatewayRuleManager.loadRules(rules); | ||||
@@ -148,19 +157,24 @@ public class GatewayParamParserTest { | |||||
final String expectedAddress = "66.77.88.99"; | final String expectedAddress = "66.77.88.99"; | ||||
final String expectedHeaderValue1 = "Sentinel"; | final String expectedHeaderValue1 = "Sentinel"; | ||||
final String expectedUrlParamValue1 = "17"; | final String expectedUrlParamValue1 = "17"; | ||||
final String expectedCookieValue1 = "Sentinel-Foo"; | |||||
mockClientHostAddress(itemParser, expectedAddress); | mockClientHostAddress(itemParser, expectedAddress); | ||||
Map<String, String> expectedHeaders = new HashMap<String, String>() {{ | Map<String, String> expectedHeaders = new HashMap<String, String>() {{ | ||||
put(headerName, expectedHeaderValue1); put("Host", expectedHost); | put(headerName, expectedHeaderValue1); put("Host", expectedHost); | ||||
}}; | }}; | ||||
mockHeaders(itemParser, expectedHeaders); | mockHeaders(itemParser, expectedHeaders); | ||||
mockSingleUrlParam(itemParser, paramName, expectedUrlParamValue1); | mockSingleUrlParam(itemParser, paramName, expectedUrlParamValue1); | ||||
mockSingleCookie(itemParser, cookieName, expectedCookieValue1); | |||||
Object[] params = paramParser.parseParameterFor(routeId1, request, routeIdPredicate); | Object[] params = paramParser.parseParameterFor(routeId1, request, routeIdPredicate); | ||||
// Param length should be 5 (4 with parameters, 1 normal flow with generated constant) | |||||
assertThat(params.length).isEqualTo(5); | |||||
// Param length should be 6 (5 with parameters, 1 normal flow with generated constant) | |||||
assertThat(params.length).isEqualTo(6); | |||||
assertThat(params[routeRule1.getParamItem().getIndex()]).isEqualTo(expectedAddress); | assertThat(params[routeRule1.getParamItem().getIndex()]).isEqualTo(expectedAddress); | ||||
assertThat(params[routeRule2.getParamItem().getIndex()]).isEqualTo(expectedHeaderValue1); | assertThat(params[routeRule2.getParamItem().getIndex()]).isEqualTo(expectedHeaderValue1); | ||||
assertThat(params[routeRule3.getParamItem().getIndex()]).isEqualTo(expectedUrlParamValue1); | assertThat(params[routeRule3.getParamItem().getIndex()]).isEqualTo(expectedUrlParamValue1); | ||||
assertThat(params[routeRule4.getParamItem().getIndex()]).isEqualTo(expectedHost); | assertThat(params[routeRule4.getParamItem().getIndex()]).isEqualTo(expectedHost); | ||||
assertThat(params[routeRule5.getParamItem().getIndex()]).isEqualTo(expectedCookieValue1); | |||||
assertThat(params[params.length - 1]).isEqualTo(SentinelGatewayConstants.GATEWAY_DEFAULT_PARAM); | assertThat(params[params.length - 1]).isEqualTo(SentinelGatewayConstants.GATEWAY_DEFAULT_PARAM); | ||||
assertThat(paramParser.parseParameterFor(api1, request, routeIdPredicate).length).isZero(); | assertThat(paramParser.parseParameterFor(api1, request, routeIdPredicate).length).isZero(); | ||||
@@ -196,6 +210,10 @@ public class GatewayParamParserTest { | |||||
when(parser.getHeader(any(), eq(key))).thenReturn(value); | when(parser.getHeader(any(), eq(key))).thenReturn(value); | ||||
} | } | ||||
private void mockSingleCookie(/*@Mock*/ RequestItemParser parser, String key, String value) { | |||||
when(parser.getCookieValue(any(), eq(key))).thenReturn(value); | |||||
} | |||||
@Before | @Before | ||||
public void setUp() { | public void setUp() { | ||||
GatewayApiDefinitionManager.loadApiDefinitions(new HashSet<ApiDefinition>()); | GatewayApiDefinitionManager.loadApiDefinitions(new HashSet<ApiDefinition>()); | ||||
@@ -16,9 +16,11 @@ | |||||
package com.alibaba.csp.sentinel.adapter.gateway.sc; | package com.alibaba.csp.sentinel.adapter.gateway.sc; | ||||
import java.net.InetSocketAddress; | import java.net.InetSocketAddress; | ||||
import java.util.Optional; | |||||
import com.alibaba.csp.sentinel.adapter.gateway.common.param.RequestItemParser; | import com.alibaba.csp.sentinel.adapter.gateway.common.param.RequestItemParser; | ||||
import org.springframework.http.HttpCookie; | |||||
import org.springframework.web.server.ServerWebExchange; | import org.springframework.web.server.ServerWebExchange; | ||||
/** | /** | ||||
@@ -50,4 +52,11 @@ public class ServerWebExchangeItemParser implements RequestItemParser<ServerWebE | |||||
public String getUrlParam(ServerWebExchange exchange, String paramName) { | public String getUrlParam(ServerWebExchange exchange, String paramName) { | ||||
return exchange.getRequest().getQueryParams().getFirst(paramName); | return exchange.getRequest().getQueryParams().getFirst(paramName); | ||||
} | } | ||||
@Override | |||||
public String getCookieValue(ServerWebExchange exchange, String cookieName) { | |||||
return Optional.ofNullable(exchange.getResponse().getCookies().getFirst(cookieName)) | |||||
.map(HttpCookie::getValue) | |||||
.orElse(null); | |||||
} | |||||
} | } |
@@ -15,6 +15,8 @@ | |||||
*/ | */ | ||||
package com.alibaba.csp.sentinel.adapter.gateway.zuul; | package com.alibaba.csp.sentinel.adapter.gateway.zuul; | ||||
import javax.servlet.http.Cookie; | |||||
import com.alibaba.csp.sentinel.adapter.gateway.common.param.RequestItemParser; | import com.alibaba.csp.sentinel.adapter.gateway.common.param.RequestItemParser; | ||||
import com.netflix.zuul.context.RequestContext; | import com.netflix.zuul.context.RequestContext; | ||||
@@ -44,4 +46,18 @@ public class RequestContextItemParser implements RequestItemParser<RequestContex | |||||
public String getUrlParam(RequestContext requestContext, String paramName) { | public String getUrlParam(RequestContext requestContext, String paramName) { | ||||
return requestContext.getRequest().getParameter(paramName); | return requestContext.getRequest().getParameter(paramName); | ||||
} | } | ||||
@Override | |||||
public String getCookieValue(RequestContext requestContext, String cookieName) { | |||||
Cookie[] cookies = requestContext.getRequest().getCookies(); | |||||
if (cookies == null || cookieName == null) { | |||||
return null; | |||||
} | |||||
for (Cookie cookie : cookies) { | |||||
if (cookie != null && cookieName.equals(cookie.getName())) { | |||||
return cookie.getValue(); | |||||
} | |||||
} | |||||
return null; | |||||
} | |||||
} | } |