Reputation: 1138
Imagine I have defined these REST endpoints:
@RequestMapping(path = "/user")
@RestController
public class ConfigUserController {
[...]
@GetMapping(path = "/variables/")
public ResponseEntity<List<Variable>> getAllVariables(...)
[...]
}
@GetMapping(path = "/variables/{name}")
public ResponseEntity<Variable> getVariableByName(...)
[...]
}
@PutMapping(path = "/variables/{name}/{value}")
public ResponseEntity<Variable> setVariableByName(...)
[...]
}
}
I've defined two filter (logging and authorization), inside the filters I want to get the url-patter which matched current request. Using the example above:
Basically what I need is to get the path defined in the mapping annotation inside the doFilter(ServletRequest request, ServletResponse response, FilterChain chain) method of my filters.
Ideally I want to have do the possibility to access the url-pattern value in my filter like this:
@Component
@Order(3)
public class MyAuthorizationRequestFilter extends GenericFilterBean {
[...]
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// here I get the value
String urlPattern = request.useSomeMethod().toGetUrlPattern();
[...]
boolean auth = false;
// here I do stuff
[...]
// finally let's see if the user is authorized
if (auth) {
chain.doFilter(request, responde);
} else {
throw SomeAuthorizationError();
}
}
[...]
}
I've been searching but I wasn't able to find a working solution.
If it's possible I prefer not to scan for collections or web.xml files (which I don't have), nor using reflections, because the code will be executed every time the filter get triggered and I don't want to affect performance.
Links or suggestions are welcome.
EDIT: added details, addes filter example cose
Upvotes: 4
Views: 3758
Reputation: 1432
Filters intercept requests before they reach the DispatcherServlet, making them ideal for coarse-grained tasks such as:
Note* Do the doFilter first before trying to do the call to getAttribute
Your Controller
@RequestMapping(path = "/user")
@RestController
public class ConfigUserController {
@GetMapping(path = "/variables/")
public ResponseEntity<List<Variable>> getAllVariables() {
return null;
}
@GetMapping(path = "/variables/{name}")
public ResponseEntity<Variable> getVariableByName(@PathVariable("name") String name) {
return new ResponseEntity<Variable>(new Variable(name), HttpStatus.OK);
}
@PutMapping(path = "/variables/{name}/{value}")
public ResponseEntity<Variable> setVariableByName() {
return null;
}
}
Custom Filter
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CustomFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// TODO
try {
chain.doFilter(request, response);
} catch (Exception e) {
} finally {
String pattern = (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
System.out.println("Request template is, " + pattern);
}
}
}
O/P:
Request template is, /user/variables/{name}
Note* Do the chain.doFilter first before trying to do the call to getAttribute
HandlerIntercepors intercepts requests between the DispatcherServlet and our Controllers. This is done within the Spring MVC framework, providing access to the Handler and ModelAndView objects.
Custom Interceptor
@Component
public class LoggerInterceptor implements HandlerInterceptor {
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object object, Exception arg3)
throws Exception {
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object object, ModelAndView model)
throws Exception {
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {
String path = (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
System.out.println("path : " + path);
return true;
}}
Register it with InterceptorRegistry
@Component
public class CustomServiceInterceptorAppConfig implements WebMvcConfigurer {
@Autowired
LoggerInterceptor loggerInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loggerInterceptor);
}
}
Beans that are singleton-scoped and set to be pre-instantiated (the default) are created when the container is created. It's a one time process, you don't worry about the performance.
You may pre-scan all @RequestMapping,@GetMapping, @PostMApping etc.. annotations, keep it and then in the filter match the pattern to get the desired result.
Here is the sample
Bean
@Component
public class Config {
public List<String> getPatterns() throws ClassNotFoundException {
List<String> str = new ArrayList<String>();
Class clazz = Class.forName("com.example.demo.controller.ConfigUserController"); // or list of controllers,
// //you can iterate that
// list
for (Method method : clazz.getMethods()) {
for (Annotation annotation : method.getDeclaredAnnotations()) {
if (annotation.toString().contains("GetMapping")) {
str.add(annotation.toString());
System.out.println("Annotation is..." + annotation.toString());
}
if (annotation.toString().contains("PutMapping")) {
str.add(annotation.toString());
System.out.println("Annotation is..." + annotation.toString());
}
if (annotation.toString().contains("PostMapping")) {
str.add(annotation.toString());
System.out.println("Annotation is..." + annotation.toString());
}
}
}
return str;
}
Custom Filter
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CustomFilter implements Filter {
@Autowired
Config con;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// TODO
try {
//filter match the pattern to get the desired result
System.out.println(con.getPatterns());
} catch (ClassNotFoundException e) {
}
chain.doFilter(request, response);
}
Upvotes: 6
Reputation: 1070
Updating the answer to use GenericFilterBean.doFilter
as per the question asked.
You can do this by adding a finally clause. This is needed since you need to do chain.doFilter(request, response)
first before calling request.getAttribute
because the attribute is not set until later in the lifecycle of the request. So if you are trying to do some operations based on incoming uri template, subclassing GenericFilterBean may not be the right choice here since you need to call chain.doFilter first.
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
//Do something
chain.doFilter(request, response);
} finally {
String pathTemplate = (String)
request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
System.out.println("Incoming Request path Template from Generic Filter : " + pathTemplate);
}
}
Alternate options:
Assuming that you are using spring web here.
You can define an HandlerInterceptor
and register it with InterceptorRegistry
.
@Component
public class LogPathTemplateInterceptor
implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {
String pathTemplate = (String)request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
System.out.println("Incoming Request path Template : " + pathTemplate);
return true;
}
}
After defining this, register it with InterceptorRegistry. See how the path pattern added to the interceptor.
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Autowired
private LogPathTemplateInterceptor logInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(logInterceptor)
.addPathPatterns("/variables/**");
}
}
This should log the request template path instead of path with variables.
Upvotes: 2