Skip to content

Commit

Permalink
feat: add rate limiter for comment endpoint (#4084)
Browse files Browse the repository at this point in the history
#### What type of PR is this?

/kind feature
/kind core

#### What this PR does / why we need it:

This PR limited comment creation at a rate of 10 per minute.

See #4044 for more.

#### Special notes for your reviewer:
1. Start Halo.
2. Create 11 new comments
3. Check the response.

#### Does this PR introduce a user-facing change?

```release-note
增加发表评论频率限制功能
```
  • Loading branch information
JackyLiang522 authored Jun 26, 2023
1 parent f37085f commit d28f607
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import static org.springdoc.core.fn.builders.requestbody.Builder.requestBodyBuilder;

import com.fasterxml.jackson.annotation.JsonIgnore;
import io.github.resilience4j.ratelimiter.RateLimiterRegistry;
import io.github.resilience4j.reactor.ratelimiter.operator.RateLimiterOperator;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Schema;
Expand All @@ -20,6 +22,7 @@
import org.apache.commons.lang3.StringUtils;
import org.springdoc.core.fn.builders.schema.Builder;
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
import org.springframework.context.MessageSource;
import org.springframework.data.domain.Sort;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
Expand Down Expand Up @@ -65,6 +68,8 @@ public class CommentFinderEndpoint implements CustomEndpoint {
private final CommentService commentService;
private final ReplyService replyService;
private final SystemConfigurableEnvironmentFetcher environmentFetcher;
private final RateLimiterRegistry rateLimiterRegistry;
private final MessageSource messageSource;

@Override
public RouterFunction<ServerResponse> endpoint() {
Expand Down Expand Up @@ -152,9 +157,17 @@ Mono<ServerResponse> createComment(ServerRequest request) {
comment.getSpec().setUserAgent(HaloUtils.userAgentFrom(request));
return commentService.create(comment);
})
.transformDeferred(createIpBasedRateLimiter(request))
.flatMap(comment -> ServerResponse.ok().bodyValue(comment));
}

private <T> RateLimiterOperator<T> createIpBasedRateLimiter(ServerRequest request) {
var clientIp = IpAddressUtils.getIpAddress(request);
var rateLimiter = rateLimiterRegistry.rateLimiter("comment-creation-from-ip-" + clientIp,
"comment-creation");
return RateLimiterOperator.of(rateLimiter);
}

Mono<ServerResponse> createReply(ServerRequest request) {
String commentName = request.pathVariable("name");
return request.bodyToMono(ReplyRequest.class)
Expand Down
5 changes: 5 additions & 0 deletions application/src/main/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,8 @@ resilience4j.ratelimiter:
limitForPeriod: 3
limitRefreshPeriod: 1m
timeoutDuration: 0
comment-creation:
limitForPeriod: 10
limitRefreshPeriod: 1m
timeoutDuration: 10s

Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import io.github.resilience4j.ratelimiter.RateLimiter;
import io.github.resilience4j.ratelimiter.RateLimiterConfig;
import io.github.resilience4j.ratelimiter.RateLimiterRegistry;
import java.time.Duration;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -54,6 +59,9 @@ class CommentFinderEndpointTest {
@Mock
private ReplyService replyService;

@Mock
private RateLimiterRegistry rateLimiterRegistry;

@InjectMocks
private CommentFinderEndpoint commentFinderEndpoint;

Expand Down Expand Up @@ -132,6 +140,15 @@ void listCommentReplies() {
void createComment() {
when(commentService.create(any())).thenReturn(Mono.empty());

RateLimiterConfig config = RateLimiterConfig.custom()
.limitForPeriod(10)
.limitRefreshPeriod(Duration.ofSeconds(1))
.timeoutDuration(Duration.ofSeconds(10))
.build();
RateLimiter rateLimiter = RateLimiter.of("comment-creation-from-ip-" + "0:0:0:0:0:0:0:0",
config);
when(rateLimiterRegistry.rateLimiter(anyString(), anyString())).thenReturn(rateLimiter);

final CommentRequest commentRequest = new CommentRequest();
Ref ref = new Ref();
ref.setGroup("content.halo.run");
Expand Down

0 comments on commit d28f607

Please sign in to comment.