├── .editorconfig ├── .github ├── ISSUE_TEMPLATE.md ├── maven-cd-settings.xml ├── maven-ci-settings.xml └── workflows │ ├── ci-4.x.yml │ ├── ci-5.x-stable.yml │ ├── ci-5.x.yml │ ├── ci-matrix-5.x.yml │ ├── ci.yml │ └── deploy.yml ├── .gitignore ├── LICENSE.txt ├── README.adoc ├── pom.xml └── src ├── client └── nodejs │ ├── lib │ └── tcp-vertx-eventbus.js │ ├── package.json │ └── test │ ├── TcpEventBusBridgeEchoServer.java │ └── index.js ├── main ├── asciidoc │ └── index.adoc └── java │ ├── examples │ └── TCPBridgeExamples.java │ └── io │ └── vertx │ └── ext │ └── eventbus │ └── bridge │ └── tcp │ ├── BridgeEvent.java │ ├── TcpEventBusBridge.java │ ├── impl │ ├── BridgeEventImpl.java │ ├── TcpEventBusBridgeImpl.java │ └── protocol │ │ ├── FrameHelper.java │ │ └── FrameParser.java │ └── package-info.java └── test ├── java └── io │ └── vertx │ └── ext │ └── eventbus │ └── bridge │ └── tcp │ ├── SSLKeyPairCerts.java │ ├── TcpEventBusBridgeEventTest.java │ ├── TcpEventBusBridgeHookTest.java │ ├── TcpEventBusBridgeInteropTest.java │ ├── TcpEventBusBridgeTest.java │ └── UnixDomainSocketTest.java └── resources └── .gitkeep /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | trim_trailing_whitespace = true 8 | end_of_line = lf 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Version 2 | 3 | * vert.x core: 4 | * vert.x tcp eventbus bridge: 5 | 6 | ### Context 7 | 8 | I encountered an exception which looks suspicious while ... 9 | 10 | ### Do you have a reproducer? 11 | 12 | * Link to github project/gist 13 | 14 | ### Steps to reproduce 15 | 16 | 1. ... 17 | 2. ... 18 | 3. ... 19 | 4. ... 20 | 21 | ### Extra 22 | 23 | * Anything that can be relevant 24 | -------------------------------------------------------------------------------- /.github/maven-cd-settings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | false 20 | 21 | 22 | 23 | vertx-snapshots-repository 24 | ${env.VERTX_NEXUS_USERNAME} 25 | ${env.VERTX_NEXUS_PASSWORD} 26 | 27 | 28 | 29 | 30 | 31 | google-mirror 32 | 33 | true 34 | 35 | 36 | 37 | google-maven-central 38 | GCS Maven Central mirror EU 39 | https://maven-central.storage-download.googleapis.com/maven2/ 40 | 41 | true 42 | 43 | 44 | false 45 | 46 | 47 | 48 | 49 | 50 | google-maven-central 51 | GCS Maven Central mirror 52 | https://maven-central.storage-download.googleapis.com/maven2/ 53 | 54 | true 55 | 56 | 57 | false 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /.github/maven-ci-settings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | false 20 | 21 | 22 | 23 | google-mirror 24 | 25 | true 26 | 27 | 28 | 29 | google-maven-central 30 | GCS Maven Central mirror EU 31 | https://maven-central.storage-download.googleapis.com/maven2/ 32 | 33 | true 34 | 35 | 36 | false 37 | 38 | 39 | 40 | 41 | 42 | google-maven-central 43 | GCS Maven Central mirror 44 | https://maven-central.storage-download.googleapis.com/maven2/ 45 | 46 | true 47 | 48 | 49 | false 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /.github/workflows/ci-4.x.yml: -------------------------------------------------------------------------------- 1 | name: vertx-tcp-eventbus-bridge (4.x) 2 | on: 3 | schedule: 4 | - cron: '0 4 * * *' 5 | jobs: 6 | CI: 7 | strategy: 8 | matrix: 9 | include: 10 | - os: ubuntu-latest 11 | jdk: 8 12 | - os: ubuntu-latest 13 | jdk: 17 14 | uses: ./.github/workflows/ci.yml 15 | with: 16 | branch: 4.x 17 | jdk: ${{ matrix.jdk }} 18 | os: ${{ matrix.os }} 19 | secrets: inherit 20 | Deploy: 21 | if: ${{ github.repository_owner == 'vert-x3' && (github.event_name == 'push' || github.event_name == 'schedule') }} 22 | needs: CI 23 | uses: ./.github/workflows/deploy.yml 24 | with: 25 | branch: 4.x 26 | jdk: 8 27 | secrets: inherit 28 | -------------------------------------------------------------------------------- /.github/workflows/ci-5.x-stable.yml: -------------------------------------------------------------------------------- 1 | name: vertx-tcp-eventbus-bridge (5.x-stable) 2 | on: 3 | push: 4 | branches: 5 | - '5.[0-9]+' 6 | pull_request: 7 | branches: 8 | - '5.[0-9]+' 9 | schedule: 10 | - cron: '0 6 * * *' 11 | jobs: 12 | CI-CD: 13 | uses: ./.github/workflows/ci-matrix-5.x.yml 14 | secrets: inherit 15 | with: 16 | branch: ${{ github.event_name == 'schedule' && vars.VERTX_5_STABLE_BRANCH || github.event.pull_request.head.sha || github.ref_name }} 17 | -------------------------------------------------------------------------------- /.github/workflows/ci-5.x.yml: -------------------------------------------------------------------------------- 1 | name: vertx-tcp-eventbus-bridge (5.x) 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | branches: 8 | - master 9 | schedule: 10 | - cron: '0 5 * * *' 11 | jobs: 12 | CI-CD: 13 | uses: ./.github/workflows/ci-matrix-5.x.yml 14 | secrets: inherit 15 | with: 16 | branch: ${{ github.event.pull_request.head.sha || github.ref_name }} 17 | -------------------------------------------------------------------------------- /.github/workflows/ci-matrix-5.x.yml: -------------------------------------------------------------------------------- 1 | name: CI matrix (5.x) 2 | on: 3 | workflow_call: 4 | inputs: 5 | branch: 6 | required: true 7 | type: string 8 | jobs: 9 | CI: 10 | strategy: 11 | matrix: 12 | include: 13 | - os: ubuntu-latest 14 | jdk: 11 15 | - os: ubuntu-latest 16 | jdk: 21 17 | uses: ./.github/workflows/ci.yml 18 | with: 19 | branch: ${{ inputs.branch }} 20 | jdk: ${{ matrix.jdk }} 21 | os: ${{ matrix.os }} 22 | secrets: inherit 23 | Deploy: 24 | if: ${{ github.repository_owner == 'vert-x3' && (github.event_name == 'push' || github.event_name == 'schedule') }} 25 | needs: CI 26 | uses: ./.github/workflows/deploy.yml 27 | with: 28 | branch: ${{ inputs.branch }} 29 | jdk: 11 30 | secrets: inherit 31 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | workflow_call: 4 | inputs: 5 | branch: 6 | required: true 7 | type: string 8 | jdk: 9 | default: 8 10 | type: string 11 | os: 12 | default: ubuntu-latest 13 | type: string 14 | jobs: 15 | Test: 16 | name: Run tests 17 | runs-on: ${{ inputs.os }} 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v2 21 | with: 22 | ref: ${{ inputs.branch }} 23 | - name: Install JDK 24 | uses: actions/setup-java@v2 25 | with: 26 | java-version: ${{ inputs.jdk }} 27 | distribution: temurin 28 | - name: Run tests 29 | run: mvn -s .github/maven-ci-settings.xml -q clean verify -B 30 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | on: 3 | workflow_call: 4 | inputs: 5 | branch: 6 | required: true 7 | type: string 8 | jdk: 9 | default: 8 10 | type: string 11 | jobs: 12 | Deploy: 13 | name: Deploy to OSSRH 14 | runs-on: ubuntu-latest 15 | env: 16 | VERTX_NEXUS_USERNAME: ${{ secrets.VERTX_NEXUS_USERNAME }} 17 | VERTX_NEXUS_PASSWORD: ${{ secrets.VERTX_NEXUS_PASSWORD }} 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v2 21 | with: 22 | ref: ${{ inputs.branch }} 23 | - name: Install JDK 24 | uses: actions/setup-java@v2 25 | with: 26 | java-version: ${{ inputs.jdk }} 27 | distribution: temurin 28 | - name: Get project version 29 | run: echo "PROJECT_VERSION=$(mvn org.apache.maven.plugins:maven-help-plugin:evaluate -Dexpression=project.version -q -DforceStdout | grep -v '\[')" >> $GITHUB_ENV 30 | - name: Maven deploy 31 | if: ${{ endsWith(env.PROJECT_VERSION, '-SNAPSHOT') }} 32 | run: mvn deploy -s .github/maven-cd-settings.xml -DskipTests -B 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .gradle 3 | .idea 4 | .classpath 5 | .project 6 | .settings 7 | .yardoc 8 | .yardopts 9 | build 10 | target 11 | out 12 | *.iml 13 | *.ipr 14 | *.iws 15 | test-output 16 | Scratch.java 17 | ScratchTest.java 18 | test-results 19 | test-tmp 20 | *.class 21 | ScratchPad.java 22 | src/main/resources/ext-js/*.js 23 | src/main/java/io/vertx/java/**/*.java 24 | src/main/groovy/io/vertx/groovy/**/*.groovy 25 | src/main/generated 26 | *.swp 27 | cscope.* 28 | node_modules -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = Vert.x TCP EventBus bridge 2 | 3 | image:https://github.com/vert-x3/vertx-tcp-eventbus-bridge/actions/workflows/ci-5.x.yml/badge.svg[Build Status (5.x),link=https://github.com/vert-x3/vertx-tcp-eventbus-bridge/actions/workflows/ci-5.x.yml] 4 | image:https://github.com/vert-x3/vertx-tcp-eventbus-bridge/actions/workflows/ci-4.x.yml/badge.svg[Build Status (4.x),link=https://github.com/vert-x3/vertx-tcp-eventbus-bridge/actions/workflows/ci-4.x.yml] 5 | 6 | This is a TCP eventbus bridge implementation. 7 | 8 | Please see the in-source asciidoc documentation or the main documentation on the web-site for a full description of this component: 9 | 10 | * link:http://vertx.io/docs/vertx-tcp-eventbus-bridge/java/[web-site docs] 11 | * link:src/main/asciidoc/index.adoc[in-source docs] 12 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 4.0.0 19 | 20 | 21 | io.vertx 22 | vertx5-parent 23 | 12 24 | 25 | 26 | vertx-tcp-eventbus-bridge 27 | 5.1.0-SNAPSHOT 28 | 29 | Vert.x TCP EventBus Bridge 30 | 31 | 32 | scm:git:git@github.com:vert-x3/vertx-tcp-eventbus-bridge.git 33 | scm:git:git@github.com:vert-x3/vertx-tcp-eventbus-bridge.git 34 | git@github.com:vert-x3/vertx-tcp-eventbus-bridge.git 35 | 36 | 37 | 38 | 39 | 40 | io.vertx 41 | vertx-dependencies 42 | ${project.version} 43 | pom 44 | import 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | io.vertx 53 | vertx-core 54 | 55 | 56 | 57 | io.vertx 58 | vertx-bridge-common 59 | ${project.version} 60 | 61 | 62 | 63 | io.vertx 64 | vertx-codegen-api 65 | true 66 | 67 | 68 | io.vertx 69 | vertx-codegen-json 70 | true 71 | 72 | 73 | io.vertx 74 | vertx-docgen-api 75 | true 76 | 77 | 78 | io.vertx 79 | vertx-core 80 | test-jar 81 | test 82 | 83 | 84 | junit 85 | junit 86 | test 87 | 4.13.1 88 | 89 | 90 | io.vertx 91 | vertx-unit 92 | test 93 | 94 | 95 | org.bouncycastle 96 | bcpkix-jdk15on 97 | 1.65 98 | test 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | maven-compiler-plugin 107 | 108 | 109 | default-compile 110 | 111 | 112 | 113 | io.vertx 114 | vertx-codegen 115 | processor 116 | 117 | 118 | io.vertx 119 | vertx-docgen-processor 120 | processor 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | maven-assembly-plugin 132 | 133 | 134 | package-docs 135 | 136 | single 137 | 138 | 139 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /src/client/nodejs/lib/tcp-vertx-eventbus.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by itersh on 25.10.16. 3 | */ 4 | /* 5 | * Copyright (c) 2011-2015 The original author or authors 6 | * ------------------------------------------------------ 7 | * All rights reserved. This program and the accompanying materials 8 | * are made available under the terms of the Eclipse Public License v1.0 9 | * and Apache License v2.0 which accompanies this distribution. 10 | * 11 | * The Eclipse Public License is available at 12 | * http://www.eclipse.org/legal/epl-v10.html 13 | * 14 | * The Apache License v2.0 is available at 15 | * http://www.opensource.org/licenses/apache2.0.php 16 | * 17 | * You may elect to redistribute this code under either of these licenses. 18 | */ 19 | var net = require('net'); 20 | var makeUUID = require('node-uuid').v4; 21 | var tls = require('tls'); 22 | 23 | function mergeHeaders(defaultHeaders, headers) { 24 | if (defaultHeaders) { 25 | if(!headers) { 26 | return defaultHeaders; 27 | } 28 | 29 | for (var headerName in defaultHeaders) { 30 | if (defaultHeaders.hasOwnProperty(headerName)) { 31 | // user can overwrite the default headers 32 | if (typeof headers[headerName] === 'undefined') { 33 | headers[headerName] = defaultHeaders[headerName]; 34 | } 35 | } 36 | } 37 | } 38 | 39 | // headers are required to be a object 40 | return headers || {}; 41 | } 42 | 43 | /** 44 | * WireEncode the message to the bridge 45 | */ 46 | function send(transport, message) { 47 | message = Buffer.from(message, "utf-8"); 48 | var msgLen = message.length; 49 | 50 | var buffer = Buffer.alloc(4); 51 | buffer.writeInt32BE(msgLen, 0); 52 | 53 | transport.write(Buffer.concat([buffer, message], 4 + msgLen)); 54 | } 55 | 56 | /** 57 | * EventBus 58 | * 59 | * @param {String} host 60 | * @param {Number} port 61 | * @param {Object} options 62 | * @constructor 63 | */ 64 | var EventBus = function (host, port, options) { 65 | 66 | var self = this; 67 | 68 | options = options || {}; 69 | 70 | var pingInterval = options.vertxbus_ping_interval || 5000; 71 | var pingTimerID; 72 | 73 | var sendPing = function () { 74 | send(self.transport, JSON.stringify({type: 'ping'})); 75 | }; 76 | 77 | var callback = function (err) { 78 | if (err) { 79 | self.onerror(err); 80 | } 81 | 82 | // Send the first ping then send a ping every pingInterval milliseconds 83 | sendPing(); 84 | pingTimerID = setInterval(sendPing, pingInterval); 85 | self.state = EventBus.OPEN; 86 | self.onopen && self.onopen(); 87 | }; 88 | 89 | // if user use certificate need use tls module 90 | var connectionModule = options.hasOwnProperty('pfx') || options.hasOwnProperty('cert') ? tls : net; 91 | 92 | // attributes 93 | this.transport = connectionModule.connect(port, host, options, callback); 94 | 95 | this.state = EventBus.CONNECTING; 96 | this.handlers = {}; 97 | this.replyHandlers = {}; 98 | this.defaultHeaders = null; 99 | 100 | // default event handlers 101 | this.onerror = console.error; 102 | 103 | // message buffer 104 | var buffer = Buffer.alloc(0); 105 | var len = 0; 106 | 107 | this.transport.on('close', function () { 108 | self.state = EventBus.CLOSED; 109 | if (pingTimerID) { 110 | clearInterval(pingTimerID); 111 | } 112 | self.onclose && self.onclose(); 113 | }); 114 | 115 | this.transport.on('error', self.onerror); 116 | 117 | this.transport.on('data', function (chunk) { 118 | buffer = Buffer.concat([buffer, chunk], buffer.length + chunk.length); 119 | // we need to loop since there can be several messages in a chunk 120 | do { 121 | !len && (len = buffer.readInt32BE(0)); 122 | 123 | if (len && buffer.length >= len + 4) { 124 | // we have a full message 125 | var message = buffer.slice(4, len + 4); 126 | // slice the buffer to consume the next message 127 | buffer = buffer.slice(len + 4); 128 | len = 0; 129 | 130 | var json; 131 | 132 | try { 133 | json = JSON.parse(message.toString('utf8')); 134 | } catch (e) { 135 | self.onerror(e); 136 | return; 137 | } 138 | 139 | // define a reply function on the message itself 140 | if (json.replyAddress) { 141 | Object.defineProperty(json, 'reply', { 142 | value: function (message, headers, callback) { 143 | self.send(json.replyAddress, message, headers, callback); 144 | } 145 | }); 146 | } 147 | 148 | var deliver = function (handler, json) { 149 | if (json.type === 'message' && 150 | json.failureCode !== undefined) { 151 | handler({failureCode: json.failureCode, failureType: json.failureType, message: json.message}); 152 | } else { 153 | handler(null, json); 154 | } 155 | }; 156 | 157 | if (self.handlers[json.address]) { 158 | // iterate all registered handlers 159 | var handlers = self.handlers[json.address]; 160 | // send only goes to one handler 161 | if (json.send && 162 | handlers[0] !== undefined) { 163 | deliver(handlers[0], json); 164 | } else { 165 | for (var i = 0; i < handlers.length; i++) { 166 | deliver(handlers[i], json); 167 | } 168 | } 169 | } else if (self.replyHandlers[json.address]) { 170 | // Might be a reply message 171 | var handler = self.replyHandlers[json.address]; 172 | delete self.replyHandlers[json.address]; 173 | deliver(handler, json); 174 | } else { 175 | if (json.type === 'err') { 176 | self.onerror(json); 177 | } else { 178 | console.warn('No handler found for message: ', json); 179 | } 180 | } 181 | } // if data chunked into few frames need concatenate into buffer 182 | } while (buffer.length > 4 && !len) 183 | }); 184 | }; 185 | 186 | /** 187 | * Send a message 188 | * 189 | * @param {String} address 190 | * @param {Object} message 191 | * @param {Object} [headers] 192 | * @param {Function} [callback] 193 | */ 194 | EventBus.prototype.send = function (address, message, headers, callback) { 195 | // are we ready? 196 | if (this.state != EventBus.OPEN) { 197 | throw new Error('INVALID_STATE_ERR'); 198 | } 199 | 200 | if (typeof headers === 'function') { 201 | callback = headers; 202 | headers = {}; 203 | } 204 | 205 | var envelope = { 206 | type: 'send', 207 | address: address, 208 | headers: mergeHeaders(this.defaultHeaders, headers), 209 | body: message 210 | }; 211 | 212 | if (callback) { 213 | var replyAddress = makeUUID(); 214 | envelope.replyAddress = replyAddress; 215 | this.replyHandlers[replyAddress] = callback; 216 | } 217 | 218 | send(this.transport, JSON.stringify(envelope)); 219 | }; 220 | 221 | /** 222 | * Publish a message 223 | * 224 | * @param {String} address 225 | * @param {Object} message 226 | * @param {Object} [headers] 227 | */ 228 | EventBus.prototype.publish = function (address, message, headers) { 229 | // are we ready? 230 | if (this.state != EventBus.OPEN) { 231 | throw new Error('INVALID_STATE_ERR'); 232 | } 233 | 234 | send(this.transport, JSON.stringify({ 235 | type: 'publish', 236 | address: address, 237 | headers: mergeHeaders(this.defaultHeaders, headers), 238 | body: message 239 | })); 240 | }; 241 | 242 | /** 243 | * Register a new handler 244 | * 245 | * @param {String} address 246 | * @param {Object} [headers] 247 | * @param {Function} callback 248 | */ 249 | EventBus.prototype.registerHandler = function (address, headers, callback) { 250 | // are we ready? 251 | if (this.state != EventBus.OPEN) { 252 | throw new Error('INVALID_STATE_ERR'); 253 | } 254 | 255 | if (typeof headers === 'function') { 256 | callback = headers; 257 | headers = {}; 258 | } 259 | 260 | // ensure it is an array 261 | if (!this.handlers[address]) { 262 | this.handlers[address] = []; 263 | // First handler for this address so we should register the connection 264 | send(this.transport, JSON.stringify({ 265 | type: 'register', 266 | address: address, 267 | headers: mergeHeaders(this.defaultHeaders, headers) 268 | })); 269 | } 270 | 271 | this.handlers[address].push(callback); 272 | }; 273 | 274 | /** 275 | * Unregister a handler 276 | * 277 | * @param {String} address 278 | * @param {Object} [headers] 279 | * @param {Function} callback 280 | */ 281 | EventBus.prototype.unregisterHandler = function (address, headers, callback) { 282 | // are we ready? 283 | if (this.state != EventBus.OPEN) { 284 | throw new Error('INVALID_STATE_ERR'); 285 | } 286 | 287 | var handlers = this.handlers[address]; 288 | 289 | if (handlers) { 290 | 291 | if (typeof headers === 'function') { 292 | callback = headers; 293 | headers = {}; 294 | } 295 | 296 | var idx = handlers.indexOf(callback); 297 | if (idx != -1) { 298 | handlers.splice(idx, 1); 299 | if (handlers.length === 0) { 300 | // No more local handlers so we should unregister the connection 301 | send(this.transport, JSON.stringify({ 302 | type: 'unregister', 303 | address: address, 304 | headers: mergeHeaders(this.defaultHeaders, headers) 305 | })); 306 | 307 | delete this.handlers[address]; 308 | } 309 | } 310 | } 311 | }; 312 | 313 | /** 314 | * Closes the connection to the EvenBus Bridge. 315 | */ 316 | EventBus.prototype.close = function () { 317 | this.state = EventBus.CLOSING; 318 | this.transport.close(); 319 | }; 320 | 321 | EventBus.CONNECTING = 0; 322 | EventBus.OPEN = 1; 323 | EventBus.CLOSING = 2; 324 | EventBus.CLOSED = 3; 325 | 326 | module.exports = EventBus; 327 | -------------------------------------------------------------------------------- /src/client/nodejs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vertx3-eventbus-client", 3 | "description": "Vert.x3 TCP event bus client", 4 | "dependencies": { 5 | "node-uuid": "1.4.8" 6 | }, 7 | "devDependencies": { 8 | "mocha": "2.3.3" 9 | }, 10 | "lib": "lib", 11 | "scripts": { 12 | "test": "mocha ./test/index.js" 13 | } 14 | } -------------------------------------------------------------------------------- /src/client/nodejs/test/TcpEventBusBridgeEchoServer.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import io.vertx.core.Vertx; 4 | import io.vertx.core.eventbus.EventBus; 5 | import io.vertx.core.eventbus.Message; 6 | import io.vertx.core.json.JsonObject; 7 | import io.vertx.ext.bridge.BridgeOptions; 8 | import io.vertx.ext.bridge.PermittedOptions; 9 | import io.vertx.ext.eventbus.bridge.tcp.TcpEventBusBridge; 10 | 11 | public class TcpEventBusBridgeEchoServer { 12 | 13 | public static void main(String[] args) { 14 | Vertx vertx = Vertx.vertx(); 15 | EventBus eb = vertx.eventBus(); 16 | 17 | eb.consumer("hello", (Message msg) -> { 18 | msg.reply(new JsonObject().put("value", "Hello " + msg.body().getString("value"))); 19 | }); 20 | 21 | eb.consumer("echo", 22 | (Message msg) -> msg.reply(msg.body())); 23 | 24 | eb.consumer("echo2", 25 | (Message msg) -> { 26 | if ("send".equals(msg.body().getString("response_type"))) { 27 | eb.send("echo2_response", msg.body()); 28 | } else { 29 | eb.publish("echo2_response", msg.body()); 30 | } 31 | }); 32 | 33 | TcpEventBusBridge bridge = TcpEventBusBridge.create( 34 | vertx, 35 | new BridgeOptions() 36 | .addInboundPermitted(new PermittedOptions().setAddress("hello")) 37 | .addInboundPermitted(new PermittedOptions().setAddress("echo")) 38 | .addOutboundPermitted(new PermittedOptions().setAddress("echo")) 39 | .addInboundPermitted(new PermittedOptions().setAddress("echo2")) 40 | .addOutboundPermitted(new PermittedOptions().setAddress("echo2_response"))); 41 | 42 | bridge.listen(7000, res -> System.out.println("Ready")); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/client/nodejs/test/index.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var EventBus = require('../lib/tcp-vertx-eventbus'); 3 | 4 | describe('echo test', function () { 5 | it('should echo the same message that was sent', function (done) { 6 | var eb = new EventBus('localhost', 7000); 7 | 8 | 9 | eb.onerror = function (err) { 10 | console.error(err); 11 | assert.fail(); 12 | }; 13 | 14 | eb.onopen = function () { 15 | // send a echo message 16 | eb.send('echo', {value: 'vert.x'}, function (err, res) { 17 | if (err) { 18 | assert.fail(); 19 | return; 20 | } 21 | 22 | assert.equal(res.body.value, 'vert.x'); 23 | done(); 24 | }); 25 | }; 26 | }); 27 | }); 28 | 29 | 30 | describe('send v. publish', function () { 31 | it('should deliver sends to one listener', function (done) { 32 | var eb = new EventBus('localhost', 7000); 33 | 34 | eb.onerror = function (err) { 35 | console.error(err); 36 | assert.fail(); 37 | }; 38 | 39 | var messages = []; 40 | 41 | var handler = function (err, msg) { 42 | if (err) { 43 | assert.fail(); 44 | return; 45 | } 46 | 47 | messages.push(msg); 48 | }; 49 | 50 | eb.onopen = function () { 51 | eb.registerHandler("echo2_response", handler); 52 | eb.registerHandler("echo2_response", handler); 53 | 54 | setTimeout(function () { 55 | assert.equal(messages.length, 1); 56 | done(); 57 | }, 50); 58 | 59 | eb.send('echo2', {response_type: 'send'}, function (err, res) { 60 | if (err) { 61 | assert.fail(); 62 | } 63 | }); 64 | }; 65 | }); 66 | 67 | it('should deliver publishes to every listener', function (done) { 68 | var eb = new EventBus('localhost', 7000); 69 | 70 | eb.onerror = function (err) { 71 | console.error(err); 72 | assert.fail(); 73 | }; 74 | 75 | var messages = []; 76 | 77 | var handler = function (err, msg) { 78 | if (err) { 79 | assert.fail(); 80 | return; 81 | } 82 | 83 | messages.push(msg); 84 | }; 85 | 86 | eb.onopen = function () { 87 | eb.registerHandler("echo2_response", handler); 88 | eb.registerHandler("echo2_response", handler); 89 | 90 | setTimeout(function () { 91 | assert.equal(messages.length, 2); 92 | done(); 93 | }, 50); 94 | 95 | eb.send('echo2', {response_type: 'publish'}, function (err, res) { 96 | if (err) { 97 | assert.fail(); 98 | } 99 | }); 100 | }; 101 | }); 102 | }); 103 | -------------------------------------------------------------------------------- /src/main/asciidoc/index.adoc: -------------------------------------------------------------------------------- 1 | = Vert.x TCP EventBus bridge 2 | 3 | Vert.x TCP EventBus bridge is a TCP bridge to Vert.x EventBus. 4 | To use this project, add the following dependency to the _dependencies_ section of your build descriptor: 5 | 6 | Maven (in your `pom.xml`): 7 | 8 | [source,xml,subs="+attributes"] 9 | ---- 10 | 11 | ${maven.groupId} 12 | ${maven.artifactId} 13 | ${maven.version} 14 | 15 | ---- 16 | 17 | Gradle (in your `build.gradle` file): 18 | 19 | [source,groovy,subs="+attributes"] 20 | ---- 21 | compile '${maven.groupId}:${maven.artifactId}:${maven.version}' 22 | ---- 23 | 24 | The TCP EventBus bridge is built on top of TCP, meaning that any application that can create TCP sockets can interact with a remote Vert.x instance via its event bus. 25 | 26 | The main use case for the TCP bridge _versus_ the SockJS bridge is for applications that are more resource-constrained and that need to be lightweight since the whole HTTP WebSockets is replaced with plain TCP sockets. 27 | 28 | It remains of course useful even for applications that don't have tight resource constraints: 29 | the protocol is simple enough to efficiently provide an integration interface with non-JVM applications. 30 | 31 | The protocol has been kept as simple as possible and communications use Frames both ways. 32 | The structure of a Frame looks like this: 33 | 34 | ---- 35 | <{ 36 | type: String, 37 | address: String, 38 | (replyAddress: String)?, 39 | headers: JsonObject, 40 | body: JsonObject 41 | }: JsonObject> 42 | ---- 43 | 44 | The message consists of a JSON document that may or may not have been minified. 45 | The message must be prefixed by a _big endian_ 32 bits positive integer (4 bytes) that indicates the full length of the JSON document, in bytes. 46 | 47 | The message `type` can be the following for messages sent by the TCP client: 48 | 49 | 1. `send` to send a message to an `address`, 50 | 2. `publish` to publish a message to an `address`, 51 | 3. `register` to subscribe to the messages sent or published to an `address`, 52 | 4. `unregister` to unsubscribe to the messages sent or published to an `address`, 53 | 5. `ping` to send a `ping` request to the bridge. 54 | 55 | Note that the `replyAddress` field is optional and may only be used for a `send` message. 56 | A message with that field is expected to _eventually_ receive a message back from the server whose `address` field will be that of the original `replyAddress` value. 57 | 58 | The server posts messages back to the client, and they can be of the following `type`: 59 | 60 | 1. `message` for messages sent or published to an `address`, or 61 | 2. `err` to indicate an error (the `body` shall contain details), or 62 | 3. `pong` to respond the `ping` request sent from client. 63 | 64 | An example Node.js client is available in the source of the project. 65 | This client uses the same API as the SockJS counterpart so it should make it easier to switch between the TCP and SockJS implementations. 66 | 67 | An example on how to get started with this bridge could be: 68 | 69 | [source,$lang] 70 | ---- 71 | {@link examples.TCPBridgeExamples#example1} 72 | ---- 73 | 74 | == Listening to Unix domain sockets 75 | 76 | When running on JDK 16+, or using a https://vertx.io/docs/vertx-core/java/#_native_transports[native transport], a server can listen to Unix domain sockets: 77 | 78 | [source,$lang] 79 | ---- 80 | {@link examples.TCPBridgeExamples#serverWithDomainSockets} 81 | ---- 82 | -------------------------------------------------------------------------------- /src/main/java/examples/TCPBridgeExamples.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Red Hat, Inc. 3 | * 4 | * Red Hat licenses this file to you under the Apache License, version 2.0 5 | * (the "License"); you may not use this file except in compliance with the 6 | * License. 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package examples; 18 | 19 | import io.vertx.core.Vertx; 20 | import io.vertx.core.net.SocketAddress; 21 | import io.vertx.docgen.Source; 22 | import io.vertx.ext.bridge.BridgeOptions; 23 | import io.vertx.ext.bridge.PermittedOptions; 24 | import io.vertx.ext.eventbus.bridge.tcp.TcpEventBusBridge; 25 | 26 | /** 27 | * 28 | * @author Paulo Lopes 29 | */ 30 | @Source 31 | public class TCPBridgeExamples { 32 | 33 | public void example1(Vertx vertx) { 34 | TcpEventBusBridge bridge = TcpEventBusBridge.create( 35 | vertx, 36 | new BridgeOptions() 37 | .addInboundPermitted(new PermittedOptions().setAddress("in")) 38 | .addOutboundPermitted(new PermittedOptions().setAddress("out"))); 39 | 40 | bridge.listen(7000).onComplete(res -> { 41 | if (res.succeeded()) { 42 | // succeed... 43 | } else { 44 | // fail... 45 | } 46 | }); 47 | } 48 | 49 | public void serverWithDomainSockets(TcpEventBusBridge bridge) { 50 | SocketAddress domainSocketAddress = SocketAddress.domainSocketAddress("/var/tmp/bridge.sock"); 51 | 52 | bridge.listen(domainSocketAddress).onComplete(res -> { 53 | if (res.succeeded()) { 54 | // succeed... 55 | } else { 56 | // fail... 57 | } 58 | }); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/ext/eventbus/bridge/tcp/BridgeEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Red Hat, Inc. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | 17 | package io.vertx.ext.eventbus.bridge.tcp; 18 | 19 | import io.vertx.codegen.annotations.CacheReturn; 20 | import io.vertx.codegen.annotations.Fluent; 21 | import io.vertx.codegen.annotations.VertxGen; 22 | import io.vertx.core.Future; 23 | import io.vertx.core.json.JsonObject; 24 | import io.vertx.core.net.NetSocket; 25 | 26 | /** 27 | * Represents an event that occurs on the event bus bridge. 28 | *

