Merge pull request #153 from lepdou/2020/custom_ratelimit_tip

Support custom rate limit reject response info
pull/159/head
Haotian Zhang 3 years ago committed by GitHub
commit 5aad3dfb2c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -9,5 +9,6 @@
- [Bugfix: fix circuitbreaker http code greater than 400 as fail response bug](https://github.com/Tencent/spring-cloud-tencent/pull/118) - [Bugfix: fix circuitbreaker http code greater than 400 as fail response bug](https://github.com/Tencent/spring-cloud-tencent/pull/118)
- [Feat: optimize router dependency](https://github.com/Tencent/spring-cloud-tencent/pull/115) - [Feat: optimize router dependency](https://github.com/Tencent/spring-cloud-tencent/pull/115)
- [feat:add switch of polaris, discovery and register.](https://github.com/Tencent/spring-cloud-tencent/pull/129) - [feat:add switch of polaris, discovery and register.](https://github.com/Tencent/spring-cloud-tencent/pull/129)
- [Feature: Support custom rate limit reject response info](https://github.com/Tencent/spring-cloud-tencent/pull/153)
- [Feature: Remove spring-javaformat-maven-plugin](https://github.com/Tencent/spring-cloud-tencent/pull/151) - [Feature: Remove spring-javaformat-maven-plugin](https://github.com/Tencent/spring-cloud-tencent/pull/151)
- [feat:optimize config server address.](https://github.com/Tencent/spring-cloud-tencent/pull/149) - [feat:optimize config server address.](https://github.com/Tencent/spring-cloud-tencent/pull/149)

@ -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;
}
}

@ -49,6 +49,11 @@ import static javax.servlet.DispatcherType.REQUEST;
@ConditionalOnProperty(name = "spring.cloud.polaris.ratelimit.enabled", matchIfMissing = true) @ConditionalOnProperty(name = "spring.cloud.polaris.ratelimit.enabled", matchIfMissing = true)
public class RateLimitConfiguration { public class RateLimitConfiguration {
@Bean
public PolarisRateLimitProperties polarisRateLimitProperties() {
return new PolarisRateLimitProperties();
}
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public LimitAPI limitAPI(SDKContext polarisContext) { public LimitAPI limitAPI(SDKContext polarisContext) {
@ -65,8 +70,10 @@ public class RateLimitConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public QuotaCheckServletFilter quotaCheckFilter(LimitAPI limitAPI, public QuotaCheckServletFilter quotaCheckFilter(LimitAPI limitAPI,
@Nullable PolarisRateLimiterLabelServletResolver labelResolver) { @Nullable PolarisRateLimiterLabelServletResolver labelResolver,
return new QuotaCheckServletFilter(limitAPI, labelResolver); PolarisRateLimitProperties polarisRateLimitProperties) {
return new QuotaCheckServletFilter(limitAPI, labelResolver,
polarisRateLimitProperties);
} }
@Bean @Bean
@ -91,8 +98,10 @@ public class RateLimitConfiguration {
@Bean @Bean
public QuotaCheckReactiveFilter quotaCheckReactiveFilter(LimitAPI limitAPI, public QuotaCheckReactiveFilter quotaCheckReactiveFilter(LimitAPI limitAPI,
@Nullable PolarisRateLimiterLabelReactiveResolver labelResolver) { @Nullable PolarisRateLimiterLabelReactiveResolver labelResolver,
return new QuotaCheckReactiveFilter(limitAPI, 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.HashMap;
import java.util.Map; import java.util.Map;
import javax.annotation.PostConstruct;
import com.tencent.cloud.common.metadata.MetadataContext; 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.constant.RateLimitConstant;
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelReactiveResolver; import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelReactiveResolver;
import com.tencent.cloud.polaris.ratelimit.utils.QuotaCheckUtils; 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.core.LimitAPI;
import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse; import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse;
import com.tencent.polaris.ratelimit.api.rpc.QuotaResultCode; 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.Ordered;
import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
@ -59,9 +62,21 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered {
private final PolarisRateLimiterLabelReactiveResolver labelResolver; private final PolarisRateLimiterLabelReactiveResolver labelResolver;
public QuotaCheckReactiveFilter(LimitAPI limitAPI, PolarisRateLimiterLabelReactiveResolver labelResolver) { private final PolarisRateLimitProperties polarisRateLimitProperties;
private String rejectTips;
public QuotaCheckReactiveFilter(LimitAPI limitAPI,
PolarisRateLimiterLabelReactiveResolver labelResolver,
PolarisRateLimitProperties polarisRateLimitProperties) {
this.limitAPI = limitAPI; this.limitAPI = limitAPI;
this.labelResolver = labelResolver; this.labelResolver = labelResolver;
this.polarisRateLimitProperties = polarisRateLimitProperties;
}
@PostConstruct
public void init() {
rejectTips = RateLimitUtils.getRejectTips(polarisRateLimitProperties);
} }
@Override @Override
@ -101,10 +116,10 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered {
if (quotaResponse.getCode() == QuotaResultCode.QuotaResultLimited) { if (quotaResponse.getCode() == QuotaResultCode.QuotaResultLimited) {
ServerHttpResponse response = exchange.getResponse(); ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS); response.setRawStatusCode(polarisRateLimitProperties.getRejectHttpCode());
response.getHeaders().setContentType(MediaType.APPLICATION_JSON); response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
DataBuffer dataBuffer = response.bufferFactory().allocateBuffer() 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)); return response.writeWith(Mono.just(dataBuffer));
} }
} }

@ -22,15 +22,18 @@ import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import javax.annotation.PostConstruct;
import javax.servlet.FilterChain; import javax.servlet.FilterChain;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import com.tencent.cloud.common.metadata.MetadataContext; 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.constant.RateLimitConstant;
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelServletResolver; import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelServletResolver;
import com.tencent.cloud.polaris.ratelimit.utils.QuotaCheckUtils; 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.core.LimitAPI;
import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse; import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse;
import com.tencent.polaris.ratelimit.api.rpc.QuotaResultCode; import com.tencent.polaris.ratelimit.api.rpc.QuotaResultCode;
@ -43,7 +46,6 @@ import org.springframework.util.CollectionUtils;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import static com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant.LABEL_METHOD; 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. * Servlet filter to check quota.
@ -59,9 +61,21 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter {
private final PolarisRateLimiterLabelServletResolver labelResolver; private final PolarisRateLimiterLabelServletResolver labelResolver;
public QuotaCheckServletFilter(LimitAPI limitAPI, PolarisRateLimiterLabelServletResolver labelResolver) { private final PolarisRateLimitProperties polarisRateLimitProperties;
private String rejectTips;
public QuotaCheckServletFilter(LimitAPI limitAPI,
PolarisRateLimiterLabelServletResolver labelResolver,
PolarisRateLimitProperties polarisRateLimitProperties) {
this.limitAPI = limitAPI; this.limitAPI = limitAPI;
this.labelResolver = labelResolver; this.labelResolver = labelResolver;
this.polarisRateLimitProperties = polarisRateLimitProperties;
}
@PostConstruct
public void init() {
rejectTips = RateLimitUtils.getRejectTips(polarisRateLimitProperties);
} }
@Override @Override
@ -96,8 +110,8 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter {
QuotaResponse quotaResponse = QuotaCheckUtils.getQuota(limitAPI, localNamespace, localService, 1, labels, QuotaResponse quotaResponse = QuotaCheckUtils.getQuota(limitAPI, localNamespace, localService, 1, labels,
null); null);
if (quotaResponse.getCode() == QuotaResultCode.QuotaResultLimited) { if (quotaResponse.getCode() == QuotaResultCode.QuotaResultLimited) {
response.setStatus(TOO_MANY_REQUESTS.value()); response.setStatus(polarisRateLimitProperties.getRejectHttpCode());
response.getWriter().write(RateLimitConstant.QUOTA_LIMITED_INFO); response.getWriter().write(rejectTips);
} }
else { else {
filterChain.doFilter(request, response); 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", "type": "java.lang.Boolean",
"defaultValue": true, "defaultValue": true,
"description": "Enable polaris rate limit or not." "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();
}
}

@ -24,4 +24,13 @@
</dependency> </dependency>
</dependencies> </dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project> </project>

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

Loading…
Cancel
Save