├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── github-pkg.gradle ├── sonotype.gradle └── publications.gradle ├── rsocket-broker-client ├── src │ ├── main │ │ ├── resources │ │ │ └── reference.conf │ │ └── java │ │ │ └── io │ │ │ └── rsocket │ │ │ └── broker │ │ │ └── client │ │ │ ├── Route.java │ │ │ ├── BrokerRSocketClient.java │ │ │ └── BrokerRSocketConnector.java │ └── test │ │ ├── resources │ │ └── archaiustest1.properties │ │ └── java │ │ └── io │ │ └── rsocket │ │ └── broker │ │ ├── config │ │ ├── ArchaiusBrokerClientProperties.java │ │ ├── ArchaiusSampleTests.java │ │ └── ArchaiusConfigFactory.java │ │ └── client │ │ └── BrokerRSocketClientTests.java └── build.gradle ├── README.md ├── gradle.properties ├── rsocket-broker-client-spring ├── src │ ├── main │ │ ├── resources │ │ │ └── META-INF │ │ │ │ ├── spring │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ │ └── spring.factories │ │ └── java │ │ │ └── io │ │ │ └── rsocket │ │ │ └── broker │ │ │ └── client │ │ │ └── spring │ │ │ ├── BrokerClientRSocketStrategiesAutoConfiguration.java │ │ │ ├── BrokerMetadata.java │ │ │ ├── BrokerClientProperties.java │ │ │ ├── BrokerRSocketRequesterBuilder.java │ │ │ ├── BrokerClientAutoConfiguration.java │ │ │ └── BrokerRSocketRequester.java │ └── test │ │ ├── resources │ │ └── application.yaml │ │ └── java │ │ └── io │ │ └── rsocket │ │ └── broker │ │ └── client │ │ └── spring │ │ ├── BrokerClientAutoConfigurationTests.java │ │ ├── BrokerMetadataTests.java │ │ ├── BrokerClientPropertiesTests.java │ │ ├── BrokerRSocketRequesterTests.java │ │ └── CustomClientTransportFactoryTests.java └── build.gradle ├── rsocket-broker-common ├── build.gradle └── src │ ├── main │ └── java │ │ └── io │ │ └── rsocket │ │ └── broker │ │ └── common │ │ ├── Key.java │ │ ├── Transport.java │ │ ├── MimeTypes.java │ │ ├── ImmutableKey.java │ │ ├── Id.java │ │ ├── MutableKey.java │ │ ├── Tags.java │ │ └── WellKnownKey.java │ └── test │ └── java │ └── io │ └── rsocket │ └── broker │ └── common │ └── WellKnownKeyTests.java ├── rsocket-broker-frames ├── build.gradle └── src │ ├── main │ └── java │ │ └── io │ │ └── rsocket │ │ └── broker │ │ └── frames │ │ ├── BrokerFrame.java │ │ ├── RoutingType.java │ │ ├── RouteRemoveFlyweight.java │ │ ├── BrokerInfoFlyweight.java │ │ ├── FrameType.java │ │ ├── RouteSetupFlyweight.java │ │ ├── FlyweightUtils.java │ │ ├── RouteSetup.java │ │ ├── AddressFlyweight.java │ │ ├── RouteJoinFlyweight.java │ │ ├── FrameHeaderFlyweight.java │ │ ├── BrokerInfo.java │ │ ├── RouteRemove.java │ │ ├── RouteJoin.java │ │ ├── Address.java │ │ └── TagsFlyweight.java │ └── test │ └── java │ └── io │ └── rsocket │ └── broker │ └── frames │ ├── FrameHeaderFlyweightTests.java │ ├── AddressTests.java │ ├── RouteRemoveFlyweightTests.java │ ├── BrokerInfoFlyweightTests.java │ ├── RouteJoinFlyweightTests.java │ ├── RouteSetupFlyweightTests.java │ └── AddressFlyweightTests.java ├── settings.gradle ├── .gitignore ├── rsocket-broker-common-spring ├── build.gradle └── src │ └── main │ └── java │ └── io │ └── rsocket │ └── broker │ └── common │ └── spring │ ├── ClientTransportFactory.java │ ├── TransportFactory.java │ ├── MimeTypes.java │ ├── DefaultClientTransportFactory.java │ ├── BrokerFrameDecoder.java │ └── BrokerFrameEncoder.java ├── .github └── workflows │ ├── gradle-pr.yml │ ├── gradle.yml │ └── gradle-release.yml ├── gradlew.bat ├── CODE_OF_CONDUCT.md ├── gradlew └── LICENSE.txt /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsocket-broker/rsocket-broker-client/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /rsocket-broker-client/src/main/resources/reference.conf: -------------------------------------------------------------------------------- 1 | io.rsocket.broker.client { 2 | routeId = null 3 | serviceName = null 4 | tags = { 5 | 6 | } 7 | brokers = [] 8 | } -------------------------------------------------------------------------------- /rsocket-broker-client/src/test/resources/archaiustest1.properties: -------------------------------------------------------------------------------- 1 | io.rsocket.broker.client.routeId=00000000-0000-0000-0000-000000000022 2 | io.rsocket.broker.client.serviceName=testservice -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rsocket-broker-client 2 | 3 | [![Gitter](https://badges.gitter.im/rsocket-routing/community.svg)](https://gitter.im/rsocket-routing/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) 4 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | group=io.rsocket.broker 2 | version=0.4.0-SNAPSHOT 3 | 4 | assertjVersion=3.25.3 5 | junitJupiterVersion=5.10.2 6 | reactorBomVersion=2023.0.3 7 | rsocketVersion=1.1.4 8 | springBootVersion=3.2.3 9 | 10 | 11 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /rsocket-broker-client-spring/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports: -------------------------------------------------------------------------------- 1 | io.rsocket.broker.client.spring.BrokerClientRSocketStrategiesAutoConfiguration 2 | io.rsocket.broker.client.spring.BrokerClientAutoConfiguration -------------------------------------------------------------------------------- /rsocket-broker-common/build.gradle: -------------------------------------------------------------------------------- 1 | description = 'RSocket Broker Common' 2 | 3 | dependencies { 4 | implementation 'io.projectreactor.netty:reactor-netty' 5 | testImplementation 'org.junit.jupiter:junit-jupiter' 6 | testImplementation 'org.assertj:assertj-core' 7 | } 8 | -------------------------------------------------------------------------------- /rsocket-broker-client-spring/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | # Auto Configure 2 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 3 | io.rsocket.broker.client.spring.BrokerClientRSocketStrategiesAutoConfiguration,\ 4 | io.rsocket.broker.client.spring.BrokerClientAutoConfiguration 5 | -------------------------------------------------------------------------------- /rsocket-broker-frames/build.gradle: -------------------------------------------------------------------------------- 1 | description = 'RSocket Broker Frames' 2 | 3 | dependencies { 4 | api project(':rsocket-broker-common') 5 | 6 | implementation 'io.projectreactor.netty:reactor-netty' 7 | testImplementation 'org.junit.jupiter:junit-jupiter' 8 | testImplementation 'org.assertj:assertj-core' 9 | } 10 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.kordamp.gradle.insight' version '0.54.0' 3 | } 4 | 5 | rootProject.name = 'rsocket-broker-client' 6 | 7 | include "rsocket-broker-common" 8 | include "rsocket-broker-frames" 9 | include "rsocket-broker-client" 10 | include "rsocket-broker-common-spring" 11 | include "rsocket-broker-client-spring" 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/** 6 | !**/src/test/** 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | 17 | ### IntelliJ IDEA ### 18 | .idea 19 | *.iws 20 | *.iml 21 | *.ipr 22 | out/ 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | 31 | ### VS Code ### 32 | .vscode/ 33 | -------------------------------------------------------------------------------- /rsocket-broker-client-spring/src/test/resources/application.yaml: -------------------------------------------------------------------------------- 1 | io.rsocket.broker.client: 2 | route-id: 00000000-0000-0000-0000-000000000011 3 | service-name: test_requester 4 | tags: 5 | INSTANCE_NAME: test_requester1 6 | address: 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 | brokers: 15 | - tcp://localhost:7002 16 | -------------------------------------------------------------------------------- /rsocket-broker-client/build.gradle: -------------------------------------------------------------------------------- 1 | description = 'RSocket Broker Client' 2 | 3 | dependencies { 4 | api project(':rsocket-broker-common') 5 | api project(':rsocket-broker-frames') 6 | api 'io.rsocket:rsocket-core' 7 | 8 | implementation 'io.projectreactor.netty:reactor-netty' 9 | 10 | testImplementation 'com.netflix.archaius:archaius2-core:2.3.16' 11 | testImplementation 'io.rsocket:rsocket-transport-local' 12 | testImplementation 'org.assertj:assertj-core' 13 | testImplementation 'org.junit.jupiter:junit-jupiter' 14 | testImplementation 'org.slf4j:slf4j-simple:1.7.30' 15 | } -------------------------------------------------------------------------------- /gradle/github-pkg.gradle: -------------------------------------------------------------------------------- 1 | subprojects { 2 | 3 | plugins.withType(MavenPublishPlugin) { 4 | publishing { 5 | repositories { 6 | maven { 7 | name = "GitHubPackages" 8 | url = uri("https://maven.pkg.github.com/rsocket-broker/rsocket-broker-client") 9 | credentials { 10 | username = project.findProperty("gpr.user") ?: System.getenv("GITHUB_ACTOR") 11 | password = project.findProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN") 12 | } 13 | } 14 | } 15 | } 16 | 17 | tasks.named("publish").configure { 18 | onlyIf { System.getenv('SKIP_RELEASE') != "true" } 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /rsocket-broker-common-spring/build.gradle: -------------------------------------------------------------------------------- 1 | description = 'RSocket Broker Common Spring' 2 | 3 | dependencies { 4 | api project(':rsocket-broker-common') 5 | api project(':rsocket-broker-frames') 6 | api 'io.rsocket:rsocket-core' 7 | api 'io.rsocket:rsocket-transport-netty' 8 | 9 | annotationProcessor "org.springframework.boot:spring-boot-configuration-processor" 10 | 11 | implementation 'org.springframework.boot:spring-boot-starter-rsocket' 12 | implementation 'io.projectreactor.netty:reactor-netty' 13 | 14 | testImplementation 'org.junit.jupiter:junit-jupiter' 15 | testImplementation 'org.assertj:assertj-core' 16 | } 17 | -------------------------------------------------------------------------------- /rsocket-broker-client-spring/build.gradle: -------------------------------------------------------------------------------- 1 | description = 'RSocket Broker Client Spring' 2 | 3 | dependencies { 4 | api project(':rsocket-broker-client') 5 | api project(':rsocket-broker-common-spring') 6 | api project(':rsocket-broker-common') 7 | api project(':rsocket-broker-frames') 8 | 9 | annotationProcessor "org.springframework.boot:spring-boot-configuration-processor" 10 | 11 | implementation 'org.springframework.boot:spring-boot-starter-rsocket' 12 | 13 | testImplementation 'org.assertj:assertj-core' 14 | testImplementation 'org.junit.jupiter:junit-jupiter' 15 | testImplementation('org.springframework.boot:spring-boot-starter-test') { 16 | exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' 17 | } 18 | testImplementation 'io.rsocket:rsocket-transport-local' 19 | 20 | } 21 | -------------------------------------------------------------------------------- /rsocket-broker-common-spring/src/main/java/io/rsocket/broker/common/spring/ClientTransportFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.common.spring; 18 | 19 | import io.rsocket.transport.ClientTransport; 20 | 21 | public interface ClientTransportFactory extends TransportFactory { 22 | } 23 | -------------------------------------------------------------------------------- /.github/workflows/gradle-pr.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Gradle 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 3 | 4 | name: Pull Request Java CI with Gradle 5 | 6 | on: [pull_request] 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Set up JDK 17 16 | uses: actions/setup-java@v3 17 | with: 18 | java-version: 17 19 | distribution: 'temurin' 20 | - name: Cache Gradle packages 21 | uses: actions/cache@v3 22 | with: 23 | path: ~/.gradle/caches 24 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} 25 | restore-keys: ${{ runner.os }}-gradle 26 | - name: Grant execute permission for gradlew 27 | run: chmod +x gradlew 28 | - name: Build with Gradle 29 | run: ./gradlew build -Dbuild.number=$GITHUB_RUN_ID -------------------------------------------------------------------------------- /rsocket-broker-common-spring/src/main/java/io/rsocket/broker/common/spring/TransportFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.common.spring; 18 | 19 | import java.net.URI; 20 | 21 | public interface TransportFactory { 22 | boolean supports(URI uri); 23 | 24 | TO_CREATE create(URI uri); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /rsocket-broker-common/src/main/java/io/rsocket/broker/common/Key.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.common; 18 | 19 | public interface Key { 20 | WellKnownKey getWellKnownKey(); 21 | 22 | String getKey(); 23 | 24 | static Key of(String key) { 25 | return new ImmutableKey(key); 26 | } 27 | 28 | static Key of(WellKnownKey key) { 29 | return key.getKey(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /rsocket-broker-common/src/main/java/io/rsocket/broker/common/Transport.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.common; 18 | 19 | /** 20 | * Choice of transport protocol for the RSocket server. 21 | */ 22 | public enum Transport { 23 | /** 24 | * TCP transport protocol. 25 | */ 26 | TCP, 27 | 28 | /** 29 | * WebSocket transport protocol. 30 | */ 31 | WEBSOCKET 32 | 33 | } 34 | -------------------------------------------------------------------------------- /rsocket-broker-common/src/main/java/io/rsocket/broker/common/MimeTypes.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.common; 18 | 19 | public class MimeTypes { 20 | 21 | public static final String MESSAGE_TYPE = "message"; 22 | public static final String BROKER_FRAME_SUBTYPE = "x.rsocket.broker.frame.v0"; 23 | public static final String BROKER_FRAME_MIME_TYPE = MESSAGE_TYPE + "/" + BROKER_FRAME_SUBTYPE; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /rsocket-broker-frames/src/main/java/io/rsocket/broker/frames/BrokerFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.frames; 18 | 19 | public abstract class BrokerFrame { 20 | private final FrameType frameType; 21 | private final int flags; 22 | 23 | protected BrokerFrame(FrameType frameType, int flags) { 24 | this.frameType = frameType; 25 | this.flags = flags; 26 | } 27 | 28 | public FrameType getFrameType() { 29 | return this.frameType; 30 | } 31 | 32 | public int getFlags() { 33 | return this.flags; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /gradle/sonotype.gradle: -------------------------------------------------------------------------------- 1 | subprojects { 2 | if (project.hasProperty('sonatypeUsername') && project.hasProperty('sonatypePassword')) { 3 | plugins.withType(JavaLibraryPlugin) { 4 | plugins.withType(MavenPublishPlugin) { 5 | plugins.withType(SigningPlugin) { 6 | 7 | signing { 8 | //requiring signature if there is a publish task that is not to MavenLocal 9 | required { gradle.taskGraph.allTasks.any { it.name.toLowerCase().contains("publish") && !it.name.contains("MavenLocal") } } 10 | def signingKey = project.findProperty("signingKey") 11 | def signingPassword = project.findProperty("signingPassword") 12 | 13 | useInMemoryPgpKeys(signingKey, signingPassword) 14 | 15 | afterEvaluate { 16 | sign publishing.publications.maven 17 | } 18 | } 19 | 20 | publishing { 21 | repositories { 22 | maven { 23 | name = "sonatype" 24 | url = "https://oss.sonatype.org/service/local/staging/deploy/maven2" 25 | credentials { 26 | username project.findProperty("sonatypeUsername") 27 | password project.findProperty("sonatypePassword") 28 | } 29 | } 30 | } 31 | } 32 | } 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /rsocket-broker-client/src/main/java/io/rsocket/broker/client/Route.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.client; 18 | 19 | import java.util.function.Consumer; 20 | 21 | import io.netty.buffer.ByteBufAllocator; 22 | import io.netty.buffer.CompositeByteBuf; 23 | import io.rsocket.broker.frames.Address; 24 | 25 | public interface Route { 26 | ByteBufAllocator allocator(); 27 | 28 | void encodeAddressMetadata(CompositeByteBuf metadataHolder, String serviceName); 29 | 30 | void encodeAddressMetadata(CompositeByteBuf metadataHolder, Consumer addressConsumer); 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Gradle 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 3 | 4 | name: Java CI with Gradle 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | schedule: 10 | # * is a special character in YAML so you have to quote this string 11 | - cron: '0 6 * * *' 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Set up JDK 17 20 | uses: actions/setup-java@v3 21 | with: 22 | java-version: 17 23 | distribution: 'temurin' 24 | - name: Cache Gradle packages 25 | uses: actions/cache@v3 26 | with: 27 | path: ~/.gradle/caches 28 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} 29 | restore-keys: ${{ runner.os }}-gradle 30 | - name: Grant execute permission for gradlew 31 | run: chmod +x gradlew 32 | - name: Build with Gradle 33 | run: ./gradlew -PversionSuffix="-SNAPSHOT" -PbuildNumber="${buildNumber}" build publishMavenPublicationToGitHubPackagesRepository --no-daemon --stacktrace 34 | env: 35 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 36 | buildNumber: ${{ github.run_number }} 37 | -------------------------------------------------------------------------------- /rsocket-broker-client/src/test/java/io/rsocket/broker/config/ArchaiusBrokerClientProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.config; 18 | 19 | import java.util.List; 20 | import java.util.Map; 21 | 22 | import io.rsocket.broker.common.Id; 23 | import io.rsocket.broker.common.MutableKey; 24 | import io.rsocket.broker.common.Transport; 25 | 26 | public interface ArchaiusBrokerClientProperties { 27 | 28 | String CONFIG_PREFIX = "io.rsocket.broker.client"; 29 | 30 | Id getRouteId(); 31 | 32 | String getServiceName(); 33 | 34 | Map getTags(); 35 | 36 | List getBrokers(); 37 | 38 | interface Broker { 39 | 40 | String getHost(); 41 | 42 | int getPort(); 43 | 44 | Transport getTransport(); 45 | 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /rsocket-broker-common/src/test/java/io/rsocket/broker/common/WellKnownKeyTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 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 io.rsocket.broker.common; 18 | 19 | import java.util.HashMap; 20 | 21 | import org.junit.jupiter.api.Test; 22 | 23 | import static org.assertj.core.api.Assertions.fail; 24 | 25 | public class WellKnownKeyTests { 26 | 27 | @Test 28 | void noDuplicateIds() { 29 | HashMap keysById = new HashMap<>(); 30 | for (WellKnownKey key : WellKnownKey.values()) { 31 | if (keysById.containsKey(key.getIdentifier())) { 32 | WellKnownKey existing = keysById.get(key.getIdentifier()); 33 | fail(String.format("Duplicate id %d for already exists for key %s:%s. Duplicate key %s:%s", 34 | key.getIdentifier(), existing.name(), existing.getString(), key.name(), key.getString())); 35 | } 36 | keysById.put(key.getIdentifier(), key); 37 | } 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /rsocket-broker-frames/src/main/java/io/rsocket/broker/frames/RoutingType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.frames; 18 | 19 | import java.util.HashMap; 20 | import java.util.Map; 21 | 22 | public enum RoutingType { 23 | UNICAST(AddressFlyweight.FLAGS_U), 24 | MULTICAST(AddressFlyweight.FLAGS_M), 25 | SHARD(AddressFlyweight.FLAGS_S); 26 | 27 | private static Map routingTypesByFlag; 28 | 29 | static { 30 | routingTypesByFlag = new HashMap<>(); 31 | 32 | for (RoutingType routingType : values()) { 33 | routingTypesByFlag.put(routingType.getFlag(), routingType); 34 | } 35 | } 36 | 37 | private int flag; 38 | 39 | RoutingType(int flag) { 40 | this.flag = flag; 41 | } 42 | 43 | public int getFlag() { 44 | return this.flag; 45 | } 46 | 47 | public static RoutingType from(int flag) { 48 | return routingTypesByFlag.get(flag); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /rsocket-broker-common-spring/src/main/java/io/rsocket/broker/common/spring/MimeTypes.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.common.spring; 18 | 19 | import io.rsocket.metadata.WellKnownMimeType; 20 | 21 | import org.springframework.util.MimeType; 22 | 23 | /** 24 | * Holds MimeType objects for RSocket mime types. 25 | */ 26 | public abstract class MimeTypes { 27 | 28 | /** 29 | * Broker Frame mime type. 30 | */ 31 | public static final MimeType BROKER_FRAME_MIME_TYPE = new MimeType(io.rsocket.broker.common.MimeTypes.MESSAGE_TYPE, 32 | io.rsocket.broker.common.MimeTypes.BROKER_FRAME_SUBTYPE); 33 | public static final String BROKER_FRAME_METADATA_KEY = "brokerframe"; 34 | 35 | public static final MimeType COMPOSITE_MIME_TYPE = MimeType 36 | .valueOf(WellKnownMimeType.MESSAGE_RSOCKET_COMPOSITE_METADATA.toString()); 37 | 38 | private MimeTypes() { 39 | 40 | } 41 | 42 | 43 | } 44 | -------------------------------------------------------------------------------- /rsocket-broker-frames/src/test/java/io/rsocket/broker/frames/FrameHeaderFlyweightTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.frames; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.buffer.ByteBufAllocator; 21 | import org.junit.jupiter.api.Test; 22 | 23 | import static org.assertj.core.api.Assertions.assertThat; 24 | 25 | class FrameHeaderFlyweightTests { 26 | 27 | @Test 28 | void testEncoding() { 29 | int flags = 0b01_0100_0000; 30 | ByteBuf encoded = FrameHeaderFlyweight.encode(ByteBufAllocator.DEFAULT, FrameType.ROUTE_SETUP, flags); 31 | assertThat(FrameHeaderFlyweight.majorVersion(encoded)).isEqualTo(FrameHeaderFlyweight.MAJOR_VERSION); 32 | assertThat(FrameHeaderFlyweight.minorVersion(encoded)).isEqualTo(FrameHeaderFlyweight.MINOR_VERSION); 33 | assertThat(FrameHeaderFlyweight.flags(encoded)).isEqualTo(flags); 34 | assertThat(FrameHeaderFlyweight.frameType(encoded)).isEqualTo(FrameType.ROUTE_SETUP); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /rsocket-broker-client-spring/src/test/java/io/rsocket/broker/client/spring/BrokerClientAutoConfigurationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.client.spring; 18 | 19 | import org.junit.jupiter.api.Test; 20 | 21 | import org.springframework.beans.factory.annotation.Autowired; 22 | import org.springframework.boot.SpringBootConfiguration; 23 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 24 | import org.springframework.boot.test.context.SpringBootTest; 25 | import org.springframework.messaging.rsocket.RSocketRequester; 26 | 27 | import static org.assertj.core.api.Assertions.assertThat; 28 | 29 | @SpringBootTest 30 | public class BrokerClientAutoConfigurationTests { 31 | 32 | @Autowired(required = false) 33 | private RSocketRequester requester; 34 | 35 | @Test 36 | public void contextLoads() { 37 | assertThat(requester).isNotNull(); 38 | } 39 | 40 | @SpringBootConfiguration 41 | @EnableAutoConfiguration 42 | protected static class TestConfig{} 43 | } 44 | -------------------------------------------------------------------------------- /rsocket-broker-client/src/test/java/io/rsocket/broker/config/ArchaiusSampleTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.config; 18 | 19 | import io.rsocket.broker.common.Id; 20 | import org.junit.jupiter.api.BeforeEach; 21 | import org.junit.jupiter.api.Test; 22 | 23 | import static org.assertj.core.api.Assertions.assertThat; 24 | 25 | public class ArchaiusSampleTests { 26 | 27 | private ArchaiusBrokerClientProperties properties; 28 | 29 | @BeforeEach 30 | public void setup() { 31 | /*System.setProperty(BrokerClientProperties.CONFIG_PREFIX + ".serviceName", 32 | "servicefromproperties");*/ 33 | properties = ArchaiusConfigFactory.load("archaiustest1"); 34 | } 35 | 36 | @Test 37 | // https://github.com/Netflix/archaius/blob/2.x/archaius2-core/src/test/java/com/netflix/archaius/mapper/ProxyFactoryTest.java 38 | public void configWorks() { 39 | assertThat(properties.getServiceName()).isEqualTo("testservice"); 40 | assertThat(properties.getRouteId()).isEqualTo(Id.from("00000000-0000-0000-0000-000000000022")); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /rsocket-broker-frames/src/test/java/io/rsocket/broker/frames/AddressTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.frames; 18 | 19 | import io.rsocket.broker.common.Id; 20 | import org.junit.jupiter.api.Assertions; 21 | import org.junit.jupiter.api.Test; 22 | 23 | import static io.rsocket.broker.frames.RoutingType.MULTICAST; 24 | import static org.assertj.core.api.Assertions.assertThat; 25 | 26 | class AddressTests { 27 | 28 | @Test 29 | void testEmptyTagsFails() { 30 | Assertions.assertThrows(IllegalArgumentException.class, () -> 31 | Address.from(Id.random()).build()); 32 | } 33 | 34 | @Test 35 | void testFlags() { 36 | Address address = Address.from(Id.random()).with("mytag", "myval") 37 | .encrypted().routingType(MULTICAST).build(); 38 | int flags = 0; 39 | flags |= AddressFlyweight.FLAGS_E; 40 | flags |= AddressFlyweight.FLAGS_M; 41 | assertThat(address.getFlags()).isEqualTo(flags); 42 | assertThat(address.getRoutingType()).isEqualTo(MULTICAST); 43 | assertThat(address.isEncrypted()).isTrue(); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /rsocket-broker-frames/src/test/java/io/rsocket/broker/frames/RouteRemoveFlyweightTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.frames; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.buffer.ByteBufAllocator; 21 | import io.rsocket.broker.common.Id; 22 | import org.junit.jupiter.api.Test; 23 | 24 | import static org.assertj.core.api.Assertions.assertThat; 25 | 26 | class RouteRemoveFlyweightTests { 27 | 28 | @Test 29 | void testEncodeDecode() { 30 | Id brokerId = Id.random(); 31 | Id routeId = Id.random(); 32 | long timestamp = System.currentTimeMillis(); 33 | 34 | ByteBuf encoded = RouteRemoveFlyweight 35 | .encode(ByteBufAllocator.DEFAULT, brokerId, routeId, timestamp, 0); 36 | assertThat(FrameHeaderFlyweight.frameType(encoded)).isEqualTo(FrameType.ROUTE_REMOVE); 37 | assertThat(RouteRemoveFlyweight.brokerId(encoded)).isEqualTo(brokerId); 38 | assertThat(RouteRemoveFlyweight.routeId(encoded)).isEqualTo(routeId); 39 | assertThat(RouteRemoveFlyweight.timestamp(encoded)).isEqualTo(timestamp); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /.github/workflows/gradle-release.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Gradle 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 3 | 4 | name: Release Java CI with Gradle 5 | 6 | on: 7 | # Trigger the workflow on push 8 | push: 9 | # Sequence of patterns matched against refs/tags 10 | tags: 11 | - '*' # Push events to matching *, i.e. 1.0, 20.15.10 12 | 13 | jobs: 14 | build: 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | - name: Set up JDK 17 21 | uses: actions/setup-java@v3 22 | with: 23 | java-version: 17 24 | distribution: 'temurin' 25 | - name: Cache Gradle packages 26 | uses: actions/cache@v3 27 | with: 28 | path: ~/.gradle/caches 29 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} 30 | restore-keys: ${{ runner.os }}-gradle 31 | - name: Grant execute permission for gradlew 32 | run: chmod +x gradlew 33 | - name: Build with Gradle 34 | run: ./gradlew clean build -x test 35 | - name: Publish Packages to Sonotype 36 | run: ./gradlew -Pversion="${githubRef#refs/tags/}" -PbuildNumber="${buildNumber}" sign publishMavenPublicationToSonatypeRepository 37 | env: 38 | githubRef: ${{ github.ref }} 39 | buildNumber: ${{ github.run_number }} 40 | ORG_GRADLE_PROJECT_signingKey: ${{secrets.signingKey}} 41 | ORG_GRADLE_PROJECT_signingPassword: ${{secrets.signingPassword}} 42 | ORG_GRADLE_PROJECT_sonatypeUsername: ${{secrets.sonatypeUsername}} 43 | ORG_GRADLE_PROJECT_sonatypePassword: ${{secrets.sonatypePassword}} 44 | -------------------------------------------------------------------------------- /rsocket-broker-client-spring/src/test/java/io/rsocket/broker/client/spring/BrokerMetadataTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.client.spring; 18 | 19 | import io.rsocket.broker.common.Id; 20 | import org.junit.jupiter.api.Test; 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 | 27 | import static org.assertj.core.api.Assertions.assertThat; 28 | 29 | @SpringBootTest(properties = {"io.rsocket.broker.client.auto-connect=false"}) 30 | public class BrokerMetadataTests { 31 | 32 | @Autowired 33 | private BrokerMetadata brokerMetadata; 34 | 35 | @Test 36 | public void clientWorks() { 37 | assertThat(brokerMetadata.getProperties().getRouteId()).isEqualTo(Id.from("00000000-0000-0000-0000-000000000011")); 38 | } 39 | 40 | @SpringBootConfiguration 41 | @EnableAutoConfiguration 42 | protected static class TestConfig { 43 | 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /rsocket-broker-common-spring/src/main/java/io/rsocket/broker/common/spring/DefaultClientTransportFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.common.spring; 18 | 19 | import java.net.URI; 20 | 21 | import io.rsocket.transport.ClientTransport; 22 | import io.rsocket.transport.netty.client.TcpClientTransport; 23 | import io.rsocket.transport.netty.client.WebsocketClientTransport; 24 | 25 | public class DefaultClientTransportFactory implements ClientTransportFactory { 26 | 27 | @Override 28 | public boolean supports(URI uri) { 29 | return isWebsocket(uri) || isTcp(uri); 30 | } 31 | 32 | private boolean isTcp(URI uri) { 33 | return uri.getScheme().equalsIgnoreCase("tcp"); 34 | } 35 | 36 | private boolean isWebsocket(URI uri) { 37 | return uri.getScheme().equalsIgnoreCase("ws") || uri.getScheme().equalsIgnoreCase("wss"); 38 | } 39 | 40 | @Override 41 | public ClientTransport create(URI uri) { 42 | // order of precedence, websocket, tcp 43 | if (isWebsocket(uri)) { 44 | return WebsocketClientTransport.create(uri); 45 | } 46 | else if (isTcp(uri)) { 47 | return TcpClientTransport.create(uri.getHost(), uri.getPort()); 48 | } 49 | throw new IllegalArgumentException("No valid Transport configured " + uri); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /rsocket-broker-frames/src/test/java/io/rsocket/broker/frames/BrokerInfoFlyweightTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.frames; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.buffer.ByteBufAllocator; 21 | import io.rsocket.broker.common.Id; 22 | import io.rsocket.broker.common.Tags; 23 | import io.rsocket.broker.common.WellKnownKey; 24 | import org.junit.jupiter.api.Test; 25 | 26 | import static org.assertj.core.api.Assertions.assertThat; 27 | 28 | class BrokerInfoFlyweightTests { 29 | 30 | @Test 31 | void testEncodeDecode() { 32 | Id brokerId = Id.random(); 33 | long timestamp = System.currentTimeMillis(); 34 | Tags tags = Tags.builder().with(WellKnownKey.MAJOR_VERSION, "1") 35 | .with(WellKnownKey.MINOR_VERSION, "0") 36 | .with("mycustomtag", "mycustomtagvalue") 37 | .buildTags(); 38 | ByteBuf encoded = BrokerInfoFlyweight 39 | .encode(ByteBufAllocator.DEFAULT, brokerId, timestamp, tags, 0); 40 | assertThat(FrameHeaderFlyweight.frameType(encoded)).isEqualTo(FrameType.BROKER_INFO); 41 | assertThat(BrokerInfoFlyweight.brokerId(encoded)).isEqualTo(brokerId); 42 | assertThat(BrokerInfoFlyweight.timestamp(encoded)).isEqualTo(timestamp); 43 | assertThat(BrokerInfoFlyweight.tags(encoded)).isEqualTo(tags); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /rsocket-broker-client-spring/src/main/java/io/rsocket/broker/client/spring/BrokerClientRSocketStrategiesAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.client.spring; 18 | 19 | import io.rsocket.broker.common.spring.BrokerFrameDecoder; 20 | import io.rsocket.broker.common.spring.BrokerFrameEncoder; 21 | 22 | import org.springframework.boot.autoconfigure.AutoConfigureBefore; 23 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 24 | import org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration; 25 | import org.springframework.boot.rsocket.messaging.RSocketStrategiesCustomizer; 26 | import org.springframework.context.annotation.Bean; 27 | import org.springframework.context.annotation.Configuration; 28 | 29 | import static io.rsocket.broker.client.spring.BrokerClientProperties.CONFIG_PREFIX; 30 | 31 | @Configuration 32 | @ConditionalOnProperty(name = CONFIG_PREFIX + ".enabled", matchIfMissing = true) 33 | @AutoConfigureBefore(RSocketStrategiesAutoConfiguration.class) 34 | public class BrokerClientRSocketStrategiesAutoConfiguration { 35 | @Bean 36 | public RSocketStrategiesCustomizer clientRSocketStrategiesCustomizer() { 37 | return strategies -> strategies 38 | .decoder(new BrokerFrameDecoder()) 39 | .encoder(new BrokerFrameEncoder()); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /rsocket-broker-client-spring/src/main/java/io/rsocket/broker/client/spring/BrokerMetadata.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.client.spring; 18 | 19 | import java.util.function.Consumer; 20 | 21 | import io.rsocket.broker.common.WellKnownKey; 22 | import io.rsocket.broker.common.spring.MimeTypes; 23 | import io.rsocket.broker.frames.Address; 24 | 25 | import org.springframework.messaging.rsocket.RSocketRequester; 26 | 27 | public class BrokerMetadata { 28 | 29 | private final BrokerClientProperties properties; 30 | 31 | public BrokerMetadata(BrokerClientProperties properties) { 32 | this.properties = properties; 33 | } 34 | 35 | /* for testing */ BrokerClientProperties getProperties() { 36 | return this.properties; 37 | } 38 | 39 | public Consumer> address(String destServiceName) { 40 | return spec -> { 41 | Address address = Address.from(properties.getRouteId()) 42 | .with(WellKnownKey.SERVICE_NAME, destServiceName).build(); 43 | spec.metadata(address, MimeTypes.BROKER_FRAME_MIME_TYPE); 44 | }; 45 | } 46 | 47 | public Consumer> address( 48 | Consumer builderConsumer) { 49 | return spec -> { 50 | Address.Builder builder = Address.from(properties.getRouteId()); 51 | builderConsumer.accept(builder); 52 | spec.metadata(builder.build(), MimeTypes.BROKER_FRAME_MIME_TYPE); 53 | }; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /rsocket-broker-frames/src/test/java/io/rsocket/broker/frames/RouteJoinFlyweightTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.frames; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.buffer.ByteBufAllocator; 21 | import io.rsocket.broker.common.Id; 22 | import io.rsocket.broker.common.Tags; 23 | import io.rsocket.broker.common.WellKnownKey; 24 | import org.junit.jupiter.api.Test; 25 | 26 | import static org.assertj.core.api.Assertions.assertThat; 27 | 28 | class RouteJoinFlyweightTests { 29 | 30 | @Test 31 | void testEncodeDecode() { 32 | Id brokerId = Id.random(); 33 | Id routeId = Id.random(); 34 | long timestamp = System.currentTimeMillis(); 35 | String serviceName = "myService"; 36 | Tags tags = Tags.builder().with(WellKnownKey.MAJOR_VERSION, "1") 37 | .with(WellKnownKey.MINOR_VERSION, "0") 38 | .with("mycustomtag", "mycustomtagvalue") 39 | .buildTags(); 40 | ByteBuf encoded = RouteJoinFlyweight 41 | .encode(ByteBufAllocator.DEFAULT, brokerId, routeId, timestamp, serviceName, tags, 0); 42 | assertThat(FrameHeaderFlyweight.frameType(encoded)).isEqualTo(FrameType.ROUTE_JOIN); 43 | assertThat(RouteJoinFlyweight.brokerId(encoded)).isEqualTo(brokerId); 44 | assertThat(RouteJoinFlyweight.routeId(encoded)).isEqualTo(routeId); 45 | assertThat(RouteJoinFlyweight.timestamp(encoded)).isEqualTo(timestamp); 46 | assertThat(RouteJoinFlyweight.serviceName(encoded)).isEqualTo(serviceName); 47 | assertThat(RouteJoinFlyweight.tags(encoded)).isEqualTo(tags); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /rsocket-broker-frames/src/main/java/io/rsocket/broker/frames/RouteRemoveFlyweight.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.frames; 18 | 19 | import java.util.Objects; 20 | 21 | import io.netty.buffer.ByteBuf; 22 | import io.netty.buffer.ByteBufAllocator; 23 | import io.rsocket.broker.common.Id; 24 | 25 | import static io.rsocket.broker.frames.FlyweightUtils.decodeId; 26 | import static io.rsocket.broker.frames.FlyweightUtils.encodeId; 27 | 28 | /** 29 | * https://github.com/rsocket-broker/rsocket-broker-spec/blob/master/RSocketBrokerSpecification.md#route_remove 30 | */ 31 | public class RouteRemoveFlyweight { 32 | 33 | public static ByteBuf encode(ByteBufAllocator allocator, Id brokerId, Id routeId, long timestamp, int flags) { 34 | Objects.requireNonNull(brokerId, "brokerId may not be null"); 35 | Objects.requireNonNull(routeId, "routeId may not be null"); 36 | 37 | ByteBuf byteBuf = FrameHeaderFlyweight.encode(allocator, FrameType.ROUTE_REMOVE, flags); 38 | encodeId(byteBuf, brokerId); 39 | encodeId(byteBuf, routeId); 40 | byteBuf.writeLong(timestamp); 41 | 42 | return byteBuf; 43 | } 44 | 45 | public static Id brokerId(ByteBuf byteBuf) { 46 | return decodeId(byteBuf, FrameHeaderFlyweight.BYTES); 47 | } 48 | 49 | public static Id routeId(ByteBuf byteBuf) { 50 | int offset = FrameHeaderFlyweight.BYTES + FlyweightUtils.ID_BYTES; 51 | return decodeId(byteBuf, offset); 52 | } 53 | 54 | public static long timestamp(ByteBuf byteBuf) { 55 | int offset = FrameHeaderFlyweight.BYTES + FlyweightUtils.ID_BYTES + FlyweightUtils.ID_BYTES; 56 | return byteBuf.getLong(offset); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /rsocket-broker-frames/src/test/java/io/rsocket/broker/frames/RouteSetupFlyweightTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.frames; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.buffer.ByteBufAllocator; 21 | import io.rsocket.broker.common.Id; 22 | import io.rsocket.broker.common.Tags; 23 | import io.rsocket.broker.common.WellKnownKey; 24 | import org.junit.jupiter.api.Test; 25 | 26 | import static org.assertj.core.api.Assertions.assertThat; 27 | 28 | class RouteSetupFlyweightTests { 29 | 30 | @Test 31 | void testEncodeDecode() { 32 | Id routeId = Id.random(); 33 | assertEncodeDecode(routeId); 34 | } 35 | 36 | @Test 37 | void testEncodeDecodeMaxId() { 38 | Id routeId = new Id(Long.MAX_VALUE, Long.MAX_VALUE); 39 | assertEncodeDecode(routeId); 40 | } 41 | 42 | @Test 43 | void testEncodeDecodeMinId() { 44 | Id routeId = new Id(0, 0); 45 | assertEncodeDecode(routeId); 46 | } 47 | 48 | private void assertEncodeDecode(Id routeId) { 49 | String serviceName = "myService"; 50 | Tags tags = Tags.builder().with(WellKnownKey.MAJOR_VERSION, "1") 51 | .with(WellKnownKey.MINOR_VERSION, "0") 52 | .with("mycustomtag", "mycustomtagvalue") 53 | .buildTags(); 54 | ByteBuf encoded = RouteSetupFlyweight 55 | .encode(ByteBufAllocator.DEFAULT, routeId, serviceName, tags, 0); 56 | assertThat(FrameHeaderFlyweight.frameType(encoded)).isEqualTo(FrameType.ROUTE_SETUP); 57 | assertThat(RouteSetupFlyweight.routeId(encoded)).isEqualTo(routeId); 58 | assertThat(RouteSetupFlyweight.serviceName(encoded)).isEqualTo(serviceName); 59 | assertThat(RouteSetupFlyweight.tags(encoded)).isEqualTo(tags); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /rsocket-broker-common/src/main/java/io/rsocket/broker/common/ImmutableKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.common; 18 | 19 | import java.util.Objects; 20 | import java.util.StringJoiner; 21 | 22 | public class ImmutableKey implements Key { 23 | 24 | private final WellKnownKey wellKnownKey; 25 | 26 | private final String key; 27 | 28 | public ImmutableKey(WellKnownKey wellKnownKey) { 29 | this(wellKnownKey, null); 30 | } 31 | 32 | public ImmutableKey(String key) { 33 | this(null, key); 34 | } 35 | 36 | private ImmutableKey(WellKnownKey wellKnownKey, String key) { 37 | this.wellKnownKey = wellKnownKey; 38 | this.key = key; 39 | } 40 | 41 | public WellKnownKey getWellKnownKey() { 42 | return this.wellKnownKey; 43 | } 44 | 45 | public String getKey() { 46 | return this.key; 47 | } 48 | 49 | @Override 50 | public boolean equals(Object o) { 51 | if (this == o) { 52 | return true; 53 | } 54 | if (o == null || getClass() != o.getClass()) { 55 | return false; 56 | } 57 | Key key1 = (Key) o; 58 | return this.wellKnownKey == key1.getWellKnownKey() 59 | && Objects.equals(this.key, key1.getKey()); 60 | } 61 | 62 | @Override 63 | public int hashCode() { 64 | return Objects.hash(this.wellKnownKey, this.key); 65 | } 66 | 67 | @Override 68 | public String toString() { 69 | StringJoiner joiner = new StringJoiner(", ", "[", "]"); 70 | if (wellKnownKey != null) { 71 | joiner.add(wellKnownKey.toString()); 72 | joiner.add(String.format("0x%02x", wellKnownKey.getIdentifier())); 73 | } 74 | if (key != null) { 75 | joiner.add("'" + key + "'"); 76 | } 77 | return joiner.toString(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /rsocket-broker-frames/src/main/java/io/rsocket/broker/frames/BrokerInfoFlyweight.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.frames; 18 | 19 | import java.util.Objects; 20 | 21 | import io.netty.buffer.ByteBuf; 22 | import io.netty.buffer.ByteBufAllocator; 23 | import io.rsocket.broker.common.Id; 24 | import io.rsocket.broker.common.Tags; 25 | 26 | import static io.rsocket.broker.frames.FlyweightUtils.decodeId; 27 | import static io.rsocket.broker.frames.FlyweightUtils.encodeId; 28 | 29 | /** 30 | * https://github.com/rsocket-broker/rsocket-broker-spec/blob/master/RSocketBrokerSpecification.md#broker_info 31 | */ 32 | public class BrokerInfoFlyweight { 33 | 34 | public static ByteBuf encode(ByteBufAllocator allocator, Id brokerId, long timestamp, Tags tags, int flags) { 35 | Objects.requireNonNull(brokerId, "brokerId may not be null"); 36 | Objects.requireNonNull(tags, "tags may not be null"); 37 | 38 | ByteBuf byteBuf = FrameHeaderFlyweight.encode(allocator, FrameType.BROKER_INFO, flags); 39 | encodeId(byteBuf, brokerId); 40 | 41 | byteBuf.writeLong(timestamp); 42 | 43 | TagsFlyweight.encode(byteBuf, tags); 44 | 45 | return byteBuf; 46 | } 47 | 48 | public static Id brokerId(ByteBuf byteBuf) { 49 | return decodeId(byteBuf, FrameHeaderFlyweight.BYTES); 50 | } 51 | 52 | public static long timestamp(ByteBuf byteBuf) { 53 | int offset = FrameHeaderFlyweight.BYTES + FlyweightUtils.ID_BYTES; 54 | return byteBuf.getLong(offset); 55 | } 56 | 57 | public static Tags tags(ByteBuf byteBuf) { 58 | int offset = FrameHeaderFlyweight.BYTES + FlyweightUtils.ID_BYTES + Long.BYTES; 59 | return TagsFlyweight.decode(offset, byteBuf); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /rsocket-broker-frames/src/main/java/io/rsocket/broker/frames/FrameType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.frames; 18 | 19 | /** 20 | * https://github.com/rsocket-broker/rsocket-broker-spec/blob/master/RSocketBrokerSpecification.md#frame-types 21 | */ 22 | public enum FrameType { 23 | /** 24 | * RESERVED 25 | */ 26 | RESERVED(0x00), 27 | 28 | /** 29 | * Information a routable destination sends to a broker 30 | */ 31 | ROUTE_SETUP(0x01), 32 | 33 | /** 34 | * Information passed between brokers when a routable destination connects. This 35 | * information may not arrive from the broker where the routable destination 36 | * connected, so the information could be forwarded when it connects. 37 | */ 38 | ROUTE_JOIN(0x02), 39 | 40 | /** 41 | * Information passed between brokers to indicate a routable destination is no 42 | * longer available. 43 | */ 44 | ROUTE_REMOVE(0x03), 45 | 46 | /** 47 | * Information a broker passes to another broker . 48 | */ 49 | BROKER_INFO(0x04), 50 | 51 | /** 52 | * A frame that contain information forwarding a message from an origin to a 53 | * destination. This frame is intended for the metadata field. 54 | */ 55 | ADDRESS(0x05); 56 | 57 | private static FrameType[] frameTypesById; 58 | 59 | static { 60 | frameTypesById = new FrameType[values().length]; 61 | 62 | for (FrameType frameType : values()) { 63 | frameTypesById[frameType.id] = frameType; 64 | } 65 | } 66 | 67 | private final int id; 68 | 69 | FrameType(int id) { 70 | this.id = id; 71 | } 72 | 73 | public int getId() { 74 | return this.id; 75 | } 76 | 77 | public static FrameType from(int id) { 78 | return frameTypesById[id]; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /rsocket-broker-common/src/main/java/io/rsocket/broker/common/Id.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.common; 18 | 19 | import java.util.Objects; 20 | import java.util.UUID; 21 | 22 | public class Id { 23 | private final long first; 24 | private final long second; 25 | 26 | public Id(long first, long second) { 27 | this.first = first; 28 | this.second = second; 29 | } 30 | 31 | public long getFirst() { 32 | return this.first; 33 | } 34 | 35 | public long getSecond() { 36 | return this.second; 37 | } 38 | 39 | @Override 40 | public String toString() { 41 | return "Id{" + new UUID(first, second) + '}'; 42 | } 43 | 44 | @Override 45 | public boolean equals(Object o) { 46 | if (this == o) return true; 47 | if (o == null || getClass() != o.getClass()) return false; 48 | Id id = (Id) o; 49 | return this.first == id.first && 50 | this.second == id.second; 51 | } 52 | 53 | @Override 54 | public int hashCode() { 55 | return Objects.hash(this.first, this.second); 56 | } 57 | 58 | public static Id from(long[] parts) { 59 | if (parts == null || parts.length != 2) { 60 | throw new IllegalArgumentException("parts must have a length of 2"); 61 | } 62 | return new Id(parts[0], parts[1]); 63 | } 64 | 65 | // userful convention for serialization 66 | public static Id valueOf(String uuid) { 67 | return from(uuid); 68 | } 69 | 70 | public static Id from(String uuid) { 71 | return from(UUID.fromString(uuid)); 72 | } 73 | 74 | public static Id from(UUID uuid) { 75 | Objects.requireNonNull(uuid, "uuid may not be null"); 76 | return new Id(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits()); 77 | } 78 | 79 | public static Id random() { 80 | return from(UUID.randomUUID()); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /gradle/publications.gradle: -------------------------------------------------------------------------------- 1 | apply from: "${rootDir}/gradle/github-pkg.gradle" 2 | apply from: "${rootDir}/gradle/sonotype.gradle" 3 | 4 | subprojects { 5 | plugins.withType(MavenPublishPlugin) { 6 | publishing { 7 | publications { 8 | maven(MavenPublication) { 9 | pom { 10 | name = project.name 11 | afterEvaluate { 12 | description = project.description 13 | } 14 | groupId = 'io.rsocket.broker' 15 | url = 'https://github.com/rsocket-broker' 16 | licenses { 17 | license { 18 | name = "The Apache Software License, Version 2.0" 19 | url = "https://www.apache.org/licenses/LICENSE-2.0.txt" 20 | distribution = "repo" 21 | } 22 | } 23 | developers { 24 | developer { 25 | id = 'spencergibb' 26 | name = 'Spencer Gibb' 27 | email = 'sgibb@vmware.com' 28 | } 29 | developer { 30 | id = 'OlegDokuka' 31 | name = 'Oleh Dokuka' 32 | email = 'oleh.dokuka@icloud.com' 33 | } 34 | } 35 | scm { 36 | connection = 'scm:git:https://github.com/rsocket-broker/rsocket-broker-client.git' 37 | developerConnection = 'scm:git:https://github.com/rsocket-broker/rsocket-broker-client.git' 38 | url = 'https://github.com/rsocket-broker/rsocket-broker-client' 39 | } 40 | versionMapping { 41 | usage('java-api') { 42 | fromResolutionResult('runtimeClasspath') 43 | } 44 | usage('java-runtime') { 45 | fromResolutionResult() 46 | } 47 | } 48 | } 49 | } 50 | } 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /rsocket-broker-frames/src/test/java/io/rsocket/broker/frames/AddressFlyweightTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.frames; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.buffer.ByteBufAllocator; 21 | import io.rsocket.broker.common.Id; 22 | import io.rsocket.broker.common.Tags; 23 | import io.rsocket.broker.common.WellKnownKey; 24 | import org.junit.jupiter.api.Test; 25 | 26 | import static org.assertj.core.api.Assertions.assertThat; 27 | 28 | class AddressFlyweightTests { 29 | 30 | @Test 31 | void testEncodeDecode() { 32 | Tags metadata = Tags.builder().with("mycustommetadata", "mycustommetadatavalue") 33 | .buildTags(); 34 | Tags tags = Tags.builder().with(WellKnownKey.MAJOR_VERSION, "1") 35 | .with(WellKnownKey.MINOR_VERSION, "0") 36 | .with("mycustomtag", "mycustomtagvalue") 37 | .buildTags(); 38 | assertAddress(metadata, tags, 0b01_0100_0000); 39 | } 40 | 41 | @Test 42 | void testEncodeDecodeEmptyMetadata() { 43 | Tags metadata = Tags.empty(); 44 | Tags tags = Tags.builder().with(WellKnownKey.MAJOR_VERSION, "1") 45 | .with(WellKnownKey.MINOR_VERSION, "0") 46 | .with("mycustomtag", "mycustomtagvalue") 47 | .buildTags(); 48 | assertAddress(metadata, tags, 0b00_1000_0000); 49 | } 50 | 51 | private void assertAddress(Tags metadata, Tags tags, int flags) { 52 | Id originRouteId = Id.random(); 53 | 54 | ByteBuf encoded = AddressFlyweight 55 | .encode(ByteBufAllocator.DEFAULT, originRouteId, metadata, tags, flags); 56 | assertThat(FrameHeaderFlyweight.flags(encoded)).isEqualTo(flags); 57 | assertThat(FrameHeaderFlyweight.frameType(encoded)).isEqualTo(FrameType.ADDRESS); 58 | assertThat(AddressFlyweight.originRouteId(encoded)).isEqualTo(originRouteId); 59 | // FIXME: assertThat(AddressFlyweight.metadata(encoded)).isEqualTo(metadata); 60 | assertThat(AddressFlyweight.metadata(encoded)).isEqualTo(Tags.empty()); 61 | assertThat(AddressFlyweight.tags(encoded)).isEqualTo(tags); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /rsocket-broker-frames/src/main/java/io/rsocket/broker/frames/RouteSetupFlyweight.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.frames; 18 | 19 | import java.util.Objects; 20 | 21 | import io.netty.buffer.ByteBuf; 22 | import io.netty.buffer.ByteBufAllocator; 23 | import io.rsocket.broker.common.Id; 24 | import io.rsocket.broker.common.Tags; 25 | 26 | import static io.rsocket.broker.frames.FlyweightUtils.decodeByteStringLength; 27 | import static io.rsocket.broker.frames.FlyweightUtils.decodeId; 28 | import static io.rsocket.broker.frames.FlyweightUtils.encodeByteString; 29 | import static io.rsocket.broker.frames.FlyweightUtils.encodeId; 30 | 31 | /** 32 | * https://github.com/rsocket-broker/rsocket-broker-spec/blob/master/RSocketBrokerSpecification.md#route_setup 33 | */ 34 | public class RouteSetupFlyweight { 35 | 36 | public static ByteBuf encode(ByteBufAllocator allocator, Id routeId, String serviceName, Tags tags, int flags) { 37 | Objects.requireNonNull(routeId, "routeId may not be null"); 38 | Objects.requireNonNull(serviceName, "serviceName may not be null"); 39 | Objects.requireNonNull(tags, "tags may not be null"); 40 | 41 | ByteBuf byteBuf = FrameHeaderFlyweight.encode(allocator, FrameType.ROUTE_SETUP, flags); 42 | encodeId(byteBuf, routeId); 43 | 44 | encodeByteString(byteBuf, serviceName); 45 | 46 | TagsFlyweight.encode(byteBuf, tags); 47 | 48 | return byteBuf; 49 | } 50 | 51 | public static Id routeId(ByteBuf byteBuf) { 52 | return decodeId(byteBuf, FrameHeaderFlyweight.BYTES); 53 | } 54 | 55 | public static String serviceName(ByteBuf byteBuf) { 56 | int offset = FrameHeaderFlyweight.BYTES + FlyweightUtils.ID_BYTES; 57 | return FlyweightUtils.decodeByteString(byteBuf, offset); 58 | } 59 | 60 | public static Tags tags(ByteBuf byteBuf) { 61 | int offset = FrameHeaderFlyweight.BYTES + FlyweightUtils.ID_BYTES; 62 | // serviceName length 63 | offset += decodeByteStringLength(byteBuf, offset); 64 | return TagsFlyweight.decode(offset, byteBuf); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /rsocket-broker-client-spring/src/test/java/io/rsocket/broker/client/spring/BrokerClientPropertiesTests.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 io.rsocket.broker.client.spring; 18 | 19 | import java.net.URI; 20 | import java.util.Map; 21 | 22 | import io.rsocket.broker.common.Id; 23 | import io.rsocket.broker.common.MutableKey; 24 | import org.junit.jupiter.api.Test; 25 | 26 | import org.springframework.beans.factory.annotation.Autowired; 27 | import org.springframework.boot.SpringBootConfiguration; 28 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 29 | import org.springframework.boot.test.context.SpringBootTest; 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 | @SpringBootTest(webEnvironment = RANDOM_PORT, 36 | properties = "io.rsocket.broker.client.auto-connect=false") 37 | public class BrokerClientPropertiesTests { 38 | 39 | @Autowired 40 | BrokerClientProperties properties; 41 | 42 | @Test 43 | public void clientProperties() { 44 | assertThat(properties).isNotNull(); 45 | assertThat(properties.getRouteId()).isEqualTo(Id.from("00000000-0000-0000-0000-000000000011")); 46 | assertThat(properties.getServiceName()).isEqualTo("test_requester"); 47 | assertThat(properties.getTags()).containsEntry(new MutableKey("INSTANCE_NAME"), 48 | "test_requester1"); 49 | assertThat(properties.getAddress()).containsKeys("test_responder-rc", 50 | "key.with.dots", "key.with.{replacement}"); 51 | Map map = properties.getAddress().get("test_responder-rc"); 52 | assertThat(map).contains(entry(new MutableKey("SERVICE_NAME"), "test_responder"), 53 | entry(new MutableKey("custom-tag"), "custom-value")); 54 | assertThat(properties.getBrokers()).hasSize(1); 55 | URI broker = properties.getBrokers().get(0); 56 | assertThat(broker).isNotNull().hasScheme("tcp").hasHost("localhost").hasPort(7002); 57 | } 58 | 59 | @SpringBootConfiguration 60 | @EnableAutoConfiguration 61 | static class Config { 62 | 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /rsocket-broker-common/src/main/java/io/rsocket/broker/common/MutableKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.common; 18 | 19 | import java.util.Objects; 20 | import java.util.StringJoiner; 21 | 22 | public class MutableKey implements Key { 23 | 24 | private WellKnownKey wellKnownKey; 25 | 26 | private String key; 27 | 28 | public MutableKey() { 29 | System.out.println("here"); 30 | } 31 | 32 | public MutableKey(String text) { 33 | if (text != null && !text.isEmpty()) { 34 | try { 35 | wellKnownKey = WellKnownKey.valueOf(text.toUpperCase()); 36 | } 37 | catch (IllegalArgumentException e) { 38 | // NOT a valid well know key 39 | key = text; 40 | } 41 | } 42 | } 43 | 44 | public static MutableKey of(WellKnownKey key) { 45 | MutableKey mutableKey = new MutableKey(); 46 | mutableKey.setWellKnownKey(key); 47 | return mutableKey; 48 | } 49 | 50 | public static MutableKey of(String key) { 51 | return new MutableKey(key); 52 | } 53 | 54 | public WellKnownKey getWellKnownKey() { 55 | return wellKnownKey; 56 | } 57 | 58 | public void setWellKnownKey(WellKnownKey wellKnownKey) { 59 | this.wellKnownKey = wellKnownKey; 60 | } 61 | 62 | public String getKey() { 63 | return key; 64 | } 65 | 66 | public void setKey(String key) { 67 | this.key = key; 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 | MutableKey tag = (MutableKey) o; 79 | return wellKnownKey == tag.wellKnownKey 80 | && Objects.equals(key, tag.key); 81 | } 82 | 83 | @Override 84 | public int hashCode() { 85 | return Objects.hash(wellKnownKey, key); 86 | } 87 | 88 | @Override 89 | public String toString() { 90 | StringJoiner joiner = new StringJoiner(", ", "[", "]"); 91 | if (wellKnownKey != null) { 92 | joiner.add(wellKnownKey.name()); 93 | } 94 | if (key != null) { 95 | joiner.add("'" + key + "'"); 96 | } 97 | return joiner.toString(); 98 | } 99 | 100 | } -------------------------------------------------------------------------------- /rsocket-broker-frames/src/main/java/io/rsocket/broker/frames/FlyweightUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.frames; 18 | 19 | import java.nio.charset.StandardCharsets; 20 | 21 | import io.netty.buffer.ByteBuf; 22 | import io.netty.buffer.ByteBufUtil; 23 | import io.rsocket.broker.common.Id; 24 | 25 | public class FlyweightUtils { 26 | 27 | public static final int ID_BYTES = 16; 28 | private static final int UNSIGNED_BYTE_SIZE = 8; 29 | private static final int UNSIGNED_BYTE_MAX_VALUE = (1 << UNSIGNED_BYTE_SIZE) - 1; 30 | 31 | static void encodeByteString(ByteBuf byteBuf, String s) { 32 | int length = requireUnsignedByte(ByteBufUtil.utf8Bytes(s)); 33 | byteBuf.writeByte(length); 34 | ByteBufUtil.reserveAndWriteUtf8(byteBuf, s, length); 35 | } 36 | 37 | static String decodeByteString(ByteBuf byteBuf, int offset) { 38 | int length = byteBuf.getByte(offset); 39 | length &= UNSIGNED_BYTE_MAX_VALUE; 40 | offset += Byte.BYTES; 41 | 42 | return byteBuf.toString(offset, length, StandardCharsets.UTF_8); 43 | } 44 | 45 | static int decodeByteStringLength(ByteBuf byteBuf, int offset) { 46 | int length = byteBuf.getByte(offset); 47 | length &= UNSIGNED_BYTE_MAX_VALUE; 48 | return Byte.BYTES + length; 49 | } 50 | 51 | /** 52 | * Requires that an {@code int} can be represented as an unsigned {@code byte}. 53 | * 54 | * @param i the {@code int} to test 55 | * @return the {@code int} if it can be represented as an unsigned {@code byte} 56 | * @throws IllegalArgumentException if {@code i} cannot be represented as an unsigned {@code byte} 57 | */ 58 | static int requireUnsignedByte(int i) { 59 | if (i > UNSIGNED_BYTE_MAX_VALUE) { 60 | throw new IllegalArgumentException( 61 | String.format("%d is larger than %d bits", i, UNSIGNED_BYTE_SIZE)); 62 | } 63 | 64 | return i; 65 | } 66 | 67 | static void encodeId(ByteBuf byteBuf, Id id) { 68 | byteBuf.writeLong(id.getFirst()); 69 | byteBuf.writeLong(id.getSecond()); 70 | } 71 | 72 | static Id decodeId(ByteBuf byteBuf, int offset) { 73 | long first = byteBuf.getLong(offset); 74 | long second = byteBuf.getLong(offset + Long.BYTES); 75 | return new Id(first, second); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /rsocket-broker-client/src/test/java/io/rsocket/broker/config/ArchaiusConfigFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.config; 18 | 19 | import com.netflix.archaius.ConfigProxyFactory; 20 | import com.netflix.archaius.DefaultConfigLoader; 21 | import com.netflix.archaius.DefaultPropertyFactory; 22 | import com.netflix.archaius.Layers; 23 | import com.netflix.archaius.api.config.CompositeConfig; 24 | import com.netflix.archaius.api.config.LayeredConfig; 25 | import com.netflix.archaius.api.exceptions.ConfigException; 26 | import com.netflix.archaius.config.DefaultCompositeConfig; 27 | import com.netflix.archaius.config.DefaultLayeredConfig; 28 | import com.netflix.archaius.config.DefaultSettableConfig; 29 | import com.netflix.archaius.config.EnvironmentConfig; 30 | import com.netflix.archaius.config.SystemConfig; 31 | import reactor.core.Exceptions; 32 | 33 | public class ArchaiusConfigFactory { 34 | 35 | public static ArchaiusBrokerClientProperties load() { 36 | return load("broker"); 37 | } 38 | 39 | public static ArchaiusBrokerClientProperties load(String configname) { 40 | try { 41 | LayeredConfig layeredConfig = new DefaultLayeredConfig("layered-broker-broker"); 42 | layeredConfig.addConfig(Layers.ENVIRONMENT, EnvironmentConfig.INSTANCE); 43 | layeredConfig.addConfig(Layers.SYSTEM, SystemConfig.INSTANCE); 44 | 45 | 46 | DefaultConfigLoader loader = DefaultConfigLoader.builder() 47 | //.withStrLookup(config) 48 | //.withDefaultCascadingStrategy(ConcatCascadeStrategy.from("${env}")) 49 | .build(); 50 | 51 | CompositeConfig application = new DefaultCompositeConfig(); 52 | application.replaceConfig(configname, loader.newLoader().load(configname)); 53 | 54 | layeredConfig.addConfig(Layers.APPLICATION, application); 55 | 56 | DefaultSettableConfig defaults = new DefaultSettableConfig(); 57 | // TODO: set defaults 58 | layeredConfig.addConfig(Layers.DEFAULT, defaults); 59 | 60 | DefaultPropertyFactory propertyFactory = new DefaultPropertyFactory(layeredConfig); 61 | ConfigProxyFactory factory = new ConfigProxyFactory(layeredConfig, 62 | layeredConfig.getDecoder(), propertyFactory); 63 | return factory.newProxy(ArchaiusBrokerClientProperties.class, ArchaiusBrokerClientProperties.CONFIG_PREFIX); 64 | } 65 | catch (ConfigException e) { 66 | throw Exceptions.bubble(e); 67 | } 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /rsocket-broker-frames/src/main/java/io/rsocket/broker/frames/RouteSetup.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.frames; 18 | 19 | import java.util.Objects; 20 | import java.util.StringJoiner; 21 | 22 | import io.netty.buffer.ByteBuf; 23 | import io.rsocket.broker.common.Id; 24 | import io.rsocket.broker.common.Tags; 25 | 26 | import static io.rsocket.broker.frames.RouteSetupFlyweight.routeId; 27 | import static io.rsocket.broker.frames.RouteSetupFlyweight.serviceName; 28 | import static io.rsocket.broker.frames.RouteSetupFlyweight.tags; 29 | 30 | /** 31 | * Representation of decoded RouteSetup information. 32 | */ 33 | public final class RouteSetup extends BrokerFrame { 34 | 35 | private final Id routeId; 36 | 37 | private final String serviceName; 38 | private final Tags tags; 39 | 40 | private RouteSetup(Id routeId, String serviceName, Tags tags) { 41 | super(FrameType.ROUTE_SETUP, 0); 42 | this.routeId = routeId; 43 | this.serviceName = serviceName; 44 | this.tags = tags; 45 | } 46 | 47 | public Id getRouteId() { 48 | return this.routeId; 49 | } 50 | 51 | public String getServiceName() { 52 | return this.serviceName; 53 | } 54 | 55 | public Tags getTags() { 56 | return this.tags; 57 | } 58 | 59 | @Override 60 | public String toString() { 61 | return new StringJoiner(", ", RouteSetup.class.getSimpleName() + "[", "]") 62 | .add("routeId=" + routeId) 63 | .add("serviceName='" + serviceName + "'") 64 | .add("tags=" + tags) 65 | .toString(); 66 | } 67 | 68 | public static Builder from(Id id, String serviceName) { 69 | return new Builder(id, serviceName); 70 | } 71 | 72 | public static RouteSetup from(ByteBuf byteBuf) { 73 | return from(routeId(byteBuf), serviceName(byteBuf)) 74 | .with(tags(byteBuf)).build(); 75 | } 76 | 77 | public static final class Builder extends Tags.Builder { 78 | 79 | private final Id id; 80 | 81 | private final String serviceName; 82 | 83 | private Builder(Id id, String serviceName) { 84 | Objects.requireNonNull(id, "id may not be null"); 85 | Objects.requireNonNull(serviceName, "serviceName may not be null"); 86 | if (serviceName.isEmpty()) { 87 | throw new IllegalArgumentException("serviceName may not be empty"); 88 | } 89 | this.id = id; 90 | this.serviceName = serviceName; 91 | } 92 | 93 | public RouteSetup build() { 94 | return new RouteSetup(id, serviceName, buildTags()); 95 | } 96 | 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /rsocket-broker-frames/src/main/java/io/rsocket/broker/frames/AddressFlyweight.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.frames; 18 | 19 | import java.util.Objects; 20 | 21 | import io.netty.buffer.ByteBuf; 22 | import io.netty.buffer.ByteBufAllocator; 23 | import io.rsocket.broker.common.Id; 24 | import io.rsocket.broker.common.Tags; 25 | 26 | import static io.rsocket.broker.frames.FlyweightUtils.decodeId; 27 | import static io.rsocket.broker.frames.FlyweightUtils.encodeId; 28 | 29 | /** 30 | * https://github.com/rsocket-broker/rsocket-broker-spec/blob/master/RSocketBrokerSpecification.md#address 31 | */ 32 | public class AddressFlyweight { 33 | 34 | /** (I)gnore flag: a value of 0 indicates the protocol can't ignore this frame */ 35 | public static final int FLAGS_I = 0b10_0000_0000; 36 | /** (E)ncrypted flag: a value of 1 indicates the payload is encrypted */ 37 | public static final int FLAGS_E = 0b01_0000_0000; 38 | /** (U)unicast flag: a value of 1 indicates unicast broker */ 39 | public static final int FLAGS_U = 0b00_1000_0000; 40 | /** (M)ulticast flag: a value of 1 indicates multicast broker */ 41 | public static final int FLAGS_M = 0b00_0100_0000; 42 | /** (S)hard flag: a value of 1 indicates shard broker */ 43 | public static final int FLAGS_S = 0b00_0010_0000; 44 | 45 | static final int ROUTING_TYPE_MASK = 0b11_0001_1111; 46 | 47 | public static ByteBuf encode(ByteBufAllocator allocator, Id originRouteId, Tags metadata, Tags tags, int flags) { 48 | Objects.requireNonNull(originRouteId, "originRouteId may not be null"); 49 | Objects.requireNonNull(tags, "tags may not be null"); 50 | 51 | ByteBuf byteBuf = FrameHeaderFlyweight.encode(allocator, FrameType.ADDRESS, flags); 52 | encodeId(byteBuf, originRouteId); 53 | 54 | //FIXME: how to deal with empty metadata? 55 | //TagsFlyweight.encode(byteBuf, metadata); 56 | 57 | TagsFlyweight.encode(byteBuf, tags); 58 | 59 | return byteBuf; 60 | } 61 | 62 | public static Id originRouteId(ByteBuf byteBuf) { 63 | return decodeId(byteBuf, FrameHeaderFlyweight.BYTES); 64 | } 65 | 66 | public static Tags metadata(ByteBuf byteBuf) { 67 | //int offset = FrameHeaderFlyweight.BYTES + FlyweightUtils.ID_BYTES; 68 | //return TagsFlyweight.decode(offset, byteBuf); 69 | return Tags.empty(); 70 | } 71 | 72 | public static Tags tags(ByteBuf byteBuf) { 73 | int offset = FrameHeaderFlyweight.BYTES + FlyweightUtils.ID_BYTES; 74 | // metadata length 75 | //offset += TagsFlyweight.length(offset, byteBuf); 76 | return TagsFlyweight.decode(offset, byteBuf); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /rsocket-broker-frames/src/main/java/io/rsocket/broker/frames/RouteJoinFlyweight.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.frames; 18 | 19 | import java.util.Objects; 20 | 21 | import io.netty.buffer.ByteBuf; 22 | import io.netty.buffer.ByteBufAllocator; 23 | import io.rsocket.broker.common.Id; 24 | import io.rsocket.broker.common.Tags; 25 | 26 | import static io.rsocket.broker.frames.FlyweightUtils.decodeByteStringLength; 27 | import static io.rsocket.broker.frames.FlyweightUtils.decodeId; 28 | import static io.rsocket.broker.frames.FlyweightUtils.encodeByteString; 29 | import static io.rsocket.broker.frames.FlyweightUtils.encodeId; 30 | 31 | /** 32 | * https://github.com/rsocket-broker/rsocket-broker-spec/blob/master/RSocketBrokerSpecification.md#route_join 33 | */ 34 | public class RouteJoinFlyweight { 35 | 36 | public static ByteBuf encode(ByteBufAllocator allocator, Id brokerId, Id routeId, long timestamp, String serviceName, Tags tags, int flags) { 37 | Objects.requireNonNull(brokerId, "brokerId may not be null"); 38 | Objects.requireNonNull(routeId, "routeId may not be null"); 39 | Objects.requireNonNull(serviceName, "serviceName may not be null"); 40 | Objects.requireNonNull(tags, "tags may not be null"); 41 | 42 | ByteBuf byteBuf = FrameHeaderFlyweight.encode(allocator, FrameType.ROUTE_JOIN, flags); 43 | encodeId(byteBuf, brokerId); 44 | encodeId(byteBuf, routeId); 45 | byteBuf.writeLong(timestamp); 46 | 47 | encodeByteString(byteBuf, serviceName); 48 | 49 | TagsFlyweight.encode(byteBuf, tags); 50 | 51 | return byteBuf; 52 | } 53 | 54 | public static Id brokerId(ByteBuf byteBuf) { 55 | return decodeId(byteBuf, FrameHeaderFlyweight.BYTES); 56 | } 57 | 58 | public static Id routeId(ByteBuf byteBuf) { 59 | int offset = FrameHeaderFlyweight.BYTES + FlyweightUtils.ID_BYTES; 60 | return decodeId(byteBuf, offset); 61 | } 62 | 63 | public static long timestamp(ByteBuf byteBuf) { 64 | int offset = FrameHeaderFlyweight.BYTES + FlyweightUtils.ID_BYTES + FlyweightUtils.ID_BYTES; 65 | return byteBuf.getLong(offset); 66 | } 67 | 68 | public static String serviceName(ByteBuf byteBuf) { 69 | int offset = FrameHeaderFlyweight.BYTES + FlyweightUtils.ID_BYTES + FlyweightUtils.ID_BYTES + Long.BYTES; 70 | return FlyweightUtils.decodeByteString(byteBuf, offset); 71 | } 72 | 73 | public static Tags tags(ByteBuf byteBuf) { 74 | int offset = FrameHeaderFlyweight.BYTES + FlyweightUtils.ID_BYTES + FlyweightUtils.ID_BYTES + Long.BYTES; 75 | // serviceName length 76 | offset += decodeByteStringLength(byteBuf, offset); 77 | return TagsFlyweight.decode(offset, byteBuf); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /rsocket-broker-client/src/test/java/io/rsocket/broker/client/BrokerRSocketClientTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.client; 18 | 19 | import io.netty.buffer.CompositeByteBuf; 20 | import io.rsocket.metadata.CompositeMetadata; 21 | import io.rsocket.metadata.CompositeMetadata.Entry; 22 | import io.rsocket.broker.common.Id; 23 | import io.rsocket.broker.common.MimeTypes; 24 | import io.rsocket.broker.frames.Address; 25 | import io.rsocket.broker.frames.AddressFlyweight; 26 | import io.rsocket.broker.frames.RoutingType; 27 | import io.rsocket.transport.local.LocalClientTransport; 28 | import org.junit.jupiter.api.Test; 29 | 30 | import static io.rsocket.broker.common.WellKnownKey.SERVICE_NAME; 31 | import static org.assertj.core.api.Assertions.assertThat; 32 | 33 | public class BrokerRSocketClientTests { 34 | 35 | public static final Id ORIGIN_ID = Id.from("00000000-0000-0000-0000-000000000001"); 36 | 37 | @Test 38 | public void testAddressWithServiceNameOnly() { 39 | Route route = setupRoute(); 40 | 41 | CompositeByteBuf composite = route.allocator().compositeBuffer(); 42 | route.encodeAddressMetadata(composite, "remoteservice"); 43 | 44 | Address address = assertAddress(composite); 45 | assertThat(address.getTags().asMap()).hasSize(1); 46 | } 47 | 48 | @Test 49 | public void testAddressWithMultipleTags() { 50 | Route route = setupRoute(); 51 | 52 | CompositeByteBuf composite = route.allocator().compositeBuffer(); 53 | route.encodeAddressMetadata(composite, tags -> tags.with(SERVICE_NAME, "remoteservice") 54 | .with("mykey", "mykeyvalue")); 55 | 56 | Address address = assertAddress(composite); 57 | assertThat(address.getTags().asMap()).hasSize(2); 58 | assertThat(address.getTags().get("mykey")).isEqualTo("mykeyvalue"); 59 | } 60 | 61 | private Address assertAddress(CompositeByteBuf composite) { 62 | CompositeMetadata compositeMetadata = new CompositeMetadata(composite, true); 63 | Entry entry = compositeMetadata.stream().findFirst() 64 | .orElseThrow(() -> new IllegalArgumentException("Unable to decode")); 65 | assertThat(entry.getMimeType()).isEqualTo(MimeTypes.BROKER_FRAME_MIME_TYPE); 66 | Address address = Address.from(entry.getContent(), AddressFlyweight.FLAGS_M); 67 | assertThat(address).isNotNull(); 68 | assertThat(address.getOriginRouteId()).isEqualTo(ORIGIN_ID); 69 | assertThat(address.getTags().get(SERVICE_NAME)).isEqualTo("remoteservice"); 70 | assertThat(address.getRoutingType()).isEqualTo(RoutingType.MULTICAST); 71 | return address; 72 | } 73 | 74 | private Route setupRoute() { 75 | Route route = BrokerRSocketConnector.create() 76 | .routeId(ORIGIN_ID) 77 | .serviceName("localservice") 78 | .toRSocketClient(LocalClientTransport.create("local")); 79 | return route; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /rsocket-broker-frames/src/main/java/io/rsocket/broker/frames/FrameHeaderFlyweight.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.frames; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.buffer.ByteBufAllocator; 21 | 22 | /** 23 | * https://github.com/rsocket-broker/rsocket-broker-spec/blob/master/RSocketBrokerSpecification.md#framing-header-format 24 | */ 25 | public class FrameHeaderFlyweight { 26 | 27 | public static final short MAJOR_VERSION = 0; 28 | public static final short MINOR_VERSION = 1; 29 | 30 | private static final int MAJOR_VERSION_SIZE = Short.BYTES; 31 | private static final int MINOR_VERSION_SIZE = Short.BYTES; 32 | private static final int FRAME_TYPE_SIZE = Short.BYTES; 33 | private static final int FRAME_FLAGS_MASK = 0b0000_0011_1111_1111; 34 | private static final int FLAG_BITS = 10; 35 | 36 | public static final int BYTES = MAJOR_VERSION_SIZE + MINOR_VERSION_SIZE + FRAME_TYPE_SIZE; 37 | 38 | public static ByteBuf encode(ByteBufAllocator allocator, FrameType frameType, int flags) { 39 | return encode(allocator, MAJOR_VERSION, MINOR_VERSION, frameType, flags); 40 | } 41 | 42 | public static ByteBuf encode(ByteBufAllocator allocator, short majorVersion, 43 | short minorVersion, FrameType frameType, int flags) { 44 | //TODO: check that only one broker flag is set 45 | //if (!frameType.canHaveMetadata() && ((flags & FLAGS_M) == FLAGS_M)) { 46 | // throw new IllegalStateException("bad value for metadata flag"); 47 | //} 48 | int frameId = frameType.getId() << FLAG_BITS; 49 | short typeAndFlags = (short) (frameId | (short) flags); 50 | return allocator.buffer() 51 | .writeShort(majorVersion) 52 | .writeShort(minorVersion) 53 | .writeShort(typeAndFlags); 54 | } 55 | 56 | public static short majorVersion(ByteBuf byteBuf) { 57 | return byteBuf.getShort(0); 58 | } 59 | 60 | public static short minorVersion(ByteBuf byteBuf) { 61 | return byteBuf.getShort(MAJOR_VERSION_SIZE); 62 | } 63 | 64 | public static int flags(final ByteBuf byteBuf) { 65 | if (!byteBuf.isReadable(MAJOR_VERSION_SIZE + MINOR_VERSION_SIZE + Short.BYTES)) { 66 | return 0; 67 | } 68 | byteBuf.markReaderIndex(); 69 | byteBuf.skipBytes(MAJOR_VERSION_SIZE + MINOR_VERSION_SIZE); 70 | short typeAndFlags = byteBuf.readShort(); 71 | byteBuf.resetReaderIndex(); 72 | return typeAndFlags & FRAME_FLAGS_MASK; 73 | } 74 | 75 | public static FrameType frameType(ByteBuf byteBuf) { 76 | if (!byteBuf.isReadable(MAJOR_VERSION_SIZE + MINOR_VERSION_SIZE + Short.BYTES)) { 77 | return null; 78 | } 79 | byteBuf.markReaderIndex(); 80 | byteBuf.skipBytes(MAJOR_VERSION_SIZE + MINOR_VERSION_SIZE); 81 | short typeAndFlags = byteBuf.readShort(); 82 | byteBuf.resetReaderIndex(); 83 | // move typeAndFlags right 10 bits 84 | return FrameType.from(typeAndFlags >> FLAG_BITS); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at TBD. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /rsocket-broker-common-spring/src/main/java/io/rsocket/broker/common/spring/BrokerFrameDecoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.common.spring; 18 | 19 | import java.util.Map; 20 | 21 | import io.netty.buffer.ByteBuf; 22 | import io.netty.buffer.Unpooled; 23 | import io.rsocket.broker.frames.Address; 24 | import io.rsocket.broker.frames.BrokerInfo; 25 | import io.rsocket.broker.frames.FrameHeaderFlyweight; 26 | import io.rsocket.broker.frames.FrameType; 27 | import io.rsocket.broker.frames.RouteJoin; 28 | import io.rsocket.broker.frames.RouteRemove; 29 | import io.rsocket.broker.frames.RouteSetup; 30 | import io.rsocket.broker.frames.BrokerFrame; 31 | import org.reactivestreams.Publisher; 32 | import reactor.core.publisher.Flux; 33 | import reactor.core.publisher.Mono; 34 | 35 | import org.springframework.core.ResolvableType; 36 | import org.springframework.core.codec.AbstractDecoder; 37 | import org.springframework.core.codec.DecodingException; 38 | import org.springframework.core.io.buffer.DataBuffer; 39 | import org.springframework.core.io.buffer.DataBufferUtils; 40 | import org.springframework.core.io.buffer.NettyDataBuffer; 41 | import org.springframework.util.MimeType; 42 | 43 | 44 | public class BrokerFrameDecoder extends AbstractDecoder { 45 | 46 | public BrokerFrameDecoder() { 47 | super(MimeTypes.BROKER_FRAME_MIME_TYPE); 48 | } 49 | 50 | @Override 51 | public Flux decode(Publisher inputStream, ResolvableType elementType, MimeType mimeType, Map hints) { 52 | return Flux.from(inputStream) 53 | .flatMap(dataBuffer -> { 54 | BrokerFrame frame = decode(dataBuffer, elementType, mimeType, hints); 55 | if (frame == null) { 56 | return Mono.empty(); 57 | } 58 | return Mono.just(frame); 59 | }); 60 | } 61 | 62 | @Override 63 | public BrokerFrame decode(DataBuffer buffer, ResolvableType targetType, MimeType mimeType, Map hints) throws DecodingException { 64 | try { 65 | ByteBuf byteBuf = asByteBuf(buffer); 66 | // FIXME hack for ClusterJoinListener.setupRSocket() in broker broker 67 | if (!byteBuf.isReadable()) { 68 | return new BrokerFrame(FrameType.RESERVED, 0) { 69 | }; 70 | } 71 | int flags = FrameHeaderFlyweight.flags(byteBuf); 72 | FrameType frameType = FrameHeaderFlyweight.frameType(byteBuf); 73 | switch (frameType) { 74 | case ADDRESS: 75 | return Address.from(byteBuf, flags); 76 | case BROKER_INFO: 77 | return BrokerInfo.from(byteBuf); 78 | case ROUTE_JOIN: 79 | return RouteJoin.from(byteBuf); 80 | case ROUTE_REMOVE: 81 | return RouteRemove.from(byteBuf); 82 | case ROUTE_SETUP: 83 | return RouteSetup.from(byteBuf); 84 | } 85 | throw new IllegalArgumentException("Unknown FrameType " + frameType); 86 | } 87 | finally { 88 | DataBufferUtils.release(buffer); 89 | } 90 | } 91 | 92 | private static ByteBuf asByteBuf(DataBuffer buffer) { 93 | return buffer instanceof NettyDataBuffer 94 | ? ((NettyDataBuffer) buffer).getNativeBuffer() 95 | : Unpooled.wrappedBuffer(buffer.asByteBuffer()); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /rsocket-broker-frames/src/main/java/io/rsocket/broker/frames/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 io.rsocket.broker.frames; 18 | 19 | import java.util.Objects; 20 | import java.util.StringJoiner; 21 | 22 | import io.netty.buffer.ByteBuf; 23 | import io.rsocket.broker.common.Id; 24 | import io.rsocket.broker.common.Tags; 25 | 26 | /** 27 | * Representation of decoded BrokerInfo information. 28 | */ 29 | public final class BrokerInfo extends BrokerFrame { 30 | 31 | private final Id brokerId; 32 | 33 | private final long timestamp; 34 | 35 | private final Tags tags; 36 | 37 | private BrokerInfo(Id brokerId, long timestamp, Tags tags) { 38 | super(FrameType.BROKER_INFO, 0); 39 | this.brokerId = brokerId; 40 | this.timestamp = timestamp; 41 | this.tags = tags; 42 | } 43 | 44 | public Id getBrokerId() { 45 | return this.brokerId; 46 | } 47 | 48 | public long getTimestamp() { 49 | return this.timestamp; 50 | } 51 | 52 | public Tags getTags() { 53 | return this.tags; 54 | } 55 | 56 | /** 57 | * Equals does not take into account timestamp. 58 | * @param o other BrokerInfo 59 | * @return true if equal. 60 | */ 61 | @Override 62 | public boolean equals(Object o) { 63 | if (this == o) { 64 | return true; 65 | } 66 | if (o == null || getClass() != o.getClass()) { 67 | return false; 68 | } 69 | BrokerInfo that = (BrokerInfo) o; 70 | return Objects.equals(this.brokerId, that.brokerId) 71 | && Objects.equals(this.tags, that.tags); 72 | } 73 | 74 | /** 75 | * Hashcode does not use timestamp. 76 | * @return hashcode. 77 | */ 78 | @Override 79 | public int hashCode() { 80 | return Objects.hash(this.brokerId, this.tags); 81 | } 82 | 83 | @Override 84 | public String toString() { 85 | return new StringJoiner(", ", BrokerInfo.class.getSimpleName() + "[", "]") 86 | .add("brokerId=" + brokerId) 87 | .add("timestamp=" + timestamp) 88 | .add("tags=" + tags) 89 | .toString(); 90 | } 91 | 92 | public static Builder from(Id brokerId) { 93 | return new Builder(brokerId); 94 | } 95 | 96 | public static BrokerInfo from(ByteBuf byteBuf) { 97 | return from(BrokerInfoFlyweight.brokerId(byteBuf)) 98 | .timestamp(BrokerInfoFlyweight.timestamp(byteBuf)) 99 | .with(BrokerInfoFlyweight.tags(byteBuf)) 100 | .build(); 101 | } 102 | 103 | public static final class Builder extends Tags.Builder { 104 | 105 | private final Id brokerId; 106 | 107 | private long timestamp = System.currentTimeMillis(); 108 | 109 | private Builder(Id brokerId) { 110 | Objects.requireNonNull(brokerId, "brokerId may not be null"); 111 | this.brokerId = brokerId; 112 | } 113 | 114 | public Builder timestamp(long timestamp) { 115 | this.timestamp = timestamp; 116 | return this; 117 | } 118 | 119 | public BrokerInfo build() { 120 | if (timestamp <= 0) { 121 | throw new IllegalArgumentException("timestamp must be > 0"); 122 | } 123 | return new BrokerInfo(brokerId, timestamp, buildTags()); 124 | } 125 | 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /rsocket-broker-frames/src/main/java/io/rsocket/broker/frames/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 io.rsocket.broker.frames; 18 | 19 | import java.util.Objects; 20 | import java.util.StringJoiner; 21 | 22 | import io.netty.buffer.ByteBuf; 23 | import io.rsocket.broker.common.Id; 24 | 25 | /** 26 | * Representation of decoded RouteSetup information. 27 | */ 28 | public final class RouteRemove extends BrokerFrame { 29 | 30 | private final Id brokerId; 31 | 32 | private final Id routeId; 33 | 34 | private final long timestamp; 35 | 36 | public RouteRemove(Id brokerId, Id routeId, long timestamp) { 37 | super(FrameType.ROUTE_REMOVE, 0); 38 | this.brokerId = brokerId; 39 | this.routeId = routeId; 40 | this.timestamp = timestamp; 41 | } 42 | 43 | public Id getBrokerId() { 44 | return this.brokerId; 45 | } 46 | 47 | public Id getRouteId() { 48 | return this.routeId; 49 | } 50 | 51 | public long getTimestamp() { 52 | return this.timestamp; 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 | RouteRemove routeJoin = (RouteRemove) o; 64 | return this.timestamp == routeJoin.timestamp 65 | && Objects.equals(this.brokerId, routeJoin.brokerId) 66 | && Objects.equals(this.routeId, routeJoin.routeId); 67 | } 68 | 69 | @Override 70 | public int hashCode() { 71 | return Objects.hash(this.brokerId, this.routeId, this.timestamp); 72 | } 73 | 74 | @Override 75 | public String toString() { 76 | return new StringJoiner(", ", RouteRemove.class.getSimpleName() + "[", "]") 77 | .add("brokerId=" + brokerId) 78 | .add("routeId=" + routeId) 79 | .add("timestamp=" + timestamp) 80 | .toString(); 81 | } 82 | 83 | public static Builder builder() { 84 | return new Builder(); 85 | } 86 | 87 | public static RouteRemove from(ByteBuf byteBuf) { 88 | return builder() 89 | .brokerId(RouteRemoveFlyweight.brokerId(byteBuf)) 90 | .routeId(RouteRemoveFlyweight.routeId(byteBuf)) 91 | .timestamp(RouteRemoveFlyweight.timestamp(byteBuf)) 92 | .build(); 93 | } 94 | 95 | public static final class Builder { 96 | 97 | private Id brokerId; 98 | 99 | private Id routeId; 100 | 101 | private long timestamp = System.currentTimeMillis(); 102 | 103 | public Builder brokerId(Id brokerId) { 104 | this.brokerId = brokerId; 105 | return this; 106 | } 107 | 108 | public Builder routeId(Id routeId) { 109 | this.routeId = routeId; 110 | return this; 111 | } 112 | 113 | public Builder timestamp(long timestamp) { 114 | this.timestamp = timestamp; 115 | return this; 116 | } 117 | 118 | public RouteRemove build() { 119 | Objects.requireNonNull(brokerId, "brokerId may not be null"); 120 | Objects.requireNonNull(routeId, "brokerId may not be null"); 121 | if (timestamp <= 0) { 122 | throw new IllegalArgumentException("timestamp must be > 0"); 123 | } 124 | return new RouteRemove(brokerId, routeId, timestamp); 125 | } 126 | 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /rsocket-broker-client/src/main/java/io/rsocket/broker/client/BrokerRSocketClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.client; 18 | 19 | import java.util.function.Consumer; 20 | 21 | import io.netty.buffer.ByteBuf; 22 | import io.netty.buffer.ByteBufAllocator; 23 | import io.netty.buffer.CompositeByteBuf; 24 | import io.rsocket.Payload; 25 | import io.rsocket.RSocket; 26 | import io.rsocket.core.RSocketClient; 27 | import io.rsocket.metadata.CompositeMetadataCodec; 28 | import io.rsocket.broker.common.MimeTypes; 29 | import io.rsocket.broker.common.WellKnownKey; 30 | import io.rsocket.broker.frames.Address; 31 | import io.rsocket.broker.frames.AddressFlyweight; 32 | import org.reactivestreams.Publisher; 33 | import reactor.core.publisher.Flux; 34 | import reactor.core.publisher.Mono; 35 | 36 | public class BrokerRSocketClient implements RSocketClient, Route { 37 | 38 | private final BrokerRSocketConnector connector; 39 | private final RSocketClient delegate; 40 | 41 | public BrokerRSocketClient(BrokerRSocketConnector connector, RSocketClient delegate) { 42 | this.connector = connector; 43 | this.delegate = delegate; 44 | } 45 | 46 | @Override 47 | public void encodeAddressMetadata(CompositeByteBuf metadataHolder, String serviceName) { 48 | Address.Builder builder = Address.from(connector.getRouteId()).with(WellKnownKey.SERVICE_NAME, serviceName); 49 | encodeAndAddMetadata(metadataHolder, builder.build()); 50 | } 51 | 52 | @Override 53 | public void encodeAddressMetadata(CompositeByteBuf metadataHolder, Consumer addressConsumer) { 54 | Address.Builder builder = Address.from(connector.getRouteId()); 55 | addressConsumer.accept(builder); 56 | encodeAndAddMetadata(metadataHolder, builder.build()); 57 | } 58 | 59 | @Override 60 | public ByteBufAllocator allocator() { 61 | return connector.getAllocator(); 62 | } 63 | 64 | private void encodeAndAddMetadata(CompositeByteBuf composite, Address address) { 65 | ByteBuf byteBuf = AddressFlyweight 66 | .encode(connector.getAllocator(), address.getOriginRouteId(), address.getMetadata(), address.getTags(), address.getFlags()); 67 | CompositeMetadataCodec.encodeAndAddMetadata(composite, connector.getAllocator(), MimeTypes.BROKER_FRAME_MIME_TYPE, byteBuf); 68 | } 69 | 70 | @Override 71 | public Mono source() { 72 | return delegate.source(); 73 | } 74 | 75 | @Override 76 | public Mono fireAndForget(Mono payloadMono) { 77 | return delegate.fireAndForget(payloadMono); 78 | } 79 | 80 | @Override 81 | public Mono requestResponse(Mono payloadMono) { 82 | return delegate.requestResponse(payloadMono); 83 | } 84 | 85 | @Override 86 | public Flux requestStream(Mono payloadMono) { 87 | return delegate.requestStream(payloadMono); 88 | } 89 | 90 | @Override 91 | public Flux requestChannel(Publisher payloads) { 92 | return delegate.requestChannel(payloads); 93 | } 94 | 95 | @Override 96 | public Mono metadataPush(Mono payloadMono) { 97 | return delegate.metadataPush(payloadMono); 98 | } 99 | 100 | @Override 101 | public void dispose() { 102 | delegate.dispose(); 103 | } 104 | 105 | @Override 106 | public boolean isDisposed() { 107 | return delegate.isDisposed(); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /rsocket-broker-client-spring/src/main/java/io/rsocket/broker/client/spring/BrokerClientProperties.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 io.rsocket.broker.client.spring; 18 | 19 | import java.net.URI; 20 | import java.util.ArrayList; 21 | import java.util.LinkedHashMap; 22 | import java.util.List; 23 | import java.util.Map; 24 | 25 | import io.rsocket.broker.common.Id; 26 | import io.rsocket.broker.common.MutableKey; 27 | 28 | import org.springframework.boot.context.properties.ConfigurationProperties; 29 | import org.springframework.core.style.ToStringCreator; 30 | import org.springframework.util.MimeType; 31 | 32 | import static io.rsocket.broker.client.spring.BrokerClientProperties.CONFIG_PREFIX; 33 | 34 | @ConfigurationProperties(CONFIG_PREFIX) 35 | public class BrokerClientProperties { 36 | 37 | public static final String CONFIG_PREFIX = "io.rsocket.broker.client"; 38 | 39 | private boolean enabled; 40 | 41 | private Id routeId = Id.random(); 42 | 43 | private String serviceName; 44 | 45 | private Map tags = new LinkedHashMap<>(); 46 | 47 | private List brokers = new ArrayList<>(); 48 | 49 | private Map> address = new LinkedHashMap<>(); 50 | 51 | private boolean failIfMissingBrokerMetadata = true; 52 | 53 | private MimeType dataMimeType; 54 | 55 | public BrokerClientProperties() { 56 | } 57 | 58 | public boolean isEnabled() { 59 | return this.enabled; 60 | } 61 | 62 | public void setEnabled(boolean enabled) { 63 | this.enabled = enabled; 64 | } 65 | 66 | public Id getRouteId() { 67 | return this.routeId; 68 | } 69 | 70 | public void setRouteId(Id routeId) { 71 | this.routeId = routeId; 72 | } 73 | 74 | public String getServiceName() { 75 | return this.serviceName; 76 | } 77 | 78 | public void setServiceName(String serviceName) { 79 | this.serviceName = serviceName; 80 | } 81 | 82 | public Map getTags() { 83 | return this.tags; 84 | } 85 | 86 | public void setTags(Map tags) { 87 | this.tags = tags; 88 | } 89 | 90 | public List getBrokers() { 91 | return this.brokers; 92 | } 93 | 94 | public void setBrokers(List brokers) { 95 | this.brokers = brokers; 96 | } 97 | 98 | public Map> getAddress() { 99 | return address; 100 | } 101 | 102 | public boolean isFailIfMissingBrokerMetadata() { 103 | return this.failIfMissingBrokerMetadata; 104 | } 105 | 106 | public void setFailIfMissingBrokerMetadata(boolean failIfMissingBrokerMetadata) { 107 | this.failIfMissingBrokerMetadata = failIfMissingBrokerMetadata; 108 | } 109 | 110 | public MimeType getDataMimeType() { 111 | return dataMimeType; 112 | } 113 | 114 | public void setDataMimeType(MimeType dataMimeType) { 115 | this.dataMimeType = dataMimeType; 116 | } 117 | 118 | @Override 119 | public String toString() { 120 | // @formatter:off 121 | return new ToStringCreator(this) 122 | .append("enabled", isEnabled()) 123 | .append("routeId", getRouteId()) 124 | .append("serviceName", getServiceName()) 125 | .append("tags", getTags()) 126 | .append("broker", getBrokers()) 127 | .append("address", address) 128 | .append("failIfMissingBrokerMetadata", failIfMissingBrokerMetadata) 129 | .append("dataMimeType", dataMimeType) 130 | .toString(); 131 | // @formatter:on 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /rsocket-broker-common/src/main/java/io/rsocket/broker/common/Tags.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.common; 18 | 19 | import java.util.Collections; 20 | import java.util.LinkedHashMap; 21 | import java.util.Map; 22 | import java.util.Map.Entry; 23 | import java.util.Objects; 24 | import java.util.Set; 25 | 26 | public class Tags { 27 | 28 | private static final Tags EMPTY = builder().buildTags(); 29 | 30 | private final Map tags; 31 | 32 | public Tags(Map tags) { 33 | Objects.requireNonNull(tags, "tags may not be null"); 34 | this.tags = tags; 35 | } 36 | 37 | public Map asMap() { 38 | return this.tags; 39 | } 40 | 41 | public String get(WellKnownKey key) { 42 | return tags.get(Key.of(key)); 43 | } 44 | 45 | public String get(String key) { 46 | return tags.get(Key.of(key)); 47 | } 48 | 49 | public boolean containsKey(WellKnownKey key) { 50 | return tags.containsKey(Key.of(key)); 51 | } 52 | 53 | public boolean containsKey(String key) { 54 | return tags.containsKey(Key.of(key)); 55 | } 56 | 57 | public Set> entries() { 58 | return this.tags.entrySet(); 59 | } 60 | 61 | public boolean isEmpty() { 62 | return this.tags.isEmpty(); 63 | } 64 | 65 | @Override 66 | public String toString() { 67 | return Tags.class.getSimpleName() + tags.toString(); 68 | } 69 | 70 | @Override 71 | public boolean equals(Object o) { 72 | if (this == o) return true; 73 | if (o == null || getClass() != o.getClass()) return false; 74 | Tags tags1 = (Tags) o; 75 | return this.tags.equals(tags1.tags); 76 | } 77 | 78 | @Override 79 | public int hashCode() { 80 | return Objects.hash(this.tags); 81 | } 82 | 83 | @SuppressWarnings("rawtypes") 84 | public static Builder builder() { 85 | return new Builder(); 86 | } 87 | 88 | public static Tags empty() { 89 | return EMPTY; 90 | } 91 | 92 | public static class Builder> { 93 | 94 | private final Map tags = new LinkedHashMap<>(); 95 | 96 | protected Builder() { 97 | } 98 | 99 | public SELF with(String key, String value) { 100 | Objects.requireNonNull(key, "key may not be null"); 101 | if (key.length() > 128) { 102 | throw new IllegalArgumentException("key length can not be greater than 128, was " + key 103 | .length()); 104 | } 105 | return with(Key.of(key), value); 106 | } 107 | 108 | public SELF with(WellKnownKey key, String value) { 109 | Objects.requireNonNull(key, "key may not be null"); 110 | return with(Key.of(key), value); 111 | } 112 | 113 | @SuppressWarnings("unchecked") 114 | public SELF with(Key key, String value) { 115 | Objects.requireNonNull(key, "key may not be null"); 116 | if (value != null && value.length() > 128) { 117 | throw new IllegalArgumentException("value length can not be greater than 128, was " + value 118 | .length()); 119 | } 120 | this.tags.put(key, value); 121 | return (SELF) this; 122 | } 123 | 124 | @SuppressWarnings("unchecked") 125 | public SELF with(Tags tags) { 126 | this.tags.putAll(tags.asMap()); 127 | return (SELF) this; 128 | } 129 | 130 | protected Map getTags() { 131 | return this.tags; 132 | } 133 | 134 | public Tags buildTags() { 135 | return new Tags(Collections.unmodifiableMap(this.tags)); 136 | } 137 | 138 | } 139 | 140 | } 141 | -------------------------------------------------------------------------------- /rsocket-broker-common-spring/src/main/java/io/rsocket/broker/common/spring/BrokerFrameEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.common.spring; 18 | 19 | import java.util.Map; 20 | 21 | import io.netty.buffer.ByteBuf; 22 | import io.netty.buffer.ByteBufAllocator; 23 | import io.rsocket.broker.frames.Address; 24 | import io.rsocket.broker.frames.AddressFlyweight; 25 | import io.rsocket.broker.frames.BrokerInfo; 26 | import io.rsocket.broker.frames.BrokerInfoFlyweight; 27 | import io.rsocket.broker.frames.RouteJoin; 28 | import io.rsocket.broker.frames.RouteJoinFlyweight; 29 | import io.rsocket.broker.frames.RouteRemove; 30 | import io.rsocket.broker.frames.RouteRemoveFlyweight; 31 | import io.rsocket.broker.frames.RouteSetup; 32 | import io.rsocket.broker.frames.RouteSetupFlyweight; 33 | import io.rsocket.broker.frames.BrokerFrame; 34 | import org.reactivestreams.Publisher; 35 | import reactor.core.publisher.Flux; 36 | 37 | import org.springframework.core.ResolvableType; 38 | import org.springframework.core.codec.AbstractEncoder; 39 | import org.springframework.core.io.buffer.DataBuffer; 40 | import org.springframework.core.io.buffer.DataBufferFactory; 41 | import org.springframework.core.io.buffer.NettyDataBufferFactory; 42 | import org.springframework.util.MimeType; 43 | 44 | public class BrokerFrameEncoder extends AbstractEncoder { 45 | 46 | public BrokerFrameEncoder() { 47 | super(MimeTypes.BROKER_FRAME_MIME_TYPE); 48 | } 49 | 50 | @Override 51 | public Flux encode(Publisher inputStream, DataBufferFactory bufferFactory, ResolvableType elementType, MimeType mimeType, Map hints) { 52 | return Flux.from(inputStream).map(value -> encodeValue(value, bufferFactory, 53 | elementType, mimeType, hints)); 54 | } 55 | 56 | @Override 57 | public DataBuffer encodeValue(BrokerFrame brokerFrame, DataBufferFactory bufferFactory, ResolvableType valueType, MimeType mimeType, Map hints) { 58 | NettyDataBufferFactory factory = (NettyDataBufferFactory) bufferFactory; 59 | 60 | ByteBufAllocator allocator = factory.getByteBufAllocator(); 61 | ByteBuf encoded; 62 | switch (brokerFrame.getFrameType()) { 63 | case ADDRESS: 64 | Address address = (Address) brokerFrame; 65 | encoded = AddressFlyweight.encode(allocator, address.getOriginRouteId(), 66 | address.getMetadata(), address.getTags(), brokerFrame.getFlags()); 67 | break; 68 | case BROKER_INFO: 69 | BrokerInfo brokerInfo = (BrokerInfo) brokerFrame; 70 | encoded = BrokerInfoFlyweight.encode(allocator, brokerInfo.getBrokerId(), 71 | brokerInfo.getTimestamp(), brokerInfo.getTags(), brokerFrame.getFlags()); 72 | break; 73 | case ROUTE_JOIN: 74 | RouteJoin routeJoin = (RouteJoin) brokerFrame; 75 | encoded = RouteJoinFlyweight.encode(allocator, 76 | routeJoin.getBrokerId(), routeJoin.getRouteId(), routeJoin.getTimestamp(), 77 | routeJoin.getServiceName(), routeJoin.getTags(), brokerFrame.getFlags()); 78 | break; 79 | case ROUTE_REMOVE: 80 | RouteRemove routeRemove = (RouteRemove) brokerFrame; 81 | encoded = RouteRemoveFlyweight.encode(allocator, 82 | routeRemove.getBrokerId(), routeRemove.getRouteId(), routeRemove.getTimestamp(), brokerFrame 83 | .getFlags()); 84 | break; 85 | case ROUTE_SETUP: 86 | RouteSetup routeSetup = (RouteSetup) brokerFrame; 87 | encoded = RouteSetupFlyweight.encode(allocator, 88 | routeSetup.getRouteId(), routeSetup.getServiceName(), routeSetup.getTags(), brokerFrame 89 | .getFlags()); 90 | break; 91 | default: 92 | throw new IllegalArgumentException("Unknown FrameType " + brokerFrame.getFrameType()); 93 | } 94 | 95 | return factory.wrap(encoded); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /rsocket-broker-frames/src/main/java/io/rsocket/broker/frames/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 io.rsocket.broker.frames; 18 | 19 | import java.util.Objects; 20 | import java.util.StringJoiner; 21 | 22 | import io.netty.buffer.ByteBuf; 23 | import io.rsocket.broker.common.Id; 24 | import io.rsocket.broker.common.Tags; 25 | 26 | /** 27 | * Representation of decoded RouteJoin information. 28 | */ 29 | public final class RouteJoin extends BrokerFrame { 30 | 31 | private final Id brokerId; 32 | 33 | private final Id routeId; 34 | 35 | private final long timestamp; 36 | 37 | private final String serviceName; 38 | 39 | private final Tags tags; 40 | 41 | public RouteJoin(Id brokerId, Id routeId, long timestamp, 42 | String serviceName, Tags tags) { 43 | super(FrameType.ROUTE_JOIN, 0); 44 | this.brokerId = brokerId; 45 | this.routeId = routeId; 46 | this.timestamp = timestamp; 47 | this.serviceName = serviceName; 48 | this.tags = tags; 49 | } 50 | 51 | public Id getBrokerId() { 52 | return this.brokerId; 53 | } 54 | 55 | public Id getRouteId() { 56 | return this.routeId; 57 | } 58 | 59 | public long getTimestamp() { 60 | return this.timestamp; 61 | } 62 | 63 | public String getServiceName() { 64 | return this.serviceName; 65 | } 66 | 67 | public Tags getTags() { 68 | return this.tags; 69 | } 70 | 71 | @Override 72 | public boolean equals(Object o) { 73 | if (this == o) { 74 | return true; 75 | } 76 | if (o == null || getClass() != o.getClass()) { 77 | return false; 78 | } 79 | RouteJoin routeJoin = (RouteJoin) o; 80 | return this.timestamp == routeJoin.timestamp 81 | && Objects.equals(this.brokerId, routeJoin.brokerId) 82 | && Objects.equals(this.routeId, routeJoin.routeId) 83 | && Objects.equals(this.serviceName, routeJoin.serviceName) 84 | && Objects.equals(this.tags, routeJoin.tags); 85 | } 86 | 87 | @Override 88 | public int hashCode() { 89 | return Objects.hash(this.brokerId, this.routeId, this.timestamp, this.serviceName, 90 | this.tags); 91 | } 92 | 93 | @Override 94 | public String toString() { 95 | return new StringJoiner(", ", RouteJoin.class.getSimpleName() + "[", "]") 96 | .add("brokerId=" + brokerId) 97 | .add("routeId=" + routeId) 98 | .add("timestamp=" + timestamp) 99 | .add("serviceName='" + serviceName + "'") 100 | .add("tags=" + tags) 101 | .toString(); 102 | } 103 | 104 | public static Builder builder() { 105 | return new Builder(); 106 | } 107 | 108 | public static RouteJoin from(ByteBuf byteBuf) { 109 | return builder() 110 | .brokerId(RouteJoinFlyweight.brokerId(byteBuf)) 111 | .routeId(RouteJoinFlyweight.routeId(byteBuf)) 112 | .timestamp(RouteJoinFlyweight.timestamp(byteBuf)) 113 | .serviceName(RouteJoinFlyweight.serviceName(byteBuf)) 114 | .with(RouteJoinFlyweight.tags(byteBuf)) 115 | .build(); 116 | } 117 | 118 | public static final class Builder extends Tags.Builder { 119 | 120 | private Id brokerId; 121 | 122 | private Id routeId; 123 | 124 | private long timestamp = System.currentTimeMillis(); 125 | 126 | private String serviceName; 127 | 128 | public Builder brokerId(Id brokerId) { 129 | this.brokerId = brokerId; 130 | return this; 131 | } 132 | 133 | public Builder routeId(Id routeId) { 134 | this.routeId = routeId; 135 | return this; 136 | } 137 | 138 | public Builder timestamp(long timestamp) { 139 | this.timestamp = timestamp; 140 | return this; 141 | } 142 | 143 | public Builder serviceName(String serviceName) { 144 | this.serviceName = serviceName; 145 | return this; 146 | } 147 | 148 | public RouteJoin build() { 149 | Objects.requireNonNull(brokerId, "brokerId may not be null"); 150 | Objects.requireNonNull(routeId, "routeId may not be null"); 151 | Objects.requireNonNull(serviceName, "serviceName may not be null"); 152 | if (timestamp <= 0) { 153 | throw new IllegalArgumentException("timestamp must be > 0"); 154 | } 155 | return new RouteJoin(brokerId, routeId, timestamp, serviceName, 156 | buildTags()); 157 | } 158 | 159 | } 160 | 161 | } 162 | -------------------------------------------------------------------------------- /rsocket-broker-frames/src/main/java/io/rsocket/broker/frames/Address.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.frames; 18 | 19 | import java.util.Objects; 20 | import java.util.StringJoiner; 21 | 22 | import io.netty.buffer.ByteBuf; 23 | import io.rsocket.broker.common.Id; 24 | import io.rsocket.broker.common.Key; 25 | import io.rsocket.broker.common.Tags; 26 | import io.rsocket.broker.common.WellKnownKey; 27 | 28 | import static io.rsocket.broker.frames.AddressFlyweight.FLAGS_E; 29 | import static io.rsocket.broker.frames.AddressFlyweight.metadata; 30 | import static io.rsocket.broker.frames.AddressFlyweight.originRouteId; 31 | import static io.rsocket.broker.frames.AddressFlyweight.tags; 32 | 33 | /** 34 | * Useful to hold decoded Address information. 35 | */ 36 | public class Address extends BrokerFrame { 37 | 38 | private final Id originRouteId; 39 | private final Tags metadata; 40 | private final Tags tags; 41 | 42 | private Address(Id originRouteId, Tags metadata, Tags tags, int flags) { 43 | super(FrameType.ADDRESS, flags); 44 | this.originRouteId = originRouteId; 45 | this.metadata = metadata; 46 | this.tags = tags; 47 | } 48 | 49 | public Id getOriginRouteId() { 50 | return this.originRouteId; 51 | } 52 | 53 | public Tags getMetadata() { 54 | return this.metadata; 55 | } 56 | 57 | public Tags getTags() { 58 | return this.tags; 59 | } 60 | 61 | public boolean isEncrypted() { 62 | int flag = getFlags() & FLAGS_E; 63 | return flag == FLAGS_E; 64 | } 65 | 66 | public RoutingType getRoutingType() { 67 | int routingType = getFlags() & (~AddressFlyweight.ROUTING_TYPE_MASK); 68 | return RoutingType.from(routingType); 69 | } 70 | 71 | @Override 72 | public String toString() { 73 | return new StringJoiner(", ", Address.class.getSimpleName() + "[", "]") 74 | .add("originRouteId=" + originRouteId) 75 | .add("metadata=" + metadata) 76 | .add("tags=" + tags) 77 | .add("flags=" + getFlags()) 78 | .toString(); 79 | } 80 | 81 | public static Builder from(Id originRouteId) { 82 | return new Builder(originRouteId); 83 | } 84 | 85 | public static Address from(ByteBuf byteBuf, int flags) { 86 | return from(originRouteId(byteBuf)).withMetadata(metadata(byteBuf)) 87 | .with(tags(byteBuf)).flags(flags).build(); 88 | } 89 | 90 | public static final class Builder extends Tags.Builder { 91 | private final Id originRouteId; 92 | 93 | private final Tags.Builder metadataBuilder = Tags.builder(); 94 | 95 | private int flags = AddressFlyweight.FLAGS_U; 96 | 97 | private Builder(Id originRouteId) { 98 | Objects.requireNonNull(originRouteId, "id may not be null"); 99 | this.originRouteId = originRouteId; 100 | } 101 | 102 | public Builder withMetadata(String key, String value) { 103 | metadataBuilder.with(key, value); 104 | return this; 105 | } 106 | 107 | public Builder withMetadata(WellKnownKey key, String value) { 108 | metadataBuilder.with(key, value); 109 | return this; 110 | } 111 | 112 | public Builder withMetadata(Key key, String value) { 113 | metadataBuilder.with(key, value); 114 | return this; 115 | } 116 | 117 | public Builder withMetadata(Tags metadata) { 118 | metadataBuilder.with(metadata); 119 | return this; 120 | } 121 | 122 | public Builder encrypted() { 123 | flags |= AddressFlyweight.FLAGS_E; 124 | return this; 125 | } 126 | 127 | public Builder routingType(RoutingType routingType) { 128 | if (routingType == null) { 129 | throw new IllegalArgumentException("routingType may not be null"); 130 | } 131 | // reset broker type flag 132 | flags &= AddressFlyweight.ROUTING_TYPE_MASK; 133 | flags |= routingType.getFlag(); 134 | return this; 135 | } 136 | 137 | public Address build() { 138 | Tags tags = buildTags(); 139 | if (tags.isEmpty()) { 140 | throw new IllegalArgumentException("Address tags may not be empty"); 141 | } 142 | return new Address(originRouteId, metadataBuilder.buildTags(), tags, flags); 143 | } 144 | 145 | public Builder flags(int flags) { 146 | this.flags = flags; 147 | return this; 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /rsocket-broker-client-spring/src/main/java/io/rsocket/broker/client/spring/BrokerRSocketRequesterBuilder.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 io.rsocket.broker.client.spring; 18 | 19 | import java.net.URI; 20 | import java.util.List; 21 | import java.util.function.Consumer; 22 | 23 | import io.rsocket.loadbalance.LoadbalanceStrategy; 24 | import io.rsocket.loadbalance.LoadbalanceTarget; 25 | import io.rsocket.transport.ClientTransport; 26 | import io.rsocket.transport.netty.client.TcpClientTransport; 27 | import io.rsocket.transport.netty.client.WebsocketClientTransport; 28 | import org.reactivestreams.Publisher; 29 | import reactor.core.publisher.Mono; 30 | 31 | import org.springframework.messaging.rsocket.RSocketConnectorConfigurer; 32 | import org.springframework.messaging.rsocket.RSocketRequester; 33 | import org.springframework.messaging.rsocket.RSocketStrategies; 34 | import org.springframework.util.MimeType; 35 | import org.springframework.util.RouteMatcher; 36 | 37 | public class BrokerRSocketRequesterBuilder implements RSocketRequester.Builder { 38 | 39 | private final RSocketRequester.Builder delegate; 40 | 41 | private final BrokerClientProperties properties; 42 | 43 | private final RouteMatcher routeMatcher; 44 | 45 | BrokerRSocketRequesterBuilder(RSocketRequester.Builder delegate, 46 | BrokerClientProperties properties, RouteMatcher routeMatcher) { 47 | this.delegate = delegate; 48 | this.properties = properties; 49 | this.routeMatcher = routeMatcher; 50 | } 51 | 52 | @Override 53 | public RSocketRequester.Builder rsocketConnector(RSocketConnectorConfigurer configurer) { 54 | return delegate.rsocketConnector(configurer); 55 | } 56 | 57 | @Override 58 | public RSocketRequester.Builder dataMimeType(MimeType mimeType) { 59 | return delegate.dataMimeType(mimeType); 60 | } 61 | 62 | @Override 63 | public RSocketRequester.Builder metadataMimeType(MimeType mimeType) { 64 | return delegate.metadataMimeType(mimeType); 65 | } 66 | 67 | @Override 68 | public RSocketRequester.Builder setupData(Object data) { 69 | return delegate.setupData(data); 70 | } 71 | 72 | @Override 73 | public RSocketRequester.Builder setupRoute(String route, Object... routeVars) { 74 | return delegate.setupRoute(route, routeVars); 75 | } 76 | 77 | @Override 78 | public RSocketRequester.Builder setupMetadata(Object value, MimeType mimeType) { 79 | return delegate.setupMetadata(value, mimeType); 80 | } 81 | 82 | @Override 83 | public RSocketRequester.Builder rsocketStrategies(RSocketStrategies strategies) { 84 | return delegate.rsocketStrategies(strategies); 85 | } 86 | 87 | @Override 88 | public RSocketRequester.Builder rsocketStrategies( 89 | Consumer configurer) { 90 | return delegate.rsocketStrategies(configurer); 91 | } 92 | 93 | @Override 94 | public BrokerRSocketRequester tcp(String host, int port) { 95 | return wrap(delegate.tcp(host, port)); 96 | } 97 | 98 | @Override 99 | public BrokerRSocketRequester websocket(URI uri) { 100 | return wrap(delegate.websocket(uri)); 101 | } 102 | 103 | @Override 104 | public BrokerRSocketRequester transport(ClientTransport transport) { 105 | return wrap(delegate.transport(transport)); 106 | } 107 | 108 | @Override 109 | public RSocketRequester.Builder apply(Consumer configurer) { 110 | return delegate.apply(configurer); 111 | } 112 | 113 | @Override 114 | public BrokerRSocketRequester transports(Publisher> targetPublisher, LoadbalanceStrategy loadbalanceStrategy) { 115 | return wrap(delegate.transports(targetPublisher, loadbalanceStrategy)); 116 | } 117 | 118 | @Override 119 | @Deprecated 120 | public Mono connectTcp(String host, int port) { 121 | return connect(TcpClientTransport.create(host, port)); 122 | } 123 | 124 | @Override 125 | @Deprecated 126 | public Mono connectWebSocket(URI uri) { 127 | return connect(WebsocketClientTransport.create(uri)); 128 | } 129 | 130 | @Override 131 | @Deprecated 132 | public Mono connect(ClientTransport transport) { 133 | return delegate.connect(transport).map(this::wrap); 134 | } 135 | 136 | private BrokerRSocketRequester wrap(RSocketRequester requester) { 137 | return new BrokerRSocketRequester(requester, properties, routeMatcher); 138 | } 139 | 140 | } 141 | -------------------------------------------------------------------------------- /rsocket-broker-client-spring/src/test/java/io/rsocket/broker/client/spring/BrokerRSocketRequesterTests.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 io.rsocket.broker.client.spring; 18 | 19 | import java.util.Collections; 20 | import java.util.HashMap; 21 | import java.util.Map; 22 | 23 | import io.rsocket.broker.common.Id; 24 | import io.rsocket.broker.common.Key; 25 | import io.rsocket.broker.common.Tags; 26 | import io.rsocket.broker.frames.Address; 27 | import org.junit.jupiter.api.Test; 28 | 29 | import org.springframework.messaging.rsocket.RSocketRequester; 30 | import org.springframework.messaging.rsocket.RSocketRequester.RequestSpec; 31 | import org.springframework.util.AntPathMatcher; 32 | import org.springframework.util.RouteMatcher; 33 | import org.springframework.util.SimpleRouteMatcher; 34 | 35 | import static io.rsocket.broker.client.spring.BrokerRSocketRequester.address; 36 | import static io.rsocket.broker.client.spring.BrokerRSocketRequester.expand; 37 | import static io.rsocket.broker.common.WellKnownKey.ROUTE_ID; 38 | import static io.rsocket.broker.common.WellKnownKey.SERVICE_NAME; 39 | import static org.assertj.core.api.Assertions.assertThat; 40 | import static org.mockito.Mockito.mock; 41 | import static org.mockito.Mockito.when; 42 | 43 | 44 | public class BrokerRSocketRequesterTests { 45 | 46 | @Test 47 | public void addressWorks() { 48 | RouteMatcher routeMatcher = new SimpleRouteMatcher(new AntPathMatcher(".")); 49 | RouteMatcher.Route route = routeMatcher.parseRoute("myroute.foo1.bar1"); 50 | Tags tags = Tags.builder() 51 | .with(SERVICE_NAME, "{foo}") 52 | .with(ROUTE_ID, "22") 53 | .with("mycustomkey", "{foo}-{bar}") 54 | .buildTags(); 55 | 56 | Address addr = address(routeMatcher, route, 57 | Id.from("00000000-0000-0000-0000-000000000011"), "myroute.{foo}.{bar}", tags.asMap()).build(); 58 | 59 | assertThat(addr).isNotNull(); 60 | assertThat(addr.getTags().asMap()).isNotEmpty() 61 | .containsEntry(Key.of(SERVICE_NAME), "foo1") 62 | .containsEntry(Key.of(ROUTE_ID), "22") 63 | .containsEntry(Key.of("mycustomkey"), "foo1-bar1"); 64 | } 65 | 66 | @Test 67 | public void expandArrayVars() { 68 | String result = expand("myroute.{foo}.{bar}", "foo1", "bar1"); 69 | assertThat(result).isEqualTo("myroute.foo1.bar1"); 70 | } 71 | 72 | @Test 73 | public void expandMapVars() { 74 | HashMap map = new HashMap<>(); 75 | map.put("value", "a+b"); 76 | map.put("city", "Z\u00fcrich"); 77 | String result = expand("/hotel list/{city} specials/{value}", map); 78 | 79 | assertThat(result).isEqualTo("/hotel list/Z\u00fcrich specials/a+b"); 80 | } 81 | 82 | @Test 83 | public void expandPartially() { 84 | HashMap map = new HashMap<>(); 85 | map.put("city", "Z\u00fcrich"); 86 | String result = expand("/hotel list/{city} specials/{value}", map); 87 | 88 | assertThat(result).isEqualTo("/hotel list/Zürich specials/"); 89 | } 90 | 91 | @Test 92 | public void expandSimple() { 93 | HashMap map = new HashMap<>(); 94 | map.put("foo", "1 2"); 95 | map.put("bar", "3 4"); 96 | String result = expand("/{foo} {bar}", map); 97 | assertThat(result).isEqualTo("/1 2 3 4"); 98 | } 99 | 100 | @Test // SPR-13311 101 | public void expandWithRegexVar() { 102 | String template = "/myurl/{name:[a-z]{1,5}}/show"; 103 | Map map = Collections.singletonMap("name", "test"); 104 | String result = expand(template, map); 105 | assertThat(result).isEqualTo("/myurl/test/show"); 106 | } 107 | 108 | @Test // SPR-17630 109 | public void expandWithMismatchedCurlyBraces() { 110 | String result = expand("/myurl/{{{{", Collections.emptyMap()); 111 | assertThat(result).isEqualTo("/myurl/{{{{"); 112 | } 113 | 114 | // if BrokerRequestSpec.address() calls delegate.metadata() this fails with an exception 115 | @Test 116 | public void routeWithAddress() { 117 | RequestSpec spec = mock(RequestSpec.class); 118 | RSocketRequester requester = mock(RSocketRequester.class); 119 | when(requester.route("route")).thenReturn(spec); 120 | BrokerClientProperties properties = new BrokerClientProperties(); 121 | RouteMatcher routeMatcher = mock(RouteMatcher.class); 122 | 123 | BrokerRSocketRequester brokerRSocketRequester = new BrokerRSocketRequester(requester, properties, routeMatcher); 124 | brokerRSocketRequester 125 | .route("route") 126 | .address("service") 127 | .send(); 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /rsocket-broker-client-spring/src/test/java/io/rsocket/broker/client/spring/CustomClientTransportFactoryTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.client.spring; 18 | 19 | import java.net.URI; 20 | import java.util.List; 21 | 22 | import io.rsocket.core.RSocketServer; 23 | import io.rsocket.broker.common.Id; 24 | import io.rsocket.broker.common.spring.ClientTransportFactory; 25 | import io.rsocket.loadbalance.LoadbalanceStrategy; 26 | import io.rsocket.loadbalance.LoadbalanceTarget; 27 | import io.rsocket.transport.ClientTransport; 28 | import io.rsocket.transport.local.LocalClientTransport; 29 | import io.rsocket.transport.local.LocalServerTransport; 30 | import org.junit.jupiter.api.Test; 31 | import org.reactivestreams.Publisher; 32 | import reactor.core.publisher.Flux; 33 | 34 | import org.springframework.beans.factory.annotation.Autowired; 35 | import org.springframework.boot.SpringBootConfiguration; 36 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 37 | import org.springframework.boot.test.context.SpringBootTest; 38 | import org.springframework.context.annotation.Bean; 39 | import org.springframework.core.Ordered; 40 | import org.springframework.core.annotation.Order; 41 | import org.springframework.messaging.rsocket.RSocketRequester; 42 | import org.springframework.messaging.rsocket.RSocketStrategies; 43 | import org.springframework.util.RouteMatcher; 44 | 45 | import static org.assertj.core.api.Assertions.assertThat; 46 | 47 | @SpringBootTest(properties = {"io.rsocket.broker.client.route-id=00000000-0000-0000-0000-000000000008", 48 | "io.rsocket.broker.client.brokers[0]=local://mylocal?arg1=val1"}) 49 | public class CustomClientTransportFactoryTests { 50 | 51 | @Autowired 52 | BrokerClientProperties properties; 53 | 54 | @Autowired 55 | TestBrokerRSocketRequesterBuilder requesterBuilder; 56 | 57 | @Test 58 | public void customPropertiesAreSet() { 59 | assertThat(properties.getRouteId()).isEqualTo(Id.from("00000000-0000-0000-0000-000000000008")); 60 | assertThat(properties.getBrokers()).hasSize(1); 61 | URI broker = properties.getBrokers().get(0); 62 | assertThat(broker).isNotNull().hasScheme("local").hasHost("mylocal").hasParameter("arg1", "val1"); 63 | 64 | assertThat(requesterBuilder.localTransportSet).isTrue(); 65 | } 66 | 67 | private static class TestBrokerRSocketRequesterBuilder extends BrokerRSocketRequesterBuilder { 68 | 69 | boolean localTransportSet = false; 70 | 71 | public TestBrokerRSocketRequesterBuilder(RSocketRequester.Builder delegate, BrokerClientProperties properties, RouteMatcher routeMatcher) { 72 | super(delegate, properties, routeMatcher); 73 | } 74 | 75 | @Override 76 | public BrokerRSocketRequester transport(ClientTransport transport) { 77 | if (transport instanceof LocalClientTransport) { 78 | localTransportSet = true; 79 | } 80 | return super.transport(transport); 81 | } 82 | 83 | @Override 84 | public BrokerRSocketRequester transports(Publisher> targetPublisher, LoadbalanceStrategy loadbalanceStrategy) { 85 | Flux> targets = Flux.from(targetPublisher) 86 | .map(loadbalanceTargets -> { 87 | assertThat(loadbalanceTargets).hasSize(1); 88 | if (loadbalanceTargets.get(0).getTransport() instanceof LocalClientTransport) { 89 | localTransportSet = true; 90 | } 91 | return loadbalanceTargets; 92 | }); 93 | return super.transports(targets, loadbalanceStrategy); 94 | } 95 | } 96 | 97 | @SpringBootConfiguration 98 | @EnableAutoConfiguration 99 | protected static class TestConfig { 100 | 101 | @Bean 102 | @Order(Ordered.HIGHEST_PRECEDENCE) 103 | ClientTransportFactory customClientTransportFactory() { 104 | // so LocalClientTransport doesn't fail 105 | RSocketServer.create().bind(LocalServerTransport.create("mylocal")).subscribe(); 106 | return new ClientTransportFactory() { 107 | @Override 108 | public boolean supports(URI uri) { 109 | return uri.getScheme().equalsIgnoreCase("local"); 110 | } 111 | 112 | @Override 113 | public ClientTransport create(URI uri) { 114 | return LocalClientTransport.create(uri.getHost()); 115 | } 116 | }; 117 | } 118 | 119 | @Bean 120 | BrokerRSocketRequesterBuilder testBrokerRSocketRequesterBuilder(RSocketStrategies strategies, 121 | BrokerClientProperties properties) { 122 | return new TestBrokerRSocketRequesterBuilder(RSocketRequester.builder(), properties, strategies.routeMatcher()); 123 | } 124 | 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /rsocket-broker-frames/src/main/java/io/rsocket/broker/frames/TagsFlyweight.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.frames; 18 | 19 | import java.nio.charset.StandardCharsets; 20 | import java.util.Iterator; 21 | import java.util.Map; 22 | import java.util.Objects; 23 | 24 | import io.netty.buffer.ByteBuf; 25 | import io.netty.buffer.ByteBufUtil; 26 | import io.rsocket.broker.common.Key; 27 | import io.rsocket.broker.common.Tags; 28 | import io.rsocket.broker.common.WellKnownKey; 29 | 30 | public class TagsFlyweight { 31 | private static final int WELL_KNOWN_TAG = 0x80; 32 | private static final int HAS_MORE_TAGS = 0x80; 33 | private static final int MAX_TAG_LENGTH = 0x7F; 34 | 35 | public static ByteBuf encode(ByteBuf byteBuf, Tags tags) { 36 | Objects.requireNonNull(byteBuf, "byteBuf may not be null"); 37 | 38 | Iterator> it = tags.asMap().entrySet().iterator(); 39 | 40 | while (it.hasNext()) { 41 | Map.Entry entry = it.next(); 42 | Key key = entry.getKey(); 43 | if (key.getWellKnownKey() != null) { 44 | byte id = key.getWellKnownKey().getIdentifier(); 45 | int keyLength = WELL_KNOWN_TAG | id; 46 | byteBuf.writeByte(keyLength); 47 | } 48 | else { 49 | String keyString = key.getKey(); 50 | if (keyString == null) { 51 | continue; 52 | } 53 | int keyLength = ByteBufUtil.utf8Bytes(keyString); 54 | if (keyLength == 0 || keyLength > MAX_TAG_LENGTH) { 55 | continue; 56 | } 57 | byteBuf.writeByte(keyLength); 58 | ByteBufUtil.reserveAndWriteUtf8(byteBuf, keyString, keyLength); 59 | } 60 | 61 | boolean hasMoreTags = it.hasNext(); 62 | 63 | String value = entry.getValue(); 64 | int valueLength = ByteBufUtil.utf8Bytes(value); 65 | if (valueLength == 0 || valueLength > MAX_TAG_LENGTH) { 66 | continue; 67 | } 68 | int valueByte; 69 | if (hasMoreTags) { 70 | valueByte = HAS_MORE_TAGS | valueLength; 71 | } 72 | else { 73 | valueByte = valueLength; 74 | } 75 | byteBuf.writeByte(valueByte); 76 | ByteBufUtil.reserveAndWriteUtf8(byteBuf, value, valueLength); 77 | } 78 | 79 | return byteBuf; 80 | } 81 | 82 | public static Tags decode(int offset, ByteBuf byteBuf) { 83 | 84 | Tags.Builder builder = Tags.builder(); 85 | 86 | // this means we've reached the end of the buffer 87 | if (offset >= byteBuf.writerIndex()) { 88 | return builder.buildTags(); 89 | } 90 | 91 | boolean hasMoreTags = true; 92 | 93 | while (hasMoreTags) { 94 | int keyByte = byteBuf.getByte(offset); 95 | offset += Byte.BYTES; 96 | 97 | boolean isWellKnownTag = (keyByte & WELL_KNOWN_TAG) == WELL_KNOWN_TAG; 98 | 99 | int keyLengthOrId = keyByte & MAX_TAG_LENGTH; 100 | 101 | Key key; 102 | if (isWellKnownTag) { 103 | WellKnownKey wellKnownKey = WellKnownKey.fromIdentifier(keyLengthOrId); 104 | key = Key.of(wellKnownKey); 105 | } 106 | else { 107 | String keyString = byteBuf.toString(offset, keyLengthOrId, StandardCharsets.UTF_8); 108 | offset += keyLengthOrId; 109 | key = Key.of(keyString); 110 | } 111 | 112 | int valueByte = byteBuf.getByte(offset); 113 | offset += Byte.BYTES; 114 | 115 | hasMoreTags = (valueByte & HAS_MORE_TAGS) == HAS_MORE_TAGS; 116 | int valueLength = valueByte & MAX_TAG_LENGTH; 117 | String value = byteBuf.toString(offset, valueLength, 118 | StandardCharsets.UTF_8); 119 | offset += valueLength; 120 | 121 | builder.with(key, value); 122 | } 123 | 124 | return builder.buildTags(); 125 | } 126 | 127 | // same algorithm as decode except no object creation, length is final offset minus original. 128 | public static int length(int offset, ByteBuf byteBuf) { 129 | 130 | int originalOffset = offset; 131 | 132 | // this means we've reached the end of the buffer 133 | if (offset >= byteBuf.writerIndex()) { 134 | return 0; 135 | } 136 | 137 | boolean hasMoreTags = true; 138 | 139 | while (hasMoreTags) { 140 | int keyByte = byteBuf.getByte(offset); 141 | offset += Byte.BYTES; 142 | 143 | boolean isWellKnownTag = (keyByte & WELL_KNOWN_TAG) == WELL_KNOWN_TAG; 144 | 145 | if (!isWellKnownTag) { 146 | int keyLength = keyByte & MAX_TAG_LENGTH; 147 | offset += keyLength; 148 | } 149 | 150 | int valueByte = byteBuf.getByte(offset); 151 | offset += Byte.BYTES; 152 | 153 | hasMoreTags = (valueByte & HAS_MORE_TAGS) == HAS_MORE_TAGS; 154 | int valueLength = valueByte & MAX_TAG_LENGTH; 155 | offset += valueLength; 156 | } 157 | 158 | return offset - originalOffset; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /rsocket-broker-common/src/main/java/io/rsocket/broker/common/WellKnownKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.common; 18 | 19 | import java.util.Arrays; 20 | import java.util.HashMap; 21 | import java.util.Map; 22 | 23 | /** 24 | * https://github.com/rsocket-broker/rsocket-broker-spec/blob/master/RSocketBrokerSpecification.md#well-known-keys 25 | */ 26 | public enum WellKnownKey { 27 | 28 | // CHECKSTYLE:OFF 29 | // @formatter:off 30 | UNPARSEABLE_KEY("UNPARSEABLE_KEY_DO_NOT_USE", (byte) -2), 31 | UNKNOWN_RESERVED_KEY("UNKNOWN_YET_RESERVED_DO_NOT_USE", (byte) -1), 32 | 33 | NO_TAG("NO_TAG_DO_NOT_USE", (byte) 0x00), 34 | SERVICE_NAME("io.rsocket.broker.ServiceName", (byte) 0x01), 35 | ROUTE_ID("io.rsocket.broker.RouteId", (byte) 0x02), 36 | INSTANCE_NAME("io.rsocket.broker.InstanceName", (byte) 0x03), 37 | CLUSTER_NAME("io.rsocket.broker.ClusterName", (byte) 0x04), 38 | PROVIDER("io.rsocket.broker.Provider", (byte) 0x05), 39 | REGION("io.rsocket.broker.Region", (byte) 0x06), 40 | ZONE("io.rsocket.broker.Zone", (byte) 0x07), 41 | DEVICE("io.rsocket.broker.Device", (byte) 0x08), 42 | OS("io.rsocket.broker.OS", (byte) 0x09), 43 | USER_NAME("io.rsocket.broker.UserName", (byte) 0x0A), 44 | USER_ID("io.rsocket.broker.UserId", (byte) 0x0B), 45 | MAJOR_VERSION("io.rsocket.broker.MajorVersion", (byte) 0x0C), 46 | MINOR_VERSION("io.rsocket.broker.MinorVersion", (byte) 0x0D), 47 | PATCH_VERSION("io.rsocket.broker.PatchVersion", (byte) 0x0E), 48 | VERSION("io.rsocket.broker.Version", (byte) 0x0F), 49 | ENVIRONMENT("io.rsocket.broker.Environment", (byte) 0x10), 50 | TESTC_ELL("io.rsocket.broker.TestCell", (byte) 0x11), 51 | DNS("io.rsocket.broker.DNS", (byte) 0x12), 52 | IPV4("io.rsocket.broker.IPv4", (byte) 0x13), 53 | IPV6("io.rsocket.broker.IPv6", (byte) 0x14), 54 | COUNTRY("io.rsocket.broker.Country", (byte) 0x15), 55 | TIME_ZONE("io.rsocket.broker.TimeZone", (byte) 0x1A), 56 | SHARD_KEY("io.rsocket.broker.ShardKey", (byte) 0x1B), 57 | SHARD_METHOD("io.rsocket.broker.ShardMethod", (byte) 0x1C), 58 | STICKY_ROUTE_KEY("io.rsocket.broker.StickyRouteKey", (byte) 0x1D), 59 | LB_METHOD("io.rsocket.broker.LBMethod", (byte) 0x1E), 60 | BROKER_EXTENSION("Broker Implementation Extension Key", (byte) 0x1F), 61 | WELL_KNOWN_EXTENSION("Well Known Extension Key", (byte) 0x20), 62 | BROKER_PROXY_URI("Broker Proxy URI Key", (byte) 0x21), 63 | BROKER_CLUSTER_URI("Broker Cluster URI Key", (byte) 0x22); 64 | // @formatter:on 65 | // CHECKSTYLE:ON 66 | 67 | static final WellKnownKey[] TYPES_BY_ID; 68 | static final Map TYPES_BY_STRING; 69 | 70 | static { 71 | // precompute an array of all valid mime ids, 72 | // filling the blanks with the RESERVED enum 73 | TYPES_BY_ID = new WellKnownKey[128]; // 0-127 inclusive 74 | Arrays.fill(TYPES_BY_ID, UNKNOWN_RESERVED_KEY); 75 | // also prepare a Map of the types by key string 76 | TYPES_BY_STRING = new HashMap<>(128); 77 | 78 | for (WellKnownKey value : values()) { 79 | if (value.getIdentifier() >= 0) { 80 | TYPES_BY_ID[value.getIdentifier()] = value; 81 | TYPES_BY_STRING.put(value.getString(), value); 82 | } 83 | } 84 | } 85 | 86 | private final byte identifier; 87 | 88 | private final String str; 89 | 90 | private final Key key; 91 | 92 | WellKnownKey(String str, byte identifier) { 93 | this.str = str; 94 | this.identifier = identifier; 95 | this.key = new ImmutableKey(this); 96 | } 97 | 98 | public static WellKnownKey fromIdentifier(int id) { 99 | if (id < 0x00 || id > 0x7F) { 100 | return UNPARSEABLE_KEY; 101 | } 102 | return TYPES_BY_ID[id]; 103 | } 104 | 105 | public static WellKnownKey fromMimeType(String mimeType) { 106 | if (mimeType == null) { 107 | throw new IllegalArgumentException("type must be non-null"); 108 | } 109 | 110 | // force UNPARSEABLE if by chance UNKNOWN_RESERVED_MIME_TYPE's text has been used 111 | if (mimeType.equals(UNKNOWN_RESERVED_KEY.str)) { 112 | return UNPARSEABLE_KEY; 113 | } 114 | 115 | return TYPES_BY_STRING.getOrDefault(mimeType, UNPARSEABLE_KEY); 116 | } 117 | 118 | /** 119 | * @return the byte identifier of the mime type, guaranteed to be positive or zero. 120 | */ 121 | public byte getIdentifier() { 122 | return identifier; 123 | } 124 | 125 | /** 126 | * @return the mime type represented as a {@link String}, which is made of US_ASCII 127 | * compatible characters only 128 | */ 129 | public String getString() { 130 | return str; 131 | } 132 | 133 | public Key getKey() { 134 | return this.key; 135 | } 136 | 137 | /** @see #getString() */ 138 | @Override 139 | public String toString() { 140 | return str; 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /rsocket-broker-client/src/main/java/io/rsocket/broker/client/BrokerRSocketConnector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.client; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | import java.util.function.Consumer; 22 | import java.util.function.Supplier; 23 | 24 | import io.netty.buffer.ByteBuf; 25 | import io.netty.buffer.ByteBufAllocator; 26 | import io.netty.buffer.CompositeByteBuf; 27 | import io.netty.buffer.Unpooled; 28 | import io.rsocket.Payload; 29 | import io.rsocket.RSocket; 30 | import io.rsocket.core.RSocketClient; 31 | import io.rsocket.core.RSocketConnector; 32 | import io.rsocket.frame.decoder.PayloadDecoder; 33 | import io.rsocket.metadata.CompositeMetadataCodec; 34 | import io.rsocket.metadata.WellKnownMimeType; 35 | import io.rsocket.broker.common.Id; 36 | import io.rsocket.broker.common.MimeTypes; 37 | import io.rsocket.broker.common.Tags; 38 | import io.rsocket.broker.frames.RouteSetupFlyweight; 39 | import io.rsocket.transport.ClientTransport; 40 | import io.rsocket.util.DefaultPayload; 41 | import reactor.core.publisher.Mono; 42 | import reactor.util.function.Tuple2; 43 | import reactor.util.function.Tuples; 44 | 45 | public class BrokerRSocketConnector { 46 | 47 | private RSocketConnector delegate; 48 | private Id routeId; 49 | private String serviceName; 50 | private List> setupMetadatas = new ArrayList<>(); 51 | private Tags tags; 52 | private ByteBufAllocator allocator = ByteBufAllocator.DEFAULT; 53 | private ByteBuf setupData = Unpooled.EMPTY_BUFFER; 54 | 55 | private BrokerRSocketConnector() { 56 | this(RSocketConnector.create().payloadDecoder(PayloadDecoder.ZERO_COPY) 57 | .metadataMimeType(WellKnownMimeType.MESSAGE_RSOCKET_COMPOSITE_METADATA.getString())); 58 | } 59 | 60 | private BrokerRSocketConnector(RSocketConnector rSocketConnector) { 61 | delegate = rSocketConnector; 62 | } 63 | 64 | Id getRouteId() { 65 | return this.routeId; 66 | } 67 | 68 | ByteBufAllocator getAllocator() { 69 | return this.allocator; 70 | } 71 | 72 | /** 73 | * Static factory method to create an {@link BrokerRSocketConnector} instance and customize default 74 | * settings before connecting. 75 | * @return the {@link BrokerRSocketConnector} created. 76 | */ 77 | public static BrokerRSocketConnector create() { 78 | return new BrokerRSocketConnector(); 79 | } 80 | 81 | public static BrokerRSocketConnector create(RSocketConnector connector) { 82 | return new BrokerRSocketConnector(connector); 83 | } 84 | 85 | public BrokerRSocketConnector configure(Consumer consumer) { 86 | consumer.accept(delegate); 87 | return this; 88 | } 89 | 90 | public BrokerRSocketConnector byteBufAllocator(ByteBufAllocator allocator) { 91 | this.allocator = allocator; 92 | return this; 93 | } 94 | 95 | public BrokerRSocketConnector routeId(Id routeId) { 96 | this.routeId = routeId; 97 | return this; 98 | } 99 | 100 | public BrokerRSocketConnector serviceName(String serviceName) { 101 | this.serviceName = serviceName; 102 | return this; 103 | } 104 | 105 | public BrokerRSocketConnector setupTags(Tags tags) { 106 | this.tags = tags; 107 | return this; 108 | } 109 | 110 | public BrokerRSocketConnector setupMetadata(ByteBuf byteBuf, String mimeType) { 111 | setupMetadatas.add(Tuples.of(byteBuf, mimeType)); 112 | return this; 113 | } 114 | 115 | public BrokerRSocketConnector setupData(ByteBuf byteBuf) { 116 | this.setupData = byteBuf; 117 | return this; 118 | } 119 | 120 | public Mono connect(ClientTransport transport) { 121 | createSetupPayload(); 122 | return delegate.connect(transport); 123 | } 124 | 125 | public Mono connect(Supplier transportSupplier) { 126 | createSetupPayload(); 127 | return delegate.connect(transportSupplier); 128 | } 129 | 130 | public BrokerRSocketClient toRSocketClient(ClientTransport transport) { 131 | createSetupPayload(); 132 | return new BrokerRSocketClient(this, RSocketClient.from(delegate.connect(transport))); 133 | } 134 | 135 | private void createSetupPayload() { 136 | Tags setupTags = tags; 137 | if (setupTags == null) { 138 | setupTags = Tags.empty(); 139 | } 140 | 141 | Id setupRouteId = routeId; 142 | if (setupRouteId == null) { 143 | setupRouteId = Id.random(); 144 | } 145 | 146 | if (serviceName == null || serviceName.isEmpty()) { 147 | throw new IllegalArgumentException("serviceName must not be null or empty"); 148 | } 149 | 150 | ByteBuf routeSetup = RouteSetupFlyweight.encode(allocator, setupRouteId, serviceName, setupTags, 0); 151 | CompositeByteBuf setupMetadata = allocator.compositeBuffer(); 152 | CompositeMetadataCodec 153 | .encodeAndAddMetadata(setupMetadata, allocator, MimeTypes.BROKER_FRAME_MIME_TYPE.toString(), routeSetup); 154 | setupMetadatas.forEach(entry -> CompositeMetadataCodec 155 | .encodeAndAddMetadata(setupMetadata, allocator, entry.getT2(), entry.getT1())); 156 | 157 | Payload setupPayload = DefaultPayload.create(setupData, setupMetadata); 158 | 159 | delegate.setupPayload(setupPayload); 160 | } 161 | 162 | } 163 | -------------------------------------------------------------------------------- /rsocket-broker-client-spring/src/main/java/io/rsocket/broker/client/spring/BrokerClientAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 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 io.rsocket.broker.client.spring; 18 | 19 | import java.net.URI; 20 | 21 | import io.rsocket.RSocket; 22 | import io.rsocket.broker.common.spring.ClientTransportFactory; 23 | import io.rsocket.broker.common.spring.DefaultClientTransportFactory; 24 | import io.rsocket.broker.common.spring.MimeTypes; 25 | import io.rsocket.broker.frames.RouteSetup; 26 | import io.rsocket.transport.ClientTransport; 27 | import reactor.core.Disposable; 28 | import reactor.core.publisher.Sinks; 29 | import reactor.core.publisher.Sinks.One; 30 | 31 | import org.springframework.beans.factory.ObjectProvider; 32 | import org.springframework.boot.autoconfigure.AutoConfigureAfter; 33 | import org.springframework.boot.autoconfigure.AutoConfigureBefore; 34 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 35 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 36 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 37 | import org.springframework.boot.autoconfigure.rsocket.RSocketRequesterAutoConfiguration; 38 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 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.RSocketConnectorConfigurer; 43 | import org.springframework.messaging.rsocket.RSocketRequester; 44 | import org.springframework.messaging.rsocket.RSocketStrategies; 45 | import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler; 46 | import org.springframework.util.CollectionUtils; 47 | 48 | import static io.rsocket.broker.client.spring.BrokerClientProperties.CONFIG_PREFIX; 49 | 50 | 51 | @Configuration 52 | @EnableConfigurationProperties 53 | @ConditionalOnClass({RSocket.class, RSocketRequester.class}) 54 | @ConditionalOnProperty(name = CONFIG_PREFIX + ".enabled", matchIfMissing = true) 55 | @AutoConfigureAfter(BrokerClientRSocketStrategiesAutoConfiguration.class) 56 | @AutoConfigureBefore(RSocketRequesterAutoConfiguration.class) 57 | public class BrokerClientAutoConfiguration { 58 | 59 | //TODO: accept BrokerInfo to get updated broker nodes. Metadata Push 60 | 61 | @Bean 62 | public BrokerClientProperties brokerClientProperties() { 63 | return new BrokerClientProperties(); 64 | } 65 | 66 | @Bean 67 | @Scope("prototype") // TODO: I don't think prototype works here 68 | @ConditionalOnMissingBean 69 | public BrokerRSocketRequesterBuilder brokerRSocketRequesterBuilder( 70 | RSocketConnectorConfigurer configurer, RSocketStrategies strategies, 71 | BrokerClientProperties properties) { 72 | RouteSetup.Builder routeSetup = RouteSetup.from(properties.getRouteId(), 73 | properties.getServiceName()); 74 | properties.getTags().forEach((key, value) -> { 75 | if (key.getWellKnownKey() != null) { 76 | routeSetup.with(key.getWellKnownKey(), value); 77 | } 78 | else if (key.getKey() != null) { 79 | routeSetup.with(key.getKey(), value); 80 | } 81 | }); 82 | 83 | //MicrometerRSocketInterceptor interceptor = new MicrometerRSocketInterceptor( 84 | // meterRegistry, Tag.of("servicename", properties.getServiceName())); 85 | 86 | RSocketRequester.Builder builder = RSocketRequester.builder() 87 | .setupMetadata(routeSetup.build(), MimeTypes.BROKER_FRAME_MIME_TYPE) 88 | .dataMimeType(properties.getDataMimeType() != null ? properties.getDataMimeType() : null) 89 | .rsocketStrategies(strategies).rsocketConnector(configurer); 90 | 91 | //TODO: BrokerRequesterBuilderCustomizer 92 | 93 | return new BrokerRSocketRequesterBuilder(builder, properties, 94 | strategies.routeMatcher()); 95 | 96 | } 97 | 98 | @Bean 99 | @ConditionalOnMissingBean 100 | public RSocketConnectorConfigurer rSocketConnectorConfigurer(RSocketMessageHandler messageHandler) { 101 | return connector -> connector //.addRequesterPlugin(interceptor) 102 | .acceptor(messageHandler.responder()); 103 | } 104 | 105 | @Bean 106 | public BrokerMetadata brokerMetadata(BrokerClientProperties config) { 107 | return new BrokerMetadata(config); 108 | } 109 | 110 | @Bean 111 | public DefaultClientTransportFactory defaultClientTransportFactory() { 112 | return new DefaultClientTransportFactory(); 113 | } 114 | 115 | @Bean 116 | @ConditionalOnProperty(name = CONFIG_PREFIX + ".block", matchIfMissing = true) 117 | public ClientThreadManager clientThreadManager() { 118 | return new ClientThreadManager(); 119 | } 120 | 121 | @Bean 122 | @ConditionalOnProperty(name = CONFIG_PREFIX + ".auto-connect", matchIfMissing = true) 123 | public BrokerRSocketRequester brokerClientRSocketRequester(BrokerRSocketRequesterBuilder builder, 124 | BrokerClientProperties properties, ObjectProvider transportFactories, ClientThreadManager ignored) { 125 | if (CollectionUtils.isEmpty(properties.getBrokers())) { 126 | throw new IllegalStateException(CONFIG_PREFIX + ".brokers may not be empty"); 127 | } 128 | // TODO: use loadbalancer https://github.com/rsocket-broker/rsocket-broker-client/issues/8 129 | URI broker = properties.getBrokers().iterator().next(); 130 | 131 | ClientTransport clientTransport = transportFactories.orderedStream().filter(factory -> factory.supports(broker)).findFirst() 132 | .map(factory -> factory.create(broker)) 133 | .orElseThrow(() -> new IllegalStateException("Unknown transport " + properties)); 134 | 135 | // TODO: targets and strategy as beans 136 | //Flux> loadbalanceTargets = Flux.just(Collections.singletonList(LoadbalanceTarget.from("config", clientTransport))); 137 | //RoundRobinLoadbalanceStrategy loadbalanceStrategy = new RoundRobinLoadbalanceStrategy(); 138 | //BrokerRSocketRequester requester = builder.transports(loadbalanceTargets, loadbalanceStrategy); 139 | BrokerRSocketRequester requester = builder.transport(clientTransport); 140 | 141 | // if we don't subscribe, there won't be a connection to the broker. 142 | requester.rsocketClient().source().subscribe(); 143 | 144 | return requester; 145 | } 146 | 147 | private static class ClientThreadManager implements Disposable { 148 | 149 | private final One onClose = Sinks.one(); 150 | 151 | private ClientThreadManager() { 152 | Thread awaitThread = new Thread("broker-client-thread") { 153 | @Override 154 | public void run() { 155 | onClose.asMono().block(); 156 | } 157 | }; 158 | awaitThread.setContextClassLoader(getClass().getClassLoader()); 159 | awaitThread.setDaemon(false); 160 | awaitThread.start(); 161 | } 162 | 163 | @Override 164 | public void dispose() { 165 | onClose.emitEmpty((signalType, emitResult) -> false); 166 | } 167 | 168 | @Override 169 | public boolean isDisposed() { 170 | return false; 171 | } 172 | 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /rsocket-broker-client-spring/src/main/java/io/rsocket/broker/client/spring/BrokerRSocketRequester.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 io.rsocket.broker.client.spring; 18 | 19 | import java.util.Map; 20 | import java.util.function.Consumer; 21 | import java.util.regex.Matcher; 22 | import java.util.regex.Pattern; 23 | 24 | import io.rsocket.RSocket; 25 | import io.rsocket.core.RSocketClient; 26 | import io.rsocket.broker.common.Id; 27 | import io.rsocket.broker.common.Key; 28 | import io.rsocket.broker.common.WellKnownKey; 29 | import io.rsocket.broker.common.spring.MimeTypes; 30 | import io.rsocket.broker.frames.Address; 31 | import reactor.core.publisher.Flux; 32 | import reactor.core.publisher.Mono; 33 | 34 | import org.springframework.core.ParameterizedTypeReference; 35 | import org.springframework.lang.Nullable; 36 | import org.springframework.messaging.rsocket.RSocketRequester; 37 | import org.springframework.messaging.rsocket.RSocketStrategies; 38 | import org.springframework.util.Assert; 39 | import org.springframework.util.MimeType; 40 | import org.springframework.util.ObjectUtils; 41 | import org.springframework.util.RouteMatcher; 42 | 43 | public class BrokerRSocketRequester implements RSocketRequester { 44 | 45 | /** For route variable replacement. */ 46 | private static final Pattern NAMES_PATTERN = Pattern.compile("\\{([^/]+?)\\}"); 47 | 48 | private final RSocketRequester delegate; 49 | 50 | private final BrokerClientProperties properties; 51 | 52 | private final RouteMatcher routeMatcher; 53 | 54 | BrokerRSocketRequester(RSocketRequester delegate, BrokerClientProperties properties, 55 | RouteMatcher routeMatcher) { 56 | this.delegate = delegate; 57 | this.properties = properties; 58 | this.routeMatcher = routeMatcher; 59 | } 60 | 61 | @Override 62 | public RSocket rsocket() { 63 | return delegate.rsocket(); 64 | } 65 | 66 | @Override 67 | public RSocketClient rsocketClient() { 68 | return delegate.rsocketClient(); 69 | } 70 | 71 | @Override 72 | public MimeType dataMimeType() { 73 | return delegate.dataMimeType(); 74 | } 75 | 76 | @Override 77 | public RSocketStrategies strategies(){ 78 | return delegate.strategies(); 79 | } 80 | 81 | @Override 82 | public MimeType metadataMimeType() { 83 | return delegate.metadataMimeType(); 84 | } 85 | 86 | @Override 87 | public BrokerRequestSpec route(String route, Object... routeVars) { 88 | String expandedRoute = expand(route, routeVars); 89 | BrokerRequestSpec requestSpec = new BrokerRequestSpec(delegate.route(route, routeVars), properties 90 | .isFailIfMissingBrokerMetadata(), expandedRoute); 91 | 92 | // needs to be expanded with routeVars 93 | RouteMatcher.Route parsed = routeMatcher.parseRoute(expandedRoute); 94 | 95 | properties.getAddress().entrySet().stream() 96 | .filter(entry -> routeMatcher.match(entry.getKey(), parsed)).findFirst() 97 | .ifPresent(entry -> { 98 | Map tags = entry.getValue(); 99 | Address.Builder address = address(routeMatcher, parsed, 100 | properties.getRouteId(), entry.getKey(), tags); 101 | 102 | requestSpec.metadata(address.build(), 103 | MimeTypes.BROKER_FRAME_MIME_TYPE); 104 | }); 105 | 106 | return requestSpec; 107 | } 108 | 109 | /* for testing */ 110 | static Address.Builder address(RouteMatcher routeMatcher, 111 | RouteMatcher.Route route, Id originRouteId, String routeKey, 112 | Map tags) { 113 | Map extracted = routeMatcher.matchAndExtract(routeKey, route); 114 | Address.Builder address = Address.from(originRouteId); 115 | 116 | tags.forEach((tagKey, value) -> { 117 | if (tagKey.getWellKnownKey() != null) { 118 | address.with(tagKey.getWellKnownKey(), expand(value, extracted)); 119 | } 120 | else if (tagKey.getKey() != null) { 121 | address.with(tagKey.getKey(), expand(value, extracted)); 122 | } 123 | }); 124 | return address; 125 | } 126 | 127 | @Override 128 | public BrokerRequestSpec metadata(Object metadata, MimeType mimeType) { 129 | return new BrokerRequestSpec(delegate.metadata(metadata, mimeType), properties 130 | .isFailIfMissingBrokerMetadata(), "unknown route"); 131 | } 132 | 133 | /* for testing */ 134 | static String expand(String route, Object... routeVars) { 135 | if (ObjectUtils.isEmpty(routeVars)) { 136 | return route; 137 | } 138 | StringBuffer sb = new StringBuffer(); 139 | int index = 0; 140 | Matcher matcher = NAMES_PATTERN.matcher(route); 141 | while (matcher.find()) { 142 | Assert.isTrue(index < routeVars.length, 143 | () -> "No value for variable '" + matcher.group(1) + "'"); 144 | String value = routeVars[index].toString(); 145 | value = value.contains(".") ? value.replaceAll("\\.", "%2E") : value; 146 | matcher.appendReplacement(sb, value); 147 | index++; 148 | } 149 | return sb.toString(); 150 | } 151 | 152 | /* for testing */ 153 | static String expand(String template, Map vars) { 154 | if (template == null) { 155 | return null; 156 | } 157 | if (template.indexOf('{') == -1) { 158 | return template; 159 | } 160 | if (template.indexOf(':') != -1) { 161 | template = sanitizeSource(template); 162 | } 163 | 164 | if (ObjectUtils.isEmpty(vars)) { 165 | return template; 166 | } 167 | 168 | StringBuffer sb = new StringBuffer(); 169 | Matcher matcher = NAMES_PATTERN.matcher(template); 170 | while (matcher.find()) { 171 | String match = matcher.group(1); 172 | String varName = getVariableName(match); 173 | Object varValue = vars.get(varName); 174 | String formatted = getVariableValueAsString(varValue); 175 | matcher.appendReplacement(sb, formatted); 176 | } 177 | matcher.appendTail(sb); 178 | return sb.toString(); 179 | } 180 | 181 | /** 182 | * Remove nested "{}" such as in URI vars with regular expressions. 183 | */ 184 | private static String sanitizeSource(String source) { 185 | int level = 0; 186 | StringBuilder sb = new StringBuilder(); 187 | for (char c : source.toCharArray()) { 188 | if (c == '{') { 189 | level++; 190 | } 191 | if (c == '}') { 192 | level--; 193 | } 194 | if (level > 1 || (level == 1 && c == '}')) { 195 | continue; 196 | } 197 | sb.append(c); 198 | } 199 | return sb.toString(); 200 | } 201 | 202 | private static String getVariableName(String match) { 203 | int colonIdx = match.indexOf(':'); 204 | return (colonIdx != -1 ? match.substring(0, colonIdx) : match); 205 | } 206 | 207 | private static String getVariableValueAsString(@Nullable Object variableValue) { 208 | return (variableValue != null ? variableValue.toString() : ""); 209 | } 210 | 211 | public class BrokerRequestSpec implements RequestSpec { 212 | 213 | private final RequestSpec delegate; 214 | private final boolean failIfMissingBrokerMetadata; 215 | private final String route; 216 | private boolean hasBrokerMetadata; 217 | 218 | public BrokerRequestSpec(RequestSpec delegate, boolean failIfMissingBrokerMetadata, String route) { 219 | this.delegate = delegate; 220 | this.failIfMissingBrokerMetadata = failIfMissingBrokerMetadata; 221 | this.route = route; 222 | } 223 | 224 | public BrokerRequestSpec address(String serviceName) { 225 | // call metadata directly so hasBrokerMetadata updated 226 | metadata(spec -> { 227 | Address address = Address.from(properties.getRouteId()) 228 | .with(WellKnownKey.SERVICE_NAME, serviceName).build(); 229 | spec.metadata(address, MimeTypes.BROKER_FRAME_MIME_TYPE); 230 | }); 231 | return this; 232 | } 233 | 234 | public BrokerRequestSpec address(Consumer builderConsumer) { 235 | // call metadata directly so hasBrokerMetadata updated 236 | metadata(spec -> { 237 | Address.Builder builder = Address.from(properties.getRouteId()); 238 | builderConsumer.accept(builder); 239 | spec.metadata(builder.build(), MimeTypes.BROKER_FRAME_MIME_TYPE); 240 | }); 241 | return this; 242 | } 243 | 244 | @Override 245 | public BrokerRequestSpec metadata(Consumer> configurer) { 246 | // rather than call delegate, call configurer directly with this 247 | // so metadata(Object metadata, MimeType mimeType) from this 248 | // will be called instead to set hasBrokerMetadata properly. 249 | configurer.accept(this); 250 | return this; 251 | } 252 | 253 | @Override 254 | public Mono sendMetadata() { 255 | validateMetadataSet(); 256 | return delegate.sendMetadata(); 257 | } 258 | 259 | @Override 260 | public RetrieveSpec data(Object data) { 261 | delegate.data(data); 262 | return this; 263 | } 264 | 265 | @Override 266 | public RetrieveSpec data(Object producer, Class elementClass) { 267 | delegate.data(producer, elementClass); 268 | return this; 269 | } 270 | 271 | @Override 272 | public RetrieveSpec data(Object producer, ParameterizedTypeReference elementTypeRef) { 273 | delegate.data(producer, elementTypeRef); 274 | return this; 275 | } 276 | 277 | @Override 278 | public BrokerRequestSpec metadata(Object metadata, MimeType mimeType) { 279 | if (mimeType.equals(MimeTypes.BROKER_FRAME_MIME_TYPE)) { 280 | hasBrokerMetadata = true; 281 | } 282 | delegate.metadata(metadata, mimeType); 283 | return this; 284 | } 285 | 286 | @Override 287 | public Mono send() { 288 | validateMetadataSet(); 289 | return delegate.send(); 290 | } 291 | 292 | @Override 293 | public Mono retrieveMono(Class dataType) { 294 | validateMetadataSet(); 295 | return delegate.retrieveMono(dataType); 296 | } 297 | 298 | @Override 299 | public Mono retrieveMono(ParameterizedTypeReference dataTypeRef) { 300 | validateMetadataSet(); 301 | return delegate.retrieveMono(dataTypeRef); 302 | } 303 | 304 | @Override 305 | public Flux retrieveFlux(Class dataType) { 306 | validateMetadataSet(); 307 | return delegate.retrieveFlux(dataType); 308 | } 309 | 310 | @Override 311 | public Flux retrieveFlux(ParameterizedTypeReference dataTypeRef) { 312 | validateMetadataSet(); 313 | return delegate.retrieveFlux(dataTypeRef); 314 | } 315 | 316 | private void validateMetadataSet() { 317 | if (failIfMissingBrokerMetadata && !hasBrokerMetadata) { 318 | throw new IllegalArgumentException(MimeTypes.BROKER_FRAME_MIME_TYPE + " metadata was not set for route: " + route); 319 | } 320 | } 321 | } 322 | 323 | } 324 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | https://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | https://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | --------------------------------------------------------------------------------