29 | * Please consult the documentation for a full explanation. 30 | * 31 | * @author Tim Fox 32 | */ 33 | @VertxGen 34 | public interface BridgeEvent extends io.vertx.ext.bridge.BaseBridgeEvent { 35 | 36 | /** 37 | * Get the raw JSON message for the event. This will be null for SOCKET_CREATED or SOCKET_CLOSED events as there is 38 | * no message involved. 39 | * 40 | * @param message the raw message 41 | * @return this reference, so it can be used fluently 42 | */ 43 | @Fluent 44 | BridgeEvent setRawMessage(JsonObject message); 45 | 46 | /** 47 | * Get the SockJSSocket instance corresponding to the event 48 | * 49 | * @return the SockJSSocket instance 50 | */ 51 | @CacheReturn 52 | NetSocket socket(); 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/ext/eventbus/bridge/tcp/TcpEventBusBridge.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Red Hat, Inc. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | package io.vertx.ext.eventbus.bridge.tcp; 17 | 18 | import io.vertx.codegen.annotations.VertxGen; 19 | import io.vertx.core.Future; 20 | import io.vertx.core.Handler; 21 | import io.vertx.core.Vertx; 22 | import io.vertx.core.net.NetServerOptions; 23 | import io.vertx.core.net.SocketAddress; 24 | import io.vertx.ext.bridge.BridgeOptions; 25 | import io.vertx.ext.eventbus.bridge.tcp.impl.TcpEventBusBridgeImpl; 26 | 27 | /** 28 | * TCP EventBus bridge for Vert.x 29 | * 30 | * @author Paulo Lopes 31 | */ 32 | @VertxGen 33 | public interface TcpEventBusBridge { 34 | 35 | static TcpEventBusBridge create(Vertx vertx) { 36 | return create(vertx, null, null); 37 | } 38 | 39 | static TcpEventBusBridge create(Vertx vertx, BridgeOptions options) { 40 | return create(vertx, options, null); 41 | } 42 | 43 | static TcpEventBusBridge create(Vertx vertx, BridgeOptions options, NetServerOptions netServerOptions) { 44 | return new TcpEventBusBridgeImpl(vertx, options, netServerOptions,null); 45 | } 46 | 47 | static TcpEventBusBridge create(Vertx vertx, BridgeOptions options, NetServerOptions netServerOptions,Handler eventHandler) { 48 | return new TcpEventBusBridgeImpl(vertx, options, netServerOptions,eventHandler); 49 | } 50 | 51 | /** 52 | * Start listening on the port and host as configured in the {@link io.vertx.core.net.NetServerOptions} used when creating the server. 53 | * 54 | * @return a future of the result 55 | */ 56 | Future listen(); 57 | 58 | /** 59 | * Start listening on the specified port and host, ignoring port and host configured in the {@link io.vertx.core.net.NetServerOptions} used when creating the server. 60 | * 61 | * @param port the tcp port 62 | * @param address the local address 63 | * 64 | * @return a future of the result 65 | */ 66 | Future listen(int port, String address); 67 | 68 | /** 69 | * Start listening on the specified port and host "0.0.0.0", ignoring port and host configured in the {@link io.vertx.core.net.NetServerOptions} used when creating the server. 70 | * 71 | * @param port the TCP port 72 | * 73 | * @return a future of the result 74 | */ 75 | Future listen(int port); 76 | 77 | /** 78 | * Start listening on the specified local address, ignoring port and host configured in the {@link NetServerOptions} used when creating the server. 79 | * 80 | * @param localAddress the local address to listen on 81 | * @return a future of the result 82 | */ 83 | default Future listen(SocketAddress localAddress) { 84 | return Future.failedFuture("Not supported"); 85 | } 86 | 87 | /** 88 | * Close the current socket. 89 | * 90 | * @return a future of the result 91 | */ 92 | Future close(); 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/ext/eventbus/bridge/tcp/impl/BridgeEventImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Red Hat, Inc. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | 17 | package io.vertx.ext.eventbus.bridge.tcp.impl; 18 | 19 | import io.vertx.core.AsyncResult; 20 | import io.vertx.core.Future; 21 | import io.vertx.core.Promise; 22 | import io.vertx.core.json.JsonObject; 23 | import io.vertx.core.net.NetSocket; 24 | import io.vertx.ext.bridge.BridgeEventType; 25 | import io.vertx.ext.eventbus.bridge.tcp.BridgeEvent; 26 | 27 | /** 28 | * @author Tim Fox 29 | * @author grant@iowntheinter.net 30 | */ 31 | class BridgeEventImpl implements BridgeEvent { 32 | 33 | private final BridgeEventType type; 34 | private final JsonObject rawMessage; 35 | private final NetSocket socket; 36 | private final Promise promise; 37 | 38 | public BridgeEventImpl(BridgeEventType type, JsonObject rawMessage, NetSocket socket) { 39 | this.type = type; 40 | this.rawMessage = rawMessage; 41 | this.socket = socket; 42 | this.promise = Promise.promise(); 43 | } 44 | 45 | @Override 46 | public Future future() { 47 | return promise.future(); 48 | } 49 | 50 | @Override 51 | public BridgeEventType type() { 52 | return type; 53 | } 54 | 55 | @Override 56 | public JsonObject getRawMessage() { 57 | return rawMessage; 58 | } 59 | 60 | @Override 61 | public BridgeEvent setRawMessage(JsonObject message) { 62 | if (message != rawMessage) { 63 | rawMessage.clear().mergeIn(message); 64 | } 65 | return this; 66 | } 67 | 68 | @Override 69 | public void handle(AsyncResult asyncResult) { 70 | promise.handle(asyncResult); 71 | } 72 | 73 | @Override 74 | public NetSocket socket() { 75 | return socket; 76 | } 77 | 78 | @Override 79 | public void complete(Boolean result) { 80 | promise.complete(result); 81 | } 82 | 83 | @Override 84 | public void complete() { 85 | promise.complete(); 86 | } 87 | 88 | @Override 89 | public void fail(Throwable throwable) { 90 | promise.fail(throwable); 91 | } 92 | 93 | @Override 94 | public void fail(String failureMessage) { 95 | promise.fail(failureMessage); 96 | } 97 | 98 | @Override 99 | public boolean tryComplete(Boolean result) { 100 | return promise.tryComplete(result); 101 | } 102 | 103 | @Override 104 | public boolean tryComplete() { 105 | return promise.tryComplete(); 106 | } 107 | 108 | @Override 109 | public boolean tryFail(Throwable cause) { 110 | return promise.tryFail(cause); 111 | } 112 | 113 | @Override 114 | public boolean tryFail(String failureMessage) { 115 | return promise.tryFail(failureMessage); 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/ext/eventbus/bridge/tcp/impl/TcpEventBusBridgeImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Red Hat, Inc. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | package io.vertx.ext.eventbus.bridge.tcp.impl; 17 | 18 | import io.vertx.core.AsyncResult; 19 | import io.vertx.core.Future; 20 | import io.vertx.core.Handler; 21 | import io.vertx.core.Vertx; 22 | import io.vertx.core.eventbus.*; 23 | import io.vertx.core.internal.logging.Logger; 24 | import io.vertx.core.internal.logging.LoggerFactory; 25 | import io.vertx.core.json.JsonObject; 26 | import io.vertx.core.net.NetServer; 27 | import io.vertx.core.net.NetServerOptions; 28 | import io.vertx.core.net.NetSocket; 29 | import io.vertx.core.net.SocketAddress; 30 | import io.vertx.ext.bridge.BridgeEventType; 31 | import io.vertx.ext.bridge.BridgeOptions; 32 | import io.vertx.ext.bridge.PermittedOptions; 33 | import io.vertx.ext.eventbus.bridge.tcp.BridgeEvent; 34 | import io.vertx.ext.eventbus.bridge.tcp.TcpEventBusBridge; 35 | import io.vertx.ext.eventbus.bridge.tcp.impl.protocol.FrameParser; 36 | 37 | import java.util.HashMap; 38 | import java.util.Iterator; 39 | import java.util.List; 40 | import java.util.Map; 41 | import java.util.concurrent.ConcurrentHashMap; 42 | import java.util.function.Supplier; 43 | import java.util.regex.Matcher; 44 | import java.util.regex.Pattern; 45 | 46 | import static io.vertx.ext.eventbus.bridge.tcp.impl.protocol.FrameHelper.sendErrFrame; 47 | import static io.vertx.ext.eventbus.bridge.tcp.impl.protocol.FrameHelper.sendFrame; 48 | 49 | /** 50 | * Abstract TCP EventBus bridge. Handles all common socket operations but has no knowledge on the payload. 51 | * 52 | * @author Paulo Lopes 53 | */ 54 | public class TcpEventBusBridgeImpl implements TcpEventBusBridge { 55 | 56 | private static final Logger log = LoggerFactory.getLogger(TcpEventBusBridgeImpl.class); 57 | 58 | private final EventBus eb; 59 | private final NetServer server; 60 | 61 | private final Map compiledREs = new HashMap<>(); 62 | private final BridgeOptions options; 63 | private final Handler bridgeEventHandler; 64 | 65 | 66 | public TcpEventBusBridgeImpl(Vertx vertx, BridgeOptions options, NetServerOptions netServerOptions, Handler eventHandler) { 67 | this.eb = vertx.eventBus(); 68 | this.options = options != null ? options : new BridgeOptions(); 69 | this.bridgeEventHandler = eventHandler; 70 | 71 | server = vertx.createNetServer(netServerOptions == null ? new NetServerOptions() : netServerOptions); 72 | server.connectHandler(this::handler); 73 | } 74 | 75 | @Override 76 | public Future listen() { 77 | return server.listen().map(this); 78 | } 79 | 80 | @Override 81 | public Future listen(int port) { 82 | return server.listen(port).map(this); 83 | } 84 | 85 | @Override 86 | public Future listen(SocketAddress localAddress) { 87 | return server.listen(localAddress).map(this); 88 | } 89 | 90 | @Override 91 | public Future listen(int port, String address) { 92 | return server.listen(port, address).map(this); 93 | } 94 | 95 | private void doSendOrPub(NetSocket socket, String address, JsonObject msg, Map> registry, Map> replies) { 97 | final Object body = msg.getValue("body"); 98 | final JsonObject headers = msg.getJsonObject("headers"); 99 | 100 | 101 | // default to message 102 | final String type = msg.getString("type", "message"); 103 | DeliveryOptions deliveryOptions = parseMsgHeaders(new DeliveryOptions(), headers); 104 | 105 | switch (type) { 106 | case "send": 107 | if (checkMatches(true, address, replies)) { 108 | final String replyAddress = msg.getString("replyAddress"); 109 | 110 | if (replyAddress != null) { 111 | // reply address is not null, it is a request from TCP endpoint that will wait for a response 112 | eb.request(address, body, deliveryOptions).onComplete((AsyncResult> res1) -> { 113 | if (res1.failed()) { 114 | sendErrFrame(address, replyAddress, (ReplyException) res1.cause(), socket); 115 | } else { 116 | final Message response = res1.result(); 117 | final JsonObject responseHeaders = new JsonObject(); 118 | 119 | // clone the headers from / to 120 | for (Map.Entry entry : response.headers()) { 121 | responseHeaders.put(entry.getKey(), entry.getValue()); 122 | } 123 | 124 | if (response.replyAddress() != null) { 125 | replies.put(response.replyAddress(), response); 126 | } 127 | 128 | sendFrame("message", replyAddress, response.replyAddress(), responseHeaders, true, response.body(), socket); 129 | } 130 | }); 131 | } else { 132 | // no reply address it might be a response, a failure or a request that does not need a response 133 | if (replies.containsKey(address)) { 134 | // address is registered, it is not a request 135 | Integer failureCode = msg.getInteger("failureCode"); 136 | if ( failureCode == null ) { 137 | //No failure code, it is a response 138 | replies.get(address).reply(body, deliveryOptions); 139 | } else { 140 | //Failure code, fail the original response 141 | replies.get(address).fail(msg.getInteger("failureCode"), msg.getString("message")); 142 | } 143 | } else { 144 | // it is a request that does not expect a response 145 | eb.send(address, body, deliveryOptions); 146 | } 147 | } 148 | // replies are a one time off operation 149 | replies.remove(address); 150 | } else { 151 | sendErrFrame("access_denied", socket); 152 | } 153 | break; 154 | case "publish": 155 | if (checkMatches(true, address)) { 156 | eb.publish(address, body, deliveryOptions); 157 | } else { 158 | sendErrFrame("access_denied", socket); 159 | } 160 | break; 161 | case "register": 162 | if (checkMatches(false, address)) { 163 | registry.put(address, eb.consumer(address, (Message res1) -> { 164 | // save a reference to the message so tcp bridged messages can be replied properly 165 | if (res1.replyAddress() != null) { 166 | replies.put(res1.replyAddress(), res1); 167 | } 168 | 169 | final JsonObject responseHeaders = new JsonObject(); 170 | 171 | // clone the headers from / to 172 | for (Map.Entry entry : res1.headers()) { 173 | responseHeaders.put(entry.getKey(), entry.getValue()); 174 | } 175 | 176 | sendFrame("message", res1.address(), res1.replyAddress(), responseHeaders, res1.isSend(), res1.body(), socket); 177 | })); 178 | checkCallHook(() -> new BridgeEventImpl(BridgeEventType.REGISTERED, msg, socket), null, null); 179 | } else { 180 | sendErrFrame("access_denied", socket); 181 | } 182 | break; 183 | case "unregister": 184 | if (checkMatches(false, address)) { 185 | MessageConsumer consumer = registry.remove(address); 186 | if (consumer != null) { 187 | consumer.unregister(); 188 | } else { 189 | sendErrFrame("unknown_address", socket); 190 | } 191 | } else { 192 | sendErrFrame("access_denied", socket); 193 | } 194 | break; 195 | case "ping": 196 | sendFrame("pong", socket); 197 | break; 198 | default: 199 | sendErrFrame("unknown_type", socket); 200 | break; 201 | } 202 | } 203 | 204 | private void handler(NetSocket socket) { 205 | 206 | final Map> registry = new ConcurrentHashMap<>(); 207 | final Map> replies = new ConcurrentHashMap<>(); 208 | 209 | // create a protocol parser 210 | final FrameParser parser = new FrameParser(res -> { 211 | if (res.failed()) { 212 | // could not parse the message properly 213 | log.error(res.cause()); 214 | return; 215 | } 216 | 217 | final JsonObject msg = res.result(); 218 | 219 | // short reference 220 | 221 | // default to message 222 | final String type = msg.getString("type", "message"); 223 | final String address = msg.getString("address"); 224 | BridgeEventType eventType = parseType(type); 225 | checkCallHook(() -> new BridgeEventImpl(eventType, msg, socket), 226 | () -> { 227 | if (eventType != BridgeEventType.SOCKET_PING && address == null) { 228 | sendErrFrame("missing_address", socket); 229 | log.error("msg does not have address: " + msg); 230 | return; 231 | } 232 | doSendOrPub(socket, address, msg, registry, replies); 233 | }, 234 | () -> sendErrFrame("blocked by bridgeEvent handler", socket)); 235 | }); 236 | 237 | socket.handler(parser); 238 | 239 | socket.exceptionHandler(t -> { 240 | log.error(t.getMessage(), t); 241 | registry.values().forEach(MessageConsumer::unregister); 242 | registry.clear(); 243 | socket.close(); 244 | }); 245 | 246 | socket.endHandler(v -> { 247 | registry.values().forEach(MessageConsumer::unregister); 248 | registry.clear(); 249 | }); 250 | } 251 | 252 | @Override 253 | public Future close() { 254 | return server.close(); 255 | } 256 | 257 | private void checkCallHook(Supplier eventSupplier, Runnable okAction, Runnable rejectAction) { 258 | if (bridgeEventHandler == null) { 259 | if (okAction != null) { 260 | okAction.run(); 261 | } 262 | } else { 263 | BridgeEventImpl event = eventSupplier.get(); 264 | bridgeEventHandler.handle(event); 265 | event.future().onComplete(res -> { 266 | if (res.succeeded()) { 267 | if (res.result()) { 268 | if (okAction != null) { 269 | okAction.run(); 270 | } 271 | } else { 272 | if (rejectAction != null) { 273 | rejectAction.run(); 274 | } else { 275 | log.debug("Bridge handler prevented send or pub"); 276 | } 277 | } 278 | } else { 279 | log.error("Failure in bridge event handler", res.cause()); 280 | } 281 | }); 282 | } 283 | } 284 | 285 | private boolean checkMatches(boolean inbound, String address) { 286 | return checkMatches(inbound, address, null); 287 | } 288 | 289 | private boolean checkMatches(boolean inbound, String address, Map> replies) { 290 | // special case, when dealing with replies the addresses are not in the inbound/outbound list but on 291 | // the replies registry 292 | if (replies != null && inbound && replies.containsKey(address)) { 293 | return true; 294 | } 295 | 296 | List matches = inbound ? options.getInboundPermitteds() : options.getOutboundPermitteds(); 297 | 298 | for (PermittedOptions matchHolder : matches) { 299 | String matchAddress = matchHolder.getAddress(); 300 | String matchRegex; 301 | if (matchAddress == null) { 302 | matchRegex = matchHolder.getAddressRegex(); 303 | } else { 304 | matchRegex = null; 305 | } 306 | 307 | boolean addressOK; 308 | if (matchAddress == null) { 309 | addressOK = matchRegex == null || regexMatches(matchRegex, address); 310 | } else { 311 | addressOK = matchAddress.equals(address); 312 | } 313 | 314 | if (addressOK) { 315 | return true; 316 | } 317 | } 318 | 319 | return false; 320 | } 321 | 322 | private boolean regexMatches(String matchRegex, String address) { 323 | Pattern pattern = compiledREs.get(matchRegex); 324 | if (pattern == null) { 325 | pattern = Pattern.compile(matchRegex); 326 | compiledREs.put(matchRegex, pattern); 327 | } 328 | Matcher m = pattern.matcher(address); 329 | return m.matches(); 330 | } 331 | 332 | private DeliveryOptions parseMsgHeaders(DeliveryOptions options, JsonObject headers) { 333 | if (headers == null) 334 | return options; 335 | 336 | Iterator fnameIter = headers.fieldNames().iterator(); 337 | String fname; 338 | while (fnameIter.hasNext()) { 339 | fname = fnameIter.next(); 340 | if ("timeout".equals(fname)) { 341 | options.setSendTimeout(headers.getLong(fname)); 342 | } else if ("localOnly".equals(fname)) { 343 | options.setLocalOnly(headers.getBoolean(fname)); 344 | } else if ("codecName".equals(fname)) { 345 | options.setCodecName(headers.getString(fname)); 346 | } else { 347 | options.addHeader(fname, headers.getString(fname)); 348 | } 349 | } 350 | 351 | return options; 352 | } 353 | 354 | private static BridgeEventType parseType(String typeStr) { 355 | switch (typeStr) { 356 | case "ping": 357 | return BridgeEventType.SOCKET_PING; 358 | case "register": 359 | return BridgeEventType.REGISTER; 360 | case "unregister": 361 | return BridgeEventType.UNREGISTER; 362 | case "publish": 363 | return BridgeEventType.PUBLISH; 364 | case "send": 365 | return BridgeEventType.SEND; 366 | default: 367 | throw new IllegalArgumentException("Invalid frame type " + typeStr); 368 | } 369 | } 370 | } 371 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/ext/eventbus/bridge/tcp/impl/protocol/FrameHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Red Hat, Inc. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | package io.vertx.ext.eventbus.bridge.tcp.impl.protocol; 17 | 18 | import io.vertx.core.buffer.Buffer; 19 | import io.vertx.core.eventbus.ReplyException; 20 | import io.vertx.core.json.JsonObject; 21 | import io.vertx.core.streams.WriteStream; 22 | 23 | import java.nio.charset.Charset; 24 | import java.nio.charset.StandardCharsets; 25 | 26 | /** 27 | * Helper class to format and send frames over a socket 28 | * @author Paulo Lopes 29 | */ 30 | public class FrameHelper { 31 | 32 | private static final Charset UTF8 = StandardCharsets.UTF_8; 33 | 34 | private FrameHelper() {} 35 | 36 | public static void sendFrame(String type, String address, String replyAddress, JsonObject headers, Boolean send, Object body, WriteStream handler) { 37 | final JsonObject payload = new JsonObject().put("type", type); 38 | 39 | if (address != null) { 40 | payload.put("address", address); 41 | } 42 | 43 | if (replyAddress != null) { 44 | payload.put("replyAddress", replyAddress); 45 | } 46 | 47 | if (headers != null) { 48 | payload.put("headers", headers); 49 | } 50 | 51 | if (body != null) { 52 | payload.put("body", body); 53 | } 54 | 55 | if (send != null) { 56 | payload.put("send", send); 57 | } 58 | 59 | writeFrame(payload, handler); 60 | } 61 | 62 | public static void sendFrame(String type, String address, String replyAddress, Object body, WriteStream handler) { 63 | sendFrame(type, address, replyAddress, null, null, body, handler); 64 | } 65 | 66 | public static void sendFrame(String type, String address, Object body, WriteStream handler) { 67 | sendFrame(type, address, null, null, null, body, handler); 68 | } 69 | 70 | public static void sendFrame(String type, WriteStream handler) { 71 | sendFrame(type, null, null, null, null, null, handler); 72 | } 73 | 74 | public static void sendErrFrame(String address, String replyAddress, ReplyException failure, WriteStream handler) { 75 | final JsonObject payload = new JsonObject() 76 | .put("type", "err") 77 | .put("address", replyAddress) 78 | .put("sourceAddress", address) 79 | .put("failureCode", failure.failureCode()) 80 | .put("failureType", failure.failureType().name()) 81 | .put("message", failure.getMessage()); 82 | 83 | writeFrame(payload, handler); 84 | } 85 | 86 | public static void sendErrFrame(String message, WriteStream handler) { 87 | final JsonObject payload = new JsonObject() 88 | .put("type", "err") 89 | .put("message", message); 90 | 91 | writeFrame(payload, handler); 92 | } 93 | 94 | public static void writeFrame(JsonObject payload, WriteStream handler) { 95 | // encode 96 | byte[] data = payload.encode().getBytes(UTF8); 97 | 98 | handler.write(Buffer.buffer().appendInt(data.length).appendBytes(data)); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/io/vertx/ext/eventbus/bridge/tcp/impl/protocol/FrameParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Red Hat, Inc. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | package io.vertx.ext.eventbus.bridge.tcp.impl.protocol; 17 | 18 | import io.vertx.core.AsyncResult; 19 | import io.vertx.core.Future; 20 | import io.vertx.core.Handler; 21 | import io.vertx.core.buffer.Buffer; 22 | import io.vertx.core.json.DecodeException; 23 | import io.vertx.core.json.JsonObject; 24 | 25 | /** 26 | * Simple LV parser 27 | * 28 | * @author Paulo Lopes 29 | */ 30 | public class FrameParser implements Handler { 31 | 32 | private Buffer _buffer; 33 | private int _offset; 34 | 35 | private final Handler> client; 36 | 37 | public FrameParser(Handler> client) { 38 | this.client = client; 39 | } 40 | 41 | @Override 42 | public void handle(Buffer buffer) { 43 | append(buffer); 44 | 45 | int offset; 46 | 47 | while (true) { 48 | // set a rewind point. if a failure occurs, 49 | // wait for the next handle()/append() and try again 50 | offset = _offset; 51 | 52 | // how many bytes are in the buffer 53 | int remainingBytes = bytesRemaining(); 54 | 55 | // at least 4 bytes 56 | if (remainingBytes < 4) { 57 | break; 58 | } 59 | 60 | // what is the length of the message 61 | int length = _buffer.getInt(_offset); 62 | _offset += 4; 63 | 64 | if (remainingBytes - 4 >= length) { 65 | // we have a complete message 66 | try { 67 | client.handle(Future.succeededFuture(new JsonObject(_buffer.getString(_offset, _offset + length)))); 68 | } catch (DecodeException e) { 69 | // bad json 70 | client.handle(Future.failedFuture(e)); 71 | } 72 | _offset += length; 73 | } else { 74 | // not enough data: rewind, and wait 75 | // for the next packet to appear 76 | _offset = offset; 77 | break; 78 | } 79 | } 80 | } 81 | 82 | private void append(Buffer newBuffer) { 83 | if (newBuffer == null) { 84 | return; 85 | } 86 | 87 | // first run 88 | if (_buffer == null) { 89 | _buffer = newBuffer; 90 | 91 | return; 92 | } 93 | 94 | // out of data 95 | if (_offset >= _buffer.length()) { 96 | _buffer = newBuffer; 97 | _offset = 0; 98 | 99 | return; 100 | } 101 | 102 | // very large packet 103 | if (_offset > 0) { 104 | _buffer = _buffer.getBuffer(_offset, _buffer.length()); 105 | } 106 | _buffer.appendBuffer(newBuffer); 107 | 108 | _offset = 0; 109 | } 110 | 111 | private int bytesRemaining() { 112 | return (_buffer.length() - _offset) < 0 ? 0 : (_buffer.length() - _offset); 113 | } 114 | } -------------------------------------------------------------------------------- /src/main/java/io/vertx/ext/eventbus/bridge/tcp/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Red Hat, Inc. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | 17 | @ModuleGen(name = "vertx-tcp-eventbus-bridge", groupPackage = "io.vertx") 18 | 19 | package io.vertx.ext.eventbus.bridge.tcp; 20 | 21 | import io.vertx.codegen.annotations.ModuleGen; 22 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/ext/eventbus/bridge/tcp/SSLKeyPairCerts.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Red Hat, Inc. and others 3 | * 4 | * Red Hat licenses this file to you under the Apache License, version 2.0 5 | * (the "License"); you may not use this file except in compliance with the 6 | * License. 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | * 16 | */ 17 | 18 | package io.vertx.ext.eventbus.bridge.tcp; 19 | 20 | import io.vertx.core.buffer.Buffer; 21 | import io.vertx.core.net.JksOptions; 22 | import org.bouncycastle.asn1.x500.X500Name; 23 | import org.bouncycastle.asn1.x509.AlgorithmIdentifier; 24 | import org.bouncycastle.asn1.x509.GeneralName; 25 | import org.bouncycastle.asn1.x509.GeneralNames; 26 | import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; 27 | import org.bouncycastle.cert.X509CertificateHolder; 28 | import org.bouncycastle.cert.X509v3CertificateBuilder; 29 | import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; 30 | import org.bouncycastle.crypto.params.AsymmetricKeyParameter; 31 | import org.bouncycastle.crypto.util.PrivateKeyFactory; 32 | import org.bouncycastle.operator.ContentSigner; 33 | import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder; 34 | import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder; 35 | import org.bouncycastle.operator.bc.BcContentSignerBuilder; 36 | import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder; 37 | 38 | import java.io.ByteArrayOutputStream; 39 | import java.math.BigInteger; 40 | import java.security.KeyPair; 41 | import java.security.KeyPairGenerator; 42 | import java.security.KeyStore; 43 | import java.security.NoSuchAlgorithmException; 44 | import java.security.cert.Certificate; 45 | import java.security.cert.X509Certificate; 46 | import java.util.Date; 47 | 48 | /** 49 | * Util class to generate SSL key pairs and certificates for test purpose. 50 | * 51 | * All generated key pairs and certificates are in memory. 52 | * 53 | * @author Lin Gao 54 | */ 55 | public class SSLKeyPairCerts { 56 | 57 | private static final String SERVER_CERT_SUBJECT = "CN=Vertx Server, OU=Middleware Runtime, O=Red Hat, C=US"; 58 | private static final String CLIENT_CERT_SUBJECT = "CN=Vertx Client, OU=Middleware Runtime, O=Red Hat, C=US"; 59 | private static final String PASSWORD = "wibble"; 60 | 61 | private JksOptions serverKeyStore; 62 | private JksOptions serverTrustStore; 63 | private JksOptions clientKeyStore; 64 | private JksOptions clientTrustStore; 65 | 66 | public SSLKeyPairCerts() { 67 | } 68 | 69 | /** 70 | * Creates 2 way SSL key pairs and certificates. 71 | * 72 | *

73 | * This will initialize 4 KeyStores in JKS type: 74 | *

    75 | *
  • server's keystore
  • 76 | *
  • server's truststore with client's certificate imported
  • 77 | *
  • client's keystore
  • 78 | *
  • client's truststore with server's certificate imported
  • 79 | *
80 | *

81 | * @return self 82 | */ 83 | public SSLKeyPairCerts createTwoWaySSL() { 84 | try { 85 | KeyPair serverRSAKeyPair = generateRSAKeyPair(2048); 86 | Certificate serverCert = generateSelfSignedCert(SERVER_CERT_SUBJECT, serverRSAKeyPair); 87 | 88 | KeyPair clientRSAKeyPair = generateRSAKeyPair(2048); 89 | Certificate clientCert = generateSelfSignedCert(CLIENT_CERT_SUBJECT, clientRSAKeyPair); 90 | 91 | KeyStore serverKeyStore = emptyJKSStore(PASSWORD); 92 | serverKeyStore.setKeyEntry("localserver", serverRSAKeyPair.getPrivate(), PASSWORD.toCharArray(), new Certificate[]{serverCert}); 93 | 94 | KeyStore serverTrustStore = emptyJKSStore(PASSWORD); 95 | serverTrustStore.setCertificateEntry("clientcert", clientCert); 96 | 97 | KeyStore clientKeyStore = emptyJKSStore(PASSWORD); 98 | clientKeyStore.setKeyEntry("localclient", clientRSAKeyPair.getPrivate(), PASSWORD.toCharArray(), new Certificate[]{clientCert}); 99 | 100 | KeyStore clientTrustStore = emptyJKSStore(PASSWORD); 101 | clientTrustStore.setCertificateEntry("servercert", serverCert); 102 | 103 | ByteArrayOutputStream serverKeyStoreOutputStream = new ByteArrayOutputStream(512); 104 | serverKeyStore.store(serverKeyStoreOutputStream, PASSWORD.toCharArray()); 105 | this.serverKeyStore = new JksOptions().setPassword(PASSWORD).setValue(Buffer.buffer(serverKeyStoreOutputStream.toByteArray())); 106 | 107 | ByteArrayOutputStream serverTrustStoreOutputStream = new ByteArrayOutputStream(512); 108 | serverTrustStore.store(serverTrustStoreOutputStream, PASSWORD.toCharArray()); 109 | this.serverTrustStore = new JksOptions().setPassword(PASSWORD).setValue(Buffer.buffer(serverTrustStoreOutputStream.toByteArray())); 110 | 111 | ByteArrayOutputStream clientKeyStoreOutputStream = new ByteArrayOutputStream(512); 112 | clientKeyStore.store(clientKeyStoreOutputStream, PASSWORD.toCharArray()); 113 | this.clientKeyStore = new JksOptions().setPassword(PASSWORD).setValue(Buffer.buffer(clientKeyStoreOutputStream.toByteArray())); 114 | 115 | ByteArrayOutputStream clientTrustStoreOutputStream = new ByteArrayOutputStream(512); 116 | clientTrustStore.store(clientTrustStoreOutputStream, PASSWORD.toCharArray()); 117 | this.clientTrustStore = new JksOptions().setPassword(PASSWORD).setValue(Buffer.buffer(clientTrustStoreOutputStream.toByteArray())); 118 | } catch (Exception e) { 119 | throw new RuntimeException("Cannot generate SSL key pairs and certificates", e); 120 | } 121 | return this; 122 | } 123 | 124 | // refer to: https://github.com/vert-x3/vertx-config/blob/4.0.0-milestone4/vertx-config-vault/src/test/java/io/vertx/config/vault/utils/Certificates.java#L149 125 | private X509Certificate generateSelfSignedCert(String certSub, KeyPair keyPair) throws Exception { 126 | final X509v3CertificateBuilder certificateBuilder = new X509v3CertificateBuilder( 127 | new org.bouncycastle.asn1.x500.X500Name(certSub), 128 | BigInteger.ONE, 129 | new Date(System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 30), 130 | new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 30)), 131 | new X500Name(certSub), 132 | SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded()) 133 | ); 134 | final GeneralNames subjectAltNames = new GeneralNames(new GeneralName(GeneralName.iPAddress, "127.0.0.1")); 135 | certificateBuilder.addExtension(org.bouncycastle.asn1.x509.Extension.subjectAlternativeName, false, subjectAltNames); 136 | 137 | final AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1WithRSAEncryption"); 138 | final AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId); 139 | final BcContentSignerBuilder signerBuilder = new BcRSAContentSignerBuilder(sigAlgId, digAlgId); 140 | final AsymmetricKeyParameter keyp = PrivateKeyFactory.createKey(keyPair.getPrivate().getEncoded()); 141 | final ContentSigner signer = signerBuilder.build(keyp); 142 | final X509CertificateHolder x509CertificateHolder = certificateBuilder.build(signer); 143 | final X509Certificate certificate = new JcaX509CertificateConverter().getCertificate(x509CertificateHolder); 144 | certificate.checkValidity(new Date()); 145 | certificate.verify(keyPair.getPublic()); 146 | return certificate; 147 | } 148 | 149 | private KeyPair generateRSAKeyPair(int keySize) throws NoSuchAlgorithmException { 150 | final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); 151 | keyPairGenerator.initialize(keySize); 152 | return keyPairGenerator.genKeyPair(); 153 | } 154 | 155 | private KeyStore emptyJKSStore(String password) throws Exception { 156 | KeyStore ks = KeyStore.getInstance("JKS"); 157 | ks.load(null, password.toCharArray()); 158 | return ks; 159 | } 160 | 161 | /** 162 | * @return the server's keystore options 163 | */ 164 | public JksOptions getServerKeyStore() { 165 | return serverKeyStore; 166 | } 167 | 168 | /** 169 | * @return the server's truststore options 170 | */ 171 | public JksOptions getServerTrustStore() { 172 | return serverTrustStore; 173 | } 174 | 175 | /** 176 | * @return the client's keystore options 177 | */ 178 | public JksOptions getClientKeyStore() { 179 | return clientKeyStore; 180 | } 181 | 182 | /** 183 | * @return the client's truststore options 184 | */ 185 | public JksOptions getClientTrustStore() { 186 | return clientTrustStore; 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/ext/eventbus/bridge/tcp/TcpEventBusBridgeEventTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Red Hat, Inc. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | package io.vertx.ext.eventbus.bridge.tcp; 17 | 18 | import io.vertx.core.Vertx; 19 | import io.vertx.core.eventbus.Message; 20 | import io.vertx.core.http.ClientAuth; 21 | import io.vertx.core.json.JsonObject; 22 | import io.vertx.core.internal.logging.Logger; 23 | import io.vertx.core.internal.logging.LoggerFactory; 24 | import io.vertx.core.net.*; 25 | import io.vertx.ext.bridge.BridgeOptions; 26 | import io.vertx.ext.bridge.PermittedOptions; 27 | import io.vertx.ext.eventbus.bridge.tcp.impl.protocol.FrameHelper; 28 | import io.vertx.ext.unit.Async; 29 | import io.vertx.ext.unit.TestContext; 30 | import io.vertx.ext.unit.junit.VertxUnitRunner; 31 | import org.junit.After; 32 | import org.junit.Before; 33 | import org.junit.Test; 34 | import org.junit.runner.RunWith; 35 | 36 | import javax.net.ssl.SSLPeerUnverifiedException; 37 | import java.security.cert.Certificate; 38 | import java.security.cert.X509Certificate; 39 | 40 | @RunWith(VertxUnitRunner.class) 41 | public class TcpEventBusBridgeEventTest { 42 | 43 | private static final Logger logger = LoggerFactory.getLogger(TcpEventBusBridgeEventTest.class); 44 | 45 | private Vertx vertx; 46 | 47 | private SSLKeyPairCerts sslKeyPairCerts; 48 | 49 | @Before 50 | public void before(TestContext context) { 51 | vertx = Vertx.vertx(); 52 | final Async async = context.async(); 53 | vertx.eventBus().consumer("hello", (Message msg) -> msg.reply(new JsonObject().put("value", "Hello " + msg.body().getString("value")))); 54 | vertx.eventBus().consumer("echo", (Message msg) -> msg.reply(msg.body())); 55 | vertx.setPeriodic(1000, __ -> vertx.eventBus().send("ping", new JsonObject().put("value", "hi"))); 56 | sslKeyPairCerts = new SSLKeyPairCerts().createTwoWaySSL(); 57 | TcpEventBusBridge bridge = TcpEventBusBridge.create( 58 | vertx, 59 | new BridgeOptions() 60 | .addInboundPermitted(new PermittedOptions().setAddress("hello")) 61 | .addInboundPermitted(new PermittedOptions().setAddress("echo")) 62 | .addInboundPermitted(new PermittedOptions().setAddress("test")) 63 | .addOutboundPermitted(new PermittedOptions().setAddress("echo")) 64 | .addOutboundPermitted(new PermittedOptions().setAddress("ping")), 65 | new NetServerOptions() 66 | .setClientAuth(ClientAuth.REQUEST) 67 | .setSsl(true) 68 | .setTrustOptions(sslKeyPairCerts.getServerTrustStore()) 69 | .setKeyCertOptions(sslKeyPairCerts.getServerKeyStore()), 70 | be -> { 71 | logger.info("Handled a bridge event " + be.getRawMessage()); 72 | if (be.socket().isSsl()) { 73 | try { 74 | for (Certificate c : be.socket().peerCertificates()) { 75 | logger.info(((X509Certificate)c).getSubjectDN().toString()); 76 | } 77 | } catch (SSLPeerUnverifiedException e) { 78 | throw new RuntimeException("Failed to get peer certificates chain", e); 79 | } 80 | } 81 | be.complete(true); 82 | }); 83 | bridge.listen(7000).onComplete(res -> { 84 | context.assertTrue(res.succeeded()); 85 | async.complete(); 86 | }); 87 | } 88 | 89 | @After 90 | public void after(TestContext context) { 91 | vertx.close().onComplete(context.asyncAssertSuccess()); 92 | } 93 | 94 | @Test 95 | public void testSendVoidMessage(TestContext context) { 96 | // Send a request and get a response 97 | NetClient client = vertx.createNetClient(new NetClientOptions() 98 | .setSsl(true) 99 | .setHostnameVerificationAlgorithm("") 100 | .setTrustOptions(sslKeyPairCerts.getClientTrustStore()) 101 | .setKeyCertOptions(sslKeyPairCerts.getClientKeyStore())); 102 | final Async async = context.async(); 103 | vertx.eventBus().consumer("test", (Message msg) -> { 104 | client.close(); 105 | async.complete(); 106 | }); 107 | client.connect(7000, "localhost").onComplete(conn -> { 108 | context.assertFalse(conn.failed()); 109 | NetSocket socket = conn.result(); 110 | FrameHelper.sendFrame("send", "test", new JsonObject().put("value", "vert.x"), socket); 111 | }); 112 | } 113 | 114 | @Test 115 | public void testSendVoidMessageTrustAll(TestContext context) { 116 | NetClient client = vertx.createNetClient(new NetClientOptions() 117 | .setSsl(true) 118 | .setTrustAll(true) 119 | .setHostnameVerificationAlgorithm("") 120 | .setKeyCertOptions(sslKeyPairCerts.getClientKeyStore()) 121 | ); 122 | final Async async = context.async(); 123 | vertx.eventBus().consumer("test", (Message msg) -> { 124 | client.close(); 125 | async.complete(); 126 | }); 127 | client.connect(7000, "localhost").onComplete(conn -> { 128 | context.assertFalse(conn.failed()); 129 | NetSocket socket = conn.result(); 130 | FrameHelper.sendFrame("send", "test", new JsonObject().put("value", "vert.x"), socket); 131 | }); 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/ext/eventbus/bridge/tcp/TcpEventBusBridgeHookTest.java: -------------------------------------------------------------------------------- 1 | package io.vertx.ext.eventbus.bridge.tcp; 2 | 3 | import io.vertx.core.Vertx; 4 | import io.vertx.core.json.JsonObject; 5 | import io.vertx.core.net.NetClient; 6 | import io.vertx.core.net.NetServerOptions; 7 | import io.vertx.core.net.NetSocket; 8 | import io.vertx.ext.bridge.BridgeEventType; 9 | import io.vertx.ext.bridge.BridgeOptions; 10 | import io.vertx.ext.bridge.PermittedOptions; 11 | import io.vertx.ext.eventbus.bridge.tcp.impl.protocol.FrameHelper; 12 | import io.vertx.ext.eventbus.bridge.tcp.impl.protocol.FrameParser; 13 | import io.vertx.ext.unit.Async; 14 | import io.vertx.ext.unit.TestContext; 15 | import io.vertx.ext.unit.junit.VertxUnitRunner; 16 | import org.junit.After; 17 | import org.junit.Before; 18 | import org.junit.Test; 19 | import org.junit.runner.RunWith; 20 | 21 | @RunWith(VertxUnitRunner.class) 22 | public class TcpEventBusBridgeHookTest { 23 | 24 | private final String address = "hello"; 25 | 26 | private Vertx vertx; 27 | 28 | @Before 29 | public void before(TestContext context) { 30 | vertx = Vertx.vertx(); 31 | } 32 | 33 | @After 34 | public void after(TestContext context) { 35 | vertx.close().onComplete(context.asyncAssertSuccess()); 36 | } 37 | 38 | @Test 39 | public void testRegister(TestContext context) { 40 | 41 | final JsonObject payload = new JsonObject().put("value", "Francesco"); 42 | 43 | // 0. Start the bridge 44 | // 1. Check if REGISTER hook is called 45 | // 2. Check if REGISTERED hook is called 46 | // 3. Try to send a message while managing REGISTERED event 47 | // 4. Check if client receives the message 48 | 49 | Async bridgeStart = context.async(); 50 | Async register = context.async(); 51 | Async registered = context.async(); 52 | Async request = context.async(); 53 | 54 | TcpEventBusBridge bridge = TcpEventBusBridge.create( 55 | vertx, 56 | new BridgeOptions() 57 | .addInboundPermitted(new PermittedOptions()) 58 | .addOutboundPermitted(new PermittedOptions()), 59 | new NetServerOptions(), 60 | be -> { 61 | if (be.type() == BridgeEventType.REGISTER) { 62 | context.assertNotNull(be.socket()); 63 | context.assertEquals(address, be.getRawMessage().getString("address")); 64 | register.complete(); 65 | } else if (be.type() == BridgeEventType.REGISTERED) { 66 | context.assertNotNull(be.socket()); 67 | context.assertEquals(address, be.getRawMessage().getString("address")); 68 | 69 | // The client should be able to receive this message 70 | vertx.eventBus().send(address, payload); 71 | 72 | registered.complete(); 73 | } 74 | be.complete(true); 75 | } 76 | ); 77 | bridge.listen(7000).onComplete(res -> { 78 | context.assertTrue(res.succeeded()); 79 | bridgeStart.complete(); 80 | }); 81 | 82 | bridgeStart.await(); 83 | 84 | NetClient client = vertx.createNetClient(); 85 | 86 | client.connect(7000, "localhost").onComplete(conn -> { 87 | context.assertFalse(conn.failed()); 88 | 89 | NetSocket socket = conn.result(); 90 | 91 | // 1 reply will arrive 92 | // MESSAGE for address 93 | final FrameParser parser = new FrameParser(parse -> { 94 | context.assertTrue(parse.succeeded()); 95 | JsonObject frame = parse.result(); 96 | 97 | context.assertNotEquals("err", frame.getString("type")); 98 | context.assertEquals("Francesco", frame.getJsonObject("body").getString("value")); 99 | client.close(); 100 | request.complete(); 101 | }); 102 | 103 | socket.handler(parser); 104 | 105 | FrameHelper.sendFrame("register", address, null, socket); 106 | }); 107 | 108 | register.await(); 109 | registered.await(); 110 | request.await(); 111 | 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/ext/eventbus/bridge/tcp/TcpEventBusBridgeInteropTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Red Hat, Inc. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | package io.vertx.ext.eventbus.bridge.tcp; 17 | 18 | import io.vertx.core.Vertx; 19 | import io.vertx.core.buffer.Buffer; 20 | import io.vertx.core.eventbus.EventBus; 21 | import io.vertx.core.eventbus.Message; 22 | import io.vertx.core.json.JsonObject; 23 | import io.vertx.core.net.NetClient; 24 | import io.vertx.core.net.NetSocket; 25 | import io.vertx.ext.bridge.BridgeOptions; 26 | import io.vertx.ext.bridge.PermittedOptions; 27 | import io.vertx.ext.eventbus.bridge.tcp.impl.protocol.FrameHelper; 28 | import io.vertx.ext.eventbus.bridge.tcp.impl.protocol.FrameParser; 29 | import io.vertx.ext.unit.Async; 30 | import io.vertx.ext.unit.TestContext; 31 | import io.vertx.ext.unit.junit.VertxUnitRunner; 32 | import org.junit.After; 33 | import org.junit.Before; 34 | import org.junit.Test; 35 | import org.junit.runner.RunWith; 36 | 37 | import java.io.DataInputStream; 38 | import java.io.DataOutputStream; 39 | import java.io.IOException; 40 | import java.net.Socket; 41 | 42 | @RunWith(VertxUnitRunner.class) 43 | public class TcpEventBusBridgeInteropTest { 44 | 45 | private Vertx vertx; 46 | 47 | @Before 48 | public void before(TestContext context) { 49 | vertx = Vertx.vertx(); 50 | final Async async = context.async(); 51 | 52 | vertx.eventBus().consumer("hello", (Message msg) -> msg.reply(new JsonObject().put("value", "Hello " + msg.body().getString("value")))); 53 | 54 | TcpEventBusBridge bridge = TcpEventBusBridge.create( 55 | vertx, 56 | new BridgeOptions() 57 | .addInboundPermitted(new PermittedOptions()) 58 | .addOutboundPermitted(new PermittedOptions())); 59 | 60 | bridge.listen(7000).onComplete(res -> { 61 | context.assertTrue(res.succeeded()); 62 | async.complete(); 63 | }); 64 | } 65 | 66 | @After 67 | public void after(TestContext context) { 68 | vertx.close().onComplete(context.asyncAssertSuccess()); 69 | } 70 | 71 | @Test 72 | public void testInteropWithPlainJava(TestContext context) { 73 | final Async async = context.async(); 74 | 75 | // register a local consumer 76 | final EventBus eb = vertx.eventBus(); 77 | 78 | eb.consumer("io.vertx", msg -> { 79 | // assert that headers are received 80 | context.assertEquals("findAll", msg.headers().get("action")); 81 | // assert body is not null 82 | context.assertNotNull(msg.body()); 83 | msg.reply(msg.body()); 84 | }); 85 | 86 | // run some plain java (blocking) 87 | vertx.executeBlocking(() -> { 88 | JsonObject headers = new JsonObject(); 89 | headers.put("action", "findAll"); 90 | 91 | JsonObject body = new JsonObject(); 92 | body.put("model", "news"); 93 | 94 | JsonObject protocol = new JsonObject(); 95 | protocol.put("type", "send"); 96 | protocol.put("headers", headers); 97 | protocol.put("body", body); 98 | protocol.put("address", "io.vertx"); 99 | protocol.put("replyAddress", "durp"); 100 | 101 | 102 | Buffer buffer = Buffer.buffer(); 103 | buffer.appendInt(protocol.encode().getBytes().length); 104 | buffer.appendString(protocol.encode()); 105 | 106 | Socket clientSocket = new Socket("localhost", 7000); 107 | 108 | DataOutputStream output = new DataOutputStream(clientSocket.getOutputStream()); 109 | output.write(buffer.getBytes()); 110 | 111 | DataInputStream input = new DataInputStream(clientSocket.getInputStream()); 112 | 113 | int bytesLength = input.readInt(); 114 | byte[] bytes = new byte[bytesLength]; 115 | for(int i = 0; i < bytesLength; i++) { 116 | bytes[i] = input.readByte(); 117 | } 118 | 119 | input.close(); 120 | output.close(); 121 | clientSocket.close(); 122 | 123 | JsonObject reply = new JsonObject(new String(bytes)); 124 | 125 | // assert that the body is the same we sent 126 | context.assertEquals(body, reply.getJsonObject("body")); 127 | 128 | return null; 129 | }).onComplete(res -> { 130 | context.assertTrue(res.succeeded()); 131 | async.complete(); 132 | }); 133 | } 134 | 135 | @Test 136 | public void testSendMessageWithReplyBacktrack(TestContext context) { 137 | // Send a request and get a response 138 | NetClient client = vertx.createNetClient(); 139 | final Async async = context.async(); 140 | 141 | client.connect(7000, "localhost").onComplete(conn -> { 142 | context.assertFalse(conn.failed()); 143 | 144 | NetSocket socket = conn.result(); 145 | 146 | final FrameParser parser = new FrameParser(parse -> { 147 | context.assertTrue(parse.succeeded()); 148 | JsonObject frame = parse.result(); 149 | context.assertNotEquals("err", frame.getString("type")); 150 | context.assertEquals("Hello vert.x", frame.getJsonObject("body").getString("value")); 151 | client.close(); 152 | async.complete(); 153 | }); 154 | 155 | socket.handler(parser); 156 | 157 | FrameHelper.sendFrame("send", "hello", "#backtrack", new JsonObject().put("value", "vert.x"), socket); 158 | }); 159 | } 160 | 161 | @Test 162 | public void testSendMessageWithDuplicateReplyID(TestContext context) { 163 | // replies must always return to the same origin 164 | 165 | NetClient client = vertx.createNetClient(); 166 | final Async async = context.async(); 167 | 168 | client.connect(7000, "localhost").onComplete(conn -> { 169 | context.assertFalse(conn.failed()); 170 | 171 | NetSocket socket = conn.result(); 172 | 173 | vertx.eventBus().consumer("third-party-receiver", msg -> context.fail()); 174 | 175 | final FrameParser parser = new FrameParser(parse -> { 176 | context.assertTrue(parse.succeeded()); 177 | client.close(); 178 | async.complete(); 179 | }); 180 | 181 | socket.handler(parser); 182 | 183 | 184 | FrameHelper.sendFrame("send", "hello", "third-party-receiver", new JsonObject().put("value", "vert.x"), socket); 185 | }); 186 | } 187 | 188 | @Test 189 | public void testRegister(TestContext context) { 190 | // Send a request and get a response 191 | NetClient client = vertx.createNetClient(); 192 | final Async async = context.async(); 193 | 194 | client.connect(7000, "localhost").onComplete(conn -> { 195 | context.assertFalse(conn.failed()); 196 | 197 | NetSocket socket = conn.result(); 198 | 199 | // 1 reply will arrive 200 | // MESSAGE for echo 201 | final FrameParser parser = new FrameParser(parse -> { 202 | context.assertTrue(parse.succeeded()); 203 | JsonObject frame = parse.result(); 204 | 205 | context.assertNotEquals("err", frame.getString("type")); 206 | context.assertEquals("Vert.x", frame.getJsonObject("body").getString("value")); 207 | client.close(); 208 | async.complete(); 209 | }); 210 | 211 | socket.handler(parser); 212 | 213 | FrameHelper.sendFrame("register", "echo", null, socket); 214 | 215 | // now try to publish a message so it gets delivered both to the consumer registred on the startup and to this 216 | // remote consumer 217 | 218 | FrameHelper.sendFrame("publish", "echo", new JsonObject().put("value", "Vert.x"), socket); 219 | }); 220 | 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/ext/eventbus/bridge/tcp/TcpEventBusBridgeTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Red Hat, Inc. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | package io.vertx.ext.eventbus.bridge.tcp; 17 | 18 | import java.util.concurrent.atomic.AtomicBoolean; 19 | 20 | import org.junit.After; 21 | import org.junit.Before; 22 | import org.junit.Test; 23 | import org.junit.runner.RunWith; 24 | import io.vertx.core.Handler; 25 | import io.vertx.core.Vertx; 26 | import io.vertx.core.eventbus.Message; 27 | import io.vertx.core.json.JsonObject; 28 | import io.vertx.core.net.NetClient; 29 | import io.vertx.core.net.NetServerOptions; 30 | import io.vertx.core.net.NetSocket; 31 | import io.vertx.ext.bridge.BridgeEventType; 32 | import io.vertx.ext.bridge.BridgeOptions; 33 | import io.vertx.ext.bridge.PermittedOptions; 34 | import io.vertx.ext.eventbus.bridge.tcp.impl.protocol.FrameHelper; 35 | import io.vertx.ext.eventbus.bridge.tcp.impl.protocol.FrameParser; 36 | import io.vertx.ext.unit.Async; 37 | import io.vertx.ext.unit.TestContext; 38 | import io.vertx.ext.unit.junit.VertxUnitRunner; 39 | 40 | @RunWith(VertxUnitRunner.class) 41 | public class TcpEventBusBridgeTest { 42 | 43 | private Vertx vertx; 44 | private volatile Handler eventHandler = event -> event.complete(true); 45 | 46 | @Before 47 | public void before(TestContext context) { 48 | vertx = Vertx.vertx(); 49 | final Async async = context.async(); 50 | 51 | vertx.eventBus().consumer("hello", (Message msg) -> msg.reply(new JsonObject().put("value", "Hello " + msg.body().getString("value")))); 52 | 53 | vertx.eventBus().consumer("echo", (Message msg) -> msg.reply(msg.body())); 54 | 55 | vertx.setPeriodic(1000, __ -> vertx.eventBus().send("ping", new JsonObject().put("value", "hi"))); 56 | 57 | TcpEventBusBridge bridge = TcpEventBusBridge.create( 58 | vertx, 59 | new BridgeOptions() 60 | .addInboundPermitted(new PermittedOptions().setAddress("hello")) 61 | .addInboundPermitted(new PermittedOptions().setAddress("echo")) 62 | .addInboundPermitted(new PermittedOptions().setAddress("test")) 63 | .addOutboundPermitted(new PermittedOptions().setAddress("echo")) 64 | .addOutboundPermitted(new PermittedOptions().setAddress("test")) 65 | .addOutboundPermitted(new PermittedOptions().setAddress("ping")), new NetServerOptions(), event -> eventHandler.handle(event)); 66 | 67 | bridge.listen(7000).onComplete(res -> { 68 | context.assertTrue(res.succeeded()); 69 | async.complete(); 70 | }); 71 | } 72 | 73 | @After 74 | public void after(TestContext context) { 75 | vertx.close().onComplete(context.asyncAssertSuccess()); 76 | } 77 | 78 | @Test 79 | public void testSendVoidMessage(TestContext context) { 80 | // Send a request and get a response 81 | NetClient client = vertx.createNetClient(); 82 | final Async async = context.async(); 83 | 84 | vertx.eventBus().consumer("test", (Message msg) -> { 85 | client.close(); 86 | async.complete(); 87 | }); 88 | 89 | client.connect(7000, "localhost").onComplete(context.asyncAssertSuccess(socket -> { 90 | FrameHelper.sendFrame("send", "test", new JsonObject().put("value", "vert.x"), socket); 91 | })); 92 | } 93 | 94 | @Test 95 | public void testSendVoidStringMessage(TestContext context) { 96 | // Send a request and get a response 97 | NetClient client = vertx.createNetClient(); 98 | final Async async = context.async(); 99 | 100 | vertx.eventBus().consumer("test", (Message msg) -> { 101 | context.assertTrue(msg.body() instanceof String); 102 | context.assertEquals("I'm not a JSON Object", msg.body()); 103 | client.close(); 104 | async.complete(); 105 | }); 106 | 107 | client.connect(7000, "localhost").onComplete(context.asyncAssertSuccess(socket -> { 108 | FrameHelper.sendFrame("send", "test", "I'm not a JSON Object", socket); 109 | })); 110 | } 111 | 112 | @Test 113 | public void testNoHandlers(TestContext context) { 114 | // Send a request and get a response 115 | NetClient client = vertx.createNetClient(); 116 | final Async async = context.async(); 117 | 118 | client.connect(7000, "localhost").onComplete(context.asyncAssertSuccess(socket -> { 119 | 120 | final FrameParser parser = new FrameParser(parse -> { 121 | context.assertTrue(parse.succeeded()); 122 | JsonObject frame = parse.result(); 123 | 124 | context.assertEquals("err", frame.getString("type")); 125 | context.assertEquals("#backtrack", frame.getString("address")); 126 | 127 | client.close(); 128 | async.complete(); 129 | }); 130 | 131 | socket.handler(parser); 132 | 133 | FrameHelper.sendFrame("send", "test", "#backtrack", new JsonObject().put("value", "vert.x"), socket); 134 | })); 135 | } 136 | 137 | @Test 138 | public void testErrorReply(TestContext context) { 139 | // Send a request and get a response 140 | NetClient client = vertx.createNetClient(); 141 | final Async async = context.async(); 142 | 143 | vertx.eventBus().consumer("test", (Message msg) -> { 144 | msg.fail(0, "oops!"); 145 | }); 146 | 147 | client.connect(7000, "localhost").onComplete(context.asyncAssertSuccess(socket -> { 148 | 149 | final FrameParser parser = new FrameParser(parse -> { 150 | context.assertTrue(parse.succeeded()); 151 | JsonObject frame = parse.result(); 152 | 153 | context.assertEquals("err", frame.getString("type")); 154 | context.assertEquals("#backtrack", frame.getString("address")); 155 | 156 | client.close(); 157 | async.complete(); 158 | }); 159 | 160 | socket.handler(parser); 161 | 162 | FrameHelper.sendFrame("send", "test", "#backtrack", new JsonObject().put("value", "vert.x"), socket); 163 | })); 164 | } 165 | 166 | @Test 167 | public void testSendsFromOtherSideOfBridge(TestContext context) { 168 | NetClient client = vertx.createNetClient(); 169 | final Async async = context.async(); 170 | 171 | client.connect(7000, "localhost").onComplete(context.asyncAssertSuccess(socket -> { 172 | 173 | final FrameParser parser = new FrameParser(parse -> { 174 | context.assertTrue(parse.succeeded()); 175 | JsonObject frame = parse.result(); 176 | context.assertNotEquals("err", frame.getString("type")); 177 | context.assertEquals(true, frame.getBoolean("send")); 178 | context.assertEquals("hi", frame.getJsonObject("body").getString("value")); 179 | client.close(); 180 | async.complete(); 181 | }); 182 | 183 | socket.handler(parser); 184 | 185 | FrameHelper.sendFrame("register", "ping", null, socket); 186 | })); 187 | 188 | } 189 | 190 | @Test 191 | public void testSendMessageWithReplyBacktrack(TestContext context) { 192 | // Send a request and get a response 193 | NetClient client = vertx.createNetClient(); 194 | final Async async = context.async(); 195 | 196 | client.connect(7000, "localhost").onComplete(context.asyncAssertSuccess(socket -> { 197 | 198 | final FrameParser parser = new FrameParser(parse -> { 199 | context.assertTrue(parse.succeeded()); 200 | JsonObject frame = parse.result(); 201 | context.assertNotEquals("err", frame.getString("type")); 202 | context.assertEquals(true, frame.getBoolean("send")); 203 | context.assertEquals("Hello vert.x", frame.getJsonObject("body").getString("value")); 204 | client.close(); 205 | async.complete(); 206 | }); 207 | 208 | socket.handler(parser); 209 | 210 | FrameHelper.sendFrame("send", "hello", "#backtrack", new JsonObject().put("value", "vert.x"), socket); 211 | })); 212 | } 213 | 214 | @Test 215 | public void testSendMessageWithReplyBacktrackTimeout(TestContext context) { 216 | // Send a request and get a response 217 | NetClient client = vertx.createNetClient(); 218 | final Async async = context.async(); 219 | 220 | // This does not reply and will provoke a timeout 221 | vertx.eventBus().consumer("test", (Message msg) -> { /* Nothing! */ } ); 222 | 223 | client.connect(7000, "localhost").onComplete(context.asyncAssertSuccess(socket -> { 224 | 225 | final FrameParser parser = new FrameParser(parse -> { 226 | context.assertTrue(parse.succeeded()); 227 | JsonObject frame = parse.result(); 228 | context.assertEquals("err", frame.getString("type")); 229 | context.assertEquals("TIMEOUT", frame.getString("failureType")); 230 | context.assertEquals(-1, frame.getInteger("failureCode")); 231 | context.assertEquals("#backtrack", frame.getString("address")); 232 | client.close(); 233 | async.complete(); 234 | }); 235 | 236 | socket.handler(parser); 237 | 238 | JsonObject headers = new JsonObject().put("timeout", 100L); 239 | 240 | FrameHelper.sendFrame("send", "test", "#backtrack", headers, null, new JsonObject().put("value", "vert.x"), socket); 241 | })); 242 | } 243 | 244 | @Test 245 | public void testSendMessageWithDuplicateReplyID(TestContext context) { 246 | // replies must always return to the same origin 247 | 248 | NetClient client = vertx.createNetClient(); 249 | final Async async = context.async(); 250 | 251 | client.connect(7000, "localhost").onComplete(context.asyncAssertSuccess(socket -> { 252 | 253 | vertx.eventBus().consumer("third-party-receiver", msg -> context.fail()); 254 | 255 | final FrameParser parser = new FrameParser(parse -> { 256 | context.assertTrue(parse.succeeded()); 257 | client.close(); 258 | async.complete(); 259 | }); 260 | 261 | socket.handler(parser); 262 | 263 | 264 | FrameHelper.sendFrame("send", "hello", "third-party-receiver", new JsonObject().put("value", "vert.x"), socket); 265 | })); 266 | } 267 | 268 | @Test 269 | public void testRegister(TestContext context) { 270 | // Send a request and get a response 271 | NetClient client = vertx.createNetClient(); 272 | final Async async = context.async(); 273 | 274 | client.connect(7000, "localhost").onComplete(context.asyncAssertSuccess(socket -> { 275 | 276 | // 1 reply will arrive 277 | // MESSAGE for echo 278 | final FrameParser parser = new FrameParser(parse -> { 279 | context.assertTrue(parse.succeeded()); 280 | JsonObject frame = parse.result(); 281 | 282 | context.assertNotEquals("err", frame.getString("type")); 283 | context.assertEquals(false, frame.getBoolean("send")); 284 | context.assertEquals("Vert.x", frame.getJsonObject("body").getString("value")); 285 | client.close(); 286 | async.complete(); 287 | }); 288 | 289 | socket.handler(parser); 290 | 291 | FrameHelper.sendFrame("register", "echo", null, socket); 292 | 293 | // now try to publish a message so it gets delivered both to the consumer registred on the startup and to this 294 | // remote consumer 295 | 296 | FrameHelper.sendFrame("publish", "echo", new JsonObject().put("value", "Vert.x"), socket); 297 | })); 298 | 299 | } 300 | 301 | @Test 302 | public void testUnRegister(TestContext context) { 303 | // Send a request and get a response 304 | NetClient client = vertx.createNetClient(); 305 | final Async async = context.async(); 306 | 307 | final String address = "test"; 308 | client.connect(7000, "localhost").onComplete(context.asyncAssertSuccess(socket -> { 309 | 310 | // 2 replies will arrive: 311 | // 1). message published to test 312 | // 2). err of NO_HANDLERS because of consumer for 'test' is unregistered. 313 | final AtomicBoolean unregistered = new AtomicBoolean(false); 314 | final FrameParser parser = new FrameParser(parse -> { 315 | context.assertTrue(parse.succeeded()); 316 | JsonObject frame = parse.result(); 317 | if (unregistered.get()) { 318 | // consumer on 'test' has been unregistered, send message will fail. 319 | context.assertEquals("err", frame.getString("type")); 320 | context.assertEquals("#backtrack", frame.getString("address")); 321 | context.assertEquals("NO_HANDLERS", frame.getString("failureType")); 322 | context.assertEquals("No handlers for address test", frame.getString("message")); 323 | client.close(); 324 | async.complete(); 325 | } else { 326 | // got message, then unregister the handler 327 | context.assertNotEquals("err", frame.getString("type")); 328 | context.assertEquals(false, frame.getBoolean("send")); 329 | context.assertEquals("Vert.x", frame.getJsonObject("body").getString("value")); 330 | unregistered.compareAndSet(false, true); 331 | FrameHelper.sendFrame("unregister", address, null, socket); 332 | FrameHelper.sendFrame("send", address, "#backtrack", new JsonObject().put("value", "This will fail anyway!"), socket); 333 | } 334 | }); 335 | 336 | socket.handler(parser); 337 | 338 | FrameHelper.sendFrame("register", address, null, socket); 339 | FrameHelper.sendFrame("publish", address, new JsonObject().put("value", "Vert.x"), socket); 340 | })); 341 | 342 | } 343 | 344 | @Test 345 | public void testReplyFromClient(TestContext context) { 346 | // Send a request from java and get a response from the client 347 | NetClient client = vertx.createNetClient(); 348 | final Async async = context.async(); 349 | final String address = "test"; 350 | client.connect(7000, "localhost").onComplete(context.asyncAssertSuccess(socket -> { 351 | 352 | final FrameParser parser = new FrameParser(parse -> { 353 | context.assertTrue(parse.succeeded()); 354 | JsonObject frame = parse.result(); 355 | if ("message".equals(frame.getString("type"))) { 356 | context.assertEquals(true, frame.getBoolean("send")); 357 | context.assertEquals("Vert.x", frame.getJsonObject("body").getString("value")); 358 | FrameHelper.sendFrame("send", frame.getString("replyAddress"), new JsonObject().put("value", "You got it"), socket); 359 | } 360 | }); 361 | 362 | socket.handler(parser); 363 | 364 | FrameHelper.sendFrame("register", address, null, socket); 365 | 366 | // There is now way to know that the register actually happened, wait a bit before sending. 367 | vertx.setTimer( 500L, timerId -> { 368 | vertx.eventBus().request(address, new JsonObject().put("value", "Vert.x")).onComplete(respMessage -> { 369 | context.assertTrue(respMessage.succeeded()); 370 | context.assertEquals("You got it", respMessage.result().body().getString("value")); 371 | client.close(); 372 | async.complete(); 373 | }); 374 | }); 375 | 376 | })); 377 | 378 | } 379 | 380 | @Test 381 | public void testReplyStringMessageFromClient(TestContext context) { 382 | // Send a request from java and get a response from the client 383 | NetClient client = vertx.createNetClient(); 384 | final Async async = context.async(); 385 | final String address = "test"; 386 | client.connect(7000, "localhost").onComplete(context.asyncAssertSuccess(socket -> { 387 | 388 | final FrameParser parser = new FrameParser(parse -> { 389 | context.assertTrue(parse.succeeded()); 390 | JsonObject frame = parse.result(); 391 | if ("message".equals(frame.getString("type"))) { 392 | context.assertEquals(true, frame.getBoolean("send")); 393 | context.assertEquals("Vert.x", frame.getJsonObject("body").getString("value")); 394 | FrameHelper.sendFrame("send", frame.getString("replyAddress"), "You got it", socket); 395 | } 396 | }); 397 | 398 | socket.handler(parser); 399 | 400 | FrameHelper.sendFrame("register", address, null, socket); 401 | 402 | // There is now way to know that the register actually happened, wait a bit before sending. 403 | vertx.setTimer( 500L, timerId -> { 404 | vertx.eventBus().request(address, new JsonObject().put("value", "Vert.x")).onComplete(respMessage -> { 405 | context.assertTrue(respMessage.succeeded()); 406 | context.assertEquals("You got it", respMessage.result().body()); 407 | client.close(); 408 | async.complete(); 409 | }); 410 | }); 411 | 412 | })); 413 | 414 | } 415 | 416 | @Test 417 | public void testFailFromClient(TestContext context) { 418 | // Send a request from java and get a response from the client 419 | NetClient client = vertx.createNetClient(); 420 | final Async async = context.async(); 421 | final String address = "test"; 422 | client.connect(7000, "localhost").onComplete(context.asyncAssertSuccess(socket -> { 423 | 424 | final FrameParser parser = new FrameParser(parse -> { 425 | context.assertTrue(parse.succeeded()); 426 | JsonObject frame = parse.result(); 427 | if ("message".equals(frame.getString("type"))) { 428 | context.assertEquals(true, frame.getBoolean("send")); 429 | context.assertEquals("Vert.x", frame.getJsonObject("body").getString("value")); 430 | FrameHelper.writeFrame(new JsonObject().put("type","send").put("address",frame.getString("replyAddress")).put("failureCode", 1234).put("message", "ooops!"), socket); 431 | } 432 | }); 433 | 434 | socket.handler(parser); 435 | 436 | FrameHelper.sendFrame("register", address, null, socket); 437 | 438 | // There is now way to know that the register actually happened, wait a bit before sending. 439 | vertx.setTimer( 500L, timerId -> { 440 | vertx.eventBus().request(address, new JsonObject().put("value", "Vert.x")).onComplete(respMessage -> { 441 | context.assertTrue(respMessage.failed()); 442 | context.assertEquals("ooops!", respMessage.cause().getMessage()); 443 | client.close(); 444 | async.complete(); 445 | }); 446 | }); 447 | 448 | })); 449 | 450 | } 451 | 452 | @Test 453 | public void testSendPing(TestContext context) { 454 | NetClient client = vertx.createNetClient(); 455 | final Async async = context.async(); 456 | // MESSAGE for ping 457 | final FrameParser parser = new FrameParser(parse -> { 458 | context.assertTrue(parse.succeeded()); 459 | JsonObject frame = parse.result(); 460 | context.assertEquals("pong", frame.getString("type")); 461 | client.close(); 462 | async.complete(); 463 | }); 464 | client.connect(7000, "localhost").onComplete(context.asyncAssertSuccess(socket -> { 465 | socket.handler(parser); 466 | FrameHelper.sendFrame("register", "echo", null, socket); 467 | FrameHelper.sendFrame("ping", socket); 468 | })); 469 | } 470 | 471 | @Test 472 | public void testNoAddress(TestContext context) { 473 | NetClient client = vertx.createNetClient(); 474 | final Async async = context.async(); 475 | final AtomicBoolean errorOnce = new AtomicBoolean(false); 476 | final FrameParser parser = new FrameParser(parse -> { 477 | context.assertTrue(parse.succeeded()); 478 | JsonObject frame = parse.result(); 479 | if (!errorOnce.compareAndSet(false, true)) { 480 | context.fail("Client gets error message twice!"); 481 | } else { 482 | context.assertEquals("err", frame.getString("type")); 483 | context.assertEquals("missing_address", frame.getString("message")); 484 | vertx.setTimer(200, l -> { 485 | client.close(); 486 | async.complete(); 487 | }); 488 | } 489 | }); 490 | client.connect(7000, "localhost").onComplete(context.asyncAssertSuccess(socket -> { 491 | socket.handler(parser); 492 | FrameHelper.sendFrame("send", socket); 493 | })); 494 | } 495 | 496 | } 497 | -------------------------------------------------------------------------------- /src/test/java/io/vertx/ext/eventbus/bridge/tcp/UnixDomainSocketTest.java: -------------------------------------------------------------------------------- 1 | package io.vertx.ext.eventbus.bridge.tcp; 2 | 3 | import io.vertx.core.Vertx; 4 | import io.vertx.core.internal.VertxInternal; 5 | import io.vertx.core.json.JsonObject; 6 | import io.vertx.core.net.NetClient; 7 | import io.vertx.core.net.NetSocket; 8 | import io.vertx.core.net.SocketAddress; 9 | import io.vertx.ext.bridge.BridgeOptions; 10 | import io.vertx.ext.bridge.PermittedOptions; 11 | import io.vertx.ext.eventbus.bridge.tcp.impl.protocol.FrameHelper; 12 | import io.vertx.ext.eventbus.bridge.tcp.impl.protocol.FrameParser; 13 | import io.vertx.ext.unit.Async; 14 | import io.vertx.ext.unit.TestContext; 15 | import io.vertx.ext.unit.junit.VertxUnitRunner; 16 | import org.junit.After; 17 | import org.junit.Before; 18 | import org.junit.Rule; 19 | import org.junit.Test; 20 | import org.junit.rules.TemporaryFolder; 21 | import org.junit.runner.RunWith; 22 | 23 | import java.io.File; 24 | 25 | import static org.junit.Assume.assumeTrue; 26 | 27 | @RunWith(VertxUnitRunner.class) 28 | public class UnixDomainSocketTest { 29 | 30 | @Rule 31 | public TemporaryFolder tmp = new TemporaryFolder(); 32 | 33 | private VertxInternal vertx; 34 | private SocketAddress domainSocketAddress; 35 | 36 | @Before 37 | public void before(TestContext context) throws Exception { 38 | vertx = (VertxInternal) Vertx.vertx(); 39 | assumeTrue("Domain sockets not supported on this platform", vertx.transport().supportsDomainSockets()); 40 | 41 | domainSocketAddress = SocketAddress.domainSocketAddress(new File(tmp.newFolder(), "bridge.sock").getAbsolutePath()); 42 | 43 | Async async = context.async(); 44 | 45 | TcpEventBusBridge bridge = TcpEventBusBridge.create( 46 | vertx, 47 | new BridgeOptions() 48 | .addInboundPermitted(new PermittedOptions()) 49 | .addOutboundPermitted(new PermittedOptions())); 50 | 51 | bridge.listen(domainSocketAddress).onComplete(res -> { 52 | context.assertTrue(res.succeeded()); 53 | async.complete(); 54 | }); 55 | } 56 | 57 | @After 58 | public void after(TestContext context) { 59 | vertx.close().onComplete(context.asyncAssertSuccess()); 60 | } 61 | 62 | @Test 63 | public void testRegister(TestContext context) { 64 | // Send a request and get a response 65 | NetClient client = vertx.createNetClient(); 66 | final Async async = context.async(); 67 | 68 | client.connect(domainSocketAddress).onComplete(conn -> { 69 | context.assertFalse(conn.failed()); 70 | 71 | NetSocket socket = conn.result(); 72 | 73 | // 1 reply will arrive 74 | // MESSAGE for echo 75 | final FrameParser parser = new FrameParser(parse -> { 76 | context.assertTrue(parse.succeeded()); 77 | JsonObject frame = parse.result(); 78 | 79 | context.assertNotEquals("err", frame.getString("type")); 80 | context.assertEquals("Vert.x", frame.getJsonObject("body").getString("value")); 81 | client.close(); 82 | async.complete(); 83 | }); 84 | 85 | socket.handler(parser); 86 | 87 | FrameHelper.sendFrame("register", "echo", null, socket); 88 | 89 | // now try to publish a message so it gets delivered both to the consumer registred on the startup and to this 90 | // remote consumer 91 | 92 | FrameHelper.sendFrame("publish", "echo", new JsonObject().put("value", "Vert.x"), socket); 93 | }); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/test/resources/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vert-x3/vertx-tcp-eventbus-bridge/483f731d9fed28ac0fb5a4c6a4fcf0ec36e17e71/src/test/resources/.gitkeep --------------------------------------------------------------------------------