├── .devcontainer ├── Dockerfile ├── devcontainer.json └── setup.sh ├── .github ├── actions-rs │ └── grcov.yml └── workflows │ ├── grcov.yml │ └── retty.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── codecov.yml ├── docs ├── retty.io.jpg └── retty.io.png ├── examples ├── chat_server_tcp.rs ├── chat_server_udp.rs ├── client_tcp.rs ├── client_udp.rs ├── echo_server_tcp.rs └── echo_server_udp.rs ├── src ├── bootstrap │ ├── bootstrap_tcp │ │ ├── bootstrap_tcp_client.rs │ │ ├── bootstrap_tcp_server.rs │ │ └── mod.rs │ ├── bootstrap_udp │ │ ├── bootstrap_udp_client.rs │ │ ├── bootstrap_udp_server.rs │ │ └── mod.rs │ └── mod.rs ├── channel │ ├── handler.rs │ ├── handler_internal.rs │ ├── mod.rs │ ├── pipeline.rs │ └── pipeline_internal.rs ├── codec │ ├── byte_to_message_decoder │ │ ├── line_based_frame_decoder.rs │ │ └── mod.rs │ ├── mod.rs │ └── string_codec │ │ └── mod.rs ├── executor │ └── mod.rs ├── lib.rs └── transport │ └── mod.rs └── tests └── echo_test.rs /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | WORKDIR /home/ 4 | 5 | COPY . . 6 | 7 | RUN bash ./setup.sh 8 | 9 | ENV PATH="/root/.cargo/bin:$PATH" 10 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Codespaces Rust Starter", 3 | "extensions": [ 4 | "cschleiden.vscode-github-actions", 5 | "ms-vsliveshare.vsliveshare", 6 | "matklad.rust-analyzer", 7 | "serayuzgur.crates", 8 | "vadimcn.vscode-lldb" 9 | ], 10 | "dockerFile": "Dockerfile", 11 | "settings": { 12 | "editor.formatOnSave": true, 13 | "terminal.integrated.shell.linux": "/usr/bin/zsh", 14 | "files.exclude": { 15 | "**/CODE_OF_CONDUCT.md": true, 16 | "**/LICENSE": true 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /.devcontainer/setup.sh: -------------------------------------------------------------------------------- 1 | ## update and install some things we should probably have 2 | apt-get update 3 | apt-get install -y \ 4 | curl \ 5 | git \ 6 | gnupg2 \ 7 | jq \ 8 | sudo \ 9 | zsh \ 10 | vim \ 11 | build-essential \ 12 | openssl 13 | 14 | ## Install rustup and common components 15 | curl https://sh.rustup.rs -sSf | sh -s -- -y 16 | rustup install nightly 17 | rustup component add rustfmt 18 | rustup component add rustfmt --toolchain nightly 19 | rustup component add clippy 20 | rustup component add clippy --toolchain nightly 21 | 22 | cargo install cargo-expand 23 | cargo install cargo-edit 24 | 25 | ## setup and install oh-my-zsh 26 | sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)" 27 | cp -R /root/.oh-my-zsh /home/$USERNAME 28 | cp /root/.zshrc /home/$USERNAME 29 | sed -i -e "s/\/root\/.oh-my-zsh/\/home\/$USERNAME\/.oh-my-zsh/g" /home/$USERNAME/.zshrc 30 | chown -R $USER_UID:$USER_GID /home/$USERNAME/.oh-my-zsh /home/$USERNAME/.zshrc 31 | -------------------------------------------------------------------------------- /.github/actions-rs/grcov.yml: -------------------------------------------------------------------------------- 1 | branch: true 2 | ignore-not-existing: true 3 | llvm: true 4 | filter: covered 5 | output-type: lcov 6 | output-path: ./lcov.info 7 | source-dir: . 8 | ignore: 9 | - "/*" 10 | - "C:/*" 11 | - "../*" 12 | excl-line: "#\\[derive\\(" 13 | excl-start: "mod tests \\{" 14 | excl-br-line: "#\\[derive\\(" 15 | excl-br-start: "mod tests \\{" -------------------------------------------------------------------------------- /.github/workflows/grcov.yml: -------------------------------------------------------------------------------- 1 | name: coverage 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | grcov: 14 | name: Coverage 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | matrix: 18 | os: 19 | - ubuntu-latest 20 | toolchain: 21 | - nightly 22 | steps: 23 | - name: Checkout source code 24 | uses: actions/checkout@v2 25 | 26 | - name: Install Rust 27 | uses: actions-rs/toolchain@v1 28 | with: 29 | profile: minimal 30 | toolchain: ${{ matrix.toolchain }} 31 | override: true 32 | 33 | - name: Install grcov 34 | uses: actions-rs/install@v0.1 35 | with: 36 | crate: grcov 37 | version: latest 38 | use-tool-cache: true 39 | 40 | - name: Test 41 | uses: actions-rs/cargo@v1 42 | with: 43 | command: test 44 | args: --all --no-fail-fast ${{ matrix.cargo_flags }} 45 | env: 46 | CARGO_INCREMENTAL: "0" 47 | RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort -Cdebug-assertions=off' 48 | RUSTDOCFLAGS: '-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort -Cdebug-assertions=off' 49 | 50 | - name: Generate coverage data 51 | id: grcov 52 | # uses: actions-rs/grcov@v0.1 53 | run: | 54 | grcov target/debug/ \ 55 | --branch \ 56 | --llvm \ 57 | --source-dir . \ 58 | --output-path lcov.info \ 59 | --ignore='/**' \ 60 | --ignore='C:/**' \ 61 | --ignore='../**' \ 62 | --ignore-not-existing \ 63 | --excl-line "#\\[derive\\(" \ 64 | --excl-br-line "#\\[derive\\(" \ 65 | --excl-start "#\\[cfg\\(test\\)\\]" \ 66 | --excl-br-start "#\\[cfg\\(test\\)\\]" \ 67 | --commit-sha ${{ github.sha }} \ 68 | --service-job-id ${{ github.job }} \ 69 | --service-name "GitHub Actions" \ 70 | --service-number ${{ github.run_id }} 71 | - name: Upload coverage as artifact 72 | uses: actions/upload-artifact@v2 73 | with: 74 | name: lcov.info 75 | # path: ${{ steps.grcov.outputs.report }} 76 | path: lcov.info 77 | 78 | - name: Upload coverage to codecov.io 79 | uses: codecov/codecov-action@v1 80 | with: 81 | # file: ${{ steps.grcov.outputs.report }} 82 | file: lcov.info 83 | fail_ci_if_error: true 84 | -------------------------------------------------------------------------------- /.github/workflows/retty.yml: -------------------------------------------------------------------------------- 1 | name: retty 2 | 3 | on: 4 | push: 5 | branches: master 6 | pull_request: 7 | branches: master 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | name: Build and test 15 | strategy: 16 | matrix: 17 | os: [ 'ubuntu-latest', 'macos-latest', 'windows-latest' ] 18 | runs-on: ${{ matrix.os }} 19 | steps: 20 | - uses: actions/checkout@v2 21 | - name: Build 22 | run: cargo build --verbose 23 | - name: Run tests 24 | run: cargo test --verbose 25 | 26 | rustfmt_and_clippy: 27 | name: Check rustfmt style && run clippy 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@v2 31 | - uses: actions-rs/toolchain@v1 32 | with: 33 | toolchain: 1.76.0 34 | profile: minimal 35 | components: clippy, rustfmt 36 | override: true 37 | - name: Cache cargo registry 38 | uses: actions/cache@v1 39 | with: 40 | path: ~/.cargo/registry 41 | key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} 42 | - name: Run clippy 43 | uses: actions-rs/cargo@v1 44 | with: 45 | command: clippy 46 | - name: Check formating 47 | uses: actions-rs/cargo@v1 48 | with: 49 | command: fmt 50 | args: --all -- --check 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | /.idea/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "retty" 3 | version = "0.29.0" 4 | authors = ["Rusty Rain "] 5 | edition = "2021" 6 | description = "Retty — an asynchronous Rust networking framework that makes it easy to build protocols, application clients/servers." 7 | license = "MIT/Apache-2.0" 8 | documentation = "https://docs.rs/retty" 9 | repository = "https://github.com/retty-io/retty" 10 | homepage = "https://retty.io" 11 | keywords = ["networking", "protocols"] 12 | categories = ["network-programming", "asynchronous"] 13 | 14 | [dependencies] 15 | bytes = "1.5.0" 16 | log = "0.4.21" 17 | waitgroup = "0.1.2" 18 | smol = "2.0.0" 19 | async-net = "2.0.0" 20 | scoped-tls = "1.0.1" 21 | async-broadcast = "0.7.0" 22 | futures-lite = "2.2.0" 23 | tokio = { version = "1.36.0", default-features = false, features = ["macros"] } 24 | async-transport = { version = "0.5.0", default-features = false, features = ["runtime-smol"] } 25 | core_affinity = "0.8.1" 26 | 27 | [dev-dependencies] 28 | chrono = "0.4.35" 29 | env_logger = "0.11.3" 30 | clap = { version = "4.5.2", features = ["derive"] } 31 | anyhow = "1.0.80" 32 | ctrlc = "3.4.2" 33 | futures = "0.3.30" 34 | local-sync = "0.1.1" 35 | 36 | [[example]] 37 | name = "chat_server_tcp" 38 | path = "examples/chat_server_tcp.rs" 39 | 40 | [[example]] 41 | name = "chat_server_udp" 42 | path = "examples/chat_server_udp.rs" 43 | 44 | [[example]] 45 | name = "client_tcp" 46 | path = "examples/client_tcp.rs" 47 | 48 | [[example]] 49 | name = "client_udp" 50 | path = "examples/client_udp.rs" 51 | 52 | [[example]] 53 | name = "echo_server_tcp" 54 | path = "examples/echo_server_tcp.rs" 55 | 56 | [[example]] 57 | name = "echo_server_udp" 58 | path = "examples/echo_server_udp.rs" 59 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 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. -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Retty.io 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Retty 3 |
4 |

5 |

6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | License: MIT/Apache 2.0 23 | 24 |

25 |

26 | Retty is an asynchronous Rust networking framework that makes it easy to build protocols, application clients/servers. 27 |

28 |

29 | It's like Netty or Wangle, but in Rust. 30 |

31 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | require_ci_to_pass: yes 3 | max_report_age: off 4 | token: 29ae73e1-d93d-4363-9144-c8e534b86aa6 5 | 6 | coverage: 7 | precision: 2 8 | round: down 9 | range: 50..90 10 | status: 11 | project: 12 | default: 13 | enabled: no 14 | threshold: 0.2 15 | if_not_found: success 16 | patch: 17 | default: 18 | enabled: no 19 | if_not_found: success 20 | changes: 21 | default: 22 | enabled: no 23 | if_not_found: success 24 | -------------------------------------------------------------------------------- /docs/retty.io.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/retty-io/retty/657d90db9a383604f23152ae896c7603dae3795b/docs/retty.io.jpg -------------------------------------------------------------------------------- /docs/retty.io.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/retty-io/retty/657d90db9a383604f23152ae896c7603dae3795b/docs/retty.io.png -------------------------------------------------------------------------------- /examples/chat_server_tcp.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use std::{ 3 | cell::RefCell, collections::HashMap, io::Write, net::SocketAddr, rc::Rc, rc::Weak, 4 | str::FromStr, time::Instant, 5 | }; 6 | 7 | use retty::bootstrap::BootstrapTcpServer; 8 | use retty::channel::{Context, Handler, OutboundPipeline, Pipeline}; 9 | use retty::codec::{ 10 | byte_to_message_decoder::{LineBasedFrameDecoder, TaggedByteToMessageCodec, TerminatorType}, 11 | string_codec::TaggedStringCodec, 12 | }; 13 | use retty::executor::LocalExecutorBuilder; 14 | use retty::transport::{TaggedBytesMut, TaggedString, TransportContext}; 15 | 16 | //////////////////////////////////////////////////////////////////////////////////////////////////// 17 | struct Shared { 18 | peers: HashMap>>, 19 | } 20 | 21 | impl Shared { 22 | /// Create a new, empty, instance of `Shared`. 23 | fn new() -> Self { 24 | Shared { 25 | peers: HashMap::new(), 26 | } 27 | } 28 | 29 | fn contains(&self, peer: &SocketAddr) -> bool { 30 | self.peers.contains_key(peer) 31 | } 32 | 33 | fn join( 34 | &mut self, 35 | peer: SocketAddr, 36 | pipeline: Weak>, 37 | ) { 38 | println!("{} joined", peer); 39 | self.peers.insert(peer, pipeline); 40 | } 41 | 42 | fn leave(&mut self, peer: &SocketAddr) { 43 | println!("{} left", peer); 44 | self.peers.remove(peer); 45 | } 46 | 47 | /// Send message to every peer, except for the sender. 48 | fn broadcast(&self, sender: SocketAddr, msg: TaggedString) { 49 | print!("broadcast message: {}", msg.message); 50 | for (peer, pipeline) in self.peers.iter() { 51 | if *peer != sender { 52 | if let Some(pipeline) = pipeline.upgrade() { 53 | let _ = pipeline.write(TaggedString { 54 | now: msg.now, 55 | transport: TransportContext { 56 | local_addr: msg.transport.local_addr, 57 | peer_addr: *peer, 58 | ecn: msg.transport.ecn, 59 | protocol: msg.transport.protocol, 60 | }, 61 | message: msg.message.clone(), 62 | }); 63 | } 64 | } 65 | } 66 | } 67 | } 68 | 69 | //////////////////////////////////////////////////////////////////////////////////////////////////// 70 | struct ChatHandler { 71 | state: Rc>, 72 | pipeline: Weak>, 73 | peer_addr: Option, 74 | } 75 | 76 | impl ChatHandler { 77 | fn new( 78 | state: Rc>, 79 | pipeline: Weak>, 80 | ) -> Self { 81 | ChatHandler { 82 | state, 83 | pipeline, 84 | peer_addr: None, 85 | } 86 | } 87 | } 88 | 89 | impl Handler for ChatHandler { 90 | type Rin = TaggedString; 91 | type Rout = Self::Rin; 92 | type Win = TaggedString; 93 | type Wout = Self::Win; 94 | 95 | fn name(&self) -> &str { 96 | "ChatHandler" 97 | } 98 | 99 | fn handle_read( 100 | &mut self, 101 | _ctx: &Context, 102 | msg: Self::Rin, 103 | ) { 104 | let peer_addr = msg.transport.peer_addr; 105 | println!( 106 | "received: {} from {:?} to {}", 107 | msg.message, peer_addr, msg.transport.local_addr 108 | ); 109 | 110 | let mut s = self.state.borrow_mut(); 111 | if !s.contains(&peer_addr) { 112 | s.join(peer_addr, self.pipeline.clone()); 113 | self.peer_addr = Some(peer_addr); 114 | } 115 | s.broadcast( 116 | peer_addr, 117 | TaggedString { 118 | now: Instant::now(), 119 | transport: TransportContext { 120 | local_addr: msg.transport.local_addr, 121 | ecn: msg.transport.ecn, 122 | ..Default::default() 123 | }, 124 | message: format!("{}\r\n", msg.message), 125 | }, 126 | ); 127 | } 128 | fn handle_read_eof(&mut self, ctx: &Context) { 129 | // first leave itself from state, otherwise, it may still receive message from broadcast, 130 | // which may cause data racing. 131 | if let Some(peer_addr) = self.peer_addr { 132 | let mut s = self.state.borrow_mut(); 133 | s.leave(&peer_addr); 134 | } 135 | ctx.fire_close(); 136 | } 137 | fn poll_timeout( 138 | &mut self, 139 | _ctx: &Context, 140 | _eto: &mut Instant, 141 | ) { 142 | //last handler, no need to fire_poll_timeout 143 | } 144 | 145 | fn poll_write( 146 | &mut self, 147 | ctx: &Context, 148 | ) -> Option { 149 | ctx.fire_poll_write() 150 | } 151 | } 152 | 153 | #[derive(Parser)] 154 | #[command(name = "Chat Server TCP")] 155 | #[command(author = "Rusty Rain ")] 156 | #[command(version = "0.1.0")] 157 | #[command(about = "An example of chat server tcp", long_about = None)] 158 | struct Cli { 159 | #[arg(short, long)] 160 | debug: bool, 161 | #[arg(long, default_value_t = format!("0.0.0.0"))] 162 | host: String, 163 | #[arg(long, default_value_t = 8080)] 164 | port: u16, 165 | #[arg(long, default_value_t = format!("INFO"))] 166 | log_level: String, 167 | } 168 | 169 | fn main() -> anyhow::Result<()> { 170 | let cli = Cli::parse(); 171 | let host = cli.host; 172 | let port = cli.port; 173 | let log_level = log::LevelFilter::from_str(&cli.log_level)?; 174 | if cli.debug { 175 | env_logger::Builder::new() 176 | .format(|buf, record| { 177 | writeln!( 178 | buf, 179 | "{}:{} [{}] {} - {}", 180 | record.file().unwrap_or("unknown"), 181 | record.line().unwrap_or(0), 182 | record.level(), 183 | chrono::Local::now().format("%H:%M:%S.%6f"), 184 | record.args() 185 | ) 186 | }) 187 | .filter(None, log_level) 188 | .init(); 189 | } 190 | 191 | println!("listening {}:{}...", host, port); 192 | 193 | LocalExecutorBuilder::default().run(async move { 194 | // Create the shared state. This is how all the peers communicate. 195 | // The server task will hold a handle to this. For every new client, the 196 | // `state` handle is cloned and passed into the handler that processes the 197 | // client connection. 198 | let state = Rc::new(RefCell::new(Shared::new())); 199 | 200 | let mut bootstrap = BootstrapTcpServer::new(); 201 | bootstrap.pipeline(Box::new(move || { 202 | let pipeline: Rc> = Rc::new(Pipeline::new()); 203 | 204 | let line_based_frame_decoder_handler = TaggedByteToMessageCodec::new(Box::new( 205 | LineBasedFrameDecoder::new(8192, true, TerminatorType::BOTH), 206 | )); 207 | let string_codec_handler = TaggedStringCodec::new(); 208 | let pipeline_wr = Rc::downgrade(&pipeline); 209 | let chat_handler = ChatHandler::new(state.clone(), pipeline_wr); 210 | 211 | pipeline.add_back(line_based_frame_decoder_handler); 212 | pipeline.add_back(string_codec_handler); 213 | pipeline.add_back(chat_handler); 214 | pipeline.update() 215 | })); 216 | 217 | bootstrap.bind(format!("{}:{}", host, port)).await.unwrap(); 218 | 219 | println!("Press ctrl-c to stop"); 220 | println!("try `nc {} {}` in another shell", host, port); 221 | let (tx, rx) = futures::channel::oneshot::channel(); 222 | std::thread::spawn(move || { 223 | let mut tx = Some(tx); 224 | ctrlc::set_handler(move || { 225 | if let Some(tx) = tx.take() { 226 | let _ = tx.send(()); 227 | } 228 | }) 229 | .expect("Error setting Ctrl-C handler"); 230 | }); 231 | let _ = rx.await; 232 | 233 | bootstrap.graceful_stop().await; 234 | }); 235 | 236 | Ok(()) 237 | } 238 | -------------------------------------------------------------------------------- /examples/chat_server_udp.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use std::{ 3 | cell::RefCell, collections::HashMap, io::Write, net::SocketAddr, rc::Rc, rc::Weak, 4 | str::FromStr, time::Instant, 5 | }; 6 | 7 | use retty::bootstrap::BootstrapUdpServer; 8 | use retty::channel::{Context, Handler, OutboundPipeline, Pipeline}; 9 | use retty::codec::{ 10 | byte_to_message_decoder::{LineBasedFrameDecoder, TaggedByteToMessageCodec, TerminatorType}, 11 | string_codec::TaggedStringCodec, 12 | }; 13 | use retty::executor::LocalExecutorBuilder; 14 | use retty::transport::{TaggedBytesMut, TaggedString, TransportContext}; 15 | 16 | //////////////////////////////////////////////////////////////////////////////////////////////////// 17 | struct Shared { 18 | peers: HashMap>>, 19 | } 20 | 21 | impl Shared { 22 | /// Create a new, empty, instance of `Shared`. 23 | fn new() -> Self { 24 | Shared { 25 | peers: HashMap::new(), 26 | } 27 | } 28 | 29 | fn contains(&self, peer: &SocketAddr) -> bool { 30 | self.peers.contains_key(peer) 31 | } 32 | 33 | fn join( 34 | &mut self, 35 | peer: SocketAddr, 36 | pipeline: Weak>, 37 | ) { 38 | println!("{} joined", peer); 39 | self.peers.insert(peer, pipeline); 40 | } 41 | 42 | fn leave(&mut self, peer: &SocketAddr) { 43 | println!("{} left", peer); 44 | self.peers.remove(peer); 45 | } 46 | 47 | /// Send message to every peer, except for the sender. 48 | fn broadcast(&self, sender: SocketAddr, msg: TaggedString) { 49 | print!("broadcast message: {}", msg.message); 50 | for (peer, pipeline) in self.peers.iter() { 51 | if *peer != sender { 52 | if let Some(pipeline) = pipeline.upgrade() { 53 | let _ = pipeline.write(TaggedString { 54 | now: msg.now, 55 | transport: TransportContext { 56 | local_addr: msg.transport.local_addr, 57 | peer_addr: *peer, 58 | ecn: msg.transport.ecn, 59 | protocol: msg.transport.protocol, 60 | }, 61 | message: msg.message.clone(), 62 | }); 63 | } 64 | } 65 | } 66 | } 67 | } 68 | 69 | //////////////////////////////////////////////////////////////////////////////////////////////////// 70 | struct ChatHandler { 71 | state: Rc>, 72 | pipeline: Weak>, 73 | } 74 | 75 | impl ChatHandler { 76 | fn new( 77 | state: Rc>, 78 | pipeline: Weak>, 79 | ) -> Self { 80 | ChatHandler { state, pipeline } 81 | } 82 | } 83 | 84 | impl Handler for ChatHandler { 85 | type Rin = TaggedString; 86 | type Rout = Self::Rin; 87 | type Win = TaggedString; 88 | type Wout = Self::Win; 89 | 90 | fn name(&self) -> &str { 91 | "ChatHandler" 92 | } 93 | 94 | fn handle_read( 95 | &mut self, 96 | _ctx: &Context, 97 | msg: Self::Rin, 98 | ) { 99 | let peer_addr = msg.transport.peer_addr; 100 | println!( 101 | "received: {} from {:?} to {}", 102 | msg.message, peer_addr, msg.transport.local_addr 103 | ); 104 | 105 | let mut s = self.state.borrow_mut(); 106 | if msg.message == "bye" { 107 | s.leave(&peer_addr); 108 | } else { 109 | if !s.contains(&peer_addr) { 110 | s.join(peer_addr, self.pipeline.clone()); 111 | } 112 | s.broadcast( 113 | peer_addr, 114 | TaggedString { 115 | now: Instant::now(), 116 | transport: TransportContext { 117 | local_addr: msg.transport.local_addr, 118 | ecn: msg.transport.ecn, 119 | ..Default::default() 120 | }, 121 | message: format!("{}\r\n", msg.message), 122 | }, 123 | ); 124 | } 125 | } 126 | fn poll_timeout( 127 | &mut self, 128 | _ctx: &Context, 129 | _eto: &mut Instant, 130 | ) { 131 | //last handler, no need to fire_poll_timeout 132 | } 133 | 134 | fn poll_write( 135 | &mut self, 136 | ctx: &Context, 137 | ) -> Option { 138 | ctx.fire_poll_write() 139 | } 140 | } 141 | 142 | #[derive(Parser)] 143 | #[command(name = "Chat Server UDP")] 144 | #[command(author = "Rusty Rain ")] 145 | #[command(version = "0.1.0")] 146 | #[command(about = "An example of chat server udp", long_about = None)] 147 | struct Cli { 148 | #[arg(short, long)] 149 | debug: bool, 150 | #[arg(long, default_value_t = format!("0.0.0.0"))] 151 | host: String, 152 | #[arg(long, default_value_t = 8080)] 153 | port: u16, 154 | #[arg(long, default_value_t = format!("INFO"))] 155 | log_level: String, 156 | } 157 | 158 | fn main() -> anyhow::Result<()> { 159 | let cli = Cli::parse(); 160 | let host = cli.host; 161 | let port = cli.port; 162 | let log_level = log::LevelFilter::from_str(&cli.log_level)?; 163 | if cli.debug { 164 | env_logger::Builder::new() 165 | .format(|buf, record| { 166 | writeln!( 167 | buf, 168 | "{}:{} [{}] {} - {}", 169 | record.file().unwrap_or("unknown"), 170 | record.line().unwrap_or(0), 171 | record.level(), 172 | chrono::Local::now().format("%H:%M:%S.%6f"), 173 | record.args() 174 | ) 175 | }) 176 | .filter(None, log_level) 177 | .init(); 178 | } 179 | 180 | println!("listening {}:{}...", host, port); 181 | 182 | LocalExecutorBuilder::default().run(async move { 183 | // Create the shared state. This is how all the peers communicate. 184 | // The server task will hold a handle to this. For every new client, the 185 | // `state` handle is cloned and passed into the handler that processes the 186 | // client connection. 187 | let state = Rc::new(RefCell::new(Shared::new())); 188 | 189 | let mut bootstrap = BootstrapUdpServer::new(); 190 | bootstrap.pipeline(Box::new(move || { 191 | let pipeline: Rc> = Rc::new(Pipeline::new()); 192 | 193 | let line_based_frame_decoder_handler = TaggedByteToMessageCodec::new(Box::new( 194 | LineBasedFrameDecoder::new(8192, true, TerminatorType::BOTH), 195 | )); 196 | let string_codec_handler = TaggedStringCodec::new(); 197 | let pipeline_wr = Rc::downgrade(&pipeline); 198 | let chat_handler = ChatHandler::new(state.clone(), pipeline_wr); 199 | 200 | pipeline.add_back(line_based_frame_decoder_handler); 201 | pipeline.add_back(string_codec_handler); 202 | pipeline.add_back(chat_handler); 203 | pipeline.update() 204 | })); 205 | 206 | bootstrap.bind(format!("{}:{}", host, port)).await.unwrap(); 207 | 208 | println!("Press ctrl-c to stop"); 209 | println!("try `nc {} {}` in another shell", host, port); 210 | let (tx, rx) = futures::channel::oneshot::channel(); 211 | std::thread::spawn(move || { 212 | let mut tx = Some(tx); 213 | ctrlc::set_handler(move || { 214 | if let Some(tx) = tx.take() { 215 | let _ = tx.send(()); 216 | } 217 | }) 218 | .expect("Error setting Ctrl-C handler"); 219 | }); 220 | let _ = rx.await; 221 | 222 | bootstrap.graceful_stop().await; 223 | }); 224 | 225 | Ok(()) 226 | } 227 | -------------------------------------------------------------------------------- /examples/client_tcp.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use futures::StreamExt; 3 | use std::{error::Error, io::Write, net::SocketAddr, str::FromStr, time::Instant}; 4 | 5 | use retty::bootstrap::BootstrapTcpClient; 6 | use retty::channel::{Context, Handler, Pipeline}; 7 | use retty::codec::{ 8 | byte_to_message_decoder::{LineBasedFrameDecoder, TaggedByteToMessageCodec, TerminatorType}, 9 | string_codec::TaggedStringCodec, 10 | }; 11 | use retty::executor::LocalExecutorBuilder; 12 | use retty::transport::{Protocol, TaggedBytesMut, TaggedString, TransportContext}; 13 | 14 | //////////////////////////////////////////////////////////////////////////////////////////////////// 15 | struct EchoHandler; 16 | 17 | impl EchoHandler { 18 | fn new() -> Self { 19 | Self {} 20 | } 21 | } 22 | 23 | impl Handler for EchoHandler { 24 | type Rin = TaggedString; 25 | type Rout = Self::Rin; 26 | type Win = TaggedString; 27 | type Wout = Self::Win; 28 | 29 | fn name(&self) -> &str { 30 | "EchoHandler" 31 | } 32 | 33 | fn handle_read( 34 | &mut self, 35 | _ctx: &Context, 36 | msg: Self::Rin, 37 | ) { 38 | println!( 39 | "received back: {} from {:?}", 40 | msg.message, msg.transport.peer_addr 41 | ); 42 | } 43 | fn handle_exception( 44 | &mut self, 45 | ctx: &Context, 46 | err: Box, 47 | ) { 48 | println!("received exception: {}", err); 49 | ctx.fire_close(); 50 | } 51 | fn handle_read_eof(&mut self, ctx: &Context) { 52 | println!("EOF received :("); 53 | ctx.fire_close(); 54 | } 55 | fn poll_timeout( 56 | &mut self, 57 | _ctx: &Context, 58 | _eto: &mut Instant, 59 | ) { 60 | //last handler, no need to fire_poll_timeout 61 | } 62 | 63 | fn poll_write( 64 | &mut self, 65 | ctx: &Context, 66 | ) -> Option { 67 | ctx.fire_poll_write() 68 | } 69 | } 70 | 71 | #[derive(Parser)] 72 | #[command(name = "Echo Client TCP")] 73 | #[command(author = "Rusty Rain ")] 74 | #[command(version = "0.1.0")] 75 | #[command(about = "An example of echo client tcp", long_about = None)] 76 | struct Cli { 77 | #[arg(short, long)] 78 | debug: bool, 79 | #[arg(long, default_value_t = format!("0.0.0.0"))] 80 | host: String, 81 | #[arg(long, default_value_t = 8080)] 82 | port: u16, 83 | #[arg(long, default_value_t = format!("INFO"))] 84 | log_level: String, 85 | } 86 | 87 | fn main() -> anyhow::Result<()> { 88 | let cli = Cli::parse(); 89 | let host = cli.host; 90 | let port = cli.port; 91 | let log_level = log::LevelFilter::from_str(&cli.log_level)?; 92 | if cli.debug { 93 | env_logger::Builder::new() 94 | .format(|buf, record| { 95 | writeln!( 96 | buf, 97 | "{}:{} [{}] {} - {}", 98 | record.file().unwrap_or("unknown"), 99 | record.line().unwrap_or(0), 100 | record.level(), 101 | chrono::Local::now().format("%H:%M:%S.%6f"), 102 | record.args() 103 | ) 104 | }) 105 | .filter(None, log_level) 106 | .init(); 107 | } 108 | 109 | println!("Connecting {}:{}...", host, port); 110 | 111 | let transport = TransportContext { 112 | local_addr: SocketAddr::from_str("0.0.0.0:0")?, 113 | peer_addr: SocketAddr::from_str(&format!("{}:{}", host, port))?, 114 | ecn: None, 115 | protocol: Protocol::TCP, 116 | }; 117 | 118 | LocalExecutorBuilder::default().run(async move { 119 | let mut bootstrap = BootstrapTcpClient::new(); 120 | bootstrap.pipeline(Box::new(move || { 121 | let pipeline: Pipeline = Pipeline::new(); 122 | 123 | let line_based_frame_decoder_handler = TaggedByteToMessageCodec::new(Box::new( 124 | LineBasedFrameDecoder::new(8192, true, TerminatorType::BOTH), 125 | )); 126 | let string_codec_handler = TaggedStringCodec::new(); 127 | let echo_handler = EchoHandler::new(); 128 | 129 | pipeline.add_back(line_based_frame_decoder_handler); 130 | pipeline.add_back(string_codec_handler); 131 | pipeline.add_back(echo_handler); 132 | pipeline.finalize() 133 | })); 134 | 135 | let pipeline = bootstrap.connect(transport.peer_addr).await.unwrap(); 136 | 137 | println!("Enter bye to stop"); 138 | let (mut tx, mut rx) = futures::channel::mpsc::channel(8); 139 | std::thread::spawn(move || { 140 | let mut buffer = String::new(); 141 | while std::io::stdin().read_line(&mut buffer).is_ok() { 142 | match buffer.trim_end() { 143 | "" => break, 144 | line => { 145 | if tx.try_send(line.to_string()).is_err() { 146 | break; 147 | } 148 | if line == "bye" { 149 | break; 150 | } 151 | } 152 | }; 153 | buffer.clear(); 154 | } 155 | }); 156 | while let Some(line) = rx.next().await { 157 | pipeline.write(TaggedString { 158 | now: Instant::now(), 159 | transport, 160 | message: format!("{}\r\n", line), 161 | }); 162 | if line == "bye" { 163 | pipeline.close(); 164 | break; 165 | } 166 | } 167 | 168 | bootstrap.graceful_stop().await; 169 | }); 170 | 171 | Ok(()) 172 | } 173 | -------------------------------------------------------------------------------- /examples/client_udp.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use futures::StreamExt; 3 | use std::{io::Write, net::SocketAddr, str::FromStr, time::Instant}; 4 | 5 | use retty::bootstrap::BootstrapUdpClient; 6 | use retty::channel::{Context, Handler, Pipeline}; 7 | use retty::codec::{ 8 | byte_to_message_decoder::{LineBasedFrameDecoder, TaggedByteToMessageCodec, TerminatorType}, 9 | string_codec::TaggedStringCodec, 10 | }; 11 | use retty::executor::LocalExecutorBuilder; 12 | use retty::transport::{Protocol, TaggedBytesMut, TaggedString, TransportContext}; 13 | 14 | //////////////////////////////////////////////////////////////////////////////////////////////////// 15 | struct EchoHandler; 16 | 17 | impl EchoHandler { 18 | fn new() -> Self { 19 | EchoHandler {} 20 | } 21 | } 22 | 23 | impl Handler for EchoHandler { 24 | type Rin = TaggedString; 25 | type Rout = Self::Rin; 26 | type Win = TaggedString; 27 | type Wout = Self::Win; 28 | 29 | fn name(&self) -> &str { 30 | "EchoHandler" 31 | } 32 | 33 | fn handle_read( 34 | &mut self, 35 | _ctx: &Context, 36 | msg: Self::Rin, 37 | ) { 38 | println!( 39 | "received back: {} from {:?}", 40 | msg.message, msg.transport.peer_addr 41 | ); 42 | } 43 | fn poll_timeout( 44 | &mut self, 45 | _ctx: &Context, 46 | _eto: &mut Instant, 47 | ) { 48 | //last handler, no need to fire_poll_timeout 49 | } 50 | 51 | fn poll_write( 52 | &mut self, 53 | ctx: &Context, 54 | ) -> Option { 55 | ctx.fire_poll_write() 56 | } 57 | } 58 | 59 | #[derive(Parser)] 60 | #[command(name = "Echo Client UDP")] 61 | #[command(author = "Rusty Rain ")] 62 | #[command(version = "0.1.0")] 63 | #[command(about = "An example of echo client udp", long_about = None)] 64 | struct Cli { 65 | #[arg(short, long)] 66 | debug: bool, 67 | #[arg(long, default_value_t = format!("0.0.0.0"))] 68 | host: String, 69 | #[arg(long, default_value_t = 8080)] 70 | port: u16, 71 | #[arg(long, default_value_t = format!("INFO"))] 72 | log_level: String, 73 | } 74 | 75 | fn main() -> anyhow::Result<()> { 76 | let cli = Cli::parse(); 77 | let host = cli.host; 78 | let port = cli.port; 79 | let log_level = log::LevelFilter::from_str(&cli.log_level)?; 80 | if cli.debug { 81 | env_logger::Builder::new() 82 | .format(|buf, record| { 83 | writeln!( 84 | buf, 85 | "{}:{} [{}] {} - {}", 86 | record.file().unwrap_or("unknown"), 87 | record.line().unwrap_or(0), 88 | record.level(), 89 | chrono::Local::now().format("%H:%M:%S.%6f"), 90 | record.args() 91 | ) 92 | }) 93 | .filter(None, log_level) 94 | .init(); 95 | } 96 | 97 | println!("Connecting {}:{}...", host, port); 98 | 99 | let transport = TransportContext { 100 | local_addr: SocketAddr::from_str("0.0.0.0:0")?, 101 | peer_addr: SocketAddr::from_str(&format!("{}:{}", host, port))?, 102 | ecn: None, 103 | protocol: Protocol::UDP, 104 | }; 105 | 106 | LocalExecutorBuilder::default().run(async move { 107 | let mut bootstrap = BootstrapUdpClient::new(); 108 | bootstrap.pipeline(Box::new(move || { 109 | let pipeline: Pipeline = Pipeline::new(); 110 | 111 | let line_based_frame_decoder_handler = TaggedByteToMessageCodec::new(Box::new( 112 | LineBasedFrameDecoder::new(8192, true, TerminatorType::BOTH), 113 | )); 114 | let string_codec_handler = TaggedStringCodec::new(); 115 | let echo_handler = EchoHandler::new(); 116 | 117 | pipeline.add_back(line_based_frame_decoder_handler); 118 | pipeline.add_back(string_codec_handler); 119 | pipeline.add_back(echo_handler); 120 | pipeline.finalize() 121 | })); 122 | 123 | bootstrap.bind(transport.local_addr).await.unwrap(); 124 | 125 | let pipeline = bootstrap.connect(transport.peer_addr).await.unwrap(); 126 | 127 | println!("Enter bye to stop"); 128 | let (mut tx, mut rx) = futures::channel::mpsc::channel(8); 129 | std::thread::spawn(move || { 130 | let mut buffer = String::new(); 131 | while std::io::stdin().read_line(&mut buffer).is_ok() { 132 | match buffer.trim_end() { 133 | "" => break, 134 | line => { 135 | if tx.try_send(line.to_string()).is_err() { 136 | break; 137 | } 138 | if line == "bye" { 139 | break; 140 | } 141 | } 142 | }; 143 | buffer.clear(); 144 | } 145 | }); 146 | while let Some(line) = rx.next().await { 147 | pipeline.write(TaggedString { 148 | now: Instant::now(), 149 | transport, 150 | message: format!("{}\r\n", line), 151 | }); 152 | if line == "bye" { 153 | pipeline.close(); 154 | break; 155 | } 156 | } 157 | 158 | bootstrap.graceful_stop().await; 159 | }); 160 | 161 | Ok(()) 162 | } 163 | -------------------------------------------------------------------------------- /examples/echo_server_tcp.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use std::collections::VecDeque; 3 | use std::{io::Write, str::FromStr, time::Instant}; 4 | 5 | use retty::bootstrap::BootstrapTcpServer; 6 | use retty::channel::{Context, Handler, Pipeline}; 7 | use retty::codec::{ 8 | byte_to_message_decoder::{LineBasedFrameDecoder, TaggedByteToMessageCodec, TerminatorType}, 9 | string_codec::TaggedStringCodec, 10 | }; 11 | use retty::executor::LocalExecutorBuilder; 12 | use retty::transport::{TaggedBytesMut, TaggedString}; 13 | 14 | //////////////////////////////////////////////////////////////////////////////////////////////////// 15 | struct EchoHandler { 16 | transmits: VecDeque, 17 | } 18 | 19 | impl EchoHandler { 20 | fn new() -> Self { 21 | Self { 22 | transmits: VecDeque::new(), 23 | } 24 | } 25 | } 26 | 27 | impl Handler for EchoHandler { 28 | type Rin = TaggedString; 29 | type Rout = Self::Rin; 30 | type Win = TaggedString; 31 | type Wout = Self::Win; 32 | 33 | fn name(&self) -> &str { 34 | "EchoHandler" 35 | } 36 | 37 | fn handle_read( 38 | &mut self, 39 | _ctx: &Context, 40 | msg: Self::Rin, 41 | ) { 42 | println!( 43 | "handling {} from {:?}", 44 | msg.message, msg.transport.peer_addr 45 | ); 46 | self.transmits.push_back(TaggedString { 47 | now: Instant::now(), 48 | transport: msg.transport, 49 | message: format!("{}\r\n", msg.message), 50 | }); 51 | } 52 | fn handle_read_eof(&mut self, ctx: &Context) { 53 | ctx.fire_close(); 54 | } 55 | fn poll_timeout( 56 | &mut self, 57 | _ctx: &Context, 58 | _eto: &mut Instant, 59 | ) { 60 | //last handler, no need to fire_poll_timeout 61 | } 62 | 63 | fn poll_write( 64 | &mut self, 65 | ctx: &Context, 66 | ) -> Option { 67 | if let Some(msg) = ctx.fire_poll_write() { 68 | self.transmits.push_back(msg); 69 | } 70 | self.transmits.pop_front() 71 | } 72 | } 73 | 74 | #[derive(Parser)] 75 | #[command(name = "Echo Server TCP")] 76 | #[command(author = "Rusty Rain ")] 77 | #[command(version = "0.1.0")] 78 | #[command(about = "An example of echo server tcp", long_about = None)] 79 | struct Cli { 80 | #[arg(short, long)] 81 | debug: bool, 82 | #[arg(long, default_value_t = format!("0.0.0.0"))] 83 | host: String, 84 | #[arg(long, default_value_t = 8080)] 85 | port: u16, 86 | #[arg(long, default_value_t = format!("INFO"))] 87 | log_level: String, 88 | } 89 | 90 | fn main() -> anyhow::Result<()> { 91 | let cli = Cli::parse(); 92 | let host = cli.host; 93 | let port = cli.port; 94 | let log_level = log::LevelFilter::from_str(&cli.log_level)?; 95 | if cli.debug { 96 | env_logger::Builder::new() 97 | .format(|buf, record| { 98 | writeln!( 99 | buf, 100 | "{}:{} [{}] {} - {}", 101 | record.file().unwrap_or("unknown"), 102 | record.line().unwrap_or(0), 103 | record.level(), 104 | chrono::Local::now().format("%H:%M:%S.%6f"), 105 | record.args() 106 | ) 107 | }) 108 | .filter(None, log_level) 109 | .init(); 110 | } 111 | 112 | println!("listening {}:{}...", host, port); 113 | 114 | LocalExecutorBuilder::default().run(async move { 115 | let mut bootstrap = BootstrapTcpServer::new(); 116 | bootstrap.pipeline(Box::new(move || { 117 | let pipeline: Pipeline = Pipeline::new(); 118 | 119 | let line_based_frame_decoder_handler = TaggedByteToMessageCodec::new(Box::new( 120 | LineBasedFrameDecoder::new(8192, true, TerminatorType::BOTH), 121 | )); 122 | let string_codec_handler = TaggedStringCodec::new(); 123 | let echo_handler = EchoHandler::new(); 124 | 125 | pipeline.add_back(line_based_frame_decoder_handler); 126 | pipeline.add_back(string_codec_handler); 127 | pipeline.add_back(echo_handler); 128 | pipeline.finalize() 129 | })); 130 | 131 | bootstrap.bind(format!("{}:{}", host, port)).await.unwrap(); 132 | 133 | println!("Press ctrl-c to stop"); 134 | println!("try `nc {} {}` in another shell", host, port); 135 | let (tx, rx) = futures::channel::oneshot::channel(); 136 | std::thread::spawn(move || { 137 | let mut tx = Some(tx); 138 | ctrlc::set_handler(move || { 139 | if let Some(tx) = tx.take() { 140 | let _ = tx.send(()); 141 | } 142 | }) 143 | .expect("Error setting Ctrl-C handler"); 144 | }); 145 | let _ = rx.await; 146 | 147 | bootstrap.graceful_stop().await; 148 | }); 149 | 150 | Ok(()) 151 | } 152 | -------------------------------------------------------------------------------- /examples/echo_server_udp.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use std::collections::VecDeque; 3 | use std::{io::Write, str::FromStr, time::Instant}; 4 | 5 | use retty::bootstrap::BootstrapUdpServer; 6 | use retty::channel::{Context, Handler, Pipeline}; 7 | use retty::codec::{ 8 | byte_to_message_decoder::{LineBasedFrameDecoder, TaggedByteToMessageCodec, TerminatorType}, 9 | string_codec::TaggedStringCodec, 10 | }; 11 | use retty::executor::LocalExecutorBuilder; 12 | use retty::transport::{TaggedBytesMut, TaggedString}; 13 | 14 | //////////////////////////////////////////////////////////////////////////////////////////////////// 15 | struct EchoHandler { 16 | transmits: VecDeque, 17 | } 18 | 19 | impl EchoHandler { 20 | fn new() -> Self { 21 | Self { 22 | transmits: VecDeque::new(), 23 | } 24 | } 25 | } 26 | 27 | impl Handler for EchoHandler { 28 | type Rin = TaggedString; 29 | type Rout = Self::Rin; 30 | type Win = TaggedString; 31 | type Wout = Self::Win; 32 | 33 | fn name(&self) -> &str { 34 | "EchoHandler" 35 | } 36 | 37 | fn handle_read( 38 | &mut self, 39 | _ctx: &Context, 40 | msg: Self::Rin, 41 | ) { 42 | println!( 43 | "handling {} from {:?}", 44 | msg.message, msg.transport.peer_addr 45 | ); 46 | if msg.message != "bye" { 47 | self.transmits.push_back(TaggedString { 48 | now: Instant::now(), 49 | transport: msg.transport, 50 | message: format!("{}\r\n", msg.message), 51 | }); 52 | } 53 | } 54 | fn poll_timeout( 55 | &mut self, 56 | _ctx: &Context, 57 | _eto: &mut Instant, 58 | ) { 59 | //last handler, no need to fire_poll_timeout 60 | } 61 | 62 | fn poll_write( 63 | &mut self, 64 | ctx: &Context, 65 | ) -> Option { 66 | if let Some(msg) = ctx.fire_poll_write() { 67 | self.transmits.push_back(msg); 68 | } 69 | self.transmits.pop_front() 70 | } 71 | } 72 | 73 | #[derive(Parser)] 74 | #[command(name = "Echo Server UDP")] 75 | #[command(author = "Rusty Rain ")] 76 | #[command(version = "0.1.0")] 77 | #[command(about = "An example of echo server udp", long_about = None)] 78 | struct Cli { 79 | #[arg(short, long)] 80 | debug: bool, 81 | #[arg(long, default_value_t = format!("0.0.0.0"))] 82 | host: String, 83 | #[arg(long, default_value_t = 8080)] 84 | port: u16, 85 | #[arg(long, default_value_t = format!("INFO"))] 86 | log_level: String, 87 | } 88 | 89 | fn main() -> anyhow::Result<()> { 90 | let cli = Cli::parse(); 91 | let host = cli.host; 92 | let port = cli.port; 93 | let log_level = log::LevelFilter::from_str(&cli.log_level)?; 94 | if cli.debug { 95 | env_logger::Builder::new() 96 | .format(|buf, record| { 97 | writeln!( 98 | buf, 99 | "{}:{} [{}] {} - {}", 100 | record.file().unwrap_or("unknown"), 101 | record.line().unwrap_or(0), 102 | record.level(), 103 | chrono::Local::now().format("%H:%M:%S.%6f"), 104 | record.args() 105 | ) 106 | }) 107 | .filter(None, log_level) 108 | .init(); 109 | } 110 | 111 | println!("listening {}:{}...", host, port); 112 | 113 | LocalExecutorBuilder::default().run(async move { 114 | let mut bootstrap = BootstrapUdpServer::new(); 115 | bootstrap.pipeline(Box::new(move || { 116 | let pipeline: Pipeline = Pipeline::new(); 117 | 118 | let line_based_frame_decoder_handler = TaggedByteToMessageCodec::new(Box::new( 119 | LineBasedFrameDecoder::new(8192, true, TerminatorType::BOTH), 120 | )); 121 | let string_codec_handler = TaggedStringCodec::new(); 122 | let echo_handler = EchoHandler::new(); 123 | 124 | pipeline.add_back(line_based_frame_decoder_handler); 125 | pipeline.add_back(string_codec_handler); 126 | pipeline.add_back(echo_handler); 127 | pipeline.finalize() 128 | })); 129 | 130 | bootstrap.bind(format!("{}:{}", host, port)).await.unwrap(); 131 | 132 | println!("Press ctrl-c to stop"); 133 | println!("try `nc -u {} {}` in another shell", host, port); 134 | let (tx, rx) = futures::channel::oneshot::channel(); 135 | std::thread::spawn(move || { 136 | let mut tx = Some(tx); 137 | ctrlc::set_handler(move || { 138 | if let Some(tx) = tx.take() { 139 | let _ = tx.send(()); 140 | } 141 | }) 142 | .expect("Error setting Ctrl-C handler"); 143 | }); 144 | let _ = rx.await; 145 | 146 | bootstrap.graceful_stop().await; 147 | }); 148 | 149 | Ok(()) 150 | } 151 | -------------------------------------------------------------------------------- /src/bootstrap/bootstrap_tcp/bootstrap_tcp_client.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | /// A Bootstrap that makes it easy to bootstrap a pipeline to use for TCP clients. 4 | pub struct BootstrapTcpClient { 5 | bootstrap_tcp: BootstrapTcp, 6 | } 7 | 8 | impl Default for BootstrapTcpClient { 9 | fn default() -> Self { 10 | Self::new() 11 | } 12 | } 13 | 14 | impl BootstrapTcpClient { 15 | /// Creates a new BootstrapTcpClient 16 | pub fn new() -> Self { 17 | Self { 18 | bootstrap_tcp: BootstrapTcp::new(), 19 | } 20 | } 21 | 22 | /// Sets max payload size, default is 2048 bytes 23 | pub fn max_payload_size(&mut self, max_payload_size: usize) -> &mut Self { 24 | self.bootstrap_tcp.max_payload_size(max_payload_size); 25 | self 26 | } 27 | 28 | /// Creates pipeline instances from when calling [BootstrapTcpClient::connect]. 29 | pub fn pipeline( 30 | &mut self, 31 | pipeline_factory_fn: PipelineFactoryFn, 32 | ) -> &mut Self { 33 | self.bootstrap_tcp.pipeline(pipeline_factory_fn); 34 | self 35 | } 36 | 37 | /// Connects to the remote peer 38 | pub async fn connect( 39 | &mut self, 40 | addr: A, 41 | ) -> Result>, Error> { 42 | self.bootstrap_tcp.connect(addr).await 43 | } 44 | 45 | /// Stops the client 46 | pub async fn stop(&self) { 47 | self.bootstrap_tcp.stop().await 48 | } 49 | 50 | /// Waits for stop of the client 51 | pub async fn wait_for_stop(&self) { 52 | self.bootstrap_tcp.wait_for_stop().await 53 | } 54 | 55 | /// Gracefully stop the client 56 | pub async fn graceful_stop(&self) { 57 | self.bootstrap_tcp.graceful_stop().await 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/bootstrap/bootstrap_tcp/bootstrap_tcp_server.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | /// A Bootstrap that makes it easy to bootstrap a pipeline to use for TCP servers. 4 | pub struct BootstrapTcpServer { 5 | bootstrap_tcp: BootstrapTcp, 6 | } 7 | 8 | impl Default for BootstrapTcpServer { 9 | fn default() -> Self { 10 | Self::new() 11 | } 12 | } 13 | 14 | impl BootstrapTcpServer { 15 | /// Creates a new BootstrapTcpServer 16 | pub fn new() -> Self { 17 | Self { 18 | bootstrap_tcp: BootstrapTcp::new(), 19 | } 20 | } 21 | 22 | /// Sets max payload size, default is 2048 bytes 23 | pub fn max_payload_size(&mut self, max_payload_size: usize) -> &mut Self { 24 | self.bootstrap_tcp.max_payload_size(max_payload_size); 25 | self 26 | } 27 | 28 | /// Creates pipeline instances from when calling [BootstrapTcpServer::bind]. 29 | pub fn pipeline( 30 | &mut self, 31 | pipeline_factory_fn: PipelineFactoryFn, 32 | ) -> &mut Self { 33 | self.bootstrap_tcp.pipeline(pipeline_factory_fn); 34 | self 35 | } 36 | 37 | /// Binds local address and port 38 | pub async fn bind(&self, addr: A) -> Result { 39 | self.bootstrap_tcp.bind(addr).await 40 | } 41 | 42 | /// Stops the server 43 | pub async fn stop(&self) { 44 | self.bootstrap_tcp.stop().await 45 | } 46 | 47 | /// Waits for stop of the server 48 | pub async fn wait_for_stop(&self) { 49 | self.bootstrap_tcp.wait_for_stop().await 50 | } 51 | 52 | /// Gracefully stop the server 53 | pub async fn graceful_stop(&self) { 54 | self.bootstrap_tcp.graceful_stop().await 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/bootstrap/bootstrap_tcp/mod.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::transport::Protocol; 3 | use smol::{ 4 | net::{AsyncToSocketAddrs, TcpListener, TcpStream}, 5 | Timer, 6 | }; 7 | 8 | pub(crate) mod bootstrap_tcp_client; 9 | pub(crate) mod bootstrap_tcp_server; 10 | 11 | struct BootstrapTcp { 12 | boostrap: Bootstrap, 13 | } 14 | 15 | impl Default for BootstrapTcp { 16 | fn default() -> Self { 17 | Self::new() 18 | } 19 | } 20 | 21 | impl BootstrapTcp { 22 | fn new() -> Self { 23 | Self { 24 | boostrap: Bootstrap::new(), 25 | } 26 | } 27 | 28 | fn max_payload_size(&mut self, max_payload_size: usize) -> &mut Self { 29 | self.boostrap.max_payload_size(max_payload_size); 30 | self 31 | } 32 | 33 | fn pipeline(&mut self, pipeline_factory_fn: PipelineFactoryFn) -> &mut Self { 34 | self.boostrap.pipeline(pipeline_factory_fn); 35 | self 36 | } 37 | 38 | async fn bind(&self, addr: A) -> Result { 39 | let listener = TcpListener::bind(addr).await?; 40 | let local_addr = listener.local_addr()?; 41 | let pipeline_factory_fn = Rc::clone(self.boostrap.pipeline_factory_fn.as_ref().unwrap()); 42 | 43 | let (close_tx, mut close_rx) = async_broadcast::broadcast(1); 44 | { 45 | let mut tx = self.boostrap.close_tx.borrow_mut(); 46 | *tx = Some(close_tx); 47 | } 48 | 49 | let worker = { 50 | let workgroup = WaitGroup::new(); 51 | let worker = workgroup.worker(); 52 | { 53 | let mut wg = self.boostrap.wg.borrow_mut(); 54 | *wg = Some(workgroup); 55 | } 56 | worker 57 | }; 58 | 59 | let max_payload_size = self.boostrap.max_payload_size; 60 | 61 | spawn_local(async move { 62 | let _w = worker; 63 | 64 | let child_wg = WaitGroup::new(); 65 | loop { 66 | tokio::select! { 67 | _ = close_rx.recv() => { 68 | trace!("listener exit loop"); 69 | break; 70 | } 71 | res = listener.accept() => { 72 | match res { 73 | Ok((socket, _peer_addr)) => { 74 | // A new task is spawned for each inbound socket. The socket is 75 | // moved to the new task and processed there. 76 | let pipeline_rd = (pipeline_factory_fn)(); 77 | let child_close_rx = close_rx.clone(); 78 | let child_worker = child_wg.worker(); 79 | spawn_local(async move { 80 | let _ = Self::process_pipeline(socket, 81 | max_payload_size, 82 | pipeline_rd, 83 | child_close_rx, 84 | child_worker).await; 85 | }).detach(); 86 | } 87 | Err(err) => { 88 | warn!("listener accept error {}", err); 89 | break; 90 | } 91 | } 92 | } 93 | } 94 | } 95 | child_wg.wait().await; 96 | }) 97 | .detach(); 98 | 99 | Ok(local_addr) 100 | } 101 | 102 | async fn connect( 103 | &self, 104 | addr: A, 105 | ) -> Result>, Error> { 106 | let socket = TcpStream::connect(addr).await?; 107 | let pipeline_factory_fn = Rc::clone(self.boostrap.pipeline_factory_fn.as_ref().unwrap()); 108 | 109 | let (close_tx, close_rx) = async_broadcast::broadcast(1); 110 | { 111 | let mut tx = self.boostrap.close_tx.borrow_mut(); 112 | *tx = Some(close_tx); 113 | } 114 | 115 | let worker = { 116 | let workgroup = WaitGroup::new(); 117 | let worker = workgroup.worker(); 118 | { 119 | let mut wg = self.boostrap.wg.borrow_mut(); 120 | *wg = Some(workgroup); 121 | } 122 | worker 123 | }; 124 | 125 | let pipeline_rd = (pipeline_factory_fn)(); 126 | let pipeline_wr = Rc::clone(&pipeline_rd); 127 | let max_payload_size = self.boostrap.max_payload_size; 128 | 129 | spawn_local(async move { 130 | let _ = Self::process_pipeline(socket, max_payload_size, pipeline_rd, close_rx, worker) 131 | .await; 132 | }) 133 | .detach(); 134 | 135 | Ok(pipeline_wr) 136 | } 137 | 138 | async fn process_pipeline( 139 | mut socket: TcpStream, 140 | max_payload_size: usize, 141 | pipeline: Rc>, 142 | mut close_rx: async_broadcast::Receiver<()>, 143 | worker: Worker, 144 | ) -> Result<(), Error> { 145 | let _w = worker; 146 | 147 | let local_addr = socket.local_addr()?; 148 | let peer_addr = socket.peer_addr()?; 149 | 150 | let mut buf = vec![0u8; max_payload_size]; 151 | 152 | pipeline.transport_active(); 153 | loop { 154 | // prioritize socket.write than socket.read 155 | while let Some(transmit) = pipeline.poll_transmit() { 156 | match socket.write(&transmit.message).await { 157 | Ok(n) => { 158 | trace!("socket write {} bytes", n); 159 | } 160 | Err(err) => { 161 | warn!("socket write error {}", err); 162 | break; 163 | } 164 | } 165 | } 166 | 167 | let mut eto = Instant::now() + Duration::from_secs(MAX_DURATION_IN_SECS); 168 | pipeline.poll_timeout(&mut eto); 169 | 170 | let delay_from_now = eto 171 | .checked_duration_since(Instant::now()) 172 | .unwrap_or(Duration::from_secs(0)); 173 | if delay_from_now.is_zero() { 174 | pipeline.handle_timeout(Instant::now()); 175 | continue; 176 | } 177 | 178 | let timeout = Timer::after(delay_from_now); 179 | 180 | tokio::select! { 181 | _ = close_rx.recv() => { 182 | trace!("pipeline socket exit loop"); 183 | break; 184 | } 185 | _ = timeout => { 186 | pipeline.handle_timeout(Instant::now()); 187 | } 188 | res = socket.read(&mut buf) => { 189 | match res { 190 | Ok(n) => { 191 | if n == 0 { 192 | pipeline.handle_read_eof(); 193 | break; 194 | } 195 | 196 | trace!("socket read {} bytes", n); 197 | pipeline.read(TaggedBytesMut { 198 | now: Instant::now(), 199 | transport: TransportContext { 200 | local_addr, 201 | peer_addr, 202 | ecn: None, 203 | protocol: Protocol::TCP, 204 | }, 205 | message: BytesMut::from(&buf[..n]), 206 | }); 207 | } 208 | Err(err) => { 209 | warn!("socket read error {}", err); 210 | break; 211 | } 212 | } 213 | } 214 | } 215 | } 216 | pipeline.transport_inactive(); 217 | 218 | Ok(()) 219 | } 220 | 221 | async fn stop(&self) { 222 | self.boostrap.stop().await 223 | } 224 | 225 | async fn wait_for_stop(&self) { 226 | self.boostrap.wait_for_stop().await 227 | } 228 | 229 | async fn graceful_stop(&self) { 230 | self.boostrap.graceful_stop().await 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/bootstrap/bootstrap_udp/bootstrap_udp_client.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | /// A Bootstrap that makes it easy to bootstrap a pipeline to use for UDP clients. 4 | pub struct BootstrapUdpClient { 5 | bootstrap_udp: BootstrapUdp, 6 | } 7 | 8 | impl Default for BootstrapUdpClient { 9 | fn default() -> Self { 10 | Self::new() 11 | } 12 | } 13 | 14 | impl BootstrapUdpClient { 15 | /// Creates a new BootstrapUdpClient 16 | pub fn new() -> Self { 17 | Self { 18 | bootstrap_udp: BootstrapUdp::new(), 19 | } 20 | } 21 | 22 | /// Sets max payload size, default is 2048 bytes 23 | pub fn max_payload_size(&mut self, max_payload_size: usize) -> &mut Self { 24 | self.bootstrap_udp.max_payload_size(max_payload_size); 25 | self 26 | } 27 | 28 | /// Creates pipeline instances from when calling [BootstrapUdpClient::bind]. 29 | pub fn pipeline( 30 | &mut self, 31 | pipeline_factory_fn: PipelineFactoryFn, 32 | ) -> &mut Self { 33 | self.bootstrap_udp.pipeline(pipeline_factory_fn); 34 | self 35 | } 36 | 37 | /// Binds local address and port 38 | pub async fn bind(&mut self, addr: A) -> Result { 39 | self.bootstrap_udp.bind(addr).await 40 | } 41 | 42 | /// Connects to the remote peer 43 | pub async fn connect( 44 | &mut self, 45 | addr: SocketAddr, 46 | ) -> Result>, Error> { 47 | self.bootstrap_udp.connect(Some(addr)).await 48 | } 49 | 50 | /// Stops the client 51 | pub async fn stop(&self) { 52 | self.bootstrap_udp.stop().await 53 | } 54 | 55 | /// Waits for stop of the client 56 | pub async fn wait_for_stop(&self) { 57 | self.bootstrap_udp.wait_for_stop().await 58 | } 59 | 60 | /// Gracefully stop the client 61 | pub async fn graceful_stop(&self) { 62 | self.bootstrap_udp.graceful_stop().await 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/bootstrap/bootstrap_udp/bootstrap_udp_server.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | /// A Bootstrap that makes it easy to bootstrap a pipeline to use for UDP servers. 4 | pub struct BootstrapUdpServer { 5 | bootstrap_udp: BootstrapUdp, 6 | } 7 | 8 | impl Default for BootstrapUdpServer { 9 | fn default() -> Self { 10 | Self::new() 11 | } 12 | } 13 | 14 | impl BootstrapUdpServer { 15 | /// Creates a new BootstrapUdpServer 16 | pub fn new() -> Self { 17 | Self { 18 | bootstrap_udp: BootstrapUdp::new(), 19 | } 20 | } 21 | 22 | /// Sets max payload size, default is 2048 bytes 23 | pub fn max_payload_size(&mut self, max_payload_size: usize) -> &mut Self { 24 | self.bootstrap_udp.max_payload_size(max_payload_size); 25 | self 26 | } 27 | 28 | /// Creates pipeline instances from when calling [BootstrapUdpServer::bind]. 29 | pub fn pipeline( 30 | &mut self, 31 | pipeline_factory_fn: PipelineFactoryFn, 32 | ) -> &mut Self { 33 | self.bootstrap_udp.pipeline(pipeline_factory_fn); 34 | self 35 | } 36 | 37 | /// Binds local address and port 38 | pub async fn bind(&mut self, addr: A) -> Result { 39 | let local_addr = self.bootstrap_udp.bind(addr).await?; 40 | let peer_addr: Option = None; 41 | self.bootstrap_udp.connect(peer_addr).await?; 42 | Ok(local_addr) 43 | } 44 | 45 | /// Stops the server 46 | pub async fn stop(&self) { 47 | self.bootstrap_udp.stop().await 48 | } 49 | 50 | /// Waits for stop of the server 51 | pub async fn wait_for_stop(&self) { 52 | self.bootstrap_udp.wait_for_stop().await 53 | } 54 | 55 | /// Gracefully stop the server 56 | pub async fn graceful_stop(&self) { 57 | self.bootstrap_udp.graceful_stop().await 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/bootstrap/bootstrap_udp/mod.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::transport::Protocol; 3 | use async_transport::{AsyncUdpSocket, Capabilities, RecvMeta, Transmit, UdpSocket, BATCH_SIZE}; 4 | use std::mem::MaybeUninit; 5 | 6 | pub(crate) mod bootstrap_udp_client; 7 | pub(crate) mod bootstrap_udp_server; 8 | 9 | struct BootstrapUdp { 10 | boostrap: Bootstrap, 11 | 12 | socket: Option, 13 | } 14 | 15 | impl Default for BootstrapUdp { 16 | fn default() -> Self { 17 | Self::new() 18 | } 19 | } 20 | 21 | impl BootstrapUdp { 22 | fn new() -> Self { 23 | Self { 24 | boostrap: Bootstrap::new(), 25 | 26 | socket: None, 27 | } 28 | } 29 | 30 | fn max_payload_size(&mut self, max_payload_size: usize) -> &mut Self { 31 | self.boostrap.max_payload_size(max_payload_size); 32 | self 33 | } 34 | 35 | fn pipeline(&mut self, pipeline_factory_fn: PipelineFactoryFn) -> &mut Self { 36 | self.boostrap.pipeline(pipeline_factory_fn); 37 | self 38 | } 39 | 40 | async fn bind(&mut self, addr: A) -> Result { 41 | let socket = UdpSocket::bind(addr).await?; 42 | let local_addr = socket.local_addr()?; 43 | self.socket = Some(socket); 44 | Ok(local_addr) 45 | } 46 | 47 | async fn connect( 48 | &mut self, 49 | _peer_addr: Option, 50 | ) -> Result>, Error> { 51 | let socket = self.socket.take().unwrap(); 52 | let local_addr = socket.local_addr()?; 53 | 54 | let pipeline_factory_fn = Rc::clone(self.boostrap.pipeline_factory_fn.as_ref().unwrap()); 55 | let pipeline = (pipeline_factory_fn)(); 56 | let pipeline_wr = Rc::clone(&pipeline); 57 | 58 | let (close_tx, mut close_rx) = async_broadcast::broadcast(1); 59 | { 60 | let mut tx = self.boostrap.close_tx.borrow_mut(); 61 | *tx = Some(close_tx); 62 | } 63 | 64 | let worker = { 65 | let workgroup = WaitGroup::new(); 66 | let worker = workgroup.worker(); 67 | { 68 | let mut wg = self.boostrap.wg.borrow_mut(); 69 | *wg = Some(workgroup); 70 | } 71 | worker 72 | }; 73 | 74 | let max_payload_size = self.boostrap.max_payload_size; 75 | 76 | spawn_local(async move { 77 | let _w = worker; 78 | 79 | let capabilities = Capabilities::new(); 80 | let buf = vec![0u8; max_payload_size * capabilities.gro_segments() * BATCH_SIZE]; 81 | let buf_len = buf.len(); 82 | let mut recv_buf: Box<[u8]> = buf.into(); 83 | let mut metas = [RecvMeta::default(); BATCH_SIZE]; 84 | let mut iovs = MaybeUninit::<[std::io::IoSliceMut<'_>; BATCH_SIZE]>::uninit(); 85 | recv_buf 86 | .chunks_mut(buf_len / BATCH_SIZE) 87 | .enumerate() 88 | .for_each(|(i, buf)| unsafe { 89 | iovs.as_mut_ptr() 90 | .cast::>() 91 | .add(i) 92 | .write(std::io::IoSliceMut::<'_>::new(buf)); 93 | }); 94 | let mut iovs = unsafe { iovs.assume_init() }; 95 | 96 | pipeline.transport_active(); 97 | loop { 98 | // prioritize socket.write than socket.read 99 | while let Some(msg) = pipeline.poll_transmit() { 100 | let transmit = Transmit { 101 | destination: msg.transport.peer_addr, 102 | ecn: msg.transport.ecn, 103 | contents: msg.message.to_vec(), 104 | segment_size: None, 105 | src_ip: Some(msg.transport.local_addr.ip()), 106 | }; 107 | match socket.send(&capabilities, &[transmit]).await { 108 | Ok(_) => { 109 | trace!("socket write {} bytes", msg.message.len()); 110 | } 111 | Err(err) => { 112 | warn!("socket write error {}", err); 113 | break; 114 | } 115 | } 116 | } 117 | 118 | let mut eto = Instant::now() + Duration::from_secs(MAX_DURATION_IN_SECS); 119 | pipeline.poll_timeout(&mut eto); 120 | 121 | let delay_from_now = eto 122 | .checked_duration_since(Instant::now()) 123 | .unwrap_or(Duration::from_secs(0)); 124 | if delay_from_now.is_zero() { 125 | pipeline.handle_timeout(Instant::now()); 126 | continue; 127 | } 128 | 129 | let timeout = Timer::after(delay_from_now); 130 | 131 | tokio::select! { 132 | _ = close_rx.recv() => { 133 | trace!("pipeline socket exit loop"); 134 | break; 135 | } 136 | _ = timeout => { 137 | pipeline.handle_timeout(Instant::now()); 138 | } 139 | res = socket.recv(&mut iovs, &mut metas) => { 140 | match res { 141 | Ok(n) => { 142 | if n == 0 { 143 | pipeline.handle_read_eof(); 144 | break; 145 | } 146 | 147 | for (meta, buf) in metas.iter().zip(iovs.iter()).take(n) { 148 | let message: BytesMut = buf[0..meta.len].into(); 149 | if !message.is_empty() { 150 | trace!("socket read {} bytes", message.len()); 151 | pipeline 152 | .read(TaggedBytesMut { 153 | now: Instant::now(), 154 | transport: TransportContext { 155 | local_addr, 156 | peer_addr: meta.addr, 157 | ecn: meta.ecn, 158 | protocol: Protocol::UDP, 159 | }, 160 | message, 161 | }); 162 | } 163 | } 164 | } 165 | Err(err) => { 166 | warn!("socket read error {}", err); 167 | break; 168 | } 169 | } 170 | } 171 | } 172 | } 173 | pipeline.transport_inactive(); 174 | }) 175 | .detach(); 176 | 177 | Ok(pipeline_wr) 178 | } 179 | 180 | async fn stop(&self) { 181 | self.boostrap.stop().await 182 | } 183 | 184 | async fn wait_for_stop(&self) { 185 | self.boostrap.wait_for_stop().await 186 | } 187 | 188 | async fn graceful_stop(&self) { 189 | self.boostrap.graceful_stop().await 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/bootstrap/mod.rs: -------------------------------------------------------------------------------- 1 | //! The helpful bootstrap APIs which enable an easy implementation of typical client side and server side pipeline initialization. 2 | 3 | use async_net::AsyncToSocketAddrs; 4 | use bytes::BytesMut; 5 | use futures_lite::{AsyncReadExt, AsyncWriteExt}; 6 | use log::{trace, warn}; 7 | use smol::Timer; 8 | use std::{ 9 | cell::RefCell, 10 | io::Error, 11 | net::SocketAddr, 12 | rc::Rc, 13 | time::{Duration, Instant}, 14 | }; 15 | use waitgroup::{WaitGroup, Worker}; 16 | 17 | use crate::channel::{InboundPipeline, OutboundPipeline, Pipeline}; 18 | use crate::executor::spawn_local; 19 | use crate::transport::{TaggedBytesMut, TransportContext}; 20 | 21 | mod bootstrap_tcp; 22 | mod bootstrap_udp; 23 | 24 | pub use bootstrap_tcp::{ 25 | bootstrap_tcp_client::BootstrapTcpClient, bootstrap_tcp_server::BootstrapTcpServer, 26 | }; 27 | pub use bootstrap_udp::{ 28 | bootstrap_udp_client::BootstrapUdpClient, bootstrap_udp_server::BootstrapUdpServer, 29 | }; 30 | 31 | /// Creates a new [Pipeline] 32 | pub type PipelineFactoryFn = Box Rc>)>; 33 | 34 | const MAX_DURATION_IN_SECS: u64 = 86400; // 1 day 35 | 36 | struct Bootstrap { 37 | max_payload_size: usize, 38 | pipeline_factory_fn: Option>>, 39 | close_tx: Rc>>>, 40 | wg: Rc>>, 41 | } 42 | 43 | impl Default for Bootstrap { 44 | fn default() -> Self { 45 | Self::new() 46 | } 47 | } 48 | 49 | impl Bootstrap { 50 | fn new() -> Self { 51 | Self { 52 | max_payload_size: 2048, // Typical internet MTU = 1500, rounded up to a power of 2 53 | pipeline_factory_fn: None, 54 | close_tx: Rc::new(RefCell::new(None)), 55 | wg: Rc::new(RefCell::new(None)), 56 | } 57 | } 58 | 59 | fn max_payload_size(&mut self, max_payload_size: usize) -> &mut Self { 60 | self.max_payload_size = max_payload_size; 61 | self 62 | } 63 | 64 | fn pipeline(&mut self, pipeline_factory_fn: PipelineFactoryFn) -> &mut Self { 65 | self.pipeline_factory_fn = Some(Rc::new(Box::new(pipeline_factory_fn))); 66 | self 67 | } 68 | 69 | async fn stop(&self) { 70 | let mut close_tx = self.close_tx.borrow_mut(); 71 | if let Some(close_tx) = close_tx.take() { 72 | let _ = close_tx.try_broadcast(()); 73 | } 74 | } 75 | 76 | async fn wait_for_stop(&self) { 77 | let wg = { 78 | let mut wg = self.wg.borrow_mut(); 79 | wg.take() 80 | }; 81 | if let Some(wg) = wg { 82 | wg.wait().await; 83 | } 84 | } 85 | 86 | async fn graceful_stop(&self) { 87 | self.stop().await; 88 | self.wait_for_stop().await; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/channel/handler.rs: -------------------------------------------------------------------------------- 1 | use crate::channel::handler_internal::{ContextInternal, HandlerInternal}; 2 | use log::{trace, warn}; 3 | use std::any::Any; 4 | use std::cell::RefCell; 5 | use std::marker::PhantomData; 6 | use std::rc::Rc; 7 | use std::{error::Error, time::Instant}; 8 | 9 | /// Handles both inbound and outbound events 10 | pub trait Handler { 11 | /// Associated read input message type 12 | type Rin: 'static; 13 | /// Associated read output message type 14 | type Rout: 'static; 15 | /// Associated write input message type 16 | type Win: 'static; 17 | /// Associated write output message type for 18 | type Wout: 'static; 19 | 20 | /// Returns handler name 21 | fn name(&self) -> &str; 22 | 23 | #[doc(hidden)] 24 | #[allow(clippy::type_complexity)] 25 | fn generate( 26 | self, 27 | ) -> ( 28 | String, 29 | Rc>, 30 | Rc>, 31 | ) 32 | where 33 | Self: Sized + 'static, 34 | { 35 | let handler_name = self.name().to_owned(); 36 | let context: Context = 37 | Context::new(self.name()); 38 | 39 | let handler: Box< 40 | dyn Handler, 41 | > = Box::new(self); 42 | 43 | ( 44 | handler_name, 45 | Rc::new(RefCell::new(handler)), 46 | Rc::new(RefCell::new(context)), 47 | ) 48 | } 49 | 50 | /// Transport is active now, which means it is connected. 51 | fn transport_active(&mut self, ctx: &Context) { 52 | ctx.fire_transport_active(); 53 | } 54 | /// Transport is inactive now, which means it is disconnected. 55 | fn transport_inactive(&mut self, ctx: &Context) { 56 | ctx.fire_transport_inactive(); 57 | } 58 | 59 | /// Handles input message. 60 | fn handle_read( 61 | &mut self, 62 | ctx: &Context, 63 | msg: Self::Rin, 64 | ); 65 | /// Polls output message from internal transmit queue 66 | fn poll_write( 67 | &mut self, 68 | ctx: &Context, 69 | ) -> Option; 70 | 71 | /// Handles a timeout event. 72 | fn handle_timeout( 73 | &mut self, 74 | ctx: &Context, 75 | now: Instant, 76 | ) { 77 | ctx.fire_timeout(now); 78 | } 79 | /// Polls earliest timeout (eto) in its inbound operations. 80 | fn poll_timeout( 81 | &mut self, 82 | ctx: &Context, 83 | eto: &mut Instant, 84 | ) { 85 | ctx.fire_poll_timeout(eto); 86 | } 87 | 88 | /// Reads an EOF event. 89 | fn handle_read_eof(&mut self, ctx: &Context) { 90 | ctx.fire_read_eof(); 91 | } 92 | /// Handle an Error exception in one of its operations. 93 | fn handle_exception( 94 | &mut self, 95 | ctx: &Context, 96 | err: Box, 97 | ) { 98 | ctx.fire_exception(err); 99 | } 100 | /// Handle a close event. 101 | fn handle_close(&mut self, ctx: &Context) { 102 | ctx.fire_close(); 103 | } 104 | } 105 | 106 | impl HandlerInternal 107 | for Box> 108 | { 109 | fn transport_active_internal(&mut self, ctx: &dyn ContextInternal) { 110 | if let Some(ctx) = ctx.as_any().downcast_ref::>() { 111 | self.transport_active(ctx); 112 | } else { 113 | panic!( 114 | "ctx can't downcast_ref::> in {} handler", 115 | ctx.name() 116 | ); 117 | } 118 | } 119 | fn transport_inactive_internal(&mut self, ctx: &dyn ContextInternal) { 120 | if let Some(ctx) = ctx.as_any().downcast_ref::>() { 121 | self.transport_inactive(ctx); 122 | } else { 123 | panic!( 124 | "ctx can't downcast_ref::> in {} handler", 125 | ctx.name() 126 | ); 127 | } 128 | } 129 | 130 | fn handle_read_internal(&mut self, ctx: &dyn ContextInternal, msg: Box) { 131 | if let Some(ctx) = ctx.as_any().downcast_ref::>() { 132 | if let Ok(msg) = msg.downcast::() { 133 | self.handle_read(ctx, *msg); 134 | } else { 135 | panic!("msg can't downcast:: in {} handler", ctx.name()); 136 | } 137 | } else { 138 | panic!( 139 | "ctx can't downcast::> in {} handler", 140 | ctx.name() 141 | ); 142 | } 143 | } 144 | fn poll_write_internal(&mut self, ctx: &dyn ContextInternal) -> Option> { 145 | if let Some(ctx) = ctx.as_any().downcast_ref::>() { 146 | if let Some(msg) = self.poll_write(ctx) { 147 | Some(Box::new(msg)) 148 | } else { 149 | None 150 | } 151 | } else { 152 | panic!( 153 | "ctx can't downcast_ref::> in {} handler", 154 | ctx.name() 155 | ); 156 | } 157 | } 158 | 159 | fn handle_timeout_internal(&mut self, ctx: &dyn ContextInternal, now: Instant) { 160 | if let Some(ctx) = ctx.as_any().downcast_ref::>() { 161 | self.handle_timeout(ctx, now); 162 | } else { 163 | panic!( 164 | "ctx can't downcast_ref::> in {} handler", 165 | ctx.name() 166 | ); 167 | } 168 | } 169 | fn poll_timeout_internal(&mut self, ctx: &dyn ContextInternal, eto: &mut Instant) { 170 | if let Some(ctx) = ctx.as_any().downcast_ref::>() { 171 | self.poll_timeout(ctx, eto); 172 | } else { 173 | panic!( 174 | "ctx can't downcast_ref::> in {} handler", 175 | ctx.name() 176 | ); 177 | } 178 | } 179 | 180 | fn handle_read_eof_internal(&mut self, ctx: &dyn ContextInternal) { 181 | if let Some(ctx) = ctx.as_any().downcast_ref::>() { 182 | self.handle_read_eof(ctx); 183 | } else { 184 | panic!( 185 | "ctx can't downcast_ref::> in {} handler", 186 | ctx.name() 187 | ); 188 | } 189 | } 190 | fn handle_exception_internal(&mut self, ctx: &dyn ContextInternal, err: Box) { 191 | if let Some(ctx) = ctx.as_any().downcast_ref::>() { 192 | self.handle_exception(ctx, err); 193 | } else { 194 | panic!( 195 | "ctx can't downcast_ref::> in {} handler", 196 | ctx.name() 197 | ); 198 | } 199 | } 200 | fn handle_close_internal(&mut self, ctx: &dyn ContextInternal) { 201 | if let Some(ctx) = ctx.as_any().downcast_ref::>() { 202 | self.handle_close(ctx); 203 | } else { 204 | panic!( 205 | "ctx can't downcast_ref::> in {} handler", 206 | ctx.name() 207 | ); 208 | } 209 | } 210 | } 211 | 212 | /// Enables a [Handler] to interact with its Pipeline and other handlers. 213 | pub struct Context { 214 | name: String, 215 | 216 | next_context: Option>>, 217 | next_handler: Option>>, 218 | 219 | phantom: PhantomData<(Rin, Rout, Win, Wout)>, 220 | } 221 | 222 | impl Context { 223 | /// Creates a new Context 224 | pub fn new(name: &str) -> Self { 225 | Self { 226 | name: name.to_string(), 227 | 228 | next_context: None, 229 | next_handler: None, 230 | 231 | phantom: PhantomData, 232 | } 233 | } 234 | 235 | /// Transport is active now, which means it is connected. 236 | pub fn fire_transport_active(&self) { 237 | if let (Some(next_handler), Some(next_context)) = (&self.next_handler, &self.next_context) { 238 | let (mut next_handler, next_context) = 239 | (next_handler.borrow_mut(), next_context.borrow()); 240 | next_handler.transport_active_internal(&*next_context); 241 | } 242 | } 243 | 244 | /// Transport is inactive now, which means it is disconnected. 245 | pub fn fire_transport_inactive(&self) { 246 | if let (Some(next_handler), Some(next_context)) = (&self.next_handler, &self.next_context) { 247 | let (mut next_handler, next_context) = 248 | (next_handler.borrow_mut(), next_context.borrow()); 249 | next_handler.transport_inactive_internal(&*next_context); 250 | } 251 | } 252 | 253 | /// Handle input message. 254 | pub fn fire_read(&self, msg: Rout) { 255 | if let (Some(next_handler), Some(next_context)) = (&self.next_handler, &self.next_context) { 256 | let (mut next_handler, next_context) = 257 | (next_handler.borrow_mut(), next_context.borrow()); 258 | next_handler.handle_read_internal(&*next_context, Box::new(msg)); 259 | } else { 260 | warn!("handle_read reached end of pipeline"); 261 | } 262 | } 263 | 264 | /// Polls output message. 265 | pub fn fire_poll_write(&self) -> Option { 266 | if let (Some(next_handler), Some(next_context)) = (&self.next_handler, &self.next_context) { 267 | let (mut next_handler, next_context) = 268 | (next_handler.borrow_mut(), next_context.borrow()); 269 | if let Some(msg) = next_handler.poll_write_internal(&*next_context) { 270 | if let Ok(msg) = msg.downcast::() { 271 | Some(*msg) 272 | } else { 273 | panic!( 274 | "msg can't downcast:: in {} handler", 275 | next_context.name() 276 | ); 277 | } 278 | } else { 279 | None 280 | } 281 | } else { 282 | warn!("poll_write reached end of pipeline"); 283 | None 284 | } 285 | } 286 | 287 | /// Handles a timeout event. 288 | pub fn fire_timeout(&self, now: Instant) { 289 | if let (Some(next_handler), Some(next_context)) = (&self.next_handler, &self.next_context) { 290 | let (mut next_handler, next_context) = 291 | (next_handler.borrow_mut(), next_context.borrow()); 292 | next_handler.handle_timeout_internal(&*next_context, now); 293 | } else { 294 | warn!("handle_timeout reached end of pipeline"); 295 | } 296 | } 297 | 298 | /// Polls earliest timeout (eto) in its inbound operations. 299 | pub fn fire_poll_timeout(&self, eto: &mut Instant) { 300 | if let (Some(next_handler), Some(next_context)) = (&self.next_handler, &self.next_context) { 301 | let (mut next_handler, next_context) = 302 | (next_handler.borrow_mut(), next_context.borrow()); 303 | next_handler.poll_timeout_internal(&*next_context, eto); 304 | } else { 305 | trace!("poll_timeout reached end of pipeline"); 306 | } 307 | } 308 | 309 | /// Reads an EOF event. 310 | pub fn fire_read_eof(&self) { 311 | if let (Some(next_handler), Some(next_context)) = (&self.next_handler, &self.next_context) { 312 | let (mut next_handler, next_context) = 313 | (next_handler.borrow_mut(), next_context.borrow()); 314 | next_handler.handle_read_eof_internal(&*next_context); 315 | } else { 316 | warn!("handle_read_eof reached end of pipeline"); 317 | } 318 | } 319 | 320 | /// Reads an Error exception in one of its inbound operations. 321 | pub fn fire_exception(&self, err: Box) { 322 | if let (Some(next_handler), Some(next_context)) = (&self.next_handler, &self.next_context) { 323 | let (mut next_handler, next_context) = 324 | (next_handler.borrow_mut(), next_context.borrow()); 325 | next_handler.handle_exception_internal(&*next_context, err); 326 | } else { 327 | warn!("handle_exception reached end of pipeline"); 328 | } 329 | } 330 | 331 | /// Writes a close event. 332 | pub fn fire_close(&self) { 333 | if let (Some(next_handler), Some(next_context)) = (&self.next_handler, &self.next_context) { 334 | let (mut next_handler, next_context) = 335 | (next_handler.borrow_mut(), next_context.borrow()); 336 | next_handler.handle_close_internal(&*next_context); 337 | } else { 338 | warn!("handle_close reached end of pipeline"); 339 | } 340 | } 341 | } 342 | 343 | impl ContextInternal 344 | for Context 345 | { 346 | fn fire_transport_active_internal(&self) { 347 | self.fire_transport_active(); 348 | } 349 | fn fire_transport_inactive_internal(&self) { 350 | self.fire_transport_inactive(); 351 | } 352 | fn fire_read_internal(&self, msg: Box) { 353 | if let Ok(msg) = msg.downcast::() { 354 | self.fire_read(*msg); 355 | } else { 356 | panic!("msg can't downcast:: in {} handler", self.name()); 357 | } 358 | } 359 | fn fire_poll_write_internal(&self) -> Option> { 360 | if let Some(msg) = self.fire_poll_write() { 361 | Some(Box::new(msg)) 362 | } else { 363 | None 364 | } 365 | } 366 | 367 | fn fire_timeout_internal(&self, now: Instant) { 368 | self.fire_timeout(now); 369 | } 370 | fn fire_poll_timeout_internal(&self, eto: &mut Instant) { 371 | self.fire_poll_timeout(eto); 372 | } 373 | 374 | fn fire_read_eof_internal(&self) { 375 | self.fire_read_eof(); 376 | } 377 | fn fire_exception_internal(&self, err: Box) { 378 | self.fire_exception(err); 379 | } 380 | fn fire_close_internal(&self) { 381 | self.fire_close(); 382 | } 383 | 384 | fn name(&self) -> &str { 385 | self.name.as_str() 386 | } 387 | fn as_any(&self) -> &dyn Any { 388 | self 389 | } 390 | fn set_next_context(&mut self, next_context: Option>>) { 391 | self.next_context = next_context; 392 | } 393 | fn set_next_handler(&mut self, next_handler: Option>>) { 394 | self.next_handler = next_handler; 395 | } 396 | } 397 | -------------------------------------------------------------------------------- /src/channel/handler_internal.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | use std::cell::RefCell; 3 | use std::error::Error; 4 | use std::rc::Rc; 5 | use std::time::Instant; 6 | 7 | #[doc(hidden)] 8 | pub trait HandlerInternal { 9 | fn transport_active_internal(&mut self, ctx: &dyn ContextInternal); 10 | fn transport_inactive_internal(&mut self, ctx: &dyn ContextInternal); 11 | 12 | fn handle_read_internal(&mut self, ctx: &dyn ContextInternal, msg: Box); 13 | fn poll_write_internal(&mut self, ctx: &dyn ContextInternal) -> Option>; 14 | 15 | fn handle_timeout_internal(&mut self, ctx: &dyn ContextInternal, now: Instant); 16 | fn poll_timeout_internal(&mut self, ctx: &dyn ContextInternal, eto: &mut Instant); 17 | 18 | fn handle_read_eof_internal(&mut self, ctx: &dyn ContextInternal); 19 | fn handle_exception_internal(&mut self, ctx: &dyn ContextInternal, err: Box); 20 | fn handle_close_internal(&mut self, ctx: &dyn ContextInternal); 21 | } 22 | 23 | #[doc(hidden)] 24 | pub trait ContextInternal { 25 | fn fire_transport_active_internal(&self); 26 | fn fire_transport_inactive_internal(&self); 27 | 28 | fn fire_read_internal(&self, msg: Box); 29 | fn fire_poll_write_internal(&self) -> Option>; 30 | 31 | fn fire_timeout_internal(&self, now: Instant); 32 | fn fire_poll_timeout_internal(&self, eto: &mut Instant); 33 | 34 | fn fire_read_eof_internal(&self); 35 | fn fire_exception_internal(&self, err: Box); 36 | fn fire_close_internal(&self); 37 | 38 | fn name(&self) -> &str; 39 | fn as_any(&self) -> &dyn Any; 40 | fn set_next_context(&mut self, next_in_context: Option>>); 41 | fn set_next_handler(&mut self, next_in_handler: Option>>); 42 | } 43 | -------------------------------------------------------------------------------- /src/channel/mod.rs: -------------------------------------------------------------------------------- 1 | //! The handler and pipeline APIs which are asynchronous and event-driven abstraction of various transports 2 | pub(crate) mod handler; 3 | pub(crate) mod handler_internal; 4 | pub(crate) mod pipeline; 5 | pub(crate) mod pipeline_internal; 6 | 7 | pub use self::{ 8 | handler::{Context, Handler}, 9 | pipeline::{InboundPipeline, OutboundPipeline, Pipeline}, 10 | }; 11 | -------------------------------------------------------------------------------- /src/channel/pipeline.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, error::Error, rc::Rc, time::Instant}; 2 | 3 | use crate::channel::{handler::Handler, pipeline_internal::PipelineInternal}; 4 | 5 | /// InboundPipeline 6 | pub trait InboundPipeline { 7 | /// Transport is active now, which means it is connected. 8 | fn transport_active(&self); 9 | 10 | /// Transport is inactive now, which means it is disconnected. 11 | fn transport_inactive(&self); 12 | 13 | /// Reads a message. 14 | fn read(&self, msg: R); 15 | 16 | /// Reads an EOF event. 17 | fn handle_read_eof(&self); 18 | 19 | /// Reads an Error exception in one of its inbound operations. 20 | fn handle_exception(&self, err: Box); 21 | 22 | /// Handles a timeout event. 23 | fn handle_timeout(&self, now: Instant); 24 | 25 | /// Polls an event. 26 | fn poll_timeout(&self, eto: &mut Instant); 27 | 28 | /// Polls an outgoing message 29 | fn poll_transmit(&self) -> Option; 30 | } 31 | 32 | /// OutboundPipeline 33 | pub trait OutboundPipeline { 34 | /// Writes a message. 35 | fn write(&self, msg: W); 36 | 37 | /// Writes a close event. 38 | fn close(&self); 39 | } 40 | 41 | /// Pipeline implements an advanced form of the Intercepting Filter pattern to give a user full control 42 | /// over how an event is handled and how the Handlers in a pipeline interact with each other. 43 | pub struct Pipeline { 44 | internal: RefCell>, 45 | } 46 | 47 | impl Default for Pipeline { 48 | fn default() -> Self { 49 | Self::new() 50 | } 51 | } 52 | 53 | impl Pipeline { 54 | /// Creates a new Pipeline 55 | pub fn new() -> Self { 56 | Self { 57 | internal: RefCell::new(PipelineInternal::new()), 58 | } 59 | } 60 | 61 | /// Appends a [Handler] at the last position of this pipeline. 62 | pub fn add_back(&self, handler: impl Handler + 'static) -> &Self { 63 | { 64 | let mut internal = self.internal.borrow_mut(); 65 | internal.add_back(handler); 66 | } 67 | self 68 | } 69 | 70 | /// Inserts a [Handler] at the first position of this pipeline. 71 | pub fn add_front(&self, handler: impl Handler + 'static) -> &Self { 72 | { 73 | let mut internal = self.internal.borrow_mut(); 74 | internal.add_front(handler); 75 | } 76 | self 77 | } 78 | 79 | /// Removes a [Handler] at the last position of this pipeline. 80 | pub fn remove_back(&self) -> Result<&Self, std::io::Error> { 81 | let result = { 82 | let mut internal = self.internal.borrow_mut(); 83 | internal.remove_back() 84 | }; 85 | match result { 86 | Ok(()) => Ok(self), 87 | Err(err) => Err(err), 88 | } 89 | } 90 | 91 | /// Removes a [Handler] at the first position of this pipeline. 92 | pub fn remove_front(&self) -> Result<&Self, std::io::Error> { 93 | let result = { 94 | let mut internal = self.internal.borrow_mut(); 95 | internal.remove_front() 96 | }; 97 | match result { 98 | Ok(()) => Ok(self), 99 | Err(err) => Err(err), 100 | } 101 | } 102 | 103 | /// Removes a [Handler] from this pipeline based on handler_name. 104 | pub fn remove(&self, handler_name: &str) -> Result<&Self, std::io::Error> { 105 | let result = { 106 | let mut internal = self.internal.borrow_mut(); 107 | internal.remove(handler_name) 108 | }; 109 | match result { 110 | Ok(()) => Ok(self), 111 | Err(err) => Err(err), 112 | } 113 | } 114 | 115 | #[allow(clippy::len_without_is_empty)] 116 | /// Returns the number of Handlers in this pipeline. 117 | pub fn len(&self) -> usize { 118 | let internal = self.internal.borrow(); 119 | internal.len() 120 | } 121 | 122 | /// Updates the Rc version's pipeline. 123 | pub fn update(self: Rc) -> Rc { 124 | { 125 | let internal = self.internal.borrow(); 126 | internal.finalize(); 127 | } 128 | self 129 | } 130 | 131 | /// Finalizes the pipeline. 132 | pub fn finalize(self) -> Rc { 133 | let pipeline = Rc::new(self); 134 | pipeline.update() 135 | } 136 | } 137 | 138 | impl InboundPipeline for Pipeline { 139 | /// Transport is active now, which means it is connected. 140 | fn transport_active(&self) { 141 | let internal = self.internal.borrow(); 142 | internal.transport_active(); 143 | } 144 | 145 | /// Transport is inactive now, which means it is disconnected. 146 | fn transport_inactive(&self) { 147 | let internal = self.internal.borrow(); 148 | internal.transport_inactive(); 149 | } 150 | 151 | /// Reads a message. 152 | fn read(&self, msg: R) { 153 | let internal = self.internal.borrow(); 154 | internal.handle_read(msg); 155 | } 156 | 157 | /// Reads an EOF event. 158 | fn handle_read_eof(&self) { 159 | let internal = self.internal.borrow(); 160 | internal.handle_read_eof(); 161 | } 162 | 163 | /// Reads an Error exception in one of its inbound operations. 164 | fn handle_exception(&self, err: Box) { 165 | let internal = self.internal.borrow(); 166 | internal.handle_exception(err); 167 | } 168 | 169 | /// Handles a timeout event. 170 | fn handle_timeout(&self, now: Instant) { 171 | let internal = self.internal.borrow(); 172 | internal.handle_timeout(now); 173 | } 174 | 175 | /// Polls earliest timeout (eto) in its inbound operations. 176 | fn poll_timeout(&self, eto: &mut Instant) { 177 | let internal = self.internal.borrow(); 178 | internal.poll_timeout(eto); 179 | } 180 | 181 | /// Polls an outgoing message 182 | fn poll_transmit(&self) -> Option { 183 | let internal = self.internal.borrow(); 184 | internal.poll_write() 185 | } 186 | } 187 | 188 | impl OutboundPipeline for Pipeline { 189 | /// Writes a message to pipeline 190 | fn write(&self, msg: W) { 191 | let internal = self.internal.borrow(); 192 | internal.write(msg); 193 | } 194 | 195 | /// Writes a close event. 196 | fn close(&self) { 197 | let internal = self.internal.borrow(); 198 | internal.handle_close(); 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/channel/pipeline_internal.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | use std::{cell::RefCell, error::Error, io::ErrorKind, marker::PhantomData, rc::Rc, time::Instant}; 3 | 4 | use crate::channel::{ 5 | handler::Handler, 6 | handler_internal::{ContextInternal, HandlerInternal}, 7 | Context, 8 | }; 9 | 10 | const RESERVED_RETTY_PIPELINE_HANDLE_NAME: &str = "ReservedRettyPipelineHandlerName"; 11 | 12 | pub(crate) struct PipelineInternal { 13 | names: Vec, 14 | handlers: Vec>>, 15 | contexts: Vec>>, 16 | 17 | transmits: Rc>>, 18 | phantom: PhantomData, 19 | } 20 | 21 | impl PipelineInternal { 22 | pub(crate) fn new() -> Self { 23 | let transmits = Rc::new(RefCell::new(VecDeque::new())); 24 | let last_handler = LastHandler::new(transmits.clone()); 25 | let (name, handler, context) = last_handler.generate(); 26 | Self { 27 | names: vec![name], 28 | handlers: vec![handler], 29 | contexts: vec![context], 30 | 31 | transmits, 32 | phantom: PhantomData, 33 | } 34 | } 35 | 36 | pub(crate) fn add_back(&mut self, handler: impl Handler + 'static) { 37 | let (name, handler, context) = handler.generate(); 38 | if name == RESERVED_RETTY_PIPELINE_HANDLE_NAME { 39 | panic!("handle name {} is reserved", name); 40 | } 41 | 42 | let len = self.names.len(); 43 | 44 | self.names.insert(len - 1, name); 45 | self.handlers.insert(len - 1, handler); 46 | self.contexts.insert(len - 1, context); 47 | } 48 | 49 | pub(crate) fn add_front(&mut self, handler: impl Handler + 'static) { 50 | let (name, handler, context) = handler.generate(); 51 | if name == RESERVED_RETTY_PIPELINE_HANDLE_NAME { 52 | panic!("handle name {} is reserved", name); 53 | } 54 | 55 | self.names.insert(0, name); 56 | self.handlers.insert(0, handler); 57 | self.contexts.insert(0, context); 58 | } 59 | 60 | pub(crate) fn remove_back(&mut self) -> Result<(), std::io::Error> { 61 | let len = self.names.len(); 62 | if len == 1 { 63 | Err(std::io::Error::new( 64 | ErrorKind::NotFound, 65 | "No handlers in pipeline", 66 | )) 67 | } else { 68 | self.names.remove(len - 2); 69 | self.handlers.remove(len - 2); 70 | self.contexts.remove(len - 2); 71 | 72 | Ok(()) 73 | } 74 | } 75 | 76 | pub(crate) fn remove_front(&mut self) -> Result<(), std::io::Error> { 77 | let len = self.names.len(); 78 | if len == 1 { 79 | Err(std::io::Error::new( 80 | ErrorKind::NotFound, 81 | "No handlers in pipeline", 82 | )) 83 | } else { 84 | self.names.remove(0); 85 | self.handlers.remove(0); 86 | self.contexts.remove(0); 87 | 88 | Ok(()) 89 | } 90 | } 91 | 92 | pub(crate) fn remove(&mut self, handler_name: &str) -> Result<(), std::io::Error> { 93 | if handler_name == RESERVED_RETTY_PIPELINE_HANDLE_NAME { 94 | return Err(std::io::Error::new( 95 | ErrorKind::PermissionDenied, 96 | format!("handle name {} is reserved", handler_name), 97 | )); 98 | } 99 | 100 | let mut to_be_removed = vec![]; 101 | for (index, name) in self.names.iter().enumerate() { 102 | if name == handler_name { 103 | to_be_removed.push(index); 104 | } 105 | } 106 | 107 | if !to_be_removed.is_empty() { 108 | for index in to_be_removed.into_iter().rev() { 109 | self.names.remove(index); 110 | self.handlers.remove(index); 111 | self.contexts.remove(index); 112 | } 113 | 114 | Ok(()) 115 | } else { 116 | Err(std::io::Error::new( 117 | ErrorKind::NotFound, 118 | format!("No such handler \"{}\" in pipeline", handler_name), 119 | )) 120 | } 121 | } 122 | 123 | pub(crate) fn len(&self) -> usize { 124 | self.names.len() - 1 125 | } 126 | 127 | pub(crate) fn finalize(&self) { 128 | let mut enumerate = self.contexts.iter().enumerate(); 129 | let ctx_pipe_len = self.contexts.len(); 130 | for _ in 0..ctx_pipe_len { 131 | let (j, ctx) = enumerate.next().unwrap(); 132 | let mut curr = ctx.borrow_mut(); 133 | 134 | let (next_context, next_handler) = (self.contexts.get(j + 1), self.handlers.get(j + 1)); 135 | match (next_context, next_handler) { 136 | (Some(next_ctx), Some(next_hdr)) => { 137 | curr.set_next_context(Some(next_ctx.clone())); 138 | curr.set_next_handler(Some(next_hdr.clone())); 139 | } 140 | _ => { 141 | curr.set_next_context(None); 142 | curr.set_next_handler(None); 143 | } 144 | } 145 | } 146 | } 147 | 148 | pub(crate) fn write(&self, msg: W) { 149 | let mut transmits = self.transmits.borrow_mut(); 150 | transmits.push_back(msg); 151 | } 152 | 153 | pub(crate) fn transport_active(&self) { 154 | let (mut handler, context) = ( 155 | self.handlers.first().unwrap().borrow_mut(), 156 | self.contexts.first().unwrap().borrow(), 157 | ); 158 | handler.transport_active_internal(&*context); 159 | } 160 | 161 | pub(crate) fn transport_inactive(&self) { 162 | let (mut handler, context) = ( 163 | self.handlers.first().unwrap().borrow_mut(), 164 | self.contexts.first().unwrap().borrow(), 165 | ); 166 | handler.transport_inactive_internal(&*context); 167 | } 168 | 169 | pub(crate) fn handle_read(&self, msg: R) { 170 | let (mut handler, context) = ( 171 | self.handlers.first().unwrap().borrow_mut(), 172 | self.contexts.first().unwrap().borrow(), 173 | ); 174 | handler.handle_read_internal(&*context, Box::new(msg)); 175 | } 176 | 177 | pub(crate) fn poll_write(&self) -> Option { 178 | let (mut handler, context) = ( 179 | self.handlers.first().unwrap().borrow_mut(), 180 | self.contexts.first().unwrap().borrow(), 181 | ); 182 | if let Some(msg) = handler.poll_write_internal(&*context) { 183 | if let Ok(msg) = msg.downcast::() { 184 | Some(*msg) 185 | } else { 186 | panic!("msg can't downcast:: in {} handler", context.name()); 187 | } 188 | } else { 189 | None 190 | } 191 | } 192 | 193 | pub(crate) fn handle_close(&self) { 194 | let (mut handler, context) = ( 195 | self.handlers.first().unwrap().borrow_mut(), 196 | self.contexts.first().unwrap().borrow(), 197 | ); 198 | handler.handle_close_internal(&*context); 199 | } 200 | 201 | pub(crate) fn handle_timeout(&self, now: Instant) { 202 | let (mut handler, context) = ( 203 | self.handlers.first().unwrap().borrow_mut(), 204 | self.contexts.first().unwrap().borrow(), 205 | ); 206 | handler.handle_timeout_internal(&*context, now); 207 | } 208 | 209 | pub(crate) fn poll_timeout(&self, eto: &mut Instant) { 210 | let (mut handler, context) = ( 211 | self.handlers.first().unwrap().borrow_mut(), 212 | self.contexts.first().unwrap().borrow(), 213 | ); 214 | handler.poll_timeout_internal(&*context, eto); 215 | } 216 | 217 | pub(crate) fn handle_read_eof(&self) { 218 | let (mut handler, context) = ( 219 | self.handlers.first().unwrap().borrow_mut(), 220 | self.contexts.first().unwrap().borrow(), 221 | ); 222 | handler.handle_read_eof_internal(&*context); 223 | } 224 | 225 | pub(crate) fn handle_exception(&self, err: Box) { 226 | let (mut handler, context) = ( 227 | self.handlers.first().unwrap().borrow_mut(), 228 | self.contexts.first().unwrap().borrow(), 229 | ); 230 | handler.handle_exception_internal(&*context, err); 231 | } 232 | } 233 | 234 | pub(crate) struct LastHandler { 235 | transmits: Rc>>, 236 | } 237 | 238 | impl LastHandler { 239 | pub(crate) fn new(transmits: Rc>>) -> Self { 240 | Self { transmits } 241 | } 242 | } 243 | 244 | impl Handler for LastHandler { 245 | type Rin = W; 246 | type Rout = Self::Rin; 247 | type Win = Self::Rin; 248 | type Wout = Self::Rin; 249 | 250 | fn name(&self) -> &str { 251 | RESERVED_RETTY_PIPELINE_HANDLE_NAME 252 | } 253 | 254 | fn handle_read( 255 | &mut self, 256 | ctx: &Context, 257 | msg: Self::Rin, 258 | ) { 259 | ctx.fire_read(msg); 260 | } 261 | 262 | fn poll_write( 263 | &mut self, 264 | _ctx: &Context, 265 | ) -> Option { 266 | let mut transmits = self.transmits.borrow_mut(); 267 | transmits.pop_front() 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /src/codec/byte_to_message_decoder/line_based_frame_decoder.rs: -------------------------------------------------------------------------------- 1 | use crate::codec::byte_to_message_decoder::MessageDecoder; 2 | 3 | use bytes::BytesMut; 4 | use std::io::ErrorKind; 5 | 6 | /// Delimiter with different terminator type \n` or `\r\n` 7 | #[derive(Default, PartialEq, Eq)] 8 | pub enum TerminatorType { 9 | /// Delimiter with \n` or `\r\n` 10 | #[default] 11 | BOTH, 12 | 13 | /// Delimiter with `\n` only 14 | NEWLINE, 15 | 16 | /// Delimiter with `\r\n` only 17 | CarriageNewline, 18 | } 19 | 20 | /// A line based frame decoder with [TerminatorType] as delimiter 21 | #[derive(Default)] 22 | pub struct LineBasedFrameDecoder { 23 | max_length: usize, 24 | strip_delimiter: bool, 25 | terminator_type: TerminatorType, 26 | 27 | discarding: bool, 28 | discarded_bytes: usize, 29 | } 30 | 31 | impl LineBasedFrameDecoder { 32 | /// Creates a new LineBasedFrameDecoder 33 | pub fn new(max_length: usize, strip_delimiter: bool, terminator_type: TerminatorType) -> Self { 34 | Self { 35 | max_length, 36 | strip_delimiter, 37 | terminator_type, 38 | ..Default::default() 39 | } 40 | } 41 | 42 | fn find_end_of_line(&mut self, buf: &BytesMut) -> Option { 43 | let mut i = 0usize; 44 | while i < self.max_length && i < buf.len() { 45 | let b = buf[i]; 46 | if (b == b'\n' && self.terminator_type != TerminatorType::CarriageNewline) 47 | || (self.terminator_type != TerminatorType::NEWLINE 48 | && b == b'\r' 49 | && i + 1 < buf.len() 50 | && buf[i + 1] == b'\n') 51 | { 52 | return Some(i); 53 | } 54 | i += 1; 55 | } 56 | 57 | None 58 | } 59 | } 60 | 61 | impl MessageDecoder for LineBasedFrameDecoder { 62 | fn decode(&mut self, buf: &mut BytesMut) -> Result, std::io::Error> { 63 | let eol = self.find_end_of_line(buf); 64 | let mut offset = 0; 65 | if !self.discarding { 66 | if let Some(eol) = eol { 67 | offset += eol; 68 | let delim_length = if buf[offset] == b'\r' { 2 } else { 1 }; 69 | if eol > self.max_length { 70 | return Err(std::io::Error::new( 71 | ErrorKind::Other, 72 | format!("frame length {} exceeds max {}", eol, self.max_length), 73 | )); 74 | } 75 | 76 | let frame = if self.strip_delimiter { 77 | let frame = buf.split_to(eol); 78 | let _ = buf.split_to(delim_length); 79 | frame 80 | } else { 81 | buf.split_to(eol + delim_length) 82 | }; 83 | 84 | Ok(Some(frame)) 85 | } else { 86 | let len = buf.len(); 87 | if len > self.max_length { 88 | self.discarded_bytes = len; 89 | let _ = buf.split_to(len); 90 | self.discarding = true; 91 | Err(std::io::Error::new( 92 | ErrorKind::Other, 93 | format!("over {}", len), 94 | )) 95 | } else { 96 | Ok(None) 97 | } 98 | } 99 | } else { 100 | if let Some(eol) = eol { 101 | offset += eol; 102 | let delim_length = if buf[offset] == b'\r' { 2 } else { 1 }; 103 | let _ = buf.split_to(eol + delim_length); 104 | self.discarded_bytes = 0; 105 | self.discarding = false; 106 | } else { 107 | self.discarded_bytes = buf.len(); 108 | let _ = buf.split_to(buf.len()); 109 | } 110 | 111 | Ok(None) 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/codec/byte_to_message_decoder/mod.rs: -------------------------------------------------------------------------------- 1 | //! Handlers for converting byte to message 2 | use crate::channel::{Context, Handler}; 3 | use crate::transport::TaggedBytesMut; 4 | use bytes::BytesMut; 5 | use std::time::Instant; 6 | 7 | mod line_based_frame_decoder; 8 | 9 | pub use line_based_frame_decoder::{LineBasedFrameDecoder, TerminatorType}; 10 | 11 | /// This trait allows for decoding messages. 12 | pub trait MessageDecoder { 13 | /// Decodes byte buffer to message buffer 14 | fn decode(&mut self, buf: &mut BytesMut) -> Result, std::io::Error>; 15 | } 16 | 17 | /// A tagged Byte to Message Codec handler that reads with input of TaggedBytesMut and output of TaggedBytesMut, 18 | /// or writes with input of TaggedBytesMut and output of TaggedBytesMut 19 | pub struct TaggedByteToMessageCodec { 20 | transport_active: bool, 21 | message_decoder: Box, 22 | } 23 | 24 | impl TaggedByteToMessageCodec { 25 | /// Creates a new TaggedByteToMessageCodec handler 26 | pub fn new(message_decoder: Box) -> Self { 27 | Self { 28 | transport_active: false, 29 | message_decoder, 30 | } 31 | } 32 | } 33 | 34 | impl Handler for TaggedByteToMessageCodec { 35 | type Rin = TaggedBytesMut; 36 | type Rout = Self::Rin; 37 | type Win = TaggedBytesMut; 38 | type Wout = Self::Win; 39 | 40 | fn name(&self) -> &str { 41 | "TaggedByteToMessageCodec" 42 | } 43 | 44 | fn transport_active(&mut self, ctx: &Context) { 45 | self.transport_active = true; 46 | ctx.fire_transport_active(); 47 | } 48 | fn transport_inactive(&mut self, ctx: &Context) { 49 | self.transport_active = false; 50 | ctx.fire_transport_inactive(); 51 | } 52 | fn handle_read( 53 | &mut self, 54 | ctx: &Context, 55 | mut msg: Self::Rin, 56 | ) { 57 | while self.transport_active { 58 | match self.message_decoder.decode(&mut msg.message) { 59 | Ok(message) => { 60 | if let Some(message) = message { 61 | ctx.fire_read(TaggedBytesMut { 62 | now: Instant::now(), 63 | transport: msg.transport, 64 | message, 65 | }); 66 | } else { 67 | return; 68 | } 69 | } 70 | Err(err) => { 71 | ctx.fire_exception(Box::new(err)); 72 | return; 73 | } 74 | } 75 | } 76 | } 77 | 78 | fn poll_write( 79 | &mut self, 80 | ctx: &Context, 81 | ) -> Option { 82 | ctx.fire_poll_write() 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/codec/mod.rs: -------------------------------------------------------------------------------- 1 | //! Extensible encoder/decoder and its common implementations which deal with the packet fragmentation 2 | //! and reassembly issue found in a stream-based transport such as TCP or datagram-based transport such as UDP. 3 | 4 | pub mod byte_to_message_decoder; 5 | pub mod string_codec; 6 | -------------------------------------------------------------------------------- /src/codec/string_codec/mod.rs: -------------------------------------------------------------------------------- 1 | //! Handlers for converting between TaggedBytesMut and TaggedString 2 | 3 | use bytes::{BufMut, BytesMut}; 4 | use std::time::Instant; 5 | 6 | use crate::channel::{Context, Handler}; 7 | use crate::transport::{TaggedBytesMut, TaggedString}; 8 | 9 | /// A tagged StringCodec handler that reads with input of TaggedBytesMut and output of TaggedString, 10 | /// or writes with input of TaggedString and output of TaggedBytesMut 11 | pub struct TaggedStringCodec; 12 | 13 | impl Default for TaggedStringCodec { 14 | fn default() -> Self { 15 | Self::new() 16 | } 17 | } 18 | 19 | impl TaggedStringCodec { 20 | /// Creates a new TaggedStringCodec handler 21 | pub fn new() -> Self { 22 | Self {} 23 | } 24 | } 25 | 26 | impl Handler for TaggedStringCodec { 27 | type Rin = TaggedBytesMut; 28 | type Rout = TaggedString; 29 | type Win = TaggedString; 30 | type Wout = TaggedBytesMut; 31 | 32 | fn name(&self) -> &str { 33 | "TaggedStringCodec" 34 | } 35 | 36 | fn handle_read( 37 | &mut self, 38 | ctx: &Context, 39 | msg: Self::Rin, 40 | ) { 41 | match String::from_utf8(msg.message.to_vec()) { 42 | Ok(message) => { 43 | ctx.fire_read(TaggedString { 44 | now: msg.now, 45 | transport: msg.transport, 46 | message, 47 | }); 48 | } 49 | Err(err) => ctx.fire_exception(err.into()), 50 | } 51 | } 52 | 53 | fn poll_write( 54 | &mut self, 55 | ctx: &Context, 56 | ) -> Option { 57 | if let Some(msg) = ctx.fire_poll_write() { 58 | let mut buf = BytesMut::new(); 59 | buf.put(msg.message.as_bytes()); 60 | Some(TaggedBytesMut { 61 | now: Instant::now(), 62 | transport: msg.transport, 63 | message: buf, 64 | }) 65 | } else { 66 | None 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/executor/mod.rs: -------------------------------------------------------------------------------- 1 | //! Async executors. 2 | 3 | use core_affinity::{set_for_current, CoreId}; 4 | use scoped_tls::scoped_thread_local; 5 | use smol::{LocalExecutor, Task}; 6 | use std::{ 7 | future::Future, 8 | io::Result, 9 | thread::{self, JoinHandle}, 10 | }; 11 | 12 | scoped_thread_local!(static LOCAL_EX: LocalExecutor<'_>); 13 | 14 | /// A factory that can be used to configure and create a [`LocalExecutor`]. 15 | #[derive(Debug, Default)] 16 | pub struct LocalExecutorBuilder { 17 | core_id: Option, 18 | name: String, 19 | } 20 | 21 | impl LocalExecutorBuilder { 22 | /// Creates a new LocalExecutorBuilder 23 | pub fn new() -> Self { 24 | Self::default() 25 | } 26 | 27 | /// Names the thread-to-be. Currently, the name is used for identification only in panic messages. 28 | pub fn name(mut self, name: &str) -> Self { 29 | self.name = String::from(name); 30 | self 31 | } 32 | 33 | /// Pins the thread to the specified CPU core 34 | pub fn core_id(mut self, core_id: CoreId) -> Self { 35 | self.core_id = Some(core_id); 36 | self 37 | } 38 | 39 | /// Runs the local executor on the current thread until the given future completes. 40 | pub fn run(mut self, f: impl Future) -> T { 41 | if let Some(core_id) = self.core_id.take() { 42 | set_for_current(core_id); 43 | } 44 | 45 | let local_ex = LocalExecutor::new(); 46 | LOCAL_EX.set(&local_ex, || { 47 | futures_lite::future::block_on(local_ex.run(f)) 48 | }) 49 | } 50 | 51 | /// Spawns a thread to run the local executor until the given future completes. 52 | pub fn spawn(mut self, fut_gen: G) -> Result> 53 | where 54 | G: FnOnce() -> F + Send + 'static, 55 | F: Future + 'static, 56 | T: Send + 'static, 57 | { 58 | let mut core_id = self.core_id.take(); 59 | 60 | thread::Builder::new().name(self.name).spawn(move || { 61 | if let Some(core_id) = core_id.take() { 62 | set_for_current(core_id); 63 | } 64 | 65 | let local_ex = LocalExecutor::new(); 66 | LOCAL_EX.set(&local_ex, || { 67 | futures_lite::future::block_on(local_ex.run(fut_gen())) 68 | }) 69 | }) 70 | } 71 | } 72 | 73 | /// Spawns a task onto the current single-threaded executor. 74 | /// 75 | /// If called from a [`LocalExecutor`], the task is spawned on it. 76 | /// Otherwise, this method panics. 77 | pub fn spawn_local(future: impl Future + 'static) -> Task { 78 | if LOCAL_EX.is_set() { 79 | LOCAL_EX.with(|local_ex| local_ex.spawn(future)) 80 | } else { 81 | panic!("`spawn_local()` must be called from a `LocalExecutor`") 82 | } 83 | } 84 | 85 | /// Attempts to yield local to run a task if at least one is scheduled. 86 | /// Running a scheduled task means simply polling its future once. 87 | pub fn try_yield_local() -> bool { 88 | if LOCAL_EX.is_set() { 89 | LOCAL_EX.with(|local_ex| local_ex.try_tick()) 90 | } else { 91 | panic!("`try_yield_local()` must be called from a `LocalExecutor`") 92 | } 93 | } 94 | 95 | /// Yield local to run other tasks until there is no other pending task. 96 | pub fn yield_local() { 97 | if LOCAL_EX.is_set() { 98 | LOCAL_EX.with(|local_ex| while local_ex.try_tick() {}) 99 | } else { 100 | panic!("`try_yield_local()` must be called from a `LocalExecutor`") 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! ### What is Retty? 2 | //! Retty is an asynchronous Rust networking framework that makes it easy to build protocols, application clients/servers. 3 | //! 4 | //! It's like [Netty](https://netty.io) or [Wangle](https://github.com/facebook/wangle), but in Rust. 5 | //! 6 | //! ### What is a Pipeline? 7 | //! The fundamental abstraction of Retty is the [Pipeline](crate::channel::Pipeline). 8 | //! It offers immense flexibility to customize how requests and responses are handled by your service. 9 | //! Once you have fully understood this abstraction, 10 | //! you will be able to write all sorts of sophisticated protocols, application clients/servers. 11 | //! 12 | //! A [Pipeline](crate::channel::Pipeline) is a chain of request/response [handlers](crate::channel::Handler) that handle inbound request and 13 | //! outbound response. Once you chain handlers together, it provides an agile way to convert 14 | //! a raw data stream into the desired message type and the inverse -- desired message type to raw data stream. 15 | //! Pipeline implements an advanced form of the Intercepting Filter pattern to give a user full control 16 | //! over how an event is handled and how the handlers in a pipeline interact with each other. 17 | //! 18 | //! A [Handler](crate::channel::Handler) should do one and only one function - just like the UNIX philosophy. If you have a handler that 19 | //! is doing more than one function than you should split it into individual handlers. This is really important for 20 | //! maintainability and flexibility as its common to change your protocol for one reason or the other. 21 | //! 22 | //! ### How does an event flow in a Pipeline? 23 | //! ```text 24 | //! | write() 25 | //! +---------------------------------------------------+---------------+ 26 | //! | Pipeline | | 27 | //! | \|/ | 28 | //! | +----------+----------+------------+-----------+----------+ | 29 | //! | | Handler N | | 30 | //! | +----------+----------+------------+-----------+----------+ | 31 | //! | /|\ | | 32 | //! | | | | 33 | //! | | | | 34 | //! | | \|/ | 35 | //! | +----------+----------+------------+-----------+----------+ | 36 | //! | | Handler N-1 | | 37 | //! | +----------+----------+------------+-----------+----------+ | 38 | //! | /|\ | | 39 | //! | | | | 40 | //! | | Context.fire_poll_write() | 41 | //! | | | | 42 | //! | | | | 43 | //! | Context.fire_read() | | 44 | //! | | | | 45 | //! | | \|/ | 46 | //! | +----------+----------+------------+-----------+----------+ | 47 | //! | | Handler 2 | | 48 | //! | +----------+----------+------------+-----------+----------+ | 49 | //! | /|\ | | 50 | //! | | | | 51 | //! | | | | 52 | //! | | \|/ | 53 | //! | +----------+----------+------------+-----------+----------+ | 54 | //! | | Handler 1 | | 55 | //! | +----------+----------+------------+-----------+----------+ | 56 | //! | /|\ | | 57 | //! +---------------+-----------------------------------+---------------+ 58 | //! | read() | poll_write() 59 | //! | \|/ 60 | //! +---------------+-----------------------------------+---------------+ 61 | //! | | | | 62 | //! | Internal I/O Threads (Transport Implementation) | 63 | //! +-------------------------------------------------------------------+ 64 | //! ``` 65 | //! 66 | //! ### Echo Server Example 67 | //! Let's look at how to write an echo server. 68 | //! 69 | //! Here's the main piece of code in our echo server; it receives a string from inbound direction in the pipeline, 70 | //! prints it to stdout and sends it back to outbound direction in the pipeline. It's really important to add the 71 | //! line delimiter because our pipeline will use a line decoder. 72 | //! ```ignore 73 | //! struct EchoServerHandler { 74 | //! transmits: VecDeque, 75 | //! } 76 | //! 77 | //! impl Handler for EchoServerHandler { 78 | //! type Rin = TaggedString; 79 | //! type Rout = Self::Rin; 80 | //! type Win = TaggedString; 81 | //! type Wout = Self::Win; 82 | //! 83 | //! fn name(&self) -> &str { 84 | //! "EchoServerHandler" 85 | //! } 86 | //! 87 | //! fn handle_read( 88 | //! &mut self, 89 | //! _ctx: &Context, 90 | //! msg: Self::Rin, 91 | //! ) { 92 | //! println!("handling {}", msg.message); 93 | //! self.transmits.push_back(TaggedString { 94 | //! now: Instant::now(), 95 | //! transport: msg.transport, 96 | //! message: format!("{}\r\n", msg.message), 97 | //! }); 98 | //! } 99 | //! 100 | //! fn poll_write( 101 | //! &mut self, 102 | //! ctx: &Context, 103 | //! ) -> Option { 104 | //! if let Some(msg) = ctx.fire_poll_write() { 105 | //! self.transmits.push_back(msg); 106 | //! } 107 | //! self.transmits.pop_front() 108 | //! } 109 | //! } 110 | //! ``` 111 | //! 112 | //! This needs to be the final handler in the pipeline. Now the definition of the pipeline is needed to handle the requests and responses. 113 | //! ```ignore 114 | //! let mut bootstrap = BootstrapServerTcp::new(); 115 | //! bootstrap.pipeline(Box::new(move || { 116 | //! let pipeline: Pipeline = Pipeline::new(); 117 | //! 118 | //! let line_based_frame_decoder_handler = TaggedByteToMessageCodec::new(Box::new( 119 | //! LineBasedFrameDecoder::new(8192, true, TerminatorType::BOTH), 120 | //! )); 121 | //! let string_codec_handler = TaggedStringCodec::new(); 122 | //! let echo_server_handler = EchoServerHandler::new(); 123 | //! 124 | //! pipeline.add_back(line_based_frame_decoder_handler); 125 | //! pipeline.add_back(string_codec_handler); 126 | //! pipeline.add_back(echo_server_handler); 127 | //! pipeline.finalize() 128 | //! })); 129 | //! ``` 130 | //! 131 | //! It is very important to be strict in the order of insertion as they are ordered by insertion. The pipeline has 4 handlers: 132 | //! 133 | //! * [TaggedByteToMessageCodec](crate::codec::byte_to_message_decoder::TaggedByteToMessageCodec) 134 | //! * Inbound: receives a zero-copy byte buffer and splits on line-endings 135 | //! * Outbound: just passes the byte buffer to AsyncTransport 136 | //! * [TaggedStringCodec](crate::codec::string_codec::TaggedStringCodec) 137 | //! * Inbound: receives a byte buffer and decodes it into a std::string and pass up to the EchoHandler. 138 | //! * Outbound: receives a std::string and encodes it into a byte buffer and pass down to the TaggedByteToMessageCodec. 139 | //! * EchoHandler 140 | //! * Inbound: receives a std::string and writes it to the pipeline, which will send the message outbound. 141 | //! * Outbound: receives a std::string and forwards it to TaggedStringCodec. 142 | //! 143 | //! Now that all needs to be done is plug the pipeline factory into a [BootstrapServerTcp](crate::bootstrap::BootstrapTcpServer) and that’s pretty much it. 144 | //! Bind a local host:port and wait for it to stop. 145 | //! 146 | //! ```ignore 147 | //! bootstrap.bind(format!("{}:{}", host, port)).await?; 148 | //! 149 | //! println!("Press ctrl-c to stop"); 150 | //! tokio::select! { 151 | //! _ = tokio::signal::ctrl_c() => { 152 | //! bootstrap.graceful_stop().await; 153 | //! } 154 | //! }; 155 | //! ``` 156 | //! 157 | //! ### Echo Client Example 158 | //! The code for the echo client is very similar to the Echo Server. Here is the main echo handler. 159 | //! 160 | //! ```ignore 161 | //! struct EchoClientHandler; 162 | //! 163 | //! impl Handler for EchoClientHandler { 164 | //! type Rin = TaggedString; 165 | //! type Rout = Self::Rin; 166 | //! type Win = TaggedString; 167 | //! type Wout = Self::Win; 168 | //! 169 | //! fn name(&self) -> &str { 170 | //! "EchoClientHandler" 171 | //! } 172 | //! 173 | //! fn handle_read( 174 | //! &mut self, 175 | //! _ctx: &Context, 176 | //! msg: Self::Rin, 177 | //! ) { 178 | //! println!("received back: {}", msg.message); 179 | //! } 180 | //! 181 | //! fn read_exception( 182 | //! &mut self, 183 | //! ctx: &Context, 184 | //! err: Box, 185 | //! ) { 186 | //! println!("received exception: {}", err); 187 | //! ctx.fire_close(); 188 | //! } 189 | //! 190 | //! fn read_eof(&mut self, ctx: &Context) { 191 | //! println!("EOF received :("); 192 | //! ctx.fire_close(); 193 | //! } 194 | //! 195 | //! fn poll_write( 196 | //! &mut self, 197 | //! ctx: &Context, 198 | //! ) -> Option { 199 | //! ctx.fire_poll_write() 200 | //! } 201 | //! } 202 | //! ``` 203 | //! 204 | //! Notice that we override other methods—read_exception and read_eof. 205 | //! There are few other methods that can be overriden. If you need to handle a particular event, 206 | //! just override the corresponding method. 207 | //! 208 | //! Now onto the client’s pipeline factory. It is identical the server’s pipeline factory, which 209 | //! handles writing data. 210 | //! ```ignore 211 | //! let mut bootstrap = BootstrapClientTcp::new(); 212 | //! bootstrap.pipeline(Box::new( move || { 213 | //! let pipeline: Pipeline = Pipeline::new(); 214 | //! 215 | //! let line_based_frame_decoder_handler = TaggedByteToMessageCodec::new(Box::new( 216 | //! LineBasedFrameDecoder::new(8192, true, TerminatorType::BOTH), 217 | //! )); 218 | //! let string_codec_handler = TaggedStringCodec::new(); 219 | //! let echo_client_handler = EchoClientHandler::new(); 220 | //! 221 | //! pipeline.add_back(line_based_frame_decoder_handler); 222 | //! pipeline.add_back(string_codec_handler); 223 | //! pipeline.add_back(echo_client_handler); 224 | //! pipeline.finalize() 225 | //! })); 226 | //! ``` 227 | //! 228 | //! Now that all needs to be done is plug the pipeline factory into a BootstrapTcpClient and that’s pretty much it. 229 | //! Connect to the remote peer and then read line from stdin and write it to pipeline. 230 | //! ```ignore 231 | //! let pipeline = bootstrap.connect(format!("{}:{}", host, port)).await?; 232 | //! 233 | //! println!("Enter bye to stop"); 234 | //! let mut buffer = String::new(); 235 | //! while tokio::io::stdin().read_line(&mut buffer).await.is_ok() { 236 | //! match buffer.trim_end() { 237 | //! "" => break, 238 | //! line => { 239 | //! pipeline.write(TaggedString { 240 | //! now: Instant::now(), 241 | //! transport, 242 | //! message: format!("{}\r\n", line), 243 | //! }); 244 | //! if line == "bye" { 245 | //! pipeline.close(); 246 | //! break; 247 | //! } 248 | //! } 249 | //! }; 250 | //! buffer.clear(); 251 | //! } 252 | //! 253 | //! bootstrap.graceful_stop().await; 254 | //! ``` 255 | #![doc(html_logo_url = "https://raw.githubusercontent.com/retty-io/retty/master/docs/retty.io.jpg")] 256 | #![warn(rust_2018_idioms)] 257 | #![allow(dead_code)] 258 | #![warn(missing_docs)] 259 | 260 | pub mod bootstrap; 261 | pub mod channel; 262 | pub mod codec; 263 | pub mod executor; 264 | pub mod transport; 265 | -------------------------------------------------------------------------------- /src/transport/mod.rs: -------------------------------------------------------------------------------- 1 | //! Transport abstraction for TCP and UDP 2 | use bytes::BytesMut; 3 | use std::net::SocketAddr; 4 | use std::str::FromStr; 5 | use std::time::Instant; 6 | 7 | pub use ::async_transport::EcnCodepoint; 8 | 9 | /// Type of protocol, either UDP or TCP 10 | #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] 11 | pub enum Protocol { 12 | /// UDP 13 | #[default] 14 | UDP, 15 | /// TCP 16 | TCP, 17 | } 18 | 19 | /// Transport Context with local address, peer address, ECN, protocol, etc. 20 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] 21 | pub struct TransportContext { 22 | /// Local socket address, either IPv4 or IPv6 23 | pub local_addr: SocketAddr, 24 | /// Peer socket address, either IPv4 or IPv6 25 | pub peer_addr: SocketAddr, 26 | /// Type of protocol, either UDP or TCP 27 | pub protocol: Protocol, 28 | /// Explicit congestion notification bits to set on the packet 29 | pub ecn: Option, 30 | } 31 | 32 | impl Default for TransportContext { 33 | fn default() -> Self { 34 | Self { 35 | local_addr: SocketAddr::from_str("0.0.0.0:0").unwrap(), 36 | peer_addr: SocketAddr::from_str("0.0.0.0:0").unwrap(), 37 | protocol: Protocol::UDP, 38 | ecn: None, 39 | } 40 | } 41 | } 42 | 43 | /// A generic transmit with [TransportContext] 44 | pub struct Transmit { 45 | /// Received/Sent time 46 | pub now: Instant, 47 | /// A transport context with [local_addr](TransportContext::local_addr) and [peer_addr](TransportContext::peer_addr) 48 | pub transport: TransportContext, 49 | /// Message body with generic type 50 | pub message: T, 51 | } 52 | 53 | /// BytesMut type transmit with [TransportContext] 54 | pub type TaggedBytesMut = Transmit; 55 | 56 | /// String type transmit with [TransportContext] 57 | pub type TaggedString = Transmit; 58 | 59 | /// Four Tuple consists of local address and peer address 60 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] 61 | pub struct FourTuple { 62 | /// Local socket address, either IPv4 or IPv6 63 | pub local_addr: SocketAddr, 64 | /// Peer socket address, either IPv4 or IPv6 65 | pub peer_addr: SocketAddr, 66 | } 67 | 68 | impl From<&TransportContext> for FourTuple { 69 | fn from(value: &TransportContext) -> Self { 70 | Self { 71 | local_addr: value.local_addr, 72 | peer_addr: value.peer_addr, 73 | } 74 | } 75 | } 76 | 77 | impl From for FourTuple { 78 | fn from(value: TransportContext) -> Self { 79 | Self { 80 | local_addr: value.local_addr, 81 | peer_addr: value.peer_addr, 82 | } 83 | } 84 | } 85 | 86 | /// Five Tuple consists of local address, peer address and protocol 87 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] 88 | pub struct FiveTuple { 89 | /// Local socket address, either IPv4 or IPv6 90 | pub local_addr: SocketAddr, 91 | /// Peer socket address, either IPv4 or IPv6 92 | pub peer_addr: SocketAddr, 93 | /// Type of protocol, either UDP or TCP 94 | pub protocol: Protocol, 95 | } 96 | 97 | impl From<&TransportContext> for FiveTuple { 98 | fn from(value: &TransportContext) -> Self { 99 | Self { 100 | local_addr: value.local_addr, 101 | peer_addr: value.peer_addr, 102 | protocol: value.protocol, 103 | } 104 | } 105 | } 106 | 107 | impl From for FiveTuple { 108 | fn from(value: TransportContext) -> Self { 109 | Self { 110 | local_addr: value.local_addr, 111 | peer_addr: value.peer_addr, 112 | protocol: value.protocol, 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /tests/echo_test.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use core_affinity::CoreId; 4 | use local_sync::mpsc::{unbounded::channel, unbounded::Tx as LocalSender}; 5 | use std::cell::RefCell; 6 | use std::collections::VecDeque; 7 | use std::net::SocketAddr; 8 | use std::rc::Rc; 9 | use std::str::FromStr; 10 | use std::time::Instant; 11 | 12 | use retty::bootstrap::{ 13 | BootstrapTcpClient, BootstrapTcpServer, BootstrapUdpClient, BootstrapUdpServer, 14 | }; 15 | use retty::channel::{Context, Handler, Pipeline}; 16 | use retty::codec::{ 17 | byte_to_message_decoder::{ 18 | LineBasedFrameDecoder, TaggedByteToMessageCodec, TerminatorType, 19 | }, 20 | string_codec::TaggedStringCodec, 21 | }; 22 | use retty::executor::{spawn_local, yield_local, LocalExecutorBuilder}; 23 | use retty::transport::{ 24 | EcnCodepoint, Protocol, TaggedBytesMut, TaggedString, TransportContext, 25 | }; 26 | 27 | //////////////////////////////////////////////////////////////////////////////////////////////////// 28 | struct EchoHandler { 29 | is_server: bool, 30 | tx: Rc>>>, 31 | count: Rc>, 32 | check_ecn: bool, 33 | transmits: VecDeque, 34 | } 35 | 36 | impl EchoHandler { 37 | fn new( 38 | is_server: bool, 39 | tx: Rc>>>, 40 | count: Rc>, 41 | check_ecn: bool, 42 | ) -> Self { 43 | EchoHandler { 44 | is_server, 45 | tx, 46 | count, 47 | check_ecn, 48 | transmits: VecDeque::new(), 49 | } 50 | } 51 | } 52 | 53 | impl Handler for EchoHandler { 54 | type Rin = TaggedString; 55 | type Rout = Self::Rin; 56 | type Win = TaggedString; 57 | type Wout = Self::Win; 58 | 59 | fn name(&self) -> &str { 60 | "EchoHandler" 61 | } 62 | 63 | fn handle_read( 64 | &mut self, 65 | _ctx: &Context, 66 | msg: Self::Rin, 67 | ) { 68 | { 69 | let mut count = self.count.borrow_mut(); 70 | println!( 71 | "is_server = {}, count = {} msg = {} with ECN = {:?}", 72 | self.is_server, *count, msg.message, msg.transport.ecn 73 | ); 74 | *count += 1; 75 | if self.check_ecn { 76 | assert_eq!(Some(EcnCodepoint::Ect1), msg.transport.ecn); 77 | } 78 | } 79 | 80 | if self.is_server { 81 | self.transmits.push_back(TaggedString { 82 | now: Instant::now(), 83 | transport: msg.transport, 84 | message: format!("{}\r\n", msg.message), 85 | }); 86 | } 87 | 88 | if msg.message == "bye" { 89 | let mut tx = self.tx.borrow_mut(); 90 | if let Some(tx) = tx.take() { 91 | let _ = tx.send(()); 92 | } 93 | } 94 | } 95 | fn poll_timeout( 96 | &mut self, 97 | _ctx: &Context, 98 | _eto: &mut Instant, 99 | ) { 100 | //last handler, no need to fire_poll_timeout 101 | } 102 | 103 | fn poll_write( 104 | &mut self, 105 | ctx: &Context, 106 | ) -> Option { 107 | if let Some(msg) = ctx.fire_poll_write() { 108 | self.transmits.push_back(msg); 109 | } 110 | self.transmits.pop_front() 111 | } 112 | } 113 | 114 | #[test] 115 | fn test_echo_udp() { 116 | let handler = LocalExecutorBuilder::new() 117 | .name("ech_udp_thread") 118 | .core_id(CoreId { id: 0 }) 119 | .spawn(|| async move { 120 | const ITER: usize = 100; //1024; 121 | 122 | let (tx, mut rx) = channel(); 123 | 124 | let server_count = Rc::new(RefCell::new(0)); 125 | let server_count_clone = server_count.clone(); 126 | let (server_done_tx, mut server_done_rx) = channel(); 127 | let server_done_tx = Rc::new(RefCell::new(Some(server_done_tx))); 128 | 129 | let mut server = BootstrapUdpServer::new(); 130 | server.pipeline(Box::new(move || { 131 | let pipeline: Pipeline = Pipeline::new(); 132 | 133 | let line_based_frame_decoder_handler = TaggedByteToMessageCodec::new(Box::new( 134 | LineBasedFrameDecoder::new(8192, true, TerminatorType::BOTH), 135 | )); 136 | let string_codec_handler = TaggedStringCodec::new(); 137 | let echo_handler = EchoHandler::new( 138 | true, 139 | Rc::clone(&server_done_tx), 140 | Rc::clone(&server_count_clone), 141 | #[cfg(not(target_os = "windows"))] 142 | true, 143 | #[cfg(target_os = "windows")] 144 | false, 145 | ); 146 | 147 | pipeline.add_back(line_based_frame_decoder_handler); 148 | pipeline.add_back(string_codec_handler); 149 | pipeline.add_back(echo_handler); 150 | pipeline.finalize() 151 | })); 152 | 153 | let server_addr = server.bind("127.0.0.1:0").await.unwrap(); 154 | 155 | spawn_local(async move { 156 | let client_count = Rc::new(RefCell::new(0)); 157 | let client_count_clone = client_count.clone(); 158 | let (client_done_tx, mut client_done_rx) = channel(); 159 | let client_done_tx = Rc::new(RefCell::new(Some(client_done_tx))); 160 | 161 | let mut client = BootstrapUdpClient::new(); 162 | client.pipeline(Box::new(move || { 163 | let pipeline: Pipeline = Pipeline::new(); 164 | 165 | let line_based_frame_decoder_handler = TaggedByteToMessageCodec::new( 166 | Box::new(LineBasedFrameDecoder::new(8192, true, TerminatorType::BOTH)), 167 | ); 168 | let string_codec_handler = TaggedStringCodec::new(); 169 | let echo_handler = EchoHandler::new( 170 | false, 171 | Rc::clone(&client_done_tx), 172 | Rc::clone(&client_count_clone), 173 | #[cfg(not(target_os = "windows"))] 174 | true, 175 | #[cfg(target_os = "windows")] 176 | false, 177 | ); 178 | 179 | pipeline.add_back(line_based_frame_decoder_handler); 180 | pipeline.add_back(string_codec_handler); 181 | pipeline.add_back(echo_handler); 182 | pipeline.finalize() 183 | })); 184 | 185 | let client_addr = client.bind("127.0.0.1:0").await.unwrap(); 186 | let pipeline = client.connect(server_addr).await.unwrap(); 187 | 188 | for i in 0..ITER { 189 | // write 190 | pipeline.write(TaggedString { 191 | now: Instant::now(), 192 | transport: TransportContext { 193 | local_addr: client_addr, 194 | peer_addr: server_addr, 195 | ecn: EcnCodepoint::from_bits(1), 196 | protocol: Protocol::UDP, 197 | }, 198 | message: format!("{}\r\n", i), 199 | }); 200 | yield_local(); 201 | } 202 | pipeline.write(TaggedString { 203 | now: Instant::now(), 204 | transport: TransportContext { 205 | local_addr: client_addr, 206 | peer_addr: server_addr, 207 | ecn: EcnCodepoint::from_bits(1), 208 | protocol: Protocol::UDP, 209 | }, 210 | message: format!("bye\r\n"), 211 | }); 212 | yield_local(); 213 | 214 | assert!(client_done_rx.recv().await.is_some()); 215 | 216 | assert!(tx.send(client_count).is_ok()); 217 | 218 | client.graceful_stop().await; 219 | }) 220 | .detach(); 221 | 222 | let client_count = rx.recv().await.unwrap(); 223 | assert!(server_done_rx.recv().await.is_some()); 224 | 225 | let (client_count, server_count) = (client_count.borrow(), server_count.borrow()); 226 | assert_eq!(*client_count, *server_count); 227 | assert_eq!(ITER + 1, *client_count); 228 | 229 | server.graceful_stop().await; 230 | }) 231 | .unwrap(); 232 | 233 | handler.join().unwrap(); 234 | } 235 | 236 | #[cfg(not(target_os = "windows"))] 237 | #[test] 238 | fn test_echo_tcp() { 239 | LocalExecutorBuilder::default().run(async { 240 | const ITER: usize = 100; //1024; 241 | 242 | let (tx, mut rx) = channel(); 243 | 244 | let server_count = Rc::new(RefCell::new(0)); 245 | let server_count_clone = server_count.clone(); 246 | let (server_done_tx, mut server_done_rx) = channel(); 247 | let server_done_tx = Rc::new(RefCell::new(Some(server_done_tx))); 248 | 249 | let mut server = BootstrapTcpServer::new(); 250 | server.pipeline(Box::new(move || { 251 | let pipeline: Pipeline = Pipeline::new(); 252 | 253 | let line_based_frame_decoder_handler = TaggedByteToMessageCodec::new(Box::new( 254 | LineBasedFrameDecoder::new(8192, true, TerminatorType::BOTH), 255 | )); 256 | let string_codec_handler = TaggedStringCodec::new(); 257 | let echo_handler = EchoHandler::new( 258 | true, 259 | Rc::clone(&server_done_tx), 260 | Rc::clone(&server_count_clone), 261 | false, 262 | ); 263 | 264 | pipeline.add_back(line_based_frame_decoder_handler); 265 | pipeline.add_back(string_codec_handler); 266 | pipeline.add_back(echo_handler); 267 | pipeline.finalize() 268 | })); 269 | 270 | let server_addr = server.bind("127.0.0.1:0").await.unwrap(); 271 | 272 | spawn_local(async move { 273 | let client_count = Rc::new(RefCell::new(0)); 274 | let client_count_clone = client_count.clone(); 275 | let (client_done_tx, mut client_done_rx) = channel(); 276 | let client_done_tx = Rc::new(RefCell::new(Some(client_done_tx))); 277 | 278 | let mut client = BootstrapTcpClient::new(); 279 | client.pipeline(Box::new(move || { 280 | let pipeline: Pipeline = Pipeline::new(); 281 | 282 | let line_based_frame_decoder_handler = TaggedByteToMessageCodec::new(Box::new( 283 | LineBasedFrameDecoder::new(8192, true, TerminatorType::BOTH), 284 | )); 285 | let string_codec_handler = TaggedStringCodec::new(); 286 | let echo_handler = EchoHandler::new( 287 | false, 288 | Rc::clone(&client_done_tx), 289 | Rc::clone(&client_count_clone), 290 | false, 291 | ); 292 | 293 | pipeline.add_back(line_based_frame_decoder_handler); 294 | pipeline.add_back(string_codec_handler); 295 | pipeline.add_back(echo_handler); 296 | pipeline.finalize() 297 | })); 298 | 299 | let client_addr = SocketAddr::from_str("127.0.0.1:0").unwrap(); 300 | 301 | let pipeline = client.connect(server_addr).await.unwrap(); 302 | 303 | for i in 0..ITER { 304 | // write 305 | pipeline.write(TaggedString { 306 | now: Instant::now(), 307 | transport: TransportContext { 308 | local_addr: client_addr, 309 | peer_addr: server_addr, 310 | ecn: None, 311 | protocol: Protocol::TCP, 312 | }, 313 | message: format!("{}\r\n", i), 314 | }); 315 | yield_local(); 316 | } 317 | pipeline.write(TaggedString { 318 | now: Instant::now(), 319 | transport: TransportContext { 320 | local_addr: client_addr, 321 | peer_addr: server_addr, 322 | ecn: None, 323 | protocol: Protocol::TCP, 324 | }, 325 | message: format!("bye\r\n"), 326 | }); 327 | yield_local(); 328 | 329 | assert!(client_done_rx.recv().await.is_some()); 330 | 331 | assert!(tx.send(client_count).is_ok()); 332 | 333 | client.graceful_stop().await; 334 | }) 335 | .detach(); 336 | 337 | let client_count = rx.recv().await.unwrap(); 338 | assert!(server_done_rx.recv().await.is_some()); 339 | 340 | let (client_count, server_count) = (client_count.borrow(), server_count.borrow()); 341 | assert_eq!(*client_count, *server_count); 342 | assert_eq!(ITER + 1, *client_count); 343 | 344 | server.graceful_stop().await; 345 | }); 346 | } 347 | } 348 | --------------------------------------------------------------------------------