JAVA SpringBoot request filters
Tags: java, springboot, spring, ratelimit
Published: 2021-05-16 19:50:14

# Intro
`Filter` provides a convenient mechanism for inspecting and filtering HTTP requests entering your application. For example, you want to inject a custom header to a request/response based on some conditions, or you want to run some checks before accessing the controller and serve the request. 

# Bad
Sometimes we see people implement those kinds of checks and assertions in the controller. yes, it works but it's not the best place to put it there.

# Good
Use `Filters` which it runs before accessing the controllers and serve your requests.

# Filters flow
![filters-flow](https://ahmedash95-articles.s3.eu-central-1.amazonaws.com/dev.to/java-springboot-request-filters/filters-flow)

# Hands On

## How to write? 
In order to create a request filter. you just need to create a class that implements the `Filter` interface. 
```java
package com.ahmedash95.example.filters;

import javax.servlet.*;

@Component
public class RoomsCreateFilter implements Filter 
{
  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
	 // your code goes here.

	// then you need to pass it in the chain. 
	chain.doFilter(request, response);
  }
}
```

If you are not familiar with the code above. here are some clarifications:

- `@Component` is an annotation that allows us to register the class as a bean in the ApplicationContext
- `implements Filter` interface tells Spring how this class should be used when register via `@Component`
- `import javax.servlet.*;` imports Filter, ServletRequest, ServletResponse, FilterChain, and ServletException.
- `chain.doFilter(request, response);` at the end is a must. as it tells spring how to continue handling the request. without it the response will be empty as the chain got broken.

## Return response on errors
If you validate something and want to stop processing the request and just return the response. you can simply modify the `Response` object and return it from your Filter class.
```java
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    boolean valid = false; // check for something
    if (!valid) {
        ((HttpServletResponse) response).setStatus(422);
        response.getOutputStream().write("Validation error".getBytes());
        return;
    }
    

    chain.doFilter(request, response);
}
```

## Examples
Let's write some real-world examples and some different scenarios to make sure you get the full picture.

### 1.Ensure API key is provided and valid
Let's say you are working on API that provides something. and you want to ensure that all requests to your application will have the API key and the key is valid. of course, you don't want to do the key validation to be in every controller and method. 

Let's start by creating our filter first
```java
package com.example.demo;

import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;

@Component
public class APIKeyValidatorFilter implements Filter {
	@Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    	HttpServletRequest req = (HttpServletRequest) request;
    	String key = req.getHeader("X-API-KEY");
    	if(key == null) {
            ((HttpServletResponse) response).setStatus(401);
            response.getOutputStream().write("API Key is missing!".getBytes());
            return;
        }

        if(!KeyValidator.valid(key)) {
            ((HttpServletResponse) response).setStatus(403);
            response.getOutputStream().write("API Key is invalid".getBytes());
            return;
        }

        chain.doFilter(request, response);
    }
}
```
As you see. we basically do some checks. if the key is not provided we show the key missing error message. if is provided but not valid we show The key is an invalid message.

### 2.Ratelimit some endpoints
Let's say you want to protect the `POST /comment` endpoint. so your users must not be able to submit more than 2 comments in one minute. **Again** it can be done in the controller but It's not the best place to do it.

Let's create the Filter class
```java
package com.example.demo;

import javax.servlet.Filter;
import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;

public class PostCommentRateLimit implements Filter
{
    RateLimiter rateLimiter;

    public PostCommentRateLimit(RateLimiter rateLimiter) {
        this.rateLimiter = rateLimiter;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        int userId = 1; // Get User
        boolean valid = rateLimiter.validate(String.format("ratelimit.user.comments:%d", userId), 2);
        if(!valid) {
            ((HttpServletResponse) response).setStatus(429);
            response.getOutputStream().write("Too Many Requests".getBytes());
            return;
        }
        

        chain.doFilter(request, response);
    }
}
```
Few things to explain here:
- We did not use `@Component` here. because we want to apply the filter on `POST /comment` endpoint only. so we will register it ourself next.
- We have `RateLimiter` in constructor. and you don't need to worry about it. use whatever library fits your needs.

**As mentioned** we did not use `@Component` because it would apply the filter for all requests. instead we will create another class to register our filters the way we want.
```java
package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RequestsFilterRegister {

    @Autowired
    RateLimiter rateLimiter;

    @Bean
    public FilterRegistrationBean<PostCommentRateLimit> registerPostCommentsRateLimiter(){
        FilterRegistrationBean<PostCommentRateLimit> registrationBean  = new FilterRegistrationBean<>();

        registrationBean.setFilter(new PostCommentRateLimit(rateLimiter));
        registrationBean.addUrlPatterns("/comment");

        return registrationBean;
    }
}
```
- We created `RequestsFilterRegister` as `@Configuration` class
- The only method there `registerPostCommentsRateLimiter` to register the filter as we want.
- we used `addUrlPatterns` to apply the filter only on `/comment` endpoint.

Now we have one small problem. the filter is applied to any method on `/comment` either GET or POST. and to fix that we just need to modify the `PostCommentRateLimit@doFilter` to skip if the method is not post
```java
HttpServletRequest req = (HttpServletRequest) request;
if (!req.getMethod().equals("POST")) {
    chain.doFilter(request, response);
    return;
}
``` 

Now the full class is
```java
package com.example.demo;

import javax.servlet.Filter;
import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class PostCommentRateLimit implements Filter
{
    RateLimiter rateLimiter;

    public PostCommentRateLimit(RateLimiter rateLimiter) {
        this.rateLimiter = rateLimiter;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        if (!req.getMethod().equals("POST")) {
            chain.doFilter(request, response);
            return;
        }

        int userId = 1; // Get User
        boolean valid = rateLimiter.validate(String.format("ratelimit.user.comments:%d", userId), 2);
        if(!valid) {
            ((HttpServletResponse) response).setStatus(429);
            response.getOutputStream().write("Too Many Requests".getBytes());
            return;
        }
        

        chain.doFilter(request, response);
    }
}
```

# Conclution
Now you know What is `Request Filters`, how to write them, and you applied some real-world examples. also you saw how to customize the routes and methods to apply filters on.

The full source code [https://github.com/ahmedash95/java-spring-request-filters](https://github.com/ahmedash95/java-spring-request-filters)

For more, you can check [baeldung.com/spring-boot-add-filter](https://www.baeldung.com/spring-boot-add-filter)

### Note
I'm still learning Java and spring. so if you see any mistakes or a better way in writing Filters Your comments and suggestions will be greatly appreciated.