├── .all-contributorsrc ├── .github ├── dependabot.yml ├── project.yml └── workflows │ ├── build.yml │ ├── pre-release.yml │ ├── quarkus-snapshot.yaml │ ├── release-perform.yml │ └── release-prepare.yml ├── .gitignore ├── LICENSE ├── README.md ├── deployment ├── pom.xml └── src │ └── main │ └── java │ └── io │ └── quarkiverse │ └── rsocket │ └── deployment │ ├── RSocketBuildItem.java │ ├── RoutedRsocketHandlerBuildItem.java │ └── RsocketProcessor.java ├── docs ├── antora.yml └── modules │ └── ROOT │ ├── nav.adoc │ └── pages │ ├── config.adoc │ └── index.adoc ├── integration-tests ├── pom.xml └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── quarkiverse │ │ │ └── rsocket │ │ │ └── test │ │ │ └── FooFnf.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── io │ └── quarkiverse │ └── rsocket │ └── test │ ├── Car.java │ └── RSocketTestCase.java ├── pom.xml └── runtime ├── pom.xml └── src └── main ├── java └── io │ └── quarkiverse │ └── rsocket │ └── runtime │ ├── CborEncoder.java │ ├── Encoder.java │ ├── EncoderManager.java │ ├── FireAndForgetHandler.java │ ├── JsonEncoder.java │ ├── NettyRSocketServer.java │ ├── ProxyEncoder.java │ ├── QuarkusRSocketClient.java │ ├── QuarkusRSocketConnector.java │ ├── RSocketConfig.java │ ├── RSocketRecorder.java │ ├── RequestChannelHandler.java │ ├── RequestResponseHandler.java │ ├── RequestStreamHandler.java │ └── RoutedRsocket.java └── resources └── META-INF └── quarkus-extension.yaml /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "contributors": [ 8 | { 9 | "login": "dufoli", 10 | "name": "olivier dufour", 11 | "avatar_url": "https://avatars.githubusercontent.com/u/202057?v=4", 12 | "profile": "https://github.com/dufoli", 13 | "contributions": [ 14 | "code", 15 | "maintenance" 16 | ] 17 | } 18 | ], 19 | "contributorsPerLine": 7, 20 | "projectName": "quarkus-rsocket", 21 | "projectOwner": "quarkiverse", 22 | "repoType": "github", 23 | "repoHost": "https://github.com", 24 | "skipCi": true 25 | } 26 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "maven" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | ignore: 13 | - dependency-name: "org.apache.maven.plugins:maven-compiler-plugin" 14 | -------------------------------------------------------------------------------- /.github/project.yml: -------------------------------------------------------------------------------- 1 | release: 2 | current-version: 0 3 | next-version: 0.0.1-SNAPSHOT 4 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - "master" 7 | paths-ignore: 8 | - '.gitignore' 9 | - 'CODEOWNERS' 10 | - 'LICENSE' 11 | - '*.md' 12 | - '*.adoc' 13 | - '*.txt' 14 | - '.all-contributorsrc' 15 | pull_request: 16 | paths-ignore: 17 | - '.gitignore' 18 | - 'CODEOWNERS' 19 | - 'LICENSE' 20 | - '*.md' 21 | - '*.adoc' 22 | - '*.txt' 23 | - '.all-contributorsrc' 24 | 25 | jobs: 26 | build: 27 | 28 | runs-on: ubuntu-latest 29 | 30 | steps: 31 | - uses: actions/checkout@v2 32 | 33 | - name: Set up JDK 11 34 | uses: actions/setup-java@v1 35 | with: 36 | java-version: 11 37 | 38 | - name: Get Date 39 | id: get-date 40 | run: | 41 | echo "::set-output name=date::$(/bin/date -u "+%Y-%m")" 42 | shell: bash 43 | - name: Cache Maven Repository 44 | id: cache-maven 45 | uses: actions/cache@v2 46 | with: 47 | path: ~/.m2/repository 48 | # refresh cache every month to avoid unlimited growth 49 | key: maven-repo-${{ runner.os }}-${{ steps.get-date.outputs.date }} 50 | 51 | - name: Build with Maven 52 | run: mvn -B formatter:validate verify --file pom.xml 53 | 54 | -------------------------------------------------------------------------------- /.github/workflows/pre-release.yml: -------------------------------------------------------------------------------- 1 | name: Quarkiverse Pre Release 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - '.github/project.yml' 7 | 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.ref }} 10 | cancel-in-progress: true 11 | 12 | jobs: 13 | pre-release: 14 | name: Pre-Release 15 | uses: quarkiverse/.github/.github/workflows/pre-release.yml@main 16 | secrets: inherit 17 | -------------------------------------------------------------------------------- /.github/workflows/quarkus-snapshot.yaml: -------------------------------------------------------------------------------- 1 | name: "Quarkus ecosystem CI" 2 | on: 3 | workflow_dispatch: 4 | watch: 5 | types: [started] 6 | 7 | # For this CI to work, ECOSYSTEM_CI_TOKEN needs to contain a GitHub with rights to close the Quarkus issue that the user/bot has opened, 8 | # while 'ECOSYSTEM_CI_REPO_PATH' needs to be set to the corresponding path in the 'quarkusio/quarkus-ecosystem-ci' repository 9 | 10 | env: 11 | ECOSYSTEM_CI_REPO: quarkusio/quarkus-ecosystem-ci 12 | ECOSYSTEM_CI_REPO_FILE: context.yaml 13 | JAVA_VERSION: 17 14 | 15 | ######################### 16 | # Repo specific setting # 17 | ######################### 18 | 19 | ECOSYSTEM_CI_REPO_PATH: quarkus-rsocket 20 | 21 | jobs: 22 | build: 23 | name: "Build against latest Quarkus snapshot" 24 | runs-on: ubuntu-latest 25 | # Allow to manually launch the ecosystem CI in addition to the bots 26 | if: github.actor == 'quarkusbot' || github.actor == 'quarkiversebot' || github.actor == '' 27 | 28 | steps: 29 | - name: Install yq 30 | run: sudo add-apt-repository ppa:rmescandon/yq && sudo apt update && sudo apt install yq -y 31 | 32 | - name: Set up Java 33 | uses: actions/setup-java@v1 34 | with: 35 | java-version: ${{ env.JAVA_VERSION }} 36 | 37 | - name: Checkout repo 38 | uses: actions/checkout@v2 39 | with: 40 | path: current-repo 41 | 42 | - name: Checkout Ecosystem 43 | uses: actions/checkout@v2 44 | with: 45 | repository: ${{ env.ECOSYSTEM_CI_REPO }} 46 | path: ecosystem-ci 47 | 48 | - name: Setup and Run Tests 49 | run: ./ecosystem-ci/setup-and-test 50 | env: 51 | ECOSYSTEM_CI_TOKEN: ${{ secrets.ECOSYSTEM_CI_TOKEN }} 52 | -------------------------------------------------------------------------------- /.github/workflows/release-perform.yml: -------------------------------------------------------------------------------- 1 | name: Quarkiverse Perform Release 2 | run-name: Perform ${{github.event.inputs.tag || github.ref_name}} Release 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | workflow_dispatch: 8 | inputs: 9 | tag: 10 | description: 'Tag to release' 11 | required: true 12 | 13 | permissions: 14 | attestations: write 15 | id-token: write 16 | contents: read 17 | 18 | concurrency: 19 | group: ${{ github.workflow }}-${{ github.ref }} 20 | cancel-in-progress: true 21 | 22 | jobs: 23 | perform-release: 24 | name: Perform Release 25 | uses: quarkiverse/.github/.github/workflows/perform-release.yml@main 26 | secrets: inherit 27 | with: 28 | version: ${{github.event.inputs.tag || github.ref_name}} 29 | -------------------------------------------------------------------------------- /.github/workflows/release-prepare.yml: -------------------------------------------------------------------------------- 1 | name: Quarkiverse Prepare Release 2 | 3 | on: 4 | pull_request: 5 | types: [ closed ] 6 | paths: 7 | - '.github/project.yml' 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | prepare-release: 15 | name: Prepare Release 16 | if: ${{ github.event.pull_request.merged == true}} 17 | uses: quarkiverse/.github/.github/workflows/prepare-release.yml@main 18 | secrets: inherit 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | # Eclipse 26 | .project 27 | .classpath 28 | .settings/ 29 | bin/ 30 | 31 | # IntelliJ 32 | .idea 33 | *.ipr 34 | *.iml 35 | *.iws 36 | 37 | # NetBeans 38 | nb-configuration.xml 39 | 40 | # Visual Studio Code 41 | .vscode 42 | .factorypath 43 | 44 | # OSX 45 | .DS_Store 46 | 47 | # Vim 48 | *.swp 49 | *.swo 50 | 51 | # patch 52 | *.orig 53 | *.rej 54 | 55 | # Gradle 56 | .gradle/ 57 | build/ 58 | 59 | # Maven 60 | target/ 61 | pom.xml.tag 62 | pom.xml.releaseBackup 63 | pom.xml.versionsBackup 64 | release.properties 65 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Quarkus - Rsocket 2 | 3 | [![All Contributors](https://img.shields.io/badge/all_contributors-1-orange.svg?style=flat-square)](#contributors-) 4 | 5 | 6 | ## Welcome to Quarkiverse! 7 | 8 | Congratulations and thank you for creating a new Quarkus extension project in Quarkiverse! 9 | 10 | Feel free to replace this content with the proper description of your new project and necessary instructions how to use and contribute to it. 11 | 12 | You can find the basic info, Quarkiverse policies and conventions in [the Quarkiverse wiki](https://github.com/quarkiverse/quarkiverse/wiki). 13 | 14 | Other useful articles related to Quarkus extension development can be found under the [Writing Extensions](https://quarkus.io/guides/#writing-extensions) guide category on the [Quarkus.io](http://quarkus.io) website. 15 | 16 | Thanks again, good luck and have fun! 17 | 18 | ## Documentation 19 | 20 | The documentation for this extension should be maintained as part of this repository and it is stored in the `docs/` directory. 21 | 22 | The layout should follow the [Antora's Standard File and Directory Set](https://docs.antora.org/antora/2.3/standard-directories/). 23 | 24 | Once the docs are ready to be published, please open a PR including this repository in the [Quarkiverse Docs Antora playbook](https://github.com/quarkiverse/quarkiverse-docs/blob/master/antora-playbook.yml#L7). See an example [here](https://github.com/quarkiverse/quarkiverse-docs/pull/1). 25 | ## Contributors ✨ 26 | 27 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |

