├── .circleci └── config.yml ├── .gitignore ├── .mvn ├── jvm.config ├── maven.config └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── LICENSE ├── LICENSE.txt ├── README.adoc ├── docs ├── pom.xml └── src │ └── main │ └── asciidoc │ ├── README.adoc │ ├── _attributes.adoc │ ├── _configprops.adoc │ ├── appendix.adoc │ ├── gateway-grafana-dashboard.json │ ├── ghpages.sh │ ├── images │ ├── gateway-grafana-dashboard.jpeg │ └── spring_cloud_gateway_diagram.png │ ├── including.adoc │ ├── index.adoc │ ├── intro.adoc │ ├── sagan-boot.adoc │ ├── sagan-index.adoc │ └── spring-cloud-rsocket.adoc ├── mvnw ├── mvnw.cmd ├── pom.xml ├── spring-cloud-rsocket-broker ├── pom.xml └── src │ ├── main │ ├── java │ │ └── org │ │ │ └── springframework │ │ │ └── cloud │ │ │ └── gateway │ │ │ └── rsocket │ │ │ ├── actuate │ │ │ ├── BrokerActuator.java │ │ │ ├── BrokerActuatorHandlerRegistration.java │ │ │ ├── BrokerInfo.java │ │ │ ├── RouteJoin.java │ │ │ └── RouteRemove.java │ │ │ ├── autoconfigure │ │ │ ├── BrokerProperties.java │ │ │ ├── GatewayRSocketAutoConfiguration.java │ │ │ └── GatewayRSocketEnvironmentPostProcessor.java │ │ │ ├── cluster │ │ │ ├── ClusterJoinListener.java │ │ │ ├── ClusterService.java │ │ │ └── RouteJoinListener.java │ │ │ ├── core │ │ │ ├── AbstractGatewayRSocket.java │ │ │ ├── GatewayExchange.java │ │ │ ├── GatewayFilter.java │ │ │ ├── GatewayFilterChain.java │ │ │ ├── GatewayPredicate.java │ │ │ ├── GatewayRSocket.java │ │ │ ├── GatewayRSocketFactory.java │ │ │ ├── GatewayServerRSocketFactoryProcessor.java │ │ │ ├── PendingRequestRSocket.java │ │ │ └── PendingRequestRSocketFactory.java │ │ │ ├── filter │ │ │ ├── AbstractFilterChain.java │ │ │ ├── AbstractRSocketExchange.java │ │ │ ├── FilterChain.java │ │ │ ├── RSocketExchange.java │ │ │ └── RSocketFilter.java │ │ │ ├── metrics │ │ │ ├── MicrometerResponderRSocket.java │ │ │ └── MicrometerResponderRSocketInterceptor.java │ │ │ ├── route │ │ │ ├── DefaultRoute.java │ │ │ ├── Route.java │ │ │ └── Routes.java │ │ │ ├── routing │ │ │ ├── LoadBalancerFactory.java │ │ │ ├── RoutingTable.java │ │ │ ├── RoutingTableRoutes.java │ │ │ └── RoutingTableSocketAcceptorFilter.java │ │ │ ├── socketacceptor │ │ │ ├── GatewaySocketAcceptor.java │ │ │ ├── SocketAcceptorExchange.java │ │ │ ├── SocketAcceptorFilter.java │ │ │ ├── SocketAcceptorFilterChain.java │ │ │ ├── SocketAcceptorPredicate.java │ │ │ └── SocketAcceptorPredicateFilter.java │ │ │ └── support │ │ │ └── AsyncPredicate.java │ └── resources │ │ └── META-INF │ │ └── spring.factories │ └── test │ ├── java │ └── org │ │ └── springframework │ │ └── cloud │ │ └── gateway │ │ └── rsocket │ │ ├── actuate │ │ ├── BrokerActuatorIntegrationTests.java │ │ └── BrokerActuatorRegistrarTests.java │ │ ├── autoconfigure │ │ └── GatewayRSocketAutoConfigurationTests.java │ │ ├── cluster │ │ └── ClusterServiceTests.java │ │ ├── core │ │ ├── GatewayRSocketIntegrationTests.java │ │ └── GatewayRSocketTests.java │ │ ├── routing │ │ ├── RoutingTableRoutesTests.java │ │ └── RoutingTableTests.java │ │ ├── socketacceptor │ │ ├── GatewaySocketAcceptorTests.java │ │ └── SocketAcceptorPredicateFilterTests.java │ │ └── test │ │ ├── PingPongApp.java │ │ └── SocketAcceptorFilterOrderTests.java │ └── resources │ └── application.yml ├── spring-cloud-rsocket-client ├── pom.xml └── src │ ├── main │ ├── java │ │ └── org │ │ │ └── springframework │ │ │ └── cloud │ │ │ └── gateway │ │ │ └── rsocket │ │ │ └── client │ │ │ ├── BrokerClient.java │ │ │ ├── BrokerClientConnectionListener.java │ │ │ ├── ClientProperties.java │ │ │ ├── ClientRSocketRequester.java │ │ │ ├── ClientRSocketRequesterBuilder.java │ │ │ └── GatewayRSocketClientAutoConfiguration.java │ └── resources │ │ └── META-INF │ │ └── spring.factories │ └── test │ ├── java │ └── org │ │ └── springframework │ │ └── cloud │ │ └── gateway │ │ └── rsocket │ │ └── client │ │ ├── ClientPropertiesTests.java │ │ └── ClientRSocketRequesterTests.java │ └── resources │ └── application.yml ├── spring-cloud-rsocket-common ├── pom.xml └── src │ ├── main │ ├── java │ │ └── org │ │ │ └── springframework │ │ │ └── cloud │ │ │ └── gateway │ │ │ └── rsocket │ │ │ └── common │ │ │ ├── autoconfigure │ │ │ ├── Broker.java │ │ │ ├── GatewayRSocketCommonAutoConfiguration.java │ │ │ └── GatewayRSocketCommonMetadataAutoConfiguration.java │ │ │ └── metadata │ │ │ ├── Forwarding.java │ │ │ ├── Metadata.java │ │ │ ├── RouteSetup.java │ │ │ ├── TagsMetadata.java │ │ │ └── WellKnownKey.java │ └── resources │ │ └── META-INF │ │ └── spring.factories │ └── test │ └── java │ └── org │ └── springframework │ └── cloud │ └── gateway │ └── rsocket │ └── common │ ├── metadata │ ├── ForwardingIntegrationTests.java │ ├── ForwardingTests.java │ ├── RouteSetupIntegrationTests.java │ ├── RouteSetupTests.java │ └── TagsMetadataTests.java │ └── test │ └── MetadataEncoder.java ├── spring-cloud-rsocket-dependencies └── pom.xml └── src └── checkstyle └── checkstyle-suppressions.xml /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: springcloud/pipeline-base 6 | # user: appuser 7 | environment: 8 | _JAVA_OPTIONS: "-Xms1024m -Xmx2048m" 9 | TERM: dumb 10 | branches: 11 | ignore: 12 | - gh-pages # list of branches to ignore 13 | resource_class: large 14 | steps: 15 | - checkout 16 | - restore_cache: 17 | key: sc-gateway-{{ .Branch }} 18 | - run: 19 | command: apt-get -y install redis-server 20 | - run: 21 | name: "Download dependencies" 22 | command: | 23 | ./mvnw -s .settings.xml -U --fail-never dependency:go-offline || true 24 | - save_cache: 25 | key: sc-gateway-{{ .Branch }} 26 | paths: 27 | - ~/.m2 28 | - run: 29 | name: "Running build" 30 | command: ./mvnw -s .settings.xml clean org.jacoco:jacoco-maven-plugin:prepare-agent install -U -P sonar -nsu --batch-mode -Dmaven.test.redirectTestOutputToFile=true -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn 31 | - run: 32 | name: "Aggregate test results" 33 | when: always 34 | command: | 35 | mkdir -p ~/junit/ 36 | find . -type f -regex ".*/target/.*-reports/.*" -exec cp {} ~/junit/ \; 37 | bash <(curl -s https://codecov.io/bash) 38 | - store_artifacts: 39 | path: ~/junit/ 40 | destination: artifacts 41 | - store_test_results: 42 | path: ~/junit/ 43 | destination: testartifacts -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | #* 3 | *# 4 | .#* 5 | .classpath 6 | .project 7 | .settings/ 8 | .springBeans 9 | target/ 10 | bin/ 11 | _site/ 12 | .idea 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .factorypath 17 | *.log 18 | .shelf 19 | *.swp 20 | *.swo 21 | .vscode/ -------------------------------------------------------------------------------- /.mvn/jvm.config: -------------------------------------------------------------------------------- 1 | -Xmx1024m -XX:CICompilerCount=1 -XX:TieredStopAtLevel=1 -Djava.security.egd=file:/dev/./urandom -------------------------------------------------------------------------------- /.mvn/maven.config: -------------------------------------------------------------------------------- 1 | -DaltSnapshotDeploymentRepository=repo.spring.io::default::https://repo.spring.io/libs-snapshot-local -P spring 2 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/spring-cloud-rsocket/02f4914c92a28906dc9c96b16276c440d0d26daa/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip 2 | -------------------------------------------------------------------------------- /docs/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.springframework.cloud 8 | spring-cloud-rsocket 9 | 0.2.0.BUILD-SNAPSHOT 10 | 11 | spring-cloud-rsocket-docs 12 | pom 13 | Spring Cloud RSocket Docs 14 | Spring Cloud RSocket Docs 15 | 16 | spring-cloud-rsocket 17 | ${basedir}/.. 18 | 1.0.x,2.1.x 19 | spring.cloud.gateway.rsocket.* 20 | 21 | 22 | 23 | ${project.groupId} 24 | spring-cloud-rsocket-common 25 | 26 | 27 | ${project.groupId} 28 | spring-cloud-rsocket-client 29 | 30 | 31 | ${project.groupId} 32 | spring-cloud-rsocket-broker 33 | 34 | 35 | 36 | 37 | 38 | maven-deploy-plugin 39 | 40 | true 41 | 42 | 43 | 44 | 45 | 46 | 47 | docs 48 | 49 | 50 | 51 | pl.project13.maven 52 | git-commit-id-plugin 53 | 54 | 55 | org.codehaus.mojo 56 | exec-maven-plugin 57 | 58 | 59 | org.apache.maven.plugins 60 | maven-dependency-plugin 61 | 62 | 63 | org.apache.maven.plugins 64 | maven-resources-plugin 65 | 66 | 67 | org.asciidoctor 68 | asciidoctor-maven-plugin 69 | 70 | 71 | org.apache.maven.plugins 72 | maven-antrun-plugin 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /docs/src/main/asciidoc/README.adoc: -------------------------------------------------------------------------------- 1 | image::https://circleci.com/gh/spring-cloud-incubator/spring-cloud-rsocket/tree/master.svg?style=svg["CircleCI", link="https://circleci.com/gh/spring-cloud-incubator/spring-cloud-rsocket/tree/master"] 2 | image::https://codecov.io/gh/spring-cloud-incubator/spring-cloud-rsocket/branch/master/graph/badge.svg["Codecov", link="https://codecov.io/gh/spring-cloud-incubator/spring-cloud-rsocket/branch/master"] 3 | image::https://api.codacy.com/project/badge/Grade/a6885a06921e4f72a0df0b7aabd6d118["Codacy code quality", link="https://www.codacy.com/app/spring-cloud-incubator/spring-cloud-rsocket?utm_source=github.com&utm_medium=referral&utm_content=spring-cloud-incubator/spring-cloud-rsocket&utm_campaign=Badge_Grade"] 4 | 5 | 6 | include::intro.adoc[] 7 | 8 | == Incubator status 9 | 10 | This project is currently in an incubator status. When the RSocket Routing and Forwarding extension is completed in rsocket-java, this project will move out of incubator. 11 | 12 | == Features 13 | 14 | * Java 8 15 | * Spring Framework 5.2 16 | * Spring Boot 2.2 17 | * TODO: list features 18 | 19 | include::including.adoc[] 20 | 21 | == Building 22 | 23 | include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/docs/src/main/asciidoc/building-jdk8.adoc[] 24 | 25 | == Contributing 26 | 27 | include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/docs/src/main/asciidoc/contributing.adoc[] 28 | -------------------------------------------------------------------------------- /docs/src/main/asciidoc/_attributes.adoc: -------------------------------------------------------------------------------- 1 | :doctype: book 2 | :idprefix: 3 | :idseparator: - 4 | :toc: left 5 | :toclevels: 4 6 | :tabsize: 4 7 | :numbered: 8 | :sectanchors: 9 | :sectnums: 10 | :icons: font 11 | :hide-uri-scheme: 12 | :docinfo: shared,private 13 | 14 | :sc-ext: java 15 | :project-full-name: Spring Cloud Gateway 16 | :all: {asterisk}{asterisk} -------------------------------------------------------------------------------- /docs/src/main/asciidoc/appendix.adoc: -------------------------------------------------------------------------------- 1 | :numbered!: 2 | [appendix] 3 | [[common-application-properties]] 4 | == Common application properties 5 | 6 | include::_attributes.adoc[] 7 | 8 | Various properties can be specified inside your `application.properties` file, inside your `application.yml` file, or as command line switches. 9 | This appendix provides a list of common {project-full-name} properties and references to the underlying classes that consume them. 10 | 11 | NOTE: Property contributions can come from additional jar files on your classpath, so you should not consider this an exhaustive list. 12 | Also, you can define your own properties. 13 | 14 | include::_configprops.adoc[] -------------------------------------------------------------------------------- /docs/src/main/asciidoc/images/gateway-grafana-dashboard.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/spring-cloud-rsocket/02f4914c92a28906dc9c96b16276c440d0d26daa/docs/src/main/asciidoc/images/gateway-grafana-dashboard.jpeg -------------------------------------------------------------------------------- /docs/src/main/asciidoc/images/spring_cloud_gateway_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/spring-cloud-rsocket/02f4914c92a28906dc9c96b16276c440d0d26daa/docs/src/main/asciidoc/images/spring_cloud_gateway_diagram.png -------------------------------------------------------------------------------- /docs/src/main/asciidoc/including.adoc: -------------------------------------------------------------------------------- 1 | == Including in your project 2 | 3 | You will need to include the spring snapshot repository to your pom.xml: 4 | ```xml 5 | 6 | 7 | spring-snapshots 8 | Spring Snapshots 9 | https://repo.spring.io/libs-snapshot-local 10 | 11 | true 12 | 13 | 14 | false 15 | 16 | 17 | 18 | ``` 19 | 20 | Add the following to your dependency management section. 21 | ```xml 22 | 23 | 24 | 25 | org.springframework.cloud 26 | spring-cloud-rsocket-dependencies 27 | 0.2.0.BUILD-SNAPSHOT 28 | pom 29 | import 30 | 31 | 32 | 33 | ``` 34 | 35 | Then use the following dependencies for a client or broker respectively: 36 | 37 | ```xml 38 | 39 | org.springframework.cloud 40 | spring-cloud-rsocket-client 41 | 42 | ``` 43 | or 44 | ``` 45 | 46 | org.springframework.cloud 47 | spring-cloud-rsocket-broker 48 | 49 | ``` 50 | -------------------------------------------------------------------------------- /docs/src/main/asciidoc/index.adoc: -------------------------------------------------------------------------------- 1 | spring-cloud-rsocket.adoc -------------------------------------------------------------------------------- /docs/src/main/asciidoc/intro.adoc: -------------------------------------------------------------------------------- 1 | This project provides an RSocket broker and broker client built on top of the Spring Ecosystem, including: Spring 5, Spring Boot 2 and Project Reactor. Spring Cloud RSocket aims to an implementation of the RSocket Routing and Forwarding specification extension and provide cross cutting concerns to them such as: security, monitoring/metrics, and resiliency. 2 | 3 | -------------------------------------------------------------------------------- /docs/src/main/asciidoc/sagan-boot.adoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-attic/spring-cloud-rsocket/02f4914c92a28906dc9c96b16276c440d0d26daa/docs/src/main/asciidoc/sagan-boot.adoc -------------------------------------------------------------------------------- /docs/src/main/asciidoc/sagan-index.adoc: -------------------------------------------------------------------------------- 1 | This project provides a library for building an API Gateway on top of Spring MVC. Spring Cloud Gateway aims to provide a simple, yet effective way to route to APIs and provide cross cutting concerns to them such as: security, monitoring/metrics, and resiliency. 2 | 3 | ## Features 4 | 5 | Spring Cloud Gateway features: 6 | 7 | * Built on Spring Framework 5, Project Reactor and Spring Boot 2.0 8 | * Able to match routes on any request attribute. 9 | * Predicates and filters are specific to routes. 10 | * Hystrix Circuit Breaker integration. 11 | * Spring Cloud DiscoveryClient integration 12 | * Easy to write Predicates and Filters 13 | * Request Rate Limiting 14 | * Path Rewriting 15 | 16 | ## Getting Started 17 | 18 | ```java 19 | @SpringBootApplication 20 | public class DemogatewayApplication { 21 | @Bean 22 | public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { 23 | return builder.routes() 24 | .route("path_route", r -> r.path("/get") 25 | .uri("http://httpbin.org")) 26 | .route("host_route", r -> r.host("*.myhost.org") 27 | .uri("http://httpbin.org")) 28 | .route("rewrite_route", r -> r.host("*.rewrite.org") 29 | .filters(f -> f.rewritePath("/foo/(?.*)", "/${segment}")) 30 | .uri("http://httpbin.org")) 31 | .route("hystrix_route", r -> r.host("*.hystrix.org") 32 | .filters(f -> f.hystrix(c -> c.setName("slowcmd"))) 33 | .uri("http://httpbin.org")) 34 | .route("hystrix_fallback_route", r -> r.host("*.hystrixfallback.org") 35 | .filters(f -> f.hystrix(c -> c.setName("slowcmd").setFallbackUri("forward:/hystrixfallback"))) 36 | .uri("http://httpbin.org")) 37 | .route("limit_route", r -> r 38 | .host("*.limited.org").and().path("/anything/**") 39 | .filters(f -> f.requestRateLimiter(c -> c.setRateLimiter(redisRateLimiter()))) 40 | .uri("http://httpbin.org")) 41 | .build(); 42 | } 43 | } 44 | ``` 45 | 46 | To run your own gateway use the `spring-cloud-starter-gateway` dependency. -------------------------------------------------------------------------------- /docs/src/main/asciidoc/spring-cloud-rsocket.adoc: -------------------------------------------------------------------------------- 1 | = Spring Cloud RSocket 2 | include::_attributes.adoc[] 3 | 4 | *{spring-cloud-version}* 5 | 6 | include::intro.adoc[] 7 | 8 | include::including.adoc[] 9 | 10 | TODO: add documentation. 11 | 12 | == Configuration properties 13 | 14 | To see the list of all Spring Cloud RSocket related configuration properties please check link:appendix.html[the Appendix page]. 15 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 21 | 4.0.0 22 | 23 | org.springframework.cloud 24 | spring-cloud-rsocket 25 | 0.2.0.BUILD-SNAPSHOT 26 | .. 27 | 28 | org.springframework.cloud 29 | spring-cloud-rsocket-broker 30 | Spring Cloud RSocket Broker 31 | Spring Cloud RSocket Broker 32 | 33 | 34 | 35 | org.springframework.cloud 36 | spring-cloud-rsocket-common 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-rsocket 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-configuration-processor 45 | true 46 | 47 | 48 | io.rsocket 49 | rsocket-core 50 | 51 | 52 | io.rsocket 53 | rsocket-micrometer 54 | 55 | 56 | io.rsocket 57 | rsocket-transport-netty 58 | 59 | 60 | io.micrometer 61 | micrometer-core 62 | 63 | 64 | org.roaringbitmap 65 | RoaringBitmap 66 | 67 | 68 | org.springframework.boot 69 | spring-boot-starter-actuator 70 | test 71 | 72 | 73 | org.projectlombok 74 | lombok 75 | test 76 | 77 | 78 | org.springframework.boot 79 | spring-boot-starter-test 80 | test 81 | 82 | 83 | io.projectreactor 84 | reactor-test 85 | test 86 | 87 | 88 | org.springframework.cloud 89 | spring-cloud-rsocket-common 90 | ${project.version} 91 | test-jar 92 | test 93 | 94 | 95 | 96 | 97 | 98 | org.apache.maven.plugins 99 | maven-compiler-plugin 100 | 101 | 102 | -parameters 103 | 104 | 105 | 106 | 107 | 108 | default-compile 109 | none 110 | 111 | 112 | 113 | default-testCompile 114 | none 115 | 116 | 117 | java-compile 118 | compile 119 | 120 | compile 121 | 122 | 123 | 124 | java-test-compile 125 | test-compile 126 | 127 | testCompile 128 | 129 | 130 | 131 | 132 | 133 | org.apache.maven.plugins 134 | maven-jar-plugin 135 | 3.1.0 136 | 137 | 138 | 139 | test-jar 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | java8plus 149 | 150 | [1.8,2.0) 151 | 152 | 153 | 154 | 155 | org.apache.maven.plugins 156 | maven-compiler-plugin 157 | 158 | 159 | -parameters 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/actuate/BrokerActuator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.actuate; 18 | 19 | import java.math.BigInteger; 20 | import java.util.List; 21 | 22 | import io.rsocket.RSocket; 23 | import org.apache.commons.logging.Log; 24 | import org.apache.commons.logging.LogFactory; 25 | import reactor.core.publisher.Mono; 26 | import reactor.util.function.Tuple2; 27 | 28 | import org.springframework.cloud.gateway.rsocket.autoconfigure.BrokerProperties; 29 | import org.springframework.cloud.gateway.rsocket.cluster.ClusterService; 30 | import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata; 31 | import org.springframework.cloud.gateway.rsocket.routing.RoutingTable; 32 | import org.springframework.messaging.handler.annotation.MessageMapping; 33 | import org.springframework.stereotype.Controller; 34 | 35 | @Controller 36 | public class BrokerActuator { 37 | 38 | private static final Log log = LogFactory.getLog(BrokerActuator.class); 39 | 40 | /** 41 | * Path for BrokerInfo actuator endpoint. 42 | */ 43 | public static final String BROKER_INFO_PATH = "actuator.gateway.brokerinfo"; 44 | 45 | /** 46 | * Path for RouteJoin actuator endpoint. 47 | */ 48 | public static final String ROUTE_JOIN_PATH = "actuator.gateway.routejoin"; 49 | 50 | /** 51 | * Path for RouteJoin actuator endpoint. 52 | */ 53 | public static final String ROUTE_REMOVE_PATH = "actuator.gateway.routeremove"; 54 | 55 | private final BrokerProperties properties; 56 | 57 | private final ClusterService clusterService; 58 | 59 | private final RoutingTable routingTable; 60 | 61 | public BrokerActuator(BrokerProperties properties, ClusterService clusterService, 62 | RoutingTable routingTable) { 63 | this.properties = properties; 64 | this.clusterService = clusterService; 65 | this.routingTable = routingTable; 66 | } 67 | 68 | @MessageMapping("hello") 69 | public Mono hello(String name) { 70 | return Mono.just("Hello " + name); 71 | } 72 | 73 | @MessageMapping(BROKER_INFO_PATH) 74 | public BigInteger brokerInfo(BrokerInfo brokerInfo) { 75 | log.info("BrokerInfo: " + brokerInfo); 76 | clusterService.registerIncoming(brokerInfo); 77 | return properties.getRouteId(); 78 | } 79 | 80 | @MessageMapping(ROUTE_JOIN_PATH) 81 | @SuppressWarnings("Duplicates") 82 | public RouteJoin routeJoin(RouteJoin routeJoin) { 83 | log.info("RouteJoin: " + routeJoin); 84 | TagsMetadata findBrokerQuery = TagsMetadata.builder() 85 | .routeId(routeJoin.getBrokerId().toString()).build(); 86 | List> rSockets = routingTable 87 | .findRSockets(findBrokerQuery); 88 | 89 | if (rSockets.size() != 1) { 90 | // should only be one broker 91 | if (log.isDebugEnabled()) { 92 | log.debug("Expected 1 RSocket for broker: " + routeJoin.getBrokerId() 93 | + ", found " + rSockets.size()); 94 | } 95 | return null; 96 | } 97 | RSocket brokerRSocket = rSockets.iterator().next().getT2(); 98 | TagsMetadata.Builder tags = TagsMetadata.builder(); 99 | // TODO: other tags. 100 | // routeJoin.getTags().forEach(tags::with); 101 | tags.routeId(routeJoin.getRouteId().toString()) 102 | .serviceName(routeJoin.getServiceName()); 103 | 104 | TagsMetadata tagsMetadata = tags.build(); 105 | routingTable.register(tagsMetadata, brokerRSocket); 106 | 107 | brokerRSocket.onClose().doOnSuccess(v -> { 108 | if (log.isDebugEnabled()) { 109 | log.debug("Broker closed, deregistering " + tagsMetadata); 110 | } 111 | routingTable.deregister(tagsMetadata); 112 | }).doOnError(t -> { 113 | if (log.isErrorEnabled()) { 114 | log.error("Error received on broker, deregistering " + tagsMetadata, t); 115 | } 116 | routingTable.deregister(tagsMetadata); 117 | }).subscribe(); 118 | // TODO: keep track of disposable? 119 | return routeJoin; 120 | } 121 | 122 | @MessageMapping(ROUTE_REMOVE_PATH) 123 | public boolean routeRemove(RouteRemove routeRemove) { 124 | log.info("RouteRemove: " + routeRemove); 125 | return routingTable.deregister(TagsMetadata.builder() 126 | .routeId(routeRemove.getRouteId().toString()).build()); 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/actuate/BrokerActuatorHandlerRegistration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.actuate; 18 | 19 | import java.util.concurrent.atomic.AtomicBoolean; 20 | 21 | import io.netty.buffer.ByteBuf; 22 | import io.netty.buffer.ByteBufAllocator; 23 | import io.netty.buffer.Unpooled; 24 | import io.rsocket.AbstractRSocket; 25 | import io.rsocket.ConnectionSetupPayload; 26 | import io.rsocket.Payload; 27 | import io.rsocket.SocketAcceptor; 28 | import io.rsocket.frame.SetupFrameFlyweight; 29 | import io.rsocket.util.DefaultPayload; 30 | 31 | import org.springframework.cloud.gateway.rsocket.autoconfigure.BrokerProperties; 32 | import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata; 33 | import org.springframework.cloud.gateway.rsocket.routing.RoutingTable; 34 | import org.springframework.context.SmartLifecycle; 35 | import org.springframework.core.io.buffer.DataBufferFactory; 36 | import org.springframework.core.io.buffer.NettyDataBufferFactory; 37 | import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler; 38 | 39 | import static io.rsocket.metadata.WellKnownMimeType.APPLICATION_CBOR; 40 | import static io.rsocket.metadata.WellKnownMimeType.MESSAGE_RSOCKET_COMPOSITE_METADATA; 41 | 42 | public class BrokerActuatorHandlerRegistration implements SmartLifecycle { 43 | 44 | private final AtomicBoolean running = new AtomicBoolean(); 45 | 46 | private final RoutingTable routingTable; 47 | 48 | private final RSocketMessageHandler messageHandler; 49 | 50 | private final BrokerProperties properties; 51 | 52 | public BrokerActuatorHandlerRegistration(RoutingTable routingTable, 53 | RSocketMessageHandler messageHandler, BrokerProperties properties) { 54 | this.routingTable = routingTable; 55 | this.messageHandler = messageHandler; 56 | this.properties = properties; 57 | } 58 | 59 | @Override 60 | public void start() { 61 | if (this.running.compareAndSet(false, true)) { 62 | ConnectionSetupPayload connectionSetupPayload = getConnectionSetupPayload(); 63 | SocketAcceptor responder = this.messageHandler.responder(); 64 | responder.accept(connectionSetupPayload, new AbstractRSocket() { 65 | }).subscribe(rSocket -> { 66 | TagsMetadata tagsMetadata = TagsMetadata.builder() 67 | .routeId(properties.getRouteId().toString()) 68 | .serviceName(properties.getServiceName()) 69 | // TODO: move to well known implementation key 70 | .with("proxy", Boolean.FALSE.toString()).build(); 71 | routingTable.register(tagsMetadata, rSocket); 72 | }); 73 | } 74 | } 75 | 76 | private ConnectionSetupPayload getConnectionSetupPayload() { 77 | DataBufferFactory dataBufferFactory = messageHandler.getRSocketStrategies() 78 | .dataBufferFactory(); 79 | NettyDataBufferFactory ndbf = (NettyDataBufferFactory) dataBufferFactory; 80 | ByteBufAllocator byteBufAllocator = ndbf.getByteBufAllocator(); 81 | Payload setupPayload = DefaultPayload.create(Unpooled.EMPTY_BUFFER, 82 | Unpooled.EMPTY_BUFFER); 83 | ByteBuf setup = SetupFrameFlyweight.encode(byteBufAllocator, false, 1, 1, 84 | MESSAGE_RSOCKET_COMPOSITE_METADATA.getString(), 85 | // TODO: configurable? 86 | APPLICATION_CBOR.getString(), setupPayload); 87 | return ConnectionSetupPayload.create(setup); 88 | } 89 | 90 | @Override 91 | public void stop() { 92 | if (this.running.compareAndSet(true, false)) { 93 | System.out.println(); 94 | } 95 | } 96 | 97 | @Override 98 | public boolean isAutoStartup() { 99 | return true; 100 | } 101 | 102 | @Override 103 | public void stop(Runnable callback) { 104 | stop(); 105 | if (callback != null) { 106 | callback.run(); 107 | } 108 | } 109 | 110 | @Override 111 | public int getPhase() { 112 | return 0; 113 | } 114 | 115 | @Override 116 | public boolean isRunning() { 117 | return this.running.get(); 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/actuate/BrokerInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.actuate; 18 | 19 | import java.math.BigInteger; 20 | import java.util.Map; 21 | import java.util.Objects; 22 | 23 | import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata; 24 | import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata.Key; 25 | import org.springframework.cloud.gateway.rsocket.common.metadata.WellKnownKey; 26 | import org.springframework.core.style.ToStringCreator; 27 | import org.springframework.util.Assert; 28 | 29 | public final class BrokerInfo { 30 | 31 | private final BigInteger brokerId; 32 | 33 | private final long timestamp; 34 | 35 | private final Map tags; 36 | 37 | private BrokerInfo(BigInteger brokerId, long timestamp, Map tags) { 38 | this.brokerId = brokerId; 39 | this.timestamp = timestamp; 40 | this.tags = tags; 41 | } 42 | 43 | public BigInteger getBrokerId() { 44 | return this.brokerId; 45 | } 46 | 47 | public long getTimestamp() { 48 | return this.timestamp; 49 | } 50 | 51 | public Map getTags() { 52 | return this.tags; 53 | } 54 | 55 | @Override 56 | public boolean equals(Object o) { 57 | if (this == o) { 58 | return true; 59 | } 60 | if (o == null || getClass() != o.getClass()) { 61 | return false; 62 | } 63 | BrokerInfo that = (BrokerInfo) o; 64 | return this.timestamp == that.timestamp 65 | && Objects.equals(this.brokerId, that.brokerId) 66 | && Objects.equals(this.tags, that.tags); 67 | } 68 | 69 | @Override 70 | public int hashCode() { 71 | return Objects.hash(this.brokerId, this.timestamp, this.tags); 72 | } 73 | 74 | @Override 75 | public String toString() { 76 | // @formatter:off 77 | return new ToStringCreator(this) 78 | .append("id", brokerId) 79 | .append("timestamp", timestamp) 80 | .append("tags", getTags()) 81 | .toString(); 82 | // @formatter:on 83 | } 84 | 85 | public static Builder of(BigInteger brokerId) { 86 | return new Builder(brokerId); 87 | } 88 | 89 | public static Builder of(Long brokerId) { 90 | return of(BigInteger.valueOf(brokerId)); 91 | } 92 | 93 | public static final class Builder { 94 | 95 | private final BigInteger brokerId; 96 | 97 | private long timestamp = System.currentTimeMillis(); 98 | 99 | private final TagsMetadata.Builder tagsBuilder = TagsMetadata.builder(); 100 | 101 | private Builder(BigInteger brokerId) { 102 | Assert.notNull(brokerId, "brokerId may not be null"); 103 | this.brokerId = brokerId; 104 | } 105 | 106 | public Builder timestamp(long timestamp) { 107 | this.timestamp = timestamp; 108 | return this; 109 | } 110 | 111 | public Builder with(String key, String value) { 112 | tagsBuilder.with(key, value); 113 | return this; 114 | } 115 | 116 | public Builder with(WellKnownKey key, String value) { 117 | tagsBuilder.with(key, value); 118 | return this; 119 | } 120 | 121 | public Builder with(Key key, String value) { 122 | tagsBuilder.with(key, value); 123 | return this; 124 | } 125 | 126 | public Builder with(TagsMetadata tagsMetadata) { 127 | tagsBuilder.with(tagsMetadata); 128 | return this; 129 | } 130 | 131 | public BrokerInfo build() { 132 | Assert.isTrue(timestamp > 0, "timestamp must be > 0"); 133 | return new BrokerInfo(brokerId, timestamp, tagsBuilder.build().getTags()); 134 | } 135 | 136 | } 137 | 138 | } 139 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/actuate/RouteJoin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.actuate; 18 | 19 | import java.math.BigInteger; 20 | import java.util.Map; 21 | import java.util.Objects; 22 | 23 | import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata; 24 | import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata.Key; 25 | import org.springframework.cloud.gateway.rsocket.common.metadata.WellKnownKey; 26 | import org.springframework.core.style.ToStringCreator; 27 | import org.springframework.util.Assert; 28 | 29 | public final class RouteJoin { 30 | 31 | private final BigInteger brokerId; 32 | 33 | private final BigInteger routeId; 34 | 35 | private final long timestamp; 36 | 37 | private final String serviceName; 38 | 39 | private final Map tags; 40 | 41 | public RouteJoin(BigInteger brokerId, BigInteger routeId, long timestamp, 42 | String serviceName, Map tags) { 43 | this.brokerId = brokerId; 44 | this.routeId = routeId; 45 | this.timestamp = timestamp; 46 | this.serviceName = serviceName; 47 | this.tags = tags; 48 | } 49 | 50 | public BigInteger getBrokerId() { 51 | return this.brokerId; 52 | } 53 | 54 | public BigInteger getRouteId() { 55 | return this.routeId; 56 | } 57 | 58 | public long getTimestamp() { 59 | return this.timestamp; 60 | } 61 | 62 | public String getServiceName() { 63 | return this.serviceName; 64 | } 65 | 66 | public Map getTags() { 67 | return this.tags; 68 | } 69 | 70 | @Override 71 | public boolean equals(Object o) { 72 | if (this == o) { 73 | return true; 74 | } 75 | if (o == null || getClass() != o.getClass()) { 76 | return false; 77 | } 78 | RouteJoin routeJoin = (RouteJoin) o; 79 | return this.timestamp == routeJoin.timestamp 80 | && Objects.equals(this.brokerId, routeJoin.brokerId) 81 | && Objects.equals(this.routeId, routeJoin.routeId) 82 | && Objects.equals(this.serviceName, routeJoin.serviceName) 83 | && Objects.equals(this.tags, routeJoin.tags); 84 | } 85 | 86 | @Override 87 | public int hashCode() { 88 | return Objects.hash(this.brokerId, this.routeId, this.timestamp, this.serviceName, 89 | this.tags); 90 | } 91 | 92 | @Override 93 | public String toString() { 94 | return new ToStringCreator(this).append("brokerId", brokerId) 95 | .append("routeId", routeId).append("timestamp", timestamp) 96 | .append("serviceName", serviceName).append("tags", tags).toString(); 97 | 98 | } 99 | 100 | public static Builder builder() { 101 | return new Builder(); 102 | } 103 | 104 | public static final class Builder { 105 | 106 | private BigInteger brokerId; 107 | 108 | private BigInteger routeId; 109 | 110 | private long timestamp = System.currentTimeMillis(); 111 | 112 | private String serviceName; 113 | 114 | private TagsMetadata.Builder tagsBuilder = TagsMetadata.builder(); 115 | 116 | public Builder brokerId(BigInteger brokerId) { 117 | this.brokerId = brokerId; 118 | return this; 119 | } 120 | 121 | public Builder brokerId(long brokerId) { 122 | return brokerId(BigInteger.valueOf(brokerId)); 123 | } 124 | 125 | public Builder routeId(BigInteger routeId) { 126 | this.routeId = routeId; 127 | return this; 128 | } 129 | 130 | public Builder routeId(long routeId) { 131 | return routeId(BigInteger.valueOf(routeId)); 132 | } 133 | 134 | public Builder timestamp(long timestamp) { 135 | this.timestamp = timestamp; 136 | return this; 137 | } 138 | 139 | public Builder serviceName(String serviceName) { 140 | this.serviceName = serviceName; 141 | return this; 142 | } 143 | 144 | public Builder with(String key, String value) { 145 | tagsBuilder.with(key, value); 146 | return this; 147 | } 148 | 149 | public Builder with(WellKnownKey key, String value) { 150 | tagsBuilder.with(key, value); 151 | return this; 152 | } 153 | 154 | public Builder with(Key key, String value) { 155 | tagsBuilder.with(key, value); 156 | return this; 157 | } 158 | 159 | public Builder with(TagsMetadata tagsMetadata) { 160 | tagsBuilder.with(tagsMetadata); 161 | return this; 162 | } 163 | 164 | public RouteJoin build() { 165 | Assert.notNull(brokerId, "brokerId may not be null"); 166 | Assert.notNull(routeId, "brokerId may not be null"); 167 | Assert.notNull(serviceName, "brokerId may not be null"); 168 | Assert.isTrue(timestamp > 0, "timestamp must be > 0"); 169 | return new RouteJoin(brokerId, routeId, timestamp, serviceName, 170 | tagsBuilder.build().getTags()); 171 | } 172 | 173 | } 174 | 175 | } 176 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/actuate/RouteRemove.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.actuate; 18 | 19 | import java.math.BigInteger; 20 | import java.util.Objects; 21 | 22 | import org.springframework.core.style.ToStringCreator; 23 | import org.springframework.util.Assert; 24 | 25 | public final class RouteRemove { 26 | 27 | private final BigInteger brokerId; 28 | 29 | private final BigInteger routeId; 30 | 31 | private final long timestamp; 32 | 33 | public RouteRemove(BigInteger brokerId, BigInteger routeId, long timestamp) { 34 | this.brokerId = brokerId; 35 | this.routeId = routeId; 36 | this.timestamp = timestamp; 37 | } 38 | 39 | public BigInteger getBrokerId() { 40 | return this.brokerId; 41 | } 42 | 43 | public BigInteger getRouteId() { 44 | return this.routeId; 45 | } 46 | 47 | public long getTimestamp() { 48 | return this.timestamp; 49 | } 50 | 51 | @Override 52 | public boolean equals(Object o) { 53 | if (this == o) { 54 | return true; 55 | } 56 | if (o == null || getClass() != o.getClass()) { 57 | return false; 58 | } 59 | RouteRemove routeJoin = (RouteRemove) o; 60 | return this.timestamp == routeJoin.timestamp 61 | && Objects.equals(this.brokerId, routeJoin.brokerId) 62 | && Objects.equals(this.routeId, routeJoin.routeId); 63 | } 64 | 65 | @Override 66 | public int hashCode() { 67 | return Objects.hash(this.brokerId, this.routeId, this.timestamp); 68 | } 69 | 70 | @Override 71 | public String toString() { 72 | return new ToStringCreator(this).append("brokerId", brokerId) 73 | .append("routeId", routeId).append("timestamp", timestamp).toString(); 74 | 75 | } 76 | 77 | public static Builder builder() { 78 | return new Builder(); 79 | } 80 | 81 | public static final class Builder { 82 | 83 | private BigInteger brokerId; 84 | 85 | private BigInteger routeId; 86 | 87 | private long timestamp = System.currentTimeMillis(); 88 | 89 | public Builder brokerId(BigInteger brokerId) { 90 | this.brokerId = brokerId; 91 | return this; 92 | } 93 | 94 | public Builder brokerId(long brokerId) { 95 | return brokerId(BigInteger.valueOf(brokerId)); 96 | } 97 | 98 | public Builder routeId(BigInteger routeId) { 99 | this.routeId = routeId; 100 | return this; 101 | } 102 | 103 | public Builder routeId(long routeId) { 104 | return routeId(BigInteger.valueOf(routeId)); 105 | } 106 | 107 | public Builder timestamp(long timestamp) { 108 | this.timestamp = timestamp; 109 | return this; 110 | } 111 | 112 | public RouteRemove build() { 113 | Assert.notNull(brokerId, "brokerId may not be null"); 114 | Assert.notNull(routeId, "brokerId may not be null"); 115 | Assert.isTrue(timestamp > 0, "timestamp must be > 0"); 116 | return new RouteRemove(brokerId, routeId, timestamp); 117 | } 118 | 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/BrokerProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.autoconfigure; 18 | 19 | import java.math.BigInteger; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | import org.springframework.boot.context.properties.ConfigurationProperties; 24 | import org.springframework.cloud.gateway.rsocket.common.autoconfigure.Broker; 25 | import org.springframework.core.style.ToStringCreator; 26 | 27 | @ConfigurationProperties("spring.cloud.gateway.rsocket") 28 | public class BrokerProperties { 29 | 30 | /** 31 | * Enable Gateway RSocket. 32 | */ 33 | private boolean enabled = true; 34 | 35 | private String id = "gateway"; // TODO: + UUID? 36 | 37 | private BigInteger routeId; 38 | 39 | private String serviceName = "gateway"; 40 | 41 | private List brokers = new ArrayList<>(); 42 | 43 | /** 44 | * Tag names and values to be supplied to Micrometer Interceptor. 45 | */ 46 | private List micrometerTags = new ArrayList<>(); 47 | 48 | public BrokerProperties() { 49 | micrometerTags.add("component"); 50 | micrometerTags.add("gateway"); 51 | } 52 | 53 | public boolean isEnabled() { 54 | return enabled; 55 | } 56 | 57 | public void setEnabled(boolean enabled) { 58 | this.enabled = enabled; 59 | } 60 | 61 | public String getId() { 62 | return this.id; 63 | } 64 | 65 | public void setId(String id) { 66 | this.id = id; 67 | } 68 | 69 | public List getMicrometerTags() { 70 | return micrometerTags; 71 | } 72 | 73 | public void setMicrometerTags(List micrometerTags) { 74 | this.micrometerTags = micrometerTags; 75 | } 76 | 77 | public BigInteger getRouteId() { 78 | return this.routeId; 79 | } 80 | 81 | public void setRouteId(BigInteger routeId) { 82 | this.routeId = routeId; 83 | } 84 | 85 | public String getServiceName() { 86 | return this.serviceName; 87 | } 88 | 89 | public void setServiceName(String serviceName) { 90 | this.serviceName = serviceName; 91 | } 92 | 93 | public List getBrokers() { 94 | return this.brokers; 95 | } 96 | 97 | public void setBrokers(List brokers) { 98 | this.brokers = brokers; 99 | } 100 | 101 | @Override 102 | public String toString() { 103 | // @formatter:off 104 | return new ToStringCreator(this) 105 | .append("enabled", enabled) 106 | .append("id", id) 107 | .append("micrometerTags", micrometerTags) 108 | .append("routeId", routeId) 109 | .append("serviceName", serviceName) 110 | .append("brokers", brokers) 111 | .toString(); 112 | // @formatter:on 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketEnvironmentPostProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.autoconfigure; 18 | 19 | import java.util.Collections; 20 | import java.util.Map; 21 | 22 | import org.springframework.boot.SpringApplication; 23 | import org.springframework.boot.env.EnvironmentPostProcessor; 24 | import org.springframework.core.env.ConfigurableEnvironment; 25 | import org.springframework.core.env.MapPropertySource; 26 | 27 | public class GatewayRSocketEnvironmentPostProcessor implements EnvironmentPostProcessor { 28 | 29 | @Override 30 | public void postProcessEnvironment(ConfigurableEnvironment env, 31 | SpringApplication application) { 32 | Boolean enabled = env.getProperty("spring.cloud.gateway.rsocket.enabled", 33 | Boolean.class, true); 34 | if (enabled && !env.containsProperty("spring.rsocket.server.port")) { 35 | Map map = Collections 36 | .singletonMap("spring.rsocket.server.port", 7002); 37 | env.getPropertySources() 38 | .addLast(new MapPropertySource("Default Gateway RSocket Port", map)); 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/cluster/ClusterJoinListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.cluster; 18 | 19 | import java.math.BigInteger; 20 | 21 | import io.rsocket.SocketAcceptor; 22 | import reactor.core.publisher.Mono; 23 | import reactor.util.function.Tuple2; 24 | import reactor.util.function.Tuples; 25 | 26 | import org.springframework.boot.context.event.ApplicationReadyEvent; 27 | import org.springframework.cloud.gateway.rsocket.actuate.BrokerActuator; 28 | import org.springframework.cloud.gateway.rsocket.actuate.BrokerInfo; 29 | import org.springframework.cloud.gateway.rsocket.autoconfigure.BrokerProperties; 30 | import org.springframework.cloud.gateway.rsocket.common.autoconfigure.Broker; 31 | import org.springframework.cloud.gateway.rsocket.common.metadata.Forwarding; 32 | import org.springframework.cloud.gateway.rsocket.common.metadata.RouteSetup; 33 | import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata; 34 | import org.springframework.cloud.gateway.rsocket.core.GatewayRSocketFactory; 35 | import org.springframework.context.ApplicationListener; 36 | import org.springframework.messaging.rsocket.RSocketRequester; 37 | import org.springframework.messaging.rsocket.RSocketStrategies; 38 | 39 | public class ClusterJoinListener implements ApplicationListener { 40 | 41 | private final ClusterService clusterService; 42 | 43 | private final BrokerProperties properties; 44 | 45 | private final RSocketStrategies strategies; 46 | 47 | private final GatewayRSocketFactory gatewayRSocketFactory; 48 | 49 | public ClusterJoinListener(ClusterService clusterService, BrokerProperties properties, 50 | RSocketStrategies strategies, GatewayRSocketFactory gatewayRSocketFactory) { 51 | this.clusterService = clusterService; 52 | this.properties = properties; 53 | this.strategies = strategies; 54 | this.gatewayRSocketFactory = gatewayRSocketFactory; 55 | } 56 | 57 | @Override 58 | public void onApplicationEvent(ApplicationReadyEvent event) { 59 | for (Broker broker : properties.getBrokers()) { 60 | RouteSetup routeSetup = RouteSetup 61 | .of(properties.getRouteId(), properties.getServiceName()).build(); 62 | 63 | // TODO: micrometer 64 | RSocketRequester.builder().rsocketStrategies(strategies) 65 | .setupMetadata(routeSetup, RouteSetup.ROUTE_SETUP_MIME_TYPE) 66 | .rsocketFactory(rsocketFactory -> rsocketFactory 67 | .acceptor(brokerSocketAcceptor())) 68 | // TODO: other types 69 | .connectTcp(broker.getHost(), broker.getPort()) 70 | .flatMap(this::callBrokerInfo).subscribe(this::registerOutgoing); 71 | } 72 | } 73 | 74 | /** 75 | * For incoming requests to this broker node, the RSocketRequester needs an acceptor 76 | * that is able to hand out GatewayRSocket instances. So here is a very simple one 77 | * that just constructs tags metadata and creates a GatewayRSocket. 78 | * @return A SocketAcceptor that creates a GatewayRSocket. 79 | */ 80 | SocketAcceptor brokerSocketAcceptor() { 81 | return (setup, sendingSocket) -> { 82 | TagsMetadata.Builder builder = TagsMetadata.builder(); 83 | // TODO: other tags. 84 | builder.serviceName(properties.getServiceName()) 85 | .routeId(properties.getRouteId().toString()); 86 | return Mono.just(gatewayRSocketFactory.create(builder.build())); 87 | }; 88 | } 89 | 90 | Mono> callBrokerInfo( 91 | RSocketRequester requester) { 92 | Forwarding forwarding = Forwarding.of(properties.getRouteId()) 93 | .serviceName("gateway").disableProxy().build(); 94 | return requester.route(BrokerActuator.BROKER_INFO_PATH) 95 | .metadata(forwarding, Forwarding.FORWARDING_MIME_TYPE) 96 | .data(BrokerInfo.of(properties.getRouteId()).build()) 97 | .retrieveMono(BigInteger.class) 98 | .map(brokerId -> Tuples.of(brokerId, requester)); 99 | } 100 | 101 | boolean registerOutgoing(Tuple2 tuple) { 102 | return clusterService.registerOutgoing(tuple.getT1().toString(), tuple.getT2()); 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/cluster/ClusterService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.cluster; 18 | 19 | import java.math.BigInteger; 20 | import java.util.Map; 21 | import java.util.concurrent.ConcurrentHashMap; 22 | 23 | import org.springframework.cloud.gateway.rsocket.actuate.BrokerActuator; 24 | import org.springframework.cloud.gateway.rsocket.actuate.BrokerInfo; 25 | import org.springframework.cloud.gateway.rsocket.actuate.RouteJoin; 26 | import org.springframework.cloud.gateway.rsocket.common.metadata.Forwarding; 27 | import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata; 28 | import org.springframework.core.style.ToStringCreator; 29 | import org.springframework.messaging.rsocket.RSocketRequester; 30 | 31 | public class ClusterService { 32 | 33 | final Map incomingBrokers = new ConcurrentHashMap<>(); 34 | 35 | final Map outgoingBrokers = new ConcurrentHashMap<>(); 36 | 37 | public boolean registerIncoming(BrokerInfo brokerInfo) { 38 | String brokerId = brokerInfo.getBrokerId().toString(); 39 | 40 | // TODO: validate that there is a corresponding route in RoutingTable 41 | if (incomingBrokers.containsKey(brokerId)) { 42 | BrokerEntry brokerEntry = incomingBrokers.get(brokerId); 43 | if (brokerEntry.timestamp < brokerInfo.getTimestamp()) { 44 | incomingBrokers.put(brokerId, new BrokerEntry(brokerInfo.getBrokerId(), 45 | brokerInfo.getTags(), brokerInfo.getTimestamp())); 46 | return true; 47 | } 48 | } 49 | else { 50 | incomingBrokers.put(brokerId, new BrokerEntry(brokerInfo.getBrokerId(), 51 | brokerInfo.getTags(), brokerInfo.getTimestamp())); 52 | return true; 53 | } 54 | return false; 55 | } 56 | 57 | public boolean registerOutgoing(String routeId, RSocketRequester requester) { 58 | // TODO: BrokerClient instead of RSocketRequester? 59 | // TODO: validation 60 | outgoingBrokers.put(routeId, requester); 61 | return true; 62 | } 63 | 64 | public boolean send(RouteJoin routeJoin) { 65 | outgoingBrokers.values().forEach(requester -> { 66 | Forwarding forwarding = Forwarding.of(routeJoin.getRouteId()) 67 | .serviceName("gateway").disableProxy().build(); 68 | requester.route(BrokerActuator.ROUTE_JOIN_PATH) 69 | .metadata(forwarding, Forwarding.FORWARDING_MIME_TYPE).data(routeJoin) 70 | .retrieveMono(RouteJoin.class) 71 | .subscribe(res -> System.out.println("RouteJoin: " + res)); 72 | }); 73 | return true; 74 | } 75 | 76 | static class BrokerEntry { 77 | 78 | private final BigInteger brokerId; 79 | 80 | private final Map tags; 81 | 82 | private final Long timestamp; 83 | 84 | BrokerEntry(BigInteger brokerId, Map tags, 85 | Long timestamp) { 86 | this.brokerId = brokerId; 87 | this.tags = tags; 88 | this.timestamp = timestamp; 89 | } 90 | 91 | public BigInteger getBrokerId() { 92 | return this.brokerId; 93 | } 94 | 95 | public Map getTags() { 96 | return this.tags; 97 | } 98 | 99 | public Long getTimestamp() { 100 | return this.timestamp; 101 | } 102 | 103 | @Override 104 | public String toString() { 105 | // @formatter:off 106 | return new ToStringCreator(this) 107 | .append("brokerId", brokerId) 108 | .append("tags", tags) 109 | .append("timestamp", timestamp) 110 | .toString(); 111 | // @formatter:on 112 | } 113 | 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/cluster/RouteJoinListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.cluster; 18 | 19 | import java.math.BigInteger; 20 | import java.util.function.Consumer; 21 | 22 | import org.springframework.cloud.gateway.rsocket.actuate.RouteJoin; 23 | import org.springframework.cloud.gateway.rsocket.autoconfigure.BrokerProperties; 24 | import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata; 25 | import org.springframework.cloud.gateway.rsocket.routing.RoutingTable; 26 | import org.springframework.cloud.gateway.rsocket.routing.RoutingTable.RegisteredEvent; 27 | 28 | import static org.springframework.cloud.gateway.rsocket.common.metadata.WellKnownKey.SERVICE_NAME; 29 | 30 | public class RouteJoinListener implements Consumer { 31 | 32 | private final ClusterService clusterService; 33 | 34 | private final RoutingTable routingTable; 35 | 36 | private final BrokerProperties properties; 37 | 38 | public RouteJoinListener(ClusterService clusterService, RoutingTable routingTable, 39 | BrokerProperties properties) { 40 | this.clusterService = clusterService; 41 | this.routingTable = routingTable; 42 | this.properties = properties; 43 | routingTable.addListener(this); 44 | } 45 | 46 | @Override 47 | public void accept(RegisteredEvent registeredEvent) { 48 | BigInteger brokerId = properties.getRouteId(); 49 | TagsMetadata tagsMetadata = registeredEvent.getRoutingMetadata(); 50 | String serviceName = tagsMetadata.get(SERVICE_NAME); 51 | 52 | // Do not send RouteJoin requests for self 53 | if (!brokerId.toString().equals(tagsMetadata.getRouteId()) && 54 | // or for other gateways yet 55 | !"gateway".equals(serviceName)) { 56 | BigInteger routeId = new BigInteger(tagsMetadata.getRouteId()); 57 | RouteJoin routeJoin = RouteJoin.builder().brokerId(brokerId).routeId(routeId) 58 | .serviceName(serviceName).with(tagsMetadata).build(); 59 | 60 | clusterService.send(routeJoin); 61 | } 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/core/AbstractGatewayRSocket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.core; 18 | 19 | import io.micrometer.core.instrument.MeterRegistry; 20 | import io.micrometer.core.instrument.Tags; 21 | import io.rsocket.AbstractRSocket; 22 | import io.rsocket.Payload; 23 | import io.rsocket.ResponderRSocket; 24 | import org.apache.commons.logging.Log; 25 | import org.apache.commons.logging.LogFactory; 26 | 27 | import org.springframework.cloud.gateway.rsocket.autoconfigure.BrokerProperties; 28 | import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata; 29 | import org.springframework.messaging.rsocket.MetadataExtractor; 30 | import org.springframework.util.Assert; 31 | import org.springframework.util.StringUtils; 32 | 33 | /** 34 | * Convience class to hold and calculate exchange and metrics related information. 35 | */ 36 | public abstract class AbstractGatewayRSocket extends AbstractRSocket 37 | implements ResponderRSocket { 38 | 39 | private static final Log log = LogFactory.getLog(AbstractGatewayRSocket.class); 40 | 41 | protected final MeterRegistry meterRegistry; 42 | 43 | private final BrokerProperties properties; 44 | 45 | private final MetadataExtractor metadataExtractor; 46 | 47 | private final TagsMetadata metadata; 48 | 49 | AbstractGatewayRSocket(MeterRegistry meterRegistry, BrokerProperties properties, 50 | MetadataExtractor metadataExtractor, TagsMetadata metadata) { 51 | this.meterRegistry = meterRegistry; 52 | this.properties = properties; 53 | this.metadataExtractor = metadataExtractor; 54 | this.metadata = metadata; 55 | } 56 | 57 | protected GatewayExchange createExchange(GatewayExchange.Type type, Payload payload) { 58 | GatewayExchange exchange = GatewayExchange.fromPayload(type, payload, 59 | metadataExtractor); 60 | Tags tags = getTags(exchange); 61 | exchange.setTags(tags); 62 | return exchange; 63 | } 64 | 65 | protected Tags getTags(GatewayExchange exchange) { 66 | // TODO: add tags to exchange 67 | String requesterName = "FIXME"; // FIXME: this.metadata.get(SERVICE_NAME); 68 | String requesterId = "FIXME"; // FIXME: this.metadata.getRouteId(); 69 | String responderName = "FIXME"; // FIXME: exchange.getRoutingMetadata().getName(); 70 | Assert.hasText(responderName, "responderName must not be empty"); 71 | Assert.hasText(requesterId, "requesterId must not be empty"); 72 | Assert.hasText(requesterName, "requesterName must not be empty"); 73 | // responder.id happens in a callback, later 74 | return Tags.of("requester.name", requesterName, "responder.name", responderName, 75 | "requester.id", requesterId, "gateway.id", this.properties.getId()); 76 | } 77 | 78 | protected void count(GatewayExchange exchange, String suffix) { 79 | count(exchange, suffix, Tags.empty()); 80 | } 81 | 82 | protected void count(GatewayExchange exchange, Tags additionalTags) { 83 | count(exchange, null, additionalTags); 84 | } 85 | 86 | protected void count(GatewayExchange exchange, String suffix, Tags additionalTags) { 87 | Tags tags = exchange.getTags().and(additionalTags); 88 | String name = getMetricName(exchange, suffix); 89 | this.meterRegistry.counter(name, tags).increment(); 90 | } 91 | 92 | protected String getMetricName(GatewayExchange exchange) { 93 | return getMetricName(exchange, null); 94 | } 95 | 96 | protected String getMetricName(GatewayExchange exchange, String suffix) { 97 | StringBuilder name = new StringBuilder("forward."); 98 | name.append(exchange.getType().getKey()); 99 | if (StringUtils.hasLength(suffix)) { 100 | name.append("."); 101 | name.append(suffix); 102 | } 103 | return name.toString(); 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/core/GatewayExchange.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.core; 18 | 19 | import java.util.Map; 20 | 21 | import io.micrometer.core.instrument.Tags; 22 | import io.rsocket.Payload; 23 | import org.apache.commons.logging.Log; 24 | import org.apache.commons.logging.LogFactory; 25 | 26 | import org.springframework.cloud.gateway.rsocket.common.metadata.Forwarding; 27 | import org.springframework.cloud.gateway.rsocket.common.metadata.Metadata; 28 | import org.springframework.cloud.gateway.rsocket.filter.AbstractRSocketExchange; 29 | import org.springframework.messaging.rsocket.MetadataExtractor; 30 | 31 | /** 32 | * Exchange object used in GatewayFilterChain started by GatewayRSocket. 33 | */ 34 | public class GatewayExchange extends AbstractRSocketExchange { 35 | 36 | private static final Log log = LogFactory.getLog(GatewayExchange.class); 37 | 38 | /** 39 | * Key for the route object in attributes. 40 | */ 41 | public static final String ROUTE_ATTR = "__route_attr_"; 42 | 43 | public enum Type { 44 | 45 | /** 46 | * RSocket fire and forget request type. 47 | */ 48 | FIRE_AND_FORGET("request.fnf"), 49 | 50 | /** 51 | * RSocket request channel request type. 52 | */ 53 | REQUEST_CHANNEL("request.channel"), 54 | 55 | /** 56 | * RSocket request response request type. 57 | */ 58 | REQUEST_RESPONSE("request.response"), 59 | 60 | /** 61 | * RSocket request stream request type. 62 | */ 63 | REQUEST_STREAM("request.stream"); 64 | 65 | private String key; 66 | 67 | Type(String key) { 68 | this.key = key; 69 | } 70 | 71 | String getKey() { 72 | return this.key; 73 | } 74 | 75 | } 76 | 77 | private final Type type; 78 | 79 | private final Forwarding routingMetadata; 80 | 81 | private Tags tags = Tags.empty(); 82 | 83 | public static GatewayExchange fromPayload(Type type, Payload payload, 84 | MetadataExtractor metadataExtractor) { 85 | if (payload == null || !payload.hasMetadata()) { 86 | return null; 87 | } 88 | 89 | // TODO: deal with payload mimetype 90 | Map metadataMap = metadataExtractor.extract(payload, 91 | Metadata.COMPOSITE_MIME_TYPE); 92 | 93 | GatewayExchange exchange = new GatewayExchange(type, 94 | getForwardingMetadata(metadataMap)); 95 | 96 | // TODO: custm metadata extractors 97 | // Adds routing metadata to exchange 98 | if (metadataMap.containsKey("route")) { 99 | exchange.getAttributes().put("route-metadata", metadataMap.get("route")); 100 | } 101 | 102 | return exchange; 103 | } 104 | 105 | private static Forwarding getForwardingMetadata(Map metadataMap) { 106 | if (metadataMap.containsKey("forwarding")) { 107 | Forwarding metadata = (Forwarding) metadataMap.get("forwarding"); 108 | 109 | if (log.isDebugEnabled()) { 110 | log.debug("found routing metadata " + metadata); 111 | } 112 | return metadata; 113 | } 114 | 115 | return null; 116 | } 117 | 118 | public GatewayExchange(Type type, Forwarding routingMetadata) { 119 | this.type = type; 120 | this.routingMetadata = routingMetadata; 121 | } 122 | 123 | public Type getType() { 124 | return type; 125 | } 126 | 127 | public Forwarding getRoutingMetadata() { 128 | return routingMetadata; 129 | } 130 | 131 | public Tags getTags() { 132 | return this.tags; 133 | } 134 | 135 | public void setTags(Tags tags) { 136 | this.tags = tags; 137 | } 138 | 139 | } 140 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/core/GatewayFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.core; 18 | 19 | import org.springframework.cloud.gateway.rsocket.filter.RSocketFilter; 20 | 21 | public interface GatewayFilter 22 | extends RSocketFilter { 23 | 24 | } 25 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/core/GatewayFilterChain.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.core; 18 | 19 | import java.util.List; 20 | 21 | import reactor.core.publisher.Mono; 22 | 23 | import org.springframework.cloud.gateway.rsocket.filter.AbstractFilterChain; 24 | import org.springframework.cloud.gateway.rsocket.filter.RSocketFilter.Success; 25 | 26 | public class GatewayFilterChain 27 | extends AbstractFilterChain { 28 | 29 | /** 30 | * Public constructor with the list of filters and the target handler to use. 31 | * @param filters the filters ahead of the handler 32 | */ 33 | private GatewayFilterChain(List filters) { 34 | super(filters); 35 | } 36 | 37 | protected GatewayFilterChain(List allFilters, 38 | GatewayFilter currentFilter, GatewayFilterChain next) { 39 | super(allFilters, currentFilter, next); 40 | } 41 | 42 | @Override 43 | protected GatewayFilterChain create(List allFilters, 44 | GatewayFilter currentFilter, GatewayFilterChain next) { 45 | return new GatewayFilterChain(allFilters, currentFilter, next); 46 | } 47 | 48 | public static Mono executeFilterChain(List filters, 49 | GatewayExchange exchange) { 50 | return new GatewayFilterChain(filters).filter(exchange); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/core/GatewayPredicate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.core; 18 | 19 | import org.springframework.cloud.gateway.rsocket.support.AsyncPredicate; 20 | 21 | public interface GatewayPredicate extends AsyncPredicate { 22 | 23 | } 24 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/core/GatewayRSocketFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.core; 18 | 19 | import io.micrometer.core.instrument.MeterRegistry; 20 | import org.apache.commons.logging.Log; 21 | import org.apache.commons.logging.LogFactory; 22 | 23 | import org.springframework.cloud.gateway.rsocket.autoconfigure.BrokerProperties; 24 | import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata; 25 | import org.springframework.cloud.gateway.rsocket.route.Routes; 26 | import org.springframework.cloud.gateway.rsocket.routing.LoadBalancerFactory; 27 | import org.springframework.cloud.gateway.rsocket.routing.RoutingTable; 28 | import org.springframework.messaging.rsocket.MetadataExtractor; 29 | import org.springframework.util.Assert; 30 | 31 | import static org.springframework.cloud.gateway.rsocket.common.metadata.WellKnownKey.ROUTE_ID; 32 | import static org.springframework.cloud.gateway.rsocket.common.metadata.WellKnownKey.SERVICE_NAME; 33 | 34 | public class GatewayRSocketFactory { 35 | 36 | private static final Log log = LogFactory.getLog(GatewayRSocket.class); 37 | 38 | private final RoutingTable routingTable; 39 | 40 | private final Routes routes; 41 | 42 | private final PendingRequestRSocketFactory pendingFactory; 43 | 44 | private final LoadBalancerFactory loadBalancerFactory; 45 | 46 | private final MeterRegistry meterRegistry; 47 | 48 | private final BrokerProperties properties; 49 | 50 | private final MetadataExtractor metadataExtractor; 51 | 52 | public GatewayRSocketFactory(RoutingTable routingTable, Routes routes, 53 | PendingRequestRSocketFactory pendingFactory, 54 | LoadBalancerFactory loadBalancerFactory, MeterRegistry meterRegistry, 55 | BrokerProperties properties, MetadataExtractor metadataExtractor) { 56 | this.routingTable = routingTable; 57 | this.routes = routes; 58 | this.pendingFactory = pendingFactory; 59 | this.loadBalancerFactory = loadBalancerFactory; 60 | this.meterRegistry = meterRegistry; 61 | this.properties = properties; 62 | this.metadataExtractor = metadataExtractor; 63 | } 64 | 65 | @SuppressWarnings("Duplicates") 66 | public GatewayRSocket create(TagsMetadata metadata) { 67 | Assert.hasText(metadata.get(ROUTE_ID), "metadata must contain " + ROUTE_ID); 68 | Assert.hasText(metadata.get(SERVICE_NAME), 69 | "metadata must contain " + SERVICE_NAME); 70 | 71 | GatewayRSocket gatewayRSocket = new GatewayRSocket(this.routes, 72 | this.pendingFactory, this.loadBalancerFactory, this.meterRegistry, 73 | this.properties, this.metadataExtractor, metadata); 74 | gatewayRSocket.onClose().doOnSuccess(v -> { 75 | if (log.isDebugEnabled()) { 76 | log.debug("Closed, deregistering " + metadata); 77 | } 78 | routingTable.deregister(metadata); 79 | }).doOnError(t -> { 80 | if (log.isErrorEnabled()) { 81 | log.error("Error received, deregistering " + metadata, t); 82 | } 83 | routingTable.deregister(metadata); 84 | }).doOnNext(v -> { 85 | if (log.isTraceEnabled()) { 86 | log.trace("OnClose doOnNext"); 87 | } 88 | }).doOnTerminate(() -> { 89 | if (log.isTraceEnabled()) { 90 | log.trace("OnClose doOnTerminate"); 91 | } 92 | }).doFinally(st -> { 93 | if (log.isTraceEnabled()) { 94 | log.trace("OnClose doFinally"); 95 | } 96 | }).subscribe(); 97 | return gatewayRSocket; 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/core/GatewayServerRSocketFactoryProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.core; 18 | 19 | import java.util.Arrays; 20 | import java.util.List; 21 | import java.util.stream.Collectors; 22 | 23 | import io.micrometer.core.instrument.MeterRegistry; 24 | import io.micrometer.core.instrument.Tag; 25 | import io.micrometer.core.instrument.Tags; 26 | import io.rsocket.RSocketFactory.ServerRSocketFactory; 27 | import io.rsocket.micrometer.MicrometerDuplexConnectionInterceptor; 28 | import io.rsocket.plugins.RSocketInterceptor; 29 | import org.apache.commons.logging.Log; 30 | import org.apache.commons.logging.LogFactory; 31 | 32 | import org.springframework.boot.rsocket.server.ServerRSocketFactoryProcessor; 33 | import org.springframework.cloud.gateway.rsocket.autoconfigure.BrokerProperties; 34 | import org.springframework.util.Assert; 35 | 36 | public class GatewayServerRSocketFactoryProcessor 37 | implements ServerRSocketFactoryProcessor { 38 | 39 | private static final Log log = LogFactory 40 | .getLog(GatewayServerRSocketFactoryProcessor.class); 41 | 42 | private static final RSocketInterceptor[] EMPTY_INTERCEPTORS = new RSocketInterceptor[0]; 43 | 44 | private final BrokerProperties properties; 45 | 46 | private final List serverInterceptors; 47 | 48 | private final MeterRegistry meterRegistry; 49 | 50 | public GatewayServerRSocketFactoryProcessor(BrokerProperties properties, 51 | MeterRegistry meterRegistry) { 52 | this(properties, meterRegistry, EMPTY_INTERCEPTORS); 53 | } 54 | 55 | public GatewayServerRSocketFactoryProcessor(BrokerProperties properties, 56 | MeterRegistry meterRegistry, RSocketInterceptor... interceptors) { 57 | Assert.notNull(properties, "properties may not be null"); 58 | Assert.notNull(meterRegistry, "meterRegistry may not be null"); 59 | Assert.notNull(interceptors, "interceptors may not be null"); 60 | this.properties = properties; 61 | this.meterRegistry = meterRegistry; 62 | this.serverInterceptors = Arrays.asList(interceptors); 63 | } 64 | 65 | @Override 66 | public ServerRSocketFactory process(ServerRSocketFactory factory) { 67 | serverInterceptors.forEach(factory::addResponderPlugin); 68 | 69 | List micrometerTags = properties.getMicrometerTags(); 70 | Tag[] tags = Tags.of(micrometerTags.toArray(new String[] {})) 71 | .and("gateway.id", properties.getId()).stream() 72 | .collect(Collectors.toList()).toArray(new Tag[] {}); 73 | 74 | return factory 75 | // TODO: add as bean like serverInterceptors above 76 | .addConnectionPlugin( 77 | new MicrometerDuplexConnectionInterceptor(meterRegistry, tags)) 78 | .errorConsumer(throwable -> { 79 | if (log.isDebugEnabled()) { 80 | log.debug("Error with connection", throwable); 81 | } 82 | }); // TODO: add configurable errorConsumer 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/core/PendingRequestRSocketFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.core; 18 | 19 | import java.util.Set; 20 | import java.util.function.Consumer; 21 | import java.util.function.Function; 22 | import java.util.logging.Level; 23 | 24 | import io.micrometer.core.instrument.Tags; 25 | import org.apache.commons.logging.Log; 26 | import org.apache.commons.logging.LogFactory; 27 | import reactor.core.Disposable; 28 | import reactor.core.publisher.Mono; 29 | 30 | import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata; 31 | import org.springframework.cloud.gateway.rsocket.route.Route; 32 | import org.springframework.cloud.gateway.rsocket.route.Routes; 33 | import org.springframework.cloud.gateway.rsocket.routing.RoutingTable; 34 | import org.springframework.messaging.rsocket.MetadataExtractor; 35 | 36 | public class PendingRequestRSocketFactory { 37 | 38 | private static final Log log = LogFactory.getLog(PendingRequestRSocket.class); 39 | 40 | private final RoutingTable routingTable; 41 | 42 | private final Routes routes; 43 | 44 | private final MetadataExtractor metadataExtractor; 45 | 46 | public PendingRequestRSocketFactory(RoutingTable routingTable, Routes routes, 47 | MetadataExtractor metadataExtractor) { 48 | this.routingTable = routingTable; 49 | this.routes = routes; 50 | this.metadataExtractor = metadataExtractor; 51 | } 52 | 53 | public Mono create(GatewayExchange exchange) { 54 | if (log.isDebugEnabled()) { 55 | log.debug("creating pending RSocket for " + exchange.getRoutingMetadata()); 56 | } 57 | PendingRequestRSocket pending = constructPendingRSocket(exchange); 58 | Disposable disposable = this.routingTable.addListener(pending); 59 | pending.setSubscriptionDisposable(disposable); 60 | return Mono.just(pending); 61 | } 62 | 63 | protected PendingRequestRSocket constructPendingRSocket(GatewayExchange exchange) { 64 | Function> routeFinder = registeredEvent -> getRouteMono( 65 | registeredEvent, exchange); 66 | Consumer tagsMetadataConsumer = tagsMetadata -> { 67 | Tags tags = exchange.getTags().and("responder.id", tagsMetadata.getRouteId()); 68 | exchange.setTags(tags); 69 | }; 70 | return new PendingRequestRSocket(metadataExtractor, routeFinder, 71 | tagsMetadataConsumer); 72 | } 73 | 74 | /** 75 | * Finds routes using exchange of original request that created pending RSocket. 76 | * @param registeredEvent newly registered event 77 | * @param exchange from original request 78 | * @return route if route matches 79 | */ 80 | protected Mono getRouteMono(RoutingTable.RegisteredEvent registeredEvent, 81 | GatewayExchange exchange) { 82 | return this.routes.findRoute(exchange) 83 | .log(PendingRequestRSocket.class.getName() + ".find route pending", 84 | Level.FINEST) 85 | // TODO: can this be replaced with filter? 86 | .flatMap( 87 | route -> matchRoute(route, registeredEvent.getRoutingMetadata())); 88 | } 89 | 90 | /** 91 | * Matches route found using original exchange with routeIds from recently registered 92 | * routes. 93 | * @param route route found using original exchange. 94 | * @param tagsMetadata tags from recent registration. 95 | * @return 96 | */ 97 | private Mono matchRoute(Route route, TagsMetadata tagsMetadata) { 98 | Set routeIds = this.routingTable.findRouteIds(tagsMetadata); 99 | if (routeIds.contains(route.getId())) { 100 | return Mono.just(route); 101 | } 102 | return Mono.empty(); 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractFilterChain.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.filter; 18 | 19 | import java.util.Collections; 20 | import java.util.List; 21 | import java.util.ListIterator; 22 | 23 | import org.apache.commons.logging.Log; 24 | import org.apache.commons.logging.LogFactory; 25 | import reactor.core.publisher.Mono; 26 | 27 | import org.springframework.cloud.gateway.rsocket.filter.RSocketFilter.Success; 28 | import org.springframework.lang.Nullable; 29 | 30 | /** 31 | * Default implementation of {@link FilterChain}. 32 | * 33 | *

