diff --git a/sentinel-demo/pom.xml b/sentinel-demo/pom.xml index ffcd42f4..364c8854 100755 --- a/sentinel-demo/pom.xml +++ b/sentinel-demo/pom.xml @@ -30,6 +30,7 @@ sentinel-demo-slot-chain-spi sentinel-demo-cluster sentinel-demo-command-handler + sentinel-demo-spring-webflux diff --git a/sentinel-demo/sentinel-demo-spring-webflux/pom.xml b/sentinel-demo/sentinel-demo-spring-webflux/pom.xml new file mode 100644 index 00000000..08bf190a --- /dev/null +++ b/sentinel-demo/sentinel-demo-spring-webflux/pom.xml @@ -0,0 +1,44 @@ + + + + sentinel-demo + com.alibaba.csp + 1.5.0-SNAPSHOT + + 4.0.0 + + sentinel-demo-spring-webflux + + + 2.1.3.RELEASE + + + + + com.alibaba.csp + sentinel-core + + + com.alibaba.csp + sentinel-transport-simple-http + + + com.alibaba.csp + sentinel-spring-webflux-adapter + ${project.version} + + + + org.springframework.boot + spring-boot-starter-webflux + ${spring.boot.version} + + + org.springframework.boot + spring-boot-starter-data-redis-reactive + ${spring.boot.version} + + + \ No newline at end of file diff --git a/sentinel-demo/sentinel-demo-spring-webflux/src/main/java/com/alibaba/csp/sentinel/demo/spring/webflux/WebFluxDemoApplication.java b/sentinel-demo/sentinel-demo-spring-webflux/src/main/java/com/alibaba/csp/sentinel/demo/spring/webflux/WebFluxDemoApplication.java new file mode 100644 index 00000000..70524606 --- /dev/null +++ b/sentinel-demo/sentinel-demo-spring-webflux/src/main/java/com/alibaba/csp/sentinel/demo/spring/webflux/WebFluxDemoApplication.java @@ -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; + +/** + *

A demo for Spring WebFlux reactive application.

+ * + *

To integrate with Sentinel dashboard, you can run the demo with the parameters (an example): + * -Dproject.name=WebFluxDemoApplication -Dcsp.sentinel.dashboard.server=localhost:8080 + * -Dcsp.sentinel.api.port=8720 + * + *

+ * + * @author Eric Zhao + */ +@SpringBootApplication +public class WebFluxDemoApplication { + + public static void main(String[] args) { + SpringApplication.run(WebFluxDemoApplication.class, args); + } +} diff --git a/sentinel-demo/sentinel-demo-spring-webflux/src/main/java/com/alibaba/csp/sentinel/demo/spring/webflux/config/RedisConfig.java b/sentinel-demo/sentinel-demo-spring-webflux/src/main/java/com/alibaba/csp/sentinel/demo/spring/webflux/config/RedisConfig.java new file mode 100644 index 00000000..edd0a233 --- /dev/null +++ b/sentinel-demo/sentinel-demo-spring-webflux/src/main/java/com/alibaba/csp/sentinel/demo/spring/webflux/config/RedisConfig.java @@ -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 stringReactiveRedisTemplate(ReactiveRedisConnectionFactory connectionFactory){ + RedisSerializationContext serializationContext = RedisSerializationContext + .newSerializationContext(new StringRedisSerializer()) + .hashKey(new StringRedisSerializer()) + .hashValue(new StringRedisSerializer()) + .build(); + return new ReactiveRedisTemplate<>(connectionFactory, serializationContext); + } +} diff --git a/sentinel-demo/sentinel-demo-spring-webflux/src/main/java/com/alibaba/csp/sentinel/demo/spring/webflux/config/WebFluxConfig.java b/sentinel-demo/sentinel-demo-spring-webflux/src/main/java/com/alibaba/csp/sentinel/demo/spring/webflux/config/WebFluxConfig.java new file mode 100644 index 00000000..60536673 --- /dev/null +++ b/sentinel-demo/sentinel-demo-spring-webflux/src/main/java/com/alibaba/csp/sentinel/demo/spring/webflux/config/WebFluxConfig.java @@ -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 viewResolvers; + private final ServerCodecConfigurer serverCodecConfigurer; + + public WebFluxConfig(ObjectProvider> 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(); + } +} diff --git a/sentinel-demo/sentinel-demo-spring-webflux/src/main/java/com/alibaba/csp/sentinel/demo/spring/webflux/controller/BazController.java b/sentinel-demo/sentinel-demo-spring-webflux/src/main/java/com/alibaba/csp/sentinel/demo/spring/webflux/controller/BazController.java new file mode 100644 index 00000000..c31113de --- /dev/null +++ b/sentinel-demo/sentinel-demo-spring-webflux/src/main/java/com/alibaba/csp/sentinel/demo/spring/webflux/controller/BazController.java @@ -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 apiGetValue(@PathVariable("id") Long id) { + return bazService.getById(id) + .transform(new SentinelReactorTransformer<>("BazService:getById")); + } + + @PostMapping("/{id}") + public Mono apiSetValue(@PathVariable("id") Long id, @RequestBody String value) { + return bazService.setValue(id, value) + .transform(new SentinelReactorTransformer<>("BazService:setValue")); + } +} diff --git a/sentinel-demo/sentinel-demo-spring-webflux/src/main/java/com/alibaba/csp/sentinel/demo/spring/webflux/controller/FooController.java b/sentinel-demo/sentinel-demo-spring-webflux/src/main/java/com/alibaba/csp/sentinel/demo/spring/webflux/controller/FooController.java new file mode 100644 index 00000000..790f5fc1 --- /dev/null +++ b/sentinel-demo/sentinel-demo-spring-webflux/src/main/java/com/alibaba/csp/sentinel/demo/spring/webflux/controller/FooController.java @@ -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 apiNormalSingle() { + return fooService.emitSingle() + // transform the publisher here. + .transform(new SentinelReactorTransformer<>("demo_foo_normal_single")); + } + + @GetMapping("/flux") + public Flux apiNormalFlux() { + return fooService.emitMultiple() + .transform(new SentinelReactorTransformer<>("demo_foo_normal_flux")); + } + + @GetMapping("/slow") + public Mono apiDoSomethingSlow(ServerHttpResponse response) { + return fooService.doSomethingSlow(); + } +} diff --git a/sentinel-demo/sentinel-demo-spring-webflux/src/main/java/com/alibaba/csp/sentinel/demo/spring/webflux/service/BazService.java b/sentinel-demo/sentinel-demo-spring-webflux/src/main/java/com/alibaba/csp/sentinel/demo/spring/webflux/service/BazService.java new file mode 100644 index 00000000..3ca0f7ee --- /dev/null +++ b/sentinel-demo/sentinel-demo-spring-webflux/src/main/java/com/alibaba/csp/sentinel/demo/spring/webflux/service/BazService.java @@ -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; + +/** + *

A sample service for interacting with Redis via reactive Redis client.

+ *

To play this service, you need a Redis instance running in local.

+ * + * @author Eric Zhao + */ +@Service +public class BazService { + + @Autowired + private ReactiveRedisTemplate template; + + public Mono 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 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:"; +} diff --git a/sentinel-demo/sentinel-demo-spring-webflux/src/main/java/com/alibaba/csp/sentinel/demo/spring/webflux/service/FooService.java b/sentinel-demo/sentinel-demo-spring-webflux/src/main/java/com/alibaba/csp/sentinel/demo/spring/webflux/service/FooService.java new file mode 100644 index 00000000..699d2c2e --- /dev/null +++ b/sentinel-demo/sentinel-demo-spring-webflux/src/main/java/com/alibaba/csp/sentinel/demo/spring/webflux/service/FooService.java @@ -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 emitSingle() { + return Mono.just(ThreadLocalRandom.current().nextInt(0, 2000)) + .map(e -> e + "d"); + } + + public Flux emitMultiple() { + int start = ThreadLocalRandom.current().nextInt(0, 6000); + return Flux.range(start, 10); + } + + public Mono doSomethingSlow() { + return Mono.fromCallable(() -> { + Thread.sleep(2000); + System.out.println("doSomethingSlow: " + Thread.currentThread().getName()); + return "ok"; + }).publishOn(scheduler); + } +} diff --git a/sentinel-demo/sentinel-demo-spring-webflux/src/main/resources/application.properties b/sentinel-demo/sentinel-demo-spring-webflux/src/main/resources/application.properties new file mode 100644 index 00000000..063a66b6 --- /dev/null +++ b/sentinel-demo/sentinel-demo-spring-webflux/src/main/resources/application.properties @@ -0,0 +1,2 @@ +spring.application.name=sentinel-webflux-demo-application +server.port=8081 \ No newline at end of file