Support custom rate limit reject response info

pull/128/head
lepdou 3 years ago
parent 747b6b2b19
commit e655ef74c2

@ -1,4 +1,5 @@
# Change Log
---
- [Feature: Support custom rate limit reject response info](https://github.com/Tencent/spring-cloud-tencent/pull/128)

@ -0,0 +1,71 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.ratelimit.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.http.HttpStatus;
/**
* The properties for rate limit.
*
* @author lepdou 2022-04-20
*/
@ConfigurationProperties("spring.cloud.polaris.ratelimit")
public class PolarisRateLimitProperties {
/**
* custom tips when reject request.
*/
private String rejectRequestTips;
/**
* context file path.
*/
private String rejectRequestTipsFilePath;
/**
* custom http code when reject request.
*/
private int rejectHttpCode = HttpStatus.TOO_MANY_REQUESTS.value();
public String getRejectRequestTips() {
return rejectRequestTips;
}
public void setRejectRequestTips(String rejectRequestTips) {
this.rejectRequestTips = rejectRequestTips;
}
public String getRejectRequestTipsFilePath() {
return rejectRequestTipsFilePath;
}
public void setRejectRequestTipsFilePath(String rejectRequestTipsFilePath) {
this.rejectRequestTipsFilePath = rejectRequestTipsFilePath;
}
public int getRejectHttpCode() {
return rejectHttpCode;
}
public void setRejectHttpCode(int rejectHttpCode) {
this.rejectHttpCode = rejectHttpCode;
}
}

@ -51,6 +51,11 @@ import static javax.servlet.DispatcherType.REQUEST;
matchIfMissing = true)
public class RateLimitConfiguration {
@Bean
public PolarisRateLimitProperties polarisRateLimitProperties() {
return new PolarisRateLimitProperties();
}
@Bean
@ConditionalOnMissingBean
public LimitAPI limitAPI(SDKContext polarisContext) {
@ -67,8 +72,10 @@ public class RateLimitConfiguration {
@Bean
@ConditionalOnMissingBean
public QuotaCheckServletFilter quotaCheckFilter(LimitAPI limitAPI,
@Nullable PolarisRateLimiterLabelServletResolver labelResolver) {
return new QuotaCheckServletFilter(limitAPI, labelResolver);
@Nullable PolarisRateLimiterLabelServletResolver labelResolver,
PolarisRateLimitProperties polarisRateLimitProperties) {
return new QuotaCheckServletFilter(limitAPI, labelResolver,
polarisRateLimitProperties);
}
@Bean
@ -93,8 +100,10 @@ public class RateLimitConfiguration {
@Bean
public QuotaCheckReactiveFilter quotaCheckReactiveFilter(LimitAPI limitAPI,
@Nullable PolarisRateLimiterLabelReactiveResolver labelResolver) {
return new QuotaCheckReactiveFilter(limitAPI, labelResolver);
@Nullable PolarisRateLimiterLabelReactiveResolver labelResolver,
PolarisRateLimitProperties polarisRateLimitProperties) {
return new QuotaCheckReactiveFilter(limitAPI, labelResolver,
polarisRateLimitProperties);
}
}

@ -22,10 +22,14 @@ import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.PostConstruct;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant;
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelReactiveResolver;
import com.tencent.cloud.polaris.ratelimit.utils.QuotaCheckUtils;
import com.tencent.cloud.polaris.ratelimit.utils.RateLimitUtils;
import com.tencent.polaris.ratelimit.api.core.LimitAPI;
import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse;
import com.tencent.polaris.ratelimit.api.rpc.QuotaResultCode;
@ -36,7 +40,6 @@ import reactor.core.publisher.Mono;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.CollectionUtils;
@ -60,10 +63,21 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered {
private final PolarisRateLimiterLabelReactiveResolver labelResolver;
private final PolarisRateLimitProperties polarisRateLimitProperties;
private String rejectTips;
public QuotaCheckReactiveFilter(LimitAPI limitAPI,
PolarisRateLimiterLabelReactiveResolver labelResolver) {
PolarisRateLimiterLabelReactiveResolver labelResolver,
PolarisRateLimitProperties polarisRateLimitProperties) {
this.limitAPI = limitAPI;
this.labelResolver = labelResolver;
this.polarisRateLimitProperties = polarisRateLimitProperties;
}
@PostConstruct
public void init() {
rejectTips = RateLimitUtils.getRejectTips(polarisRateLimitProperties);
}
@Override
@ -104,11 +118,10 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered {
if (quotaResponse.getCode() == QuotaResultCode.QuotaResultLimited) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
response.setRawStatusCode(polarisRateLimitProperties.getRejectHttpCode());
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
DataBuffer dataBuffer = response.bufferFactory().allocateBuffer()
.write(RateLimitConstant.QUOTA_LIMITED_INFO
.getBytes(StandardCharsets.UTF_8));
.write(rejectTips.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(dataBuffer));
}
}

@ -22,15 +22,18 @@ import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant;
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelServletResolver;
import com.tencent.cloud.polaris.ratelimit.utils.QuotaCheckUtils;
import com.tencent.cloud.polaris.ratelimit.utils.RateLimitUtils;
import com.tencent.polaris.ratelimit.api.core.LimitAPI;
import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse;
import com.tencent.polaris.ratelimit.api.rpc.QuotaResultCode;
@ -43,7 +46,6 @@ import org.springframework.util.CollectionUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import static com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant.LABEL_METHOD;
import static org.springframework.http.HttpStatus.TOO_MANY_REQUESTS;
/**
* Servlet filter to check quota.
@ -60,10 +62,21 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter {
private final PolarisRateLimiterLabelServletResolver labelResolver;
private final PolarisRateLimitProperties polarisRateLimitProperties;
private String rejectTips;
public QuotaCheckServletFilter(LimitAPI limitAPI,
PolarisRateLimiterLabelServletResolver labelResolver) {
PolarisRateLimiterLabelServletResolver labelResolver,
PolarisRateLimitProperties polarisRateLimitProperties) {
this.limitAPI = limitAPI;
this.labelResolver = labelResolver;
this.polarisRateLimitProperties = polarisRateLimitProperties;
}
@PostConstruct
public void init() {
rejectTips = RateLimitUtils.getRejectTips(polarisRateLimitProperties);
}
@Override
@ -100,8 +113,8 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter {
QuotaResponse quotaResponse = QuotaCheckUtils.getQuota(limitAPI,
localNamespace, localService, 1, labels, null);
if (quotaResponse.getCode() == QuotaResultCode.QuotaResultLimited) {
response.setStatus(TOO_MANY_REQUESTS.value());
response.getWriter().write(RateLimitConstant.QUOTA_LIMITED_INFO);
response.setStatus(polarisRateLimitProperties.getRejectHttpCode());
response.getWriter().write(rejectTips);
}
else {
filterChain.doFilter(request, response);

@ -0,0 +1,70 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.ratelimit.utils;
import com.tencent.cloud.common.util.ResourceFileUtils;
import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties;
import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant;
import com.tencent.cloud.polaris.ratelimit.filter.QuotaCheckServletFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
/**
* Rate limit utils.
*
* @author lepdou 2022-04-20
*/
public final class RateLimitUtils {
private static final Logger LOG = LoggerFactory
.getLogger(QuotaCheckServletFilter.class);
private RateLimitUtils() {
}
public static String getRejectTips(
PolarisRateLimitProperties polarisRateLimitProperties) {
String tips = polarisRateLimitProperties.getRejectRequestTips();
if (!StringUtils.isEmpty(tips)) {
return tips;
}
String rejectFilePath = polarisRateLimitProperties.getRejectRequestTipsFilePath();
if (!StringUtils.isEmpty(rejectFilePath)) {
try {
tips = ResourceFileUtils.readFile(rejectFilePath);
}
catch (Exception e) {
LOG.error("[RateLimit] Read custom reject tips file error. path = {}",
rejectFilePath, e);
}
}
if (!StringUtils.isEmpty(tips)) {
return tips;
}
return RateLimitConstant.QUOTA_LIMITED_INFO;
}
}

@ -5,6 +5,24 @@
"type": "java.lang.Boolean",
"defaultValue": true,
"description": "Enable polaris rate limit or not."
},
{
"name": "spring.cloud.polaris.ratelimit.rejectRequestTips",
"type": "java.lang.String",
"defaultValue": "",
"description": "Custom tips when reject request."
},
{
"name": "spring.cloud.polaris.ratelimit.rejectRequestTipsFilePath",
"type": "java.lang.String",
"defaultValue": "",
"description": "Custom tips file path when reject request."
},
{
"name": "spring.cloud.polaris.ratelimit.rejectHttpCode",
"type": "java.lang.Integer",
"defaultValue": "429",
"description": "Custom http code when reject request."
}
]
}

@ -0,0 +1,54 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.common.util;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import org.springframework.core.io.ClassPathResource;
/**
* Read file content from classpath resource.
*
* @author lepdou 2022-04-20
*/
public final class ResourceFileUtils {
private ResourceFileUtils() {
}
public static String readFile(String path) throws IOException {
StringBuilder sb = new StringBuilder();
ClassPathResource classPathResource = new ClassPathResource(path);
if (classPathResource.exists() && classPathResource.isReadable()) {
try (InputStream inputStream = classPathResource.getInputStream()) {
byte[] buffer = new byte[1024 * 10];
int len;
while ((len = inputStream.read(buffer)) != -1) {
sb.append(new String(buffer, 0, len, StandardCharsets.UTF_8));
}
}
}
return sb.toString();
}
}

@ -1,27 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>polaris-ratelimit-example</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>polaris-ratelimit-example</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ratelimit-callee-service</artifactId>
<artifactId>ratelimit-callee-service</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-ratelimit</artifactId>
</dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-ratelimit</artifactId>
</dependency>
</dependencies>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

@ -10,3 +10,4 @@ spring:
enabled: true
ratelimit:
enabled: true
rejectRequestTipsFilePath: reject-tips.html

Loading…
Cancel
Save