We often share few common validations, logging scenarios, modify data for every request/response. To cover these scenarios with some common code, we need to intercept the incoming request and the outgoing response.
To intercept the request-response, we can use the Filter interface provided in javax.servlet package.
Methods provided by Filter interface:
- init
- This method is invoked only once
@Override public void init(FilterConfig filterConfig) throws ServletException { }
- doFilter
- Invoked each time whenever client sends a request or server sends a response.
- We can perform all our logic in this method
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { }
- destroy
- clean up and removes filter from service
@Override public void destroy() { }
Dependencies that are needed to define filter in Spring-boot project comes from
- Gradle:
implementation 'org.springframework.boot:spring-boot-starter-web'
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.3.4.RELEASE</version> </dependency>
Use-case:
So in our example, We'll define a RequestValidationFilter which basically validates certain headers are present in the incoming request and sends it forward to the controller, if not it will throw an exception.
First, Let's define a simple controller which handles simple http calls:
AppController.java
package com.artifactsbyrake.pocs.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/test") public class AppController { @GetMapping(value = "/hello") public String getSayHello() { return "GET - Hello Rake!!!"; } @PostMapping(value = "/hello") public String postSayHello() { return "POST - Hello Rake!!!"; } }
Now, let's define our filter which will intercept the requests to be handled by the controller
RequestValidationFilter.java
package com.artifactsbyrake.pocs.filter; import com.artifactsbyrake.pocs.exceptions.MissingHeaderException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.Collections; import java.util.function.Function; import java.util.stream.Collectors; /** * This filter checks for required fields in headers needed to process the request */ @Component @Order(Ordered.HIGHEST_PRECEDENCE) public class RequestValidationFilter implements Filter { public final String CUSTOM_HEADER = "CUSTOM_HEADER"; private final static Logger LOG = LoggerFactory.getLogger(RequestValidationFilter.class); @Override public void init(FilterConfig filterConfig) throws ServletException { LOG.info("Initializing filter :{}", this); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; // printing headers req.getHeaderNames().asIterator().forEachRemaining(System.out::println); //extract headers HttpHeaders headers = Collections.list(req.getHeaderNames()) .stream() .collect(Collectors.toMap( Function.identity(), h -> Collections.list(req.getHeaders(h)), (oldValue, newValue) -> newValue, HttpHeaders::new )); //check for required header and throw exception if not found if (!headers.containsKey(CUSTOM_HEADER) || CollectionUtils.isEmpty(headers.get(CUSTOM_HEADER))) { throw new MissingHeaderException("Invalid Request - Missing header for " + CUSTOM_HEADER); } chain.doFilter(request, response); } @Override public void destroy() { LOG.info("Destroying filter :{}", this); } }
Exception to throw for missing headers:
MissingHeaderException.java
package com.artifactsbyrake.pocs.exceptions; public class MissingHeaderException extends RuntimeException { private static final long serialVersionUID = 1L; public MissingHeaderException(String message){ super(message); } }
time to test the magic 👽
- With header
- You can use n number of filters and order them
- Filter chain handles the order in which the filters needs to be applied
- Also there are ways you can use to apply specific filters to certain pattern of requests
Happy Coding 👨💻
Comments
Post a Comment