34 | * Each instance of this class represents one link in the chain. The public constructor 35 | * {@link #AbstractFilterChain(List)} initializes the full chain and represents its first 36 | * link. 37 | * 38 | *

39 | * This class is immutable and thread-safe. It can be created once and re-used to handle 40 | * request concurrently. 41 | * 42 | * Copied from org.springframework.web.server.handler.AbstractFilterChain 43 | * 44 | * @since 5.0 45 | */ 46 | public abstract class AbstractFilterChain 47 | implements FilterChain { 48 | 49 | private final Log log = LogFactory.getLog(getClass()); 50 | 51 | protected final List allFilters; 52 | 53 | @Nullable 54 | protected final F currentFilter; 55 | 56 | @Nullable 57 | protected final FC next; 58 | 59 | /** 60 | * Public constructor with the list of filters and the target handler to use. 61 | * @param filters the filters ahead of the handler 62 | */ 63 | @SuppressWarnings("unchecked") 64 | protected AbstractFilterChain(List filters) { 65 | this.allFilters = Collections.unmodifiableList(filters); 66 | FC chain = initChain(filters); 67 | this.currentFilter = (F) chain.currentFilter; 68 | this.next = (FC) chain.next; 69 | } 70 | 71 | private FC initChain(List filters) { 72 | FC chain = create(filters, null, null); 73 | ListIterator iterator = filters.listIterator(filters.size()); 74 | while (iterator.hasPrevious()) { 75 | chain = create(filters, iterator.previous(), chain); 76 | } 77 | return chain; 78 | } 79 | 80 | /** 81 | * Private constructor to represent one link in the chain. 82 | */ 83 | protected AbstractFilterChain(List allFilters, @Nullable F currentFilter, 84 | @Nullable FC next) { 85 | 86 | this.allFilters = allFilters; 87 | this.currentFilter = currentFilter; 88 | this.next = next; 89 | } 90 | 91 | /** 92 | * Private constructor to represent one link in the chain. 93 | */ 94 | protected abstract FC create(List allFilters, @Nullable F currentFilter, 95 | @Nullable FC next); 96 | 97 | public List getFilters() { 98 | return this.allFilters; 99 | } 100 | 101 | @Override 102 | @SuppressWarnings("unchecked") 103 | public Mono filter(E exchange) { 104 | return Mono.defer(() -> this.currentFilter != null && this.next != null 105 | ? this.currentFilter.filter(exchange, this.next) : getMonoSuccess()); 106 | } 107 | 108 | private Mono getMonoSuccess() { 109 | if (log.isDebugEnabled()) { 110 | log.debug("filter chain completed with success"); 111 | } 112 | return MONO_SUCCESS; 113 | } 114 | 115 | private static final Mono MONO_SUCCESS = Mono.just(Success.INSTANCE); 116 | 117 | } 118 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractRSocketExchange.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.filter; 18 | 19 | import java.util.Map; 20 | import java.util.concurrent.ConcurrentHashMap; 21 | 22 | public abstract class AbstractRSocketExchange implements RSocketExchange { 23 | 24 | private final Map attributes = new ConcurrentHashMap<>(); 25 | 26 | @Override 27 | public Map getAttributes() { 28 | return this.attributes; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/filter/FilterChain.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.filter; 18 | 19 | import reactor.core.publisher.Mono; 20 | 21 | import org.springframework.cloud.gateway.rsocket.filter.RSocketFilter.Success; 22 | 23 | /** 24 | * Contract to allow a {@link RSocketFilter} to delegate to the next in the chain. 25 | * 26 | * @author Spencer Gibb 27 | */ 28 | public interface FilterChain { 29 | 30 | /** 31 | * Delegate to the next {@code WebFilter} in the chain. 32 | * @param exchange the current server exchange 33 | * @return {@code Mono} to indicate when request handling is complete 34 | */ 35 | Mono filter(E exchange); 36 | 37 | } 38 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketExchange.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.filter; 18 | 19 | import java.util.Map; 20 | 21 | import org.springframework.lang.Nullable; 22 | import org.springframework.util.Assert; 23 | 24 | public interface RSocketExchange { 25 | 26 | /** 27 | * Return a mutable map of request attributes for the current exchange. 28 | * @return current attributes. 29 | */ 30 | Map getAttributes(); 31 | 32 | /** 33 | * Return the request attribute value if present. 34 | * @param name the attribute name 35 | * @param the attribute type 36 | * @return the attribute value 37 | */ 38 | @SuppressWarnings("unchecked") 39 | @Nullable 40 | default T getAttribute(String name) { 41 | return (T) getAttributes().get(name); 42 | } 43 | 44 | /** 45 | * Return the request attribute value or if not present raise an 46 | * {@link IllegalArgumentException}. 47 | * @param name the attribute name 48 | * @param the attribute type 49 | * @return the attribute value 50 | */ 51 | @SuppressWarnings("unchecked") 52 | default T getRequiredAttribute(String name) { 53 | T value = getAttribute(name); 54 | Assert.notNull(value, () -> "Required attribute '" + name + "' is missing"); 55 | return value; 56 | } 57 | 58 | /** 59 | * Return the request attribute value, or a default, fallback value. 60 | * @param name the attribute name 61 | * @param defaultValue a default value to return instead 62 | * @param the attribute type 63 | * @return the attribute value 64 | */ 65 | @SuppressWarnings("unchecked") 66 | default T getAttributeOrDefault(String name, T defaultValue) { 67 | return (T) getAttributes().getOrDefault(name, defaultValue); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.filter; 18 | 19 | import reactor.core.publisher.Mono; 20 | 21 | /** 22 | * Contract for interception-style, chained processing of Web requests that may be used to 23 | * implement cross-cutting, application-agnostic requirements such as security, timeouts, 24 | * and others. 25 | * 26 | * Copied from WebFilter 27 | * 28 | * @author Spencer Gibb 29 | */ 30 | public interface RSocketFilter> { 31 | 32 | /** 33 | * Enum to signal successful end of chain reached without the end being empty, i.e. 34 | * Mono<Void> via Mono.empty(). This is because at the end of the chain an 35 | * actual value needs to be returned. We can map success, but not empty. 36 | */ 37 | enum Success { 38 | 39 | INSTANCE 40 | 41 | } // should never have more than one value 42 | 43 | /** 44 | * Process the Web request and (optionally) delegate to the next {@code RSocketFilter} 45 | * through the given {@link FilterChain}. 46 | * @param exchange the current RSocket exchange 47 | * @param chain provides a way to delegate to the next filter 48 | * @return {@code Mono} to indicate when request processing is complete. 49 | */ 50 | Mono filter(E exchange, FC chain); 51 | 52 | } 53 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocketInterceptor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.metrics; 18 | 19 | import java.util.Objects; 20 | 21 | import io.micrometer.core.instrument.Meter; 22 | import io.micrometer.core.instrument.MeterRegistry; 23 | import io.micrometer.core.instrument.Tag; 24 | import io.rsocket.RSocket; 25 | import io.rsocket.plugins.RSocketInterceptor; 26 | 27 | public class MicrometerResponderRSocketInterceptor implements RSocketInterceptor { 28 | 29 | private final MeterRegistry meterRegistry; 30 | 31 | private final Tag[] tags; 32 | 33 | /** 34 | * Creates a new {@link RSocketInterceptor}. 35 | * @param meterRegistry the {@link MeterRegistry} to use to create {@link Meter}s. 36 | * @param tags the additional tags to attach to each {@link Meter} 37 | * @throws NullPointerException if {@code meterRegistry} is {@code null} 38 | */ 39 | public MicrometerResponderRSocketInterceptor(MeterRegistry meterRegistry, 40 | Tag... tags) { 41 | this.meterRegistry = Objects.requireNonNull(meterRegistry, 42 | "meterRegistry must not be null"); 43 | this.tags = tags; 44 | } 45 | 46 | @Override 47 | public MicrometerResponderRSocket apply(RSocket delegate) { 48 | Objects.requireNonNull(delegate, "delegate must not be null"); 49 | 50 | return new MicrometerResponderRSocket(delegate, meterRegistry, tags); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/route/Route.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.route; 18 | 19 | import java.util.List; 20 | 21 | import org.springframework.cloud.gateway.rsocket.core.GatewayExchange; 22 | import org.springframework.cloud.gateway.rsocket.core.GatewayFilter; 23 | import org.springframework.cloud.gateway.rsocket.support.AsyncPredicate; 24 | import org.springframework.core.Ordered; 25 | 26 | /** 27 | * @author Spencer Gibb 28 | */ 29 | public interface Route extends Ordered { 30 | 31 | String getId(); 32 | 33 | default int getOrder() { 34 | return 0; 35 | } 36 | 37 | AsyncPredicate getPredicate(); 38 | 39 | List getFilters(); 40 | 41 | } 42 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/route/Routes.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.route; 18 | 19 | import org.apache.commons.logging.Log; 20 | import org.apache.commons.logging.LogFactory; 21 | import reactor.core.publisher.Flux; 22 | import reactor.core.publisher.Mono; 23 | 24 | import org.springframework.cloud.gateway.rsocket.core.GatewayExchange; 25 | 26 | /** 27 | * @author Spencer Gibb 28 | */ 29 | public interface Routes { 30 | 31 | /** log. */ 32 | Log log = LogFactory.getLog(Routes.class); 33 | 34 | Flux getRoutes(); 35 | 36 | default Mono findRoute(GatewayExchange exchange) { 37 | return getRoutes() 38 | // individually filter routes so that filterWhen error delaying is not a 39 | // problem 40 | .concatMap(route -> Mono.just(route).filterWhen(r -> { 41 | // add the current route we are testing 42 | // TODO: exchange attributes 43 | // exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, 44 | // r.getId()); 45 | return r.getPredicate().apply(exchange); 46 | }) 47 | // instead of immediately stopping main flux due to error, log and 48 | // swallow it 49 | .doOnError(e -> log.error( 50 | "Error applying predicate for route: " + route.getId(), 51 | e)) 52 | .onErrorResume(e -> Mono.empty())) 53 | .next().map(route -> { 54 | if (log.isDebugEnabled()) { 55 | log.debug("Route matched: " + route.getId()); 56 | } 57 | return route; 58 | }); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/routing/LoadBalancerFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.routing; 18 | 19 | import java.util.List; 20 | import java.util.Random; 21 | import java.util.concurrent.atomic.AtomicInteger; 22 | import java.util.function.Function; 23 | 24 | import io.rsocket.RSocket; 25 | import org.apache.commons.logging.Log; 26 | import org.apache.commons.logging.LogFactory; 27 | import reactor.core.publisher.Mono; 28 | import reactor.util.function.Tuple2; 29 | 30 | import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata; 31 | 32 | public class LoadBalancerFactory { 33 | 34 | private static final Log log = LogFactory.getLog(LoadBalancerFactory.class); 35 | 36 | private final RoutingTable routingTable; 37 | 38 | public LoadBalancerFactory(RoutingTable routingTable) { 39 | this.routingTable = routingTable; 40 | } 41 | 42 | public List> find(TagsMetadata tagsMetadata) { 43 | List> rSockets = this.routingTable 44 | .findRSockets(tagsMetadata); 45 | return rSockets; 46 | } 47 | 48 | // TODO: potentially GatewayExchange or return a new Result Object? 49 | public Mono> choose(TagsMetadata tagsMetadata) { 50 | List> rSockets = this.routingTable 51 | .findRSockets(tagsMetadata); 52 | // TODO: change loadbalancer impl based on tags 53 | // TODO: cache loadbalancers based on tags 54 | return new RoundRobinLoadBalancer(tagsMetadata).apply(rSockets); 55 | } 56 | 57 | // TODO: Flux as input? 58 | // TODO: reuse commons load balancer? 59 | public interface LoadBalancer extends 60 | Function>, Mono>> { 61 | 62 | } 63 | 64 | public static class RoundRobinLoadBalancer implements LoadBalancer { 65 | 66 | private final TagsMetadata tagsMetadata; 67 | 68 | private final AtomicInteger position; 69 | 70 | public RoundRobinLoadBalancer(TagsMetadata tagsMetadata) { 71 | this(tagsMetadata, new Random().nextInt(1000)); 72 | } 73 | 74 | public RoundRobinLoadBalancer(TagsMetadata tagsMetadata, int seedPosition) { 75 | this.tagsMetadata = tagsMetadata; 76 | this.position = new AtomicInteger(seedPosition); 77 | } 78 | 79 | @Override 80 | public Mono> apply( 81 | List> rSockets) { 82 | if (rSockets.isEmpty()) { 83 | if (log.isWarnEnabled()) { 84 | log.warn("No servers available for: " + this.tagsMetadata); 85 | } 86 | return Mono.empty(); 87 | } 88 | // TODO: enforce order? 89 | int pos = Math.abs(this.position.incrementAndGet()); 90 | 91 | Tuple2 tuple = rSockets.get(pos % rSockets.size()); 92 | return Mono.just(tuple); 93 | } 94 | 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/routing/RoutingTableRoutes.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.routing; 18 | 19 | import java.util.Collection; 20 | import java.util.Collections; 21 | import java.util.List; 22 | import java.util.Map; 23 | import java.util.Objects; 24 | import java.util.Set; 25 | import java.util.concurrent.ConcurrentHashMap; 26 | import java.util.function.Consumer; 27 | 28 | import org.apache.commons.logging.Log; 29 | import org.apache.commons.logging.LogFactory; 30 | import org.reactivestreams.Publisher; 31 | import reactor.core.publisher.Flux; 32 | import reactor.core.publisher.Mono; 33 | 34 | import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata; 35 | import org.springframework.cloud.gateway.rsocket.core.GatewayExchange; 36 | import org.springframework.cloud.gateway.rsocket.core.GatewayFilter; 37 | import org.springframework.cloud.gateway.rsocket.route.Route; 38 | import org.springframework.cloud.gateway.rsocket.route.Routes; 39 | import org.springframework.cloud.gateway.rsocket.support.AsyncPredicate; 40 | import org.springframework.core.style.ToStringCreator; 41 | 42 | /** 43 | * View of RoutingTable as Route objects. 44 | */ 45 | public class RoutingTableRoutes 46 | implements Routes, Consumer { 47 | 48 | private static final Log log = LogFactory.getLog(RoutingTableRoutes.class); 49 | 50 | private Map routes = new ConcurrentHashMap<>(); 51 | 52 | private final RoutingTable routingTable; 53 | 54 | public RoutingTableRoutes(RoutingTable routingTable) { 55 | this.routingTable = routingTable; 56 | this.routingTable.addListener(this); 57 | } 58 | 59 | @Override 60 | public Flux getRoutes() { 61 | // TODO: sorting? 62 | // TODO: caching 63 | 64 | Collection routeCollection = routes.values(); 65 | if (log.isDebugEnabled()) { 66 | log.debug("Found routes: " + routeCollection); 67 | } 68 | return Flux.fromIterable(routeCollection); 69 | } 70 | 71 | @Override 72 | public void accept(RoutingTable.RegisteredEvent registeredEvent) { 73 | TagsMetadata routingMetadata = registeredEvent.getRoutingMetadata(); 74 | String routeId = routingMetadata.getRouteId(); 75 | 76 | routes.computeIfAbsent(routeId, key -> createRoute(routeId)); 77 | } 78 | 79 | private Route createRoute(String routeId) { 80 | AsyncPredicate predicate = new RoutIdPredicate(routingTable, 81 | routeId); 82 | 83 | RegistryRoute route = new RegistryRoute(routeId, predicate); 84 | 85 | if (log.isDebugEnabled()) { 86 | log.debug("Created Route for registered service " + route); 87 | } 88 | 89 | return route; 90 | } 91 | 92 | static class RoutIdPredicate implements AsyncPredicate { 93 | 94 | private final RoutingTable routingTable; 95 | 96 | private final String routeId; 97 | 98 | RoutIdPredicate(RoutingTable routingTable, String routeId) { 99 | this.routingTable = routingTable; 100 | this.routeId = routeId; 101 | } 102 | 103 | @Override 104 | public Publisher apply(GatewayExchange exchange) { 105 | // TODO: standard predicates 106 | // TODO: allow customized predicates 107 | Set routeIds = routingTable 108 | .findRouteIds(exchange.getRoutingMetadata()); 109 | return Mono.just(routeIds.contains(routeId)); 110 | } 111 | 112 | @Override 113 | public String toString() { 114 | return String.format("[RoutIdPredicate %s]", routeId); 115 | } 116 | 117 | } 118 | 119 | static class RegistryRoute implements Route { 120 | 121 | final String id; 122 | 123 | final AsyncPredicate predicate; 124 | 125 | RegistryRoute(String id, AsyncPredicate predicate) { 126 | this.id = id; 127 | this.predicate = predicate; 128 | } 129 | 130 | @Override 131 | public String getId() { 132 | return this.id; 133 | } 134 | 135 | @Override 136 | public AsyncPredicate getPredicate() { 137 | return this.predicate; 138 | } 139 | 140 | @Override 141 | public List getFilters() { 142 | return Collections.emptyList(); 143 | } 144 | 145 | @Override 146 | public boolean equals(Object o) { 147 | if (this == o) { 148 | return true; 149 | } 150 | if (o == null || getClass() != o.getClass()) { 151 | return false; 152 | } 153 | RegistryRoute that = (RegistryRoute) o; 154 | return Objects.equals(this.id, that.id) 155 | && Objects.equals(this.predicate, that.predicate); 156 | } 157 | 158 | @Override 159 | public int hashCode() { 160 | return Objects.hash(this.id, this.predicate); 161 | } 162 | 163 | @Override 164 | public String toString() { 165 | return new ToStringCreator(this).append("id", id) 166 | .append("predicate", predicate).toString(); 167 | 168 | } 169 | 170 | } 171 | 172 | } 173 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/routing/RoutingTableSocketAcceptorFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.routing; 18 | 19 | import reactor.core.publisher.Mono; 20 | 21 | import org.springframework.cloud.gateway.rsocket.socketacceptor.SocketAcceptorExchange; 22 | import org.springframework.cloud.gateway.rsocket.socketacceptor.SocketAcceptorFilter; 23 | import org.springframework.cloud.gateway.rsocket.socketacceptor.SocketAcceptorFilterChain; 24 | import org.springframework.core.Ordered; 25 | 26 | /** 27 | * Filter that registers the SendingSocket. 28 | */ 29 | public class RoutingTableSocketAcceptorFilter implements SocketAcceptorFilter, Ordered { 30 | 31 | private final RoutingTable routingTable; 32 | 33 | public RoutingTableSocketAcceptorFilter(RoutingTable routingTable) { 34 | this.routingTable = routingTable; 35 | } 36 | 37 | @Override 38 | public Mono filter(SocketAcceptorExchange exchange, 39 | SocketAcceptorFilterChain chain) { 40 | if (exchange.getMetadata() != null) { 41 | // TODO: needed? && 42 | // StringUtils.hasLength(exchange.getMetadata().getServiceName())) { 43 | this.routingTable.register(exchange.getMetadata().getEnrichedTagsMetadata(), 44 | exchange.getSendingSocket()); 45 | } 46 | 47 | return chain.filter(exchange); 48 | } 49 | 50 | @Override 51 | public int getOrder() { 52 | return HIGHEST_PRECEDENCE + 1000; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.socketacceptor; 18 | 19 | import java.util.List; 20 | import java.util.Map; 21 | import java.util.logging.Level; 22 | import java.util.stream.Collectors; 23 | 24 | import io.micrometer.core.instrument.MeterRegistry; 25 | import io.micrometer.core.instrument.Tag; 26 | import io.micrometer.core.instrument.Tags; 27 | import io.rsocket.ConnectionSetupPayload; 28 | import io.rsocket.RSocket; 29 | import io.rsocket.SocketAcceptor; 30 | import org.apache.commons.logging.Log; 31 | import org.apache.commons.logging.LogFactory; 32 | import reactor.core.publisher.Mono; 33 | 34 | import org.springframework.cloud.gateway.rsocket.autoconfigure.BrokerProperties; 35 | import org.springframework.cloud.gateway.rsocket.common.metadata.RouteSetup; 36 | import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata; 37 | import org.springframework.cloud.gateway.rsocket.core.GatewayRSocketFactory; 38 | import org.springframework.cloud.gateway.rsocket.metrics.MicrometerResponderRSocket; 39 | import org.springframework.messaging.rsocket.MetadataExtractor; 40 | import org.springframework.util.MimeType; 41 | 42 | public class GatewaySocketAcceptor implements SocketAcceptor { 43 | 44 | private static final Log log = LogFactory.getLog(GatewaySocketAcceptor.class); 45 | 46 | private final SocketAcceptorFilterChain filterChain; 47 | 48 | private final GatewayRSocketFactory rSocketFactory; 49 | 50 | private final MeterRegistry meterRegistry; 51 | 52 | private final BrokerProperties properties; 53 | 54 | private final MetadataExtractor metadataExtractor; 55 | 56 | public GatewaySocketAcceptor(GatewayRSocketFactory rSocketFactory, 57 | List filters, MeterRegistry meterRegistry, 58 | BrokerProperties properties, MetadataExtractor metadataExtractor) { 59 | this.rSocketFactory = rSocketFactory; 60 | this.filterChain = new SocketAcceptorFilterChain(filters); 61 | this.meterRegistry = meterRegistry; 62 | this.properties = properties; 63 | this.metadataExtractor = metadataExtractor; 64 | } 65 | 66 | @Override 67 | @SuppressWarnings("Duplicates") 68 | public Mono accept(ConnectionSetupPayload setup, RSocket sendingSocket) { 69 | if (log.isTraceEnabled()) { 70 | log.trace("accept()"); 71 | } 72 | // decorate GatewayRSocket with metrics 73 | // current gateway id, type requester, service name (from metadata), service id 74 | 75 | Tags requesterTags = Tags.of("gateway.id", properties.getId(), "type", 76 | "requester"); 77 | 78 | Tags metadataTags; 79 | SocketAcceptorExchange exchange; 80 | 81 | Map metadataMap = null; 82 | try { 83 | metadataMap = this.metadataExtractor.extract(setup, 84 | MimeType.valueOf(setup.metadataMimeType())); 85 | } 86 | catch (Exception e) { 87 | if (log.isDebugEnabled()) { 88 | log.debug("Error extracting metadata", e); 89 | } 90 | return Mono.error(e); 91 | } 92 | if (metadataMap.containsKey("routesetup")) { 93 | RouteSetup metadata = (RouteSetup) metadataMap.get("routesetup"); 94 | metadataTags = Tags.of("service.name", metadata.getServiceName()) 95 | .and("service.id", metadata.getId().toString()); 96 | // enrich exchange to have metadata 97 | exchange = new SocketAcceptorExchange(setup, 98 | decorate(sendingSocket, requesterTags.and(metadataTags)), metadata); 99 | } 100 | else { 101 | metadataTags = Tags.of("service.name", "UNKNOWN").and("service.id", 102 | "UNKNOWN"); 103 | exchange = new SocketAcceptorExchange(setup, 104 | decorate(sendingSocket, requesterTags)); 105 | } 106 | 107 | Tags responderTags = Tags 108 | .of("gateway.id", properties.getId(), "type", "responder") 109 | .and(metadataTags); 110 | 111 | // decorate with metrics gateway id, type responder, service name, service id 112 | // (instance id) 113 | return this.filterChain.filter(exchange).log( 114 | GatewaySocketAcceptor.class.getName() + ".socket acceptor filter chain", 115 | Level.FINEST).map(success -> { 116 | TagsMetadata tags = exchange.getMetadata().getEnrichedTagsMetadata(); 117 | return decorate(this.rSocketFactory.create(tags), responderTags); 118 | }); 119 | } 120 | 121 | private RSocket decorate(RSocket rSocket, Tags tags) { 122 | Tag[] tagArray = tags.stream().collect(Collectors.toList()).toArray(new Tag[] {}); 123 | return new MicrometerResponderRSocket(rSocket, meterRegistry, tagArray); 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorExchange.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.socketacceptor; 18 | 19 | import java.math.BigInteger; 20 | 21 | import io.rsocket.ConnectionSetupPayload; 22 | import io.rsocket.RSocket; 23 | 24 | import org.springframework.cloud.gateway.rsocket.common.metadata.RouteSetup; 25 | import org.springframework.cloud.gateway.rsocket.filter.AbstractRSocketExchange; 26 | 27 | public class SocketAcceptorExchange extends AbstractRSocketExchange { 28 | 29 | private final ConnectionSetupPayload setup; 30 | 31 | private final RSocket sendingSocket; 32 | 33 | private final RouteSetup metadata; 34 | 35 | public SocketAcceptorExchange(ConnectionSetupPayload setup, RSocket sendingSocket) { 36 | this(setup, sendingSocket, RouteSetup.of((BigInteger) null, null).build()); 37 | } 38 | 39 | public SocketAcceptorExchange(ConnectionSetupPayload setup, RSocket sendingSocket, 40 | RouteSetup metadata) { 41 | this.setup = setup; 42 | this.sendingSocket = sendingSocket; 43 | this.metadata = metadata; 44 | } 45 | 46 | public ConnectionSetupPayload getSetup() { 47 | return setup; 48 | } 49 | 50 | public RSocket getSendingSocket() { 51 | return sendingSocket; 52 | } 53 | 54 | public RouteSetup getMetadata() { 55 | return metadata; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.socketacceptor; 18 | 19 | import org.springframework.cloud.gateway.rsocket.filter.RSocketFilter; 20 | 21 | public interface SocketAcceptorFilter 22 | extends RSocketFilter { 23 | 24 | } 25 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorFilterChain.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.socketacceptor; 18 | 19 | import java.util.List; 20 | 21 | import org.springframework.cloud.gateway.rsocket.filter.AbstractFilterChain; 22 | 23 | public class SocketAcceptorFilterChain extends 24 | AbstractFilterChain { 25 | 26 | /** 27 | * Public constructor with the list of filters and the target handler to use. 28 | * @param filters the filters ahead of the handler 29 | */ 30 | public SocketAcceptorFilterChain(List filters) { 31 | super(filters); 32 | } 33 | 34 | public SocketAcceptorFilterChain(List allFilters, 35 | SocketAcceptorFilter currentFilter, SocketAcceptorFilterChain next) { 36 | super(allFilters, currentFilter, next); 37 | } 38 | 39 | @Override 40 | protected SocketAcceptorFilterChain create(List allFilters, 41 | SocketAcceptorFilter currentFilter, SocketAcceptorFilterChain next) { 42 | return new SocketAcceptorFilterChain(allFilters, currentFilter, next); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.socketacceptor; 18 | 19 | import org.springframework.cloud.gateway.rsocket.support.AsyncPredicate; 20 | 21 | public interface SocketAcceptorPredicate extends AsyncPredicate { 22 | 23 | } 24 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicateFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.socketacceptor; 18 | 19 | import java.util.List; 20 | 21 | import reactor.core.publisher.Mono; 22 | 23 | import org.springframework.cloud.gateway.rsocket.support.AsyncPredicate; 24 | import org.springframework.core.Ordered; 25 | import org.springframework.util.Assert; 26 | 27 | public class SocketAcceptorPredicateFilter implements SocketAcceptorFilter, Ordered { 28 | 29 | private final AsyncPredicate predicate; 30 | 31 | // TODO: change from List to Flux? 32 | public SocketAcceptorPredicateFilter(List predicates) { 33 | Assert.notNull(predicates, "predicates may not be null"); 34 | if (predicates.isEmpty()) { 35 | predicate = exchange -> Mono.just(true); 36 | } 37 | else { 38 | AsyncPredicate combined = predicates.get(0); 39 | for (SocketAcceptorPredicate p : predicates.subList(1, predicates.size())) { 40 | combined = combined.and(p); 41 | } 42 | predicate = combined; 43 | } 44 | } 45 | 46 | @Override 47 | public int getOrder() { 48 | return HIGHEST_PRECEDENCE + 10000; 49 | } 50 | 51 | @Override 52 | public Mono filter(SocketAcceptorExchange exchange, 53 | SocketAcceptorFilterChain chain) { 54 | return Mono.from(predicate.apply(exchange)).flatMap(test -> { 55 | if (test) { 56 | return chain.filter(exchange); 57 | } 58 | return Mono.empty(); 59 | }); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/java/org/springframework/cloud/gateway/rsocket/support/AsyncPredicate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.support; 18 | 19 | import java.util.function.Function; 20 | 21 | import org.reactivestreams.Publisher; 22 | import reactor.core.publisher.Flux; 23 | import reactor.core.publisher.Mono; 24 | 25 | import org.springframework.util.Assert; 26 | 27 | /** 28 | * @author Ben Hale 29 | */ 30 | public interface AsyncPredicate extends Function> { 31 | 32 | default AsyncPredicate and(AsyncPredicate other) { 33 | Assert.notNull(other, "other must not be null"); 34 | 35 | return t -> Flux.zip(apply(t), other.apply(t)) 36 | .map(tuple -> tuple.getT1() && tuple.getT2()); 37 | } 38 | 39 | default AsyncPredicate negate() { 40 | return t -> Mono.from(apply(t)).map(b -> !b); 41 | } 42 | 43 | default AsyncPredicate or(AsyncPredicate other) { 44 | Assert.notNull(other, "other must not be null"); 45 | 46 | return t -> Flux.zip(apply(t), other.apply(t)) 47 | .map(tuple -> tuple.getT1() || tuple.getT2()); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | # Auto Configure 2 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 3 | org.springframework.cloud.gateway.rsocket.autoconfigure.GatewayRSocketAutoConfiguration 4 | 5 | # Environment Post Processors 6 | org.springframework.boot.env.EnvironmentPostProcessor=\ 7 | org.springframework.cloud.gateway.rsocket.autoconfigure.GatewayRSocketEnvironmentPostProcessor 8 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/test/java/org/springframework/cloud/gateway/rsocket/actuate/BrokerActuatorRegistrarTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.actuate; 18 | 19 | import org.junit.Test; 20 | import org.junit.runner.RunWith; 21 | 22 | import org.springframework.beans.factory.annotation.Autowired; 23 | import org.springframework.boot.SpringBootConfiguration; 24 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 25 | import org.springframework.boot.test.context.SpringBootTest; 26 | import org.springframework.test.context.junit4.SpringRunner; 27 | 28 | import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; 29 | 30 | @RunWith(SpringRunner.class) 31 | @SpringBootTest(webEnvironment = RANDOM_PORT, 32 | properties = { "spring.rsocket.server.port=0", 33 | "spring.cloud.gateway.rsocket.route-id=55", 34 | "spring.cloud.gateway.rsocket.service-name=gateway" }) 35 | public class BrokerActuatorRegistrarTests { 36 | 37 | @Autowired 38 | private BrokerActuatorHandlerRegistration registrar; 39 | 40 | @Test 41 | public void test() { 42 | } 43 | 44 | @SpringBootConfiguration 45 | @EnableAutoConfiguration 46 | static class Config { 47 | 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/test/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfigurationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.autoconfigure; 18 | 19 | import io.rsocket.SocketAcceptor; 20 | import org.junit.Test; 21 | 22 | import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration; 23 | import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; 24 | import org.springframework.boot.autoconfigure.AutoConfigurations; 25 | import org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration; 26 | import org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration; 27 | import org.springframework.boot.rsocket.context.RSocketServerBootstrap; 28 | import org.springframework.boot.rsocket.server.RSocketServer; 29 | import org.springframework.boot.rsocket.server.RSocketServerFactory; 30 | import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; 31 | import org.springframework.cloud.gateway.rsocket.common.autoconfigure.GatewayRSocketCommonAutoConfiguration; 32 | import org.springframework.cloud.gateway.rsocket.core.GatewayServerRSocketFactoryProcessor; 33 | import org.springframework.cloud.gateway.rsocket.routing.RoutingTable; 34 | import org.springframework.cloud.gateway.rsocket.routing.RoutingTableRoutes; 35 | import org.springframework.cloud.gateway.rsocket.routing.RoutingTableSocketAcceptorFilter; 36 | import org.springframework.cloud.gateway.rsocket.socketacceptor.GatewaySocketAcceptor; 37 | import org.springframework.cloud.gateway.rsocket.socketacceptor.SocketAcceptorPredicate; 38 | import org.springframework.cloud.gateway.rsocket.socketacceptor.SocketAcceptorPredicateFilter; 39 | import org.springframework.context.annotation.Bean; 40 | import org.springframework.context.annotation.Configuration; 41 | 42 | import static org.assertj.core.api.Assertions.assertThat; 43 | import static org.mockito.ArgumentMatchers.any; 44 | import static org.mockito.Mockito.mock; 45 | import static org.mockito.Mockito.when; 46 | 47 | public class GatewayRSocketAutoConfigurationTests { 48 | 49 | @Test 50 | public void gatewayRSocketConfigured() { 51 | new ReactiveWebApplicationContextRunner().withUserConfiguration(MyConfig.class) 52 | .withSystemProperties("spring.cloud.gateway.rsocket.route-id=11") 53 | .withConfiguration( 54 | AutoConfigurations.of(RSocketStrategiesAutoConfiguration.class, 55 | RSocketMessagingAutoConfiguration.class, 56 | GatewayRSocketCommonAutoConfiguration.class, 57 | GatewayRSocketAutoConfiguration.class, 58 | CompositeMeterRegistryAutoConfiguration.class, 59 | MetricsAutoConfiguration.class)) 60 | .run(context -> assertThat(context).hasSingleBean(RoutingTable.class) 61 | .hasSingleBean(RoutingTableRoutes.class) 62 | .hasSingleBean(RoutingTableSocketAcceptorFilter.class) 63 | .hasSingleBean(GatewayServerRSocketFactoryProcessor.class) 64 | .hasSingleBean(BrokerProperties.class) 65 | .hasSingleBean(GatewaySocketAcceptor.class) 66 | .hasSingleBean(SocketAcceptorPredicateFilter.class) 67 | .hasSingleBean(RSocketServerBootstrap.class) 68 | .doesNotHaveBean(SocketAcceptorPredicate.class)); 69 | } 70 | 71 | @Configuration 72 | protected static class MyConfig { 73 | 74 | @Bean 75 | RSocketServerFactory rSocketServerFactory() { 76 | RSocketServerFactory serverFactory = mock(RSocketServerFactory.class); 77 | when(serverFactory.create(any(SocketAcceptor.class))) 78 | .thenReturn(mock(RSocketServer.class)); 79 | return serverFactory; 80 | } 81 | 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/test/java/org/springframework/cloud/gateway/rsocket/cluster/ClusterServiceTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.cluster; 18 | 19 | import org.junit.Test; 20 | 21 | import org.springframework.cloud.gateway.rsocket.actuate.BrokerInfo; 22 | 23 | import static org.assertj.core.api.Assertions.assertThat; 24 | 25 | public class ClusterServiceTests { 26 | 27 | @Test 28 | public void registerIncomingWorks() { 29 | ClusterService routingTable = new ClusterService(); 30 | 31 | BrokerInfo brokerInfo = BrokerInfo.of(1L).timestamp(100L).build(); 32 | boolean result = routingTable.registerIncoming(brokerInfo); 33 | 34 | String brokerId = brokerInfo.getBrokerId().toString(); 35 | assertThat(result).isTrue(); 36 | assertThat(routingTable.incomingBrokers).containsKey(brokerId); 37 | 38 | brokerInfo = BrokerInfo.of(1L).timestamp(10L).build(); 39 | result = routingTable.registerIncoming(brokerInfo); 40 | assertThat(result).isFalse(); 41 | assertThat(routingTable.incomingBrokers.get(brokerId)).isNotNull() 42 | .extracting(ClusterService.BrokerEntry::getTimestamp).isEqualTo(100L); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/test/java/org/springframework/cloud/gateway/rsocket/core/GatewayRSocketIntegrationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.core; 18 | 19 | import java.time.Duration; 20 | 21 | import org.junit.AfterClass; 22 | import org.junit.BeforeClass; 23 | import org.junit.Test; 24 | import org.junit.runner.RunWith; 25 | import reactor.core.publisher.Hooks; 26 | import reactor.test.StepVerifier; 27 | 28 | import org.springframework.beans.factory.annotation.Autowired; 29 | import org.springframework.boot.autoconfigure.rsocket.RSocketProperties; 30 | import org.springframework.boot.rsocket.context.RSocketServerBootstrap; 31 | import org.springframework.boot.test.context.SpringBootTest; 32 | import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; 33 | import org.springframework.cloud.gateway.rsocket.test.PingPongApp; 34 | import org.springframework.test.context.junit4.SpringRunner; 35 | import org.springframework.test.util.ReflectionTestUtils; 36 | import org.springframework.util.SocketUtils; 37 | 38 | import static org.assertj.core.api.Assertions.assertThat; 39 | 40 | @RunWith(SpringRunner.class) 41 | @SpringBootTest(classes = PingPongApp.class, 42 | properties = { "ping.take=10", "ping.subscribe=false" }, 43 | webEnvironment = WebEnvironment.RANDOM_PORT) 44 | public class GatewayRSocketIntegrationTests { 45 | 46 | private static int port; 47 | 48 | @Autowired 49 | private PingPongApp.Ping ping; 50 | 51 | @Autowired 52 | private PingPongApp.Pong pong; 53 | 54 | @Autowired 55 | private RSocketProperties properties; 56 | 57 | @Autowired 58 | private PingPongApp.MySocketAcceptorFilter mySocketAcceptorFilter; 59 | 60 | @Autowired 61 | private RSocketServerBootstrap server; 62 | 63 | @BeforeClass 64 | public static void init() { 65 | Hooks.onOperatorDebug(); 66 | port = SocketUtils.findAvailableTcpPort(); 67 | System.setProperty("spring.rsocket.server.port", String.valueOf(port)); 68 | } 69 | 70 | @AfterClass 71 | public static void after() { 72 | System.clearProperty("spring.rsocket.server.port"); 73 | } 74 | 75 | @Test 76 | public void contextLoads() { 77 | // @formatter:off 78 | StepVerifier.create(ping.getPongFlux()) 79 | .expectSubscription() 80 | .then(() -> server.stop()) 81 | .thenConsumeWhile(s -> true) 82 | .expectComplete() 83 | .verify(Duration.ofSeconds(20)); 84 | // @formatter:on 85 | 86 | assertThat(ping.getPongsReceived()).isGreaterThan(0); 87 | assertThat(pong.getPingsReceived()).isGreaterThan(0); 88 | Object server = properties.getServer(); 89 | Object port = ReflectionTestUtils.invokeGetterMethod(server, "port"); 90 | assertThat(port).isNotEqualTo(7002); 91 | assertThat(mySocketAcceptorFilter.invoked()).isTrue(); 92 | assertThat(this.server.isRunning()).isFalse(); 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/test/java/org/springframework/cloud/gateway/rsocket/routing/RoutingTableRoutesTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.routing; 18 | 19 | import java.util.HashSet; 20 | 21 | import io.rsocket.RSocket; 22 | import org.junit.Test; 23 | import reactor.core.publisher.Mono; 24 | import reactor.test.StepVerifier; 25 | 26 | import org.springframework.cloud.gateway.rsocket.common.metadata.Forwarding; 27 | import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata; 28 | import org.springframework.cloud.gateway.rsocket.core.GatewayExchange; 29 | import org.springframework.cloud.gateway.rsocket.route.Route; 30 | import org.springframework.cloud.gateway.rsocket.routing.RoutingTable.RegisteredEvent; 31 | import org.springframework.cloud.gateway.rsocket.routing.RoutingTable.RouteEntry; 32 | 33 | import static org.assertj.core.api.Assertions.assertThat; 34 | import static org.mockito.ArgumentMatchers.any; 35 | import static org.mockito.Mockito.mock; 36 | import static org.mockito.Mockito.when; 37 | import static org.springframework.cloud.gateway.rsocket.core.GatewayExchange.Type.REQUEST_RESPONSE; 38 | 39 | public class RoutingTableRoutesTests { 40 | 41 | @Test 42 | public void routesAreBuilt() { 43 | RoutingTable routingTable = mock(RoutingTable.class); 44 | RoutingTableRoutes routes = new RoutingTableRoutes(routingTable); 45 | 46 | HashSet routeIds = new HashSet<>(); 47 | routeIds.add("2"); 48 | when(routingTable.findRouteIds(any(TagsMetadata.class))).thenReturn(routeIds); 49 | addRoute(routes, "1"); 50 | addRoute(routes, "2"); 51 | addRoute(routes, "3"); 52 | 53 | Forwarding forwarding = Forwarding.of(1L).routeId("2").build(); 54 | Mono routeMono = routes 55 | .findRoute(new GatewayExchange(REQUEST_RESPONSE, forwarding)); 56 | 57 | StepVerifier.create(routeMono).consumeNextWith(route -> { 58 | assertThat(route).isNotNull().extracting(Route::getId).isEqualTo("2"); 59 | }).verifyComplete(); 60 | } 61 | 62 | void addRoute(RoutingTableRoutes routes, String routeId) { 63 | TagsMetadata tagsMetadata = TagsMetadata.builder().routeId(routeId).build(); 64 | 65 | RSocket rsocket = mock(RSocket.class); 66 | routes.accept(new RegisteredEvent(new RouteEntry(rsocket, tagsMetadata))); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicateFilterTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.socketacceptor; 18 | 19 | import java.util.Arrays; 20 | import java.util.Collections; 21 | import java.util.List; 22 | 23 | import io.rsocket.ConnectionSetupPayload; 24 | import io.rsocket.RSocket; 25 | import org.junit.Test; 26 | import org.reactivestreams.Publisher; 27 | import reactor.core.publisher.Mono; 28 | import reactor.test.StepVerifier; 29 | 30 | import org.springframework.cloud.gateway.rsocket.filter.RSocketFilter.Success; 31 | 32 | import static org.assertj.core.api.Assertions.assertThat; 33 | import static org.mockito.Mockito.mock; 34 | 35 | public class SocketAcceptorPredicateFilterTests { 36 | 37 | @Test 38 | public void noPredicateWorks() { 39 | Mono result = runFilter(Collections.emptyList()); 40 | StepVerifier.create(result).expectNext(Success.INSTANCE).verifyComplete(); 41 | } 42 | 43 | @Test 44 | public void singleTruePredicateWorks() { 45 | TestPredicate predicate = new TestPredicate(true); 46 | Mono result = runFilter(predicate); 47 | StepVerifier.create(result).expectNext(Success.INSTANCE).verifyComplete(); 48 | assertThat(predicate.invoked()).isTrue(); 49 | } 50 | 51 | @Test 52 | public void singleFalsePredicateWorks() { 53 | TestPredicate predicate = new TestPredicate(false); 54 | Mono result = runFilter(predicate); 55 | StepVerifier.create(result).verifyComplete(); 56 | 57 | assertThat(predicate.invoked()).isTrue(); 58 | } 59 | 60 | @Test 61 | public void multipleFalsePredicateWorks() { 62 | TestPredicate predicate = new TestPredicate(false); 63 | TestPredicate predicate2 = new TestPredicate(false); 64 | Mono result = runFilter(predicate, predicate2); 65 | StepVerifier.create(result).verifyComplete(); 66 | 67 | assertThat(predicate.invoked()).isTrue(); 68 | assertThat(predicate2.invoked()).isTrue(); // Async predicates don't short circuit 69 | } 70 | 71 | @Test 72 | public void multiplePredicatesNoSuccessWorks() { 73 | TestPredicate truePredicate = new TestPredicate(true); 74 | TestPredicate falsePredicate = new TestPredicate(false); 75 | Mono result = runFilter(truePredicate, falsePredicate); 76 | StepVerifier.create(result).verifyComplete(); 77 | assertThat(truePredicate.invoked()).isTrue(); 78 | assertThat(falsePredicate.invoked()).isTrue(); 79 | } 80 | 81 | @Test 82 | public void multiplePredicatesSuccessWorks() { 83 | TestPredicate truePredicate = new TestPredicate(true); 84 | TestPredicate truePredicate2 = new TestPredicate(true); 85 | Mono result = runFilter(truePredicate, truePredicate2); 86 | StepVerifier.create(result).expectNext(Success.INSTANCE).verifyComplete(); 87 | assertThat(truePredicate.invoked()).isTrue(); 88 | assertThat(truePredicate2.invoked()).isTrue(); 89 | } 90 | 91 | private Mono runFilter(SocketAcceptorPredicate predicate) { 92 | return runFilter(Collections.singletonList(predicate)); 93 | } 94 | 95 | private Mono runFilter(SocketAcceptorPredicate... predicates) { 96 | return runFilter(Arrays.asList(predicates)); 97 | } 98 | 99 | private Mono runFilter(List predicates) { 100 | SocketAcceptorPredicateFilter filter = new SocketAcceptorPredicateFilter( 101 | predicates); 102 | SocketAcceptorExchange exchange = new SocketAcceptorExchange( 103 | mock(ConnectionSetupPayload.class), mock(RSocket.class)); 104 | SocketAcceptorFilterChain filterChain = new SocketAcceptorFilterChain( 105 | Collections.singletonList(filter)); 106 | return filter.filter(exchange, filterChain); 107 | } 108 | 109 | private class TestPredicate implements SocketAcceptorPredicate { 110 | 111 | private boolean invoked = false; 112 | 113 | private final Mono test; 114 | 115 | TestPredicate(boolean value) { 116 | test = Mono.just(value); 117 | } 118 | 119 | @Override 120 | public Publisher apply(SocketAcceptorExchange exchange) { 121 | invoked = true; 122 | return test; 123 | } 124 | 125 | public boolean invoked() { 126 | return invoked; 127 | } 128 | 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/test/java/org/springframework/cloud/gateway/rsocket/test/SocketAcceptorFilterOrderTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.test; 18 | 19 | import java.util.Arrays; 20 | import java.util.Collections; 21 | import java.util.List; 22 | 23 | import org.junit.Test; 24 | 25 | import org.springframework.cloud.gateway.rsocket.routing.RoutingTable; 26 | import org.springframework.cloud.gateway.rsocket.routing.RoutingTableSocketAcceptorFilter; 27 | import org.springframework.cloud.gateway.rsocket.socketacceptor.SocketAcceptorFilter; 28 | import org.springframework.cloud.gateway.rsocket.socketacceptor.SocketAcceptorPredicateFilter; 29 | import org.springframework.core.OrderComparator; 30 | 31 | import static org.assertj.core.api.Assertions.assertThat; 32 | import static org.mockito.Mockito.mock; 33 | 34 | public class SocketAcceptorFilterOrderTests { 35 | 36 | @Test 37 | public void predicateFilterAfterRegistryFilter() { 38 | SocketAcceptorFilter predicateFilter = new SocketAcceptorPredicateFilter( 39 | Collections.emptyList()); 40 | SocketAcceptorFilter registryFilter = new RoutingTableSocketAcceptorFilter( 41 | mock(RoutingTable.class)); 42 | List filters = Arrays.asList(predicateFilter, 43 | registryFilter); 44 | OrderComparator.sort(filters); 45 | 46 | assertThat(filters).containsExactly(registryFilter, predicateFilter); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-broker/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | 2 | logging: 3 | level: 4 | # org.springframework.cloud.gateway.rsocket: DEBUG 5 | org.springframework.cloud.gateway.rsocket: TRACE 6 | org.springframework.messaging.handler.invocation.reactive: TRACE 7 | 8 | management: 9 | endpoints: 10 | web: 11 | exposure: 12 | include: '*' 13 | spring: 14 | cloud: 15 | gateway: 16 | rsocket: 17 | route-id: 1234 18 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 21 | 4.0.0 22 | 23 | org.springframework.cloud 24 | spring-cloud-rsocket 25 | 0.2.0.BUILD-SNAPSHOT 26 | .. 27 | 28 | org.springframework.cloud 29 | spring-cloud-rsocket-client 30 | Spring Cloud RSocket Client 31 | Spring Cloud RSocket Client 32 | 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-validation 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-rsocket 41 | 42 | 43 | org.springframework.cloud 44 | spring-cloud-rsocket-common 45 | 46 | 47 | org.springframework.boot 48 | spring-boot-configuration-processor 49 | true 50 | 51 | 52 | org.springframework.boot 53 | spring-boot-starter-actuator 54 | test 55 | 56 | 57 | org.projectlombok 58 | lombok 59 | test 60 | 61 | 62 | org.springframework.boot 63 | spring-boot-starter-test 64 | test 65 | 66 | 67 | io.projectreactor 68 | reactor-test 69 | test 70 | 71 | 72 | 73 | 74 | 75 | org.apache.maven.plugins 76 | maven-compiler-plugin 77 | 78 | 79 | -parameters 80 | 81 | 82 | 83 | 84 | 85 | default-compile 86 | none 87 | 88 | 89 | 90 | default-testCompile 91 | none 92 | 93 | 94 | java-compile 95 | compile 96 | 97 | compile 98 | 99 | 100 | 101 | java-test-compile 102 | test-compile 103 | 104 | testCompile 105 | 106 | 107 | 108 | 109 | 110 | org.apache.maven.plugins 111 | maven-jar-plugin 112 | 3.1.0 113 | 114 | 115 | 116 | test-jar 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | java8plus 126 | 127 | [1.8,2.0) 128 | 129 | 130 | 131 | 132 | org.apache.maven.plugins 133 | maven-compiler-plugin 134 | 135 | 136 | -parameters 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-client/src/main/java/org/springframework/cloud/gateway/rsocket/client/BrokerClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.client; 18 | 19 | import java.util.function.Consumer; 20 | 21 | import reactor.core.publisher.Mono; 22 | 23 | import org.springframework.cloud.gateway.rsocket.common.autoconfigure.Broker; 24 | import org.springframework.cloud.gateway.rsocket.common.metadata.Forwarding; 25 | import org.springframework.messaging.rsocket.RSocketRequester; 26 | 27 | public class BrokerClient { 28 | 29 | private final ClientProperties properties; 30 | 31 | private final RSocketRequester.Builder builder; 32 | 33 | public BrokerClient(ClientProperties properties, RSocketRequester.Builder builder) { 34 | this.properties = properties; 35 | this.builder = builder; 36 | } 37 | 38 | public ClientProperties getProperties() { 39 | return this.properties; 40 | } 41 | 42 | public RSocketRequester.Builder getRSocketRequesterBuilder() { 43 | return this.builder; 44 | } 45 | 46 | public Mono connect() { 47 | return connect(builder); 48 | } 49 | 50 | public Mono connect(RSocketRequester.Builder requesterBuilder) { 51 | Broker broker = properties.getBroker(); 52 | switch (broker.getConnectionType()) { 53 | case WEBSOCKET: 54 | return requesterBuilder.connectWebSocket(broker.getWsUri()); 55 | } 56 | return requesterBuilder.connectTcp(broker.getHost(), broker.getPort()); 57 | } 58 | 59 | public Consumer> forwarding(String destServiceName) { 60 | return spec -> { 61 | Forwarding forwarding = Forwarding.of(properties.getRouteId()) 62 | .serviceName(destServiceName).build(); 63 | spec.metadata(forwarding, Forwarding.FORWARDING_MIME_TYPE); 64 | }; 65 | } 66 | 67 | public Consumer> forwarding( 68 | Consumer builderConsumer) { 69 | return spec -> { 70 | Forwarding.Builder builder = Forwarding.of(properties.getRouteId()); 71 | builderConsumer.accept(builder); 72 | spec.metadata(builder.build(), Forwarding.FORWARDING_MIME_TYPE); 73 | }; 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-client/src/main/java/org/springframework/cloud/gateway/rsocket/client/BrokerClientConnectionListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.client; 18 | 19 | import java.util.function.Consumer; 20 | 21 | import org.springframework.boot.context.event.ApplicationReadyEvent; 22 | import org.springframework.context.ApplicationEventPublisher; 23 | import org.springframework.context.ApplicationListener; 24 | import org.springframework.context.PayloadApplicationEvent; 25 | import org.springframework.core.Ordered; 26 | import org.springframework.core.ResolvableType; 27 | import org.springframework.messaging.rsocket.RSocketRequester; 28 | 29 | /** 30 | * Automatically subscribes to {@link BrokerClient}. On subscribe it publishes a 31 | * {@link PayloadApplicationEvent} with a generic type of {@link RSocketRequester}. 32 | */ 33 | public class BrokerClientConnectionListener 34 | implements ApplicationListener, Ordered { 35 | 36 | private final BrokerClient brokerClient; 37 | 38 | private final ApplicationEventPublisher publisher; 39 | 40 | public BrokerClientConnectionListener(BrokerClient brokerClient, 41 | ApplicationEventPublisher publisher) { 42 | this.brokerClient = brokerClient; 43 | this.publisher = publisher; 44 | } 45 | 46 | @Override 47 | public void onApplicationEvent(ApplicationReadyEvent event) { 48 | // TODO: is there a better event the just RSocketRequester? 49 | // TODO: save Disposable? 50 | this.brokerClient.connect().subscribe(publishEvent()); 51 | } 52 | 53 | private Consumer publishEvent() { 54 | return requester -> publisher.publishEvent(new RSocketRequesterEvent<>( 55 | BrokerClientConnectionListener.this, requester)); 56 | } 57 | 58 | @Override 59 | public int getOrder() { 60 | return Ordered.HIGHEST_PRECEDENCE; // TODO: configurable 61 | } 62 | 63 | private static final class RSocketRequesterEvent 64 | extends PayloadApplicationEvent { 65 | 66 | private RSocketRequesterEvent(Object source, T payload) { 67 | super(source, payload); 68 | } 69 | 70 | @Override 71 | public ResolvableType getResolvableType() { 72 | return ResolvableType.forClassWithGenerics(getClass(), 73 | ResolvableType.forClass(RSocketRequester.class)); 74 | } 75 | 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-client/src/main/java/org/springframework/cloud/gateway/rsocket/client/ClientProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.client; 18 | 19 | import java.math.BigInteger; 20 | import java.util.LinkedHashMap; 21 | import java.util.Map; 22 | import java.util.Objects; 23 | import java.util.StringJoiner; 24 | 25 | import javax.validation.Valid; 26 | import javax.validation.constraints.NotEmpty; 27 | import javax.validation.constraints.NotNull; 28 | 29 | import org.springframework.boot.context.properties.ConfigurationProperties; 30 | import org.springframework.boot.context.properties.NestedConfigurationProperty; 31 | import org.springframework.cloud.gateway.rsocket.common.autoconfigure.Broker; 32 | import org.springframework.cloud.gateway.rsocket.common.metadata.WellKnownKey; 33 | import org.springframework.core.style.ToStringCreator; 34 | import org.springframework.util.StringUtils; 35 | import org.springframework.validation.annotation.Validated; 36 | 37 | @ConfigurationProperties("spring.cloud.gateway.rsocket.client") 38 | @Validated 39 | public class ClientProperties { 40 | 41 | @NotNull 42 | private BigInteger routeId; 43 | 44 | @NotEmpty 45 | private String serviceName; 46 | 47 | private Map tags = new LinkedHashMap<>(); 48 | 49 | @Valid 50 | @NestedConfigurationProperty 51 | private Broker broker = new Broker(); 52 | 53 | private Map> forwarding = new LinkedHashMap<>(); 54 | 55 | public BigInteger getRouteId() { 56 | return this.routeId; 57 | } 58 | 59 | public void setRouteId(BigInteger routeId) { 60 | this.routeId = routeId; 61 | } 62 | 63 | public String getServiceName() { 64 | return this.serviceName; 65 | } 66 | 67 | public void setServiceName(String serviceName) { 68 | this.serviceName = serviceName; 69 | } 70 | 71 | public Map getTags() { 72 | return tags; 73 | } 74 | 75 | public Broker getBroker() { 76 | return this.broker; 77 | } 78 | 79 | public void setBroker(Broker broker) { 80 | this.broker = broker; 81 | } 82 | 83 | public Map> getForwarding() { 84 | return forwarding; 85 | } 86 | 87 | @Override 88 | public String toString() { 89 | // @formatter:off 90 | return new ToStringCreator(this) 91 | .append("routeId", routeId) 92 | .append("serviceName", serviceName) 93 | .append("tags", tags) 94 | .append("broker", broker) 95 | .append("forwarding", forwarding) 96 | .toString(); 97 | // @formatter:on 98 | } 99 | 100 | public static class TagKey { 101 | 102 | private WellKnownKey wellKnownKey; 103 | 104 | private String customKey; 105 | 106 | public TagKey() { 107 | System.out.println("here"); 108 | } 109 | 110 | public TagKey(String text) { 111 | if (!StringUtils.isEmpty(text)) { 112 | try { 113 | wellKnownKey = WellKnownKey.valueOf(text.toUpperCase()); 114 | } 115 | catch (IllegalArgumentException e) { 116 | // NOT a valid well know key 117 | customKey = text; 118 | } 119 | } 120 | } 121 | 122 | public static TagKey of(WellKnownKey key) { 123 | TagKey tagKey = new TagKey(); 124 | tagKey.setWellKnownKey(key); 125 | return tagKey; 126 | } 127 | 128 | public static TagKey of(String key) { 129 | return new TagKey(key); 130 | } 131 | 132 | public WellKnownKey getWellKnownKey() { 133 | return wellKnownKey; 134 | } 135 | 136 | public void setWellKnownKey(WellKnownKey wellKnownKey) { 137 | this.wellKnownKey = wellKnownKey; 138 | } 139 | 140 | public String getCustomKey() { 141 | return customKey; 142 | } 143 | 144 | public void setCustomKey(String customKey) { 145 | this.customKey = customKey; 146 | } 147 | 148 | @Override 149 | public boolean equals(Object o) { 150 | if (this == o) { 151 | return true; 152 | } 153 | if (o == null || getClass() != o.getClass()) { 154 | return false; 155 | } 156 | TagKey tag = (TagKey) o; 157 | return wellKnownKey == tag.wellKnownKey 158 | && Objects.equals(customKey, tag.customKey); 159 | } 160 | 161 | @Override 162 | public int hashCode() { 163 | return Objects.hash(wellKnownKey, customKey); 164 | } 165 | 166 | @Override 167 | public String toString() { 168 | StringJoiner joiner = new StringJoiner(", ", "[", "]"); 169 | if (wellKnownKey != null) { 170 | joiner.add(wellKnownKey.name()); 171 | } 172 | if (customKey != null) { 173 | joiner.add("'" + customKey + "'"); 174 | } 175 | return joiner.toString(); 176 | } 177 | 178 | } 179 | 180 | } 181 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-client/src/main/java/org/springframework/cloud/gateway/rsocket/client/ClientRSocketRequesterBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.client; 18 | 19 | import java.net.URI; 20 | import java.util.function.Consumer; 21 | 22 | import io.rsocket.transport.ClientTransport; 23 | import io.rsocket.transport.netty.client.TcpClientTransport; 24 | import io.rsocket.transport.netty.client.WebsocketClientTransport; 25 | import reactor.core.publisher.Mono; 26 | 27 | import org.springframework.messaging.rsocket.ClientRSocketFactoryConfigurer; 28 | import org.springframework.messaging.rsocket.RSocketRequester; 29 | import org.springframework.messaging.rsocket.RSocketStrategies; 30 | import org.springframework.util.MimeType; 31 | import org.springframework.util.RouteMatcher; 32 | 33 | final class ClientRSocketRequesterBuilder implements RSocketRequester.Builder { 34 | 35 | private final RSocketRequester.Builder delegate; 36 | 37 | private final ClientProperties properties; 38 | 39 | private final RouteMatcher routeMatcher; 40 | 41 | ClientRSocketRequesterBuilder(RSocketRequester.Builder delegate, 42 | ClientProperties properties, RouteMatcher routeMatcher) { 43 | this.delegate = delegate; 44 | this.properties = properties; 45 | this.routeMatcher = routeMatcher; 46 | } 47 | 48 | @Override 49 | public RSocketRequester.Builder dataMimeType(MimeType mimeType) { 50 | return delegate.dataMimeType(mimeType); 51 | } 52 | 53 | @Override 54 | public RSocketRequester.Builder metadataMimeType(MimeType mimeType) { 55 | return delegate.metadataMimeType(mimeType); 56 | } 57 | 58 | @Override 59 | public RSocketRequester.Builder setupData(Object data) { 60 | return delegate.setupData(data); 61 | } 62 | 63 | @Override 64 | public RSocketRequester.Builder setupRoute(String route, Object... routeVars) { 65 | return delegate.setupRoute(route, routeVars); 66 | } 67 | 68 | @Override 69 | public RSocketRequester.Builder setupMetadata(Object value, MimeType mimeType) { 70 | return delegate.setupMetadata(value, mimeType); 71 | } 72 | 73 | @Override 74 | public RSocketRequester.Builder rsocketStrategies(RSocketStrategies strategies) { 75 | return delegate.rsocketStrategies(strategies); 76 | } 77 | 78 | @Override 79 | public RSocketRequester.Builder rsocketStrategies( 80 | Consumer configurer) { 81 | return delegate.rsocketStrategies(configurer); 82 | } 83 | 84 | @Override 85 | public RSocketRequester.Builder rsocketFactory( 86 | ClientRSocketFactoryConfigurer configurer) { 87 | return delegate.rsocketFactory(configurer); 88 | } 89 | 90 | @Override 91 | public RSocketRequester.Builder apply(Consumer configurer) { 92 | return delegate.apply(configurer); 93 | } 94 | 95 | @Override 96 | public Mono connectTcp(String host, int port) { 97 | return connect(TcpClientTransport.create(host, port)); 98 | } 99 | 100 | @Override 101 | public Mono connectWebSocket(URI uri) { 102 | return connect(WebsocketClientTransport.create(uri)); 103 | } 104 | 105 | @Override 106 | public Mono connect(ClientTransport transport) { 107 | return delegate.connect(transport) 108 | .map(requester -> new ClientRSocketRequester(requester, properties, 109 | routeMatcher)); 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-client/src/main/java/org/springframework/cloud/gateway/rsocket/client/GatewayRSocketClientAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.client; 18 | 19 | import java.math.BigInteger; 20 | import java.util.function.Supplier; 21 | 22 | import io.micrometer.core.instrument.MeterRegistry; 23 | import io.micrometer.core.instrument.Tag; 24 | import io.rsocket.RSocket; 25 | import io.rsocket.micrometer.MicrometerRSocketInterceptor; 26 | import io.rsocket.plugins.RSocketInterceptor; 27 | 28 | import org.springframework.beans.factory.annotation.Qualifier; 29 | import org.springframework.boot.autoconfigure.AutoConfigureAfter; 30 | import org.springframework.boot.autoconfigure.AutoConfigureBefore; 31 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 32 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 33 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 34 | import org.springframework.boot.autoconfigure.rsocket.RSocketRequesterAutoConfiguration; 35 | import org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration; 36 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 37 | import org.springframework.cloud.gateway.rsocket.common.metadata.RouteSetup; 38 | import org.springframework.context.ApplicationEventPublisher; 39 | import org.springframework.context.annotation.Bean; 40 | import org.springframework.context.annotation.Configuration; 41 | import org.springframework.context.annotation.Scope; 42 | import org.springframework.messaging.rsocket.ClientRSocketFactoryConfigurer; 43 | import org.springframework.messaging.rsocket.RSocketRequester; 44 | import org.springframework.messaging.rsocket.RSocketStrategies; 45 | import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler; 46 | 47 | import static org.springframework.cloud.gateway.rsocket.common.autoconfigure.GatewayRSocketCommonAutoConfiguration.ID_GENERATOR_BEAN_NAME; 48 | 49 | /** 50 | * @author Spencer Gibb 51 | */ 52 | @Configuration 53 | // TODO: add this property to config metadata 54 | @ConditionalOnProperty(name = "spring.cloud.gateway.rsocket.enabled", 55 | matchIfMissing = true) 56 | @EnableConfigurationProperties 57 | @ConditionalOnClass({ RSocket.class, RSocketRequester.class }) 58 | @AutoConfigureAfter(RSocketStrategiesAutoConfiguration.class) 59 | @AutoConfigureBefore(RSocketRequesterAutoConfiguration.class) 60 | public class GatewayRSocketClientAutoConfiguration { 61 | 62 | private final RSocketMessageHandler messageHandler; 63 | 64 | public GatewayRSocketClientAutoConfiguration(RSocketMessageHandler handler) { 65 | messageHandler = handler; 66 | } 67 | 68 | @Bean 69 | @Scope("prototype") // TODO: I don't think prototype works here 70 | @ConditionalOnMissingBean 71 | public RSocketRequester.Builder gatewayRSocketRequesterBuilder( 72 | RSocketStrategies strategies, ClientProperties properties, 73 | MeterRegistry meterRegistry) { 74 | RouteSetup.Builder routeSetup = RouteSetup.of(properties.getRouteId(), 75 | properties.getServiceName()); 76 | properties.getTags().forEach((key, value) -> { 77 | if (key.getWellKnownKey() != null) { 78 | routeSetup.with(key.getWellKnownKey(), value); 79 | } 80 | else if (key.getCustomKey() != null) { 81 | routeSetup.with(key.getCustomKey(), value); 82 | } 83 | }); 84 | 85 | MicrometerRSocketInterceptor interceptor = new MicrometerRSocketInterceptor( 86 | meterRegistry, Tag.of("servicename", properties.getServiceName())); 87 | 88 | RSocketRequester.Builder builder = RSocketRequester.builder() 89 | .setupMetadata(routeSetup.build(), RouteSetup.ROUTE_SETUP_MIME_TYPE) 90 | .rsocketStrategies(strategies).rsocketFactory(configurer(interceptor)); 91 | 92 | return new ClientRSocketRequesterBuilder(builder, properties, 93 | strategies.routeMatcher()); 94 | } 95 | 96 | private ClientRSocketFactoryConfigurer configurer(RSocketInterceptor interceptor) { 97 | return rsocketFactory -> rsocketFactory.addRequesterPlugin(interceptor) 98 | .acceptor(messageHandler.responder()); 99 | } 100 | 101 | @Bean 102 | public BrokerClient brokerClient(RSocketRequester.Builder builder, 103 | ClientProperties properties) { 104 | return new BrokerClient(properties, builder); 105 | } 106 | 107 | @Bean 108 | @ConditionalOnProperty(name = "spring.cloud.gateway.rsocket.client.auto-connect", 109 | matchIfMissing = true) 110 | public BrokerClientConnectionListener brokerClientConnectionListener( 111 | BrokerClient client, ApplicationEventPublisher publisher) { 112 | return new BrokerClientConnectionListener(client, publisher); 113 | } 114 | 115 | @Bean 116 | public ClientProperties clientProperties( 117 | @Qualifier(ID_GENERATOR_BEAN_NAME) Supplier idGenerator) { 118 | ClientProperties clientProperties = new ClientProperties(); 119 | clientProperties.setRouteId(idGenerator.get()); 120 | return clientProperties; 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-client/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | # Auto Configure 2 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 3 | org.springframework.cloud.gateway.rsocket.client.GatewayRSocketClientAutoConfiguration 4 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-client/src/test/java/org/springframework/cloud/gateway/rsocket/client/ClientPropertiesTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.client; 18 | 19 | import java.util.Map; 20 | 21 | import org.junit.Test; 22 | import org.junit.runner.RunWith; 23 | 24 | import org.springframework.beans.factory.annotation.Autowired; 25 | import org.springframework.boot.SpringBootConfiguration; 26 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 27 | import org.springframework.boot.test.context.SpringBootTest; 28 | import org.springframework.cloud.gateway.rsocket.client.ClientProperties.TagKey; 29 | import org.springframework.test.context.junit4.SpringRunner; 30 | 31 | import static org.assertj.core.api.Assertions.assertThat; 32 | import static org.assertj.core.api.Assertions.entry; 33 | import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; 34 | 35 | @RunWith(SpringRunner.class) 36 | @SpringBootTest(webEnvironment = RANDOM_PORT, 37 | properties = "spring.cloud.gateway.rsocket.client.auto-connect=false") 38 | public class ClientPropertiesTests { 39 | 40 | @Autowired 41 | ClientProperties properties; 42 | 43 | @Test 44 | public void clientProperties() { 45 | assertThat(properties).isNotNull(); 46 | assertThat(properties.getRouteId()).isEqualTo(11L); 47 | assertThat(properties.getServiceName()).isEqualTo("test_requester"); 48 | assertThat(properties.getTags()).containsEntry(TagKey.of("INSTANCE_NAME"), 49 | "test_requester1"); 50 | assertThat(properties.getForwarding()).containsKeys("test_responder-rc", 51 | "key.with.dots", "key.with.{replacement}"); 52 | Map map = properties.getForwarding().get("test_responder-rc"); 53 | assertThat(map).contains(entry(TagKey.of("SERVICE_NAME"), "test_responder"), 54 | entry(TagKey.of("custom-tag"), "custom-value")); 55 | assertThat(properties.getBroker()).isNotNull().extracting("host", "port") 56 | .containsExactly("localhost", 7002); 57 | } 58 | 59 | @SpringBootConfiguration 60 | @EnableAutoConfiguration 61 | static class Config { 62 | 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-client/src/test/java/org/springframework/cloud/gateway/rsocket/client/ClientRSocketRequesterTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.client; 18 | 19 | import java.math.BigInteger; 20 | import java.util.Collections; 21 | import java.util.HashMap; 22 | import java.util.LinkedHashMap; 23 | import java.util.Map; 24 | 25 | import org.junit.jupiter.api.Test; 26 | 27 | import org.springframework.cloud.gateway.rsocket.client.ClientProperties.TagKey; 28 | import org.springframework.cloud.gateway.rsocket.common.metadata.Forwarding; 29 | import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata.Key; 30 | import org.springframework.util.AntPathMatcher; 31 | import org.springframework.util.RouteMatcher; 32 | import org.springframework.util.SimpleRouteMatcher; 33 | 34 | import static org.assertj.core.api.Assertions.assertThat; 35 | import static org.springframework.cloud.gateway.rsocket.client.ClientRSocketRequester.expand; 36 | import static org.springframework.cloud.gateway.rsocket.client.ClientRSocketRequester.forwarding; 37 | import static org.springframework.cloud.gateway.rsocket.common.metadata.WellKnownKey.ROUTE_ID; 38 | import static org.springframework.cloud.gateway.rsocket.common.metadata.WellKnownKey.SERVICE_NAME; 39 | 40 | public class ClientRSocketRequesterTests { 41 | 42 | @Test 43 | public void forwardingWorks() { 44 | RouteMatcher routeMatcher = new SimpleRouteMatcher(new AntPathMatcher(".")); 45 | RouteMatcher.Route route = routeMatcher.parseRoute("myroute.foo1.bar1"); 46 | LinkedHashMap tags = new LinkedHashMap<>(); 47 | tags.put(TagKey.of(SERVICE_NAME), "{foo}"); 48 | tags.put(TagKey.of(ROUTE_ID), "22"); 49 | tags.put(TagKey.of("mycustomkey"), "{foo}-{bar}"); 50 | Forwarding fwd = (Forwarding) forwarding(routeMatcher, route, 51 | new BigInteger("11"), "myroute.{foo}.{bar}", tags).build(); 52 | 53 | assertThat(fwd).isNotNull(); 54 | assertThat(fwd.getEnrichedTagsMetadata().getTags()).isNotEmpty() 55 | .containsEntry(new Key(SERVICE_NAME), "foo1") 56 | .containsEntry(new Key(ROUTE_ID), "22") 57 | .containsEntry(new Key("mycustomkey"), "foo1-bar1"); 58 | } 59 | 60 | @Test 61 | public void expandArrayVars() { 62 | String result = expand("myroute.{foo}.{bar}", "foo1", "bar1"); 63 | assertThat(result).isEqualTo("myroute.foo1.bar1"); 64 | } 65 | 66 | @Test 67 | public void expandMapVars() { 68 | HashMap map = new HashMap<>(); 69 | map.put("value", "a+b"); 70 | map.put("city", "Z\u00fcrich"); 71 | String result = expand("/hotel list/{city} specials/{value}", map); 72 | 73 | assertThat(result).isEqualTo("/hotel list/Z\u00fcrich specials/a+b"); 74 | } 75 | 76 | @Test 77 | public void expandPartially() { 78 | HashMap map = new HashMap<>(); 79 | map.put("city", "Z\u00fcrich"); 80 | String result = expand("/hotel list/{city} specials/{value}", map); 81 | 82 | assertThat(result).isEqualTo("/hotel list/Zürich specials/"); 83 | } 84 | 85 | @Test 86 | public void expandSimple() { 87 | HashMap map = new HashMap<>(); 88 | map.put("foo", "1 2"); 89 | map.put("bar", "3 4"); 90 | String result = expand("/{foo} {bar}", map); 91 | assertThat(result).isEqualTo("/1 2 3 4"); 92 | } 93 | 94 | @Test // SPR-13311 95 | public void expandWithRegexVar() { 96 | String template = "/myurl/{name:[a-z]{1,5}}/show"; 97 | Map map = Collections.singletonMap("name", "test"); 98 | String result = expand(template, map); 99 | assertThat(result).isEqualTo("/myurl/test/show"); 100 | } 101 | 102 | @Test // SPR-17630 103 | public void expandWithMismatchedCurlyBraces() { 104 | String result = expand("/myurl/{{{{", Collections.emptyMap()); 105 | assertThat(result).isEqualTo("/myurl/{{{{"); 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-client/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring.cloud.gateway.rsocket.client: 2 | route-id: 11 3 | service-name: test_requester 4 | tags: 5 | INSTANCE_NAME: test_requester1 6 | forwarding: 7 | test_responder-rc: 8 | service_name: test_responder 9 | custom-tag: custom-value 10 | "[key.with.dots]": 11 | service_name: service_with_dots 12 | "[key.with.{replacement}]": 13 | service_name: service_with_replacement 14 | broker: 15 | host: localhost 16 | port: 7002 17 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 21 | 4.0.0 22 | 23 | org.springframework.cloud 24 | spring-cloud-rsocket 25 | 0.2.0.BUILD-SNAPSHOT 26 | .. 27 | 28 | org.springframework.cloud 29 | spring-cloud-rsocket-common 30 | Spring Cloud RSocket Common 31 | Spring Cloud RSocket Common 32 | 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-rsocket 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-validation 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-configuration-processor 45 | true 46 | 47 | 48 | io.rsocket 49 | rsocket-core 50 | 51 | 52 | io.rsocket 53 | rsocket-micrometer 54 | 55 | 56 | io.rsocket 57 | rsocket-transport-netty 58 | 59 | 60 | io.micrometer 61 | micrometer-core 62 | 63 | 64 | org.roaringbitmap 65 | RoaringBitmap 66 | 67 | 68 | org.springframework.boot 69 | spring-boot-starter-actuator 70 | test 71 | 72 | 73 | org.projectlombok 74 | lombok 75 | test 76 | 77 | 78 | org.springframework.boot 79 | spring-boot-starter-test 80 | test 81 | 82 | 83 | io.projectreactor 84 | reactor-test 85 | test 86 | 87 | 88 | 89 | 90 | 91 | org.apache.maven.plugins 92 | maven-compiler-plugin 93 | 94 | 95 | -parameters 96 | 97 | 98 | 99 | 100 | 101 | default-compile 102 | none 103 | 104 | 105 | 106 | default-testCompile 107 | none 108 | 109 | 110 | java-compile 111 | compile 112 | 113 | compile 114 | 115 | 116 | 117 | java-test-compile 118 | test-compile 119 | 120 | testCompile 121 | 122 | 123 | 124 | 125 | 126 | org.apache.maven.plugins 127 | maven-jar-plugin 128 | 3.1.0 129 | 130 | 131 | 132 | test-jar 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | java8plus 142 | 143 | [1.8,2.0) 144 | 145 | 146 | 147 | 148 | org.apache.maven.plugins 149 | maven-compiler-plugin 150 | 151 | 152 | -parameters 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-common/src/main/java/org/springframework/cloud/gateway/rsocket/common/autoconfigure/Broker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.common.autoconfigure; 18 | 19 | import java.net.URI; 20 | 21 | import javax.validation.constraints.NotNull; 22 | 23 | import org.springframework.core.style.ToStringCreator; 24 | 25 | public class Broker { 26 | 27 | public enum ConnectionType { 28 | 29 | /** TCP RSocket connection. */ 30 | TCP, 31 | /** WEBSOCKET RSocket connection. */ 32 | WEBSOCKET 33 | 34 | } 35 | 36 | // FIXME: validate based on connectionType 37 | private String host; 38 | 39 | private int port; 40 | 41 | @NotNull 42 | private ConnectionType connectionType = ConnectionType.TCP; 43 | 44 | private URI wsUri; 45 | 46 | public String getHost() { 47 | return this.host; 48 | } 49 | 50 | public void setHost(String host) { 51 | this.host = host; 52 | } 53 | 54 | public int getPort() { 55 | return this.port; 56 | } 57 | 58 | public void setPort(int port) { 59 | this.port = port; 60 | } 61 | 62 | public ConnectionType getConnectionType() { 63 | return this.connectionType; 64 | } 65 | 66 | public void setConnectionType(ConnectionType connectionType) { 67 | this.connectionType = connectionType; 68 | } 69 | 70 | public URI getWsUri() { 71 | return this.wsUri; 72 | } 73 | 74 | public void setWsUri(URI wsUri) { 75 | this.wsUri = wsUri; 76 | } 77 | 78 | @Override 79 | public String toString() { 80 | // @formatter:off 81 | return new ToStringCreator(this) 82 | .append("host", host) 83 | .append("port", port) 84 | .append("wsUri", wsUri) 85 | .append("connectionType", connectionType) 86 | .toString(); 87 | // @formatter:on 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-common/src/main/java/org/springframework/cloud/gateway/rsocket/common/autoconfigure/GatewayRSocketCommonAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.common.autoconfigure; 18 | 19 | import java.math.BigInteger; 20 | import java.security.SecureRandom; 21 | import java.util.function.Supplier; 22 | 23 | import io.rsocket.RSocket; 24 | 25 | import org.springframework.boot.autoconfigure.AutoConfigureBefore; 26 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 27 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 28 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 29 | import org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration; 30 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 31 | import org.springframework.boot.rsocket.messaging.RSocketStrategiesCustomizer; 32 | import org.springframework.cloud.gateway.rsocket.common.metadata.Forwarding; 33 | import org.springframework.cloud.gateway.rsocket.common.metadata.RouteSetup; 34 | import org.springframework.context.annotation.Bean; 35 | import org.springframework.context.annotation.Configuration; 36 | 37 | /** 38 | * @author Spencer Gibb 39 | */ 40 | @Configuration 41 | @ConditionalOnProperty(name = "spring.cloud.gateway.rsocket.enabled", 42 | matchIfMissing = true) 43 | @EnableConfigurationProperties 44 | @ConditionalOnClass(RSocket.class) 45 | @AutoConfigureBefore(RSocketStrategiesAutoConfiguration.class) 46 | public class GatewayRSocketCommonAutoConfiguration { 47 | 48 | /** 49 | * Name of id generator bean. 50 | */ 51 | public static final String ID_GENERATOR_BEAN_NAME = "gatewayRSocketIdGenerator"; 52 | 53 | private final SecureRandom secureRandom = new SecureRandom(); 54 | 55 | @Bean 56 | public RSocketStrategiesCustomizer gatewayRSocketStrategiesCustomizer() { 57 | return strategies -> { 58 | strategies.decoder(new Forwarding.Decoder(), new RouteSetup.Decoder()) 59 | .encoder(new Forwarding.Encoder(), new RouteSetup.Encoder()); 60 | }; 61 | } 62 | 63 | @Bean(name = ID_GENERATOR_BEAN_NAME) 64 | @ConditionalOnMissingBean(name = ID_GENERATOR_BEAN_NAME) 65 | public Supplier gatewayRSocketIdGenerator() { 66 | return () -> { 67 | byte[] bytes = new byte[16]; 68 | secureRandom.nextBytes(bytes); 69 | return new BigInteger(bytes); 70 | }; 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-common/src/main/java/org/springframework/cloud/gateway/rsocket/common/autoconfigure/GatewayRSocketCommonMetadataAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.common.autoconfigure; 18 | 19 | import io.rsocket.RSocket; 20 | 21 | import org.springframework.beans.factory.InitializingBean; 22 | import org.springframework.boot.autoconfigure.AutoConfigureAfter; 23 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 24 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 25 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 26 | import org.springframework.cloud.gateway.rsocket.common.metadata.Forwarding; 27 | import org.springframework.cloud.gateway.rsocket.common.metadata.RouteSetup; 28 | import org.springframework.context.ApplicationContext; 29 | import org.springframework.context.annotation.Configuration; 30 | import org.springframework.messaging.rsocket.DefaultMetadataExtractor; 31 | import org.springframework.messaging.rsocket.MetadataExtractor; 32 | import org.springframework.messaging.rsocket.RSocketStrategies; 33 | 34 | import static org.springframework.cloud.gateway.rsocket.common.metadata.Forwarding.FORWARDING_MIME_TYPE; 35 | import static org.springframework.cloud.gateway.rsocket.common.metadata.RouteSetup.ROUTE_SETUP_MIME_TYPE; 36 | 37 | /** 38 | * @author Spencer Gibb 39 | */ 40 | @Configuration 41 | @ConditionalOnProperty(name = "spring.cloud.gateway.rsocket.enabled", 42 | matchIfMissing = true) 43 | @EnableConfigurationProperties 44 | @ConditionalOnClass(RSocket.class) 45 | @AutoConfigureAfter({ GatewayRSocketCommonAutoConfiguration.class }) 46 | public class GatewayRSocketCommonMetadataAutoConfiguration implements InitializingBean { 47 | 48 | private final ApplicationContext context; 49 | 50 | public GatewayRSocketCommonMetadataAutoConfiguration(ApplicationContext context) { 51 | this.context = context; 52 | } 53 | 54 | @Override 55 | public void afterPropertiesSet() { 56 | RSocketStrategies rSocketStrategies = this.context 57 | .getBean(RSocketStrategies.class); 58 | MetadataExtractor metadataExtractor = rSocketStrategies.metadataExtractor(); 59 | // TODO: see if possible to make easier in framework. 60 | if (metadataExtractor instanceof DefaultMetadataExtractor) { 61 | DefaultMetadataExtractor extractor = (DefaultMetadataExtractor) metadataExtractor; 62 | extractor.metadataToExtract(FORWARDING_MIME_TYPE, Forwarding.class, 63 | Forwarding.METADATA_KEY); 64 | extractor.metadataToExtract(ROUTE_SETUP_MIME_TYPE, RouteSetup.class, 65 | RouteSetup.METADATA_KEY); 66 | } 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-common/src/main/java/org/springframework/cloud/gateway/rsocket/common/metadata/Metadata.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.common.metadata; 18 | 19 | import io.rsocket.metadata.WellKnownMimeType; 20 | 21 | import org.springframework.util.MimeType; 22 | 23 | public abstract class Metadata { 24 | 25 | /** 26 | * Composite Metadata MimeType. 27 | */ 28 | public static final MimeType COMPOSITE_MIME_TYPE = MimeType 29 | .valueOf(WellKnownMimeType.MESSAGE_RSOCKET_COMPOSITE_METADATA.toString()); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-common/src/main/java/org/springframework/cloud/gateway/rsocket/common/metadata/WellKnownKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.common.metadata; 18 | 19 | import java.util.Arrays; 20 | import java.util.HashMap; 21 | import java.util.Map; 22 | 23 | public enum WellKnownKey { 24 | 25 | // CHECKSTYLE:OFF 26 | // @formatter:off 27 | UNPARSEABLE_KEY("UNPARSEABLE_KEY_DO_NOT_USE", (byte) -2), 28 | UNKNOWN_RESERVED_KEY("UNKNOWN_YET_RESERVED_DO_NOT_USE", (byte) -1), 29 | 30 | NO_TAG("NO_TAG_DO_NOT_USE", (byte) 0x00), 31 | SERVICE_NAME("io.rsocket.routing.ServiceName", (byte) 0x01), 32 | ROUTE_ID("io.rsocket.routing.RouteId", (byte) 0x02), 33 | INSTANCE_NAME("io.rsocket.routing.InstanceName", (byte) 0x03), 34 | CLUSTER_NAME("io.rsocket.routing.ClusterName", (byte) 0x04), 35 | PROVIDER("io.rsocket.routing.Provider", (byte) 0x05), 36 | REGION("io.rsocket.routing.Region", (byte) 0x06), 37 | ZONE("io.rsocket.routing.Zone", (byte) 0x07), 38 | DEVICE("io.rsocket.routing.Device", (byte) 0x08), 39 | OS("io.rsocket.routing.OS", (byte) 0x09), 40 | USER_NAME("io.rsocket.routing.UserName", (byte) 0x0A), 41 | USER_ID("io.rsocket.routing.UserId", (byte) 0x0B), 42 | MAJOR_VERSION("io.rsocket.routing.MajorVersion", (byte) 0x0C), 43 | MINOR_VERSION("io.rsocket.routing.MinorVersion", (byte) 0x0D), 44 | PATCH_VERSION("io.rsocket.routing.PatchVersion", (byte) 0x0E), 45 | VERSION("io.rsocket.routing.Version", (byte) 0x0F), 46 | ENVIRONMENT("io.rsocket.routing.Environment", (byte) 0x10), 47 | TESTC_ELL("io.rsocket.routing.TestCell", (byte) 0x11), 48 | DNS("io.rsocket.routing.DNS", (byte) 0x12), 49 | IPV4("io.rsocket.routing.IPv4", (byte) 0x13), 50 | IPV6("io.rsocket.routing.IPv6", (byte) 0x14), 51 | COUNTRY("io.rsocket.routing.Country", (byte) 0x15), 52 | TIME_ZONE("io.rsocket.routing.TimeZone", (byte) 0x1A), 53 | SHARD_KEY("io.rsocket.routing.ShardKey", (byte) 0x1B), 54 | SHARD_METHOD("io.rsocket.routing.ShardMethod", (byte) 0x1C), 55 | STICKY_ROUTE_KEY("io.rsocket.routing.StickyRouteKey", (byte) 0x1D), 56 | LB_METHOD("io.rsocket.routing.LBMethod", (byte) 0x1E), 57 | BROKER_EXTENSION("Broker Implementation Extension Key", (byte) 0x1E), 58 | WELL_KNOWN_EXTENSION("Well Known Extension Key", (byte) 0x1E); 59 | // @formatter:on 60 | // CHECKSTYLE:ON 61 | 62 | static final WellKnownKey[] TYPES_BY_ID; 63 | static final Map TYPES_BY_STRING; 64 | 65 | static { 66 | // precompute an array of all valid mime ids, 67 | // filling the blanks with the RESERVED enum 68 | TYPES_BY_ID = new WellKnownKey[128]; // 0-127 inclusive 69 | Arrays.fill(TYPES_BY_ID, UNKNOWN_RESERVED_KEY); 70 | // also prepare a Map of the types by key string 71 | TYPES_BY_STRING = new HashMap<>(128); 72 | 73 | for (WellKnownKey value : values()) { 74 | if (value.getIdentifier() >= 0) { 75 | TYPES_BY_ID[value.getIdentifier()] = value; 76 | TYPES_BY_STRING.put(value.getString(), value); 77 | } 78 | } 79 | } 80 | 81 | private final byte identifier; 82 | 83 | private final String str; 84 | 85 | WellKnownKey(String str, byte identifier) { 86 | this.str = str; 87 | this.identifier = identifier; 88 | } 89 | 90 | public static WellKnownKey fromIdentifier(int id) { 91 | if (id < 0x00 || id > 0x7F) { 92 | return UNPARSEABLE_KEY; 93 | } 94 | return TYPES_BY_ID[id]; 95 | } 96 | 97 | public static WellKnownKey fromMimeType(String mimeType) { 98 | if (mimeType == null) { 99 | throw new IllegalArgumentException("type must be non-null"); 100 | } 101 | 102 | // force UNPARSEABLE if by chance UNKNOWN_RESERVED_MIME_TYPE's text has been used 103 | if (mimeType.equals(UNKNOWN_RESERVED_KEY.str)) { 104 | return UNPARSEABLE_KEY; 105 | } 106 | 107 | return TYPES_BY_STRING.getOrDefault(mimeType, UNPARSEABLE_KEY); 108 | } 109 | 110 | /** 111 | * @return the byte identifier of the mime type, guaranteed to be positive or zero. 112 | */ 113 | public byte getIdentifier() { 114 | return identifier; 115 | } 116 | 117 | /** 118 | * @return the mime type represented as a {@link String}, which is made of US_ASCII 119 | * compatible characters only 120 | */ 121 | public String getString() { 122 | return str; 123 | } 124 | 125 | /** @see #getString() */ 126 | @Override 127 | public String toString() { 128 | return str; 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-common/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | # Auto Configure 2 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 3 | org.springframework.cloud.gateway.rsocket.common.autoconfigure.GatewayRSocketCommonAutoConfiguration,\ 4 | org.springframework.cloud.gateway.rsocket.common.autoconfigure.GatewayRSocketCommonMetadataAutoConfiguration 5 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-common/src/test/java/org/springframework/cloud/gateway/rsocket/common/metadata/ForwardingIntegrationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.common.metadata; 18 | 19 | import java.util.Map; 20 | 21 | import io.netty.buffer.ByteBuf; 22 | import io.netty.buffer.Unpooled; 23 | import io.rsocket.Payload; 24 | import io.rsocket.util.DefaultPayload; 25 | import org.junit.runner.RunWith; 26 | 27 | import org.springframework.beans.factory.annotation.Autowired; 28 | import org.springframework.boot.SpringBootConfiguration; 29 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 30 | import org.springframework.boot.test.context.SpringBootTest; 31 | import org.springframework.cloud.gateway.rsocket.common.test.MetadataEncoder; 32 | import org.springframework.core.io.buffer.DataBuffer; 33 | import org.springframework.messaging.rsocket.MetadataExtractor; 34 | import org.springframework.messaging.rsocket.RSocketStrategies; 35 | import org.springframework.test.context.junit4.SpringRunner; 36 | 37 | import static org.assertj.core.api.Assertions.assertThat; 38 | import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; 39 | 40 | @RunWith(SpringRunner.class) 41 | @SpringBootTest(properties = "spring.rsocket.server.port=0", webEnvironment = RANDOM_PORT) 42 | public class ForwardingIntegrationTests extends ForwardingTests { 43 | 44 | @Autowired 45 | private RSocketStrategies strategies; 46 | 47 | @Override 48 | protected ByteBuf encode(Forwarding forwarding) { 49 | DataBuffer dataBuffer = new MetadataEncoder(Metadata.COMPOSITE_MIME_TYPE, 50 | strategies).metadata(forwarding, Forwarding.FORWARDING_MIME_TYPE) 51 | .encode(); 52 | return TagsMetadata.asByteBuf(dataBuffer); 53 | } 54 | 55 | @Override 56 | protected Forwarding decode(ByteBuf byteBuf) { 57 | MetadataExtractor metadataExtractor = strategies.metadataExtractor(); 58 | Payload payload = DefaultPayload.create(Unpooled.EMPTY_BUFFER, byteBuf); 59 | Map metadata = metadataExtractor.extract(payload, 60 | Metadata.COMPOSITE_MIME_TYPE); 61 | assertThat(metadata).containsKey(Forwarding.METADATA_KEY); 62 | 63 | return (Forwarding) metadata.get(Forwarding.METADATA_KEY); 64 | } 65 | 66 | @SpringBootConfiguration 67 | @EnableAutoConfiguration 68 | static class Config { 69 | 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-common/src/test/java/org/springframework/cloud/gateway/rsocket/common/metadata/ForwardingTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.common.metadata; 18 | 19 | import java.math.BigInteger; 20 | import java.util.LinkedHashMap; 21 | 22 | import io.netty.buffer.ByteBuf; 23 | import org.junit.Test; 24 | 25 | import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata.Key; 26 | 27 | import static org.assertj.core.api.Assertions.assertThat; 28 | import static org.springframework.cloud.gateway.rsocket.common.metadata.RouteSetupTests.MAX_BIGINT; 29 | import static org.springframework.cloud.gateway.rsocket.common.metadata.RouteSetupTests.TWO_BYTE_BIGINT; 30 | import static org.springframework.cloud.gateway.rsocket.common.metadata.WellKnownKey.REGION; 31 | 32 | public class ForwardingTests { 33 | 34 | @Test 35 | public void encodeAndDecodeWorksMaxBigint() { 36 | ByteBuf byteBuf = createForwarding(MAX_BIGINT); 37 | assertForwarding(byteBuf, MAX_BIGINT); 38 | } 39 | 40 | @Test 41 | public void encodeAndDecodeWorksMinBigint() { 42 | ByteBuf byteBuf = createForwarding(BigInteger.ONE); 43 | assertForwarding(byteBuf, BigInteger.ONE); 44 | } 45 | 46 | @Test 47 | public void encodeAndDecodeWorksTwoBytes() { 48 | ByteBuf byteBuf = createForwarding(TWO_BYTE_BIGINT); 49 | assertForwarding(byteBuf, TWO_BYTE_BIGINT); 50 | } 51 | 52 | protected ByteBuf createForwarding(BigInteger originRouteId) { 53 | LinkedHashMap tags = new LinkedHashMap<>(); 54 | Forwarding forwarding = Forwarding.of(originRouteId).with(REGION, "us-east-1") 55 | .build(); 56 | return encode(forwarding); 57 | } 58 | 59 | protected ByteBuf encode(Forwarding forwarding) { 60 | return forwarding.encode(); 61 | } 62 | 63 | protected void assertForwarding(ByteBuf byteBuf, BigInteger originRouteId) { 64 | Forwarding forwarding = decode(byteBuf); 65 | assertThat(forwarding).isNotNull(); 66 | assertThat(forwarding.getOriginRouteId()).isEqualTo(originRouteId); 67 | assertThat(forwarding.getTags()).hasSize(1).containsOnlyKeys(new Key(REGION)) 68 | .containsValues("us-east-1"); 69 | } 70 | 71 | protected Forwarding decode(ByteBuf byteBuf) { 72 | return Forwarding.decodeForwarding(byteBuf); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-common/src/test/java/org/springframework/cloud/gateway/rsocket/common/metadata/RouteSetupIntegrationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.common.metadata; 18 | 19 | import java.util.Map; 20 | 21 | import io.netty.buffer.ByteBuf; 22 | import io.netty.buffer.Unpooled; 23 | import io.rsocket.Payload; 24 | import io.rsocket.util.DefaultPayload; 25 | import org.junit.runner.RunWith; 26 | 27 | import org.springframework.beans.factory.annotation.Autowired; 28 | import org.springframework.boot.SpringBootConfiguration; 29 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 30 | import org.springframework.boot.test.context.SpringBootTest; 31 | import org.springframework.cloud.gateway.rsocket.common.test.MetadataEncoder; 32 | import org.springframework.core.io.buffer.DataBuffer; 33 | import org.springframework.messaging.rsocket.MetadataExtractor; 34 | import org.springframework.messaging.rsocket.RSocketStrategies; 35 | import org.springframework.test.context.junit4.SpringRunner; 36 | 37 | import static org.assertj.core.api.Assertions.assertThat; 38 | import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; 39 | 40 | @RunWith(SpringRunner.class) 41 | @SpringBootTest(properties = "spring.rsocket.server.port=0", webEnvironment = RANDOM_PORT) 42 | public class RouteSetupIntegrationTests extends RouteSetupTests { 43 | 44 | @Autowired 45 | private RSocketStrategies strategies; 46 | 47 | @Override 48 | protected ByteBuf encode(RouteSetup routeSetup) { 49 | DataBuffer dataBuffer = new MetadataEncoder(Metadata.COMPOSITE_MIME_TYPE, 50 | strategies).metadata(routeSetup, RouteSetup.ROUTE_SETUP_MIME_TYPE) 51 | .encode(); 52 | return TagsMetadata.asByteBuf(dataBuffer); 53 | } 54 | 55 | @Override 56 | protected RouteSetup decode(ByteBuf byteBuf) { 57 | MetadataExtractor metadataExtractor = strategies.metadataExtractor(); 58 | Payload payload = DefaultPayload.create(Unpooled.EMPTY_BUFFER, byteBuf); 59 | Map metadata = metadataExtractor.extract(payload, 60 | Metadata.COMPOSITE_MIME_TYPE); 61 | assertThat(metadata).containsKey(RouteSetup.METADATA_KEY); 62 | 63 | return (RouteSetup) metadata.get(RouteSetup.METADATA_KEY); 64 | } 65 | 66 | @SpringBootConfiguration 67 | @EnableAutoConfiguration 68 | static class Config { 69 | 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-common/src/test/java/org/springframework/cloud/gateway/rsocket/common/metadata/RouteSetupTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.common.metadata; 18 | 19 | import java.math.BigInteger; 20 | 21 | import io.netty.buffer.ByteBuf; 22 | import org.junit.Test; 23 | 24 | import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata.Key; 25 | 26 | import static org.assertj.core.api.Assertions.assertThat; 27 | import static org.springframework.cloud.gateway.rsocket.common.metadata.WellKnownKey.REGION; 28 | 29 | public class RouteSetupTests { 30 | 31 | static final BigInteger MAX_BIGINT = new BigInteger( 32 | "170141183460469231731687303715884105727"); 33 | static final BigInteger TWO_BYTE_BIGINT = new BigInteger("128"); 34 | 35 | @Test 36 | public void bigIntegerTest() { 37 | byte[] bytes = MAX_BIGINT.toByteArray(); 38 | System.out.println("max bytes: " + bytes.length); 39 | 40 | bytes = BigInteger.ONE.toByteArray(); 41 | System.out.println("min bytes: " + bytes.length); 42 | 43 | BigInteger bigInteger = TWO_BYTE_BIGINT; 44 | bytes = bigInteger.toByteArray(); 45 | System.out.println("16 bytes: " + bytes.length); 46 | } 47 | 48 | @Test 49 | public void encodeAndDecodeWorksMaxBigint() { 50 | ByteBuf byteBuf = createRouteSetup(MAX_BIGINT); 51 | assertRouteSetup(byteBuf, MAX_BIGINT); 52 | } 53 | 54 | @Test 55 | public void encodeAndDecodeWorksMinBigint() { 56 | ByteBuf byteBuf = createRouteSetup(BigInteger.ONE); 57 | assertRouteSetup(byteBuf, BigInteger.ONE); 58 | } 59 | 60 | @Test 61 | public void encodeAndDecodeWorksTwoBytes() { 62 | ByteBuf byteBuf = createRouteSetup(TWO_BYTE_BIGINT); 63 | assertRouteSetup(byteBuf, TWO_BYTE_BIGINT); 64 | } 65 | 66 | @Test 67 | public void encodeAndDecodeWorksEmptyTags() { 68 | ByteBuf byteBuf = createRouteSetup(TWO_BYTE_BIGINT, false); 69 | assertRouteSetup(byteBuf, TWO_BYTE_BIGINT, false); 70 | } 71 | 72 | protected ByteBuf createRouteSetup(BigInteger id) { 73 | return createRouteSetup(id, true); 74 | } 75 | 76 | protected ByteBuf createRouteSetup(BigInteger id, boolean addTags) { 77 | RouteSetup.Builder routeSetup = RouteSetup.of(id, "myservice11111111"); 78 | if (addTags) { 79 | routeSetup.with(REGION, "us-east-1"); 80 | } 81 | return encode(routeSetup.build()); 82 | } 83 | 84 | protected ByteBuf encode(RouteSetup routeSetup) { 85 | return routeSetup.encode(); 86 | } 87 | 88 | protected void assertRouteSetup(ByteBuf byteBuf, BigInteger routeId) { 89 | assertRouteSetup(byteBuf, routeId, true); 90 | } 91 | 92 | protected void assertRouteSetup(ByteBuf byteBuf, BigInteger routeId, 93 | boolean addTags) { 94 | RouteSetup routeSetup = decode(byteBuf); 95 | assertThat(routeSetup).isNotNull(); 96 | assertThat(routeSetup.getId()).isEqualTo(routeId); 97 | assertThat(routeSetup.getServiceName()).isEqualTo("myservice11111111"); 98 | if (addTags) { 99 | assertThat(routeSetup.getTags()).hasSize(1).containsOnlyKeys(new Key(REGION)) 100 | .containsValues("us-east-1"); 101 | } 102 | else { 103 | assertThat(routeSetup.getTags()).isEmpty(); 104 | } 105 | } 106 | 107 | protected RouteSetup decode(ByteBuf byteBuf) { 108 | return RouteSetup.decodeRouteSetup(byteBuf); 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-common/src/test/java/org/springframework/cloud/gateway/rsocket/common/metadata/TagsMetadataTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.springframework.cloud.gateway.rsocket.common.metadata; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import org.junit.Test; 21 | 22 | import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata.Key; 23 | 24 | import static org.assertj.core.api.Assertions.assertThat; 25 | import static org.springframework.cloud.gateway.rsocket.common.metadata.WellKnownKey.ROUTE_ID; 26 | import static org.springframework.cloud.gateway.rsocket.common.metadata.WellKnownKey.SERVICE_NAME; 27 | 28 | public class TagsMetadataTests { 29 | 30 | @Test 31 | public void encodeAndDecodeWorksAllWellKnowKeys() { 32 | ByteBuf byteBuf = TagsMetadata.builder().with(ROUTE_ID, "routeId1111111") 33 | .with(SERVICE_NAME, "serviceName2222222").encode(); 34 | TagsMetadata metadata = TagsMetadata.decode(byteBuf); 35 | assertThat(metadata).isNotNull(); 36 | assertThat(metadata.getTags()).hasSize(2) 37 | .containsOnlyKeys(new Key(ROUTE_ID), new Key(SERVICE_NAME)) 38 | .containsValues("routeId1111111", "serviceName2222222"); 39 | } 40 | 41 | @Test 42 | public void encodeAndDecodeWorksAllStringKeys() { 43 | ByteBuf byteBuf = TagsMetadata.builder().with("mykey111111111", "myval1111111") 44 | .with("mykey2222222222", "myval2222222").encode(); 45 | TagsMetadata metadata = TagsMetadata.decode(byteBuf); 46 | assertThat(metadata).isNotNull(); 47 | assertThat(metadata.getTags()).hasSize(2) 48 | .containsOnlyKeys(new Key("mykey111111111"), new Key("mykey2222222222")) 49 | .containsValues("myval1111111", "myval2222222"); 50 | } 51 | 52 | @Test 53 | public void encodeAndDecodeWorksMixedKeys() { 54 | ByteBuf byteBuf = TagsMetadata.builder().with(ROUTE_ID, "routeId1111111") 55 | .with("mykey2222222222", "myval2222222").encode(); 56 | TagsMetadata metadata = TagsMetadata.decode(byteBuf); 57 | assertThat(metadata).isNotNull(); 58 | assertThat(metadata.getTags()).hasSize(2) 59 | .containsOnlyKeys(new Key(ROUTE_ID), new Key("mykey2222222222")) 60 | .containsValues("routeId1111111", "myval2222222"); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /spring-cloud-rsocket-dependencies/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | spring-cloud-dependencies-parent 8 | org.springframework.cloud 9 | 2.2.0.BUILD-SNAPSHOT 10 | 11 | 12 | 13 | spring-cloud-rsocket-dependencies 14 | 0.2.0.BUILD-SNAPSHOT 15 | pom 16 | 17 | spring-cloud-rsocket-dependencies 18 | Spring Cloud RSocket Dependencies 19 | 20 | 21 | 0.8.9 22 | 23 | 24 | 25 | 26 | 27 | org.springframework.cloud 28 | spring-cloud-rsocket-common 29 | ${project.version} 30 | 31 | 32 | org.springframework.cloud 33 | spring-cloud-rsocket-client 34 | ${project.version} 35 | 36 | 37 | org.springframework.cloud 38 | spring-cloud-rsocket-broker 39 | ${project.version} 40 | 41 | 42 | org.roaringbitmap 43 | RoaringBitmap 44 | ${roaringbitmap.version} 45 | 46 | 47 | 48 | 49 | 50 | 51 | spring 52 | 53 | 54 | spring-snapshots 55 | Spring Snapshots 56 | https://repo.spring.io/libs-snapshot-local 57 | 58 | true 59 | 60 | 61 | false 62 | 63 | 64 | 65 | spring-milestones 66 | Spring Milestones 67 | https://repo.spring.io/libs-milestone-local 68 | 69 | false 70 | 71 | 72 | 73 | spring-releases 74 | Spring Releases 75 | https://repo.spring.io/release 76 | 77 | false 78 | 79 | 80 | 81 | 82 | 83 | spring-snapshots 84 | Spring Snapshots 85 | https://repo.spring.io/libs-snapshot-local 86 | 87 | true 88 | 89 | 90 | false 91 | 92 | 93 | 94 | spring-milestones 95 | Spring Milestones 96 | https://repo.spring.io/libs-milestone-local 97 | 98 | false 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /src/checkstyle/checkstyle-suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | --------------------------------------------------------------------------------