Implements MVC and Webflux concurrency limits. (#3165)
Creates a MVC HandlerInterceptor and Webflux WebFilter. Also creates a MicrometerMetricRegistry.
This commit is contained in:
1
pom.xml
1
pom.xml
@@ -148,6 +148,7 @@
|
||||
<!-- Not part of the reactor build: build and deploy separately -->
|
||||
<!--module>spring-cloud-netflix-hystrix-contract</module-->
|
||||
<module>spring-cloud-netflix-core</module>
|
||||
<module>spring-cloud-netflix-concurrency-limits</module>
|
||||
<module>spring-cloud-netflix-hystrix-dashboard</module>
|
||||
<module>spring-cloud-netflix-hystrix-stream</module>
|
||||
<module>spring-cloud-netflix-eureka-client</module>
|
||||
|
||||
70
spring-cloud-netflix-concurrency-limits/pom.xml
Normal file
70
spring-cloud-netflix-concurrency-limits/pom.xml
Normal file
@@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix</artifactId>
|
||||
<version>2.1.0.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<artifactId>spring-cloud-netflix-concurrency-limits</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Spring Cloud Netflix Concurrency Limits</name>
|
||||
<url>https://projects.spring.io/spring-cloud/</url>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.netflix.concurrency-limits</groupId>
|
||||
<artifactId>concurrency-limits-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.concurrency-limits</groupId>
|
||||
<artifactId>concurrency-limits-servlet</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<!--<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-commons</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-context</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-test-support</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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 org.springframework.cloud.netflix.concurrency.limits.micrometer;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.netflix.concurrency.limits.MetricRegistry;
|
||||
import io.micrometer.core.instrument.DistributionSummary;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.Tags;
|
||||
|
||||
public class MicrometerMetricRegistry implements MetricRegistry {
|
||||
|
||||
private final MeterRegistry meterRegistry;
|
||||
//TODO: baseId?
|
||||
|
||||
public MicrometerMetricRegistry(MeterRegistry meterRegistry) {
|
||||
this.meterRegistry = meterRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SampleListener registerDistribution(String id, String... tagNameValuePairs) {
|
||||
DistributionSummary summary = this.meterRegistry.summary(id, tagNameValuePairs);
|
||||
return value -> summary.record(value.longValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerGauge(String id, Supplier<Number> supplier, String... tagNameValuePairs) {
|
||||
this.meterRegistry.gauge(id, Tags.of(tagNameValuePairs), supplier.get());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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 org.springframework.cloud.netflix.concurrency.limits.reactive;
|
||||
|
||||
import com.netflix.concurrency.limits.Limiter;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
|
||||
public class ConcurrencyLimitsWebFilter implements WebFilter {
|
||||
|
||||
private final Limiter<ServerWebExchange> limiter;
|
||||
|
||||
public ConcurrencyLimitsWebFilter(Limiter<ServerWebExchange> limiter) {
|
||||
this.limiter = limiter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||
return limiter.acquire(exchange)
|
||||
.map(listener -> chain.filter(exchange)
|
||||
.doOnSuccess(v -> listener.onSuccess())
|
||||
.doOnError(throwable -> listener.onIgnore()))
|
||||
.orElseGet(() -> {
|
||||
exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
|
||||
//TODO: set body
|
||||
return exchange.getResponse().setComplete();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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 org.springframework.cloud.netflix.concurrency.limits.reactive;
|
||||
|
||||
import com.netflix.concurrency.limits.Limiter;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnWebApplication(type = Type.REACTIVE)
|
||||
@ConditionalOnClass({WebFilter.class, Mono.class})
|
||||
public class ReactiveConcurrencyLimitsAutoConfiguration {
|
||||
|
||||
private final ObjectProvider<Consumer<ServerWebExchangeLimiterBuilder>> configurerProvider;
|
||||
|
||||
public ReactiveConcurrencyLimitsAutoConfiguration(ObjectProvider<Consumer<ServerWebExchangeLimiterBuilder>> configurerProvider) {
|
||||
this.configurerProvider = configurerProvider;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public Limiter<ServerWebExchange> webfluxLimiter() {
|
||||
ServerWebExchangeLimiterBuilder builder = new ServerWebExchangeLimiterBuilder();
|
||||
|
||||
this.configurerProvider.ifAvailable(consumer -> consumer.accept(builder));
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ConcurrencyLimitsWebFilter concurrencyLimitsWebFilter(Limiter<ServerWebExchange> limiter) {
|
||||
return new ConcurrencyLimitsWebFilter(limiter);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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 org.springframework.cloud.netflix.concurrency.limits.reactive;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.netflix.concurrency.limits.limiter.AbstractPartitionedLimiter;
|
||||
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
public class ServerWebExchangeLimiterBuilder extends AbstractPartitionedLimiter.Builder<ServerWebExchangeLimiterBuilder, ServerWebExchange> {
|
||||
/**
|
||||
* Partition the limit by header
|
||||
* @return Chainable builder
|
||||
*/
|
||||
public ServerWebExchangeLimiterBuilder partitionByHeader(String name) {
|
||||
return partitionResolver(exchange -> exchange.getRequest().getHeaders().getFirst(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Partition the limit by {@link Principal}. Percentages of the limit are partitioned to named
|
||||
* groups. Group membership is derived from the provided mapping function.
|
||||
* @param principalToGroup Mapping function from {@link Principal} to a named group.
|
||||
* @param configurer Configuration function though which group percentages may be specified
|
||||
* Unspecified group values may only use excess capacity.
|
||||
* @return Chainable builder
|
||||
*/
|
||||
/*public ServerWebExchangeLimiterBuilder partitionByUserPrincipal(Function<Principal, String> principalToGroup, Consumer<LookupPartitionStrategy.Builder<ServerWebExchange>> configurer) {
|
||||
return partitionResolver(
|
||||
exchange -> Optional.ofNullable(request.getUserPrincipal()).map(principalToGroup).orElse(null),
|
||||
configurer);
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Partition the limit by request attribute
|
||||
* @return Chainable builder
|
||||
*/
|
||||
public ServerWebExchangeLimiterBuilder partitionByAttribute(String name) {
|
||||
return partitionResolver( exchange -> exchange.getAttribute(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Partition the limit by request parameter
|
||||
* @return Chainable builder
|
||||
*/
|
||||
public ServerWebExchangeLimiterBuilder partitionByParameter(String name) {
|
||||
return partitionResolver(exchange -> exchange.getRequest().getQueryParams().getFirst(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Partition the limit by the full path. Percentages of the limit are partitioned to named
|
||||
* groups. Group membership is derived from the provided mapping function.
|
||||
* @param pathToGroup Mapping function from full path to a named group.
|
||||
* @return Chainable builder
|
||||
*/
|
||||
public ServerWebExchangeLimiterBuilder partitionByPathInfo(Function<String, String> pathToGroup) {
|
||||
return partitionResolver(
|
||||
exchange -> {
|
||||
//TODO: pathWithinApplication?
|
||||
String path = exchange.getRequest().getPath().contextPath().value();
|
||||
return Optional.ofNullable(path).map(pathToGroup).orElse(null);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ServerWebExchangeLimiterBuilder self() {
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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 org.springframework.cloud.netflix.concurrency.limits.web;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.netflix.concurrency.limits.Limiter;
|
||||
import com.netflix.concurrency.limits.Limiter.Listener;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
public class ConcurrencyLimitsHandlerInterceptor implements HandlerInterceptor {
|
||||
|
||||
private final Limiter<HttpServletRequest> limiter;
|
||||
|
||||
public ConcurrencyLimitsHandlerInterceptor(Limiter<HttpServletRequest> limiter) {
|
||||
this.limiter = limiter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
Optional<Listener> listener = limiter.acquire(request);
|
||||
if (listener.isPresent()) {
|
||||
request.setAttribute("concurrency_limiter_listener", listener.get());
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
//TODO: headers with information?
|
||||
/*response.sendError(HttpStatus.TOO_MANY_REQUESTS.value());
|
||||
response.getWriter().print("Concurrency limit exceeded");*/
|
||||
response.sendError(HttpStatus.TOO_MANY_REQUESTS.value(),
|
||||
"Concurrency limit exceeded");
|
||||
} catch (IOException e) {
|
||||
// ignore
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
|
||||
Listener listener = (Listener) request.getAttribute("concurrency_limiter_listener");
|
||||
if (listener != null) {
|
||||
if (ex != null) {
|
||||
listener.onIgnore();
|
||||
} else {
|
||||
listener.onSuccess();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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 org.springframework.cloud.netflix.concurrency.limits.web;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import com.netflix.concurrency.limits.Limiter;
|
||||
import com.netflix.concurrency.limits.servlet.ServletLimiterBuilder;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnWebApplication(type = Type.SERVLET)
|
||||
@ConditionalOnClass({HttpServletRequest.class, HandlerInterceptor.class})
|
||||
public class MvcConcurrencyLimitsAutoConfiguration implements WebMvcConfigurer {
|
||||
|
||||
private final ObjectProvider<Consumer<ServletLimiterBuilder>> configurerProvider;
|
||||
|
||||
public MvcConcurrencyLimitsAutoConfiguration(ObjectProvider<Consumer<ServletLimiterBuilder>> configurerProvider) {
|
||||
this.configurerProvider = configurerProvider;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public Limiter<HttpServletRequest> servletLimiter() {
|
||||
ServletLimiterBuilder builder = new ServletLimiterBuilder();
|
||||
|
||||
this.configurerProvider.ifAvailable(consumer -> consumer.accept(builder));
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
protected static class HandlerInterceptorConfiguration implements WebMvcConfigurer {
|
||||
|
||||
@Autowired
|
||||
private Limiter<HttpServletRequest> limiter;
|
||||
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(new ConcurrencyLimitsHandlerInterceptor(limiter));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.springframework.cloud.netflix.concurrency.limits.reactive.ReactiveConcurrencyLimitsAutoConfiguration,\
|
||||
org.springframework.cloud.netflix.concurrency.limits.web.MvcConcurrencyLimitsAutoConfiguration
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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 org.springframework.cloud.netflix.concurrency.limits.micrometer;
|
||||
|
||||
import io.micrometer.core.instrument.Gauge;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class MicrometerMetricRegistryTests {
|
||||
@Test
|
||||
public void testGuage() {
|
||||
MeterRegistry registry = new SimpleMeterRegistry();
|
||||
MicrometerMetricRegistry metricRegistry = new MicrometerMetricRegistry(registry);
|
||||
|
||||
metricRegistry.registerGauge("bar", () -> 10);
|
||||
|
||||
Gauge bar = registry.get("bar").gauge();
|
||||
|
||||
assertThat(bar.value()).isEqualTo(10.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore //FIXME: micrometer doesn't allow recreating a gauge
|
||||
public void testUnregister() {
|
||||
MeterRegistry registry = new SimpleMeterRegistry();
|
||||
MicrometerMetricRegistry metricRegistry = new MicrometerMetricRegistry(registry);
|
||||
|
||||
metricRegistry.registerGauge("bar", () -> 10);
|
||||
metricRegistry.registerGauge("bar", () -> 20);
|
||||
|
||||
Gauge bar = registry.get("bar").gauge();
|
||||
|
||||
assertThat(bar.value()).isEqualTo(20.0);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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 org.springframework.cloud.netflix.concurrency.limits.reactive;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.netflix.concurrency.limits.limit.FixedLimit;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.cloud.netflix.concurrency.limits.test.AbstractConcurrencyLimitsTests;
|
||||
import org.springframework.cloud.test.ClassPathExclusions;
|
||||
import org.springframework.cloud.test.ModifiedClassPathRunner;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.util.SocketUtils;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
@RunWith(ModifiedClassPathRunner.class)
|
||||
@ClassPathExclusions({"spring-boot-starter-tomcat-*", "tomcat-embed-*"})
|
||||
public class ConcurrencyLimitsWebFilterTests extends AbstractConcurrencyLimitsTests {
|
||||
|
||||
private int port;
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
port = SocketUtils.findAvailableTcpPort();
|
||||
client = WebClient.create("http://localhost:"+port);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("Duplicates")
|
||||
public void webFilterWorks() {
|
||||
|
||||
try (ConfigurableApplicationContext context = new SpringApplicationBuilder()
|
||||
.properties("server.port="+port, "spring.main.web-application-type=reactive")
|
||||
.sources(TestConfig.class).run()) {
|
||||
|
||||
assertLimiter(client);
|
||||
}
|
||||
}
|
||||
|
||||
@SpringBootConfiguration
|
||||
@EnableAutoConfiguration
|
||||
@Import(HelloControllerConfiguration.class)
|
||||
protected static class TestConfig {
|
||||
|
||||
@Bean
|
||||
public Consumer<ServerWebExchangeLimiterBuilder> limiterBuilderConfigurer() {
|
||||
return limiterBuilder -> limiterBuilder
|
||||
.limit(FixedLimit.of(1));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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 org.springframework.cloud.netflix.concurrency.limits.test;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
import reactor.util.function.Tuple2;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class AbstractConcurrencyLimitsTests {
|
||||
|
||||
protected WebClient client;
|
||||
|
||||
protected void assertLimiter(WebClient client) {
|
||||
// TODO: assert the body
|
||||
Flux<Tuple2<String, HttpStatus>> flux = Flux.range(1, 100)
|
||||
.flatMap(integer -> client.get().uri("/").exchange(), 4)
|
||||
// .log("reqs", Level.INFO)
|
||||
.flatMap(response -> response.bodyToMono(String.class)
|
||||
.defaultIfEmpty("")
|
||||
/*.log("body2mono", Level.INFO)*/
|
||||
.zipWith(Mono.just(response.statusCode())));
|
||||
|
||||
Responses responses = new Responses();
|
||||
StepVerifier.create(flux)
|
||||
.thenConsumeWhile(response -> true, response -> {
|
||||
HttpStatus status = response.getT2();
|
||||
if (status.equals(HttpStatus.OK)) {
|
||||
responses.success.incrementAndGet();
|
||||
} else if (status.equals(HttpStatus.TOO_MANY_REQUESTS)) {
|
||||
responses.tooManyReqs.incrementAndGet();
|
||||
String body = response.getT1();
|
||||
//TODO: body from handler isn't coming thru
|
||||
// assertThat(body).isEqualTo("Concurrency limit exceeded");
|
||||
} else {
|
||||
responses.other.incrementAndGet();
|
||||
}
|
||||
}).verifyComplete();
|
||||
|
||||
System.out.println("Responses: " + responses);
|
||||
|
||||
assertThat(responses.other).hasValue(0);
|
||||
assertThat(responses.tooManyReqs).hasValueGreaterThanOrEqualTo(1);
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@RestController
|
||||
protected static class HelloControllerConfiguration {
|
||||
|
||||
@GetMapping
|
||||
public String get() {
|
||||
return "Hello";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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 org.springframework.cloud.netflix.concurrency.limits.test;
|
||||
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class Responses {
|
||||
public AtomicInteger success = new AtomicInteger(0);
|
||||
public AtomicInteger tooManyReqs = new AtomicInteger(0);
|
||||
public AtomicInteger other = new AtomicInteger(0);
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringCreator(this)
|
||||
.append("success", success)
|
||||
.append("tooManyReqs", tooManyReqs)
|
||||
.append("other", other)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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 org.springframework.cloud.netflix.concurrency.limits.web;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.netflix.concurrency.limits.limit.FixedLimit;
|
||||
import com.netflix.concurrency.limits.servlet.ServletLimiterBuilder;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.web.server.LocalServerPort;
|
||||
import org.springframework.cloud.netflix.concurrency.limits.test.AbstractConcurrencyLimitsTests;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(properties = "logging.level.reactor.netty=DEBUG", webEnvironment = RANDOM_PORT)
|
||||
public class ConcurrencyLimitsHandlerInterceptorTests extends AbstractConcurrencyLimitsTests {
|
||||
|
||||
@LocalServerPort
|
||||
public int port;
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
client = WebClient.create("http://localhost:"+port);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handlerInterceptorWorks() {
|
||||
assertLimiter(client);
|
||||
}
|
||||
|
||||
@SpringBootConfiguration
|
||||
@EnableAutoConfiguration
|
||||
@Import(HelloControllerConfiguration.class)
|
||||
protected static class TestConfig {
|
||||
|
||||
@Bean
|
||||
public Consumer<ServletLimiterBuilder> limiterBuilderConfigurer() {
|
||||
return servletLimiterBuilder -> servletLimiterBuilder
|
||||
.limit(FixedLimit.of(1));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
<description>Spring Cloud Netflix Dependencies</description>
|
||||
<properties>
|
||||
<archaius.version>0.7.6</archaius.version>
|
||||
<concurrency-limits.version>0.1.1</concurrency-limits.version>
|
||||
<eureka.version>1.9.3</eureka.version>
|
||||
<hystrix.version>1.5.12</hystrix.version>
|
||||
<ribbon.version>2.2.5</ribbon.version>
|
||||
@@ -180,6 +181,16 @@
|
||||
<artifactId>jersey-apache-client4</artifactId>
|
||||
<version>${eureka-jersey.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.concurrency-limits</groupId>
|
||||
<artifactId>concurrency-limits-core</artifactId>
|
||||
<version>${concurrency-limits.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.concurrency-limits</groupId>
|
||||
<artifactId>concurrency-limits-servlet</artifactId>
|
||||
<version>${concurrency-limits.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.servo</groupId>
|
||||
<artifactId>servo-core</artifactId>
|
||||
|
||||
Reference in New Issue
Block a user