├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── cli-completion.yml │ ├── coursier.yml │ ├── homebrew-tap.yml │ ├── homebrew.yml │ ├── scoop-bucket.yml │ └── scoop.yml ├── .gitignore ├── .mvn └── wrapper │ ├── MavenWrapperDownloader.java │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── LICENSE ├── README.md ├── mvnw ├── mvnw.cmd ├── next-development.sh ├── pom.xml ├── prepare-release.sh ├── quick-e2e.sh └── src ├── main ├── java │ └── am │ │ └── ik │ │ └── rsocket │ │ ├── Args.java │ │ ├── InteractionModel.java │ │ ├── MetadataEncoder.java │ │ ├── RscApplication.java │ │ ├── RscCommandLineRunner.java │ │ ├── SetupMetadataMimeType.java │ │ ├── Transport.java │ │ ├── completion │ │ ├── CliCompletionHelpFormatter.java │ │ └── ShellType.java │ │ ├── file │ │ └── FileSystemResourceLoader.java │ │ ├── routing │ │ └── Route.java │ │ ├── security │ │ ├── AuthenticationSetupMetadata.java │ │ ├── BasicAuthentication.java │ │ ├── BearerAuthentication.java │ │ └── SimpleAuthentication.java │ │ └── tracing │ │ ├── Reporter.java │ │ ├── Span.java │ │ └── Tracing.java └── resources │ ├── META-INF │ └── native-image │ │ └── am.ik.rsocket │ │ └── rsc │ │ └── reflect-config.json │ ├── application.properties │ └── completions │ ├── bash │ ├── fish │ ├── powershell │ └── zsh └── test └── java └── am └── ik └── rsocket ├── ArgsTest.java ├── InteractionModelTest.java └── RscApplicationTests.java /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: maven 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "20:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - master 6 | paths: 7 | - src/** 8 | - pom.xml 9 | - .github/workflows/ci.yml 10 | pull_request: 11 | branches: 12 | - master 13 | paths: 14 | - src/** 15 | - '!src/main/resources/completions/*' 16 | - pom.xml 17 | - .github/workflows/ci.yml 18 | jobs: 19 | build: 20 | runs-on: ${{ matrix.os }} 21 | strategy: 22 | matrix: 23 | include: 24 | - os: ubuntu-latest 25 | platform: x86_64-pc-linux 26 | classifier: linux-x86_64 27 | move: mv 28 | copy: cp 29 | separator: / 30 | extension: "" 31 | - os: macos-latest 32 | platform: x86_64-apple-darwin 33 | classifier: osx-x86_64 34 | move: mv 35 | copy: cp 36 | separator: / 37 | extension: "" 38 | - os: windows-latest 39 | platform: x86_64-pc-win32 40 | classifier: windows-x86_64 41 | move: move 42 | copy: copy 43 | separator: \ 44 | extension: .exe 45 | steps: 46 | - uses: actions/checkout@v1 47 | - uses: ayltai/setup-graalvm@v1 48 | with: 49 | java-version: 8 50 | graalvm-version: 21.0.0.2 51 | native-image: true 52 | - uses: ilammy/msvc-dev-cmd@v1.5.0 53 | if: runner.os == 'Windows' 54 | - uses: microsoft/setup-msbuild@v1 55 | if: runner.os == 'Windows' 56 | - uses: actions/cache@v1 57 | with: 58 | path: ~/.m2/repository 59 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 60 | restore-keys: | 61 | ${{ runner.os }}-maven- 62 | - name: tweak-for-windows 63 | if: runner.os == 'Windows' 64 | run: | 65 | xcopy /E /Y C:\hostedtoolcache\windows\GraalVM\java8-windows-amd64-21.0.0.2\x64\jre\lib C:\hostedtoolcache\windows\GraalVM\java8-windows-amd64-21.0.0.2\x64\lib 66 | - name: native-image 67 | run: | 68 | mkdir dist 69 | mvn package -DskipTests 70 | ${{ matrix.copy }} target${{ matrix.separator }}rsc*.jar dist${{ matrix.separator }} 71 | mvn package -Dversion-generate-skip=true -Pnative -DskipTests 72 | ${{ matrix.move }} target${{ matrix.separator }}classes${{ matrix.separator }}rsc-${{ matrix.classifier }}${{ matrix.extension }} dist${{ matrix.separator }}rsc-${{ matrix.platform }}${{ matrix.extension }} 73 | - name: chmod 74 | if: runner.os != 'Windows' 75 | run: | 76 | chmod +x dist${{ matrix.separator }}rsc-${{ matrix.platform }}${{ matrix.extension }} 77 | - name: e2e-test-native 78 | env: 79 | RSC_PATH: ..${{ matrix.separator }}dist${{ matrix.separator }}rsc-${{ matrix.platform }}${{ matrix.extension }} 80 | RSC_OIDCUSERNAME: ${{ secrets.RSC_OIDCUSERNAME }} 81 | RSC_OIDCPASSWORD: ${{ secrets.RSC_OIDCPASSWORD }} 82 | run: | 83 | git clone https://github.com/making/rsc-e2e 84 | mvn test -f rsc-e2e 85 | - name: e2e-test-uber-jar 86 | if: runner.os == 'Linux' 87 | env: 88 | RSC_OIDCUSERNAME: ${{ secrets.RSC_OIDCUSERNAME }} 89 | RSC_OIDCPASSWORD: ${{ secrets.RSC_OIDCPASSWORD }} 90 | run: | 91 | export RSC_PATH="java -jar ../$(ls ./dist/*.jar)" 92 | mvn test -f rsc-e2e 93 | - name: upload-dist 94 | uses: actions/upload-artifact@v1 95 | with: 96 | name: dist 97 | path: dist 98 | -------------------------------------------------------------------------------- /.github/workflows/cli-completion.yml: -------------------------------------------------------------------------------- 1 | name: CLI completion 2 | on: 3 | push: 4 | branches: 5 | - master 6 | paths: 7 | - src/main/java/am/ik/rsocket/Args.java 8 | - src/main/java/am/ik/rsocket/completion/* 9 | - .github/workflows/cli-completion.yml 10 | workflow_dispatch: { } 11 | jobs: 12 | completion: 13 | name: cli-completion 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v1 17 | - uses: actions-rs/toolchain@v1 18 | with: 19 | toolchain: stable 20 | - run: cargo install cli-completion 21 | - uses: actions/cache@v1 22 | with: 23 | path: ~/.m2/repository 24 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 25 | restore-keys: | 26 | ${{ runner.os }}-maven- 27 | - name: Update Completions 28 | run: | 29 | mvn package -DskipTests 30 | java -jar target/*.jar -h cli-completion > /tmp/cli-completion.yml 31 | cli-completion --zsh /tmp/cli-completion.yml > src/main/resources/completions/zsh 32 | cli-completion --bash /tmp/cli-completion.yml > src/main/resources/completions/bash 33 | cli-completion --fish /tmp/cli-completion.yml > src/main/resources/completions/fish 34 | cli-completion --powershell /tmp/cli-completion.yml > src/main/resources/completions/powershell 35 | git diff 36 | - name: Create Pull Request 37 | uses: peter-evans/create-pull-request@v3 38 | with: 39 | token: ${{ secrets.ACCESS_TOKEN }} 40 | commit-message: Update completions 41 | committer: ${{ secrets.GIT_NAME }} <${{ secrets.GIT_EMAIL }}> 42 | author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com> 43 | signoff: false 44 | branch: update-completions 45 | delete-branch: true 46 | title: Update completions 47 | body: Update completions 48 | base: master 49 | - name: Check outputs 50 | run: | 51 | echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}" 52 | echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}" 53 | -------------------------------------------------------------------------------- /.github/workflows/coursier.yml: -------------------------------------------------------------------------------- 1 | name: Coursier 2 | on: 3 | schedule: 4 | - cron: 0 3 * * * 5 | push: 6 | branches: 7 | - master 8 | paths: 9 | - .github/workflows/coursier.yml 10 | pull_request: 11 | branches: 12 | - master 13 | paths: 14 | - .github/workflows/coursier.yml 15 | workflow_dispatch: { } 16 | jobs: 17 | install: 18 | name: cs install 19 | runs-on: ${{ matrix.os }} 20 | strategy: 21 | matrix: 22 | include: 23 | - os: ubuntu-latest 24 | - os: macos-latest 25 | - os: windows-latest 26 | steps: 27 | - name: cs install 28 | uses: laughedelic/coursier-setup@v1 29 | with: 30 | apps: rsc --contrib 31 | - name: check cli 32 | run: | 33 | rsc --version 34 | rsc --showSystemProperties 35 | rsc wss://demo.rsocket.io/rsocket --route searchTweets --stream -d Trump --take 3 --retry 3 --debug -------------------------------------------------------------------------------- /.github/workflows/homebrew-tap.yml: -------------------------------------------------------------------------------- 1 | name: Homebrew-tap 2 | on: 3 | release: 4 | types: 5 | - released 6 | workflow_dispatch: { } 7 | jobs: 8 | update-homebrew-tap: 9 | name: update homebrew-tap 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: download-macox-binaries 13 | uses: dsaltares/fetch-gh-release-asset@master 14 | with: 15 | repo: making/rsc 16 | version: "latest" 17 | file: rsc-x86_64-apple-darwin 18 | token: ${{ secrets.ACCESS_TOKEN }} 19 | - name: download-linux-binaries 20 | id: download-linux-binaries 21 | uses: dsaltares/fetch-gh-release-asset@master 22 | with: 23 | repo: making/rsc 24 | version: "latest" 25 | file: rsc-x86_64-pc-linux 26 | token: ${{ secrets.ACCESS_TOKEN }} 27 | - name: send a pull request 28 | env: 29 | REPOSITORY: making/homebrew-tap 30 | VERSION: ${{ steps.download-linux-binaries.outputs.version }} 31 | GIT_EMAIL: ${{ secrets.GIT_EMAIL }} 32 | GIT_NAME: ${{ secrets.GIT_NAME }} 33 | GIT_SSH_KEY: ${{ secrets.GIT_SSH_KEY }} 34 | ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} 35 | BRANCH_NAME: update-rsc 36 | BASE: master 37 | run: | 38 | set -ex 39 | mkdir -p ~/.ssh 40 | cat > ~/.ssh/config < ~/.ssh/id_rsa < rsc.rb 56 | class Rsc < Formula 57 | desc "RSocket Client CLI (RSC)" 58 | homepage "https://github.com/making/rsc" 59 | version "${VERSION}" 60 | license "Apache-2.0" 61 | 62 | depends_on :arch => :x86_64 63 | 64 | if OS.mac? 65 | url "https://github.com/making/rsc/releases/download/#{version}/rsc-x86_64-apple-darwin" 66 | sha256 "$(shasum -a 256 ../rsc-x86_64-apple-darwin | awk '{print $1}')" 67 | elsif OS.linux? 68 | url "https://github.com/making/rsc/releases/download/#{version}/rsc-x86_64-pc-linux" 69 | sha256 "$(shasum -a 256 ../rsc-x86_64-pc-linux | awk '{print $1}')" 70 | end 71 | 72 | def install 73 | if OS.mac? 74 | mv "rsc-x86_64-apple-darwin", "rsc" 75 | elsif OS.linux? 76 | mv "rsc-x86_64-pc-linux", "rsc" 77 | end 78 | bin.install "rsc" 79 | chmod 0755, "#{bin}/rsc" 80 | output = Utils.safe_popen_read("#{bin}/rsc", "--completion", "bash") 81 | (bash_completion/"rsc").write output 82 | output = Utils.safe_popen_read("#{bin}/rsc", "--completion", "zsh") 83 | (zsh_completion/"_rsc").write output 84 | output = Utils.safe_popen_read("#{bin}/rsc", "--completion", "fish") 85 | (fish_completion/"rsc.fish").write output 86 | end 87 | 88 | test do 89 | system "#{bin}/rsc -v" 90 | end 91 | end 92 | EOF 93 | git diff 94 | git add -A 95 | git commit -m "Bump rsc to ${VERSION}" 96 | git push origin ${BRANCH_NAME} 97 | curl -u ${GIT_NAME}:${ACCESS_TOKEN} -H "Content-Type: application/json" -X POST -d "{\"title\":\"Bump rsc to ${VERSION}\",\"body\":\"automatically created pr\",\"head\":\"${BRANCH_NAME}\",\"base\":\"${BASE}\"}" https://api.github.com/repos/${REPOSITORY}/pulls 98 | -------------------------------------------------------------------------------- /.github/workflows/homebrew.yml: -------------------------------------------------------------------------------- 1 | name: Homebrew 2 | on: 3 | schedule: 4 | - cron: 0 3 * * * 5 | push: 6 | branches: 7 | - master 8 | paths: 9 | - .github/workflows/homebrew.yml 10 | pull_request: 11 | branches: 12 | - master 13 | paths: 14 | - .github/workflows/homebrew.yml 15 | workflow_dispatch: { } 16 | jobs: 17 | install: 18 | name: brew install 19 | runs-on: ${{ matrix.os }} 20 | strategy: 21 | matrix: 22 | include: 23 | - os: ubuntu-latest 24 | - os: macos-latest 25 | steps: 26 | - name: brew install 27 | run: | 28 | brew install making/tap/rsc 29 | - name: check cli 30 | run: | 31 | rsc --version 32 | rsc --showSystemProperties 33 | rsc wss://demo.rsocket.io/rsocket --route searchTweets --stream -d Trump --take 3 --retry 3 --debug -------------------------------------------------------------------------------- /.github/workflows/scoop-bucket.yml: -------------------------------------------------------------------------------- 1 | name: Scoop-bucket 2 | on: 3 | release: 4 | types: 5 | - released 6 | workflow_dispatch: { } 7 | jobs: 8 | update-homebrew-tap: 9 | name: update scoop-bucket 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: download-windows-binaries 13 | id: download-windows-binaries 14 | uses: dsaltares/fetch-gh-release-asset@master 15 | with: 16 | repo: making/rsc 17 | version: "latest" 18 | file: rsc-x86_64-pc-win32.exe 19 | token: ${{ secrets.ACCESS_TOKEN }} 20 | - name: send a pull request 21 | env: 22 | REPOSITORY: making/scoop-bucket 23 | VERSION: ${{ steps.download-windows-binaries.outputs.version }} 24 | GIT_EMAIL: ${{ secrets.GIT_EMAIL }} 25 | GIT_NAME: ${{ secrets.GIT_NAME }} 26 | GIT_SSH_KEY: ${{ secrets.GIT_SSH_KEY }} 27 | ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} 28 | BRANCH_NAME: update-rsc 29 | BASE: main 30 | run: | 31 | set -ex 32 | mkdir -p ~/.ssh 33 | cat > ~/.ssh/config < ~/.ssh/id_rsa < rsc.json 49 | { 50 | "version": "${VERSION}", 51 | "description": "RSocket Client CLI (RSC) that aims to be a curl for RSocket", 52 | "homepage": "https://github.com/making/rsc", 53 | "license": "Apache-2.0", 54 | "architecture": { 55 | "64bit": { 56 | "url": "https://github.com/making/rsc/releases/download/${VERSION}/rsc-x86_64-pc-win32.exe#/rsc.exe", 57 | "hash": "$(shasum -a 256 ../rsc-x86_64-pc-win32.exe | awk '{print $1}')" 58 | } 59 | }, 60 | "bin": "rsc.exe", 61 | "post_install": "rsc -v", 62 | "checkver": { 63 | "github": "https://github.com/making/rsc" 64 | }, 65 | "autoupdate": { 66 | "architecture": { 67 | "64bit": { 68 | "url": "https://github.com/making/rsc/releases/download/\$version/rsc-x86_64-pc-win32.exe" 69 | } 70 | }, 71 | "hash": { 72 | "url": "\$url.sha256" 73 | } 74 | } 75 | } 76 | EOF 77 | git diff 78 | git add -A 79 | git commit -m "Bump rsc to ${VERSION}" 80 | git push origin ${BRANCH_NAME} 81 | curl -u ${GIT_NAME}:${ACCESS_TOKEN} -H "Content-Type: application/json" -X POST -d "{\"title\":\"Bump rsc to ${VERSION}\",\"body\":\"automatically created pr\",\"head\":\"${BRANCH_NAME}\",\"base\":\"${BASE}\"}" https://api.github.com/repos/${REPOSITORY}/pulls 82 | -------------------------------------------------------------------------------- /.github/workflows/scoop.yml: -------------------------------------------------------------------------------- 1 | name: Scoop 2 | on: 3 | schedule: 4 | - cron: 0 3 * * * 5 | push: 6 | branches: 7 | - master 8 | paths: 9 | - .github/workflows/scoop.yml 10 | pull_request: 11 | branches: 12 | - master 13 | paths: 14 | - .github/workflows/scoop.yml 15 | workflow_dispatch: { } 16 | jobs: 17 | install: 18 | name: scoop install 19 | runs-on: windows-latest 20 | steps: 21 | - name: check cli 22 | run: | 23 | Invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh') 24 | scoop bucket add making https://github.com/making/scoop-bucket.git 25 | scoop update 26 | scoop install rsc 27 | 28 | rsc --version 29 | rsc --showSystemProperties 30 | rsc wss://demo.rsocket.io/rsocket --route searchTweets --stream -d Trump --take 3 --retry 3 --debug -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | rsc-e2e -------------------------------------------------------------------------------- /.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2007-present 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 | import java.net.*; 18 | import java.io.*; 19 | import java.nio.channels.*; 20 | import java.util.Properties; 21 | 22 | public class MavenWrapperDownloader { 23 | 24 | private static final String WRAPPER_VERSION = "0.5.6"; 25 | 26 | /** 27 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 28 | */ 29 | private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" 30 | + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; 31 | 32 | /** 33 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 34 | * use instead of the default one. 35 | */ 36 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 37 | ".mvn/wrapper/maven-wrapper.properties"; 38 | 39 | /** 40 | * Path where the maven-wrapper.jar will be saved to. 41 | */ 42 | private static final String MAVEN_WRAPPER_JAR_PATH = 43 | ".mvn/wrapper/maven-wrapper.jar"; 44 | 45 | /** 46 | * Name of the property which should be used to override the default download url for the wrapper. 47 | */ 48 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 49 | 50 | public static void main(String args[]) { 51 | System.out.println("- Downloader started"); 52 | File baseDirectory = new File(args[0]); 53 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 54 | 55 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 56 | // wrapperUrl parameter. 57 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 58 | String url = DEFAULT_DOWNLOAD_URL; 59 | if (mavenWrapperPropertyFile.exists()) { 60 | FileInputStream mavenWrapperPropertyFileInputStream = null; 61 | try { 62 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 63 | Properties mavenWrapperProperties = new Properties(); 64 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 65 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 66 | } 67 | catch (IOException e) { 68 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 69 | } 70 | finally { 71 | try { 72 | if (mavenWrapperPropertyFileInputStream != null) { 73 | mavenWrapperPropertyFileInputStream.close(); 74 | } 75 | } 76 | catch (IOException e) { 77 | // Ignore ... 78 | } 79 | } 80 | } 81 | System.out.println("- Downloading from: " + url); 82 | 83 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 84 | if (!outputFile.getParentFile().exists()) { 85 | if (!outputFile.getParentFile().mkdirs()) { 86 | System.out.println( 87 | "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 88 | } 89 | } 90 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 91 | try { 92 | downloadFileFromURL(url, outputFile); 93 | System.out.println("Done"); 94 | System.exit(0); 95 | } 96 | catch (Throwable e) { 97 | System.out.println("- Error downloading"); 98 | e.printStackTrace(); 99 | System.exit(1); 100 | } 101 | } 102 | 103 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 104 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { 105 | String username = System.getenv("MVNW_USERNAME"); 106 | char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); 107 | Authenticator.setDefault(new Authenticator() { 108 | @Override 109 | protected PasswordAuthentication getPasswordAuthentication() { 110 | return new PasswordAuthentication(username, password); 111 | } 112 | }); 113 | } 114 | URL website = new URL(urlString); 115 | ReadableByteChannel rbc; 116 | rbc = Channels.newChannel(website.openStream()); 117 | FileOutputStream fos = new FileOutputStream(destination); 118 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 119 | fos.close(); 120 | rbc.close(); 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/making/rsc/491415384fb26306a464b730eb81280ed211ddac/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /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 2019 Toshiaki Maki 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 | # RSocket Client CLI (RSC) 2 | [![CI](https://github.com/making/rsc/workflows/CI/badge.svg)](https://github.com/making/rsc/actions?query=workflow%3ACI) 3 | 4 | > Aiming to be a curl for RSocket 5 | 6 | ``` 7 | usage: rsc [options] uri 8 | 9 | Non-option arguments: 10 | [String: uri] 11 | 12 | Option Description 13 | ------ ----------- 14 | --ab, --authBearer [String] Enable Authentication Metadata 15 | Extension (Bearer). 16 | --authBasic [String] [DEPRECATED] Enable Authentication 17 | Metadata Extension (Basic). This 18 | Metadata exists only for the 19 | backward compatibility with Spring 20 | Security 5.2 21 | --channel Shortcut of --im REQUEST_CHANNEL 22 | --completion [ShellType] Output shell completion code for the 23 | specified shell (bash, zsh, fish, 24 | powershell) 25 | -d, --data [String] Data. Use '-' to read data from 26 | standard input. (default: ) 27 | --dataMimeType, --dmt [String] MimeType for data (default: 28 | application/json) 29 | --debug Enable FrameLogger 30 | --delayElements [Long] Enable delayElements(delay) in milli 31 | seconds 32 | --dumpOpts Dump options as a file that can be 33 | loaded by --optsFile option 34 | --fnf Shortcut of --im FIRE_AND_FORGET 35 | -h, --help [String] Print help 36 | --im, --interactionModel InteractionModel (default: 37 | [InteractionModel] REQUEST_RESPONSE) 38 | -l, --load [String] Load a file as Data. (e.g. ./foo.txt, 39 | /tmp/foo.txt, https://example.com) 40 | --limitRate [Integer] Enable limitRate(rate) 41 | --log [String] Enable log() 42 | -m, --metadata [String] Metadata (default: ) 43 | --metadataMimeType, --mmt [String] MimeType for metadata (default: 44 | application/json) 45 | --optsFile [String] Configure options from a YAML file (e. 46 | g. ./opts.yaml, /tmp/opts.yaml, 47 | https://example.com/opts.yaml) 48 | --printB3 Print B3 propagation info. Ignored 49 | unless --trace is set. 50 | -q, --quiet Disable the output on next 51 | -r, --route [String] Enable Routing Metadata Extension 52 | --request Shortcut of --im REQUEST_RESPONSE 53 | --resume [Integer] Enable resume. Resume session duration 54 | can be configured in seconds. 55 | --retry [Integer] Enable retry. Retry every 1 second 56 | with the given max attempts. 57 | --sd, --setupData [String] Data for Setup payload 58 | --setupMetadata, --sm [String] Metadata for Setup payload 59 | --setupMetadataMimeType, --smmt Metadata MimeType for Setup payload 60 | [String] (default: application/json) 61 | --showSystemProperties Show SystemProperties for troubleshoot 62 | --stacktrace Show Stacktrace when an exception 63 | happens 64 | --stream Shortcut of --im REQUEST_STREAM 65 | --take [Integer] Enable take(n) 66 | --trace [TracingMetadataCodec$Flags] Enable Tracing (Zipkin) Metadata 67 | Extension. Unless sampling state 68 | (UNDECIDED, NOT_SAMPLE, SAMPLE, 69 | DEBUG) is specified, DEBUG is used 70 | if no state is specified. 71 | --trustCert [String] PEM file for a trusted certificate. (e. 72 | g. ./foo.crt, /tmp/foo.crt, https: 73 | //example.com/foo.crt) 74 | -u, --as, --authSimple [String] Enable Authentication Metadata 75 | Extension (Simple). The format must 76 | be 'username:password'. 77 | -v, --version Print version 78 | -w, --wiretap Enable wiretap 79 | --wsHeader, --wsh [String] Header for web socket connection 80 | --zipkinUrl [String] Zipkin URL to send a span (e.g. http: 81 | //localhost:9411). Ignored unless -- 82 | trace is set. 83 | ``` 84 | 85 | ## Install 86 | 87 | Download an executable jar or native binary from [Releases](https://github.com/making/rsc/releases). 88 | 89 | To get `rsc` binary working on Windows, you will need to install [Visual C++ Redistributable Packages](https://www.microsoft.com/en-us/download/details.aspx?id=48145) in advance. 90 | 91 | ### Install via Homebrew (Mac / Linux) 92 | [![Homebrew](https://github.com/making/rsc/workflows/Homebrew/badge.svg)](https://github.com/making/rsc/actions?query=workflow%3AHomebrew) 93 | 94 | You can install native binary for Mac or Linux via [Homebrew](https://brew.sh/). 95 | 96 | ``` 97 | brew install making/tap/rsc 98 | ``` 99 | 100 | ### Install via Scoop (Windows) 101 | [![Scoop](https://github.com/making/rsc/workflows/Scoop/badge.svg)](https://github.com/making/rsc/actions?query=workflow%3AScoop) 102 | 103 | You can install native binary for Windows via [Scoop](https://scoop.sh/). 104 | 105 | ``` 106 | scoop bucket add making https://github.com/making/scoop-bucket.git 107 | scoop update 108 | scoop install rsc 109 | ``` 110 | 111 | ### Install via Coursier (Mac / Linux / Windows) 112 | [![Coursier](https://github.com/making/rsc/workflows/Coursier/badge.svg)](https://github.com/making/rsc/actions?query=workflow%3ACoursier) 113 | 114 | If you do not already have [couriser](https://get-coursier.io) installed on your machine, install it following steps given here: https://get-coursier.io/docs/cli-installation. 115 | 116 | To install the graalvm binary do: 117 | 118 | ``` 119 | cs install rsc --contrib 120 | ``` 121 | 122 | To install the jvm binary (executable jar) do: 123 | 124 | ``` 125 | cs install rscj --contrib 126 | ``` 127 | 128 | ## Example usages 129 | 130 | ``` 131 | rsc --request --route=uppercase --data=Foo --debug tcp://localhost:7001 132 | ``` 133 | 134 | ``` 135 | rsc --stream --route=hello --debug --take=30 ws://localhost:8080/rsocket 136 | ``` 137 | 138 | ``` 139 | rsc --stream --route=searchTweets --data=Trump wss://demo.rsocket.io/rsocket 140 | ``` 141 | 142 | You can also send data via a file or URL using `-l`/`--load` option instead of `-d`/`--data` as follows 143 | 144 | ``` 145 | rsc --request --route=hello --load=./hello.txt --debug tcp://localhost:8080 146 | rsc --request --route=hello --load=/tmp/hello.txt --debug tcp://localhost:8080 147 | rsc --request --route=hello --load=https://example.com --debug tcp://localhost:8080 148 | ``` 149 | 150 | ## Enable shell autocompletion 151 | 152 | rsc (0.8.0+) provides autocompletion support for Bash, Zsh, Fish and Powershell. 153 | 154 | ``` 155 | rsc --completion 156 | ``` 157 | 158 | shows the completion script. 159 | 160 | ![rsc-completion](https://user-images.githubusercontent.com/106908/106292859-af40bc00-6290-11eb-9f76-99b0d5e2914a.gif) 161 | 162 | If you install `rsc` via Homebrew, the completion script is also installed under `/usr/local/Homebrew/completions/`. 163 | 164 | Below are the procedures to set up autocompletion manually. 165 | 166 | ### Zsh 167 | 168 | Add the following to the beginning of your `~/.zshrc` 169 | 170 | ``` 171 | autoload -Uz compinit && compinit 172 | ``` 173 | 174 | You now need to ensure that the rsc completion script gets sourced in all your shell sessions. 175 | 176 | ``` 177 | echo 'source <(rsc --completion bash)' >>~/.zshrc 178 | ``` 179 | 180 | ### Bash 181 | 182 | the completion script depends on [bash-completion](https://github.com/scop/bash-completion). 183 | 184 | #### on Mac 185 | 186 | the completion script doesn't work with Bash 3.2 which is the default bash version on Mac. 187 | It requires Bash 4.1+ and bash-completion v2. 188 | 189 | You can install these as follows 190 | 191 | ``` 192 | brew install bash 193 | brew install bash-completion@2 194 | ``` 195 | 196 | Make sure `bash -v` shows 4.1+. 197 | 198 | Add the bellow to your `~/.bash_profile` 199 | 200 | ``` 201 | [[ -r "/usr/local/etc/profile.d/bash_completion.sh" ]] && . "/usr/local/etc/profile.d/bash_completion.sh" 202 | ``` 203 | 204 | You now need to ensure that the rsc completion script gets sourced in all your shell sessions. 205 | 206 | ``` 207 | echo 'source <(rsc --completion bash)' >>~/.bash_profile 208 | ``` 209 | 210 | #### on Linux 211 | You can install bash-completion with `apt-get install bash-completion` or `yum install bash-completion`, etc. 212 | 213 | Add `source /usr/share/bash-completion/bash_completion` to your `~/.bashrc`. 214 | 215 | You now need to ensure that the rsc completion script gets sourced in all your shell sessions. 216 | 217 | ``` 218 | echo 'source <(rsc --completion bash)' >>~/.bashrc 219 | ``` 220 | 221 | ### Fish 222 | 223 | TBD (help wanted) 224 | 225 | ``` 226 | rsc --completion fish 227 | ``` 228 | 229 | ### Powershell 230 | 231 | ``` 232 | rsc --completion powershell | Out-String | Invoke-Expression 233 | ``` 234 | 235 | ## Log options 236 | 237 | ### Default 238 | 239 | By default, the data of the payload will be output (since 0.2.0). 240 | 241 | ``` 242 | $ rsc --route=add --data='{"x":10, "y":20}' tcp://localhost:7001 243 | {"result":30} 244 | ``` 245 | 246 | ### Enable Reactor's log() operator 247 | 248 | `--log` option enables Reactive Stream Level log. `--quiet`/`-q` option disables the default output. 249 | 250 | ``` 251 | $ rsc --route=add --data='{"x":10, "y":20}' --log --quiet tcp://localhost:7001 252 | 2021-02-06 17:50:15.809 INFO 95810 --- [actor-tcp-nio-2] rsc : onSubscribe(FluxMap.MapSubscriber) 253 | 2021-02-06 17:50:15.809 INFO 95810 --- [actor-tcp-nio-2] rsc : request(unbounded) 254 | 2021-02-06 17:50:15.820 INFO 95810 --- [actor-tcp-nio-2] rsc : onNext({"result":30}) 255 | 2021-02-06 17:50:15.820 INFO 95810 --- [actor-tcp-nio-2] rsc : onComplete() 256 | ``` 257 | 258 | ### Enable FrameLogger 259 | 260 | `--debug` option enables RSocket Level log. 261 | 262 | ``` 263 | $ rsc --route=add --data='{"x":10, "y":20}' --debug --quiet tcp://localhost:7001 264 | 2021-02-06 17:50:32.560 DEBUG 95820 --- [actor-tcp-nio-2] io.rsocket.FrameLogger : sending -> 265 | Frame => Stream ID: 0 Type: SETUP Flags: 0b0 Length: 75 266 | Data: 267 | 268 | 2021-02-06 17:50:32.560 DEBUG 95820 --- [actor-tcp-nio-2] io.rsocket.FrameLogger : sending -> 269 | Frame => Stream ID: 1 Type: REQUEST_RESPONSE Flags: 0b100000000 Length: 33 270 | Metadata: 271 | +-------------------------------------------------+ 272 | | 0 1 2 3 4 5 6 7 8 9 a b c d e f | 273 | +--------+-------------------------------------------------+----------------+ 274 | |00000000| fe 00 00 04 03 61 64 64 |.....add | 275 | +--------+-------------------------------------------------+----------------+ 276 | Data: 277 | +-------------------------------------------------+ 278 | | 0 1 2 3 4 5 6 7 8 9 a b c d e f | 279 | +--------+-------------------------------------------------+----------------+ 280 | |00000000| 7b 22 78 22 3a 31 30 2c 20 22 79 22 3a 32 30 7d |{"x":10, "y":20}| 281 | +--------+-------------------------------------------------+----------------+ 282 | 2021-02-06 17:50:32.571 DEBUG 95820 --- [actor-tcp-nio-2] io.rsocket.FrameLogger : receiving -> 283 | Frame => Stream ID: 1 Type: NEXT_COMPLETE Flags: 0b1100000 Length: 19 284 | Data: 285 | +-------------------------------------------------+ 286 | | 0 1 2 3 4 5 6 7 8 9 a b c d e f | 287 | +--------+-------------------------------------------------+----------------+ 288 | |00000000| 7b 22 72 65 73 75 6c 74 22 3a 33 30 7d |{"result":30} | 289 | +--------+-------------------------------------------------+----------------+ 290 | ``` 291 | 292 | ### Enable Reactor's wiretap 293 | 294 | `--wiretap`/`-w` option enables TCP Level log. 295 | 296 | 297 | ``` 298 | $ rsc --route=add --data='{"x":10, "y":20}' --wiretap --quiet tcp://localhost:7001 299 | 2021-02-06 17:51:20.140 DEBUG 95837 --- [actor-tcp-nio-2] reactor.netty.tcp.TcpClient : [id: 0x39d9fefe] REGISTERED 300 | 2021-02-06 17:51:20.141 DEBUG 95837 --- [actor-tcp-nio-2] reactor.netty.tcp.TcpClient : [id: 0x39d9fefe] CONNECT: localhost/127.0.0.1:7001 301 | 2021-02-06 17:51:20.141 DEBUG 95837 --- [actor-tcp-nio-2] reactor.netty.tcp.TcpClient : [id: 0x39d9fefe, L:/127.0.0.1:62801 - R:localhost/127.0.0.1:7001] ACTIVE 302 | 2021-02-06 17:51:20.141 DEBUG 95837 --- [actor-tcp-nio-2] reactor.netty.tcp.TcpClient : [id: 0x39d9fefe, L:/127.0.0.1:62801 - R:localhost/127.0.0.1:7001] WRITE: 78B 303 | +-------------------------------------------------+ 304 | | 0 1 2 3 4 5 6 7 8 9 a b c d e f | 305 | +--------+-------------------------------------------------+----------------+ 306 | |00000000| 00 00 4b 00 00 00 00 04 00 00 01 00 00 00 00 4e |..K............N| 307 | |00000010| 20 00 01 5f 90 27 6d 65 73 73 61 67 65 2f 78 2e | .._.'message/x.| 308 | |00000020| 72 73 6f 63 6b 65 74 2e 63 6f 6d 70 6f 73 69 74 |rsocket.composit| 309 | |00000030| 65 2d 6d 65 74 61 64 61 74 61 2e 76 30 10 61 70 |e-metadata.v0.ap| 310 | |00000040| 70 6c 69 63 61 74 69 6f 6e 2f 6a 73 6f 6e |plication/json | 311 | +--------+-------------------------------------------------+----------------+ 312 | 2021-02-06 17:51:20.142 DEBUG 95837 --- [actor-tcp-nio-2] reactor.netty.tcp.TcpClient : [id: 0x39d9fefe, L:/127.0.0.1:62801 - R:localhost/127.0.0.1:7001] WRITE: 36B 313 | +-------------------------------------------------+ 314 | | 0 1 2 3 4 5 6 7 8 9 a b c d e f | 315 | +--------+-------------------------------------------------+----------------+ 316 | |00000000| 00 00 21 00 00 00 01 11 00 00 00 08 fe 00 00 04 |..!.............| 317 | |00000010| 03 61 64 64 7b 22 78 22 3a 31 30 2c 20 22 79 22 |.add{"x":10, "y"| 318 | |00000020| 3a 32 30 7d |:20} | 319 | +--------+-------------------------------------------------+----------------+ 320 | 2021-02-06 17:51:20.142 DEBUG 95837 --- [actor-tcp-nio-2] reactor.netty.tcp.TcpClient : [id: 0x39d9fefe, L:/127.0.0.1:62801 - R:localhost/127.0.0.1:7001] FLUSH 321 | 2021-02-06 17:51:20.152 DEBUG 95837 --- [actor-tcp-nio-2] reactor.netty.tcp.TcpClient : [id: 0x39d9fefe, L:/127.0.0.1:62801 - R:localhost/127.0.0.1:7001] READ: 22B 322 | +-------------------------------------------------+ 323 | | 0 1 2 3 4 5 6 7 8 9 a b c d e f | 324 | +--------+-------------------------------------------------+----------------+ 325 | |00000000| 00 00 13 00 00 00 01 28 60 7b 22 72 65 73 75 6c |.......(`{"resul| 326 | |00000010| 74 22 3a 33 30 7d |t":30} | 327 | +--------+-------------------------------------------------+----------------+ 328 | 2021-02-06 17:51:20.152 DEBUG 95837 --- [actor-tcp-nio-2] reactor.netty.tcp.TcpClient : [id: 0x39d9fefe, L:/127.0.0.1:62801 - R:localhost/127.0.0.1:7001] READ COMPLETE 329 | ``` 330 | 331 | ## Setup payload 332 | 333 | The data in `SETUP` payload can be specified by `--setupData`/`--sd` option and metadata can be specified by `--setupMetaData`/`--smd`. 334 | Also the MIME type of the setup metadata can be specified by `--setupMetadataMimeType`/`--smmt` option. 335 | 336 | For example: 337 | 338 | ``` 339 | rsc --setupData=foo --setupMetadata='{"value":"metadata"}' --setupMetadataMimeType=application/json --route=add --data='{"x":10, "y":20}' tcp://localhost:7001 340 | ``` 341 | 342 | As of 0.6.0, the following MIME types are supported. 343 | 344 | * `application/json` (default) 345 | * `text/plain` 346 | * `message/x.rsocket.authentication.v0` 347 | * `message/x.rsocket.authentication.basic.v0` 348 | * `message/x.rsocket.application+json` (0.7.1+) 349 | 350 | Accordingly, enum name of [`SetupMetadataMimeType`](https://github.com/making/rsc/blob/master/src/main/java/am/ik/rsocket/SetupMetadataMimeType.java) instead can be used with `--smmt` option 351 | 352 | * `APPLICATION_JSON` 353 | * `TEXT_PLAIN` 354 | * `MESSAGE_RSOCKET_AUTHENTICATION` 355 | * `AUTHENTICATION_BASIC` 356 | * `APP_INFO` (0.7.1+) 357 | 358 | ## Composite Metadata 359 | 360 | `rsc` always uses [Composite Metadata Extension](https://github.com/rsocket/rsocket/blob/master/Extensions/CompositeMetadata.md). 361 | If multiple metadataMimeTypes are specified, they are automatically composed (the order matters). 362 | 363 | ``` 364 | $ rsc --metadataMimeType=text/plain --metadata=hello --metadataMimeType=application/json --metadata='{"hello":"world"}' --data='{"x":10, "y":20}' --wiretap --quiet tcp://localhost:7001 365 | 2021-02-06 18:00:29.100 DEBUG 95998 --- [actor-tcp-nio-2] reactor.netty.tcp.TcpClient : [id: 0x9c1425b3] REGISTERED 366 | 2021-02-06 18:00:29.101 DEBUG 95998 --- [actor-tcp-nio-2] reactor.netty.tcp.TcpClient : [id: 0x9c1425b3] CONNECT: localhost/127.0.0.1:7001 367 | 2021-02-06 18:00:29.101 DEBUG 95998 --- [actor-tcp-nio-2] reactor.netty.tcp.TcpClient : [id: 0x9c1425b3, L:/127.0.0.1:62844 - R:localhost/127.0.0.1:7001] ACTIVE 368 | 2021-02-06 18:00:29.102 DEBUG 95998 --- [actor-tcp-nio-2] reactor.netty.tcp.TcpClient : [id: 0x9c1425b3, L:/127.0.0.1:62844 - R:localhost/127.0.0.1:7001] WRITE: 78B 369 | +-------------------------------------------------+ 370 | | 0 1 2 3 4 5 6 7 8 9 a b c d e f | 371 | +--------+-------------------------------------------------+----------------+ 372 | |00000000| 00 00 4b 00 00 00 00 04 00 00 01 00 00 00 00 4e |..K............N| 373 | |00000010| 20 00 01 5f 90 27 6d 65 73 73 61 67 65 2f 78 2e | .._.'message/x.| 374 | |00000020| 72 73 6f 63 6b 65 74 2e 63 6f 6d 70 6f 73 69 74 |rsocket.composit| 375 | |00000030| 65 2d 6d 65 74 61 64 61 74 61 2e 76 30 10 61 70 |e-metadata.v0.ap| 376 | |00000040| 70 6c 69 63 61 74 69 6f 6e 2f 6a 73 6f 6e |plication/json | 377 | +--------+-------------------------------------------------+----------------+ 378 | 2021-02-06 18:00:29.102 DEBUG 95998 --- [actor-tcp-nio-2] reactor.netty.tcp.TcpClient : [id: 0x9c1425b3, L:/127.0.0.1:62844 - R:localhost/127.0.0.1:7001] WRITE: 58B 379 | +-------------------------------------------------+ 380 | | 0 1 2 3 4 5 6 7 8 9 a b c d e f | 381 | +--------+-------------------------------------------------+----------------+ 382 | |00000000| 00 00 37 00 00 00 01 11 00 00 00 1e a1 00 00 05 |..7.............| 383 | |00000010| 68 65 6c 6c 6f 85 00 00 11 7b 22 68 65 6c 6c 6f |hello....{"hello| 384 | |00000020| 22 3a 22 77 6f 72 6c 64 22 7d 7b 22 78 22 3a 31 |":"world"}{"x":1| 385 | |00000030| 30 2c 20 22 79 22 3a 32 30 7d |0, "y":20} | 386 | +--------+-------------------------------------------------+----------------+ 387 | 2021-02-06 18:00:29.102 DEBUG 95998 --- [actor-tcp-nio-2] reactor.netty.tcp.TcpClient : [id: 0x9c1425b3, L:/127.0.0.1:62844 - R:localhost/127.0.0.1:7001] FLUSH 388 | ... 389 | ``` 390 | 391 | `--route` option is still respected. 392 | 393 | ``` 394 | $ rsc --metadataMimeType=text/plain --metadata=hello --metadataMimeType=application/json --metadata='{"hello":"world"}' --route=add --data='{"x":10, "y":20}' --wiretap --quiet tcp://localhost:7001 395 | 2021-02-06 18:01:28.434 DEBUG 96015 --- [actor-tcp-nio-2] reactor.netty.tcp.TcpClient : [id: 0xf49f4cd5] REGISTERED 396 | 2021-02-06 18:01:28.435 DEBUG 96015 --- [actor-tcp-nio-2] reactor.netty.tcp.TcpClient : [id: 0xf49f4cd5] CONNECT: localhost/127.0.0.1:7001 397 | 2021-02-06 18:01:28.435 DEBUG 96015 --- [actor-tcp-nio-2] reactor.netty.tcp.TcpClient : [id: 0xf49f4cd5, L:/127.0.0.1:62848 - R:localhost/127.0.0.1:7001] ACTIVE 398 | 2021-02-06 18:01:28.436 DEBUG 96015 --- [actor-tcp-nio-2] reactor.netty.tcp.TcpClient : [id: 0xf49f4cd5, L:/127.0.0.1:62848 - R:localhost/127.0.0.1:7001] WRITE: 78B 399 | +-------------------------------------------------+ 400 | | 0 1 2 3 4 5 6 7 8 9 a b c d e f | 401 | +--------+-------------------------------------------------+----------------+ 402 | |00000000| 00 00 4b 00 00 00 00 04 00 00 01 00 00 00 00 4e |..K............N| 403 | |00000010| 20 00 01 5f 90 27 6d 65 73 73 61 67 65 2f 78 2e | .._.'message/x.| 404 | |00000020| 72 73 6f 63 6b 65 74 2e 63 6f 6d 70 6f 73 69 74 |rsocket.composit| 405 | |00000030| 65 2d 6d 65 74 61 64 61 74 61 2e 76 30 10 61 70 |e-metadata.v0.ap| 406 | |00000040| 70 6c 69 63 61 74 69 6f 6e 2f 6a 73 6f 6e |plication/json | 407 | +--------+-------------------------------------------------+----------------+ 408 | 2021-02-06 18:01:28.436 DEBUG 96015 --- [actor-tcp-nio-2] reactor.netty.tcp.TcpClient : [id: 0xf49f4cd5, L:/127.0.0.1:62848 - R:localhost/127.0.0.1:7001] WRITE: 66B 409 | +-------------------------------------------------+ 410 | | 0 1 2 3 4 5 6 7 8 9 a b c d e f | 411 | +--------+-------------------------------------------------+----------------+ 412 | |00000000| 00 00 3f 00 00 00 01 11 00 00 00 26 fe 00 00 04 |..?........&....| 413 | |00000010| 03 61 64 64 a1 00 00 05 68 65 6c 6c 6f 85 00 00 |.add....hello...| 414 | |00000020| 11 7b 22 68 65 6c 6c 6f 22 3a 22 77 6f 72 6c 64 |.{"hello":"world| 415 | |00000030| 22 7d 7b 22 78 22 3a 31 30 2c 20 22 79 22 3a 32 |"}{"x":10, "y":2| 416 | |00000040| 30 7d |0} | 417 | +--------+-------------------------------------------------+----------------+ 418 | 2021-02-06 18:01:28.437 DEBUG 96015 --- [actor-tcp-nio-2] reactor.netty.tcp.TcpClient : [id: 0xf49f4cd5, L:/127.0.0.1:62848 - R:localhost/127.0.0.1:7001] FLUSH 419 | ... 420 | ``` 421 | 422 | If you use `--route/-r` option, you need to specify to `--metadataMimeType/--mmt` option for the additional metadata even if the type is `application/json` which is the default mime type. 423 | 424 | For example: 425 | 426 | ``` 427 | rsc -r functionRouter --mmt application/json -m '{"function":"uppercase"}' -d 'RSocket' tcp://localhost:8080 428 | ``` 429 | 430 | ## Backpressure 431 | 432 | The `onNext` output can be delayed with the `--delayElements` (milli seconds) option. Accordingly, the number of `request` will be automatically adjusted. 433 | 434 | ``` 435 | $ rsc --stream --delayElements=100 --log --route=uppercase.stream --data=rsocket tcp://localhost:7001 436 | 2021-02-06 18:14:18.230 INFO 96438 --- [actor-tcp-nio-2] rsc : onSubscribe(FluxMap.MapSubscriber) 437 | 2021-02-06 18:14:18.230 INFO 96438 --- [actor-tcp-nio-2] rsc : request(32) 438 | 2021-02-06 18:14:18.235 INFO 96438 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 439 | 2021-02-06 18:14:18.235 INFO 96438 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 440 | 2021-02-06 18:14:18.235 INFO 96438 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 441 | 2021-02-06 18:14:18.235 INFO 96438 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 442 | 2021-02-06 18:14:18.235 INFO 96438 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 443 | 2021-02-06 18:14:18.235 INFO 96438 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 444 | 2021-02-06 18:14:18.235 INFO 96438 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 445 | 2021-02-06 18:14:18.235 INFO 96438 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 446 | 2021-02-06 18:14:18.235 INFO 96438 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 447 | 2021-02-06 18:14:18.235 INFO 96438 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 448 | 2021-02-06 18:14:18.235 INFO 96438 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 449 | 2021-02-06 18:14:18.235 INFO 96438 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 450 | 2021-02-06 18:14:18.235 INFO 96438 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 451 | 2021-02-06 18:14:18.235 INFO 96438 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 452 | 2021-02-06 18:14:18.235 INFO 96438 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 453 | 2021-02-06 18:14:18.235 INFO 96438 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 454 | 2021-02-06 18:14:18.235 INFO 96438 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 455 | 2021-02-06 18:14:18.235 INFO 96438 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 456 | 2021-02-06 18:14:18.235 INFO 96438 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 457 | 2021-02-06 18:14:18.235 INFO 96438 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 458 | 2021-02-06 18:14:18.235 INFO 96438 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 459 | 2021-02-06 18:14:18.235 INFO 96438 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 460 | 2021-02-06 18:14:18.236 INFO 96438 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 461 | 2021-02-06 18:14:18.236 INFO 96438 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 462 | 2021-02-06 18:14:18.236 INFO 96438 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 463 | 2021-02-06 18:14:18.236 INFO 96438 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 464 | 2021-02-06 18:14:18.236 INFO 96438 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 465 | 2021-02-06 18:14:18.236 INFO 96438 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 466 | 2021-02-06 18:14:18.236 INFO 96438 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 467 | 2021-02-06 18:14:18.236 INFO 96438 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 468 | 2021-02-06 18:14:18.236 INFO 96438 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 469 | 2021-02-06 18:14:18.236 INFO 96438 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 470 | RSOCKET 471 | RSOCKET 472 | RSOCKET 473 | RSOCKET 474 | RSOCKET 475 | RSOCKET 476 | RSOCKET 477 | RSOCKET 478 | RSOCKET 479 | RSOCKET 480 | RSOCKET 481 | RSOCKET 482 | RSOCKET 483 | RSOCKET 484 | RSOCKET 485 | RSOCKET 486 | RSOCKET 487 | RSOCKET 488 | RSOCKET 489 | RSOCKET 490 | RSOCKET 491 | RSOCKET 492 | RSOCKET 493 | 2021-02-06 18:14:20.595 INFO 96438 --- [ parallel-8] rsc : request(24) 494 | 2021-02-06 18:14:20.598 INFO 96438 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 495 | 2021-02-06 18:14:20.598 INFO 96438 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 496 | ... 497 | ``` 498 | 499 | You can also limit the number of `request` with `--limitRate` option. 500 | 501 | ``` 502 | $ rsc --stream --delayElements=100 --limitRate=8 --log --route=uppercase.stream --data=rsocket tcp://localhost:7001 503 | 2021-02-06 18:06:04.919 INFO 96118 --- [actor-tcp-nio-2] rsc : onSubscribe(FluxMap.MapSubscriber) 504 | 2021-02-06 18:06:04.919 INFO 96118 --- [actor-tcp-nio-2] rsc : request(8) 505 | 2021-02-06 18:06:04.922 INFO 96118 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 506 | 2021-02-06 18:06:04.922 INFO 96118 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 507 | 2021-02-06 18:06:04.922 INFO 96118 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 508 | 2021-02-06 18:06:04.922 INFO 96118 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 509 | 2021-02-06 18:06:04.922 INFO 96118 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 510 | 2021-02-06 18:06:04.922 INFO 96118 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 511 | 2021-02-06 18:06:04.922 INFO 96118 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 512 | 2021-02-06 18:06:04.922 INFO 96118 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 513 | RSOCKET 514 | RSOCKET 515 | RSOCKET 516 | RSOCKET 517 | RSOCKET 518 | 2021-02-06 18:06:05.435 INFO 96118 --- [ parallel-6] rsc : request(6) 519 | 2021-02-06 18:06:05.439 INFO 96118 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 520 | 2021-02-06 18:06:05.439 INFO 96118 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 521 | 2021-02-06 18:06:05.439 INFO 96118 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 522 | 2021-02-06 18:06:05.439 INFO 96118 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 523 | 2021-02-06 18:06:05.439 INFO 96118 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 524 | 2021-02-06 18:06:05.439 INFO 96118 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 525 | RSOCKET 526 | RSOCKET 527 | RSOCKET 528 | RSOCKET 529 | RSOCKET 530 | RSOCKET 531 | 2021-02-06 18:06:06.048 INFO 96118 --- [ parallel-12] rsc : request(6) 532 | 2021-02-06 18:06:06.050 INFO 96118 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 533 | 2021-02-06 18:06:06.050 INFO 96118 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 534 | 2021-02-06 18:06:06.050 INFO 96118 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 535 | 2021-02-06 18:06:06.050 INFO 96118 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 536 | 2021-02-06 18:06:06.050 INFO 96118 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 537 | 2021-02-06 18:06:06.050 INFO 96118 --- [actor-tcp-nio-2] rsc : onNext(RSOCKET) 538 | RSOCKET 539 | RSOCKET 540 | RSOCKET 541 | RSOCKET 542 | RSOCKET 543 | RSOCKET 544 | ... 545 | ``` 546 | 547 | **Tip**: Using `--limitRate 1 --delayElements 1000 --debug` is a convenient way to trace a stream. 548 | 549 | ## Authentication 550 | 551 | `rsc` supports [Authentication Extension](https://github.com/rsocket/rsocket/blob/master/Extensions/Security/Authentication.md) since 0.6.0. 552 | 553 | The demo application is [here](https://github.com/making/demo-rsocket-security). 554 | 555 | Note that since RSocket Java 1.0.3, [username field length is extended](https://github.com/rsocket/rsocket-java/pull/938). 556 | 557 | rsc 0.6.0 uses RSocket Java 1.0.2. To support extended username, rsc 0.7.0 which uses RSocket Java 1.1.0 or above is required. 558 | 559 | ### [Simple Authentication Type](https://github.com/rsocket/rsocket/blob/master/Extensions/Security/Simple.md) 560 | 561 | 562 | To send credentials per stream, use `--authSimple :` option as follows: 563 | 564 | ``` 565 | rsc tcp://localhost:8888 --authSimple user:password -r hello -d World 566 | ``` 567 | 568 | For shorter options, `--as` or `-u` (like `curl`!) are also available. 569 | 570 | ``` 571 | rsc tcp://localhost:8888 -u user:password -r hello -d World 572 | ``` 573 | 574 | To send credentials in `SETUP` payload, use `--sm simple:: --smmt message/x.rsocket.authentication.v0` as follows. 575 | 576 | ``` 577 | rsc tcp://localhost:8888 --sm simple:user:password --smmt message/x.rsocket.authentication.v0 -r hello -d World 578 | ``` 579 | 580 | slightly shorter version 581 | 582 | ``` 583 | rsc tcp://localhost:8888 --sm simple:user:password --smmt MESSAGE_RSOCKET_AUTHENTICATION -r hello -d World 584 | ``` 585 | 586 | ### [Bearer Token Authentication Type](https://github.com/rsocket/rsocket/blob/master/Extensions/Security/Bearer.md) 587 | 588 | To send token per stream, use `--authBearer ` option as follows: 589 | 590 | ``` 591 | rsc tcp://localhost:8888 --authBearer MY_TOKEN -r hello -d World 592 | ``` 593 | 594 | For shorter option, `--ab` is also available. 595 | 596 | To send credentials in `SETUP` payload, use `--sm token: --smmt message/x.rsocket.authentication.v0` as follows. 597 | 598 | ``` 599 | rsc tcp://localhost:8888 --sm token:MY_TOKEN --smmt message/x.rsocket.authentication.v0 -r hello -d World 600 | ``` 601 | 602 | slightly shorter version 603 | 604 | ``` 605 | rsc tcp://localhost:8888 --sm token:MY_TOKEN --smmt MESSAGE_RSOCKET_AUTHENTICATION -r hello -d World 606 | ``` 607 | 608 | ### Basic Authentication 609 | 610 | [Basic Authentication](https://github.com/rsocket/rsocket/issues/272) is not a part of Authentication Extension. 611 | It was implemented by Spring Security 5.2 before the spec was standardized. 612 | 613 | `rsc` supports Basic Authentication for the backward compatibility with Spring Security 5.2. 614 | 615 | To send credentials per stream, use `--authBasic :` option as follows: 616 | 617 | ``` 618 | rsc tcp://localhost:8888 --authBasic user:password -r hello -d World 619 | ``` 620 | 621 | To send credentials in `SETUP` payload, use `--sm : --smmt message/x.rsocket.authentication.basic.v0` as follows. 622 | 623 | ``` 624 | rsc tcp://localhost:8888 --sm user:password --smmt message/x.rsocket.authentication.basic.v0 -r hello -d World 625 | ``` 626 | 627 | slightly shorter version 628 | 629 | ``` 630 | rsc tcp://localhost:8888 --sm user:password --smmt AUTHENTICATION_BASIC -r hello -d World 631 | ``` 632 | 633 | ## Tracing 634 | 635 | `rsc` supports [Tracing (Zipkin) Metadata Extension](https://github.com/rsocket/rsocket/blob/master/Extensions/Tracing-Zipkin.md) since 0.5.0 636 | 637 | The demo application is [here](https://github.com/making/demo-rsocket-tracing). 638 | 639 | ``` 640 | $ rsc ws://localhost:8080/rsocket -r rr --trace --printB3 --zipkinUrl http://localhost:9411 641 | Hello World! 642 | b3=5f035ed7dd21129b105564ef64c90731-105564ef64c90731-d 643 | ``` 644 | 645 | ![image](https://user-images.githubusercontent.com/106908/86621556-5ad6a600-bff9-11ea-9040-8c300d2d8bcd.png) 646 | 647 | ## TODOs 648 | 649 | - [x] Support resuming (0.3.0) 650 | - [x] Support Composite Metadata (0.3.0) 651 | - [x] Setup data (0.4.0) 652 | - [x] Setup Metadata (0.6.0) 653 | - [x] RSocket Authentication (0.6.0) 654 | - [x] Request Channel (0.4.0) 655 | - [x] Input from a file (0.8.0) 656 | - [x] Input from STDIN (0.4.0) 657 | - [ ] RSocket Routing Broker 658 | - [ ] Client side responder 659 | 660 | ## Build 661 | 662 | ``` 663 | ./mvnw clean package -Pnative -DskipTests 664 | ``` 665 | 666 | A native binary will be created in `target/classes/rsc-(osx|linux|windows)-x86_64` depending on your OS. 667 | 668 | For linux binary, you can use Docker: 669 | 670 | ``` 671 | ./mvnw spring-boot:build-image -DskipTests 672 | docker run --rm rsc: --version 673 | ``` 674 | 675 | ### How to run E2E testing 676 | 677 | ``` 678 | git clone https://github.com/making/rsc-e2e 679 | cd rsc-e2e 680 | export RSC_PATH=... 681 | export RSC_OIDCISSUERURL=https://uaa.run.pivotal.io/oauth/token # you can change this 682 | export RSC_OIDCUSERNAME=... 683 | export RSC_OIDCPASSWORD=... 684 | ./mvnw test 685 | ``` 686 | 687 | ## License 688 | Licensed under the Apache License, Version 2.0. 689 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Mingw, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | fi 118 | 119 | if [ -z "$JAVA_HOME" ]; then 120 | javaExecutable="`which javac`" 121 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 122 | # readlink(1) is not available as standard on Solaris 10. 123 | readLink=`which readlink` 124 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 125 | if $darwin ; then 126 | javaHome="`dirname \"$javaExecutable\"`" 127 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 128 | else 129 | javaExecutable="`readlink -f \"$javaExecutable\"`" 130 | fi 131 | javaHome="`dirname \"$javaExecutable\"`" 132 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 133 | JAVA_HOME="$javaHome" 134 | export JAVA_HOME 135 | fi 136 | fi 137 | fi 138 | 139 | if [ -z "$JAVACMD" ] ; then 140 | if [ -n "$JAVA_HOME" ] ; then 141 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 142 | # IBM's JDK on AIX uses strange locations for the executables 143 | JAVACMD="$JAVA_HOME/jre/sh/java" 144 | else 145 | JAVACMD="$JAVA_HOME/bin/java" 146 | fi 147 | else 148 | JAVACMD="`which java`" 149 | fi 150 | fi 151 | 152 | if [ ! -x "$JAVACMD" ] ; then 153 | echo "Error: JAVA_HOME is not defined correctly." >&2 154 | echo " We cannot execute $JAVACMD" >&2 155 | exit 1 156 | fi 157 | 158 | if [ -z "$JAVA_HOME" ] ; then 159 | echo "Warning: JAVA_HOME environment variable is not set." 160 | fi 161 | 162 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 163 | 164 | # traverses directory structure from process work directory to filesystem root 165 | # first directory with .mvn subdirectory is considered project base directory 166 | find_maven_basedir() { 167 | 168 | if [ -z "$1" ] 169 | then 170 | echo "Path not specified to find_maven_basedir" 171 | return 1 172 | fi 173 | 174 | basedir="$1" 175 | wdir="$1" 176 | while [ "$wdir" != '/' ] ; do 177 | if [ -d "$wdir"/.mvn ] ; then 178 | basedir=$wdir 179 | break 180 | fi 181 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 182 | if [ -d "${wdir}" ]; then 183 | wdir=`cd "$wdir/.."; pwd` 184 | fi 185 | # end of workaround 186 | done 187 | echo "${basedir}" 188 | } 189 | 190 | # concatenates all lines of a file 191 | concat_lines() { 192 | if [ -f "$1" ]; then 193 | echo "$(tr -s '\n' ' ' < "$1")" 194 | fi 195 | } 196 | 197 | BASE_DIR=`find_maven_basedir "$(pwd)"` 198 | if [ -z "$BASE_DIR" ]; then 199 | exit 1; 200 | fi 201 | 202 | ########################################################################################## 203 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 204 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 205 | ########################################################################################## 206 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 207 | if [ "$MVNW_VERBOSE" = true ]; then 208 | echo "Found .mvn/wrapper/maven-wrapper.jar" 209 | fi 210 | else 211 | if [ "$MVNW_VERBOSE" = true ]; then 212 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 213 | fi 214 | if [ -n "$MVNW_REPOURL" ]; then 215 | jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 216 | else 217 | jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 218 | fi 219 | while IFS="=" read key value; do 220 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 221 | esac 222 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 223 | if [ "$MVNW_VERBOSE" = true ]; then 224 | echo "Downloading from: $jarUrl" 225 | fi 226 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 227 | if $cygwin; then 228 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 229 | fi 230 | 231 | if command -v wget > /dev/null; then 232 | if [ "$MVNW_VERBOSE" = true ]; then 233 | echo "Found wget ... using wget" 234 | fi 235 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 236 | wget "$jarUrl" -O "$wrapperJarPath" 237 | else 238 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" 239 | fi 240 | elif command -v curl > /dev/null; then 241 | if [ "$MVNW_VERBOSE" = true ]; then 242 | echo "Found curl ... using curl" 243 | fi 244 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 245 | curl -o "$wrapperJarPath" "$jarUrl" -f 246 | else 247 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f 248 | fi 249 | 250 | else 251 | if [ "$MVNW_VERBOSE" = true ]; then 252 | echo "Falling back to using Java to download" 253 | fi 254 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 255 | # For Cygwin, switch paths to Windows format before running javac 256 | if $cygwin; then 257 | javaClass=`cygpath --path --windows "$javaClass"` 258 | fi 259 | if [ -e "$javaClass" ]; then 260 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 261 | if [ "$MVNW_VERBOSE" = true ]; then 262 | echo " - Compiling MavenWrapperDownloader.java ..." 263 | fi 264 | # Compiling the Java class 265 | ("$JAVA_HOME/bin/javac" "$javaClass") 266 | fi 267 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 268 | # Running the downloader 269 | if [ "$MVNW_VERBOSE" = true ]; then 270 | echo " - Running MavenWrapperDownloader.java ..." 271 | fi 272 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 273 | fi 274 | fi 275 | fi 276 | fi 277 | ########################################################################################## 278 | # End of extension 279 | ########################################################################################## 280 | 281 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 282 | if [ "$MVNW_VERBOSE" = true ]; then 283 | echo $MAVEN_PROJECTBASEDIR 284 | fi 285 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 286 | 287 | # For Cygwin, switch paths to Windows format before running java 288 | if $cygwin; then 289 | [ -n "$M2_HOME" ] && 290 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 291 | [ -n "$JAVA_HOME" ] && 292 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 293 | [ -n "$CLASSPATH" ] && 294 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 295 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 296 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 297 | fi 298 | 299 | # Provide a "standardized" way to retrieve the CLI args that will 300 | # work with both Windows and non-Windows executions. 301 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 302 | export MAVEN_CMD_LINE_ARGS 303 | 304 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 305 | 306 | exec "$JAVACMD" \ 307 | $MAVEN_OPTS \ 308 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 309 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 310 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 311 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 124 | 125 | FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 162 | if ERRORLEVEL 1 goto error 163 | goto end 164 | 165 | :error 166 | set ERROR_CODE=1 167 | 168 | :end 169 | @endlocal & set ERROR_CODE=%ERROR_CODE% 170 | 171 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 172 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 173 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 174 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 175 | :skipRcPost 176 | 177 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 178 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 179 | 180 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 181 | 182 | exit /B %ERROR_CODE% 183 | -------------------------------------------------------------------------------- /next-development.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | VERSION=$1 4 | 5 | if [ "${VERSION}" = "" ];then 6 | echo "usage: $0 VERSION" 7 | exit 1 8 | fi 9 | 10 | set -x 11 | 12 | VERSION=${VERSION}-SNAPSHOT 13 | mvn versions:set -DnewVersion=${VERSION} -DallowSnapshots -DgenerateBackupPoms=false 14 | git commit -m "Bump to ${VERSION}" pom.xml 15 | set +x 16 | echo "Run: git push origin master" -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.springframework.boot 8 | spring-boot-starter-parent 9 | 2.5.2 10 | 11 | 12 | am.ik.rsocket 13 | rsc 14 | 0.9.2-SNAPSHOT 15 | rsc 16 | RSocket Client CLI 17 | 18 | 19 | 1.8 20 | 0.10.1 21 | 22 | false 23 | 24 | --enable-all-security-services 25 | --enable-https 26 | --enable-http 27 | -Dfile.encoding=UTF-8 28 | -H:IncludeResourceBundles=joptsimple.HelpFormatterMessages 29 | -H:IncludeResourceBundles=joptsimple.ExceptionMessages 30 | -H:IncludeResources=completions/.* 31 | 32 | 33 | 34 | 35 | 36 | org.springframework.experimental 37 | spring-native 38 | ${spring-native.version} 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-rsocket 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-starter-json 47 | 48 | 49 | org.springframework 50 | spring-messaging 51 | 52 | 53 | com.fasterxml.jackson.dataformat 54 | jackson-dataformat-cbor 55 | 56 | 57 | org.apache.logging.log4j 58 | log4j-to-slf4j 59 | 60 | 61 | org.slf4j 62 | jul-to-slf4j 63 | 64 | 65 | jakarta.annotation 66 | annotations-api 67 | 68 | 69 | 70 | 71 | net.sf.jopt-simple 72 | jopt-simple 73 | 6.0-alpha-3 74 | 75 | 76 | org.springframework.boot 77 | spring-boot-starter-test 78 | test 79 | 80 | 81 | io.projectreactor 82 | reactor-test 83 | test 84 | 85 | 86 | 87 | 88 | 89 | 90 | kr.motd.maven 91 | os-maven-plugin 92 | 1.7.0 93 | 94 | 95 | 96 | 97 | org.apache.maven.plugins 98 | maven-antrun-plugin 99 | 3.0.0 100 | 101 | 102 | gen-build-info 103 | process-resources 104 | 105 | run 106 | 107 | 108 | ${version-generate-skip} 109 | 110 | 111 | package am.ik.rsocket; 112 | public class Version { 113 | public static final String getVersion() { 114 | return "${project.version}"; 115 | } 116 | public static final String getBuild() { 117 | return "${maven.build.timestamp}"; 118 | } 119 | public static final String getRSocketJava() { 120 | return "${rsocket.version}"; 121 | } 122 | public static final String getVersionAsJson() { 123 | return "{\"version\": \"${project.version}\", \"build\": \"${maven.build.timestamp}\", \"rsocket-java\": \"${rsocket.version}\"}"; 124 | } 125 | } 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | org.codehaus.mojo 134 | build-helper-maven-plugin 135 | 136 | 137 | test 138 | generate-sources 139 | 140 | add-source 141 | 142 | 143 | 144 | ${project.build.directory}/generated-sources 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | org.springframework.boot 153 | spring-boot-maven-plugin 154 | 155 | ${repackage.classifier} 156 | 157 | paketobuildpacks/builder:tiny 158 | 159 | true 160 | ${native-build.args} 161 | 162 | 163 | 164 | 165 | 166 | 167 | org.springframework.experimental 168 | spring-aot-maven-plugin 169 | ${spring-native.version} 170 | 171 | true 172 | true 173 | 174 | 175 | 176 | test-generate 177 | 178 | test-generate 179 | 180 | 181 | 182 | generate 183 | 184 | generate 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | native 194 | 195 | exec 196 | 0.9.1 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | org.graalvm.buildtools 210 | native-maven-plugin 211 | ${native-buildtools.version} 212 | 213 | am.ik.rsocket.RscApplication 214 | ${native-build.args} 215 | ${project.artifactId}-${os.detected.classifier} 216 | 217 | ${basedir}/target/classes 218 | 219 | 220 | 221 | test-native 222 | test 223 | 224 | test 225 | 226 | 227 | 228 | build-native 229 | package 230 | 231 | build 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | central 243 | https://repo.maven.apache.org/maven2 244 | 245 | false 246 | 247 | 248 | 249 | spring-release 250 | Spring release 251 | https://repo.spring.io/release 252 | 253 | false 254 | 255 | 256 | 257 | spring-snapshot 258 | Spring Snapshots 259 | https://repo.spring.io/snapshot 260 | 261 | true 262 | 263 | 264 | 265 | spring-milestone 266 | Spring Milestone 267 | https://repo.spring.io/milestone 268 | 269 | false 270 | 271 | 272 | 273 | 274 | 275 | central 276 | https://repo.maven.apache.org/maven2 277 | 278 | false 279 | 280 | 281 | 282 | spring-release 283 | Spring release 284 | https://repo.spring.io/release 285 | 286 | false 287 | 288 | 289 | 290 | spring-snapshot 291 | Spring Snapshots 292 | https://repo.spring.io/snapshot 293 | 294 | true 295 | 296 | 297 | 298 | spring-milestone 299 | Spring Milestone 300 | https://repo.spring.io/milestone 301 | 302 | false 303 | 304 | 305 | 306 | 307 | -------------------------------------------------------------------------------- /prepare-release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | VERSION=$1 4 | 5 | if [ "${VERSION}" = "" ];then 6 | echo "usage: $0 VERSION" 7 | exit 1 8 | fi 9 | 10 | set -x 11 | 12 | mvn versions:set -DnewVersion=${VERSION} -DgenerateBackupPoms=false 13 | git commit -m "Release ${VERSION}" pom.xml 14 | git tag ${VERSION} 15 | #./build-all-binaries.sh 16 | set +x 17 | echo "Run: git push origin ${VERSION} && git push origin master" -------------------------------------------------------------------------------- /quick-e2e.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | RSC=$1 4 | if [ "${RSC}" == "" ];then 5 | RSC=rsc 6 | fi 7 | OS=$2 8 | if [ "${OS}" == "" ];then 9 | OS=macOS 10 | fi 11 | 12 | if [ ! -f ./rsc-e2e ];then 13 | echo ">>> Download rsc-e2e" 14 | wget -qO rsc-e2e https://github.com/making/rsc-e2e/releases/download/0.1.0/rsc-e2e-${OS} 15 | fi 16 | chmod +x rsc-e2e 17 | ./rsc-e2e & 18 | 19 | while ! `perl -mIO::Socket::INET -le 'exit(IO::Socket::INET->new(PeerAddr=>shift,PeerPort=>shift,Proto=>shift,Timeout=>5)?0:1)' localhost 7001`; do 20 | sleep 1 21 | done 22 | 23 | ${RSC} -v 24 | ${RSC} --showSystemProperties 25 | 26 | echo ">>> Test Fire and Forget" 27 | DEBUG=`${RSC} --fnf -r uppercase.fnf -d hello -q --debug tcp://localhost:7001` 28 | set -x 29 | echo "$DEBUG" | grep "Stream ID: 1 Type: REQUEST_FNF" > /dev/null 30 | set +x 31 | 32 | echo ">>> Test Request Response" 33 | ${RSC} -r uppercase.rr -d hello tcp://localhost:7001 | grep "^HELLO$" > /dev/null 34 | DEBUG=`${RSC} -r uppercase.rr -d hello -q --debug tcp://localhost:7001` 35 | set -x 36 | echo "$DEBUG" | grep "Stream ID: 1 Type: REQUEST_RESPONSE" > /dev/null 37 | echo "$DEBUG" | grep "Stream ID: 1 Type: NEXT_COMPLETE" > /dev/null 38 | set +x 39 | 40 | echo ">>> Test Request Stream" 41 | ${RSC} --stream -r uppercase.stream -d hello --limitRate 3 --take 3 tcp://localhost:7001 | grep "^HELLO$" | wc -l | grep "3$" > /dev/null 42 | DEBUG=`${RSC} --stream -r uppercase.stream -d hello --limitRate 3 --take 3 -q --debug tcp://localhost:7001` 43 | set -x 44 | echo "$DEBUG" | grep "Stream ID: 1 Type: REQUEST_STREAM" > /dev/null 45 | echo "$DEBUG" | grep "Stream ID: 1 Type: NEXT" | wc -l | grep "3$" > /dev/null 46 | echo "$DEBUG" | grep "Stream ID: 1 Type: CANCEL" > /dev/null 47 | set +x 48 | 49 | echo ">>> Test Request Channel" 50 | FILE=$(mktemp) 51 | cat < ${FILE} 52 | abc 53 | def 54 | ghi 55 | jkl 56 | EOF 57 | CHANNEL=`cat ${FILE} | ${RSC} --channel -r uppercase.channel -d - tcp://localhost:7001` 58 | DEBUG=`cat ${FILE} | ${RSC} --channel -r uppercase.channel -d - -q --debug tcp://localhost:7001` 59 | set -x 60 | echo "$CHANNEL" | grep "^ABC$" > /dev/null 61 | echo "$CHANNEL" | grep "^DEF$" > /dev/null 62 | echo "$CHANNEL" | grep "^GHI$" > /dev/null 63 | echo "$CHANNEL" | grep "^JKL$" > /dev/null 64 | echo "$DEBUG" | grep "Stream ID: 1 Type: REQUEST_CHANNEL" > /dev/null 65 | echo "$DEBUG" | grep "Stream ID: 1 Type: NEXT" | wc -l | grep "7$" > /dev/null 66 | echo "$DEBUG" | grep "Stream ID: 1 Type: REQUEST_N" > /dev/null 67 | echo "$DEBUG" | grep "Stream ID: 1 Type: COMPLETE" | wc -l | grep "2$" > /dev/null 68 | set +x 69 | 70 | pkill -KILL rsc-e2e 71 | echo "✅ E2E test succeeded!" -------------------------------------------------------------------------------- /src/main/java/am/ik/rsocket/Args.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Toshiaki Maki 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 | * http://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 | package am.ik.rsocket; 17 | 18 | import java.io.IOException; 19 | import java.io.InputStream; 20 | import java.io.PrintStream; 21 | import java.io.UncheckedIOException; 22 | import java.net.URI; 23 | import java.nio.charset.StandardCharsets; 24 | import java.security.GeneralSecurityException; 25 | import java.security.cert.CertificateFactory; 26 | import java.security.cert.X509Certificate; 27 | import java.time.Duration; 28 | import java.util.ArrayList; 29 | import java.util.Arrays; 30 | import java.util.Iterator; 31 | import java.util.LinkedHashMap; 32 | import java.util.LinkedHashSet; 33 | import java.util.List; 34 | import java.util.Map; 35 | import java.util.Objects; 36 | import java.util.Optional; 37 | import java.util.Set; 38 | import java.util.TreeMap; 39 | 40 | import am.ik.rsocket.completion.ShellType; 41 | import am.ik.rsocket.file.FileSystemResourceLoader; 42 | import am.ik.rsocket.routing.Route; 43 | import am.ik.rsocket.security.BasicAuthentication; 44 | import am.ik.rsocket.security.BearerAuthentication; 45 | import am.ik.rsocket.security.SimpleAuthentication; 46 | import am.ik.rsocket.tracing.Span; 47 | import am.ik.rsocket.tracing.Tracing; 48 | import io.netty.buffer.ByteBuf; 49 | import io.netty.buffer.ByteBufAllocator; 50 | import io.netty.buffer.CompositeByteBuf; 51 | import io.netty.buffer.PooledByteBufAllocator; 52 | import io.netty.buffer.Unpooled; 53 | import io.netty.handler.ssl.SslContextBuilder; 54 | import io.rsocket.Payload; 55 | import io.rsocket.metadata.CompositeMetadataCodec; 56 | import io.rsocket.metadata.TracingMetadataCodec.Flags; 57 | import io.rsocket.metadata.WellKnownMimeType; 58 | import io.rsocket.transport.ClientTransport; 59 | import io.rsocket.util.DefaultPayload; 60 | import joptsimple.HelpFormatter; 61 | import joptsimple.OptionParser; 62 | import joptsimple.OptionSet; 63 | import joptsimple.OptionSpec; 64 | import org.yaml.snakeyaml.DumperOptions; 65 | import org.yaml.snakeyaml.DumperOptions.FlowStyle; 66 | import org.yaml.snakeyaml.Yaml; 67 | import reactor.netty.tcp.TcpClient; 68 | import reactor.util.function.Tuple2; 69 | import reactor.util.function.Tuples; 70 | 71 | import org.springframework.core.io.Resource; 72 | import org.springframework.util.StreamUtils; 73 | import org.springframework.util.StringUtils; 74 | 75 | import static am.ik.rsocket.Transport.TCP; 76 | import static am.ik.rsocket.Transport.WEBSOCKET; 77 | import static java.util.stream.Collectors.toList; 78 | 79 | public class Args { 80 | 81 | private final OptionParser parser = new OptionParser(); 82 | 83 | private final OptionSpec version = parser.acceptsAll(Arrays.asList("v", "version"), "Print version"); 84 | 85 | private final OptionSpec help = parser.acceptsAll(Arrays.asList("h", "help"), "Print help").withOptionalArg(); 86 | 87 | private final OptionSpec wiretap = parser.acceptsAll(Arrays.asList("w", "wiretap"), "Enable wiretap"); 88 | 89 | private final OptionSpec debug = parser.acceptsAll(Arrays.asList("debug"), "Enable FrameLogger"); 90 | 91 | private final OptionSpec quiet = parser.acceptsAll(Arrays.asList("q", "quiet"), "Disable the output on next"); 92 | 93 | private final OptionSpec interactionModel = parser 94 | .acceptsAll(Arrays.asList("im", "interactionModel"), "InteractionModel").withOptionalArg() 95 | .ofType(InteractionModel.class).defaultsTo(InteractionModel.REQUEST_RESPONSE); 96 | 97 | private final OptionSpec stream = parser.acceptsAll(Arrays.asList("stream"), 98 | "Shortcut of --im REQUEST_STREAM"); 99 | 100 | private final OptionSpec request = parser.acceptsAll(Arrays.asList("request"), 101 | "Shortcut of --im REQUEST_RESPONSE"); 102 | 103 | private final OptionSpec fnf = parser.acceptsAll(Arrays.asList("fnf"), "Shortcut of --im FIRE_AND_FORGET"); 104 | 105 | private final OptionSpec channel = parser.acceptsAll(Arrays.asList("channel"), 106 | "Shortcut of --im REQUEST_CHANNEL"); 107 | 108 | private final OptionSpec resume = parser.acceptsAll(Arrays.asList("resume"), 109 | "Enable resume. Resume session duration can be configured in seconds.") 110 | .withOptionalArg().ofType(Integer.class); 111 | 112 | private final OptionSpec retry = parser.acceptsAll(Arrays.asList("retry"), "Enable retry. Retry every 1 second with the given max attempts.") 113 | .withOptionalArg().ofType(Integer.class); 114 | 115 | private final URI uri; 116 | 117 | private final OptionSpec dataMimeType = parser 118 | .acceptsAll(Arrays.asList("dataMimeType", "dmt"), "MimeType for data").withOptionalArg() 119 | .defaultsTo(WellKnownMimeType.APPLICATION_JSON.getString()); 120 | 121 | private final OptionSpec metadataMimeType = parser 122 | .acceptsAll(Arrays.asList("metadataMimeType", "mmt"), "MimeType for metadata (default: application/json)") 123 | .withOptionalArg(); 124 | 125 | 126 | private final OptionSpec data = parser 127 | .acceptsAll(Arrays.asList("d", "data"), "Data. Use '-' to read data from standard input.").withOptionalArg() 128 | .defaultsTo(""); 129 | 130 | private final OptionSpec load = parser 131 | .acceptsAll(Arrays.asList("l", "load"), "Load a file as Data. (e.g. ./foo.txt, /tmp/foo.txt, https://example.com)").withOptionalArg(); 132 | 133 | private final OptionSpec metadata = parser 134 | .acceptsAll(Arrays.asList("m", "metadata"), "Metadata (default: )").withOptionalArg(); 135 | 136 | private final OptionSpec setupData = parser.acceptsAll(Arrays.asList("setupData", "sd"), "Data for Setup payload") 137 | .withOptionalArg(); 138 | 139 | private final OptionSpec setupMetadata = parser.acceptsAll(Arrays.asList("sm", "setupMetadata"), "Metadata for Setup payload") 140 | .withOptionalArg(); 141 | 142 | private final OptionSpec setupMetadataMimeType = parser.acceptsAll(Arrays.asList("smmt", "setupMetadataMimeType"), "Metadata MimeType for Setup payload (default: application/json)") 143 | .withOptionalArg(); 144 | 145 | private final OptionSpec route = parser 146 | .acceptsAll(Arrays.asList("route", "r"), "Enable Routing Metadata Extension").withOptionalArg(); 147 | 148 | private final OptionSpec authSimple = parser 149 | .acceptsAll(Arrays.asList("authSimple", "as", "u"), "Enable Authentication Metadata Extension (Simple). The format must be 'username:password'.").withOptionalArg(); 150 | 151 | private final OptionSpec authBearer = parser 152 | .acceptsAll(Arrays.asList("authBearer", "ab"), "Enable Authentication Metadata Extension (Bearer).").withOptionalArg(); 153 | 154 | private final OptionSpec authBasic = parser 155 | .acceptsAll(Arrays.asList("authBasic"), "[DEPRECATED] Enable Authentication Metadata Extension (Basic). This Metadata exists only for the backward compatibility with Spring Security 5.2").withOptionalArg(); 156 | 157 | private final OptionSpec trace = parser 158 | .acceptsAll(Arrays.asList("trace"), "Enable Tracing (Zipkin) Metadata Extension. Unless sampling state (UNDECIDED, NOT_SAMPLE, SAMPLE, DEBUG) is specified, DEBUG is used if no state is specified.") 159 | .withOptionalArg().ofType(Flags.class); 160 | 161 | private final OptionSpec zipkinUrl = parser 162 | .acceptsAll(Arrays.asList("zipkinUrl"), "Zipkin URL to send a span (e.g. http://localhost:9411). Ignored unless --trace is set.").withOptionalArg(); 163 | 164 | private final OptionSpec printB3 = parser.acceptsAll(Arrays.asList("printB3"), "Print B3 propagation info. Ignored unless --trace is set."); 165 | 166 | private final OptionSpec log = parser.acceptsAll(Arrays.asList("log"), "Enable log()").withOptionalArg(); 167 | 168 | private final OptionSpec limitRate = parser 169 | .acceptsAll(Arrays.asList("limitRate"), "Enable limitRate(rate)").withOptionalArg().ofType(Integer.class); 170 | 171 | private final OptionSpec take = parser.acceptsAll(Arrays.asList("take"), "Enable take(n)") 172 | .withOptionalArg().ofType(Integer.class); 173 | 174 | private final OptionSpec delayElements = parser 175 | .acceptsAll(Arrays.asList("delayElements"), "Enable delayElements(delay) in milli seconds") 176 | .withOptionalArg().ofType(Long.class); 177 | 178 | private final OptionSpec trustCert = parser 179 | .acceptsAll(Arrays.asList("trustCert"), "PEM file for a trusted certificate. (e.g. ./foo.crt, /tmp/foo.crt, https://example.com/foo.crt)").withOptionalArg(); 180 | 181 | private final OptionSpec stacktrace = parser.acceptsAll(Arrays.asList("stacktrace"), 182 | "Show Stacktrace when an exception happens"); 183 | 184 | private final OptionSpec showSystemProperties = parser.acceptsAll(Arrays.asList("showSystemProperties"), 185 | "Show SystemProperties for troubleshoot"); 186 | 187 | private final OptionSpec wsHeader = parser.acceptsAll(Arrays.asList("wsh", "wsHeader"), "Header for web socket connection") 188 | .withOptionalArg(); 189 | 190 | private final OptionSpec completion = parser 191 | .acceptsAll(Arrays.asList("completion"), "Output shell completion code for the specified shell (bash, zsh, fish, powershell)") 192 | .withOptionalArg().ofType(ShellType.class); 193 | 194 | private final OptionSpec optsFile = parser 195 | .acceptsAll(Arrays.asList("optsFile"), "Configure options from a YAML file (e.g. ./opts.yaml, /tmp/opts.yaml, https://example.com/opts.yaml)").withOptionalArg(); 196 | 197 | private final OptionSpec dumpOpts = parser.acceptsAll(Arrays.asList("dumpOpts"), "Dump options as a file that can be loaded by --optsFile option"); 198 | 199 | private OptionSet options; 200 | 201 | private String[] args; 202 | 203 | private Tuple2 composedMetadata = null; 204 | 205 | private Span span; 206 | 207 | public static final String DEFAULT_METADATA_MIME_TYPE = WellKnownMimeType.APPLICATION_JSON.getString(); 208 | 209 | public Args(String[] args) { 210 | final OptionSpec uri = parser.nonOptions().describedAs("uri"); 211 | this.args = args; 212 | this.options = parser.parse(args); 213 | this.argsFile().ifPresent(fileArgs -> { 214 | fileArgs.addAll(Arrays.asList(args)); 215 | final String[] newArgs = fileArgs.toArray(new String[0]); 216 | this.options = parser.parse(newArgs); 217 | this.args = newArgs; 218 | }); 219 | this.uri = Optional.ofNullable(uri.value(this.options)).map(URI::create).orElse(null); 220 | } 221 | 222 | public Args(String args) { 223 | this(args.split("\\s")); 224 | } 225 | 226 | public boolean hasUri() { 227 | return this.uri != null; 228 | } 229 | 230 | public String host() { 231 | return this.uri.getHost(); 232 | } 233 | 234 | public int port() { 235 | final int port = this.uri.getPort(); 236 | if (port < 0) { 237 | if (secure()) { 238 | return 443; 239 | } 240 | else { 241 | return 80; 242 | } 243 | } 244 | return port; 245 | } 246 | 247 | public boolean secure() { 248 | final String scheme = this.uri.getScheme(); 249 | return scheme.endsWith("+tls") || scheme.equals("wss"); 250 | } 251 | 252 | public String path() { 253 | return this.uri.getPath(); 254 | } 255 | 256 | public InteractionModel interactionModel() { 257 | if (this.options.has(this.stream)) { 258 | return InteractionModel.REQUEST_STREAM; 259 | } 260 | if (this.options.has(this.request)) { 261 | return InteractionModel.REQUEST_RESPONSE; 262 | } 263 | if (this.options.has(this.channel)) { 264 | return InteractionModel.REQUEST_CHANNEL; 265 | } 266 | if (this.options.has(this.fnf)) { 267 | return InteractionModel.FIRE_AND_FORGET; 268 | } 269 | return this.options.valueOf(this.interactionModel); 270 | } 271 | 272 | public ByteBuf data() { 273 | final String data = this.options.valueOf(this.data); 274 | final Optional load = this.load(); 275 | if (load.isPresent() && StringUtils.hasText(data)) { 276 | throw new IllegalArgumentException("--data and --load are mutually exclusive."); 277 | } 278 | return load.orElseGet(() -> Unpooled.wrappedBuffer(data.getBytes(StandardCharsets.UTF_8))); 279 | } 280 | 281 | public boolean readFromStdin() { 282 | return "-".equals(this.options.valueOf(this.data)); 283 | } 284 | 285 | private Optional load() { 286 | if (this.options.has(this.load)) { 287 | final String load = this.options.valueOf(this.load); 288 | if (load == null) { 289 | throw new IllegalArgumentException("'load' is not specified."); 290 | } 291 | try { 292 | final Resource resource = new FileSystemResourceLoader().getResource(load); 293 | final byte[] data = StreamUtils.copyToByteArray(resource.getInputStream()); 294 | return Optional.of(Unpooled.wrappedBuffer(data)); 295 | } 296 | catch (IOException e) { 297 | throw new UncheckedIOException(e); 298 | } 299 | } 300 | else { 301 | return Optional.empty(); 302 | } 303 | } 304 | 305 | public Route route() { 306 | final String route = this.options.valueOf(this.route); 307 | if (route == null) { 308 | throw new IllegalArgumentException("'route' is not specified."); 309 | } 310 | return new Route(route); 311 | } 312 | 313 | public SimpleAuthentication authSimple() { 314 | final String authSimple = this.options.valueOf(this.authSimple); 315 | if (authSimple == null) { 316 | throw new IllegalArgumentException("'authSimple' is not specified."); 317 | } 318 | return SimpleAuthentication.valueOf(authSimple); 319 | } 320 | 321 | public BearerAuthentication authBearer() { 322 | final String authBearer = this.options.valueOf(this.authBearer); 323 | if (authBearer == null) { 324 | throw new IllegalArgumentException("'authBearer' is not specified."); 325 | } 326 | return new BearerAuthentication(authBearer); 327 | } 328 | 329 | public BasicAuthentication authBasic() { 330 | final String authBasic = this.options.valueOf(this.authBasic); 331 | if (authBasic == null) { 332 | throw new IllegalArgumentException("'authBasic' is not specified."); 333 | } 334 | return BasicAuthentication.valueOf(authBasic); 335 | } 336 | 337 | public String dataMimeType() { 338 | final String mimeType = this.options.valueOf(this.dataMimeType); 339 | try { 340 | return WellKnownMimeType.valueOf(mimeType).getString(); 341 | } 342 | catch (IllegalArgumentException ignored) { 343 | return mimeType; 344 | } 345 | } 346 | 347 | private Optional setupData() { 348 | if (this.options.has(this.setupData)) { 349 | final String data = this.options.valueOf(setupData); 350 | if (data == null) { 351 | throw new IllegalArgumentException("'setupData' is not specified."); 352 | } 353 | return Optional 354 | .of(Unpooled.wrappedBuffer(data.getBytes(StandardCharsets.UTF_8))); 355 | } 356 | else { 357 | return Optional.empty(); 358 | } 359 | } 360 | 361 | public Optional setupMetadata() { 362 | if (this.options.has(this.setupMetadata)) { 363 | // always encode setupMetadata as a composite metadata 364 | final String metadata = this.options.valueOf(this.setupMetadata); 365 | if (metadata == null) { 366 | throw new IllegalArgumentException("'setupMetadata' is not specified."); 367 | } 368 | final SetupMetadataMimeType setupMetadataMimeType; 369 | if (this.options.has(this.setupMetadataMimeType)) { 370 | final String mimeType = this.options.valueOf(this.setupMetadataMimeType); 371 | if (mimeType == null) { 372 | throw new IllegalArgumentException("'setupMetadataMimeType' is not specified."); 373 | } 374 | setupMetadataMimeType = SetupMetadataMimeType.of(mimeType); 375 | } 376 | else { 377 | setupMetadataMimeType = SetupMetadataMimeType.of(DEFAULT_METADATA_MIME_TYPE); 378 | } 379 | return Optional.of(addCompositeMetadata(setupMetadataMimeType.encode(metadata), 380 | setupMetadataMimeType.getValue())); 381 | } 382 | else { 383 | return Optional.empty(); 384 | } 385 | } 386 | 387 | static ByteBuf addCompositeMetadata(ByteBuf metadata, String mimeType) { 388 | final ByteBufAllocator allocator = new PooledByteBufAllocator(true); 389 | final CompositeByteBuf composite = allocator.compositeBuffer(); 390 | CompositeMetadataCodec.encodeAndAddMetadata(composite, allocator, mimeType, metadata); 391 | return composite; 392 | } 393 | 394 | static ByteBuf addCompositeMetadata(ByteBuf metadata, WellKnownMimeType mimeType) { 395 | final ByteBufAllocator allocator = new PooledByteBufAllocator(true); 396 | final CompositeByteBuf composite = allocator.compositeBuffer(); 397 | CompositeMetadataCodec.encodeAndAddMetadata(composite, allocator, mimeType, metadata); 398 | return composite; 399 | } 400 | 401 | public Optional setupPayload() { 402 | final Optional payload = this.setupData() 403 | .map(data -> this.setupMetadata() 404 | .map(metadata -> /* setupData and setupMetadata */ DefaultPayload.create(data, metadata)) 405 | .orElseGet(() -> /* only setupData */ DefaultPayload.create(data))); 406 | if (payload.isPresent()) { 407 | return payload; 408 | } 409 | else { 410 | // only setupMetadata 411 | return this.setupMetadata() 412 | .map(metadata -> DefaultPayload.create(Unpooled.EMPTY_BUFFER, metadata)); 413 | } 414 | } 415 | 416 | /** 417 | * https://github.com/rsocket/rsocket/blob/master/Extensions/CompositeMetadata.md 418 | */ 419 | public Tuple2 composeMetadata() { 420 | if (this.composedMetadata != null) { 421 | return this.composedMetadata; 422 | } 423 | final List mimeTypeList = this.metadataMimeType(); 424 | final List metadataList = this.metadata(); 425 | if (metadataList.size() == mimeTypeList.size() + 1) { 426 | // if metadata mime type is not specified, default one is used 427 | mimeTypeList.add(DEFAULT_METADATA_MIME_TYPE); 428 | } 429 | else if (metadataList.size() != mimeTypeList.size()) { 430 | throw new IllegalArgumentException( 431 | String.format("The size of metadata(%d) and metadataMimeType(%d) don't match!", metadataList.size(), 432 | mimeTypeList.size())); 433 | } 434 | if (metadataList.isEmpty()) { 435 | return Tuples.of(DEFAULT_METADATA_MIME_TYPE, Unpooled.buffer()); 436 | } 437 | // Always use composite metadata 438 | final CompositeByteBuf compositeByteBuf = Unpooled.compositeBuffer(); 439 | final ByteBufAllocator allocator = new PooledByteBufAllocator(true); 440 | final Iterator mimeTypeIterator = mimeTypeList.iterator(); 441 | final Iterator metadataIterator = metadataList.iterator(); 442 | while (mimeTypeIterator.hasNext()) { 443 | final String mimeType = mimeTypeIterator.next(); 444 | final ByteBuf metadata = metadataIterator.next(); 445 | final WellKnownMimeType wellKnownMimeType = WellKnownMimeType.fromString(mimeType); 446 | if (wellKnownMimeType != WellKnownMimeType.UNPARSEABLE_MIME_TYPE) { 447 | CompositeMetadataCodec.encodeAndAddMetadata(compositeByteBuf, allocator, wellKnownMimeType, 448 | metadata); 449 | } 450 | else { 451 | CompositeMetadataCodec.encodeAndAddMetadata(compositeByteBuf, allocator, mimeType, metadata); 452 | } 453 | } 454 | this.composedMetadata = Tuples.of(WellKnownMimeType.MESSAGE_RSOCKET_COMPOSITE_METADATA.getString(), 455 | compositeByteBuf); 456 | return this.composedMetadata; 457 | } 458 | 459 | public Optional span() { 460 | return Optional.ofNullable(this.span); 461 | } 462 | 463 | List metadata() { 464 | final List list = new ArrayList<>(); 465 | final List metadataEncoders = new ArrayList<>(); 466 | if (this.options.has(this.route)) { 467 | metadataEncoders.add(this.route()); 468 | } 469 | if (this.options.has(this.authSimple)) { 470 | metadataEncoders.add(this.authSimple()); 471 | } 472 | if (this.options.has(this.authBearer)) { 473 | metadataEncoders.add(this.authBearer()); 474 | } 475 | if (this.options.has(this.authBasic)) { 476 | metadataEncoders.add(this.authBasic()); 477 | } 478 | if (this.options.has(this.trace)) { 479 | final Flags flags = Optional.ofNullable(this.options.valueOf(this.trace)).orElse(Flags.DEBUG); 480 | this.span = Tracing.createSpan(flags); 481 | metadataEncoders.add(this.span); 482 | } 483 | metadataEncoders.forEach(metadataEncoder -> list.add(metadataEncoder.toMetadata(new PooledByteBufAllocator(true)))); 484 | list.addAll(this.options.valuesOf(this.metadata).stream() 485 | .map(metadata -> Unpooled.wrappedBuffer(metadata.getBytes(StandardCharsets.UTF_8))).collect(toList())); 486 | return list; 487 | } 488 | 489 | List metadataMimeType() { 490 | List list = new ArrayList<>(); 491 | if (this.options.has(this.route)) { 492 | list.add(WellKnownMimeType.MESSAGE_RSOCKET_ROUTING.getString()); 493 | } 494 | if (this.options.has(this.authSimple) || this.options.has(this.authBearer)) { 495 | list.add(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString()); 496 | } 497 | if (this.options.has(this.authBasic)) { 498 | list.add("message/x.rsocket.authentication.basic.v0"); 499 | } 500 | if (this.options.has(this.trace)) { 501 | list.add(WellKnownMimeType.MESSAGE_RSOCKET_TRACING_ZIPKIN.getString()); 502 | } 503 | list.addAll(this.options.valuesOf(this.metadataMimeType).stream().map(mimeType -> { 504 | try { 505 | return WellKnownMimeType.valueOf(mimeType).getString(); 506 | } 507 | catch (IllegalArgumentException ignored) { 508 | return mimeType; 509 | } 510 | }).collect(toList())); 511 | return list; 512 | } 513 | 514 | Map wsHeaders() { 515 | Map> headerSet = new LinkedHashMap<>(); 516 | this.options.valuesOf(wsHeader).forEach(header -> { 517 | String[] nameValue = header.split(":", 2); 518 | if (nameValue.length == 2) { 519 | headerSet.computeIfAbsent(nameValue[0], k -> new LinkedHashSet<>()).add(nameValue[1]); 520 | } 521 | }); 522 | 523 | Map headers = new LinkedHashMap<>(); 524 | headerSet.forEach((key, value) -> headers.put(key, String.join(";", value))); 525 | return headers; 526 | } 527 | 528 | 529 | public ClientTransport clientTransport() { 530 | final String scheme = this.uri.getScheme(); 531 | Transport transport; 532 | if (scheme.startsWith("ws")) { 533 | transport = WEBSOCKET; 534 | } 535 | else if (scheme.startsWith("tcp")) { 536 | transport = TCP; 537 | } 538 | else { 539 | throw new IllegalArgumentException(scheme + " is unsupported scheme."); 540 | } 541 | return transport.clientTransport(this); 542 | } 543 | 544 | public TcpClient tcpClient() { 545 | final TcpClient tcpClient = TcpClient.create().host(this.host()).port(this.port()).wiretap(this.wiretap()); 546 | if (this.secure()) { 547 | final List trustCerts = trustCerts(); 548 | if (trustCerts.isEmpty()) { 549 | return tcpClient.secure(); 550 | } 551 | else { 552 | return tcpClient.secure(provider -> { 553 | final List trustCertificates = this.loadCertificates(trustCerts); 554 | provider.sslContext(SslContextBuilder.forClient().trustManager(trustCertificates)); 555 | }); 556 | } 557 | } 558 | return tcpClient; 559 | } 560 | 561 | List loadCertificates(List pemLocations) { 562 | try { 563 | final FileSystemResourceLoader resourceLoader = new FileSystemResourceLoader(); 564 | final CertificateFactory fact = CertificateFactory.getInstance("X.509"); 565 | final List certificates = new ArrayList<>(); 566 | for (String pemLocation : pemLocations) { 567 | final Resource pemResource = resourceLoader.getResource(pemLocation); 568 | try (final InputStream inputStream = pemResource.getInputStream()) { 569 | final X509Certificate certificate = (X509Certificate) fact.generateCertificate(inputStream); 570 | certificates.add(certificate); 571 | } 572 | } 573 | return certificates; 574 | } 575 | catch (IOException e) { 576 | throw new UncheckedIOException(e); 577 | } 578 | catch (GeneralSecurityException e) { 579 | throw new IllegalStateException(e); 580 | } 581 | } 582 | 583 | public boolean wiretap() { 584 | return this.options.has(this.wiretap); 585 | } 586 | 587 | public boolean debug() { 588 | return this.options.has(this.debug); 589 | } 590 | 591 | public boolean quiet() { 592 | return this.options.has(this.quiet); 593 | } 594 | 595 | public boolean stacktrace() { 596 | return this.options.has(this.stacktrace); 597 | } 598 | 599 | public Optional trace() { 600 | if (this.options.has(this.trace)) { 601 | final Flags flags = this.options.valueOf(this.trace); 602 | return Optional.of(flags == null ? Flags.DEBUG : flags); 603 | } 604 | else { 605 | return Optional.empty(); 606 | } 607 | } 608 | 609 | public boolean printB3() { 610 | return this.options.has(this.printB3); 611 | } 612 | 613 | public Optional zipkinUrl() { 614 | if (this.options.has(this.zipkinUrl)) { 615 | final List zipkinUrl = this.options.valuesOf(this.zipkinUrl); 616 | if (zipkinUrl == null) { 617 | throw new IllegalArgumentException("'zipkinUrl' is not specified."); 618 | } 619 | return Optional.of(this.options.valueOf(this.zipkinUrl)); 620 | } 621 | else { 622 | return Optional.empty(); 623 | } 624 | } 625 | 626 | public Optional resume() { 627 | if (this.options.has(this.resume)) { 628 | final Integer resume = this.options.valueOf(this.resume); 629 | if (resume == null) { 630 | throw new IllegalArgumentException("'resume' is not specified."); 631 | } 632 | return Optional.of(resume).map(Duration::ofSeconds); 633 | } 634 | else { 635 | return Optional.empty(); 636 | } 637 | } 638 | 639 | public Optional retry() { 640 | if (this.options.has(this.retry)) { 641 | final Integer retry = this.options.valueOf(this.retry); 642 | if (retry == null) { 643 | throw new IllegalArgumentException("'retry' is not specified."); 644 | } 645 | return Optional.of(retry); 646 | } 647 | else { 648 | return Optional.empty(); 649 | } 650 | } 651 | 652 | public Optional limitRate() { 653 | if (this.options.has(this.limitRate)) { 654 | final Integer limitRate = this.options.valueOf(this.limitRate); 655 | if (limitRate == null) { 656 | throw new IllegalArgumentException("'limitRate' is not specified."); 657 | } 658 | return Optional.of(limitRate); 659 | } 660 | else { 661 | return Optional.empty(); 662 | } 663 | } 664 | 665 | public Optional take() { 666 | if (this.options.has(this.take)) { 667 | final Integer take = this.options.valueOf(this.take); 668 | if (take == null) { 669 | throw new IllegalArgumentException("'take' is not specified."); 670 | } 671 | return Optional.of(take); 672 | } 673 | else { 674 | return Optional.empty(); 675 | } 676 | } 677 | 678 | public Optional delayElements() { 679 | if (this.options.has(this.delayElements)) { 680 | final Long delayElements = this.options.valueOf(this.delayElements); 681 | if (delayElements == null) { 682 | throw new IllegalArgumentException("'delayElements' is not specified."); 683 | } 684 | return Optional.of(delayElements).map(Duration::ofMillis); 685 | } 686 | else { 687 | return Optional.empty(); 688 | } 689 | } 690 | 691 | public Optional log() { 692 | if (this.options.has(this.log)) { 693 | return Optional.of(Objects.toString(this.options.valueOf(this.log), "rsc")); 694 | } 695 | else { 696 | return Optional.empty(); 697 | } 698 | } 699 | 700 | public List trustCerts() { 701 | return this.options.valuesOf(this.trustCert); 702 | } 703 | 704 | public boolean help() { 705 | return this.options.has(this.help); 706 | } 707 | 708 | public String helpFormatter() { 709 | return this.options.valueOf(this.help); 710 | } 711 | 712 | public boolean version() { 713 | return this.options.has(this.version); 714 | } 715 | 716 | public void printHelp(PrintStream stream) { 717 | try { 718 | stream.println("usage: rsc [options] uri"); 719 | stream.println(); 720 | this.parser.printHelpOn(stream); 721 | } 722 | catch (IOException e) { 723 | throw new UncheckedIOException(e); 724 | } 725 | } 726 | 727 | public void printHelp(PrintStream stream, HelpFormatter helpFormatter) { 728 | try { 729 | this.parser.formatHelpWith(helpFormatter); 730 | this.parser.printHelpOn(stream); 731 | } 732 | catch (IOException e) { 733 | throw new UncheckedIOException(e); 734 | } 735 | } 736 | 737 | public boolean showSystemProperties() { 738 | return this.options.has(this.showSystemProperties); 739 | } 740 | 741 | public Optional completion() { 742 | if (this.options.has(this.completion)) { 743 | final ShellType completion = this.options.valueOf(this.completion); 744 | if (completion == null) { 745 | throw new IllegalArgumentException("'completion' must be specified. Possible values: " + Arrays.toString(ShellType.values())); 746 | } 747 | return Optional.of(completion); 748 | } 749 | else { 750 | return Optional.empty(); 751 | } 752 | } 753 | 754 | @SuppressWarnings("unchecked") 755 | private Optional> argsFile() { 756 | if (this.options.has(this.optsFile)) { 757 | final String optsFile = this.options.valueOf(this.optsFile); 758 | if (optsFile == null) { 759 | throw new IllegalArgumentException("'optsFile' is not specified."); 760 | } 761 | try { 762 | final Resource resource = new FileSystemResourceLoader().getResource(optsFile); 763 | Map yaml = new Yaml().loadAs(resource.getInputStream(), Map.class); 764 | final List options = new ArrayList<>(); 765 | for (Map.Entry entry : yaml.entrySet()) { 766 | final String optionName = (String) entry.getKey(); 767 | final Object value = entry.getValue(); 768 | if (value instanceof List) { 769 | final List optionValue = (List) value; 770 | for (String v : optionValue) { 771 | options.add(prependHyphensOptionName(optionName)); 772 | options.add(v); 773 | } 774 | } 775 | else { 776 | final String optionValue = (String) value; 777 | options.add(prependHyphensOptionName(optionName)); 778 | if (optionValue != null) { 779 | options.add(optionValue); 780 | } 781 | } 782 | } 783 | return Optional.of(options); 784 | } 785 | catch (IOException e) { 786 | throw new UncheckedIOException(e); 787 | } 788 | } 789 | else { 790 | return Optional.empty(); 791 | } 792 | } 793 | 794 | public boolean isDumpOpts() { 795 | return this.options.has(this.dumpOpts); 796 | } 797 | 798 | @SuppressWarnings("unchecked") 799 | public void dumpOpts(PrintStream stream) { 800 | String lastOptionName = null; 801 | final Map optionMap = new TreeMap<>(); 802 | final List options = new ArrayList<>(); 803 | for (String s : this.args) { 804 | if (s.matches("^-{1,2}.+=.*")) { 805 | final String[] split = s.split("=", 2); 806 | options.add(split[0]); 807 | options.add(split[1]); 808 | } 809 | else { 810 | options.add(s); 811 | } 812 | } 813 | for (String s : options) { 814 | if (s.startsWith("-")) { 815 | // name 816 | if (lastOptionName != null) { 817 | optionMap.put(removeHyphensOptionName(lastOptionName), null); 818 | } 819 | lastOptionName = s; 820 | } 821 | else if (lastOptionName != null) { 822 | // value 823 | final String optionName = removeHyphensOptionName(lastOptionName); 824 | if (optionMap.containsKey(optionName)) { 825 | final Object value = optionMap.get(optionName); 826 | if (value instanceof List) { 827 | ((List) value).add(s); 828 | } 829 | else { 830 | final List list = new ArrayList<>(); 831 | list.add((String) value); 832 | list.add(s); 833 | optionMap.put(optionName, list); 834 | } 835 | } 836 | else { 837 | optionMap.put(optionName, s); 838 | } 839 | lastOptionName = null; 840 | } 841 | } 842 | if (lastOptionName != null) { 843 | optionMap.put(removeHyphensOptionName(lastOptionName), null); 844 | } 845 | optionMap.remove("optsFile"); 846 | optionMap.remove("dumpOpts"); 847 | final DumperOptions dumperOptions = new DumperOptions(); 848 | dumperOptions.setDefaultFlowStyle(FlowStyle.BLOCK); 849 | stream.println(new Yaml(dumperOptions).dump(optionMap)); 850 | } 851 | 852 | private static String removeHyphensOptionName(String optionName) { 853 | return optionName.replaceAll("^-{1,2}", ""); 854 | } 855 | 856 | private static String prependHyphensOptionName(String optionName) { 857 | return (optionName.length() == 1 ? "-" : "--") + optionName; 858 | } 859 | } 860 | -------------------------------------------------------------------------------- /src/main/java/am/ik/rsocket/InteractionModel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Toshiaki Maki 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 | * http://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 | package am.ik.rsocket; 17 | 18 | import java.util.Scanner; 19 | 20 | import io.netty.buffer.Unpooled; 21 | import io.rsocket.Payload; 22 | import io.rsocket.RSocket; 23 | import io.rsocket.util.DefaultPayload; 24 | import org.reactivestreams.Publisher; 25 | import reactor.core.publisher.Flux; 26 | import reactor.core.publisher.Mono; 27 | import reactor.core.scheduler.Schedulers; 28 | 29 | import static java.nio.charset.StandardCharsets.UTF_8; 30 | 31 | public enum InteractionModel { 32 | REQUEST_RESPONSE { 33 | @Override 34 | Publisher request(RSocket rsocket, Args args) { 35 | final Mono payload = payloadMono(args); 36 | return payload.flatMap(p -> rsocket.requestResponse(p).map(Payload::getDataUtf8) // 37 | .transform(s -> args.log().map(s::log).orElse(s)) 38 | .transform(s -> args.quiet() ? s : s.doOnNext(System.out::println))); 39 | } 40 | }, 41 | REQUEST_STREAM { 42 | @Override 43 | Publisher request(RSocket rsocket, Args args) { 44 | final Mono payload = payloadMono(args); 45 | return payload.flatMapMany(p -> rsocket.requestStream(p).map(Payload::getDataUtf8) 46 | .transform(s -> args.log().map(s::log).orElse(s)) 47 | .transform(s -> args.limitRate().map(s::limitRate).orElse(s)) 48 | .transform(s -> args.take().map(s::take).orElse(s)) 49 | .transform(s -> args.delayElements().map(s::delayElements).orElse(s)) 50 | .transform(s -> args.quiet() ? s : s.doOnNext(System.out::println))); 51 | } 52 | }, 53 | REQUEST_CHANNEL { 54 | @Override 55 | Publisher request(RSocket rsocket, Args args) { 56 | final Flux payloads = payloadFlux(args); 57 | return rsocket.requestChannel(payloads).map(Payload::getDataUtf8) 58 | .transform(s -> args.log().map(s::log).orElse(s)) 59 | .transform(s -> args.limitRate().map(s::limitRate).orElse(s)) 60 | .transform(s -> args.take().map(s::take).orElse(s)) 61 | .transform(s -> args.delayElements().map(s::delayElements).orElse(s)) 62 | .transform(s -> args.quiet() ? s : s.doOnNext(System.out::println)); 63 | } 64 | }, 65 | FIRE_AND_FORGET { 66 | @Override 67 | Publisher request(RSocket rsocket, Args args) { 68 | final Mono payload = payloadMono(args); 69 | return payload.flatMap(p -> rsocket.fireAndForget(p).transform(s -> args.log().map(s::log).orElse(s))); 70 | } 71 | }; 72 | 73 | abstract Publisher request(RSocket rsocket, Args args); 74 | 75 | static Flux payloadFlux(Args args) { 76 | if (args.readFromStdin()) { 77 | final Scanner scanner = new Scanner(System.in); 78 | return scanToFlux(scanner) // 79 | .transform(s -> args.log().map(__ -> s.log("input")).orElse(s)) // 80 | .map(x -> DefaultPayload.create(Unpooled.wrappedBuffer(x.getBytes(UTF_8)), 81 | args.composeMetadata().getT2().retain())) 82 | .doOnTerminate(scanner::close); 83 | } else { 84 | return Flux.just(DefaultPayload.create(args.data(), args.composeMetadata().getT2())); 85 | } 86 | } 87 | 88 | static Mono payloadMono(Args args) { 89 | if (args.readFromStdin()) { 90 | final Scanner scanner = new Scanner(System.in); 91 | return scanToMono(scanner) // 92 | .transform(s -> args.log().map(__ -> s.log("input")).orElse(s)) // 93 | .map(x -> DefaultPayload.create(Unpooled.wrappedBuffer(x.getBytes(UTF_8)), 94 | args.composeMetadata().getT2())) 95 | .doOnTerminate(scanner::close); 96 | } else { 97 | return Mono.just(DefaultPayload.create(args.data(), args.composeMetadata().getT2())); 98 | } 99 | } 100 | 101 | static Flux scanToFlux(Scanner scanner) { 102 | return Flux.generate(sink -> { 103 | if (!scanner.hasNext()) { 104 | sink.complete(); 105 | } else { 106 | sink.next(scanner.nextLine()); 107 | } 108 | }).subscribeOn(Schedulers.boundedElastic()); 109 | } 110 | 111 | static Mono scanToMono(Scanner scanner) { 112 | return scanToFlux(scanner) // 113 | .collectList() // 114 | .map(list -> String.join(System.lineSeparator(), list)); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/am/ik/rsocket/MetadataEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Toshiaki Maki 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 | * http://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 | package am.ik.rsocket; 17 | 18 | import io.netty.buffer.ByteBuf; 19 | import io.netty.buffer.ByteBufAllocator; 20 | 21 | public interface MetadataEncoder { 22 | ByteBuf toMetadata(ByteBufAllocator allocator); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/am/ik/rsocket/RscApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Toshiaki Maki 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 | * http://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 | package am.ik.rsocket; 17 | 18 | import org.springframework.boot.SpringApplication; 19 | import org.springframework.boot.autoconfigure.SpringBootApplication; 20 | import org.springframework.boot.context.logging.LoggingApplicationListener; 21 | import org.springframework.context.ApplicationListener; 22 | 23 | @SpringBootApplication(proxyBeanMethods = false) 24 | public class RscApplication { 25 | 26 | public static void main(String[] args) { 27 | final SpringApplication application = new SpringApplication(RscApplication.class); 28 | for (ApplicationListener listener : application.getListeners()) { 29 | if (listener instanceof LoggingApplicationListener) { 30 | ((LoggingApplicationListener) listener).setParseArgs(false); 31 | } 32 | } 33 | application.run(args); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/am/ik/rsocket/RscCommandLineRunner.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Toshiaki Maki 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 | * http://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 | package am.ik.rsocket; 17 | 18 | import java.time.Duration; 19 | import java.util.Optional; 20 | import java.util.TreeMap; 21 | 22 | import am.ik.rsocket.completion.CliCompletionHelpFormatter; 23 | import am.ik.rsocket.completion.ShellType; 24 | import am.ik.rsocket.tracing.Reporter; 25 | import ch.qos.logback.classic.Level; 26 | import ch.qos.logback.classic.Logger; 27 | import ch.qos.logback.classic.LoggerContext; 28 | import io.rsocket.core.RSocketConnector; 29 | import io.rsocket.core.Resume; 30 | import io.rsocket.frame.decoder.PayloadDecoder; 31 | import io.rsocket.metadata.TracingMetadataCodec.Flags; 32 | import io.rsocket.transport.ClientTransport; 33 | import org.slf4j.LoggerFactory; 34 | import reactor.core.Exceptions; 35 | import reactor.core.publisher.Flux; 36 | import reactor.util.retry.Retry; 37 | 38 | import org.springframework.boot.CommandLineRunner; 39 | import org.springframework.stereotype.Component; 40 | 41 | @Component 42 | public class RscCommandLineRunner implements CommandLineRunner { 43 | @Override 44 | public void run(String... a) throws Exception { 45 | final Args args = new Args(a); 46 | try { 47 | if (args.isDumpOpts()) { 48 | args.dumpOpts(System.out); 49 | return; 50 | } 51 | if (args.help()) { 52 | if ("cli-completion".equals(args.helpFormatter())) { 53 | args.printHelp(System.out, new CliCompletionHelpFormatter()); 54 | } 55 | else { 56 | args.printHelp(System.out); 57 | } 58 | return; 59 | } 60 | if (args.version()) { 61 | printVersion(); 62 | return; 63 | } 64 | if (args.showSystemProperties()) { 65 | printSystemProperties(); 66 | return; 67 | } 68 | final Optional completion = args.completion(); 69 | if (completion.isPresent()) { 70 | System.out.println(completion.get().script()); 71 | return; 72 | } 73 | if (args.resume().isPresent() && args.retry().isPresent()) { 74 | System.err.println("--resume and --retry are mutually exclusive."); 75 | System.err.println(); 76 | System.exit(2); 77 | } 78 | if (!args.hasUri()) { 79 | System.err.println("Uri is required."); 80 | System.err.println(); 81 | args.printHelp(System.out); 82 | System.exit(2); 83 | } 84 | if (args.debug()) { 85 | configureDebugLevel("io.rsocket.FrameLogger"); 86 | } 87 | final long begin = System.nanoTime(); 88 | run(args) 89 | .doOnTerminate(() -> handleSpan(args, (System.nanoTime() - begin) / 1000)) 90 | .blockLast(); 91 | if (args.interactionModel() == InteractionModel.FIRE_AND_FORGET) { 92 | // Workaround for https://github.com/making/rsc/issues/18 93 | Thread.sleep(10); 94 | } 95 | } 96 | catch (RuntimeException e) { 97 | if (args.stacktrace()) { 98 | e.printStackTrace(); 99 | } 100 | else { 101 | System.err.println("Error: " + e.getMessage()); 102 | System.err.println(); 103 | System.err.println("Use --stacktrace option for details."); 104 | } 105 | System.exit(1); 106 | } 107 | } 108 | 109 | static Flux run(Args args) { 110 | args.log().ifPresent(RscCommandLineRunner::configureDebugLevel); 111 | final ClientTransport clientTransport = args.clientTransport(); 112 | final RSocketConnector connector = RSocketConnector.create(); 113 | final String metadataMimeType = args.composeMetadata().getT1(); 114 | args.resume().ifPresent(duration -> connector.resume(new Resume() 115 | .sessionDuration(duration) 116 | .retry(Retry.fixedDelay(10, Duration.ofSeconds(5))))); 117 | args.retry().ifPresent(maxAttempts -> connector 118 | .reconnect(retry(maxAttempts))); 119 | args.setupPayload().ifPresent(connector::setupPayload); 120 | return connector 121 | .payloadDecoder(PayloadDecoder.ZERO_COPY) 122 | .metadataMimeType(metadataMimeType) 123 | .dataMimeType(args.dataMimeType()) 124 | .connect(clientTransport) 125 | .flatMapMany(rsocket -> args.interactionModel().request(rsocket, args)) 126 | .transform(s -> args.retry().map(maxAttempts -> s.retryWhen(retry(maxAttempts))).orElse(s)); 127 | } 128 | 129 | static void handleSpan(Args args, long duration) { 130 | args.span() 131 | .ifPresent(span -> { 132 | if (args.printB3()) { 133 | System.err.println("b3=" + span.toB3SingleHeaderFormat()); 134 | } 135 | args.trace() 136 | .filter(flags -> flags == Flags.DEBUG || flags == Flags.SAMPLE) 137 | .flatMap(flags -> args.zipkinUrl()) 138 | .ifPresent(url -> Reporter.report(String.format("%s/api/v2/spans", url), 139 | span, 140 | args.interactionModel().name().toLowerCase().replace("_", "-"), 141 | duration)); 142 | }); 143 | } 144 | 145 | static Retry retry(int maxAttempts) { 146 | return Retry.fixedDelay(maxAttempts, Duration.ofSeconds(1)) 147 | .filter(e -> !Exceptions.isRetryExhausted(e)) 148 | .doBeforeRetry(System.err::println); 149 | } 150 | 151 | static void configureDebugLevel(String loggerName) { 152 | final LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); 153 | final Logger logger = loggerContext.getLogger(loggerName); 154 | logger.setLevel(Level.DEBUG); 155 | } 156 | 157 | static void printVersion() { 158 | // Version class will be generated during Maven's generated-sources phase 159 | System.out.println(am.ik.rsocket.Version.getVersionAsJson()); 160 | } 161 | 162 | static void printSystemProperties() { 163 | new TreeMap<>(System.getProperties()).forEach((k, v) -> System.out.println(k + "\t= " + v)); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/main/java/am/ik/rsocket/SetupMetadataMimeType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Toshiaki Maki 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 | * http://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 | package am.ik.rsocket; 17 | 18 | import java.nio.charset.StandardCharsets; 19 | import java.util.Objects; 20 | 21 | import am.ik.rsocket.security.AuthenticationSetupMetadata; 22 | import am.ik.rsocket.security.BasicAuthentication; 23 | import io.netty.buffer.ByteBuf; 24 | import io.netty.buffer.PooledByteBufAllocator; 25 | import io.netty.buffer.Unpooled; 26 | import io.rsocket.metadata.WellKnownMimeType; 27 | 28 | public enum SetupMetadataMimeType { 29 | TEXT_PLAIN(MimeType.wellKnown(WellKnownMimeType.TEXT_PLAIN)), 30 | APPLICATION_JSON(MimeType.wellKnown(WellKnownMimeType.APPLICATION_JSON)), 31 | MESSAGE_RSOCKET_AUTHENTICATION(MimeType.wellKnown(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION)) { 32 | @Override 33 | public ByteBuf encode(String metadata) { 34 | return AuthenticationSetupMetadata.valueOf(metadata).toMetadata(new PooledByteBufAllocator(true)); 35 | } 36 | }, 37 | AUTHENTICATION_BASIC(MimeType.custom("message/x.rsocket.authentication.basic.v0")) { 38 | @Override 39 | public ByteBuf encode(String metadata) { 40 | return BasicAuthentication.valueOf(metadata).toMetadata(new PooledByteBufAllocator(true)); 41 | } 42 | }, 43 | APP_INFO(MimeType.custom("message/x.rsocket.application+json")); 44 | 45 | private final MimeType mimeType; 46 | 47 | SetupMetadataMimeType(MimeType mimeType) { 48 | this.mimeType = mimeType; 49 | } 50 | 51 | public String getValue() { 52 | if (this.mimeType.isWellKnown()) { 53 | return this.mimeType.wellKnownMimeType.getString(); 54 | } 55 | else { 56 | return this.mimeType.custom; 57 | } 58 | } 59 | 60 | public ByteBuf encode(String metadata) { 61 | return Unpooled.wrappedBuffer(metadata.getBytes(StandardCharsets.UTF_8)); 62 | } 63 | 64 | public static SetupMetadataMimeType of(String value) { 65 | for (SetupMetadataMimeType type : values()) { 66 | if (Objects.equals(type.name(), value)) { 67 | return type; 68 | } 69 | if (type.mimeType.isWellKnown()) { 70 | if (Objects.equals(type.mimeType.wellKnownMimeType.getString(), value)) { 71 | return type; 72 | } 73 | } 74 | else { 75 | if (Objects.equals(type.mimeType.custom, value)) { 76 | return type; 77 | } 78 | } 79 | } 80 | throw new IllegalArgumentException("'" + value + "' is unsupported as a mime type of setup metadata."); 81 | } 82 | 83 | private static class MimeType { 84 | private final WellKnownMimeType wellKnownMimeType; 85 | 86 | private final String custom; 87 | 88 | static MimeType wellKnown(WellKnownMimeType wellKnownMimeType) { 89 | return new MimeType(wellKnownMimeType, null); 90 | } 91 | 92 | static MimeType custom(String custom) { 93 | return new MimeType(null, custom); 94 | } 95 | 96 | private MimeType(WellKnownMimeType wellKnownMimeType, String custom) { 97 | this.wellKnownMimeType = wellKnownMimeType; 98 | this.custom = custom; 99 | } 100 | 101 | boolean isWellKnown() { 102 | return this.wellKnownMimeType != null; 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/am/ik/rsocket/Transport.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Toshiaki Maki 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 | * http://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 | package am.ik.rsocket; 17 | 18 | import io.rsocket.transport.ClientTransport; 19 | import io.rsocket.transport.netty.client.TcpClientTransport; 20 | import io.rsocket.transport.netty.client.WebsocketClientTransport; 21 | import reactor.netty.http.client.HttpClient; 22 | 23 | public enum Transport { 24 | 25 | TCP { 26 | @Override 27 | ClientTransport clientTransport(Args args) { 28 | return TcpClientTransport.create(args.tcpClient()); 29 | } 30 | }, 31 | WEBSOCKET { 32 | @Override 33 | ClientTransport clientTransport(Args args) { 34 | WebsocketClientTransport transport = WebsocketClientTransport.create(HttpClient.from(args.tcpClient()), args.path()); 35 | args.wsHeaders().forEach(transport::header); 36 | return transport; 37 | } 38 | }; 39 | 40 | abstract ClientTransport clientTransport(Args args); 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/am/ik/rsocket/completion/CliCompletionHelpFormatter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Toshiaki Maki 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 | * http://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 | package am.ik.rsocket.completion; 17 | 18 | import java.util.ArrayList; 19 | import java.util.Arrays; 20 | import java.util.Collections; 21 | import java.util.Comparator; 22 | import java.util.HashMap; 23 | import java.util.List; 24 | import java.util.Map; 25 | import java.util.Set; 26 | import java.util.TreeSet; 27 | 28 | import am.ik.rsocket.InteractionModel; 29 | import am.ik.rsocket.SetupMetadataMimeType; 30 | import io.rsocket.metadata.TracingMetadataCodec; 31 | import io.rsocket.metadata.WellKnownMimeType; 32 | import joptsimple.HelpFormatter; 33 | import joptsimple.OptionDescriptor; 34 | 35 | public class CliCompletionHelpFormatter implements HelpFormatter { 36 | private final Map possibleValues = new HashMap() { 37 | { 38 | final Object[] wellKnownMimeTypes = Arrays.stream(WellKnownMimeType.values()) 39 | .filter(type -> type != WellKnownMimeType.UNPARSEABLE_MIME_TYPE && type != WellKnownMimeType.UNKNOWN_RESERVED_MIME_TYPE) 40 | .map(WellKnownMimeType::getString).toArray(); 41 | put("interactionModel", InteractionModel.values()); 42 | put("completion", ShellType.values()); 43 | put("trace", TracingMetadataCodec.Flags.values()); 44 | put("dataMimeType", wellKnownMimeTypes); 45 | put("metadataMimeType", wellKnownMimeTypes); 46 | put("setupMetadataMimeType", Arrays.stream(SetupMetadataMimeType.values()).map(SetupMetadataMimeType::getValue).toArray()); 47 | } 48 | }; 49 | 50 | @Override 51 | public String format(Map options) { 52 | Comparator comparator = Comparator.comparing(optionDescriptor -> optionDescriptor.options().iterator().next()); 53 | Set sorted = new TreeSet<>(comparator); 54 | sorted.addAll(options.values()); 55 | final StringBuilder sb = new StringBuilder(); 56 | sb.append("name: rsc").append(System.lineSeparator()); 57 | sb.append("binary_name: rsc").append(System.lineSeparator()); 58 | sb.append("before_help: |").append(System.lineSeparator()); 59 | sb.append(" usage: rsc [options] uri").append(System.lineSeparator()); 60 | sb.append(" Non-option arguments: [String: uri]").append(System.lineSeparator()); 61 | sb.append(System.lineSeparator()); 62 | sb.append("args:").append(System.lineSeparator()); 63 | sorted.forEach(descriptor -> { 64 | final List opts = new ArrayList<>(descriptor.options()); 65 | if (opts.contains("[arguments]")) { 66 | return; 67 | } 68 | Collections.sort(opts, Comparator.comparingInt(String::length).reversed()); 69 | final String longest = opts.get(0); 70 | final String shortest = opts.get(opts.size() - 1); 71 | sb.append("- ").append(longest).append(":").append(System.lineSeparator()); 72 | if (shortest.length() == 1) { 73 | sb.append(" short: ").append(shortest).append(System.lineSeparator()); 74 | opts.remove(shortest); 75 | } 76 | sb.append(" long: ").append(longest).append(System.lineSeparator()); 77 | if (opts.size() > 1) { 78 | sb.append(" aliases: ").append(opts.subList(1, opts.size())).append(System.lineSeparator()); 79 | } 80 | sb.append(" required: ").append(descriptor.isRequired()).append(System.lineSeparator()); 81 | sb.append(" about: \"").append(descriptor.description()).append("\"").append(System.lineSeparator()); 82 | final String indicator = descriptor.argumentTypeIndicator(); 83 | sb.append(" takes_value: ").append(!"".equals(indicator)).append(System.lineSeparator()); 84 | if (possibleValues.containsKey(longest)) { 85 | sb.append(" possible_values: ").append(Arrays.toString(Arrays.stream(possibleValues.get(longest)).map(s -> "\"" + s + "\"").toArray())).append(System.lineSeparator()); 86 | } 87 | }); 88 | return sb.toString(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/am/ik/rsocket/completion/ShellType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Toshiaki Maki 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 | * http://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 | package am.ik.rsocket.completion; 17 | 18 | import java.io.IOException; 19 | import java.io.InputStream; 20 | import java.io.UncheckedIOException; 21 | import java.nio.charset.StandardCharsets; 22 | 23 | import org.springframework.core.io.ClassPathResource; 24 | import org.springframework.util.StreamUtils; 25 | 26 | /** 27 | * Shell type that cli-completion supports 28 | * @see cli-completion 29 | */ 30 | public enum ShellType { 31 | bash, zsh, fish, powershell; 32 | 33 | public String script() { 34 | final ClassPathResource resource = new ClassPathResource("completions/" + name()); 35 | try (final InputStream stream = resource.getInputStream()) { 36 | return StreamUtils.copyToString(stream, StandardCharsets.UTF_8); 37 | } 38 | catch (IOException e) { 39 | throw new UncheckedIOException(e); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/am/ik/rsocket/file/FileSystemResourceLoader.java: -------------------------------------------------------------------------------- 1 | package am.ik.rsocket.file; 2 | 3 | import org.springframework.core.io.ContextResource; 4 | import org.springframework.core.io.DefaultResourceLoader; 5 | import org.springframework.core.io.FileSystemResource; 6 | import org.springframework.core.io.Resource; 7 | 8 | public class FileSystemResourceLoader extends DefaultResourceLoader { 9 | @Override 10 | protected Resource getResourceByPath(String path) { 11 | return new FileSystemContextResource(path); 12 | } 13 | 14 | private static class FileSystemContextResource extends FileSystemResource implements ContextResource { 15 | 16 | public FileSystemContextResource(String path) { 17 | super(path); 18 | } 19 | 20 | @Override 21 | public String getPathWithinContext() { 22 | return getPath(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/am/ik/rsocket/routing/Route.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Toshiaki Maki 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 | * http://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 | package am.ik.rsocket.routing; 17 | 18 | import java.nio.charset.StandardCharsets; 19 | 20 | import am.ik.rsocket.MetadataEncoder; 21 | import io.netty.buffer.ByteBuf; 22 | import io.netty.buffer.ByteBufAllocator; 23 | 24 | /** 25 | * https://github.com/rsocket/rsocket/blob/master/Extensions/Routing.md 26 | */ 27 | public class Route implements MetadataEncoder { 28 | private final String value; 29 | 30 | public Route(String value) { 31 | this.value = value; 32 | } 33 | 34 | public String getValue() { 35 | return value; 36 | } 37 | 38 | @Override 39 | public ByteBuf toMetadata(ByteBufAllocator allocator) { 40 | final byte[] bytes = this.value.getBytes(StandardCharsets.UTF_8); 41 | final ByteBuf buf = allocator.buffer(); 42 | buf.writeByte(bytes.length); 43 | buf.writeBytes(bytes); 44 | return buf; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/am/ik/rsocket/security/AuthenticationSetupMetadata.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Toshiaki Maki 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 | * http://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 | package am.ik.rsocket.security; 17 | 18 | import am.ik.rsocket.MetadataEncoder; 19 | 20 | public class AuthenticationSetupMetadata { 21 | public static MetadataEncoder valueOf(String text) { 22 | final String[] split = text.split(":", 2); 23 | if (split.length != 2) { 24 | throw new IllegalArgumentException("The format of Authentication Metadata in SETUP metadata must be 'type:value'. For example, 'simple:user:password' or 'bearer:token'."); 25 | } 26 | final String type = split[0].trim(); 27 | final String value = split[1].trim(); 28 | if ("simple".equalsIgnoreCase(type)) { 29 | return SimpleAuthentication.valueOf(value); 30 | } 31 | else if ("bearer".equalsIgnoreCase(type)) { 32 | return new BearerAuthentication(value); 33 | } 34 | else { 35 | throw new IllegalArgumentException("'" + type + "' is not supported as an Authentication Metadata type."); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/am/ik/rsocket/security/BasicAuthentication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Toshiaki Maki 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 | * http://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 | package am.ik.rsocket.security; 17 | 18 | import java.nio.charset.StandardCharsets; 19 | 20 | import am.ik.rsocket.MetadataEncoder; 21 | import io.netty.buffer.ByteBuf; 22 | import io.netty.buffer.ByteBufAllocator; 23 | 24 | public class BasicAuthentication implements MetadataEncoder { 25 | private final String username; 26 | 27 | private final String password; 28 | 29 | public static BasicAuthentication valueOf(String value) { 30 | final String[] split = value.split(":", 2); 31 | if (split.length != 2) { 32 | throw new IllegalArgumentException("The format of Basic Authentication must be 'username:password'."); 33 | } 34 | return new BasicAuthentication(split[0], split[1]); 35 | } 36 | 37 | public BasicAuthentication(String username, String password) { 38 | this.username = username; 39 | this.password = password; 40 | } 41 | 42 | @Override 43 | public ByteBuf toMetadata(ByteBufAllocator allocator) { 44 | final ByteBuf buf = allocator.buffer(); 45 | final byte[] username = this.username.getBytes(StandardCharsets.UTF_8); 46 | buf.writeInt(username.length); 47 | buf.writeBytes(username); 48 | buf.writeBytes(this.password.getBytes(StandardCharsets.UTF_8)); 49 | return buf; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/am/ik/rsocket/security/BearerAuthentication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Toshiaki Maki 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 | * http://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 | package am.ik.rsocket.security; 17 | 18 | import am.ik.rsocket.MetadataEncoder; 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.buffer.ByteBufAllocator; 21 | import io.rsocket.metadata.AuthMetadataCodec; 22 | 23 | public class BearerAuthentication implements MetadataEncoder { 24 | private final String token; 25 | 26 | public BearerAuthentication(String token) { 27 | this.token = token; 28 | } 29 | 30 | @Override 31 | public ByteBuf toMetadata(ByteBufAllocator allocator) { 32 | return AuthMetadataCodec.encodeBearerMetadata(allocator, this.token.toCharArray()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/am/ik/rsocket/security/SimpleAuthentication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Toshiaki Maki 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 | * http://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 | package am.ik.rsocket.security; 17 | 18 | import am.ik.rsocket.MetadataEncoder; 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.buffer.ByteBufAllocator; 21 | import io.rsocket.metadata.AuthMetadataCodec; 22 | 23 | public class SimpleAuthentication implements MetadataEncoder { 24 | private final String username; 25 | 26 | private final String password; 27 | 28 | public static SimpleAuthentication valueOf(String value) { 29 | final String[] split = value.split(":", 2); 30 | if (split.length != 2) { 31 | throw new IllegalArgumentException("The format of Simple Authentication must be 'username:password'."); 32 | } 33 | return new SimpleAuthentication(split[0], split[1]); 34 | } 35 | 36 | public SimpleAuthentication(String username, String password) { 37 | this.username = username; 38 | this.password = password; 39 | } 40 | 41 | @Override 42 | public ByteBuf toMetadata(ByteBufAllocator allocator) { 43 | return AuthMetadataCodec.encodeSimpleMetadata(allocator, this.username.toCharArray(), this.password.toCharArray()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/am/ik/rsocket/tracing/Reporter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Toshiaki Maki 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 | * http://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 | package am.ik.rsocket.tracing; 17 | 18 | import java.io.DataOutputStream; 19 | import java.io.IOException; 20 | import java.io.UncheckedIOException; 21 | import java.net.HttpURLConnection; 22 | import java.net.URL; 23 | import java.nio.charset.StandardCharsets; 24 | 25 | public final class Reporter { 26 | public static void report(String url, Span span, String rsocketName, long duratin) { 27 | try { 28 | final String content = "[" + span.toJsonString(rsocketName, duratin) + "]"; 29 | final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); 30 | conn.setRequestMethod("POST"); 31 | conn.setRequestProperty("Content-Type", "application/json"); 32 | conn.setRequestProperty("Content-Length", String.valueOf(content.getBytes(StandardCharsets.UTF_8).length)); 33 | conn.setDoOutput(true); 34 | try (final DataOutputStream wr = new DataOutputStream(conn.getOutputStream())) { 35 | wr.writeBytes(content); 36 | wr.flush(); 37 | } 38 | final int responseCode = conn.getResponseCode(); 39 | if (responseCode != 202) { 40 | System.err.printf("!! Response Code: %d\n", responseCode); 41 | } 42 | } 43 | catch (IOException e) { 44 | throw new UncheckedIOException(e); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/am/ik/rsocket/tracing/Span.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Toshiaki Maki 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 | * http://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 | package am.ik.rsocket.tracing; 17 | 18 | import java.time.Instant; 19 | 20 | import am.ik.rsocket.MetadataEncoder; 21 | import am.ik.rsocket.Version; 22 | import io.netty.buffer.ByteBuf; 23 | import io.netty.buffer.ByteBufAllocator; 24 | import io.rsocket.metadata.TracingMetadataCodec; 25 | import io.rsocket.metadata.TracingMetadataCodec.Flags; 26 | 27 | public class Span implements MetadataEncoder { 28 | private final long spanId; 29 | 30 | private final long traceIdHigh; 31 | 32 | private final long traceId; 33 | 34 | private final Flags flags; 35 | 36 | private final long timestamp; 37 | 38 | public Span(long spanId, long traceIdHigh, long traceId, Flags flags) { 39 | this.spanId = spanId; 40 | this.traceIdHigh = traceIdHigh; 41 | this.traceId = traceId; 42 | this.flags = flags; 43 | Instant instant = java.time.Clock.systemUTC().instant(); 44 | this.timestamp = (instant.getEpochSecond() * 1000000) + (instant.getNano() / 1000); 45 | } 46 | 47 | public String toJsonString(String rsocketMethod, long duration) { 48 | return String.format("{\n" 49 | + " \"id\": \"%s\",\n" 50 | + " \"traceId\": \"%s%s\",\n" 51 | + " \"name\": \"%s\",\n" 52 | + " \"timestamp\": %d,\n" 53 | + " \"duration\": %d,\n" 54 | + " \"kind\": \"CLIENT\",\n" 55 | + " \"localEndpoint\": {\n" 56 | + " \"serviceName\": \"rsc\"\n" 57 | + " },\n" 58 | + " \"tags\": {\n" 59 | + " \"rsocket.method\": \"%s\",\n" 60 | + " \"rsc.version\": \"%s\",\n" 61 | + " \"rsc.build\": \"%s\",\n" 62 | + " \"rsocket-java.version\": \"%s\"\n" 63 | + " }\n" 64 | + " }", 65 | Long.toHexString(spanId), 66 | Long.toHexString(traceIdHigh), 67 | Long.toHexString(traceId), 68 | rsocketMethod, 69 | this.timestamp, 70 | duration, 71 | rsocketMethod, 72 | Version.getVersion(), 73 | Version.getBuild(), 74 | Version.getRSocketJava() 75 | ); 76 | } 77 | 78 | public String toB3SingleHeaderFormat() { 79 | final String b3 = String.format("%s%s-%s", 80 | Long.toHexString(this.traceIdHigh), 81 | Long.toHexString(this.traceId), 82 | Long.toHexString(this.spanId)); 83 | if (this.flags == Flags.DEBUG) { 84 | return b3 + "-d"; 85 | } 86 | else if (this.flags == Flags.SAMPLE) { 87 | return b3 + "-1"; 88 | } 89 | else if (this.flags == Flags.NOT_SAMPLE) { 90 | return b3 + "-0"; 91 | } 92 | else { 93 | return b3; 94 | } 95 | } 96 | 97 | @Override 98 | public ByteBuf toMetadata(ByteBufAllocator allocator) { 99 | return TracingMetadataCodec.encode128(allocator, this.traceIdHigh, this.traceId, this.spanId, this.flags); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/am/ik/rsocket/tracing/Tracing.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2020 The OpenZipkin Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package am.ik.rsocket.tracing; 15 | 16 | import java.util.Random; 17 | 18 | import io.rsocket.metadata.TracingMetadataCodec.Flags; 19 | 20 | 21 | public final class Tracing { 22 | public static Span createSpan(Flags flags) { 23 | final long spanId = randomLong(); 24 | final long traceIdHigh = nextTraceIdHigh(); 25 | final long traceId = spanId; 26 | return new Span(spanId, traceIdHigh, traceId, flags); 27 | } 28 | 29 | /** 30 | * Copied from brave.internal.Platform 31 | */ 32 | private static long randomLong() { 33 | long nextId; 34 | do { 35 | nextId = new Random(System.nanoTime()).nextLong(); 36 | } while (nextId == 0L); 37 | return nextId; 38 | } 39 | 40 | private static long nextTraceIdHigh() { 41 | return nextTraceIdHigh(new Random(System.nanoTime()).nextInt()); 42 | } 43 | 44 | private static long nextTraceIdHigh(int random) { 45 | long epochSeconds = System.currentTimeMillis() / 1000; 46 | return (epochSeconds & 0xffffffffL) << 32 47 | | (random & 0xffffffffL); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/native-image/am.ik.rsocket/rsc/reflect-config.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "java.lang.Integer", 4 | "allDeclaredConstructors": true, 5 | "allDeclaredMethods": true 6 | }, 7 | { 8 | "name": "java.lang.Long", 9 | "allDeclaredConstructors": true, 10 | "allDeclaredMethods": true 11 | }, 12 | { 13 | "name": "java.util.Map", 14 | "allDeclaredConstructors": true, 15 | "allDeclaredMethods": true 16 | }, 17 | { 18 | "name": "am.ik.rsocket.Transport", 19 | "allDeclaredConstructors": true, 20 | "allDeclaredMethods": true 21 | }, 22 | { 23 | "name": "am.ik.rsocket.InteractionModel", 24 | "allDeclaredConstructors": true, 25 | "allDeclaredMethods": true 26 | }, 27 | { 28 | "name": "am.ik.rsocket.completion.ShellType", 29 | "allDeclaredConstructors": true, 30 | "allDeclaredMethods": true 31 | }, 32 | { 33 | "name": "io.rsocket.metadata.TracingMetadataCodec$Flags", 34 | "allDeclaredConstructors": true, 35 | "allDeclaredMethods": true 36 | }, 37 | { 38 | "name": "io.rsocket.metadata.WellKnownMimeType", 39 | "allDeclaredConstructors": true, 40 | "allDeclaredMethods": true 41 | } 42 | ] -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.main.banner-mode=off 2 | logging.level.root=off 3 | logging.level.reactor.netty.tcp.TcpClient=DEBUG -------------------------------------------------------------------------------- /src/main/resources/completions/bash: -------------------------------------------------------------------------------- 1 | _rsc() { 2 | local i cur prev opts cmds 3 | COMPREPLY=() 4 | cur="${COMP_WORDS[COMP_CWORD]}" 5 | prev="${COMP_WORDS[COMP_CWORD-1]}" 6 | cmd="" 7 | opts="" 8 | 9 | for i in ${COMP_WORDS[@]} 10 | do 11 | case "${i}" in 12 | rsc) 13 | cmd="rsc" 14 | ;; 15 | 16 | *) 17 | ;; 18 | esac 19 | done 20 | 21 | case "${cmd}" in 22 | rsc) 23 | opts=" -d -h -l -m -q -r -u -v -w -V --authBearer --authBasic --channel --completion --data --dataMimeType --debug --delayElements --dumpOpts --fnf --help --interactionModel --load --limitRate --log --metadata --metadataMimeType --optsFile --printB3 --quiet --route --request --resume --retry --setupData --setupMetadata --setupMetadataMimeType --showSystemProperties --stacktrace --stream --take --trace --trustCert --authSimple --version --wiretap --wsHeader --zipkinUrl " 24 | if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then 25 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 26 | return 0 27 | fi 28 | case "${prev}" in 29 | 30 | --authBearer) 31 | COMPREPLY=($(compgen -f "${cur}")) 32 | return 0 33 | ;; 34 | --authBasic) 35 | COMPREPLY=($(compgen -f "${cur}")) 36 | return 0 37 | ;; 38 | --completion) 39 | COMPREPLY=($(compgen -W "bash zsh fish powershell" -- "${cur}")) 40 | return 0 41 | ;; 42 | --data) 43 | COMPREPLY=($(compgen -f "${cur}")) 44 | return 0 45 | ;; 46 | -d) 47 | COMPREPLY=($(compgen -f "${cur}")) 48 | return 0 49 | ;; 50 | --dataMimeType) 51 | COMPREPLY=($(compgen -W "application/avro application/cbor application/graphql application/gzip application/javascript application/json application/octet-stream application/pdf application/vnd.apache.thrift.binary application/vnd.google.protobuf application/xml application/zip audio/aac audio/mp3 audio/mp4 audio/mpeg3 audio/mpeg audio/ogg audio/opus audio/vorbis image/bmp image/gif image/heic-sequence image/heic image/heif-sequence image/heif image/jpeg image/png image/tiff multipart/mixed text/css text/csv text/html text/plain text/xml video/H264 video/H265 video/VP8 application/x-hessian application/x-java-object application/cloudevents+json message/x.rsocket.mime-type.v0 message/x.rsocket.accept-mime-types.v0 message/x.rsocket.authentication.v0 message/x.rsocket.tracing-zipkin.v0 message/x.rsocket.routing.v0 message/x.rsocket.composite-metadata.v0" -- "${cur}")) 52 | return 0 53 | ;; 54 | --delayElements) 55 | COMPREPLY=($(compgen -f "${cur}")) 56 | return 0 57 | ;; 58 | --help) 59 | COMPREPLY=($(compgen -f "${cur}")) 60 | return 0 61 | ;; 62 | -h) 63 | COMPREPLY=($(compgen -f "${cur}")) 64 | return 0 65 | ;; 66 | --interactionModel) 67 | COMPREPLY=($(compgen -W "REQUEST_RESPONSE REQUEST_STREAM REQUEST_CHANNEL FIRE_AND_FORGET" -- "${cur}")) 68 | return 0 69 | ;; 70 | --load) 71 | COMPREPLY=($(compgen -f "${cur}")) 72 | return 0 73 | ;; 74 | -l) 75 | COMPREPLY=($(compgen -f "${cur}")) 76 | return 0 77 | ;; 78 | --limitRate) 79 | COMPREPLY=($(compgen -f "${cur}")) 80 | return 0 81 | ;; 82 | --log) 83 | COMPREPLY=($(compgen -f "${cur}")) 84 | return 0 85 | ;; 86 | --metadata) 87 | COMPREPLY=($(compgen -f "${cur}")) 88 | return 0 89 | ;; 90 | -m) 91 | COMPREPLY=($(compgen -f "${cur}")) 92 | return 0 93 | ;; 94 | --metadataMimeType) 95 | COMPREPLY=($(compgen -W "application/avro application/cbor application/graphql application/gzip application/javascript application/json application/octet-stream application/pdf application/vnd.apache.thrift.binary application/vnd.google.protobuf application/xml application/zip audio/aac audio/mp3 audio/mp4 audio/mpeg3 audio/mpeg audio/ogg audio/opus audio/vorbis image/bmp image/gif image/heic-sequence image/heic image/heif-sequence image/heif image/jpeg image/png image/tiff multipart/mixed text/css text/csv text/html text/plain text/xml video/H264 video/H265 video/VP8 application/x-hessian application/x-java-object application/cloudevents+json message/x.rsocket.mime-type.v0 message/x.rsocket.accept-mime-types.v0 message/x.rsocket.authentication.v0 message/x.rsocket.tracing-zipkin.v0 message/x.rsocket.routing.v0 message/x.rsocket.composite-metadata.v0" -- "${cur}")) 96 | return 0 97 | ;; 98 | --optsFile) 99 | COMPREPLY=($(compgen -f "${cur}")) 100 | return 0 101 | ;; 102 | --route) 103 | COMPREPLY=($(compgen -f "${cur}")) 104 | return 0 105 | ;; 106 | -r) 107 | COMPREPLY=($(compgen -f "${cur}")) 108 | return 0 109 | ;; 110 | --resume) 111 | COMPREPLY=($(compgen -f "${cur}")) 112 | return 0 113 | ;; 114 | --retry) 115 | COMPREPLY=($(compgen -f "${cur}")) 116 | return 0 117 | ;; 118 | --setupData) 119 | COMPREPLY=($(compgen -f "${cur}")) 120 | return 0 121 | ;; 122 | --setupMetadata) 123 | COMPREPLY=($(compgen -f "${cur}")) 124 | return 0 125 | ;; 126 | --setupMetadataMimeType) 127 | COMPREPLY=($(compgen -W "text/plain application/json message/x.rsocket.authentication.v0 message/x.rsocket.authentication.basic.v0 message/x.rsocket.application+json" -- "${cur}")) 128 | return 0 129 | ;; 130 | --take) 131 | COMPREPLY=($(compgen -f "${cur}")) 132 | return 0 133 | ;; 134 | --trace) 135 | COMPREPLY=($(compgen -W "UNDECIDED NOT_SAMPLE SAMPLE DEBUG" -- "${cur}")) 136 | return 0 137 | ;; 138 | --trustCert) 139 | COMPREPLY=($(compgen -f "${cur}")) 140 | return 0 141 | ;; 142 | --authSimple) 143 | COMPREPLY=($(compgen -f "${cur}")) 144 | return 0 145 | ;; 146 | -u) 147 | COMPREPLY=($(compgen -f "${cur}")) 148 | return 0 149 | ;; 150 | --wsHeader) 151 | COMPREPLY=($(compgen -f "${cur}")) 152 | return 0 153 | ;; 154 | --zipkinUrl) 155 | COMPREPLY=($(compgen -f "${cur}")) 156 | return 0 157 | ;; 158 | *) 159 | COMPREPLY=() 160 | ;; 161 | esac 162 | COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) 163 | return 0 164 | ;; 165 | 166 | esac 167 | } 168 | 169 | complete -F _rsc -o bashdefault -o default rsc 170 | -------------------------------------------------------------------------------- /src/main/resources/completions/fish: -------------------------------------------------------------------------------- 1 | complete -c rsc -n "__fish_use_subcommand" -l authBearer -d 'Enable Authentication Metadata Extension (Bearer).' -r 2 | complete -c rsc -n "__fish_use_subcommand" -l authBasic -d '[DEPRECATED] Enable Authentication Metadata Extension (Basic). This Metadata exists only for the backward compatibility with Spring Security 5.2' -r 3 | complete -c rsc -n "__fish_use_subcommand" -l completion -d 'Output shell completion code for the specified shell (bash, zsh, fish, powershell)' -r -f -a "bash zsh fish powershell" 4 | complete -c rsc -n "__fish_use_subcommand" -s d -l data -d 'Data. Use \'-\' to read data from standard input.' -r 5 | complete -c rsc -n "__fish_use_subcommand" -l dataMimeType -d 'MimeType for data' -r -f -a "application/avro application/cbor application/graphql application/gzip application/javascript application/json application/octet-stream application/pdf application/vnd.apache.thrift.binary application/vnd.google.protobuf application/xml application/zip audio/aac audio/mp3 audio/mp4 audio/mpeg3 audio/mpeg audio/ogg audio/opus audio/vorbis image/bmp image/gif image/heic-sequence image/heic image/heif-sequence image/heif image/jpeg image/png image/tiff multipart/mixed text/css text/csv text/html text/plain text/xml video/H264 video/H265 video/VP8 application/x-hessian application/x-java-object application/cloudevents+json message/x.rsocket.mime-type.v0 message/x.rsocket.accept-mime-types.v0 message/x.rsocket.authentication.v0 message/x.rsocket.tracing-zipkin.v0 message/x.rsocket.routing.v0 message/x.rsocket.composite-metadata.v0" 6 | complete -c rsc -n "__fish_use_subcommand" -l delayElements -d 'Enable delayElements(delay) in milli seconds' -r 7 | complete -c rsc -n "__fish_use_subcommand" -s h -l help -d 'Print help' -r 8 | complete -c rsc -n "__fish_use_subcommand" -l interactionModel -d 'InteractionModel' -r -f -a "REQUEST_RESPONSE REQUEST_STREAM REQUEST_CHANNEL FIRE_AND_FORGET" 9 | complete -c rsc -n "__fish_use_subcommand" -s l -l load -d 'Load a file as Data. (e.g. ./foo.txt, /tmp/foo.txt, https://example.com)' -r 10 | complete -c rsc -n "__fish_use_subcommand" -l limitRate -d 'Enable limitRate(rate)' -r 11 | complete -c rsc -n "__fish_use_subcommand" -l log -d 'Enable log()' -r 12 | complete -c rsc -n "__fish_use_subcommand" -s m -l metadata -d 'Metadata (default: )' -r 13 | complete -c rsc -n "__fish_use_subcommand" -l metadataMimeType -d 'MimeType for metadata (default: application/json)' -r -f -a "application/avro application/cbor application/graphql application/gzip application/javascript application/json application/octet-stream application/pdf application/vnd.apache.thrift.binary application/vnd.google.protobuf application/xml application/zip audio/aac audio/mp3 audio/mp4 audio/mpeg3 audio/mpeg audio/ogg audio/opus audio/vorbis image/bmp image/gif image/heic-sequence image/heic image/heif-sequence image/heif image/jpeg image/png image/tiff multipart/mixed text/css text/csv text/html text/plain text/xml video/H264 video/H265 video/VP8 application/x-hessian application/x-java-object application/cloudevents+json message/x.rsocket.mime-type.v0 message/x.rsocket.accept-mime-types.v0 message/x.rsocket.authentication.v0 message/x.rsocket.tracing-zipkin.v0 message/x.rsocket.routing.v0 message/x.rsocket.composite-metadata.v0" 14 | complete -c rsc -n "__fish_use_subcommand" -l optsFile -d 'Configure options from a YAML file (e.g. ./opts.yaml, /tmp/opts.yaml, https://example.com/opts.yaml)' -r 15 | complete -c rsc -n "__fish_use_subcommand" -s r -l route -d 'Enable Routing Metadata Extension' -r 16 | complete -c rsc -n "__fish_use_subcommand" -l resume -d 'Enable resume. Resume session duration can be configured in seconds.' -r 17 | complete -c rsc -n "__fish_use_subcommand" -l retry -d 'Enable retry. Retry every 1 second with the given max attempts.' -r 18 | complete -c rsc -n "__fish_use_subcommand" -l setupData -d 'Data for Setup payload' -r 19 | complete -c rsc -n "__fish_use_subcommand" -l setupMetadata -d 'Metadata for Setup payload' -r 20 | complete -c rsc -n "__fish_use_subcommand" -l setupMetadataMimeType -d 'Metadata MimeType for Setup payload (default: application/json)' -r -f -a "text/plain application/json message/x.rsocket.authentication.v0 message/x.rsocket.authentication.basic.v0 message/x.rsocket.application+json" 21 | complete -c rsc -n "__fish_use_subcommand" -l take -d 'Enable take(n)' -r 22 | complete -c rsc -n "__fish_use_subcommand" -l trace -d 'Enable Tracing (Zipkin) Metadata Extension. Unless sampling state (UNDECIDED, NOT_SAMPLE, SAMPLE, DEBUG) is specified, DEBUG is used if no state is specified.' -r -f -a "UNDECIDED NOT_SAMPLE SAMPLE DEBUG" 23 | complete -c rsc -n "__fish_use_subcommand" -l trustCert -d 'PEM file for a trusted certificate. (e.g. ./foo.crt, /tmp/foo.crt, https://example.com/foo.crt)' -r 24 | complete -c rsc -n "__fish_use_subcommand" -s u -l authSimple -d 'Enable Authentication Metadata Extension (Simple). The format must be \'username:password\'.' -r 25 | complete -c rsc -n "__fish_use_subcommand" -l wsHeader -d 'Header for web socket connection' -r 26 | complete -c rsc -n "__fish_use_subcommand" -l zipkinUrl -d 'Zipkin URL to send a span (e.g. http://localhost:9411). Ignored unless --trace is set.' -r 27 | complete -c rsc -n "__fish_use_subcommand" -l channel -d 'Shortcut of --im REQUEST_CHANNEL' 28 | complete -c rsc -n "__fish_use_subcommand" -l debug -d 'Enable FrameLogger' 29 | complete -c rsc -n "__fish_use_subcommand" -l dumpOpts -d 'Dump options as a file that can be loaded by --optsFile option' 30 | complete -c rsc -n "__fish_use_subcommand" -l fnf -d 'Shortcut of --im FIRE_AND_FORGET' 31 | complete -c rsc -n "__fish_use_subcommand" -l printB3 -d 'Print B3 propagation info. Ignored unless --trace is set.' 32 | complete -c rsc -n "__fish_use_subcommand" -s q -l quiet -d 'Disable the output on next' 33 | complete -c rsc -n "__fish_use_subcommand" -l request -d 'Shortcut of --im REQUEST_RESPONSE' 34 | complete -c rsc -n "__fish_use_subcommand" -l showSystemProperties -d 'Show SystemProperties for troubleshoot' 35 | complete -c rsc -n "__fish_use_subcommand" -l stacktrace -d 'Show Stacktrace when an exception happens' 36 | complete -c rsc -n "__fish_use_subcommand" -l stream -d 'Shortcut of --im REQUEST_STREAM' 37 | complete -c rsc -n "__fish_use_subcommand" -s v -l version -d 'Print version' 38 | complete -c rsc -n "__fish_use_subcommand" -s w -l wiretap -d 'Enable wiretap' 39 | complete -c rsc -n "__fish_use_subcommand" -s h -l help -d 'Prints help information' 40 | -------------------------------------------------------------------------------- /src/main/resources/completions/powershell: -------------------------------------------------------------------------------- 1 | 2 | using namespace System.Management.Automation 3 | using namespace System.Management.Automation.Language 4 | 5 | Register-ArgumentCompleter -Native -CommandName 'rsc' -ScriptBlock { 6 | param($wordToComplete, $commandAst, $cursorPosition) 7 | 8 | $commandElements = $commandAst.CommandElements 9 | $command = @( 10 | 'rsc' 11 | for ($i = 1; $i -lt $commandElements.Count; $i++) { 12 | $element = $commandElements[$i] 13 | if ($element -isnot [StringConstantExpressionAst] -or 14 | $element.StringConstantType -ne [StringConstantType]::BareWord -or 15 | $element.Value.StartsWith('-')) { 16 | break 17 | } 18 | $element.Value 19 | }) -join ';' 20 | 21 | $completions = @(switch ($command) { 22 | 'rsc' { 23 | [CompletionResult]::new('--authBearer', 'authBearer', [CompletionResultType]::ParameterName, 'Enable Authentication Metadata Extension (Bearer).') 24 | [CompletionResult]::new('--authBasic', 'authBasic', [CompletionResultType]::ParameterName, '[DEPRECATED] Enable Authentication Metadata Extension (Basic). This Metadata exists only for the backward compatibility with Spring Security 5.2') 25 | [CompletionResult]::new('--completion', 'completion', [CompletionResultType]::ParameterName, 'Output shell completion code for the specified shell (bash, zsh, fish, powershell)') 26 | [CompletionResult]::new('-d', 'd', [CompletionResultType]::ParameterName, 'Data. Use ''-'' to read data from standard input.') 27 | [CompletionResult]::new('--data', 'data', [CompletionResultType]::ParameterName, 'Data. Use ''-'' to read data from standard input.') 28 | [CompletionResult]::new('--dataMimeType', 'dataMimeType', [CompletionResultType]::ParameterName, 'MimeType for data') 29 | [CompletionResult]::new('--delayElements', 'delayElements', [CompletionResultType]::ParameterName, 'Enable delayElements(delay) in milli seconds') 30 | [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help') 31 | [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help') 32 | [CompletionResult]::new('--interactionModel', 'interactionModel', [CompletionResultType]::ParameterName, 'InteractionModel') 33 | [CompletionResult]::new('-l', 'l', [CompletionResultType]::ParameterName, 'Load a file as Data. (e.g. ./foo.txt, /tmp/foo.txt, https://example.com)') 34 | [CompletionResult]::new('--load', 'load', [CompletionResultType]::ParameterName, 'Load a file as Data. (e.g. ./foo.txt, /tmp/foo.txt, https://example.com)') 35 | [CompletionResult]::new('--limitRate', 'limitRate', [CompletionResultType]::ParameterName, 'Enable limitRate(rate)') 36 | [CompletionResult]::new('--log', 'log', [CompletionResultType]::ParameterName, 'Enable log()') 37 | [CompletionResult]::new('-m', 'm', [CompletionResultType]::ParameterName, 'Metadata (default: )') 38 | [CompletionResult]::new('--metadata', 'metadata', [CompletionResultType]::ParameterName, 'Metadata (default: )') 39 | [CompletionResult]::new('--metadataMimeType', 'metadataMimeType', [CompletionResultType]::ParameterName, 'MimeType for metadata (default: application/json)') 40 | [CompletionResult]::new('--optsFile', 'optsFile', [CompletionResultType]::ParameterName, 'Configure options from a YAML file (e.g. ./opts.yaml, /tmp/opts.yaml, https://example.com/opts.yaml)') 41 | [CompletionResult]::new('-r', 'r', [CompletionResultType]::ParameterName, 'Enable Routing Metadata Extension') 42 | [CompletionResult]::new('--route', 'route', [CompletionResultType]::ParameterName, 'Enable Routing Metadata Extension') 43 | [CompletionResult]::new('--resume', 'resume', [CompletionResultType]::ParameterName, 'Enable resume. Resume session duration can be configured in seconds.') 44 | [CompletionResult]::new('--retry', 'retry', [CompletionResultType]::ParameterName, 'Enable retry. Retry every 1 second with the given max attempts.') 45 | [CompletionResult]::new('--setupData', 'setupData', [CompletionResultType]::ParameterName, 'Data for Setup payload') 46 | [CompletionResult]::new('--setupMetadata', 'setupMetadata', [CompletionResultType]::ParameterName, 'Metadata for Setup payload') 47 | [CompletionResult]::new('--setupMetadataMimeType', 'setupMetadataMimeType', [CompletionResultType]::ParameterName, 'Metadata MimeType for Setup payload (default: application/json)') 48 | [CompletionResult]::new('--take', 'take', [CompletionResultType]::ParameterName, 'Enable take(n)') 49 | [CompletionResult]::new('--trace', 'trace', [CompletionResultType]::ParameterName, 'Enable Tracing (Zipkin) Metadata Extension. Unless sampling state (UNDECIDED, NOT_SAMPLE, SAMPLE, DEBUG) is specified, DEBUG is used if no state is specified.') 50 | [CompletionResult]::new('--trustCert', 'trustCert', [CompletionResultType]::ParameterName, 'PEM file for a trusted certificate. (e.g. ./foo.crt, /tmp/foo.crt, https://example.com/foo.crt)') 51 | [CompletionResult]::new('-u', 'u', [CompletionResultType]::ParameterName, 'Enable Authentication Metadata Extension (Simple). The format must be ''username:password''.') 52 | [CompletionResult]::new('--authSimple', 'authSimple', [CompletionResultType]::ParameterName, 'Enable Authentication Metadata Extension (Simple). The format must be ''username:password''.') 53 | [CompletionResult]::new('--wsHeader', 'wsHeader', [CompletionResultType]::ParameterName, 'Header for web socket connection') 54 | [CompletionResult]::new('--zipkinUrl', 'zipkinUrl', [CompletionResultType]::ParameterName, 'Zipkin URL to send a span (e.g. http://localhost:9411). Ignored unless --trace is set.') 55 | [CompletionResult]::new('--channel', 'channel', [CompletionResultType]::ParameterName, 'Shortcut of --im REQUEST_CHANNEL') 56 | [CompletionResult]::new('--debug', 'debug', [CompletionResultType]::ParameterName, 'Enable FrameLogger') 57 | [CompletionResult]::new('--dumpOpts', 'dumpOpts', [CompletionResultType]::ParameterName, 'Dump options as a file that can be loaded by --optsFile option') 58 | [CompletionResult]::new('--fnf', 'fnf', [CompletionResultType]::ParameterName, 'Shortcut of --im FIRE_AND_FORGET') 59 | [CompletionResult]::new('--printB3', 'printB3', [CompletionResultType]::ParameterName, 'Print B3 propagation info. Ignored unless --trace is set.') 60 | [CompletionResult]::new('-q', 'q', [CompletionResultType]::ParameterName, 'Disable the output on next') 61 | [CompletionResult]::new('--quiet', 'quiet', [CompletionResultType]::ParameterName, 'Disable the output on next') 62 | [CompletionResult]::new('--request', 'request', [CompletionResultType]::ParameterName, 'Shortcut of --im REQUEST_RESPONSE') 63 | [CompletionResult]::new('--showSystemProperties', 'showSystemProperties', [CompletionResultType]::ParameterName, 'Show SystemProperties for troubleshoot') 64 | [CompletionResult]::new('--stacktrace', 'stacktrace', [CompletionResultType]::ParameterName, 'Show Stacktrace when an exception happens') 65 | [CompletionResult]::new('--stream', 'stream', [CompletionResultType]::ParameterName, 'Shortcut of --im REQUEST_STREAM') 66 | [CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'Print version') 67 | [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version') 68 | [CompletionResult]::new('-w', 'w', [CompletionResultType]::ParameterName, 'Enable wiretap') 69 | [CompletionResult]::new('--wiretap', 'wiretap', [CompletionResultType]::ParameterName, 'Enable wiretap') 70 | [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information') 71 | [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information') 72 | break 73 | } 74 | }) 75 | 76 | $completions.Where{ $_.CompletionText -like "$wordToComplete*" } | 77 | Sort-Object -Property ListItemText 78 | } 79 | -------------------------------------------------------------------------------- /src/main/resources/completions/zsh: -------------------------------------------------------------------------------- 1 | #compdef rsc 2 | 3 | autoload -U is-at-least 4 | 5 | _rsc() { 6 | typeset -A opt_args 7 | typeset -a _arguments_options 8 | local ret=1 9 | 10 | if is-at-least 5.2; then 11 | _arguments_options=(-s -S -C) 12 | else 13 | _arguments_options=(-s -C) 14 | fi 15 | 16 | local context curcontext="$curcontext" state line 17 | _arguments "${_arguments_options[@]}" \ 18 | '--authBearer=[Enable Authentication Metadata Extension (Bearer).]' \ 19 | '--authBasic=[\[DEPRECATED\] Enable Authentication Metadata Extension (Basic). This Metadata exists only for the backward compatibility with Spring Security 5.2]' \ 20 | '--completion=[Output shell completion code for the specified shell (bash, zsh, fish, powershell)]: :(bash zsh fish powershell)' \ 21 | '-d+[Data. Use '\''-'\'' to read data from standard input.]' \ 22 | '--data=[Data. Use '\''-'\'' to read data from standard input.]' \ 23 | '--dataMimeType=[MimeType for data]: :(application/avro application/cbor application/graphql application/gzip application/javascript application/json application/octet-stream application/pdf application/vnd.apache.thrift.binary application/vnd.google.protobuf application/xml application/zip audio/aac audio/mp3 audio/mp4 audio/mpeg3 audio/mpeg audio/ogg audio/opus audio/vorbis image/bmp image/gif image/heic-sequence image/heic image/heif-sequence image/heif image/jpeg image/png image/tiff multipart/mixed text/css text/csv text/html text/plain text/xml video/H264 video/H265 video/VP8 application/x-hessian application/x-java-object application/cloudevents+json message/x.rsocket.mime-type.v0 message/x.rsocket.accept-mime-types.v0 message/x.rsocket.authentication.v0 message/x.rsocket.tracing-zipkin.v0 message/x.rsocket.routing.v0 message/x.rsocket.composite-metadata.v0)' \ 24 | '--delayElements=[Enable delayElements(delay) in milli seconds]' \ 25 | '-h+[Print help]' \ 26 | '--help=[Print help]' \ 27 | '--interactionModel=[InteractionModel]: :(REQUEST_RESPONSE REQUEST_STREAM REQUEST_CHANNEL FIRE_AND_FORGET)' \ 28 | '-l+[Load a file as Data. (e.g. ./foo.txt, /tmp/foo.txt, https://example.com)]' \ 29 | '--load=[Load a file as Data. (e.g. ./foo.txt, /tmp/foo.txt, https://example.com)]' \ 30 | '--limitRate=[Enable limitRate(rate)]' \ 31 | '--log=[Enable log()]' \ 32 | '-m+[Metadata (default: )]' \ 33 | '--metadata=[Metadata (default: )]' \ 34 | '--metadataMimeType=[MimeType for metadata (default: application/json)]: :(application/avro application/cbor application/graphql application/gzip application/javascript application/json application/octet-stream application/pdf application/vnd.apache.thrift.binary application/vnd.google.protobuf application/xml application/zip audio/aac audio/mp3 audio/mp4 audio/mpeg3 audio/mpeg audio/ogg audio/opus audio/vorbis image/bmp image/gif image/heic-sequence image/heic image/heif-sequence image/heif image/jpeg image/png image/tiff multipart/mixed text/css text/csv text/html text/plain text/xml video/H264 video/H265 video/VP8 application/x-hessian application/x-java-object application/cloudevents+json message/x.rsocket.mime-type.v0 message/x.rsocket.accept-mime-types.v0 message/x.rsocket.authentication.v0 message/x.rsocket.tracing-zipkin.v0 message/x.rsocket.routing.v0 message/x.rsocket.composite-metadata.v0)' \ 35 | '--optsFile=[Configure options from a YAML file (e.g. ./opts.yaml, /tmp/opts.yaml, https://example.com/opts.yaml)]' \ 36 | '-r+[Enable Routing Metadata Extension]' \ 37 | '--route=[Enable Routing Metadata Extension]' \ 38 | '--resume=[Enable resume. Resume session duration can be configured in seconds.]' \ 39 | '--retry=[Enable retry. Retry every 1 second with the given max attempts.]' \ 40 | '--setupData=[Data for Setup payload]' \ 41 | '--setupMetadata=[Metadata for Setup payload]' \ 42 | '--setupMetadataMimeType=[Metadata MimeType for Setup payload (default: application/json)]: :(text/plain application/json message/x.rsocket.authentication.v0 message/x.rsocket.authentication.basic.v0 message/x.rsocket.application+json)' \ 43 | '--take=[Enable take(n)]' \ 44 | '--trace=[Enable Tracing (Zipkin) Metadata Extension. Unless sampling state (UNDECIDED, NOT_SAMPLE, SAMPLE, DEBUG) is specified, DEBUG is used if no state is specified.]: :(UNDECIDED NOT_SAMPLE SAMPLE DEBUG)' \ 45 | '--trustCert=[PEM file for a trusted certificate. (e.g. ./foo.crt, /tmp/foo.crt, https://example.com/foo.crt)]' \ 46 | '-u+[Enable Authentication Metadata Extension (Simple). The format must be '\''username:password'\''.]' \ 47 | '--authSimple=[Enable Authentication Metadata Extension (Simple). The format must be '\''username:password'\''.]' \ 48 | '--wsHeader=[Header for web socket connection]' \ 49 | '--zipkinUrl=[Zipkin URL to send a span (e.g. http://localhost:9411). Ignored unless --trace is set.]' \ 50 | '--channel[Shortcut of --im REQUEST_CHANNEL]' \ 51 | '--debug[Enable FrameLogger]' \ 52 | '--dumpOpts[Dump options as a file that can be loaded by --optsFile option]' \ 53 | '--fnf[Shortcut of --im FIRE_AND_FORGET]' \ 54 | '--printB3[Print B3 propagation info. Ignored unless --trace is set.]' \ 55 | '-q[Disable the output on next]' \ 56 | '--quiet[Disable the output on next]' \ 57 | '--request[Shortcut of --im REQUEST_RESPONSE]' \ 58 | '--showSystemProperties[Show SystemProperties for troubleshoot]' \ 59 | '--stacktrace[Show Stacktrace when an exception happens]' \ 60 | '--stream[Shortcut of --im REQUEST_STREAM]' \ 61 | '-v[Print version]' \ 62 | '--version[Print version]' \ 63 | '-w[Enable wiretap]' \ 64 | '--wiretap[Enable wiretap]' \ 65 | '-h[Prints help information]' \ 66 | '--help[Prints help information]' \ 67 | && ret=0 68 | 69 | } 70 | 71 | (( $+functions[_rsc_commands] )) || 72 | _rsc_commands() { 73 | local commands; commands=( 74 | 75 | ) 76 | _describe -t commands 'rsc commands' commands "$@" 77 | } 78 | 79 | _rsc "$@" -------------------------------------------------------------------------------- /src/test/java/am/ik/rsocket/ArgsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Toshiaki Maki 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 | * http://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 | package am.ik.rsocket; 17 | 18 | import java.time.Duration; 19 | import java.util.List; 20 | import java.util.Optional; 21 | 22 | import am.ik.rsocket.routing.Route; 23 | import am.ik.rsocket.security.BasicAuthentication; 24 | import am.ik.rsocket.security.BearerAuthentication; 25 | import am.ik.rsocket.security.SimpleAuthentication; 26 | import io.netty.buffer.ByteBuf; 27 | import io.netty.buffer.ByteBufAllocator; 28 | import io.netty.buffer.Unpooled; 29 | import io.rsocket.metadata.TracingMetadataCodec.Flags; 30 | import io.rsocket.metadata.WellKnownMimeType; 31 | import io.rsocket.transport.ClientTransport; 32 | import io.rsocket.transport.netty.client.TcpClientTransport; 33 | import io.rsocket.transport.netty.client.WebsocketClientTransport; 34 | import org.junit.jupiter.api.Assertions; 35 | import org.junit.jupiter.api.Test; 36 | import reactor.util.function.Tuple2; 37 | 38 | import static am.ik.rsocket.Args.DEFAULT_METADATA_MIME_TYPE; 39 | import static am.ik.rsocket.Args.addCompositeMetadata; 40 | import static am.ik.rsocket.SetupMetadataMimeType.AUTHENTICATION_BASIC; 41 | import static am.ik.rsocket.SetupMetadataMimeType.MESSAGE_RSOCKET_AUTHENTICATION; 42 | import static java.nio.charset.StandardCharsets.UTF_8; 43 | import static java.util.stream.Collectors.toList; 44 | import static org.assertj.core.api.Assertions.assertThat; 45 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 46 | 47 | class ArgsTest { 48 | 49 | @Test 50 | void clientTransportTcp() { 51 | final Args args = new Args(new String[] { "tcp://localhost:8080" }); 52 | final ClientTransport clientTransport = args.clientTransport(); 53 | assertThat(clientTransport).isOfAnyClassIn(TcpClientTransport.class); 54 | } 55 | 56 | @Test 57 | void clientTransportWebsocket() { 58 | final Args args = new Args(new String[] { "ws://localhost:8080" }); 59 | final ClientTransport clientTransport = args.clientTransport(); 60 | assertThat(clientTransport).isOfAnyClassIn(WebsocketClientTransport.class); 61 | } 62 | 63 | @Test 64 | void clientTransportHttp() { 65 | final Args args = new Args(new String[] { "http://localhost:8080" }); 66 | final IllegalArgumentException exception = Assertions.assertThrows(IllegalArgumentException.class, 67 | args::clientTransport); 68 | assertThat(exception.getMessage()).isEqualTo("http is unsupported scheme."); 69 | } 70 | 71 | @Test 72 | void port() { 73 | final Args args = new Args(new String[] { "ws://localhost:8080" }); 74 | assertThat(args.port()).isEqualTo(8080); 75 | } 76 | 77 | @Test 78 | void portNoSecureDefault() { 79 | final Args args = new Args(new String[] { "ws://localhost" }); 80 | assertThat(args.port()).isEqualTo(80); 81 | } 82 | 83 | @Test 84 | void portSecureDefault() { 85 | final Args args = new Args(new String[] { "wss://localhost" }); 86 | assertThat(args.port()).isEqualTo(443); 87 | } 88 | 89 | @Test 90 | void resumeDisabled() { 91 | final Args args = new Args(new String[] { "tcp://localhost:8080" }); 92 | assertThat(args.resume().isPresent()).isFalse(); 93 | } 94 | 95 | @Test 96 | void resumeEnabledWithoutValue() { 97 | final Args args = new Args(new String[] { "tcp://localhost:8080", "--resume" }); 98 | assertThatThrownBy(args::resume).isInstanceOf(IllegalArgumentException.class); 99 | } 100 | 101 | @Test 102 | void resumeEnabledWithDuration() { 103 | final Args args = new Args(new String[] { "tcp://localhost:8080", "--resume", "600" }); 104 | assertThat(args.resume().isPresent()).isTrue(); 105 | assertThat(args.resume().get()).isEqualTo(Duration.ofSeconds(600)); 106 | } 107 | 108 | @Test 109 | void route() { 110 | final Args args = new Args(new String[] { "tcp://localhost:8080", "-r", "locate.aircrafts.for" }); 111 | final Tuple2 metadata = args.composeMetadata(); 112 | assertThat(metadata.getT1()).isEqualTo("message/x.rsocket.composite-metadata.v0"); 113 | assertThat(metadata.getT2()).isEqualTo(addCompositeMetadata(new Route("locate.aircrafts.for").toMetadata(ByteBufAllocator.DEFAULT), WellKnownMimeType.MESSAGE_RSOCKET_ROUTING)); 114 | } 115 | 116 | @Test 117 | void metadataDefault() { 118 | final Args args = new Args(new String[] { "tcp://localhost:8080" }); 119 | final Tuple2 metadata = args.composeMetadata(); 120 | assertThat(metadata.getT1()).isEqualTo(DEFAULT_METADATA_MIME_TYPE); 121 | assertThat(metadata.getT2().toString(UTF_8)).isEqualTo(""); 122 | } 123 | 124 | @Test 125 | void metadata() { 126 | final Args args = new Args("tcp://localhost:8080 -m {\"foo\":\"bar\"}"); 127 | final Tuple2 metadata = args.composeMetadata(); 128 | assertThat(metadata.getT1()).isEqualTo("message/x.rsocket.composite-metadata.v0"); 129 | assertThat(metadata.getT2()).isEqualTo(addCompositeMetadata(Unpooled.wrappedBuffer("{\"foo\":\"bar\"}".getBytes()), WellKnownMimeType.APPLICATION_JSON)); 130 | } 131 | 132 | @Test 133 | void metadataSingle() { 134 | final Args args = new Args(new String[] { "tcp://localhost:8080", "--metadataMimeType", 135 | "application/vnd.spring.rsocket.metadata+json", "-m", "{\"route\":\"locate.aircrafts.for\"}" }); 136 | final Tuple2 metadata = args.composeMetadata(); 137 | assertThat(metadata.getT1()).isEqualTo("message/x.rsocket.composite-metadata.v0"); 138 | assertThat(metadata.getT2()).isEqualTo(addCompositeMetadata(Unpooled.wrappedBuffer("{\"route\":\"locate.aircrafts.for\"}".getBytes()), "application/vnd.spring.rsocket.metadata+json")); 139 | } 140 | 141 | @Test 142 | void setupData() { 143 | final Args args = new Args("tcp://localhost:8080 --sd hello"); 144 | assertThat(args.setupPayload().isPresent()).isTrue(); 145 | assertThat(args.setupPayload().get().getDataUtf8()).isEqualTo("hello"); 146 | assertThat(args.setupPayload().get().getMetadataUtf8()).isEqualTo(""); 147 | } 148 | 149 | @Test 150 | void setupDataMissing() { 151 | final Args args = new Args("tcp://localhost:8080 --sd"); 152 | assertThatThrownBy(args::setupPayload) 153 | .isInstanceOf(IllegalArgumentException.class); 154 | } 155 | 156 | @Test 157 | void setupMetaData() { 158 | final Args args = new Args("tcp://localhost:8080 --sm hello"); 159 | assertThat(args.setupPayload().isPresent()).isTrue(); 160 | assertThat(args.setupPayload().get().getDataUtf8()).isEqualTo(""); 161 | final ByteBuf setupMetadata = addCompositeMetadata(Unpooled.wrappedBuffer("hello".getBytes()), DEFAULT_METADATA_MIME_TYPE); 162 | assertThat(args.setupPayload().get().getMetadata()).isEqualTo(setupMetadata.nioBuffer()); 163 | } 164 | 165 | @Test 166 | void setupMetadataMissing() { 167 | final Args args = new Args("tcp://localhost:8080 --sm"); 168 | assertThatThrownBy(args::setupPayload) 169 | .isInstanceOf(IllegalArgumentException.class); 170 | } 171 | 172 | @Test 173 | void setupDataAndMetadata() { 174 | final Args args = new Args("tcp://localhost:8080 --sd hello --sm meta"); 175 | assertThat(args.setupPayload().isPresent()).isTrue(); 176 | assertThat(args.setupPayload().get().getDataUtf8()).isEqualTo("hello"); 177 | final ByteBuf setupMetadata = addCompositeMetadata(Unpooled.wrappedBuffer("meta".getBytes()), DEFAULT_METADATA_MIME_TYPE); 178 | assertThat(args.setupPayload().get().getMetadata()).isEqualTo(setupMetadata.nioBuffer()); 179 | } 180 | 181 | @Test 182 | void setupMetadataMimeType() { 183 | final Args args = new Args("tcp://localhost:8080 --sd hello --sm {\"value\":\"foo\"} --smmt application/json"); 184 | assertThat(args.setupPayload().isPresent()).isTrue(); 185 | assertThat(args.setupPayload().get().getDataUtf8()).isEqualTo("hello"); 186 | final ByteBuf setupMetadata = addCompositeMetadata(Unpooled.wrappedBuffer("{\"value\":\"foo\"}".getBytes()), "application/json"); 187 | assertThat(args.setupPayload().get().getMetadata()).isEqualTo(setupMetadata.nioBuffer()); 188 | } 189 | 190 | @Test 191 | void setupMetadataMimeTypeEnum() { 192 | final Args args = new Args("tcp://localhost:8080 --sd hello --sm {\"value\":\"foo\"} --smmt APPLICATION_JSON"); 193 | assertThat(args.setupPayload().isPresent()).isTrue(); 194 | assertThat(args.setupPayload().get().getDataUtf8()).isEqualTo("hello"); 195 | final ByteBuf setupMetadata = addCompositeMetadata(Unpooled.wrappedBuffer("{\"value\":\"foo\"}".getBytes()), "application/json"); 196 | assertThat(args.setupPayload().get().getMetadata()).isEqualTo(setupMetadata.nioBuffer()); 197 | } 198 | 199 | @Test 200 | void setupMetadataMimeTypeMissing() { 201 | final Args args = new Args("tcp://localhost:8080 --sm foo --smmt"); 202 | assertThatThrownBy(args::setupPayload) 203 | .isInstanceOf(IllegalArgumentException.class); 204 | } 205 | 206 | @Test 207 | void setupMetadataAuthSimple() { 208 | final Args args = new Args("tcp://localhost:8080 --sm simple:user:pass --smmt MESSAGE_RSOCKET_AUTHENTICATION"); 209 | assertThat(args.setupPayload().isPresent()).isTrue(); 210 | final ByteBuf setupMetadata = addCompositeMetadata(new SimpleAuthentication("user", "pass").toMetadata(ByteBufAllocator.DEFAULT), 211 | MESSAGE_RSOCKET_AUTHENTICATION.getValue()); 212 | assertThat(args.setupPayload().get().getMetadata()).isEqualTo(setupMetadata.nioBuffer()); 213 | } 214 | 215 | @Test 216 | void setupMetadataAuthBearer() { 217 | final Args args = new Args("tcp://localhost:8080 --sm bearer:token --smmt MESSAGE_RSOCKET_AUTHENTICATION"); 218 | assertThat(args.setupPayload().isPresent()).isTrue(); 219 | final ByteBuf setupMetadata = addCompositeMetadata(new BearerAuthentication("token").toMetadata(ByteBufAllocator.DEFAULT), 220 | MESSAGE_RSOCKET_AUTHENTICATION.getValue()); 221 | assertThat(args.setupPayload().get().getMetadata()).isEqualTo(setupMetadata.nioBuffer()); 222 | } 223 | 224 | @Test 225 | void setupMetadataAuthUnknown() { 226 | final Args args = new Args("tcp://localhost:8080 --sm foo:token --smmt MESSAGE_RSOCKET_AUTHENTICATION"); 227 | assertThatThrownBy(args::setupPayload) 228 | .isInstanceOf(IllegalArgumentException.class); 229 | } 230 | 231 | @Test 232 | void setupMetadataAuthIllegal() { 233 | final Args args = new Args("tcp://localhost:8080 --sm foo --smmt MESSAGE_RSOCKET_AUTHENTICATION"); 234 | assertThatThrownBy(args::setupPayload) 235 | .isInstanceOf(IllegalArgumentException.class); 236 | } 237 | 238 | @Test 239 | void setupMetadataMimeTypeIllegal() { 240 | final Args args = new Args("tcp://localhost:8080 --sd hello --sm {\"value\":\"foo\"} --smmt application/foo"); 241 | assertThatThrownBy(args::setupPayload) 242 | .isInstanceOf(IllegalArgumentException.class); 243 | } 244 | 245 | @Test 246 | void setupMetadataBasicAuth() { 247 | final Args args = new Args("tcp://localhost:8080 --sm user:pass --smmt AUTHENTICATION_BASIC"); 248 | assertThat(args.setupPayload().isPresent()).isTrue(); 249 | final ByteBuf setupMetadata = addCompositeMetadata(new BasicAuthentication("user", "pass").toMetadata(ByteBufAllocator.DEFAULT), 250 | AUTHENTICATION_BASIC.getValue()); 251 | assertThat(args.setupPayload().get().getMetadata()).isEqualTo(setupMetadata.nioBuffer()); 252 | } 253 | 254 | @Test 255 | void metadataAuthSimple() { 256 | final Args args = new Args("tcp://localhost:8080 --authSimple user:password"); 257 | final Tuple2 metadata = args.composeMetadata(); 258 | assertThat(metadata.getT1()).isEqualTo("message/x.rsocket.composite-metadata.v0"); 259 | assertThat(metadata.getT2()).isEqualTo(addCompositeMetadata(new SimpleAuthentication("user", "password").toMetadata(ByteBufAllocator.DEFAULT), WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION)); 260 | } 261 | 262 | @Test 263 | void metadataAuthBearer() { 264 | final Args args = new Args("tcp://localhost:8080 --authBearer TOKEN"); 265 | final Tuple2 metadata = args.composeMetadata(); 266 | assertThat(metadata.getT1()).isEqualTo("message/x.rsocket.composite-metadata.v0"); 267 | assertThat(metadata.getT2()).isEqualTo(addCompositeMetadata(new BearerAuthentication("TOKEN").toMetadata(ByteBufAllocator.DEFAULT), WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION)); 268 | } 269 | 270 | @Test 271 | void metadataComposite() { 272 | final Args args = new Args(new String[] { "tcp://localhost:8080", // 273 | "--metadataMimeType", "application/json", // 274 | "-m", "{\"hello\":\"world\"}", // 275 | "--metadataMimeType", "text/plain", // 276 | "-m", "bar" }); 277 | final Tuple2 metadata = args.composeMetadata(); 278 | assertThat(metadata.getT1()).isEqualTo("message/x.rsocket.composite-metadata.v0"); 279 | assertThat(metadata.getT2().toString(UTF_8)).doesNotContain("application/json"); 280 | assertThat(metadata.getT2().toString(UTF_8)).contains("{\"hello\":\"world\"}"); 281 | assertThat(metadata.getT2().toString(UTF_8)).doesNotContain("text/plain"); 282 | assertThat(metadata.getT2().toString(UTF_8)).contains("bar"); 283 | final List metadataList = args.metadata(); 284 | final List metadataMimeTypeList = args.metadataMimeType(); 285 | assertThat(metadataList.stream().map(x -> x.toString(UTF_8)).collect(toList())) 286 | .containsExactly("{\"hello\":\"world\"}", "bar"); 287 | assertThat(metadataMimeTypeList).containsExactly("application/json", "text/plain"); 288 | } 289 | 290 | @Test 291 | void metadataCompositeWithRoute() { 292 | final Args args = new Args(new String[] { "tcp://localhost:8080", // 293 | "--metadataMimeType", "application/json", // 294 | "-m", "{\"hello\":\"world\"}", // 295 | "--metadataMimeType", "text/plain", // 296 | "-m", "bar", // 297 | "--route", "greeting" }); 298 | final Tuple2 metadata = args.composeMetadata(); 299 | assertThat(metadata.getT1()).isEqualTo("message/x.rsocket.composite-metadata.v0"); 300 | assertThat(metadata.getT2().toString(UTF_8)).doesNotContain("application/json"); 301 | assertThat(metadata.getT2().toString(UTF_8)).contains("{\"hello\":\"world\"}"); 302 | assertThat(metadata.getT2().toString(UTF_8)).doesNotContain("text/plain"); 303 | assertThat(metadata.getT2().toString(UTF_8)).contains("bar"); 304 | assertThat(metadata.getT2().toString(UTF_8)).doesNotContain("message/x.rsocket.routing.v0"); 305 | assertThat(metadata.getT2().toString(UTF_8)).contains(new Route("greeting").toMetadata(ByteBufAllocator.DEFAULT).toString(UTF_8)); 306 | final List metadataList = args.metadata(); 307 | final List metadataMimeTypeList = args.metadataMimeType(); 308 | assertThat(metadataList.stream().map(x -> x.toString(UTF_8)).collect(toList())) 309 | .containsExactly(new Route("greeting").toMetadata(ByteBufAllocator.DEFAULT).toString(UTF_8), "{\"hello\":\"world\"}", "bar"); 310 | assertThat(metadataMimeTypeList).containsExactly("message/x.rsocket.routing.v0", "application/json", 311 | "text/plain"); 312 | } 313 | 314 | @Test 315 | void metadataCompositeRouteAndAuthSimple() { 316 | final Args args = new Args("tcp://localhost:8080 -r greeting -u user:demo"); 317 | final Tuple2 metadata = args.composeMetadata(); 318 | final ByteBuf authMetadata = SimpleAuthentication.valueOf("user:demo").toMetadata(ByteBufAllocator.DEFAULT); 319 | final ByteBuf routingMetadata = new Route("greeting").toMetadata(ByteBufAllocator.DEFAULT); 320 | assertThat(metadata.getT1()).isEqualTo("message/x.rsocket.composite-metadata.v0"); 321 | assertThat(metadata.getT2().toString(UTF_8)).doesNotContain("message/x.rsocket.routing.v0"); 322 | assertThat(metadata.getT2().toString(UTF_8)).doesNotContain("message/x.rsocket.authentication.v0"); 323 | assertThat(metadata.getT2().toString(UTF_8)).contains(routingMetadata.toString(UTF_8)); 324 | assertThat(metadata.getT2().toString(UTF_8)).contains(authMetadata.toString(UTF_8)); 325 | final List metadataList = args.metadata(); 326 | final List metadataMimeTypeList = args.metadataMimeType(); 327 | assertThat(metadataList.stream().map(x -> x.toString(UTF_8)).collect(toList())).containsExactly(routingMetadata.toString(UTF_8), authMetadata.toString(UTF_8)); 328 | assertThat(metadataMimeTypeList).containsExactly("message/x.rsocket.routing.v0", "message/x.rsocket.authentication.v0"); 329 | } 330 | 331 | @Test 332 | void metadataCompositeRouteAndAuthBearer() { 333 | final Args args = new Args("tcp://localhost:8080 -r greeting --ab TOKEN"); 334 | final Tuple2 metadata = args.composeMetadata(); 335 | final ByteBuf authMetadata = new BearerAuthentication("TOKEN").toMetadata(ByteBufAllocator.DEFAULT); 336 | final ByteBuf routingMetadata = new Route("greeting").toMetadata(ByteBufAllocator.DEFAULT); 337 | assertThat(metadata.getT1()).isEqualTo("message/x.rsocket.composite-metadata.v0"); 338 | assertThat(metadata.getT2().toString(UTF_8)).doesNotContain("message/x.rsocket.routing.v0"); 339 | assertThat(metadata.getT2().toString(UTF_8)).doesNotContain("message/x.rsocket.authentication.v0"); 340 | assertThat(metadata.getT2().toString(UTF_8)).contains(routingMetadata.toString(UTF_8)); 341 | assertThat(metadata.getT2().toString(UTF_8)).contains(authMetadata.toString(UTF_8)); 342 | final List metadataList = args.metadata(); 343 | final List metadataMimeTypeList = args.metadataMimeType(); 344 | assertThat(metadataList.stream().map(x -> x.toString(UTF_8)).collect(toList())).containsExactly(routingMetadata.toString(UTF_8), authMetadata.toString(UTF_8)); 345 | assertThat(metadataMimeTypeList).containsExactly("message/x.rsocket.routing.v0", "message/x.rsocket.authentication.v0"); 346 | } 347 | 348 | @Test 349 | void metadataCompositeRouteAndAuthBasic() { 350 | final Args args = new Args("tcp://localhost:8080 -r greeting --authBasic user:demo"); 351 | final Tuple2 metadata = args.composeMetadata(); 352 | final ByteBuf authMetadata = BasicAuthentication.valueOf("user:demo").toMetadata(ByteBufAllocator.DEFAULT); 353 | final ByteBuf routingMetadata = new Route("greeting").toMetadata(ByteBufAllocator.DEFAULT); 354 | assertThat(metadata.getT1()).isEqualTo("message/x.rsocket.composite-metadata.v0"); 355 | assertThat(metadata.getT2().toString(UTF_8)).doesNotContain("message/x.rsocket.routing.v0"); 356 | assertThat(metadata.getT2().toString(UTF_8)).contains(routingMetadata.toString(UTF_8)); 357 | assertThat(metadata.getT2().toString(UTF_8)).contains(authMetadata.toString(UTF_8)); 358 | final List metadataList = args.metadata(); 359 | final List metadataMimeTypeList = args.metadataMimeType(); 360 | assertThat(metadataList.stream().map(x -> x.toString(UTF_8)).collect(toList())).containsExactly(routingMetadata.toString(UTF_8), authMetadata.toString(UTF_8)); 361 | assertThat(metadataMimeTypeList).containsExactly("message/x.rsocket.routing.v0", "message/x.rsocket.authentication.basic.v0"); 362 | } 363 | 364 | @Test 365 | void metadataCompositeWithUnknownMimeType() { 366 | final Args args = new Args(new String[] { "tcp://localhost:8080", // 367 | "--metadataMimeType", "application/vnd.spring.rsocket.metadata+json", // 368 | "-m", "{}", // 369 | "--route", "greeting" }); 370 | final Tuple2 metadata = args.composeMetadata(); 371 | assertThat(metadata.getT1()).isEqualTo("message/x.rsocket.composite-metadata.v0"); 372 | // Unknown mime type should present as a US-ASCII string 373 | assertThat(metadata.getT2().toString(UTF_8)).contains("application/vnd.spring.rsocket.metadata+json"); 374 | assertThat(metadata.getT2().toString(UTF_8)).contains("{}"); 375 | assertThat(metadata.getT2().toString(UTF_8)).doesNotContain("message/x.rsocket.routing.v0"); 376 | assertThat(metadata.getT2().toString(UTF_8)).contains(new Route("greeting").toMetadata(ByteBufAllocator.DEFAULT).toString(UTF_8)); 377 | final List metadataList = args.metadata(); 378 | final List metadataMimeTypeList = args.metadataMimeType(); 379 | assertThat(metadataList.stream().map(x -> x.toString(UTF_8)).collect(toList())) 380 | .containsExactly(new Route("greeting").toMetadata(ByteBufAllocator.DEFAULT).toString(UTF_8), "{}"); 381 | assertThat(metadataMimeTypeList).containsExactly("message/x.rsocket.routing.v0", 382 | "application/vnd.spring.rsocket.metadata+json"); 383 | } 384 | 385 | 386 | @Test 387 | void traceDefault() { 388 | final Args args = new Args(new String[] { "tcp://localhost:8080", "--trace" }); 389 | final Optional trace = args.trace(); 390 | assertThat(trace.isPresent()).isTrue(); 391 | assertThat(trace.get()).isEqualTo(Flags.DEBUG); 392 | } 393 | 394 | @Test 395 | void traceSample() { 396 | final Args args = new Args(new String[] { "tcp://localhost:8080", "--trace", "SAMPLE" }); 397 | final Optional trace = args.trace(); 398 | assertThat(trace.isPresent()).isTrue(); 399 | assertThat(trace.get()).isEqualTo(Flags.SAMPLE); 400 | } 401 | } -------------------------------------------------------------------------------- /src/test/java/am/ik/rsocket/InteractionModelTest.java: -------------------------------------------------------------------------------- 1 | package am.ik.rsocket; 2 | 3 | import java.util.Arrays; 4 | import java.util.Scanner; 5 | 6 | import org.junit.jupiter.api.Test; 7 | import reactor.core.publisher.Flux; 8 | import reactor.core.publisher.Mono; 9 | import reactor.test.StepVerifier; 10 | 11 | class InteractionModelTest { 12 | 13 | @Test 14 | void scanToFlux() { 15 | final Scanner scanner = new Scanner(String.join(System.lineSeparator(), Arrays.asList("hello", "world"))); 16 | final Flux flux = InteractionModel.scanToFlux(scanner); 17 | StepVerifier.create(flux) // 18 | .expectNext("hello") // 19 | .expectNext("world") // 20 | .expectComplete() // 21 | .verify(); 22 | } 23 | 24 | @Test 25 | void scanToMono() { 26 | final Scanner scanner = new Scanner(String.join(System.lineSeparator(), Arrays.asList("hello", "world"))); 27 | final Mono mono = InteractionModel.scanToMono(scanner); 28 | StepVerifier.create(mono) // 29 | .expectNext("hello" + System.lineSeparator() + "world") // 30 | .expectComplete() // 31 | .verify(); 32 | } 33 | } -------------------------------------------------------------------------------- /src/test/java/am/ik/rsocket/RscApplicationTests.java: -------------------------------------------------------------------------------- 1 | package am.ik.rsocket; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.extension.ExtendWith; 5 | 6 | import org.springframework.boot.test.system.CapturedOutput; 7 | import org.springframework.boot.test.system.OutputCaptureExtension; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | 11 | @ExtendWith(OutputCaptureExtension.class) 12 | class RscApplicationTests { 13 | 14 | @Test 15 | void help(CapturedOutput capture) throws Exception { 16 | RscApplication.main(new String[] { "-h" }); 17 | assertThat(capture.toString()).isNotEmpty(); 18 | } 19 | 20 | @Test 21 | void helpYamlMode(CapturedOutput capture) throws Exception { 22 | RscApplication.main(new String[] { "-h", "cli-completion" }); 23 | assertThat(capture.toString()).isNotEmpty(); 24 | } 25 | } 26 | --------------------------------------------------------------------------------