spring boot ratelimit
14 December 2019
spring interceptor를 이용해 간단한 ratelimiter를 작성해보도록 하겠습니다.
먼저 ConcurrentHashMap을 이용해, key값에 따라 value의 값을 increment할 수 있게 만들어줍니다.
package org.shashaka.io;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
@Component
@Slf4j
public class RateLimitUtils {
public static ConcurrentHashMap rateLimitMap = new ConcurrentHashMap<>();
public AtomicInteger getRate(String key) {
return rateLimitMap.compute(key, (s, atomicInteger) -> {
if (atomicInteger == null) {
atomicInteger = new AtomicInteger(1);
} else {
atomicInteger.incrementAndGet();
}
return atomicInteger;
});
}
public void clearRates() {
rateLimitMap.clear();
}
}
그리고 interceptor를 통해, method가 수행되기 전에 rate를 체크해서 통과, 혹은 에러를 발생시키도록 합니다.
package org.shashaka.io;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
@Slf4j
public class RateLimitInterceptor implements HandlerInterceptor {
private final RateLimitUtils rateLimitUtils;
@Value("${rate.limit.rate}")
private int limitRate;
public RateLimitInterceptor(RateLimitUtils rateLimitUtils) {
this.rateLimitUtils = rateLimitUtils;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String key = request.getHeader("user");
if (key == null) {
return true;
}
int rate = rateLimitUtils.getRate(key).get();
if (rate > limitRate) {
throw new RuntimeException("rate limit over");
}
return true;
}
}
interval을 설정해주기 위해서 scheduling을 사용해 해당 시간이 지나면 다시 init시켜주도록 합니다.
package org.shashaka.io;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableScheduling
@AllArgsConstructor
public class RateLimitConfig implements WebMvcConfigurer {
private final RateLimitInterceptor rateLimitInterceptor;
private final RateLimitUtils rateLimitUtils;
@Scheduled(fixedRateString = "${rate.limit.interval.milliseconds}")
public void clearRateLimit() {
rateLimitUtils.clearRates();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(rateLimitInterceptor);
}
}
local에서 테스트를 해보면 적용한 config값에 따라 동작하는 것을 확인할 수 있습니다.