Signed-off-by: Eric Zhao <sczyh16@gmail.com>master
@@ -30,6 +30,7 @@ | |||
<module>sentinel-demo-slot-chain-spi</module> | |||
<module>sentinel-demo-cluster</module> | |||
<module>sentinel-demo-command-handler</module> | |||
<module>sentinel-demo-spring-webflux</module> | |||
</modules> | |||
<dependencies> | |||
@@ -0,0 +1,44 @@ | |||
<?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"> | |||
<parent> | |||
<artifactId>sentinel-demo</artifactId> | |||
<groupId>com.alibaba.csp</groupId> | |||
<version>1.5.0-SNAPSHOT</version> | |||
</parent> | |||
<modelVersion>4.0.0</modelVersion> | |||
<artifactId>sentinel-demo-spring-webflux</artifactId> | |||
<properties> | |||
<spring.boot.version>2.1.3.RELEASE</spring.boot.version> | |||
</properties> | |||
<dependencies> | |||
<dependency> | |||
<groupId>com.alibaba.csp</groupId> | |||
<artifactId>sentinel-core</artifactId> | |||
</dependency> | |||
<dependency> | |||
<groupId>com.alibaba.csp</groupId> | |||
<artifactId>sentinel-transport-simple-http</artifactId> | |||
</dependency> | |||
<dependency> | |||
<groupId>com.alibaba.csp</groupId> | |||
<artifactId>sentinel-spring-webflux-adapter</artifactId> | |||
<version>${project.version}</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.springframework.boot</groupId> | |||
<artifactId>spring-boot-starter-webflux</artifactId> | |||
<version>${spring.boot.version}</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.springframework.boot</groupId> | |||
<artifactId>spring-boot-starter-data-redis-reactive</artifactId> | |||
<version>${spring.boot.version}</version> | |||
</dependency> | |||
</dependencies> | |||
</project> |
@@ -0,0 +1,38 @@ | |||
/* | |||
* Copyright 1999-2019 Alibaba Group Holding Ltd. | |||
* | |||
* 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 com.alibaba.csp.sentinel.demo.spring.webflux; | |||
import org.springframework.boot.SpringApplication; | |||
import org.springframework.boot.autoconfigure.SpringBootApplication; | |||
/** | |||
* <p>A demo for Spring WebFlux reactive application.</p> | |||
* | |||
* <p>To integrate with Sentinel dashboard, you can run the demo with the parameters (an example): | |||
* <code>-Dproject.name=WebFluxDemoApplication -Dcsp.sentinel.dashboard.server=localhost:8080 | |||
* -Dcsp.sentinel.api.port=8720 | |||
* </code> | |||
* </p> | |||
* | |||
* @author Eric Zhao | |||
*/ | |||
@SpringBootApplication | |||
public class WebFluxDemoApplication { | |||
public static void main(String[] args) { | |||
SpringApplication.run(WebFluxDemoApplication.class, args); | |||
} | |||
} |
@@ -0,0 +1,40 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* 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 com.alibaba.csp.sentinel.demo.spring.webflux.config; | |||
import org.springframework.context.annotation.Bean; | |||
import org.springframework.context.annotation.Configuration; | |||
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; | |||
import org.springframework.data.redis.core.ReactiveRedisTemplate; | |||
import org.springframework.data.redis.serializer.RedisSerializationContext; | |||
import org.springframework.data.redis.serializer.StringRedisSerializer; | |||
/** | |||
* @author Eric Zhao | |||
*/ | |||
@Configuration | |||
public class RedisConfig { | |||
@Bean | |||
public ReactiveRedisTemplate<String, String> stringReactiveRedisTemplate(ReactiveRedisConnectionFactory connectionFactory){ | |||
RedisSerializationContext<String, String> serializationContext = RedisSerializationContext | |||
.<String, String>newSerializationContext(new StringRedisSerializer()) | |||
.hashKey(new StringRedisSerializer()) | |||
.hashValue(new StringRedisSerializer()) | |||
.build(); | |||
return new ReactiveRedisTemplate<>(connectionFactory, serializationContext); | |||
} | |||
} |
@@ -0,0 +1,59 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* 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 com.alibaba.csp.sentinel.demo.spring.webflux.config; | |||
import java.util.Collections; | |||
import java.util.List; | |||
import com.alibaba.csp.sentinel.adapter.spring.webflux.SentinelWebFluxFilter; | |||
import com.alibaba.csp.sentinel.adapter.spring.webflux.exception.SentinelBlockExceptionHandler; | |||
import org.springframework.beans.factory.ObjectProvider; | |||
import org.springframework.context.annotation.Bean; | |||
import org.springframework.context.annotation.Configuration; | |||
import org.springframework.core.annotation.Order; | |||
import org.springframework.http.codec.ServerCodecConfigurer; | |||
import org.springframework.web.reactive.result.view.ViewResolver; | |||
/** | |||
* @author Eric Zhao | |||
*/ | |||
@Configuration | |||
public class WebFluxConfig { | |||
private final List<ViewResolver> viewResolvers; | |||
private final ServerCodecConfigurer serverCodecConfigurer; | |||
public WebFluxConfig(ObjectProvider<List<ViewResolver>> viewResolversProvider, | |||
ServerCodecConfigurer serverCodecConfigurer) { | |||
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList); | |||
this.serverCodecConfigurer = serverCodecConfigurer; | |||
} | |||
@Bean | |||
@Order(-1) | |||
public SentinelBlockExceptionHandler sentinelBlockExceptionHandler() { | |||
// Register the block exception handler for Spring WebFlux. | |||
return new SentinelBlockExceptionHandler(viewResolvers, serverCodecConfigurer); | |||
} | |||
@Bean | |||
@Order(-1) | |||
public SentinelWebFluxFilter sentinelWebFluxFilter() { | |||
// Register the Sentinel WebFlux filter. | |||
return new SentinelWebFluxFilter(); | |||
} | |||
} |
@@ -0,0 +1,51 @@ | |||
/* | |||
* Copyright 1999-2019 Alibaba Group Holding Ltd. | |||
* | |||
* 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 com.alibaba.csp.sentinel.demo.spring.webflux.controller; | |||
import com.alibaba.csp.sentinel.adapter.reactor.SentinelReactorTransformer; | |||
import com.alibaba.csp.sentinel.demo.spring.webflux.service.BazService; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.web.bind.annotation.GetMapping; | |||
import org.springframework.web.bind.annotation.PathVariable; | |||
import org.springframework.web.bind.annotation.PostMapping; | |||
import org.springframework.web.bind.annotation.RequestBody; | |||
import org.springframework.web.bind.annotation.RequestMapping; | |||
import org.springframework.web.bind.annotation.RestController; | |||
import reactor.core.publisher.Mono; | |||
/** | |||
* @author Eric Zhao | |||
*/ | |||
@RestController | |||
@RequestMapping(value = "/baz") | |||
public class BazController { | |||
@Autowired | |||
private BazService bazService; | |||
@GetMapping("/{id}") | |||
public Mono<String> apiGetValue(@PathVariable("id") Long id) { | |||
return bazService.getById(id) | |||
.transform(new SentinelReactorTransformer<>("BazService:getById")); | |||
} | |||
@PostMapping("/{id}") | |||
public Mono<Boolean> apiSetValue(@PathVariable("id") Long id, @RequestBody String value) { | |||
return bazService.setValue(id, value) | |||
.transform(new SentinelReactorTransformer<>("BazService:setValue")); | |||
} | |||
} |
@@ -0,0 +1,56 @@ | |||
/* | |||
* Copyright 1999-2019 Alibaba Group Holding Ltd. | |||
* | |||
* 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 com.alibaba.csp.sentinel.demo.spring.webflux.controller; | |||
import com.alibaba.csp.sentinel.adapter.reactor.SentinelReactorTransformer; | |||
import com.alibaba.csp.sentinel.demo.spring.webflux.service.FooService; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.http.server.reactive.ServerHttpResponse; | |||
import org.springframework.web.bind.annotation.GetMapping; | |||
import org.springframework.web.bind.annotation.RequestMapping; | |||
import org.springframework.web.bind.annotation.RestController; | |||
import reactor.core.publisher.Flux; | |||
import reactor.core.publisher.Mono; | |||
/** | |||
* @author Eric Zhao | |||
*/ | |||
@RestController | |||
@RequestMapping(value = "/foo") | |||
public class FooController { | |||
@Autowired | |||
private FooService fooService; | |||
@GetMapping("/single") | |||
public Mono<String> apiNormalSingle() { | |||
return fooService.emitSingle() | |||
// transform the publisher here. | |||
.transform(new SentinelReactorTransformer<>("demo_foo_normal_single")); | |||
} | |||
@GetMapping("/flux") | |||
public Flux<Integer> apiNormalFlux() { | |||
return fooService.emitMultiple() | |||
.transform(new SentinelReactorTransformer<>("demo_foo_normal_flux")); | |||
} | |||
@GetMapping("/slow") | |||
public Mono<String> apiDoSomethingSlow(ServerHttpResponse response) { | |||
return fooService.doSomethingSlow(); | |||
} | |||
} |
@@ -0,0 +1,53 @@ | |||
/* | |||
* Copyright 1999-2019 Alibaba Group Holding Ltd. | |||
* | |||
* 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 com.alibaba.csp.sentinel.demo.spring.webflux.service; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.data.redis.core.ReactiveRedisTemplate; | |||
import org.springframework.stereotype.Service; | |||
import reactor.core.publisher.Mono; | |||
/** | |||
* <p>A sample service for interacting with Redis via reactive Redis client.</p> | |||
* <p>To play this service, you need a Redis instance running in local.</p> | |||
* | |||
* @author Eric Zhao | |||
*/ | |||
@Service | |||
public class BazService { | |||
@Autowired | |||
private ReactiveRedisTemplate<String, String> template; | |||
public Mono<String> getById(Long id) { | |||
if (id == null || id <= 0) { | |||
return Mono.error(new IllegalArgumentException("invalid id: " + id)); | |||
} | |||
return template.opsForValue() | |||
.get(KEY_PREFIX + id) | |||
.switchIfEmpty(Mono.just("not_found")); | |||
} | |||
public Mono<Boolean> setValue(Long id, String value) { | |||
if (id == null || id <= 0 || value == null) { | |||
return Mono.error(new IllegalArgumentException("invalid parameters")); | |||
} | |||
return template.opsForValue() | |||
.set(KEY_PREFIX + id, value); | |||
} | |||
private static final String KEY_PREFIX = "sentinel-reactor-test:"; | |||
} |
@@ -0,0 +1,54 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* 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 com.alibaba.csp.sentinel.demo.spring.webflux.service; | |||
import java.util.concurrent.ExecutorService; | |||
import java.util.concurrent.Executors; | |||
import java.util.concurrent.ThreadLocalRandom; | |||
import org.springframework.stereotype.Service; | |||
import reactor.core.publisher.Flux; | |||
import reactor.core.publisher.Mono; | |||
import reactor.core.scheduler.Scheduler; | |||
import reactor.core.scheduler.Schedulers; | |||
/** | |||
* @author Eric Zhao | |||
*/ | |||
@Service | |||
public class FooService { | |||
private final ExecutorService pool = Executors.newFixedThreadPool(8); | |||
private final Scheduler scheduler = Schedulers.fromExecutor(pool); | |||
public Mono<String> emitSingle() { | |||
return Mono.just(ThreadLocalRandom.current().nextInt(0, 2000)) | |||
.map(e -> e + "d"); | |||
} | |||
public Flux<Integer> emitMultiple() { | |||
int start = ThreadLocalRandom.current().nextInt(0, 6000); | |||
return Flux.range(start, 10); | |||
} | |||
public Mono<String> doSomethingSlow() { | |||
return Mono.fromCallable(() -> { | |||
Thread.sleep(2000); | |||
System.out.println("doSomethingSlow: " + Thread.currentThread().getName()); | |||
return "ok"; | |||
}).publishOn(scheduler); | |||
} | |||
} |
@@ -0,0 +1,2 @@ | |||
spring.application.name=sentinel-webflux-demo-application | |||
server.port=8081 |