Browse Source

Add HTTP-method level flow control support in Sentinel Web Servlet Filter (#282)

- Add HTTP method level support for servlet filter with a init parameter `HTTP_METHOD_SPECIFY` as the switch. This is useful for REST APIs.
- Add test cases and fix test bug by reset the cluster node in ClusterBuilderSlot
master
Yuanqing Luo Eric Zhao 6 years ago
parent
commit
6802f97528
6 changed files with 247 additions and 5 deletions
  1. +20
    -4
      sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/CommonFilter.java
  2. +1
    -1
      sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servlet/CommonFilterTest.java
  3. +133
    -0
      sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servletmethod/CommonFilterMethodTest.java
  4. +25
    -0
      sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servletmethod/FilterMethodConfig.java
  5. +30
    -0
      sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servletmethod/TestApplication.java
  6. +38
    -0
      sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servletmethod/TestMethodController.java

+ 20
- 4
sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/CommonFilter.java View File

@@ -46,17 +46,23 @@ import com.alibaba.csp.sentinel.util.StringUtil;
*/
public class CommonFilter implements Filter {

private final static String HTTP_METHOD_SPECIFY = "HTTP_METHOD_SPECIFY";
private final static String COLON = ":";
private boolean httpMethodSpecify = false;

@Override
public void init(FilterConfig filterConfig) {

httpMethodSpecify = Boolean.parseBoolean(filterConfig.getInitParameter(HTTP_METHOD_SPECIFY));
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest sRequest = (HttpServletRequest)request;
throws IOException, ServletException {
HttpServletRequest sRequest = (HttpServletRequest) request;
Entry entry = null;

Entry methodEntry = null;

try {
String target = FilterUtil.filterTarget(sRequest);
// Clean and unify the URL.
@@ -73,9 +79,16 @@ public class CommonFilter implements Filter {
ContextUtil.enter(target, origin);
entry = SphU.entry(target, EntryType.IN);


// Add method specification if necessary
if (httpMethodSpecify) {
methodEntry = SphU.entry(sRequest.getMethod().toUpperCase() + COLON + target,
EntryType.IN);
}

chain.doFilter(request, response);
} catch (BlockException e) {
HttpServletResponse sResponse = (HttpServletResponse)response;
HttpServletResponse sResponse = (HttpServletResponse) response;
// Return the block page, or redirect to another URL.
WebCallbackManager.getUrlBlockHandler().blocked(sRequest, sResponse, e);
} catch (IOException e2) {
@@ -88,6 +101,9 @@ public class CommonFilter implements Filter {
Tracer.trace(e4);
throw e4;
} finally {
if (methodEntry != null) {
methodEntry.exit();
}
if (entry != null) {
entry.exit();
}


+ 1
- 1
sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servlet/CommonFilterTest.java View File

@@ -171,6 +171,6 @@ public class CommonFilterTest {
@After
public void cleanUp() {
FlowRuleManager.loadRules(null);
ClusterBuilderSlot.getClusterNodeMap().clear();
ClusterBuilderSlot.resetClusterNodes();
}
}

+ 133
- 0
sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servletmethod/CommonFilterMethodTest.java View File

@@ -0,0 +1,133 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.servletmethod;

import com.alibaba.csp.sentinel.adapter.servlet.config.WebServletConfig;
import com.alibaba.csp.sentinel.adapter.servlet.util.FilterUtil;
import com.alibaba.csp.sentinel.node.ClusterNode;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot;
import com.alibaba.csp.sentinel.util.StringUtil;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import java.util.Collections;

import static org.junit.Assert.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

/**
* @author Roger Law
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class CommonFilterMethodTest {

private static final String HELLO_STR = "Hello!";

private static final String HELLO_POST_STR = "Hello Post!";

private static final String GET = "GET";

private static final String POST = "POST";

private static final String COLON = ":";

@Autowired
private MockMvc mvc;

private void configureRulesFor(String resource, int count) {
configureRulesFor(resource, count, "default");
}

private void configureRulesFor(String resource, int count, String limitApp) {
FlowRule rule = new FlowRule()
.setCount(count)
.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setResource(resource);
if (StringUtil.isNotBlank(limitApp)) {
rule.setLimitApp(limitApp);
}
FlowRuleManager.loadRules(Collections.singletonList(rule));
}

@Test
public void testCommonFilterMiscellaneous() throws Exception {
String url = "/hello";
this.mvc.perform(get(url))
.andExpect(status().isOk())
.andExpect(content().string(HELLO_STR));

ClusterNode cnGet = ClusterBuilderSlot.getClusterNode(GET + COLON + url);
assertNotNull(cnGet);
assertEquals(1, cnGet.passQps());


ClusterNode cnPost = ClusterBuilderSlot.getClusterNode(POST + COLON + url);
assertNull(cnPost);

this.mvc.perform(post(url))
.andExpect(status().isOk())
.andExpect(content().string(HELLO_POST_STR));

cnPost = ClusterBuilderSlot.getClusterNode(POST + COLON + url);
assertNotNull(cnPost);
assertEquals(1, cnPost.passQps());

testCommonBlockAndRedirectBlockPage(url, cnGet, cnPost);

}

private void testCommonBlockAndRedirectBlockPage(String url, ClusterNode cnGet, ClusterNode cnPost) throws Exception {
configureRulesFor(GET + ":" + url, 0);
// The request will be blocked and response is default block message.
this.mvc.perform(get(url).accept(MediaType.TEXT_PLAIN))
.andExpect(status().isOk())
.andExpect(content().string(FilterUtil.DEFAULT_BLOCK_MSG));
assertEquals(1, cnGet.blockQps());

// Test for post pass
this.mvc.perform(post(url))
.andExpect(status().isOk())
.andExpect(content().string(HELLO_POST_STR));

assertEquals(2, cnPost.passQps());


FlowRuleManager.loadRules(null);
WebServletConfig.setBlockPage("");
}


@After
public void cleanUp() {
FlowRuleManager.loadRules(null);
ClusterBuilderSlot.resetClusterNodes();
}
}

+ 25
- 0
sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servletmethod/FilterMethodConfig.java View File

@@ -0,0 +1,25 @@
package com.alibaba.csp.sentinel.adapter.servletmethod;

import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* @author: Roger Law
**/
@Configuration
public class FilterMethodConfig {

@Bean
public FilterRegistrationBean sentinelFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new CommonFilter());
registration.addUrlPatterns("/*");
registration.addInitParameter("HTTP_METHOD_SPECIFY", "true");
registration.setName("sentinelFilter");
registration.setOrder(1);

return registration;
}
}

+ 30
- 0
sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servletmethod/TestApplication.java View File

@@ -0,0 +1,30 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.servletmethod;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
* @author Eric Zhao
*/
@SpringBootApplication
public class TestApplication {

public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}

+ 38
- 0
sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servletmethod/TestMethodController.java View File

@@ -0,0 +1,38 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.adapter.servletmethod;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @author Roger Law
*/
@RestController
public class TestMethodController {

@GetMapping("/hello")
public String apiHello() {
return "Hello!";
}

@PostMapping("/hello")
public String apiHelloPost() {
return "Hello Post!";
}

}

Loading…
Cancel
Save