├── .devcontainer └── devcontainer.json ├── .editorconfig ├── .gitignore ├── .java-version ├── .travis.yml ├── CHANGES.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts ├── settings.gradle.kts └── src │ └── main │ └── kotlin │ └── BuildHelpers.kt ├── gradle.properties ├── gradle ├── buildViaTravis.sh └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── rsocket-cli ├── settings.gradle ├── src ├── main │ ├── java │ │ └── io │ │ │ └── rsocket │ │ │ └── cli │ │ │ └── graal │ │ │ └── TargetUtilKt.java │ ├── kotlin │ │ └── io │ │ │ └── rsocket │ │ │ └── cli │ │ │ ├── Main.kt │ │ │ ├── UrlCandidates.kt │ │ │ ├── client.kt │ │ │ └── util.kt │ └── resources │ │ └── META-INF │ │ └── native-image │ │ └── io.rsocket.cli │ │ └── rsocketcli │ │ └── reflect-config.json └── test │ ├── kotlin │ └── io │ │ └── rsocket │ │ ├── RunMain.kt │ │ └── util │ │ └── HeaderUtilTest.kt │ └── resources │ ├── headers.txt │ ├── hello.text │ └── value.txt └── zsh └── _rsocket-cli /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "extensions": [ 3 | "vscjava.vscode-java-pack", 4 | "richardwillis.vscode-gradle-extension-pack" 5 | ], 6 | "forwardPorts": [], 7 | "settings": { 8 | "terminal.integrated.shell.linux": "/bin/zsh" 9 | }, 10 | "postCreateCommand": "./gradlew compile" 11 | } 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | continuation_indent_size = 2 8 | 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | 17 | [*.kt] 18 | indent_size = 2 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | 10 | # Packages # 11 | ############ 12 | # it's better to unpack these files and commit the raw source 13 | # git has its own built in compression methods 14 | *.7z 15 | *.dmg 16 | *.gz 17 | *.iso 18 | *.jar 19 | *.rar 20 | *.tar 21 | *.zip 22 | 23 | # Logs and databases # 24 | ###################### 25 | *.log 26 | 27 | # OS generated files # 28 | ###################### 29 | .DS_Store* 30 | ehthumbs.db 31 | Icon? 32 | Thumbs.db 33 | 34 | # Editor Files # 35 | ################ 36 | *~ 37 | *.swp 38 | 39 | # Gradle Files # 40 | ################ 41 | .gradle 42 | .ivy2 43 | .ivy2.cache 44 | .m2 45 | 46 | # Build output directies 47 | /target 48 | */target 49 | /build 50 | */build 51 | 52 | # IntelliJ specific files/directories 53 | out 54 | .idea 55 | *.ipr 56 | *.iws 57 | *.iml 58 | atlassian-ide-plugin.xml 59 | 60 | # Eclipse specific files/directories 61 | .classpath 62 | .project 63 | .settings 64 | .metadata 65 | 66 | # NetBeans specific files/directories 67 | .nbattrs 68 | /bin 69 | -------------------------------------------------------------------------------- /.java-version: -------------------------------------------------------------------------------- 1 | 17 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - openjdk10 4 | 5 | dist: trusty 6 | 7 | # script for build and release via Travis to Bintray 8 | script: gradle/buildViaTravis.sh 9 | 10 | # cache between builds 11 | cache: 12 | directories: 13 | - $HOME/.m2 14 | - $HOME/.gradle 15 | 16 | env: 17 | global: 18 | - secure: Vwv5vS0ZKg3yBdzOUNLhfQDPhfYU6ZR6kJxQ5yKNqZXcAjhLAdx3ZzQMqUTEV1nsID/eAXrPkkHMWlAgCRWOa3RiOBWBCSBTMNmaVKx00SqvOlol5buCia36PaeF3EFRpsfeOjj6aTqEF/1N57RJ3siXdEjmj5a4R1hCMIU6KIlDDg5tG6YTJaSx8BZr/LHsibxMes79+SDvUalU5qAJk+D/pvTcE+WsPjEaBWGoXTIacOf1DIh4CjrBOJ9vOgZU+XsxbdnkJWVuXzVxL1PfY0VYnBAKKQbTirHRahc/t+rogSCl30EByzcdBGAZuI+r5+DIfxAlUFxQadp7dtlbvTTNmqK0w5dbim2L7ZMw5YQV4xGXxjLHWot52cOxrJmsXD0hAA6KczC7JlAbuVZXEVCnGnw1oeEhtvXZmN/pb+AQHhCI6/RZKnIVl/TXz1IAJBfJiwFJAPxuX/wLuW2k9BRhtxw8cqrsQlHlP8GNlpEcmbEW6EQ40UG428RQQoVBqImzcCMuEglZBFbuaB5tP/tNmIO97LSxjr/J5agC5dIq3V3knOYDe45GrdFOIHgUHwAe5loz1uf7k8fR5BI+zF4m5xYyPqDMJ6y89y2KZ0Vf7QrKsVFDzMoJLqD0osi5d1/pRF+p7+4kdXc/yGElooBgQq38TahPKk3LxggIS1E= 19 | - secure: MrvRjKvzuxxucph0qfOKA5jC6BwxZc5inUQM9gXYDNJa6fiNVN65oED64ka9DoCH/+u2l7tyqqaDM7RP1EgpC2PG65723WgISUqlpYbjYmFWwfL/zJnOsGEJlCnoO9mD9lmbYxoZ74Jh3wI24qHtyINUxEHjWP1wPxjT9APaDXFMo2Ls5z6pHLiELe1HRx5hAe6+22RsfH8ZsT/TExmpXf7CYIgA/d4TYc1fzGuybc4mvwI2NVaxKXl6I2YYVwZIA98r48cqWsCLtBdwE6FTdSPPKcXojn1vCAGF3MJpzGzdj+EP2NvuXXVmhWbxN/tlVpfEVMEOxa6iFepXDHO2nUtgcwH1FOX9UZoCMnLygkBnm2gG1RXXqxqqrS/o3OCEMGk912XU/yQrF3LqA+5NPVCKzT3ga7OzlYLPPEFIPIF25hSaDag1IfJxWVI/P0Q9eUJNWa+B+GDwMIPg8rIKwzGaAwEUOAxOLIJcGVqhE9msCGVzLmyhtTOoVeRc1WMVcWzAGwA3U+Dz1iI5hRbsm+pzlf2luvN/GJTqI5wOzMW25YfQkwv7aE77vcA6a5fNA8QksAb09khDhADZlstZ5RSihhFIYK5w724AuzWuGpykPQyv8doGwN7mhVqCUy5yLb1jeWs3lbfW+WUqXMpMy1hS1bjqYte+pcgZAZ0kHgM= 20 | - secure: aaTIAQxdyuiD21AgjS2pQtuke205L1TFDsxCMxBs4tbtm8Gfunqg6gFPfzBDc6koc8cLxHdWH9epJx6xzgCCdWiN3b0BvBe25gMapU06eNoP56zLD/Ge8QS9oaXril9GFUhJ44kk4BL7F3sG/syFnKJ5GxBaYLsSoQrKHfvStNd8fbnTvTE6HVsJFDNm04ElqZlynD9WADmbuMrRlzySRHftnbyzpaR6MFKV0mteSS1pG3Rd/e2rL/Zo8dHMppzxETiMshQe/H4Kntzl07uzxTPGhfHZc0hLAHAutPDdek88ViodO0zVYJ8rd/xAqOiodcXjUxuIwhj6qN0s2gpZcgaabcRF0MK88MSpBp5ST2ojFVEM++v57BxYxCygnrf/pCBYLluyvYwDndEVNVr/fWVnTpg8xsy/LN3i0ezIWONPSdXaui5HMxs18e7Xzs6Ax+FP7/uyktMxlUqhGMLdhuwX6H6E/lI/oqZlxnDqJ4VH//yRATOjh3Hx88KV0oIV78SRPde6dO/vS5rwBOOOABxmFiek4CK19AfHNw7ifIEbxGCTN4qJbuUt8E6it3yj9HTBacBD8uWcbuyEkhfs9lPzlb01dXMfwmgzZgVwLq8HuKcA59aJwgL7rsUVpayX7afA7jrCBbjGgRzvrwwkaWfWy+3wOlXpAtvsxl14MFo= 21 | - secure: HyNJhLJsyUG4t66t/3lJVoLNG2DyuTPvGPAxmS6VTiAq7dcoVRQ54EQ3+yGrNiV1ZpEVWDbV1QVzhpGIQMahahdknMyuhMDnqyQLurf+wqupN/DsSsf8wub+GJQMg38VcotqEzY0QITmVd4MFh6Y1/VzuPnjVfwerJIxHcNkg93w5D9m+R87j5Q2GCKOGbxhal5kYzm9PxU+if9xFjS6h/deJFlheP8MKJpLOsa+xXb/waFbFmJCO3Tx1S/7bm0OcRaqMIgMnrWMsMnEcIR/UDBt4HQLwGlCaG62YHLc/sDXI84Axf/we5K7nayQ9HvjvQL7JSQFWNbQUJgKk/TYPpvZULCaaKvla0o7gOb7PUgiaLPow3KT00vtfT35m8neFD+CeJdLyNhpPP2aJ+KL9z1foHm8Y1I/KjEC+1Rrg4Rpm6nMGznzcHmEkF9qY0LjhezVejMPtYkEKOjKGRIiv8YePwSWgYGnCJmHXeFugEiE+2axR8ytAvAO6kSpxlVuQzFlFgXgxnvWpw1TOUvRyctcZOBq5pa7Qckq2oyuXKw0WH0FY0L6TKPMsUeZhXjmy74YI51xb/HxZFbJVLoBjQ9NX0y1vu8ND5aFvHqylP51BQvtez2TrL4LSj5Y2JYFUkQjNDeB7f2vM8TZpHhGAeRXR25DDex/jYmMUVEquMA= 22 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # RSocket Releases # 2 | 3 | No releases yet. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to RSocket 2 | 3 | If you would like to contribute code you can do so through GitHub by forking the repository and sending a pull request (on a branch other than `master`, `0.x`, `1.x`, or `gh-pages`). 4 | 5 | When submitting code, please make every effort to follow existing conventions and style in order to keep the code as readable as possible. 6 | 7 | ## License 8 | 9 | By contributing your code, you agree to license your contribution under the terms of the APLv2: https://github.com/RSocket/reactivesocket-java/blob/master/LICENSE 10 | 11 | All files are released with the Apache 2.0 license. 12 | 13 | If you are adding a new file it should have a header like this: 14 | 15 | ``` 16 | /** 17 | * Copyright 2015 Netflix, Inc. 18 | * 19 | * Licensed under the Apache License, Version 2.0 (the "License"); 20 | * you may not use this file except in compliance with the License. 21 | * You may obtain a copy of the License at 22 | * 23 | * http://www.apache.org/licenses/LICENSE-2.0 24 | * 25 | * Unless required by applicable law or agreed to in writing, software 26 | * distributed under the License is distributed on an "AS IS" BASIS, 27 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 28 | * See the License for the specific language governing permissions and 29 | * limitations under the License. 30 | */ 31 | ``` 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2012 Netflix, Inc. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RSocket CLI 2 | 3 | ## Description 4 | 5 | Simple RSocket CLI focused on sending basic traffic to servers built using RSocket e.g. help debug a mobile <=> server integration issue. 6 | 7 | Supports ws and wss URIs 8 | 9 | ## Consider if you should use rsc instead 10 | 11 | For rsocket-java specfic testing, the command line you probably want is https://github.com/making/rsc . 12 | This library builds on https://github.com/rsocket/rsocket-kotlin . 13 | 14 | # Build Status 15 | 16 | 17 | 18 | 19 | ## Build and Run 20 | 21 | To build the RSocket CLI: 22 | ``` 23 | ./gradlew --console plain installDist 24 | ``` 25 | 26 | To run: 27 | ``` 28 | ./build/install/rsocket-cli/bin/rsocket-cli --help 29 | ``` 30 | 31 | The build and run: 32 | ``` 33 | $ ./rsocket-cli --help 34 | ``` 35 | 36 | 37 | ## Install via Homebrew 38 | 39 | Use tab completion for help with specifying the operation type. 40 | 41 | ``` 42 | $ brew install yschimke/tap/rsocket-cli 43 | ``` 44 | 45 | ## Examples 46 | 47 | 48 | A generic interaction: 49 | ``` 50 | $ rsocket-cli --request --debug wss://rsocket-demo.herokuapp.com/rsocket 51 | ``` 52 | 53 | A spring routed request to query tweets: 54 | 55 | ``` 56 | $ rsocket-cli --route=searchTweets -i Sunday wss://rsocket-demo.herokuapp.com/rsocket 57 | ``` 58 | 59 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 2 | import org.apache.tools.ant.taskdefs.condition.Os 3 | 4 | plugins { 5 | kotlin("jvm") version "1.6.21" 6 | kotlin("kapt") version "1.6.21" 7 | `maven-publish` 8 | application 9 | id("net.nemerosa.versioning") version "2.15.1" 10 | id("com.diffplug.spotless") version "6.7.2" 11 | id("com.palantir.graal") version "0.10.0" 12 | } 13 | 14 | repositories { 15 | mavenCentral() 16 | maven(url = "https://jitpack.io") 17 | } 18 | 19 | group = "com.github.yschimke" 20 | description = "RSocket CLI" 21 | version = versioning.info.display 22 | 23 | base { 24 | archivesBaseName = "rsocket-cli" 25 | } 26 | 27 | application { 28 | mainClassName = "io.rsocket.cli.Main" 29 | } 30 | 31 | java { 32 | sourceCompatibility = JavaVersion.VERSION_17 33 | targetCompatibility = JavaVersion.VERSION_17 34 | } 35 | 36 | tasks { 37 | withType(KotlinCompile::class) { 38 | kotlinOptions.jvmTarget = "17" 39 | kotlinOptions.allWarningsAsErrors = false 40 | kotlinOptions.freeCompilerArgs = listOf("-Xjsr305=strict", "-Xjvm-default=all", "-opt-in=kotlin.RequiresOptIn") 41 | } 42 | } 43 | 44 | tasks { 45 | withType(Tar::class) { 46 | compression = Compression.NONE 47 | } 48 | } 49 | 50 | val sourcesJar by tasks.creating(Jar::class) { 51 | classifier = "sources" 52 | from(kotlin.sourceSets["main"].kotlin) 53 | } 54 | 55 | val javadocJar by tasks.creating(Jar::class) { 56 | classifier = "javadoc" 57 | from("$buildDir/javadoc") 58 | } 59 | 60 | val jar = tasks["jar"] as org.gradle.jvm.tasks.Jar 61 | 62 | distributions { 63 | getByName("main") { 64 | contents { 65 | duplicatesStrategy = DuplicatesStrategy.WARN 66 | 67 | from("${rootProject.projectDir}") { 68 | include("README.md", "LICENSE") 69 | } 70 | from("${rootProject.projectDir}/zsh") { 71 | into("zsh") 72 | } 73 | into("lib") { 74 | from(jar) 75 | } 76 | } 77 | } 78 | } 79 | 80 | publishing { 81 | repositories { 82 | maven(url = "build/repository") 83 | } 84 | publications { 85 | register("mavenJava", MavenPublication::class) { 86 | from(components["java"]) 87 | artifact(sourcesJar) 88 | artifact(tasks.distTar.get()) 89 | } 90 | } 91 | } 92 | 93 | graal { 94 | mainClass("io.rsocket.cli.Main") 95 | outputName("rsocket-cli") 96 | graalVersion("22.1.0") 97 | javaVersion("17") 98 | 99 | option("--enable-https") 100 | option("--no-fallback") 101 | 102 | if (Os.isFamily(Os.FAMILY_WINDOWS)) { 103 | // May be possible without, but autodetection is problematic on Windows 10 104 | // see https://github.com/palantir/gradle-graal 105 | // see https://www.graalvm.org/docs/reference-manual/native-image/#prerequisites 106 | windowsVsVarsPath("C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools\\VC\\Auxiliary\\Build\\vcvars64.bat") 107 | } 108 | } 109 | 110 | dependencies { 111 | implementation("io.rsocket.kotlin:rsocket-core:0.15.4") 112 | implementation("io.rsocket.kotlin:rsocket-transport-ktor-websocket-client:0.15.4") 113 | implementation("io.rsocket.kotlin:rsocket-transport-ktor-tcp:0.15.4") 114 | implementation("io.ktor:ktor-network-tls:2.0.2") 115 | implementation("io.ktor:ktor-client-okhttp:2.0.2") 116 | 117 | // define a BOM and its version 118 | implementation(platform("com.squareup.okhttp3:okhttp-bom:5.0.0-alpha.8")) 119 | 120 | implementation("com.github.yschimke.schoutput:schoutput:0.9.2") 121 | implementation("com.squareup.okhttp3:okhttp") 122 | implementation("com.squareup.okio:okio:3.1.0") 123 | implementation("info.picocli:picocli:4.6.3") 124 | implementation("org.jetbrains.kotlin:kotlin-reflect:1.6.21") 125 | implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.21") 126 | implementation("com.squareup.moshi:moshi:1.13.0") 127 | implementation("com.squareup.moshi:moshi-adapters:1.13.0") 128 | implementation("com.squareup.moshi:moshi-kotlin:1.13.0") 129 | implementation("org.slf4j:slf4j-jdk14:2.0.0-alpha7") 130 | 131 | kapt("info.picocli:picocli-codegen:4.6.3") 132 | compileOnly("org.graalvm.nativeimage:svm:21.1.0") 133 | 134 | testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.2") 135 | testImplementation("org.jetbrains.kotlin:kotlin-test:1.6.21") 136 | testImplementation("org.jetbrains.kotlin:kotlin-test-junit:1.6.21") 137 | 138 | testImplementation("org.junit.jupiter:junit-jupiter-engine:5.8.2") 139 | } 140 | 141 | if (properties.containsKey("graalbuild")) { 142 | val nativeImage = tasks["nativeImage"] 143 | 144 | distributions { 145 | val graal = create("graal") { 146 | contents { 147 | from("${rootProject.projectDir}") { 148 | include("README.md", "LICENSE") 149 | } 150 | from("${rootProject.projectDir}/zsh") { 151 | into("zsh") 152 | } 153 | into("bin") { 154 | from(nativeImage) 155 | } 156 | } 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | kotlinDslPluginOptions { 6 | experimentalWarning.set(false) 7 | } 8 | 9 | // Required since Gradle 4.10+. 10 | repositories { 11 | jcenter() 12 | maven(url = "https://dl.bintray.com/kotlin/kotlin-eap/") 13 | } 14 | -------------------------------------------------------------------------------- /buildSrc/settings.gradle.kts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsocket/rsocket-cli/9cd4656a8c4204b0ea871ac967353c06df53e1fc/buildSrc/settings.gradle.kts -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/BuildHelpers.kt: -------------------------------------------------------------------------------- 1 | import org.gradle.api.publish.maven.MavenPom 2 | 3 | 4 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | release.scope=patch 2 | org.gradle.parallel=true 3 | -------------------------------------------------------------------------------- /gradle/buildViaTravis.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This script will build the project. 3 | 4 | jdk_switcher use openjdk10 5 | # do stuff with OpenJDK 10 6 | wget https://github.com/sormuras/bach/raw/master/install-jdk.sh 7 | chmod +x $TRAVIS_BUILD_DIR/install-jdk.sh 8 | export JAVA_HOME=$HOME/openjdk17 9 | $TRAVIS_BUILD_DIR/install-jdk.sh -F 17 --target $JAVA_HOME 10 | 11 | if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then 12 | echo -e "Build Pull Request #$TRAVIS_PULL_REQUEST => Branch [$TRAVIS_BRANCH]" 13 | ./gradlew -Prelease.useLastTag=true build 14 | elif [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_TAG" == "" ]; then 15 | echo -e 'Build Branch with Snapshot => Branch ['${TRAVIS_BRANCH}']' 16 | ./gradlew -Prelease.travisci=true -PbintrayUser="${bintrayUser}" -PbintrayKey="${bintrayKey}" -PsonatypeUsername="${sonatypeUsername}" -PsonatypePassword="${sonatypePassword}" test --stacktrace 17 | elif [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_TAG" != "" ]; then 18 | echo -e 'Build Branch for Release => Branch ['${TRAVIS_BRANCH}'] Tag ['${TRAVIS_TAG}']' 19 | ./gradlew -Prelease.travisci=true -Prelease.useLastTag=true -PbintrayUser="${bintrayUser}" -PbintrayKey="${bintrayKey}" -PsonatypeUsername="${sonatypeUsername}" -PsonatypePassword="${sonatypePassword}" final --stacktrace 20 | else 21 | echo -e 'WARN: Should not be here => Branch ['${TRAVIS_BRANCH}'] Tag ['${TRAVIS_TAG}'] Pull Request ['${TRAVIS_PULL_REQUEST}']' 22 | ./gradlew -Prelease.useLastTag=true build 23 | fi 24 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsocket/rsocket-cli/9cd4656a8c4204b0ea871ac967353c06df53e1fc/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /rsocket-cli: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | ./gradlew -q --console plain installDist 4 | 5 | ./build/install/rsocket-cli/bin/rsocket-cli "$@" 6 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name='rsocket-cli' 2 | -------------------------------------------------------------------------------- /src/main/java/io/rsocket/cli/graal/TargetUtilKt.java: -------------------------------------------------------------------------------- 1 | package io.rsocket.cli.graal; 2 | 3 | import com.oracle.svm.core.annotate.Substitute; 4 | import com.oracle.svm.core.annotate.TargetClass; 5 | import io.rsocket.cli.UtilKt; 6 | 7 | @TargetClass(UtilKt.class) 8 | public final class TargetUtilKt { 9 | @Substitute 10 | public static void configureLogging(boolean debug) { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/kotlin/io/rsocket/cli/Main.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Netflix, Inc. 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 io.rsocket.cli 15 | 16 | import com.baulsupp.schoutput.UsageException 17 | import com.baulsupp.schoutput.outputHandlerInstance 18 | import com.baulsupp.schoutput.responses.SimpleResponse 19 | import com.baulsupp.schoutput.responses.SimpleResponseExtractor 20 | import io.ktor.utils.io.core.ByteReadPacket 21 | import io.ktor.utils.io.core.readByteBuffer 22 | import io.ktor.utils.io.core.readBytes 23 | import io.rsocket.kotlin.ExperimentalMetadataApi 24 | import io.rsocket.kotlin.RSocket 25 | import io.rsocket.kotlin.RSocketError 26 | import io.rsocket.kotlin.keepalive.KeepAlive 27 | import io.rsocket.kotlin.logging.NoopLogger 28 | import io.rsocket.kotlin.logging.PrintLogger 29 | import io.rsocket.kotlin.metadata.CompositeMetadata 30 | import io.rsocket.kotlin.metadata.RoutingMetadata 31 | import io.rsocket.kotlin.metadata.toPacket 32 | import io.rsocket.kotlin.payload.Payload 33 | import io.rsocket.kotlin.payload.PayloadMimeType 34 | import io.rsocket.kotlin.payload.buildPayload 35 | import io.rsocket.kotlin.payload.data 36 | import kotlinx.coroutines.* 37 | import kotlinx.coroutines.flow.emptyFlow 38 | import kotlinx.coroutines.flow.take 39 | import okio.ByteString.Companion.toByteString 40 | import okio.FileSystem 41 | import okio.Path.Companion.toPath 42 | import picocli.CommandLine 43 | import picocli.CommandLine.* 44 | import java.net.URI 45 | import java.nio.charset.StandardCharsets 46 | import java.util.concurrent.TimeUnit 47 | import java.util.function.Supplier 48 | import kotlin.system.exitProcess 49 | import kotlin.text.toByteArray 50 | import kotlin.time.Duration 51 | import kotlin.time.ExperimentalTime 52 | 53 | /** 54 | * Simple command line tool to make a RSocket connection and send/receive elements. 55 | * 56 | * Currently limited in features, only supports a text/line based approach. 57 | */ 58 | @Command(description = ["RSocket CLI command"], 59 | name = "rsocket-cli", mixinStandardHelpOptions = true, version = ["dev"]) 60 | class Main : Runnable { 61 | @Option(names = ["-H", "--header"], description = ["Custom header to pass to server"]) 62 | var headers: List? = null 63 | 64 | @Option(names = ["--stream"], description = ["Request Stream"]) 65 | var stream: Boolean = false 66 | 67 | @Option(names = ["--request"], description = ["Request Response"]) 68 | var requestResponse: Boolean = false 69 | 70 | @Option(names = ["--fnf"], description = ["Fire and Forget"]) 71 | var fireAndForget: Boolean = false 72 | 73 | @Option(names = ["--channel"], description = ["Channel"]) 74 | var channel: Boolean = false 75 | 76 | @Option(names = ["--metadataPush"], description = ["Metadata Push"]) 77 | var metadataPush: Boolean = false 78 | 79 | @Option(names = ["-i", "--input"], description = ["String input or @path/to/file"]) 80 | var input: String? = null 81 | 82 | @Option(names = ["-m", "--metadata"], 83 | description = ["Metadata input string input or @path/to/file"]) 84 | var metadata: String? = null 85 | 86 | @Option(names = ["--metadataFormat"], description = ["Metadata Format"]) 87 | var metadataFormat: String? = null 88 | 89 | @Option(names = ["--dataFormat"], description = ["Data Format"]) 90 | var dataFormat: String? = null 91 | 92 | @Option(names = ["-s", "--setup"], description = ["String input or @path/to/file for setup metadata"]) 93 | var setup: String? = null 94 | 95 | @Option(names = ["--route"], description = ["RSocket Route"]) 96 | var route: String? = null 97 | 98 | @Option(names = ["--debug"], description = ["Debug Output"]) 99 | var debug: Boolean = false 100 | 101 | @Option(names = ["--timeout"], description = ["Timeout in seconds"]) 102 | var timeout: Long? = null 103 | 104 | @Option(names = ["--keepalive"], description = ["Keepalive period"]) 105 | var keepalive: Int? = null 106 | 107 | @Option(names = ["--requestn", "-r"], description = ["Request N credits"]) 108 | var requestN = Integer.MAX_VALUE 109 | 110 | @Option(names = ["--resume"], description = ["resume enabled"]) 111 | var resume: Boolean = false 112 | 113 | @Option(names = ["--complete"], description = ["Complete Argument"]) 114 | var complete: String? = null 115 | 116 | @Parameters(arity = "0..1", paramLabel = "target", description = ["Endpoint URL"], 117 | completionCandidates = UrlCandidates::class) 118 | var target: String? = null 119 | 120 | lateinit var client: RSocket 121 | 122 | val outputHandler by lazy { 123 | outputHandlerInstance(SimpleResponseExtractor) 124 | } 125 | 126 | override fun run() { 127 | runBlocking { 128 | exec() 129 | } 130 | } 131 | 132 | private suspend fun exec() { 133 | configureLogging(debug) 134 | 135 | if (listOf(metadataPush, stream, fireAndForget, channel, requestResponse).all { !it }) { 136 | stream = true 137 | } 138 | 139 | if (complete != null) { 140 | printCompletions() 141 | return 142 | } 143 | 144 | val uri = sanitizeUri(target ?: throw UsageException("no target specified")) 145 | 146 | if (metadataFormat == null) { 147 | if (route != null) { 148 | metadataFormat = "message/x.rsocket.composite-metadata.v0" 149 | } else { 150 | metadataFormat = "application/json" 151 | } 152 | } 153 | 154 | if (dataFormat == null) { 155 | dataFormat = "application/json" 156 | } 157 | 158 | if (!this::client.isInitialized) { 159 | val setupPayload = parseSetupPayload() 160 | client = buildRSocket(uri, setupPayload) 161 | UrlCandidates().recordUrl(uri) 162 | } 163 | 164 | runQuery() 165 | } 166 | 167 | @OptIn(ExperimentalTime::class) 168 | private suspend fun buildRSocket( 169 | uri: String, 170 | setupPayload: Payload 171 | ) = buildClient(uri) { 172 | loggerFactory = if (debug) PrintLogger else NoopLogger 173 | connectionConfig { 174 | setupPayload(setupPayload) 175 | keepAlive = KeepAlive(Duration.seconds(keepalive ?: 5)) 176 | payloadMimeType = PayloadMimeType(dataFormat!!, metadataFormat!!) 177 | } 178 | } 179 | 180 | private fun printCompletions() { 181 | val completions = when (complete) { 182 | "url" -> UrlCandidates().toList() 183 | else -> listOf() 184 | } 185 | 186 | println(completions.joinToString("\n")) 187 | } 188 | 189 | private suspend fun runQuery() { 190 | if (timeout != null) { 191 | withTimeout(TimeUnit.SECONDS.toMillis(timeout!!)) { 192 | run(client) 193 | } 194 | } else { 195 | run(client) 196 | } 197 | } 198 | 199 | private suspend fun parseSetupPayload(): Payload = withContext(Dispatchers.IO) { 200 | val setup = setup 201 | when { 202 | setup == null -> Payload.Empty 203 | setup.startsWith("@") -> buildPayload { 204 | data(FileSystem.SYSTEM.read(expectedFile(setup.substring(1))) { readByteArray() }) 205 | } 206 | else -> buildPayload { 207 | data(setup) 208 | } 209 | } 210 | } 211 | 212 | private fun sanitizeUri(uri: String): String { 213 | val validationUri = URI(uri) 214 | if (validationUri.scheme == "ws" || validationUri.scheme == "wss") { 215 | if (validationUri.path.isEmpty()) { 216 | return "$uri/" 217 | } 218 | } 219 | return uri 220 | } 221 | 222 | fun getInputFromSource(source: String?, nullHandler: Supplier): String = 223 | when (source) { 224 | null -> nullHandler.get() 225 | else -> stringValue(source) 226 | } 227 | 228 | @OptIn(ExperimentalMetadataApi::class) 229 | suspend fun buildMetadata(): ByteArray? = when { 230 | this.route != null -> { 231 | CompositeMetadata(RoutingMetadata(route!!)).toPacket().readBytes() 232 | } 233 | this.metadata != null -> { 234 | if (this.headers != null) { 235 | throw UsageException("Can't specify headers and metadata") 236 | } 237 | 238 | getInputFromSource(this.metadata, Supplier { ""; }).toByteArray(StandardCharsets.UTF_8) 239 | } 240 | this.headers != null -> jsonEncodeStringMap(headerMap(headers)) 241 | else -> null 242 | } 243 | 244 | private suspend fun singleInputPayload(): Payload { 245 | val inputBytes = input?.toByteArray() ?: byteArrayOf() 246 | val metadata = buildMetadata() 247 | return Payload(ByteReadPacket(inputBytes), metadata?.let { ByteReadPacket(it) }) 248 | } 249 | 250 | suspend fun run(client: RSocket) { 251 | val inputPayload = singleInputPayload() 252 | 253 | when { 254 | fireAndForget -> client.fireAndForget(inputPayload) 255 | metadataPush -> client.metadataPush(inputPayload.data) 256 | requestResponse -> client.requestResponse(inputPayload) 257 | .also { showResponse(it) } 258 | stream -> client.requestStream(inputPayload).take(requestN) 259 | .collect { showResponse(it) } 260 | channel -> client.requestChannel(inputPayload, emptyFlow()).take(requestN) 261 | .collect { showResponse(it) } 262 | else -> error("No operation to run") 263 | } 264 | } 265 | 266 | private suspend fun showResponse(it: Payload) { 267 | outputHandler.showOutput(SimpleResponse(dataFormat, it.data.readByteBuffer().toByteString())) 268 | } 269 | 270 | companion object { 271 | const val NAME = "rsocket-cli" 272 | 273 | var homeDir = System.getProperty("user.home").toPath() 274 | var settingsDir = homeDir / ".rsocket-cli" 275 | 276 | @JvmStatic 277 | fun main(vararg args: String) { 278 | val main = Main() 279 | exec(main, args.toList()) 280 | } 281 | 282 | private fun exec(runnable: Main, args: List) { 283 | val cmd = CommandLine(runnable) 284 | try { 285 | val parseResult = cmd.parseArgs(*args.toTypedArray()) 286 | 287 | if (cmd.isUsageHelpRequested) { 288 | cmd.usage(cmd.out) 289 | exitProcess(parseResult.commandSpec().exitCodeOnUsageHelp()) 290 | } else if (cmd.isVersionHelpRequested) { 291 | cmd.printVersionHelp(cmd.out) 292 | exitProcess(parseResult.commandSpec().exitCodeOnVersionHelp()) 293 | } 294 | 295 | runnable.run() 296 | 297 | exitProcess(0) 298 | } catch (pe: CommandLine.ParameterException) { 299 | cmd.err.println(pe.message) 300 | if (!CommandLine.UnmatchedArgumentException.printSuggestions(pe, cmd.err)) { 301 | cmd.usage(cmd.err) 302 | } 303 | exitProcess(cmd.commandSpec.exitCodeOnInvalidInput()) 304 | } catch (ue: UsageException) { 305 | cmd.err.println(ue.message) 306 | exitProcess(cmd.commandSpec.exitCodeOnInvalidInput()) 307 | } catch (ae: RSocketError) { 308 | cmd.err.println(ae.javaClass.simpleName + ": " + ae.message) 309 | exitProcess(cmd.commandSpec.exitCodeOnExecutionException()) 310 | } catch (ex: Exception) { 311 | ex.printStackTrace(cmd.err) 312 | exitProcess(cmd.commandSpec.exitCodeOnExecutionException()) 313 | } 314 | } 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /src/main/kotlin/io/rsocket/cli/UrlCandidates.kt: -------------------------------------------------------------------------------- 1 | package io.rsocket.cli 2 | 3 | import io.rsocket.cli.Main.Companion.settingsDir 4 | import kotlinx.coroutines.Dispatchers 5 | import kotlinx.coroutines.runBlocking 6 | import kotlinx.coroutines.withContext 7 | import okio.FileSystem 8 | import okio.buffer 9 | 10 | internal class UrlCandidates constructor(val fileSystem: FileSystem = FileSystem.SYSTEM) : Iterable { 11 | override fun iterator(): Iterator { 12 | return runBlocking { knownUrls() }.iterator() 13 | } 14 | 15 | suspend fun knownUrls(): List { 16 | return withContext(Dispatchers.IO) { 17 | if (fileSystem.exists(completionFile)) { 18 | readCompletions() 19 | } else { 20 | listOf("wss://rsocket-demo.herokuapp.com/rsocket") 21 | } 22 | } 23 | } 24 | 25 | suspend fun readCompletions() = withContext(Dispatchers.IO) { 26 | fileSystem.read(completionFile) { readUtf8() } 27 | }.lines().filter { it.isNotBlank() } 28 | 29 | suspend fun recordUrl(url: String) { 30 | withContext(Dispatchers.IO) { 31 | if (!fileSystem.exists(completionFile)) { 32 | fileSystem.createDirectories(completionFile.parent!!) 33 | fileSystem.write(completionFile) { writeUtf8("$url\n") } 34 | } else { 35 | val known = fileSystem.read(completionFile) { readUtf8() }.lines() 36 | 37 | if (!known.contains(url)) { 38 | fileSystem.appendingSink(completionFile).use { 39 | it.buffer().writeUtf8("$url\n") 40 | } 41 | } 42 | "" 43 | } 44 | } 45 | } 46 | 47 | companion object { 48 | val completionFile = settingsDir / "completions.txt" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/kotlin/io/rsocket/cli/client.kt: -------------------------------------------------------------------------------- 1 | package io.rsocket.cli 2 | 3 | import com.baulsupp.schoutput.UsageException 4 | import io.ktor.client.engine.* 5 | import io.ktor.client.engine.okhttp.* 6 | import io.rsocket.kotlin.RSocket 7 | import io.rsocket.kotlin.core.RSocketConnector 8 | import io.rsocket.kotlin.core.RSocketConnectorBuilder 9 | import io.rsocket.kotlin.transport.ktor.tcp.TcpClientTransport 10 | import io.rsocket.kotlin.transport.ktor.websocket.client.WebSocketClientTransport 11 | 12 | suspend fun buildClient( 13 | uri: String, 14 | builder: RSocketConnectorBuilder.() -> Unit 15 | ): RSocket { 16 | return if (uri.startsWith("tcp:")) { 17 | buildTcpClient(uri, builder) 18 | } else { 19 | buildWsClient(uri, builder) 20 | } 21 | } 22 | 23 | suspend fun buildTcpClient( 24 | uri: String, 25 | builder: RSocketConnectorBuilder.() -> Unit 26 | ): RSocket { 27 | val (hostname, port) = "tcp://([^:]+):(\\d+)".toRegex().matchEntire(uri)?.destructured 28 | ?: throw UsageException("bad uri format: '$uri'") 29 | 30 | val transport = TcpClientTransport(hostname, port.toInt()) 31 | return RSocketConnector(builder).connect(transport) 32 | } 33 | 34 | suspend fun buildWsClient( 35 | uri: String, 36 | builder: RSocketConnectorBuilder.() -> Unit 37 | ): RSocket { 38 | val engine: HttpClientEngineFactory<*> = OkHttp 39 | 40 | val transport = WebSocketClientTransport(engine, urlString = uri, secure = uri.startsWith("wss")) 41 | return RSocketConnector(builder).connect(transport) 42 | } 43 | -------------------------------------------------------------------------------- /src/main/kotlin/io/rsocket/cli/util.kt: -------------------------------------------------------------------------------- 1 | package io.rsocket.cli 2 | 3 | import com.baulsupp.schoutput.UsageException 4 | import com.squareup.moshi.Moshi 5 | import com.squareup.moshi.Types 6 | import kotlinx.coroutines.Dispatchers 7 | import kotlinx.coroutines.withContext 8 | import okio.FileSystem 9 | import okio.Path 10 | import okio.Path.Companion.toPath 11 | import java.io.IOException 12 | import java.io.PrintWriter 13 | import java.io.StringWriter 14 | import java.time.Duration 15 | import java.time.Instant 16 | import java.time.ZoneOffset 17 | import java.time.format.DateTimeFormatterBuilder 18 | import java.time.temporal.ChronoField 19 | import java.util.logging.ConsoleHandler 20 | import java.util.logging.Formatter 21 | import java.util.logging.Level 22 | import java.util.logging.LogManager 23 | import java.util.logging.LogRecord 24 | import java.util.regex.Pattern 25 | 26 | fun expectedFile(name: String): Path { 27 | val file = normalize(name).toPath() 28 | 29 | if (!FileSystem.SYSTEM.exists(file)) { 30 | throw UsageException("file not found: $file") 31 | } 32 | 33 | return file 34 | } 35 | 36 | private fun normalize(path: String): String = when { 37 | path.startsWith("~/") -> System.getenv("HOME") + "/" + path.substring(2) 38 | else -> path 39 | } 40 | 41 | suspend fun headerMap(headers: List?): Map { 42 | val headerMap = mutableMapOf() 43 | 44 | if (headers != null) { 45 | for (header in headers) { 46 | if (header.startsWith("@")) { 47 | headerMap.putAll(headerFileMap(header)) 48 | } else { 49 | val parts = header.split(":".toRegex(), 2).toTypedArray() 50 | val name = parts[0].trim { it <= ' ' } 51 | val value = stringValue(parts[1].trim { it <= ' ' }) 52 | headerMap[name] = value 53 | } 54 | } 55 | } 56 | return headerMap 57 | } 58 | 59 | private suspend fun headerFileMap(input: String): Map { 60 | return withContext(Dispatchers.IO) { 61 | headerMap(FileSystem.SYSTEM.read(inputFile(input)) { readUtf8() }.lines()) 62 | } 63 | } 64 | 65 | fun stringValue(source: String): String = when { 66 | source.startsWith("@") -> try { 67 | FileSystem.SYSTEM.read(inputFile(source)) { readUtf8() } 68 | } catch (e: IOException) { 69 | throw UsageException(e.toString()) 70 | } 71 | else -> source 72 | } 73 | 74 | fun inputFile(path: String): Path = expectedFile(path.substring(1)) 75 | 76 | private val activeLoggers = mutableListOf() 77 | 78 | fun configureLogging(debug: Boolean) { 79 | LogManager.getLogManager().reset() 80 | 81 | val activeLogger = getLogger("") 82 | val handler = ConsoleHandler() 83 | handler.level = Level.ALL 84 | handler.formatter = OneLineLogFormat 85 | activeLogger.addHandler(handler) 86 | 87 | if (debug) { 88 | getLogger("").level = Level.INFO 89 | getLogger("io.netty").level = Level.INFO 90 | getLogger("io.reactivex").level = Level.FINE 91 | getLogger("io.rsocket").level = Level.FINEST 92 | getLogger("reactor.ipc.netty").level = Level.FINEST 93 | } else { 94 | getLogger("").level = Level.SEVERE 95 | getLogger("io.netty").level = Level.SEVERE 96 | getLogger("io.reactivex").level = Level.SEVERE 97 | getLogger("io.rsocket").level = Level.SEVERE 98 | } 99 | } 100 | 101 | private fun getLogger(name: String): java.util.logging.Logger { 102 | val logger = java.util.logging.Logger.getLogger(name) 103 | activeLoggers.add(logger) 104 | return logger 105 | } 106 | 107 | val moshi by lazy { 108 | Moshi.Builder().build()!! 109 | } 110 | 111 | fun jsonEncodeStringMap(headerMap: Map): ByteArray { 112 | val type = Types.newParameterizedType(Map::class.java, String::class.java, String::class.java) 113 | return moshi.adapter>(type).toJson(headerMap).toByteArray() 114 | } 115 | 116 | object OneLineLogFormat : Formatter() { 117 | private val d = DateTimeFormatterBuilder() 118 | .appendValue(ChronoField.HOUR_OF_DAY, 2) 119 | .appendLiteral(':') 120 | .appendValue(ChronoField.MINUTE_OF_HOUR, 2) 121 | .optionalStart() 122 | .appendLiteral(':') 123 | .appendValue(ChronoField.SECOND_OF_MINUTE, 2) 124 | .optionalStart() 125 | .appendFraction(ChronoField.NANO_OF_SECOND, 3, 3, true) 126 | .toFormatter() 127 | 128 | private val offset = ZoneOffset.systemDefault() 129 | 130 | override fun format(record: LogRecord): String { 131 | val message = OneLineLogFormat.formatMessage(record) 132 | 133 | val time = Instant.ofEpochMilli(record.millis).atZone(offset) 134 | 135 | return if (record.thrown != null) { 136 | val sw = StringWriter(4096) 137 | val pw = PrintWriter(sw) 138 | record.thrown.printStackTrace(pw) 139 | String.format("%s\t%s%n%s%n", time.format(d), message, sw.toString()) 140 | } else { 141 | String.format("%s\t%s%n", time.format(d), message) 142 | } 143 | } 144 | } 145 | 146 | private val DURATION_FORMAT = Pattern.compile("(\\d+)(ms|s|m)") 147 | 148 | fun parseShortDuration(keepalive: String): Duration { 149 | val match = DURATION_FORMAT.matcher(keepalive) 150 | 151 | if (!match.matches()) { 152 | throw UsageException("Unknown duration format '$keepalive'") 153 | } 154 | 155 | val amount = java.lang.Long.valueOf(match.group(1)) 156 | val unit = match.group(2) 157 | 158 | return when (unit) { 159 | "ms" -> Duration.ofMillis(amount) 160 | "s" -> Duration.ofSeconds(amount) 161 | else -> Duration.ofMinutes(amount) 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/native-image/io.rsocket.cli/rsocketcli/reflect-config.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "kotlin.reflect.jvm.internal.ReflectionFactoryImpl", 4 | "allDeclaredConstructors":true 5 | }, 6 | { 7 | "name": "kotlin.internal.jdk8.JDK8PlatformImplementations", 8 | "allDeclaredConstructors":true 9 | }, 10 | { 11 | "name": "kotlin.KotlinVersion", 12 | "allPublicMethods": true, 13 | "allDeclaredFields":true, 14 | "allDeclaredMethods":true, 15 | "allDeclaredConstructors":true 16 | }, 17 | { 18 | "name": "kotlin.KotlinVersion[]" 19 | }, 20 | { 21 | "name": "kotlin.KotlinVersion$Companion" 22 | }, 23 | { 24 | "name": "kotlin.KotlinVersion$Companion[]" 25 | }, 26 | { 27 | "name": "io.ktor.utils.io.pool.DefaultPool", 28 | "fields" : [ { "name" : "top", "allowUnsafeAccess" : true } ] 29 | }, 30 | { 31 | "name": "io.ktor.client.features.websocket.DefaultClientWebSocketSession", 32 | "allPublicMethods": true, 33 | "allDeclaredFields":true, 34 | "allDeclaredMethods":true, 35 | "allDeclaredConstructors":true 36 | }, 37 | { 38 | "name": "io.rsocket.kotlin.RSocket", 39 | "allPublicMethods": true 40 | }, 41 | { 42 | "name": "io.ktor.network.selector.InterestSuspensionsMap", 43 | "allDeclaredConstructors": true, 44 | "allPublicConstructors": true, 45 | "allDeclaredMethods": true, 46 | "allPublicMethods": true, 47 | "allDeclaredFields": true, 48 | "fields": [ 49 | { 50 | "name": "readHandlerReference", 51 | "allowUnsafeAccess": true 52 | }, 53 | { 54 | "name": "writeHandlerReference", 55 | "allowUnsafeAccess": true 56 | }, 57 | { 58 | "name": "connectHandlerReference", 59 | "allowUnsafeAccess": true 60 | }, 61 | { 62 | "name": "acceptHandlerReference", 63 | "allowUnsafeAccess": true 64 | } 65 | ] 66 | } 67 | ] 68 | -------------------------------------------------------------------------------- /src/test/kotlin/io/rsocket/RunMain.kt: -------------------------------------------------------------------------------- 1 | package io.rsocket 2 | 3 | import io.rsocket.cli.Main 4 | 5 | fun main() { 6 | Main.main("--route", "searchTweets", "-i", "london", "wss://demo.rsocket.io/rsocket") 7 | } 8 | -------------------------------------------------------------------------------- /src/test/kotlin/io/rsocket/util/HeaderUtilTest.kt: -------------------------------------------------------------------------------- 1 | package io.rsocket.util 2 | 3 | import io.rsocket.cli.headerMap 4 | import io.rsocket.cli.stringValue 5 | import kotlinx.coroutines.runBlocking 6 | import org.junit.Assert.assertEquals 7 | import org.junit.Test 8 | import java.net.URISyntaxException 9 | import java.nio.file.Paths 10 | 11 | class HeaderUtilTest { 12 | @Test 13 | fun testSimpleValue() { 14 | assertEquals("hello", stringValue("hello")) 15 | } 16 | 17 | @Test 18 | @Throws(URISyntaxException::class) 19 | fun testFileValue() { 20 | assertEquals("value", stringValue("@" + path("value.txt"))) 21 | } 22 | 23 | @Test 24 | @Throws(URISyntaxException::class) 25 | fun headerMap() = runBlocking { 26 | val map = headerMap(listOf("A: a", "B:b", "C: @" + path("value.txt"))) 27 | 28 | assertEquals(3, map.size.toLong()) 29 | assertEquals("a", map["A"]) 30 | assertEquals("b", map["B"]) 31 | assertEquals("value", map["C"]) 32 | } 33 | 34 | @Test 35 | @Throws(URISyntaxException::class) 36 | fun headerFileMap() = runBlocking { 37 | val map = headerMap(listOf("@" + path("headers.txt"))) 38 | 39 | assertEquals(2, map.size.toLong()) 40 | assertEquals("a", map["A"]) 41 | assertEquals("b", map["B"]) 42 | } 43 | 44 | @Throws(URISyntaxException::class) 45 | private fun path(resourceName: String): String { 46 | val resource = javaClass.classLoader.getResource(resourceName) 47 | return Paths.get(resource!!.toURI()).toFile().path 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/test/resources/headers.txt: -------------------------------------------------------------------------------- 1 | A: a 2 | B: b 3 | -------------------------------------------------------------------------------- /src/test/resources/hello.text: -------------------------------------------------------------------------------- 1 | Hello from a file! -------------------------------------------------------------------------------- /src/test/resources/value.txt: -------------------------------------------------------------------------------- 1 | value -------------------------------------------------------------------------------- /zsh/_rsocket-cli: -------------------------------------------------------------------------------- 1 | #compdef rsocket-cli 2 | #autoload 3 | 4 | _rsocket_cli_urls() { 5 | local host urlpath 6 | 7 | if ! [[ "$words[CURRENT]" =~ '^-.*$' ]] 8 | then 9 | urls=($(rsocket-cli --complete url )) 10 | compadd $urls 11 | fi 12 | } 13 | 14 | _rsocket_cli() { 15 | local curcontext="$curcontext" state state_descr line expl 16 | typeset -A opt_args 17 | 18 | _arguments -C -S \ 19 | {-T,--transport-header}='[Custom header to pass to the transport]' \ 20 | {-H,--header}='[Custom header to pass to server]' \ 21 | --request'[Request]' \ 22 | --stream'[Stream]' \ 23 | --channel'[Channel]' \ 24 | --fnf'[Fire And Forget]' \ 25 | --metadataPush'[Metadata Push]' \ 26 | --route='[Spring Route]' \ 27 | {-i,--input}='[Input string input or @path/to/file]' \ 28 | {-m,--metadata}='[Metadata input string input or @path/to/file]' \ 29 | --metadataFormat='[MetaData Format]' \ 30 | --dataFormat='[Data Format]' \ 31 | --setup='[String input or @path/to/file for setup metadata]' \ 32 | --debug'[Debug]' \ 33 | --ops='[Operation Count]' \ 34 | --timeout='[Timeout seconds]' \ 35 | --keepalive='[Keepalive seconds]' \ 36 | '--complete=[Complete options (url)]: :(url)' \ 37 | {-r,--requestn}='[Request N]' \ 38 | --resume'[Resume]' \ 39 | {-h,--help}'[Show this help message and exit.]' \ 40 | {-V,--version}'[Print version information and exit.]' \ 41 | '*:URLs:_rsocket_cli_urls' \ 42 | && ret=0 43 | } 44 | 45 | _rsocket_cli "$@" 46 | --------------------------------------------------------------------------------