olivier dufour

💻 🚧
37 | 38 | 39 | 40 | 41 | 42 | 43 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 44 | -------------------------------------------------------------------------------- /deployment/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | io.quarkiverse.rsocket 5 | quarkus-rsocket-parent 6 | 0.0.1-SNAPSHOT 7 | ../ 8 | 9 | 4.0.0 10 | 11 | quarkus-rsocket-deployment 12 | Quarkus - rsocket - Deployment 13 | rsocket Support for native image 14 | 15 | 16 | io.quarkiverse.rsocket 17 | quarkus-rsocket 18 | 19 | 20 | io.quarkus 21 | quarkus-core-deployment 22 | 23 | 24 | io.quarkus 25 | quarkus-arc-deployment 26 | 27 | 28 | org.graalvm.nativeimage 29 | svm 30 | provided 31 | 32 | 33 | io.quarkus 34 | quarkus-jackson-deployment 35 | 36 | 37 | 38 | 39 | 40 | 41 | maven-compiler-plugin 42 | 43 | 44 | 45 | io.quarkus 46 | quarkus-extension-processor 47 | ${quarkus.version} 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /deployment/src/main/java/io/quarkiverse/rsocket/deployment/RSocketBuildItem.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.rsocket.deployment; 2 | 3 | import io.quarkus.builder.item.SimpleBuildItem; 4 | 5 | public final class RSocketBuildItem extends SimpleBuildItem { 6 | private final String rSocketClassName; 7 | 8 | public RSocketBuildItem(String rSocketClassName) { 9 | this.rSocketClassName = rSocketClassName; 10 | } 11 | 12 | public String getRSocketClassName() { 13 | return rSocketClassName; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /deployment/src/main/java/io/quarkiverse/rsocket/deployment/RoutedRsocketHandlerBuildItem.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.rsocket.deployment; 2 | 3 | import io.quarkus.builder.item.MultiBuildItem; 4 | import io.rsocket.frame.FrameType; 5 | 6 | public final class RoutedRsocketHandlerBuildItem extends MultiBuildItem { 7 | private final String path; 8 | private final String handlerClassName; 9 | private final FrameType frameType; 10 | 11 | public RoutedRsocketHandlerBuildItem(String path, String handlerClassName, FrameType frameType) { 12 | this.path = path; 13 | this.handlerClassName = handlerClassName; 14 | this.frameType = frameType; 15 | } 16 | 17 | public String getPath() { 18 | return path; 19 | } 20 | 21 | public String getHandler() { 22 | return handlerClassName; 23 | } 24 | 25 | public FrameType getFrameType() { 26 | return frameType; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /deployment/src/main/java/io/quarkiverse/rsocket/deployment/RsocketProcessor.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.rsocket.deployment; 2 | 3 | import java.util.List; 4 | 5 | import org.jboss.jandex.AnnotationInstance; 6 | import org.jboss.jandex.ClassInfo; 7 | import org.jboss.jandex.DotName; 8 | import org.jboss.jandex.IndexView; 9 | import org.jboss.logging.Logger; 10 | 11 | import io.quarkiverse.rsocket.runtime.RSocketConfig; 12 | import io.quarkiverse.rsocket.runtime.RSocketRecorder; 13 | import io.quarkiverse.rsocket.runtime.RoutedRsocket; 14 | import io.quarkus.deployment.annotations.BuildProducer; 15 | import io.quarkus.deployment.annotations.BuildStep; 16 | import io.quarkus.deployment.annotations.ExecutionTime; 17 | import io.quarkus.deployment.annotations.Record; 18 | import io.quarkus.deployment.builditem.CombinedIndexBuildItem; 19 | import io.quarkus.deployment.builditem.FeatureBuildItem; 20 | import io.quarkus.runtime.RuntimeValue; 21 | import io.rsocket.frame.FrameType; 22 | 23 | public class RsocketProcessor { 24 | 25 | public static final DotName RSOCKET = DotName.createSimple("io.rsocket.RSocket"); 26 | public static final DotName FIRE_AND_FORGET_HANDLER = DotName 27 | .createSimple("io.quarkiverse.rsocket.runtime.FireAndForgetHandler"); 28 | public static final DotName REQUEST_CHANNEL_HANDLER = DotName 29 | .createSimple("io.quarkiverse.rsocket.runtime.RequestChannelHandler"); 30 | public static final DotName REQUEST_RESPONSE_HANDLER = DotName 31 | .createSimple("io.quarkiverse.rsocket.runtime.RequestResponseHandler"); 32 | public static final DotName REQUEST_STREAM_HANDLER = DotName 33 | .createSimple("io.quarkiverse.rsocket.runtime.RequestStreamHandler"); 34 | public static final DotName PATH = DotName.createSimple("javax.ws.rs.Path"); 35 | private static final Logger LOGGER = Logger.getLogger(RsocketProcessor.class); 36 | private static final String FEATURE = "rsocket"; 37 | 38 | @BuildStep 39 | FeatureBuildItem feature() { 40 | return new FeatureBuildItem(FEATURE); 41 | } 42 | 43 | @BuildStep 44 | void configure(CombinedIndexBuildItem combinedIndexBuildItem, 45 | BuildProducer rsockets, 46 | BuildProducer handlers) { 47 | IndexView index = combinedIndexBuildItem.getIndex(); 48 | for (ClassInfo classInfo : index.getAllKnownImplementors(RSOCKET)) { 49 | rsockets.produce(new RSocketBuildItem(classInfo.name().toString())); 50 | LOGGER.info("add rsocket " + classInfo.name().toString()); 51 | //quick hack to only load first rsocket and skip route if rsocket 52 | return; 53 | } 54 | for (ClassInfo classInfo : index.getAllKnownImplementors(FIRE_AND_FORGET_HANDLER)) { 55 | AnnotationInstance pathAnnotationInstance = classInfo.classAnnotation(PATH); 56 | String path = pathAnnotationInstance.value().asString(); 57 | handlers.produce(new RoutedRsocketHandlerBuildItem(path, classInfo.name().toString(), FrameType.REQUEST_FNF)); 58 | LOGGER.info("add handler " + classInfo.name().toString() + " on route :" + path); 59 | } 60 | for (ClassInfo classInfo : index.getAllKnownImplementors(REQUEST_CHANNEL_HANDLER)) { 61 | AnnotationInstance pathAnnotationInstance = classInfo.classAnnotation(PATH); 62 | String path = pathAnnotationInstance.value().asString(); 63 | handlers.produce(new RoutedRsocketHandlerBuildItem(path, classInfo.name().toString(), FrameType.REQUEST_CHANNEL)); 64 | LOGGER.info("add handler " + classInfo.name().toString() + " on route :" + path); 65 | } 66 | for (ClassInfo classInfo : index.getAllKnownImplementors(REQUEST_RESPONSE_HANDLER)) { 67 | AnnotationInstance pathAnnotationInstance = classInfo.classAnnotation(PATH); 68 | String path = pathAnnotationInstance.value().asString(); 69 | handlers.produce(new RoutedRsocketHandlerBuildItem(path, classInfo.name().toString(), FrameType.REQUEST_RESPONSE)); 70 | LOGGER.info("add handler " + classInfo.name().toString() + " on route :" + path); 71 | } 72 | for (ClassInfo classInfo : index.getAllKnownImplementors(REQUEST_STREAM_HANDLER)) { 73 | AnnotationInstance pathAnnotationInstance = classInfo.classAnnotation(PATH); 74 | String path = pathAnnotationInstance.value().asString(); 75 | handlers.produce(new RoutedRsocketHandlerBuildItem(path, classInfo.name().toString(), FrameType.REQUEST_STREAM)); 76 | LOGGER.info("add handler " + classInfo.name().toString() + " on route :" + path); 77 | } 78 | } 79 | 80 | @BuildStep 81 | @Record(ExecutionTime.RUNTIME_INIT) 82 | void startServer(RSocketRecorder recorder, 83 | RSocketConfig config, 84 | RSocketBuildItem rsockets, 85 | List handlers) { 86 | LOGGER.info("start rsocket server "); 87 | if (rsockets != null) { 88 | recorder.initServer(config, rsockets.getRSocketClassName()); 89 | LOGGER.info("rsocket server started on " + rsockets.getRSocketClassName()); 90 | } else if (handlers.size() > 0) { 91 | RuntimeValue builder = recorder.buildRouter(); 92 | for (RoutedRsocketHandlerBuildItem handler : handlers) { 93 | recorder.addHandler(builder, handler.getPath(), handler.getHandler(), handler.getFrameType()); 94 | } 95 | recorder.initServer(config, builder); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /docs/antora.yml: -------------------------------------------------------------------------------- 1 | name: quarkus-rsocket 2 | title: Rsocket 3 | version: dev 4 | nav: 5 | - modules/ROOT/nav.adoc 6 | -------------------------------------------------------------------------------- /docs/modules/ROOT/nav.adoc: -------------------------------------------------------------------------------- 1 | * xref:index.adoc[Quarkus - Rsocket] 2 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/config.adoc: -------------------------------------------------------------------------------- 1 | // 2 | // This content is generated using mvn compile and copied manually to here 3 | // 4 | [.configuration-legend] 5 | icon:lock[title=Fixed at build time] Configuration property fixed at build time - All other configuration properties are overridable at runtime 6 | [.configuration-reference.searchable, cols="80,.^10,.^10"] 7 | |=== 8 | 9 | h|[[quarkus-freemarker_configuration]]link:#quarkus-freemarker_configuration[Configuration property] 10 | 11 | h|Type 12 | h|Default 13 | 14 | a|icon:lock[title=Fixed at build time] [[quarkus-freemarker_quarkus.freemarker.resource-paths]]`link:#quarkus-freemarker_quarkus.freemarker.resource-paths[quarkus.freemarker.resource-paths]` 15 | 16 | [.description] 17 | -- 18 | Comma-separated list of absolute resource paths to scan recursively for templates. All tree folder from 'resource-paths' will be added as a resource. Unprefixed locations or locations starting with classpath will be processed in the same way. 19 | --|list of string 20 | |`freemarker/templates` 21 | 22 | 23 | a| [[quarkus-freemarker_quarkus.freemarker.file-paths]]`link:#quarkus-freemarker_quarkus.freemarker.file-paths[quarkus.freemarker.file-paths]` 24 | 25 | [.description] 26 | -- 27 | Comma-separated of file system paths where freemarker templates are located 28 | --|list of string 29 | | 30 | 31 | 32 | a| [[quarkus-freemarker_quarkus.freemarker.default-encoding]]`link:#quarkus-freemarker_quarkus.freemarker.default-encoding[quarkus.freemarker.default-encoding]` 33 | 34 | [.description] 35 | -- 36 | Set the preferred charset template files are stored in. 37 | --|string 38 | | 39 | 40 | 41 | a| [[quarkus-freemarker_quarkus.freemarker.template-exception-handler]]`link:#quarkus-freemarker_quarkus.freemarker.template-exception-handler[quarkus.freemarker.template-exception-handler]` 42 | 43 | [.description] 44 | -- 45 | Sets how errors will appear. rethrow, debug, html-debug, ignore. 46 | --|string 47 | | 48 | 49 | 50 | a| [[quarkus-freemarker_quarkus.freemarker.log-template-exceptions]]`link:#quarkus-freemarker_quarkus.freemarker.log-template-exceptions[quarkus.freemarker.log-template-exceptions]` 51 | 52 | [.description] 53 | -- 54 | If false, don't log exceptions inside FreeMarker that it will be thrown at you anyway. 55 | --|boolean 56 | | 57 | 58 | 59 | a| [[quarkus-freemarker_quarkus.freemarker.wrap-unchecked-exceptions]]`link:#quarkus-freemarker_quarkus.freemarker.wrap-unchecked-exceptions[quarkus.freemarker.wrap-unchecked-exceptions]` 60 | 61 | [.description] 62 | -- 63 | Wrap unchecked exceptions thrown during template processing into TemplateException-s. 64 | --|boolean 65 | | 66 | 67 | 68 | a| [[quarkus-freemarker_quarkus.freemarker.fallback-on-null-loop-variable]]`link:#quarkus-freemarker_quarkus.freemarker.fallback-on-null-loop-variable[quarkus.freemarker.fallback-on-null-loop-variable]` 69 | 70 | [.description] 71 | -- 72 | If false, do not fall back to higher scopes when reading a null loop variable. 73 | --|boolean 74 | | 75 | 76 | 77 | a| [[quarkus-freemarker_quarkus.freemarker.boolean-format]]`link:#quarkus-freemarker_quarkus.freemarker.boolean-format[quarkus.freemarker.boolean-format]` 78 | 79 | [.description] 80 | -- 81 | The string value for the boolean `true` and `false` values, usually intended for human consumption (not for a computer language), separated with comma. 82 | --|string 83 | | 84 | 85 | 86 | a| [[quarkus-freemarker_quarkus.freemarker.number-format]]`link:#quarkus-freemarker_quarkus.freemarker.number-format[quarkus.freemarker.number-format]` 87 | 88 | [.description] 89 | -- 90 | Sets the default number format used to convert numbers to strings. 91 | --|string 92 | | 93 | 94 | 95 | a| [[quarkus-freemarker_quarkus.freemarker.object-wrapper-expose-fields]]`link:#quarkus-freemarker_quarkus.freemarker.object-wrapper-expose-fields[quarkus.freemarker.object-wrapper-expose-fields]` 96 | 97 | [.description] 98 | -- 99 | If true, the object wrapper will be configured to expose fields. 100 | --|boolean 101 | | 102 | 103 | 104 | a|icon:lock[title=Fixed at build time] [[quarkus-freemarker_quarkus.freemarker.directive-directive]]`link:#quarkus-freemarker_quarkus.freemarker.directive-directive[quarkus.freemarker.directive]` 105 | 106 | [.description] 107 | -- 108 | List of directives to register with format name=classname 109 | --|`Map` 110 | | 111 | 112 | |=== 113 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/index.adoc: -------------------------------------------------------------------------------- 1 | = Quarkus - Rsocket 2 | :extension-status: preview 3 | 4 | Describe what the extension does here. 5 | 6 | == Installation 7 | 8 | If you want to use this extension, you need to add the `io.quarkiverse.rsocket:quarkus-rsocket` extension first. 9 | In your `pom.xml` file, add: 10 | 11 | [source,xml] 12 | ---- 13 | 14 | io.quarkiverse.rsocket 15 | quarkus-rsocket 16 | 17 | ---- 18 | 19 | [[extension-configuration-reference]] 20 | == Extension Configuration Reference 21 | 22 | include::config.adoc[leveloffset=+1, opts=optional] 23 | -------------------------------------------------------------------------------- /integration-tests/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | io.quarkiverse.rsocket 5 | quarkus-rsocket-parent 6 | 0.0.1-SNAPSHOT 7 | ../ 8 | 9 | 4.0.0 10 | 11 | quarkus-rsocket-integration-tests 12 | Quarkus - rsocket - Integration Tests 13 | rsocket Integration Tests 14 | 15 | true 16 | true 17 | ${skipTests} 18 | ${skipTests} 19 | 20 | 21 | 22 | 23 | io.quarkiverse.rsocket 24 | quarkus-rsocket 25 | ${project.version} 26 | 27 | 28 | io.quarkus 29 | quarkus-junit5 30 | test 31 | 32 | 33 | javax.ws.rs 34 | javax.ws.rs-api 35 | 2.1.1 36 | 37 | 38 | 39 | io.quarkiverse.rsocket 40 | quarkus-rsocket-deployment 41 | ${project.version} 42 | pom 43 | test 44 | 45 | 46 | * 47 | * 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | io.quarkus 57 | quarkus-maven-plugin 58 | 59 | 60 | 61 | build 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | native 72 | 73 | 74 | native 75 | 76 | 77 | 78 | 79 | 80 | org.apache.maven.plugins 81 | maven-surefire-plugin 82 | 83 | ${native.surefire.skip} 84 | 85 | org.jboss.logmanager.LogManager 86 | 87 | 88 | 89 | 90 | maven-failsafe-plugin 91 | 92 | 93 | 94 | integration-test 95 | verify 96 | 97 | 98 | 99 | 100 | ${project.build.directory}/${project.build.finalName}-runner 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | native 111 | true 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /integration-tests/src/main/java/io/quarkiverse/rsocket/test/FooFnf.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.rsocket.test; 2 | 3 | import javax.ws.rs.Path; 4 | 5 | import io.quarkiverse.rsocket.runtime.RequestResponseHandler; 6 | import io.rsocket.Payload; 7 | import reactor.core.publisher.Mono; 8 | 9 | @Path("/foo") 10 | public class FooFnf implements RequestResponseHandler { 11 | @Override 12 | public Mono handle(Payload payload) { 13 | try { 14 | return Mono.just(payload); // reflect the payload back to the sender 15 | } catch (Exception x) { 16 | return Mono.error(x); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /integration-tests/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | quarkus.rsocket.port=7000 2 | quarkus.rsocket.host=127.0.0.1 3 | -------------------------------------------------------------------------------- /integration-tests/src/test/java/io/quarkiverse/rsocket/test/Car.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.rsocket.test; 2 | 3 | public class Car { 4 | private String name; 5 | 6 | public Car() { 7 | } 8 | 9 | public String getName() { 10 | return name; 11 | } 12 | 13 | public void setName(String name) { 14 | this.name = name; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /integration-tests/src/test/java/io/quarkiverse/rsocket/test/RSocketTestCase.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.rsocket.test; 2 | 3 | import java.nio.ByteBuffer; 4 | import java.nio.CharBuffer; 5 | import java.nio.charset.StandardCharsets; 6 | import java.util.Collections; 7 | 8 | import org.jboss.logging.Logger; 9 | import org.junit.jupiter.api.Assertions; 10 | import org.junit.jupiter.api.Test; 11 | 12 | import com.google.common.base.Charsets; 13 | 14 | import io.netty.buffer.ByteBuf; 15 | import io.netty.buffer.ByteBufAllocator; 16 | import io.netty.buffer.CompositeByteBuf; 17 | import io.quarkiverse.rsocket.runtime.JsonEncoder; 18 | import io.quarkus.test.junit.QuarkusTest; 19 | import io.rsocket.Payload; 20 | import io.rsocket.core.RSocketConnector; 21 | import io.rsocket.metadata.CompositeMetadataCodec; 22 | import io.rsocket.metadata.RoutingMetadata; 23 | import io.rsocket.metadata.TaggingMetadataCodec; 24 | import io.rsocket.metadata.WellKnownMimeType; 25 | import io.rsocket.transport.netty.client.TcpClientTransport; 26 | import io.rsocket.util.DefaultPayload; 27 | 28 | @QuarkusTest 29 | public class RSocketTestCase { 30 | private static final Logger LOGGER = Logger.getLogger(RSocketTestCase.class); 31 | 32 | @Test 33 | public void testRSocketRoute() { 34 | String hello = "Hello RSocket"; 35 | CompositeByteBuf metadata = ByteBufAllocator.DEFAULT.compositeBuffer(); 36 | RoutingMetadata routingMetadata = TaggingMetadataCodec.createRoutingMetadata(ByteBufAllocator.DEFAULT, 37 | Collections.singletonList("/foo")); 38 | CompositeMetadataCodec.encodeAndAddMetadata(metadata, 39 | ByteBufAllocator.DEFAULT, 40 | WellKnownMimeType.MESSAGE_RSOCKET_ROUTING, 41 | routingMetadata.getContent()); 42 | ByteBuf data = ByteBufAllocator.DEFAULT.buffer().writeBytes(hello.getBytes(Charsets.UTF_8)); 43 | ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(CharBuffer.wrap(hello)); 44 | Payload rspPayload = RSocketConnector.create() 45 | .metadataMimeType(WellKnownMimeType.MESSAGE_RSOCKET_COMPOSITE_METADATA.getString()) 46 | //.payloadDecoder(PayloadDecoder.ZERO_COPY) 47 | .connect(TcpClientTransport.create("127.0.0.1", 7000)) 48 | .block() 49 | .requestResponse(DefaultPayload.create(byteBuffer, metadata.nioBuffer())) 50 | .block(); 51 | metadata.release(); 52 | Assertions.assertEquals(hello, rspPayload.getDataUtf8(), "failed to get response"); 53 | } 54 | 55 | @Test 56 | public void testRSocketEncoder() { 57 | 58 | CompositeByteBuf metadata = ByteBufAllocator.DEFAULT.compositeBuffer(); 59 | RoutingMetadata routingMetadata = TaggingMetadataCodec.createRoutingMetadata(ByteBufAllocator.DEFAULT, 60 | Collections.singletonList("/foo")); 61 | CompositeMetadataCodec.encodeAndAddMetadata(metadata, 62 | ByteBufAllocator.DEFAULT, 63 | WellKnownMimeType.MESSAGE_RSOCKET_ROUTING, 64 | routingMetadata.getContent()); 65 | Car c = new Car(); 66 | c.setName("batmobile"); 67 | JsonEncoder encoder = new JsonEncoder(); 68 | Payload payload = encoder.encode(c); 69 | Payload rspPayload = RSocketConnector.create() 70 | .metadataMimeType(WellKnownMimeType.MESSAGE_RSOCKET_COMPOSITE_METADATA.getString()) 71 | //.payloadDecoder(PayloadDecoder.ZERO_COPY) 72 | .connect(TcpClientTransport.create("127.0.0.1", 7000)) 73 | .block() 74 | .requestResponse(DefaultPayload.create(payload.getData(), metadata.nioBuffer())) 75 | .block(); 76 | metadata.release(); 77 | Assertions.assertEquals("{\"name\":\"batmobile\"}", rspPayload.getDataUtf8(), "failed to get response"); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | io.quarkiverse 7 | quarkiverse-parent 8 | 7 9 | 10 | io.quarkiverse.rsocket 11 | quarkus-rsocket-parent 12 | 0.0.1-SNAPSHOT 13 | pom 14 | 15 | 16 | UTF-8 17 | UTF-8 18 | 11 19 | 11 20 | true 21 | 2.2.0.Final 22 | 3.8.1 23 | 1.1.0 24 | 25 | 26 | 27 | deployment 28 | runtime 29 | 30 | 31 | 32 | 33 | 34 | io.quarkus 35 | quarkus-bom 36 | ${quarkus.version} 37 | pom 38 | import 39 | 40 | 41 | io.quarkiverse.rsocket 42 | quarkus-rsocket 43 | ${project.version} 44 | 45 | 46 | io.quarkiverse.rsocket 47 | quarkus-rsocket-deployment 48 | ${project.version} 49 | 50 | 51 | io.rsocket 52 | rsocket-core 53 | ${rsocket.version} 54 | 55 | 56 | io.rsocket 57 | rsocket-transport-netty 58 | ${rsocket.version} 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | org.apache.maven.plugins 67 | maven-compiler-plugin 68 | ${compiler.plugin.version} 69 | 70 | 71 | io.quarkus 72 | quarkus-maven-plugin 73 | ${quarkus.version} 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | it 82 | 83 | 84 | performRelease 85 | !true 86 | 87 | 88 | 89 | integration-tests 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /runtime/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | io.quarkiverse.rsocket 5 | quarkus-rsocket-parent 6 | 0.0.1-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | quarkus-rsocket 11 | Quarkus - rsocket - Runtime 12 | RSocket Support for native image 13 | 14 | 15 | io.quarkus 16 | quarkus-core 17 | 18 | 19 | io.quarkus 20 | quarkus-arc 21 | 22 | 23 | org.graalvm.nativeimage 24 | svm 25 | provided 26 | 27 | 28 | io.rsocket 29 | rsocket-core 30 | 31 | 32 | io.rsocket 33 | rsocket-transport-netty 34 | 35 | 36 | io.quarkus 37 | quarkus-jackson 38 | 39 | 40 | com.fasterxml.jackson.dataformat 41 | jackson-dataformat-cbor 42 | 43 | 44 | 45 | 46 | 47 | 48 | io.quarkus 49 | quarkus-bootstrap-maven-plugin 50 | ${quarkus.version} 51 | 52 | 53 | 54 | extension-descriptor 55 | 56 | compile 57 | 58 | ${project.groupId}:${project.artifactId}-deployment:${project.version} 59 | 60 | 61 | 62 | 63 | 64 | 65 | maven-compiler-plugin 66 | 67 | 68 | 69 | io.quarkus 70 | quarkus-extension-processor 71 | ${quarkus.version} 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /runtime/src/main/java/io/quarkiverse/rsocket/runtime/CborEncoder.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.rsocket.runtime; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.IOException; 5 | import java.nio.ByteBuffer; 6 | 7 | import com.fasterxml.jackson.databind.ObjectMapper; 8 | import com.fasterxml.jackson.databind.json.JsonMapper; 9 | import com.fasterxml.jackson.dataformat.cbor.CBORFactory; 10 | import com.fasterxml.jackson.dataformat.cbor.CBORGenerator; 11 | 12 | import io.rsocket.Payload; 13 | import io.rsocket.metadata.WellKnownMimeType; 14 | import io.rsocket.util.DefaultPayload; 15 | import io.rsocket.util.EmptyPayload; 16 | 17 | public class CborEncoder implements Encoder { 18 | 19 | private static CBORFactory factory = new CBORFactory(new JsonMapper()); 20 | private static ObjectMapper mapper = new ObjectMapper(factory); 21 | 22 | @Override 23 | public Payload encode(Object obj) { 24 | if (obj instanceof Payload) { 25 | return (Payload) obj; 26 | } else { 27 | // serialize object to payload 28 | ByteArrayOutputStream stream = new ByteArrayOutputStream(); 29 | try (CBORGenerator generator = factory.createGenerator(stream)) { 30 | generator.writeObject(obj); 31 | generator.flush(); 32 | return DefaultPayload.create(stream.toByteArray()); 33 | } catch (IOException e) { 34 | e.printStackTrace(); 35 | return EmptyPayload.INSTANCE; 36 | } 37 | } 38 | } 39 | 40 | @Override 41 | public T decode(Payload payload, Class cls) { 42 | if (Payload.class.isAssignableFrom(cls)) { 43 | return cls.cast(payload); 44 | } 45 | ByteBuffer data = payload.getData(); 46 | try { 47 | return mapper. readValue(data.array(), cls); 48 | } catch (IOException e) { 49 | e.printStackTrace(); 50 | return null; 51 | } 52 | } 53 | 54 | @Override 55 | public byte getMimeTypeId() { 56 | return WellKnownMimeType.APPLICATION_JSON.getIdentifier(); 57 | } 58 | 59 | @Override 60 | public String getMimeType() { 61 | return WellKnownMimeType.APPLICATION_JSON.getString(); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /runtime/src/main/java/io/quarkiverse/rsocket/runtime/Encoder.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.rsocket.runtime; 2 | 3 | import io.rsocket.Payload; 4 | 5 | public interface Encoder { 6 | Payload encode(Object obj); 7 | 8 | T decode(Payload payload, Class cls); 9 | 10 | String getMimeType(); 11 | 12 | byte getMimeTypeId(); 13 | } 14 | -------------------------------------------------------------------------------- /runtime/src/main/java/io/quarkiverse/rsocket/runtime/EncoderManager.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.rsocket.runtime; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class EncoderManager { 7 | private Map encoders = new HashMap<>(); 8 | private Encoder proxy = new ProxyEncoder(); 9 | 10 | public EncoderManager() { 11 | super(); 12 | register(new JsonEncoder()); 13 | register(new CborEncoder()); 14 | } 15 | 16 | public void register(Encoder encoder) { 17 | encoders.put(encoder.getMimeType(), encoder); 18 | } 19 | 20 | public Encoder getEncoder(String mime) { 21 | Encoder encoder = encoders.get(mime); 22 | return encoder != null ? encoder : proxy; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /runtime/src/main/java/io/quarkiverse/rsocket/runtime/FireAndForgetHandler.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.rsocket.runtime; 2 | 3 | import reactor.core.publisher.Mono; 4 | 5 | public interface FireAndForgetHandler { 6 | Mono handle(T payload); 7 | } 8 | -------------------------------------------------------------------------------- /runtime/src/main/java/io/quarkiverse/rsocket/runtime/JsonEncoder.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.rsocket.runtime; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.IOException; 5 | import java.nio.ByteBuffer; 6 | 7 | import com.fasterxml.jackson.databind.ObjectMapper; 8 | 9 | import io.rsocket.Payload; 10 | import io.rsocket.metadata.WellKnownMimeType; 11 | import io.rsocket.util.DefaultPayload; 12 | import io.rsocket.util.EmptyPayload; 13 | 14 | public class JsonEncoder implements Encoder { 15 | 16 | private static ObjectMapper mapper = new ObjectMapper(); 17 | 18 | @Override 19 | public Payload encode(Object obj) { 20 | if (obj instanceof Payload) { 21 | return (Payload) obj; 22 | } else { 23 | // serialize object to payload 24 | ByteArrayOutputStream stream = new ByteArrayOutputStream(); 25 | try { 26 | mapper.writeValue(stream, obj); 27 | return DefaultPayload.create(stream.toByteArray()); 28 | } catch (IOException e) { 29 | e.printStackTrace(); 30 | return EmptyPayload.INSTANCE; 31 | } 32 | } 33 | } 34 | 35 | @Override 36 | public T decode(Payload payload, Class cls) { 37 | if (Payload.class.isAssignableFrom(cls)) { 38 | return cls.cast(payload); 39 | } 40 | ByteBuffer data = payload.getData(); 41 | try { 42 | return mapper. readValue(data.array(), cls); 43 | } catch (IOException e) { 44 | e.printStackTrace(); 45 | return null; 46 | } 47 | } 48 | 49 | @Override 50 | public byte getMimeTypeId() { 51 | return WellKnownMimeType.APPLICATION_JSON.getIdentifier(); 52 | } 53 | 54 | @Override 55 | public String getMimeType() { 56 | return WellKnownMimeType.APPLICATION_JSON.getString(); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /runtime/src/main/java/io/quarkiverse/rsocket/runtime/NettyRSocketServer.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.rsocket.runtime; 2 | 3 | import javax.enterprise.inject.UnsatisfiedResolutionException; 4 | import javax.enterprise.inject.spi.CDI; 5 | 6 | import org.jboss.logging.Logger; 7 | 8 | import io.rsocket.RSocket; 9 | import io.rsocket.core.RSocketServer; 10 | import io.rsocket.exceptions.RejectedSetupException; 11 | import io.rsocket.metadata.WellKnownMimeType; 12 | import io.rsocket.transport.netty.server.TcpServerTransport; 13 | import io.rsocket.transport.netty.server.WebsocketServerTransport; 14 | import reactor.core.publisher.Mono; 15 | import reactor.netty.tcp.TcpServer; 16 | 17 | public class NettyRSocketServer { 18 | private static final Logger LOGGER = Logger.getLogger(NettyRSocketServer.class); 19 | private RSocketServer server; 20 | 21 | public void start(RSocketConfig config, String rsocketClassName) { 22 | RSocket rsocket = (RSocket) getInstance(rsocketClassName); 23 | LOGGER.info("recorder start " + rsocketClassName); 24 | start(config, rsocket); 25 | } 26 | 27 | public void start(RSocketConfig config, RSocket rsocket) { 28 | server = RSocketServer.create(); 29 | server.acceptor((setupPayload, reactiveSocket) -> { 30 | String mimetype = setupPayload.dataMimeType(); 31 | String metaMimetype = setupPayload.metadataMimeType(); 32 | if (!metaMimetype.equals(WellKnownMimeType.MESSAGE_RSOCKET_COMPOSITE_METADATA.getString())) { 33 | return Mono.error(new RejectedSetupException("Unsupported metadata mime type.")); 34 | } 35 | if (rsocket instanceof RoutedRsocket) { 36 | RoutedRsocket routedSocket = (RoutedRsocket) rsocket; 37 | routedSocket.setMimeType(mimetype); 38 | if (!routedSocket.isAuthValid(setupPayload)) { 39 | return Mono.error(new RejectedSetupException("Authentication failed")); 40 | } 41 | } 42 | return Mono.just(rsocket); 43 | }); 44 | if (config.transportProtocol == RSocketConfig.TransportProtocol.TCP) { 45 | LOGGER.info("TCP MODE"); 46 | TcpServer tcpServer = TcpServer.create().host(config.host).port(config.port); 47 | //TODO SSL support 48 | //SslContextBuilder builder; 49 | //tcpServer.secure((contextSpec) -> contextSpec.sslContext(builder.build())); 50 | TcpServerTransport transport = TcpServerTransport.create(tcpServer); 51 | server.bind(transport).block(); 52 | } else { 53 | LOGGER.info("WEBSOCKET MODE"); 54 | server.bind(WebsocketServerTransport.create(config.host, config.port)).block(); 55 | } 56 | 57 | LOGGER.info("recorder server started "); 58 | //TODO 59 | //server.interceptors(null); 60 | //server.lease(null); 61 | //server.resume(null); 62 | //server.fragment(0); 63 | //server.maxInboundPayloadSize(Integer.MAX_VALUE); 64 | //server.payloadDecoder(null); 65 | 66 | } 67 | 68 | private Class loadClass(String className) { 69 | try { 70 | return Thread.currentThread().getContextClassLoader().loadClass(className); 71 | } catch (ClassNotFoundException e) { 72 | //silent fail 73 | } 74 | try { 75 | return Class.forName(className); 76 | } catch (ClassNotFoundException e) { 77 | LOGGER.warn("failed to load class " + className); 78 | return null; 79 | } 80 | } 81 | 82 | private Object getInstance(String className) { 83 | Class classObj = loadClass(className); 84 | if (classObj == null) { 85 | LOGGER.info("failed to load class :" + className); 86 | return null; 87 | } 88 | 89 | try { 90 | return CDI.current().select(classObj).get(); 91 | } catch (UnsatisfiedResolutionException | IllegalStateException e) { 92 | //silent fail 93 | } 94 | 95 | try { 96 | return classObj.getConstructor().newInstance(); 97 | } catch (ReflectiveOperationException | RuntimeException e) { 98 | return null; 99 | } 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /runtime/src/main/java/io/quarkiverse/rsocket/runtime/ProxyEncoder.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.rsocket.runtime; 2 | 3 | import io.rsocket.Payload; 4 | 5 | public class ProxyEncoder implements Encoder { 6 | @Override 7 | public Payload encode(Object obj) { 8 | if (obj instanceof Payload) 9 | return (Payload) obj; 10 | return null; 11 | } 12 | 13 | @Override 14 | public T decode(Payload payload, Class cls) { 15 | if (Payload.class.isAssignableFrom(cls)) { 16 | return cls.cast(payload); 17 | } 18 | return null; 19 | } 20 | 21 | @Override 22 | public String getMimeType() { 23 | return null; 24 | } 25 | 26 | @Override 27 | public byte getMimeTypeId() { 28 | return 0; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /runtime/src/main/java/io/quarkiverse/rsocket/runtime/QuarkusRSocketClient.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.rsocket.runtime; 2 | 3 | import java.nio.ByteBuffer; 4 | import java.util.Collections; 5 | 6 | import org.reactivestreams.Publisher; 7 | 8 | import io.netty.buffer.ByteBuf; 9 | import io.netty.buffer.ByteBufAllocator; 10 | import io.netty.buffer.CompositeByteBuf; 11 | import io.rsocket.Payload; 12 | import io.rsocket.RSocket; 13 | import io.rsocket.core.RSocketClient; 14 | import io.rsocket.metadata.AuthMetadataCodec; 15 | import io.rsocket.metadata.CompositeMetadataCodec; 16 | import io.rsocket.metadata.RoutingMetadata; 17 | import io.rsocket.metadata.TaggingMetadata; 18 | import io.rsocket.metadata.TaggingMetadataCodec; 19 | import io.rsocket.metadata.WellKnownAuthType; 20 | import io.rsocket.metadata.WellKnownMimeType; 21 | import io.rsocket.util.DefaultPayload; 22 | import reactor.core.Disposable; 23 | import reactor.core.publisher.Flux; 24 | import reactor.core.publisher.Mono; 25 | 26 | public class QuarkusRSocketClient implements Disposable { 27 | private RSocketClient client; 28 | private String mimeType; 29 | private String route; 30 | private WellKnownAuthType authType = null; 31 | private String username; 32 | private String password; 33 | private String token; 34 | 35 | public QuarkusRSocketClient(RSocketClient client) { 36 | this.client = client; 37 | } 38 | 39 | public void route(String route) { 40 | this.route = route; 41 | } 42 | 43 | public void setOneTimeMimeType(String mimeType) { 44 | this.mimeType = mimeType; 45 | } 46 | 47 | public Mono source() { 48 | return client.source(); 49 | } 50 | 51 | public void authBearer(String token) { 52 | this.token = token; 53 | this.authType = WellKnownAuthType.BEARER; 54 | } 55 | 56 | public void authSimple(String username, String password) { 57 | this.username = username; 58 | this.password = password; 59 | this.authType = WellKnownAuthType.SIMPLE; 60 | } 61 | 62 | private CompositeByteBuf getMetadata() { 63 | CompositeByteBuf metadata = ByteBufAllocator.DEFAULT.compositeBuffer(); 64 | if (route != null) { 65 | RoutingMetadata routingMetadata = TaggingMetadataCodec.createRoutingMetadata(ByteBufAllocator.DEFAULT, 66 | Collections.singletonList(route)); 67 | CompositeMetadataCodec.encodeAndAddMetadata(metadata, 68 | ByteBufAllocator.DEFAULT, 69 | WellKnownMimeType.MESSAGE_RSOCKET_ROUTING, 70 | routingMetadata.getContent()); 71 | } 72 | 73 | if (mimeType != null) { 74 | TaggingMetadata mimeMetadata = TaggingMetadataCodec.createTaggingMetadata(ByteBufAllocator.DEFAULT, 75 | WellKnownMimeType.MESSAGE_RSOCKET_MIMETYPE.getString(), Collections.singletonList(mimeType)); 76 | CompositeMetadataCodec.encodeAndAddMetadata(metadata, 77 | ByteBufAllocator.DEFAULT, 78 | WellKnownMimeType.MESSAGE_RSOCKET_MIMETYPE, 79 | mimeMetadata.getContent()); 80 | //reset per stream mime type 81 | mimeType = null; 82 | } 83 | if (authType == WellKnownAuthType.SIMPLE) { 84 | ByteBuf byteBuf = AuthMetadataCodec.encodeSimpleMetadata(ByteBufAllocator.DEFAULT, 85 | username.toCharArray(), password.toCharArray()); 86 | CompositeMetadataCodec.encodeAndAddMetadata(metadata, 87 | ByteBufAllocator.DEFAULT, 88 | WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION, 89 | byteBuf); 90 | } 91 | if (authType == WellKnownAuthType.BEARER) { 92 | ByteBuf byteBuf = AuthMetadataCodec.encodeBearerMetadata(ByteBufAllocator.DEFAULT, 93 | token.toCharArray()); 94 | CompositeMetadataCodec.encodeAndAddMetadata(metadata, 95 | ByteBufAllocator.DEFAULT, 96 | WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION, 97 | byteBuf); 98 | } 99 | return metadata; 100 | } 101 | 102 | public Mono fireAndForget(Mono dataMono) { 103 | return client.fireAndForget(dataMono.map(data -> { 104 | return DefaultPayload.create(data, getMetadata().nioBuffer()); 105 | })); 106 | } 107 | 108 | public Mono requestResponse(Mono dataMono) { 109 | return client.requestResponse(dataMono.map(data -> { 110 | return DefaultPayload.create(data, getMetadata().nioBuffer()); 111 | })); 112 | } 113 | 114 | public Flux requestStream(Mono dataMono) { 115 | return client.requestStream(dataMono.map(data -> { 116 | return DefaultPayload.create(data, getMetadata().nioBuffer()); 117 | })); 118 | } 119 | 120 | public Flux requestChannel(Publisher datas) { 121 | return client.requestChannel(Flux.from(datas).map(data -> { 122 | return DefaultPayload.create(data, getMetadata().nioBuffer()); 123 | })); 124 | } 125 | 126 | public Mono metadataPush(Mono dataMono) { 127 | return client.metadataPush(dataMono.map(data -> { 128 | return DefaultPayload.create(data, getMetadata().nioBuffer()); 129 | })); 130 | } 131 | 132 | public void dispose() { 133 | client.dispose(); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /runtime/src/main/java/io/quarkiverse/rsocket/runtime/QuarkusRSocketConnector.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.rsocket.runtime; 2 | 3 | import java.time.Duration; 4 | import java.util.function.Consumer; 5 | import java.util.function.Supplier; 6 | 7 | import io.netty.buffer.ByteBuf; 8 | import io.netty.buffer.ByteBufAllocator; 9 | import io.rsocket.Payload; 10 | import io.rsocket.SocketAcceptor; 11 | import io.rsocket.core.RSocketClient; 12 | import io.rsocket.core.RSocketConnector; 13 | import io.rsocket.core.Resume; 14 | import io.rsocket.frame.decoder.PayloadDecoder; 15 | import io.rsocket.lease.LeaseStats; 16 | import io.rsocket.lease.Leases; 17 | import io.rsocket.metadata.AuthMetadataCodec; 18 | import io.rsocket.metadata.WellKnownMimeType; 19 | import io.rsocket.plugins.InterceptorRegistry; 20 | import io.rsocket.transport.ClientTransport; 21 | import io.rsocket.transport.netty.client.TcpClientTransport; 22 | import io.rsocket.transport.netty.client.WebsocketClientTransport; 23 | import io.rsocket.util.DefaultPayload; 24 | import reactor.core.publisher.Mono; 25 | import reactor.util.retry.Retry; 26 | 27 | public class QuarkusRSocketConnector { 28 | // can not extends RSocketConnector because constructor is private and not protected 29 | private final RSocketConnector proxy; 30 | 31 | private QuarkusRSocketConnector() { 32 | proxy = RSocketConnector.create(); 33 | //add composite metadata extension by default 34 | proxy.metadataMimeType(WellKnownMimeType.MESSAGE_RSOCKET_COMPOSITE_METADATA.getString()); 35 | proxy.dataMimeType(WellKnownMimeType.APPLICATION_JSON.getString()); 36 | } 37 | 38 | public static QuarkusRSocketConnector create() { 39 | return new QuarkusRSocketConnector(); 40 | } 41 | 42 | public static QuarkusRSocketClient connectWith(ClientTransport transport) { 43 | return create().connect(() -> { 44 | return transport; 45 | }); 46 | } 47 | 48 | public void authBearer(String token) { 49 | ByteBuf byteBuf = AuthMetadataCodec.encodeBearerMetadata(ByteBufAllocator.DEFAULT, 50 | token.toCharArray()); 51 | proxy.setupPayload(DefaultPayload.create(DefaultPayload.EMPTY_BUFFER, byteBuf.nioBuffer())); 52 | } 53 | 54 | public void authSimple(String username, String password) { 55 | ByteBuf byteBuf = AuthMetadataCodec.encodeSimpleMetadata(ByteBufAllocator.DEFAULT, 56 | username.toCharArray(), password.toCharArray()); 57 | proxy.setupPayload(DefaultPayload.create(DefaultPayload.EMPTY_BUFFER, byteBuf.nioBuffer())); 58 | } 59 | 60 | public QuarkusRSocketConnector setupPayload(Mono setupPayloadMono) { 61 | proxy.setupPayload(setupPayloadMono); 62 | return this; 63 | } 64 | 65 | public QuarkusRSocketConnector setupPayload(Payload payload) { 66 | proxy.setupPayload(payload); 67 | return this; 68 | } 69 | 70 | public QuarkusRSocketConnector dataMimeType(String dataMimeType) { 71 | proxy.dataMimeType(dataMimeType); 72 | return this; 73 | } 74 | 75 | public QuarkusRSocketConnector json() { 76 | proxy.dataMimeType(WellKnownMimeType.APPLICATION_JSON.getString()); 77 | return this; 78 | } 79 | 80 | public QuarkusRSocketConnector cbor() { 81 | proxy.dataMimeType(WellKnownMimeType.APPLICATION_CBOR.getString()); 82 | return this; 83 | } 84 | 85 | public QuarkusRSocketConnector protobuf() { 86 | proxy.dataMimeType(WellKnownMimeType.APPLICATION_PROTOBUF.getString()); 87 | return this; 88 | } 89 | 90 | public QuarkusRSocketConnector xml() { 91 | proxy.dataMimeType(WellKnownMimeType.APPLICATION_XML.getString()); 92 | return this; 93 | } 94 | 95 | public QuarkusRSocketConnector binary() { 96 | proxy.dataMimeType("application/binary"); 97 | return this; 98 | } 99 | 100 | public QuarkusRSocketConnector metadataMimeType(String metadataMimeType) { 101 | proxy.metadataMimeType(metadataMimeType); 102 | return this; 103 | } 104 | 105 | public QuarkusRSocketConnector keepAlive(Duration interval, Duration maxLifeTime) { 106 | proxy.keepAlive(interval, maxLifeTime); 107 | return this; 108 | } 109 | 110 | public QuarkusRSocketConnector interceptors(Consumer configurer) { 111 | proxy.interceptors(configurer); 112 | return this; 113 | } 114 | 115 | public QuarkusRSocketConnector acceptor(SocketAcceptor acceptor) { 116 | proxy.acceptor(acceptor); 117 | return this; 118 | } 119 | 120 | public QuarkusRSocketConnector reconnect(Retry retry) { 121 | proxy.reconnect(retry); 122 | return this; 123 | } 124 | 125 | public QuarkusRSocketConnector resume(Resume resume) { 126 | proxy.resume(resume); 127 | return this; 128 | } 129 | 130 | public QuarkusRSocketConnector lease(Supplier> supplier) { 131 | proxy.lease(supplier); 132 | return this; 133 | } 134 | 135 | public QuarkusRSocketConnector maxInboundPayloadSize(int maxInboundPayloadSize) { 136 | proxy.maxInboundPayloadSize(maxInboundPayloadSize); 137 | return this; 138 | } 139 | 140 | public QuarkusRSocketConnector fragment(int mtu) { 141 | proxy.fragment(mtu); 142 | return this; 143 | } 144 | 145 | public QuarkusRSocketConnector payloadDecoder(PayloadDecoder decoder) { 146 | proxy.payloadDecoder(decoder); 147 | return this; 148 | } 149 | 150 | public QuarkusRSocketClient connect(ClientTransport transport) { 151 | return this.connect(() -> { 152 | return transport; 153 | }); 154 | } 155 | 156 | public QuarkusRSocketClient tcp(String host, int port) { 157 | return connect(TcpClientTransport.create(host, port)); 158 | } 159 | 160 | public QuarkusRSocketClient webSocket(String host, int port) { 161 | return connect(WebsocketClientTransport.create(host, port)); 162 | } 163 | 164 | public QuarkusRSocketClient connect(Supplier transportSupplier) { 165 | return new QuarkusRSocketClient(RSocketClient.from(proxy.connect(transportSupplier))); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /runtime/src/main/java/io/quarkiverse/rsocket/runtime/RSocketConfig.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.rsocket.runtime; 2 | 3 | import io.quarkus.runtime.annotations.ConfigItem; 4 | import io.quarkus.runtime.annotations.ConfigPhase; 5 | import io.quarkus.runtime.annotations.ConfigRoot; 6 | 7 | @ConfigRoot(name = RSocketConfig.CONFIG_NAME, phase = ConfigPhase.RUN_TIME) 8 | public class RSocketConfig { 9 | public static final String CONFIG_NAME = "rsocket"; 10 | /** 11 | * Rsocket server port 12 | */ 13 | @ConfigItem(defaultValue = "7000") 14 | public int port; 15 | 16 | /** 17 | * Rsocket server host 18 | */ 19 | @ConfigItem(defaultValue = "localhost") 20 | public String host; 21 | 22 | /** 23 | * Rsocket server protocole (TCP or WebSocket) 24 | */ 25 | @ConfigItem(defaultValue = "TCP") 26 | public TransportProtocol transportProtocol; 27 | 28 | public enum TransportProtocol { 29 | TCP, 30 | WEB_SOCKET 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /runtime/src/main/java/io/quarkiverse/rsocket/runtime/RSocketRecorder.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.rsocket.runtime; 2 | 3 | import io.quarkus.runtime.RuntimeValue; 4 | import io.quarkus.runtime.annotations.Recorder; 5 | import io.rsocket.frame.FrameType; 6 | 7 | @Recorder 8 | public class RSocketRecorder { 9 | 10 | public RuntimeValue initServer(RSocketConfig config, String rsocketClassName) { 11 | NettyRSocketServer server = new NettyRSocketServer(); 12 | server.start(config, rsocketClassName); 13 | return new RuntimeValue<>(server); 14 | } 15 | 16 | public RuntimeValue buildRouter() { 17 | return new RuntimeValue<>(RoutedRsocket.builder()); 18 | } 19 | 20 | public void addHandler(RuntimeValue builder, String route, String handlerClassName, 21 | FrameType frameType) { 22 | if (frameType == FrameType.REQUEST_FNF) { 23 | builder.getValue().addFireAndForget(route, handlerClassName); 24 | } else if (frameType == FrameType.REQUEST_CHANNEL) { 25 | builder.getValue().addRequestChannel(route, handlerClassName); 26 | } else if (frameType == FrameType.REQUEST_RESPONSE) { 27 | builder.getValue().addRequestResponse(route, handlerClassName); 28 | } else if (frameType == FrameType.REQUEST_STREAM) { 29 | builder.getValue().addRequestStream(route, handlerClassName); 30 | } 31 | } 32 | 33 | public RuntimeValue initServer(RSocketConfig config, RuntimeValue builder) { 34 | NettyRSocketServer server = new NettyRSocketServer(); 35 | server.start(config, builder.getValue().build()); 36 | return new RuntimeValue<>(server); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /runtime/src/main/java/io/quarkiverse/rsocket/runtime/RequestChannelHandler.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.rsocket.runtime; 2 | 3 | import reactor.core.publisher.Flux; 4 | 5 | public interface RequestChannelHandler { 6 | Flux handle(Flux payloads); 7 | } 8 | -------------------------------------------------------------------------------- /runtime/src/main/java/io/quarkiverse/rsocket/runtime/RequestResponseHandler.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.rsocket.runtime; 2 | 3 | import reactor.core.publisher.Mono; 4 | 5 | public interface RequestResponseHandler { 6 | Mono handle(T payload); 7 | } 8 | -------------------------------------------------------------------------------- /runtime/src/main/java/io/quarkiverse/rsocket/runtime/RequestStreamHandler.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.rsocket.runtime; 2 | 3 | import reactor.core.publisher.Flux; 4 | 5 | public interface RequestStreamHandler { 6 | Flux handle(T payload); 7 | } 8 | -------------------------------------------------------------------------------- /runtime/src/main/java/io/quarkiverse/rsocket/runtime/RoutedRsocket.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.rsocket.runtime; 2 | 3 | import java.lang.reflect.ParameterizedType; 4 | import java.lang.reflect.Type; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | import javax.enterprise.inject.UnsatisfiedResolutionException; 9 | import javax.enterprise.inject.spi.CDI; 10 | 11 | import org.jboss.logging.Logger; 12 | import org.reactivestreams.Publisher; 13 | 14 | import io.rsocket.ConnectionSetupPayload; 15 | import io.rsocket.Payload; 16 | import io.rsocket.RSocket; 17 | import io.rsocket.metadata.CompositeMetadata; 18 | import io.rsocket.metadata.TaggingMetadata; 19 | import io.rsocket.metadata.WellKnownMimeType; 20 | import reactor.core.publisher.Flux; 21 | import reactor.core.publisher.Mono; 22 | 23 | public class RoutedRsocket implements RSocket { 24 | private static final Logger LOGGER = Logger.getLogger(RoutedRsocket.class); 25 | private final Map> requestResponseRoutes; 26 | private final Map> fireAndForgetRoutes; 27 | private final Map> requestStreamRoutes; 28 | private final Map> requestChannelRoutes; 29 | private final EncoderManager encoderRegistry; 30 | private String mimeType = WellKnownMimeType.APPLICATION_JSON.getString(); 31 | 32 | RoutedRsocket(Map> requestResponseRoutes, 33 | Map> fireAndForgetRoutes, 34 | Map> requestStreamRoutes, 35 | Map> requestChannelRoutes) { 36 | this.requestResponseRoutes = requestResponseRoutes; 37 | this.fireAndForgetRoutes = fireAndForgetRoutes; 38 | this.requestStreamRoutes = requestStreamRoutes; 39 | this.requestChannelRoutes = requestChannelRoutes; 40 | encoderRegistry = new EncoderManager(); 41 | } 42 | 43 | public static Builder builder() { 44 | return new Builder(); 45 | } 46 | 47 | public void setMimeType(String mimetype) { 48 | this.mimeType = mimetype; 49 | } 50 | 51 | private static Class loadClass(String className) { 52 | try { 53 | return Thread.currentThread().getContextClassLoader().loadClass(className); 54 | } catch (ClassNotFoundException e) { 55 | //silent fail 56 | } 57 | try { 58 | return Class.forName(className); 59 | } catch (ClassNotFoundException e) { 60 | return null; 61 | } 62 | } 63 | 64 | private static Object getInstance(String className) { 65 | Class classObj = loadClass(className); 66 | if (classObj == null) { 67 | return null; 68 | } 69 | 70 | try { 71 | return CDI.current().select(classObj).get(); 72 | } catch (UnsatisfiedResolutionException | IllegalStateException e) { 73 | //silent fail 74 | } 75 | 76 | try { 77 | return classObj.getConstructor().newInstance(); 78 | } catch (ReflectiveOperationException | RuntimeException e) { 79 | return null; 80 | } 81 | } 82 | 83 | public boolean isAuthValid(ConnectionSetupPayload setupPayload) { 84 | //TODO authentication... 85 | return true; 86 | } 87 | 88 | public static final class Builder { 89 | private final Map> requestResponseRoutes; 90 | private final Map> fireAndForgetRoutes; 91 | private final Map> requestStreamRoutes; 92 | private final Map> requestChannelRoutes; 93 | 94 | public Builder() { 95 | this.requestResponseRoutes = new HashMap<>(); 96 | this.fireAndForgetRoutes = new HashMap<>(); 97 | this.requestStreamRoutes = new HashMap<>(); 98 | this.requestChannelRoutes = new HashMap<>(); 99 | } 100 | 101 | public Builder addRequestResponse(String route, String handlerClassName) { 102 | RequestResponseHandler handler = (RequestResponseHandler) getInstance(handlerClassName); 103 | requestResponseRoutes.put(route, handler); 104 | return this; 105 | } 106 | 107 | public Builder addFireAndForget(String route, String handlerClassName) { 108 | FireAndForgetHandler handler = (FireAndForgetHandler) getInstance(handlerClassName); 109 | fireAndForgetRoutes.put(route, handler); 110 | return this; 111 | } 112 | 113 | public Builder addRequestStream(String route, String handlerClassName) { 114 | RequestStreamHandler handler = (RequestStreamHandler) getInstance(handlerClassName); 115 | requestStreamRoutes.put(route, handler); 116 | return this; 117 | } 118 | 119 | public Builder addRequestChannel(String route, String handlerClassName) { 120 | RequestChannelHandler handler = (RequestChannelHandler) getInstance(handlerClassName); 121 | requestChannelRoutes.put(route, handler); 122 | return this; 123 | } 124 | 125 | public RoutedRsocket build() { 126 | return new RoutedRsocket(requestResponseRoutes, fireAndForgetRoutes, requestStreamRoutes, requestChannelRoutes); 127 | } 128 | 129 | } 130 | 131 | @Override 132 | public Mono requestResponse(Payload payload) { 133 | try { 134 | LOGGER.debug("requestResponse called"); 135 | Map metadatas = parseMetadata(payload); 136 | String route = getRoute(metadatas); 137 | LOGGER.debug("route :" + route); 138 | if (route != null) { 139 | 140 | RequestResponseHandler handler = requestResponseRoutes.get(route); 141 | if (handler != null) { 142 | Class persistentClass = getHandlerGenericParam(handler); 143 | 144 | LOGGER.debug("handler found"); 145 | Encoder encoder = getEncoder(metadatas); 146 | Object obj = encoder.decode(payload, persistentClass); 147 | return handleRequestResponse(handler, obj).map(encoder::encode); 148 | } 149 | } 150 | LOGGER.debug("handler not found"); 151 | return RSocket.super.requestResponse(payload); 152 | } catch (Throwable t) { 153 | LOGGER.error("request response error", t); 154 | return Mono.error(t); 155 | } 156 | } 157 | 158 | private Class getHandlerGenericParam(Object handler) { 159 | for (Type itf : handler.getClass().getGenericInterfaces()) { 160 | if (itf instanceof ParameterizedType) { 161 | String itfRawtype = ((ParameterizedType) itf).getRawType().getTypeName(); 162 | if (itfRawtype.equals("io.quarkiverse.rsocket.runtime.RequestResponseHandler") 163 | || itfRawtype.equals("io.quarkiverse.rsocket.runtime.FireAndForgetHandler") 164 | || itfRawtype.equals("io.quarkiverse.rsocket.runtime.RequestStreamHandler") 165 | || itfRawtype.equals("io.quarkiverse.rsocket.runtime.RequestChannelHandler")) { 166 | return (Class) ((ParameterizedType) itf).getActualTypeArguments()[0]; 167 | } 168 | } 169 | } 170 | return null; 171 | } 172 | 173 | private Mono handleRequestResponse(RequestResponseHandler handler, Object obj) { 174 | return handler.handle((T) obj); 175 | } 176 | 177 | @Override 178 | public Mono fireAndForget(Payload payload) { 179 | try { 180 | Map metadatas = parseMetadata(payload); 181 | String route = getRoute(metadatas); 182 | if (route != null) { 183 | FireAndForgetHandler handler = fireAndForgetRoutes.get(route); 184 | if (handler != null) { 185 | Class persistentClass = getHandlerGenericParam(handler); 186 | Encoder encoder = getEncoder(metadatas); 187 | return handleFireAndForget(handler, encoder.decode(payload, persistentClass)); 188 | } 189 | } 190 | return RSocket.super.fireAndForget(payload); 191 | } catch (Throwable t) { 192 | return Mono.error(t); 193 | } 194 | } 195 | 196 | private Mono handleFireAndForget(FireAndForgetHandler handler, Object obj) { 197 | return handler.handle((T) obj); 198 | } 199 | 200 | @Override 201 | public Flux requestStream(Payload payload) { 202 | try { 203 | Map metadatas = parseMetadata(payload); 204 | String route = getRoute(metadatas); 205 | if (route != null) { 206 | RequestStreamHandler handler = requestStreamRoutes.get(route); 207 | if (handler != null) { 208 | Class persistentClass = getHandlerGenericParam(handler); 209 | Encoder encoder = getEncoder(metadatas); 210 | return handleRequestStream(handler, encoder.decode(payload, persistentClass)).map(encoder::encode); 211 | } 212 | } 213 | return RSocket.super.requestStream(payload); 214 | } catch (Throwable t) { 215 | return Flux.error(t); 216 | } 217 | } 218 | 219 | private Flux handleRequestStream(RequestStreamHandler handler, Object obj) { 220 | return handler.handle((T) obj); 221 | } 222 | 223 | @Override 224 | public Flux requestChannel(Publisher payloads) { 225 | return Flux.from(payloads) 226 | .switchOnFirst( 227 | (signal, flows) -> { 228 | Payload payload = null; 229 | try { 230 | payload = signal.get(); 231 | if (payload != null) { 232 | 233 | Map metadatas = parseMetadata(payload); 234 | String route = getRoute(metadatas); 235 | if (route != null) { 236 | RequestChannelHandler handler = requestChannelRoutes.get(route); 237 | if (handler != null) { 238 | Class persistentClass = getHandlerGenericParam(handler); 239 | Encoder encoder = getEncoder(metadatas); 240 | return handleRequestChannel(handler, 241 | flows.map(pl -> encoder.decode(pl, persistentClass))).map(encoder::encode); 242 | } 243 | } 244 | } 245 | return RSocket.super.requestChannel(payloads); 246 | } catch (Throwable t) { 247 | if (payload != null) { 248 | payload.release(); 249 | } 250 | return Flux.error(t); 251 | } 252 | }, 253 | false); 254 | 255 | } 256 | 257 | private Flux handleRequestChannel(RequestChannelHandler handler, Object obj) { 258 | return handler.handle((Flux) obj); 259 | } 260 | 261 | private Map parseMetadata(Payload payload) { 262 | Map metadataMap = new HashMap<>(); 263 | 264 | if (payload.hasMetadata()) { 265 | CompositeMetadata compositeMetadata = new CompositeMetadata(payload.metadata(), true); 266 | 267 | for (CompositeMetadata.Entry entry : compositeMetadata) { 268 | if (entry instanceof CompositeMetadata.WellKnownMimeTypeEntry) { 269 | TaggingMetadata metadata = new TaggingMetadata(entry.getMimeType(), entry.getContent()); 270 | 271 | metadataMap.put(entry.getMimeType(), metadata); 272 | } 273 | } 274 | } 275 | return metadataMap; 276 | } 277 | 278 | private Encoder getEncoder(Map metadatas) { 279 | //PerStreamDataMimeTypes 280 | TaggingMetadata mimetypeMetadata = metadatas.get(WellKnownMimeType.MESSAGE_RSOCKET_MIMETYPE.getString()); 281 | Encoder res = null; 282 | if (mimetypeMetadata != null && mimetypeMetadata.iterator().hasNext()) { 283 | res = encoderRegistry.getEncoder(mimetypeMetadata.iterator().next()); 284 | } 285 | if (res != null) 286 | return res; 287 | // else encoder from ConnectionSetupPayload 288 | return encoderRegistry.getEncoder(mimeType); 289 | } 290 | 291 | private String getRoute(Map metadatas) { 292 | TaggingMetadata routeMetadata = metadatas.get(WellKnownMimeType.MESSAGE_RSOCKET_ROUTING.getString()); 293 | if (routeMetadata != null && routeMetadata.iterator().hasNext()) { 294 | return routeMetadata.iterator().next(); 295 | } 296 | return null; 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /runtime/src/main/resources/META-INF/quarkus-extension.yaml: -------------------------------------------------------------------------------- 1 | name: Quarkus - Rsocket 2 | #description: Quarkus - Rsocket ... 3 | metadata: 4 | # keywords: 5 | # - rsocket 6 | # guide: ... 7 | # categories: 8 | # - "miscellaneous" 9 | # status: "preview" --------------------------------------------------------------------------------