diff --git a/build.gradle.kts b/build.gradle.kts index fc5abdc..cb75270 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -19,8 +19,12 @@ repositories { dependencies { implementation("org.springframework.boot:spring-boot-starter") + implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.springframework.boot:spring-boot-starter-aop") + compileOnly("org.projectlombok:lombok") testImplementation("org.springframework.boot:spring-boot-starter-test") testRuntimeOnly("org.junit.platform:junit-platform-launcher") + annotationProcessor("org.projectlombok:lombok") } tasks.withType { diff --git a/src/main/java/org/javaspringcourse/Exception/ErrorResponse.java b/src/main/java/org/javaspringcourse/Exception/ErrorResponse.java new file mode 100644 index 0000000..372598c --- /dev/null +++ b/src/main/java/org/javaspringcourse/Exception/ErrorResponse.java @@ -0,0 +1,4 @@ +package org.javaspringcourse.Exception; + +public record ErrorResponse(String message) { +} diff --git a/src/main/java/org/javaspringcourse/Exception/TooManyRequestsException.java b/src/main/java/org/javaspringcourse/Exception/TooManyRequestsException.java new file mode 100644 index 0000000..5341ba7 --- /dev/null +++ b/src/main/java/org/javaspringcourse/Exception/TooManyRequestsException.java @@ -0,0 +1,7 @@ +package org.javaspringcourse.Exception; + +public class TooManyRequestsException extends RuntimeException { + public TooManyRequestsException(String message) { + super(message); + } +} diff --git a/src/main/java/org/javaspringcourse/Exception/TooManyRequestsExceptionHandler.java b/src/main/java/org/javaspringcourse/Exception/TooManyRequestsExceptionHandler.java new file mode 100644 index 0000000..e4e8b98 --- /dev/null +++ b/src/main/java/org/javaspringcourse/Exception/TooManyRequestsExceptionHandler.java @@ -0,0 +1,16 @@ +package org.javaspringcourse.Exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class TooManyRequestsExceptionHandler { + + @ResponseStatus(HttpStatus.TOO_MANY_REQUESTS) + @ExceptionHandler(TooManyRequestsException.class) + public ErrorResponse handle(TooManyRequestsException e) { + return new ErrorResponse(e.getMessage()); + } +} diff --git a/src/main/java/org/javaspringcourse/RequestsLimit/RequestsLimit.java b/src/main/java/org/javaspringcourse/RequestsLimit/RequestsLimit.java new file mode 100644 index 0000000..d754f28 --- /dev/null +++ b/src/main/java/org/javaspringcourse/RequestsLimit/RequestsLimit.java @@ -0,0 +1,11 @@ +package org.javaspringcourse.RequestsLimit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface RequestsLimit { +} diff --git a/src/main/java/org/javaspringcourse/RequestsLimit/RequestsLimitAspect.java b/src/main/java/org/javaspringcourse/RequestsLimit/RequestsLimitAspect.java new file mode 100644 index 0000000..f6ecb1f --- /dev/null +++ b/src/main/java/org/javaspringcourse/RequestsLimit/RequestsLimitAspect.java @@ -0,0 +1,23 @@ +package org.javaspringcourse.RequestsLimit; + +import lombok.RequiredArgsConstructor; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.javaspringcourse.Exception.TooManyRequestsException; +import org.springframework.stereotype.Component; + +@Component +@Aspect +@RequiredArgsConstructor +public class RequestsLimitAspect { + private final RequestsLimitService requestsLimitService; + + @Before(value = "@annotation(RequestsLimit)") + private void before(JoinPoint joinPoint) { + if (!requestsLimitService.canHandleRequest( + joinPoint.getSignature().getName())) { + throw new TooManyRequestsException("Too many requests."); + } + } +} diff --git a/src/main/java/org/javaspringcourse/RequestsLimit/RequestsLimitService.java b/src/main/java/org/javaspringcourse/RequestsLimit/RequestsLimitService.java new file mode 100644 index 0000000..87c875f --- /dev/null +++ b/src/main/java/org/javaspringcourse/RequestsLimit/RequestsLimitService.java @@ -0,0 +1,24 @@ +package org.javaspringcourse.RequestsLimit; + +import lombok.RequiredArgsConstructor; +import org.javaspringcourse.props.RequestsLimitProps; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; + +@Service +@RequiredArgsConstructor +public class RequestsLimitService { + private final RequestsLimitProps requestsLimitProps; + private final Map requestsCount = new HashMap<>(); + + public boolean canHandleRequest(String apiName) { + if (requestsCount.containsKey(apiName)) { + requestsCount.put(apiName, requestsCount.get(apiName) + 1); + } else { + requestsCount.put(apiName, 1); + } + return requestsCount.get(apiName) <= requestsLimitProps.getMax(); + } +} diff --git a/src/main/java/org/javaspringcourse/config/AspectConfig.java b/src/main/java/org/javaspringcourse/config/AspectConfig.java new file mode 100644 index 0000000..26d641b --- /dev/null +++ b/src/main/java/org/javaspringcourse/config/AspectConfig.java @@ -0,0 +1,9 @@ +package org.javaspringcourse.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; + +@Configuration +@EnableAspectJAutoProxy +public class AspectConfig { +} diff --git a/src/main/java/org/javaspringcourse/controller/ApiController.java b/src/main/java/org/javaspringcourse/controller/ApiController.java new file mode 100644 index 0000000..d20e328 --- /dev/null +++ b/src/main/java/org/javaspringcourse/controller/ApiController.java @@ -0,0 +1,23 @@ +package org.javaspringcourse.controller; + +import org.javaspringcourse.RequestsLimit.RequestsLimit; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api") +public class ApiController { + + @RequestsLimit + @GetMapping("/getHelloWorld") + public String getHelloWorld() { + return "Hello World"; + } + + @RequestsLimit + @GetMapping("/getByeWorld") + public String getByeWorld() { + return "Bye World"; + } +} diff --git a/src/main/java/org/javaspringcourse/props/RequestsLimitProps.java b/src/main/java/org/javaspringcourse/props/RequestsLimitProps.java new file mode 100644 index 0000000..11af62c --- /dev/null +++ b/src/main/java/org/javaspringcourse/props/RequestsLimitProps.java @@ -0,0 +1,14 @@ +package org.javaspringcourse.props; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Getter +@Setter +@Component +@ConfigurationProperties(prefix = "requests-limit") +public class RequestsLimitProps { + private int max; +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 61bae71..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -spring.application.name=java-spring-course diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml new file mode 100644 index 0000000..8a5ebb5 --- /dev/null +++ b/src/main/resources/application.yaml @@ -0,0 +1,5 @@ +server: + port: 8080 + +requests-limit: + max: 2 \ No newline at end of file