diff --git a/CHANGELOG.md b/CHANGELOG.md index 15c44dd5..28359581 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # Change Log --- +- [Feature: Support custom rate limit reject response info](https://github.com/Tencent/spring-cloud-tencent/pull/128) diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/config/PolarisRateLimitProperties.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/config/PolarisRateLimitProperties.java new file mode 100644 index 00000000..d51d90b9 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/config/PolarisRateLimitProperties.java @@ -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; + } + +} diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/config/RateLimitConfiguration.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/config/RateLimitConfiguration.java index 446b4041..cf4d33a9 100644 --- a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/config/RateLimitConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/config/RateLimitConfiguration.java @@ -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); } } diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilter.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilter.java index 0db616f7..9a0db9a7 100644 --- a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilter.java +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilter.java @@ -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)); } } diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilter.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilter.java index 85706d94..ae160109 100644 --- a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilter.java +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilter.java @@ -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); diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/utils/RateLimitUtils.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/utils/RateLimitUtils.java new file mode 100644 index 00000000..2e036909 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/utils/RateLimitUtils.java @@ -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; + } + +} diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/resources/META-INF/additional-spring-configuration-metadata.json index b71a17cc..19b37aeb 100644 --- a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -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." } ] } diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ResourceFileUtils.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ResourceFileUtils.java new file mode 100644 index 00000000..d79bfce8 --- /dev/null +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ResourceFileUtils.java @@ -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(); + } + +} diff --git a/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/pom.xml b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/pom.xml index 62af8391..dd28040b 100644 --- a/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/pom.xml +++ b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/pom.xml @@ -1,27 +1,36 @@ - - polaris-ratelimit-example - com.tencent.cloud - ${revision} - ../pom.xml - - 4.0.0 + 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"> + + polaris-ratelimit-example + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 - ratelimit-callee-service + ratelimit-callee-service - - - org.springframework.boot - spring-boot-starter-web - + + + org.springframework.boot + spring-boot-starter-web + - com.tencent.cloud - spring-cloud-starter-tencent-polaris-ratelimit - + com.tencent.cloud + spring-cloud-starter-tencent-polaris-ratelimit + + + - + + + + org.springframework.boot + spring-boot-maven-plugin + + + diff --git a/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/resources/bootstrap.yml index 8825b0ce..a33fc48f 100644 --- a/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/resources/bootstrap.yml +++ b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/resources/bootstrap.yml @@ -10,3 +10,4 @@ spring: enabled: true ratelimit: enabled: true + rejectRequestTipsFilePath: reject-tips.html diff --git a/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/resources/reject-tips.html b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/resources/reject-tips.html new file mode 100644 index 00000000..693ef256 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-ratelimit-example/ratelimit-callee-service/src/main/resources/reject-tips.html @@ -0,0 +1,5 @@ +

+ + Custom reject content. + +