Improve compatibility for dispatched servlet request in Spring Web adapter (#1681)
This commit is contained in:
parent
5905874dd8
commit
656e3de2f9
|
|
@ -34,6 +34,21 @@ import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
import org.springframework.web.servlet.ModelAndView;
|
import org.springframework.web.servlet.ModelAndView;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Since request may be reprocessed in flow if any forwarding or including or other action
|
||||||
|
* happened (see {@link javax.servlet.ServletRequest#getDispatcherType()}) we will only
|
||||||
|
* deal with the initial request. So we use <b>reference count</b> to track in
|
||||||
|
* dispathing "onion" though which we could figure out whether we are in initial type "REQUEST".
|
||||||
|
* That means the sub-requests which we rarely meet in practice will NOT be recorded in Sentinel.
|
||||||
|
* <p>
|
||||||
|
* How to implement a forward sub-request in your action:
|
||||||
|
* <pre>
|
||||||
|
* initalRequest() {
|
||||||
|
* ModelAndView mav = new ModelAndView();
|
||||||
|
* mav.setViewName("another");
|
||||||
|
* return mav;
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
* @author kaizi2009
|
* @author kaizi2009
|
||||||
* @since 1.7.1
|
* @since 1.7.1
|
||||||
*/
|
*/
|
||||||
|
|
@ -49,20 +64,46 @@ public abstract class AbstractSentinelInterceptor implements HandlerInterceptor
|
||||||
AssertUtil.assertNotBlank(config.getRequestAttributeName(), "requestAttributeName should not be blank");
|
AssertUtil.assertNotBlank(config.getRequestAttributeName(), "requestAttributeName should not be blank");
|
||||||
this.baseWebMvcConfig = config;
|
this.baseWebMvcConfig = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param request
|
||||||
|
* @param rcKey
|
||||||
|
* @param step
|
||||||
|
* @return reference count after increasing (initial value as zero to be increased)
|
||||||
|
*/
|
||||||
|
private Integer increaseReferece(HttpServletRequest request, String rcKey, int step) {
|
||||||
|
Object obj = request.getAttribute(rcKey);
|
||||||
|
|
||||||
|
if (obj == null) {
|
||||||
|
// initial
|
||||||
|
obj = Integer.valueOf(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer newRc = (Integer)obj + step;
|
||||||
|
request.setAttribute(rcKey, newRc);
|
||||||
|
return newRc;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
try {
|
try {
|
||||||
String resourceName = getResourceName(request);
|
String resourceName = getResourceName(request);
|
||||||
|
|
||||||
if (StringUtil.isNotEmpty(resourceName)) {
|
if (StringUtil.isEmpty(resourceName)) {
|
||||||
// Parse the request origin using registered origin parser.
|
return true;
|
||||||
String origin = parseOrigin(request);
|
|
||||||
String contextName = getContextName(request);
|
|
||||||
ContextUtil.enter(contextName, origin);
|
|
||||||
setEntryInRequest(request, baseWebMvcConfig.getRequestAttributeName(), resourceName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (increaseReferece(request, this.baseWebMvcConfig.getRequestRefName(), 1) != 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the request origin using registered origin parser.
|
||||||
|
String origin = parseOrigin(request);
|
||||||
|
String contextName = getContextName(request);
|
||||||
|
ContextUtil.enter(contextName, origin);
|
||||||
|
Entry entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.IN);
|
||||||
|
request.setAttribute(baseWebMvcConfig.getRequestAttributeName(), entry);
|
||||||
return true;
|
return true;
|
||||||
} catch (BlockException e) {
|
} catch (BlockException e) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -95,11 +136,20 @@ public abstract class AbstractSentinelInterceptor implements HandlerInterceptor
|
||||||
@Override
|
@Override
|
||||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
|
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
|
||||||
Object handler, Exception ex) throws Exception {
|
Object handler, Exception ex) throws Exception {
|
||||||
Entry entry = getEntryInRequest(request, baseWebMvcConfig.getRequestAttributeName());
|
if (increaseReferece(request, this.baseWebMvcConfig.getRequestRefName(), -1) != 0) {
|
||||||
if (entry != null) {
|
return;
|
||||||
traceExceptionAndExit(entry, ex);
|
|
||||||
removeEntryInRequest(request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Entry entry = getEntryInRequest(request, baseWebMvcConfig.getRequestAttributeName());
|
||||||
|
if (entry == null) {
|
||||||
|
// should not happen
|
||||||
|
RecordLog.warn("[{}] No entry found in request, key: {}",
|
||||||
|
getClass().getSimpleName(), baseWebMvcConfig.getRequestAttributeName());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
traceExceptionAndExit(entry, ex);
|
||||||
|
removeEntryInRequest(request);
|
||||||
ContextUtil.exit();
|
ContextUtil.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -108,26 +158,6 @@ public abstract class AbstractSentinelInterceptor implements HandlerInterceptor
|
||||||
ModelAndView modelAndView) throws Exception {
|
ModelAndView modelAndView) throws Exception {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Note:
|
|
||||||
* If the attribute key already exists in request, don't create new {@link Entry},
|
|
||||||
* to guarantee the order of {@link Entry} in pair and avoid {@link com.alibaba.csp.sentinel.ErrorEntryFreeException}.
|
|
||||||
*
|
|
||||||
* Refer to:
|
|
||||||
* https://github.com/alibaba/Sentinel/issues/1531
|
|
||||||
* https://github.com/alibaba/Sentinel/issues/1482
|
|
||||||
*/
|
|
||||||
protected void setEntryInRequest(HttpServletRequest request, String name, String resourceName) throws BlockException {
|
|
||||||
Object attrVal = request.getAttribute(name);
|
|
||||||
if (attrVal != null) {
|
|
||||||
RecordLog.warn("[{}] The attribute key '{}' already exists in request, please set `requestAttributeName`",
|
|
||||||
getClass().getSimpleName(), name);
|
|
||||||
} else {
|
|
||||||
Entry entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.IN);
|
|
||||||
request.setAttribute(name, entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Entry getEntryInRequest(HttpServletRequest request, String attrKey) {
|
protected Entry getEntryInRequest(HttpServletRequest request, String attrKey) {
|
||||||
Object entryObject = request.getAttribute(attrKey);
|
Object entryObject = request.getAttribute(attrKey);
|
||||||
return entryObject == null ? null : (Entry)entryObject;
|
return entryObject == null ? null : (Entry)entryObject;
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ package com.alibaba.csp.sentinel.adapter.spring.webmvc.config;
|
||||||
|
|
||||||
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
|
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
|
||||||
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
|
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
|
||||||
import com.alibaba.csp.sentinel.util.AssertUtil;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Common base configuration for Spring Web MVC adapter.
|
* Common base configuration for Spring Web MVC adapter.
|
||||||
|
|
@ -28,6 +27,7 @@ import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||||
public abstract class BaseWebMvcConfig {
|
public abstract class BaseWebMvcConfig {
|
||||||
|
|
||||||
protected String requestAttributeName;
|
protected String requestAttributeName;
|
||||||
|
protected String requestRefName;
|
||||||
protected BlockExceptionHandler blockExceptionHandler;
|
protected BlockExceptionHandler blockExceptionHandler;
|
||||||
protected RequestOriginParser originParser;
|
protected RequestOriginParser originParser;
|
||||||
|
|
||||||
|
|
@ -37,6 +37,16 @@ public abstract class BaseWebMvcConfig {
|
||||||
|
|
||||||
public void setRequestAttributeName(String requestAttributeName) {
|
public void setRequestAttributeName(String requestAttributeName) {
|
||||||
this.requestAttributeName = requestAttributeName;
|
this.requestAttributeName = requestAttributeName;
|
||||||
|
this.requestRefName = this.requestAttributeName + "-rc";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paired with attr name used to track reference count.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String getRequestRefName() {
|
||||||
|
return requestRefName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlockExceptionHandler getBlockExceptionHandler() {
|
public BlockExceptionHandler getBlockExceptionHandler() {
|
||||||
|
|
|
||||||
|
|
@ -17,40 +17,53 @@ package com.alibaba.csp.sentinel.demo.spring.webmvc.controller;
|
||||||
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
import org.springframework.web.servlet.ModelAndView;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test controller
|
* Test controller
|
||||||
* @author kaizi2009
|
* @author kaizi2009
|
||||||
*/
|
*/
|
||||||
@RestController
|
@Controller
|
||||||
public class WebMvcTestController {
|
public class WebMvcTestController {
|
||||||
|
|
||||||
@GetMapping("/hello")
|
@GetMapping("/hello")
|
||||||
|
@ResponseBody
|
||||||
public String apiHello() {
|
public String apiHello() {
|
||||||
doBusiness();
|
doBusiness();
|
||||||
return "Hello!";
|
return "Hello!";
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/err")
|
@GetMapping("/err")
|
||||||
|
@ResponseBody
|
||||||
public String apiError() {
|
public String apiError() {
|
||||||
doBusiness();
|
doBusiness();
|
||||||
return "Oops...";
|
return "Oops...";
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/foo/{id}")
|
@GetMapping("/foo/{id}")
|
||||||
|
@ResponseBody
|
||||||
public String apiFoo(@PathVariable("id") Long id) {
|
public String apiFoo(@PathVariable("id") Long id) {
|
||||||
doBusiness();
|
doBusiness();
|
||||||
return "Hello " + id;
|
return "Hello " + id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/exclude/{id}")
|
@GetMapping("/exclude/{id}")
|
||||||
|
@ResponseBody
|
||||||
public String apiExclude(@PathVariable("id") Long id) {
|
public String apiExclude(@PathVariable("id") Long id) {
|
||||||
doBusiness();
|
doBusiness();
|
||||||
return "Exclude " + id;
|
return "Exclude " + id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/forward")
|
||||||
|
public ModelAndView apiForward() {
|
||||||
|
ModelAndView mav = new ModelAndView();
|
||||||
|
mav.setViewName("hello");
|
||||||
|
return mav;
|
||||||
|
}
|
||||||
|
|
||||||
private void doBusiness() {
|
private void doBusiness() {
|
||||||
Random random = new Random(1);
|
Random random = new Random(1);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue