- 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 ClusterBuilderSlotmaster
@@ -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(); | |||
} | |||
@@ -171,6 +171,6 @@ public class CommonFilterTest { | |||
@After | |||
public void cleanUp() { | |||
FlowRuleManager.loadRules(null); | |||
ClusterBuilderSlot.getClusterNodeMap().clear(); | |||
ClusterBuilderSlot.resetClusterNodes(); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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!"; | |||
} | |||
} |