Showing preview only (399K chars total). Download the full file or copy to clipboard to get everything.
Repository: 1c3t3a/rust-socketio
Branch: main
Commit: a4e52873105c
Files: 85
Total size: 375.4 KB
Directory structure:
gitextract_9tvnxqqn/
├── .devcontainer/
│ ├── Dockerfile
│ ├── devcontainer.json
│ └── docker-compose.yaml
├── .github/
│ ├── dependabot.yml
│ └── workflows/
│ ├── benchmark.yml
│ ├── build.yml
│ ├── coverage.yml
│ ├── publish-dry-run.yml
│ ├── publish.yml
│ └── test.yml
├── .gitignore
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Cargo.toml
├── LICENSE
├── Makefile
├── README.md
├── Roadmap.md
├── ci/
│ ├── .dockerignore
│ ├── Dockerfile
│ ├── README.md
│ ├── engine-io-polling.js
│ ├── engine-io-secure.js
│ ├── engine-io.js
│ ├── keygen.sh
│ ├── package.json
│ ├── socket-io-auth.js
│ ├── socket-io-restart-url-auth.js
│ ├── socket-io-restart.js
│ ├── socket-io.js
│ └── start_test_server.sh
├── codecov.yml
├── engineio/
│ ├── Cargo.toml
│ ├── README.md
│ ├── benches/
│ │ └── engineio.rs
│ └── src/
│ ├── asynchronous/
│ │ ├── async_socket.rs
│ │ ├── async_transports/
│ │ │ ├── mod.rs
│ │ │ ├── polling.rs
│ │ │ ├── websocket.rs
│ │ │ ├── websocket_general.rs
│ │ │ └── websocket_secure.rs
│ │ ├── callback.rs
│ │ ├── client/
│ │ │ ├── async_client.rs
│ │ │ ├── builder.rs
│ │ │ └── mod.rs
│ │ ├── generator.rs
│ │ ├── mod.rs
│ │ └── transport.rs
│ ├── callback.rs
│ ├── client/
│ │ ├── client.rs
│ │ └── mod.rs
│ ├── error.rs
│ ├── header.rs
│ ├── lib.rs
│ ├── packet.rs
│ ├── socket.rs
│ ├── transport.rs
│ └── transports/
│ ├── mod.rs
│ ├── polling.rs
│ ├── websocket.rs
│ └── websocket_secure.rs
└── socketio/
├── Cargo.toml
├── examples/
│ ├── async.rs
│ ├── callback.rs
│ ├── readme.rs
│ └── secure.rs
└── src/
├── asynchronous/
│ ├── client/
│ │ ├── ack.rs
│ │ ├── builder.rs
│ │ ├── callback.rs
│ │ ├── client.rs
│ │ └── mod.rs
│ ├── generator.rs
│ ├── mod.rs
│ └── socket.rs
├── client/
│ ├── builder.rs
│ ├── callback.rs
│ ├── client.rs
│ ├── mod.rs
│ └── raw_client.rs
├── error.rs
├── event.rs
├── lib.rs
├── packet.rs
├── payload.rs
└── socket.rs
================================================
FILE CONTENTS
================================================
================================================
FILE: .devcontainer/Dockerfile
================================================
FROM mcr.microsoft.com/vscode/devcontainers/rust:0-1
# Install socat needed for TCP proxy
RUN apt update && apt install -y socat
COPY ./ci/cert/ca.crt /usr/local/share/ca-certificates/
RUN update-ca-certificates
================================================
FILE: .devcontainer/devcontainer.json
================================================
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.187.0/containers/rust
{
"name": "Rust",
"dockerComposeFile": [
"./docker-compose.yaml"
],
"service": "rust-client",
"workspaceFolder": "/workspace/rust-socketio",
"shutdownAction": "stopCompose",
"customizations": {
"vscode": {
// Set *default* container specific settings.json values on container create.
"settings": {
"lldb.executable": "/usr/bin/lldb",
// VS Code don't watch files under ./target
"files.watcherExclude": {
"**/target/**": true
},
"rust-analyzer.cargo.features": [
"async"
]
/*,
// If you prefer rust-analzyer to be less noisy consider these settings to your settings.json
"editor.semanticTokenColorCustomizations": {
"rules": {
"*.mutable": {
"underline": false
}
}
},
"rust-analyzer.inlayHints.parameterHints": false
*/
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"rust-lang.rust-analyzer",
"bungcip.better-toml",
"vadimcn.vscode-lldb",
"eamodio.gitlens",
"streetsidesoftware.code-spell-checker"
]
}
},
"remoteUser": "vscode",
// Start a TCP proxy from to the testing node-socket-io server so doc tests can pass.
"postAttachCommand": {
"SocketIOProxy": "socat TCP-LISTEN:4200,fork,reuseaddr TCP:node-socket-io:4200",
"EngineIOProxy": "socat TCP-LISTEN:4201,fork,reuseaddr TCP:node-engine-io:4201",
"SocketIOAuthProxy": "socat TCP-LISTEN:4204,fork,reuseaddr TCP:node-socket-io-auth:4204"
}
}
================================================
FILE: .devcontainer/docker-compose.yaml
================================================
version: '3'
services:
node-engine-io-secure:
build:
context: ../ci
command: [ "node", "/test/engine-io-secure.js" ]
ports:
- "4202:4202"
environment:
- "DEBUG=*"
node-engine-io:
build:
context: ../ci
command: [ "node", "/test/engine-io.js" ]
ports:
- "4201:4201"
environment:
- "DEBUG=*"
node-engine-io-polling:
build:
context: ../ci
command: [ "node", "/test/engine-io-polling.js" ]
ports:
- "4203:4203"
environment:
- "DEBUG=*"
node-socket-io:
build:
context: ../ci
command: [ "node", "/test/socket-io.js" ]
ports:
- "4200:4200"
environment:
- "DEBUG=*"
node-socket-io-auth:
build:
context: ../ci
command: [ "node", "/test/socket-io-auth.js" ]
ports:
- "4204:4204"
environment:
- "DEBUG=*"
node-socket-restart:
build:
context: ../ci
command: [ "node", "/test/socket-io-restart.js" ]
ports:
- "4205:4205"
environment:
- "DEBUG=*"
node-socket-restart-url-auth:
build:
context: ../ci
command: [ "node", "/test/socket-io-restart-url-auth.js" ]
ports:
- "4206:4206"
environment:
- "DEBUG=*"
rust-client:
build:
context: ..
dockerfile: ./.devcontainer/Dockerfile
command: /bin/sh -c "while sleep 10000d; do :; done"
security_opt:
- seccomp:unconfined
volumes:
- "..:/workspace/rust-socketio"
environment:
- "SOCKET_IO_SERVER=http://node-socket-io:4200"
- "SOCKET_IO_AUTH_SERVER=http://node-socket-io-auth:4204"
- "ENGINE_IO_SERVER=http://node-engine-io:4201"
- "ENGINE_IO_SECURE_SERVER=https://node-engine-io-secure:4202"
- "ENGINE_IO_SECURE_HOST=node-engine-io-secure"
- "ENGINE_IO_POLLING_SERVER=http://node-engine-io-polling:4203"
- "SOCKET_IO_RESTART_SERVER=http://node-socket-restart:4205"
- "SOCKET_IO_RESTART_URL_AUTH_SERVER=http://node-socket-restart-url-auth:4206"
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: "cargo" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "monthly"
groups:
patches:
# Group cargo patch updates together to minimize PR management faff
applies-to: version-updates
update-types:
- patch
================================================
FILE: .github/workflows/benchmark.yml
================================================
on:
pull_request:
types: [opened]
issue_comment:
types: [created]
name: benchmark engine.io
jobs:
runBenchmark:
name: run benchmark
runs-on: ubuntu-latest
steps:
- uses: khan/pull-request-comment-trigger@master
id: check
with:
trigger: '/benchmark'
reaction: rocket
env:
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
- name: Checkout repository
if: steps.check.outputs.triggered == 'true'
uses: actions/checkout@v2
- name: Setup rust environment
if: steps.check.outputs.triggered == 'true'
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Setup docker
if: steps.check.outputs.triggered == 'true'
id: buildx
uses: docker/setup-buildx-action@v1
- name: Generate keys
if: steps.check.outputs.triggered == 'true'
run: make keys
- name: Build docker container
if: steps.check.outputs.triggered == 'true'
run: |
cd ci && docker build -t test_suite:latest .
docker run -d -p 4200:4200 -p 4201:4201 -p 4202:4202 -p 4203:4203 -p 4204:4204 -p 4205:4205 -p 4206:4206 test_suite:latest
- name: Extract branch name
if: steps.check.outputs.triggered == 'true'
shell: bash
run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})"
id: extract_branch
- uses: actions/checkout@master
if: steps.check.outputs.triggered == 'true'
- uses: boa-dev/criterion-compare-action@v3.2.0
if: steps.check.outputs.triggered == 'true'
with:
cwd: "engineio"
branchName: ${{ steps.extract_branch.outputs.branch }}
token: ${{ secrets.GITHUB_TOKEN }}
================================================
FILE: .github/workflows/build.yml
================================================
name: Build and code style
on:
push:
branches: [main, refactoring]
pull_request:
branches: [main]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup rust environment
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Generate Cargo.lock
run: cargo generate-lockfile
- uses: actions/cache@v2
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Build
run: cargo build --verbose --all-features
- name: Linting
run: cargo clippy --verbose --all-features
- name: Check formatting
run: cargo fmt --all -- --check
================================================
FILE: .github/workflows/coverage.yml
================================================
on:
push:
branches: [main]
pull_request:
branches: [main]
name: generate coverage
jobs:
check:
name: Setup Rust project
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Setup rust environment
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov
- name: Setup docker
id: buildx
uses: docker/setup-buildx-action@v1
- name: Generate keys
run: make keys
- name: Build docker container
run: |
cd ci && docker build -t test_suite:latest .
docker run -d --name test_suite -p 4200:4200 -p 4201:4201 -p 4202:4202 -p 4203:4203 -p 4204:4204 -p 4205:4205 -p 4206:4206 test_suite:latest
- uses: actions/cache@v2
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Generate code coverage
run: cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info
- name: Upload to codecov.io
uses: codecov/codecov-action@v1.5.2
with:
token: ${{secrets.CODECOV_TOKEN}}
files: lcov.info
fail_ci_if_error: true
- name: Collect docker logs
if: always()
run: docker logs test_suite > my_logs.txt 2>&1
- name: Upload docker logs
uses: actions/upload-artifact@v4
if: always()
with:
name: docker logs
path: my_logs.txt
================================================
FILE: .github/workflows/publish-dry-run.yml
================================================
name: Publish dry run
on:
workflow_dispatch
jobs:
publish:
name: Publish dry run
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
# Publish the engine.io crate
- uses: katyo/publish-crates@v1
with:
path: './engineio'
dry-run: true
# Publish the socket.io crate
- uses: katyo/publish-crates@v1
with:
path: './socketio'
dry-run: true
================================================
FILE: .github/workflows/publish.yml
================================================
name: Publish
on:
workflow_dispatch
jobs:
publish:
name: Publish
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
# Publish the engine.io crate
- uses: katyo/publish-crates@v1
with:
path: './engineio'
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
# Publish the socket.io crate
- uses: katyo/publish-crates@v1
with:
path: './socketio'
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
================================================
FILE: .github/workflows/test.yml
================================================
name: Test
on:
push:
branches: [main]
pull_request:
branches: [main, refactoring]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v2
- name: Setup rust environment
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Setup docker
id: buildx
uses: docker/setup-buildx-action@v1
- name: Generate keys
run: make keys
- name: Build docker container
run: |
cd ci && docker build -t test_suite:latest .
docker run -d -p 4200:4200 -p 4201:4201 -p 4202:4202 -p 4203:4203 -p 4204:4204 -p 4205:4205 -p 4206:4206 test_suite:latest
- name: Generate Cargo.lock
run: cargo generate-lockfile
- uses: actions/cache@v2
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Run testsuite
run: cargo test --verbose --features "async"
================================================
FILE: .gitignore
================================================
target
.vscode
.idea
ci/node_modules
ci/package-lock.json
ci/cert
================================================
FILE: CHANGELOG.md
================================================
# Changelog
All notable changes to this project are documented in this file.
The format is based on [Keep a Changelog], and this project adheres to
[Semantic Versioning]. The file is auto-generated using [Conventional Commits].
[keep a changelog]: https://keepachangelog.com/en/1.0.0/
[semantic versioning]: https://semver.org/spec/v2.0.0.html
[conventional commits]: https://www.conventionalcommits.org/en/v1.0.0-beta.4/
## Overview
* [unreleased](#unreleased)
* [`0.5.0`](#060) - _2024.04.16_
* [`0.5.0`](#050) - _2024.03.31_
* [`0.4.4`](#044) - _2023.11.18_
* [`0.4.3`](#043) - _2023.07.08_
* [`0.4.2`](#042) - _2023.06.25_
* [`0.4.1-alpha.2`](#041a2) - _2023.03.26_
* [`0.4.1-alpha.1`](#041a1) - _2023.01.15_
* [`0.4.0`](#041) - _2023.01.15_
* [`0.4.0`](#040) - _2022.10.20_
* [`0.3.1`](#031) - _2022.03.19_
* [`0.3.0`](#030) - _2021.12.16_
* [`0.3.0-alpha.2`](#030a3) - _2021.12.04_
* [`0.3.0-alpha.2`](#030a2) - _2021.10.14_
* [`0.3.0-alpha.1`](#030a1) - _2021.09.20_
* [`0.2.4`](#024) - _2021.05.25_
* [`0.2.3`](#023) - _2021.05.24_
* [`0.2.2`](#022) - _2021.05.13
* [`0.2.1`](#021) - _2021.04.27_
* [`0.2.0`](#020) – _2021.03.13_
* [`0.1.1`](#011) – _2021.01.10_
* [`0.1.0`](#010) – _2021.01.05_
## _[Unreleased]_
_nothing new to show for… yet!_>
## <a name="060">[0.6.0] - _Multi-payload fix and http 1.0_ </a>
_2024.04.16_
- Fix issues with processing multi-payload messages ([#392](https://github.com/1c3t3a/rust-socketio/pull/392)).
Credits to shenjackyuanjie@.
- Bump http to 1.0 and all dependencies that use http to a version that also uses http 1.0 ([#418](https://github.com/1c3t3a/rust-socketio/pull/418)).
Bumping those dependencies makes this a breaking change.
## <a name="050">[0.5.0] - _Packed with changes!_ </a>
_2024.03.31_
- Support multiple arguments to the payload through a new Payload variant called
`Text` that holds a JSON value ([#384](https://github.com/1c3t3a/rust-socketio/pull/384)).
Credits to ctrlaltf24@ and SalahaldinBilal@!
Please note: This is a breaking change: `Payload::String` is deprecated and will be removed soon.
- Async reconnections: Support for automatic reconnection in the async version of the crate!
([#400](https://github.com/1c3t3a/rust-socketio/pull/400)). Credits to rageshkrishna@.
- Add an `on_reconnect` callback that allows to change the connection configuration
([#405](https://github.com/1c3t3a/rust-socketio/pull/405)). Credits to rageshkrishna@.
- Fix bug that ignored the ping interval ([#359](https://github.com/1c3t3a/rust-socketio/pull/359)).
Credits to sirkrypt0@. This is a breaking change that removes the engine.io's stream impl.
It is however replaced by a method called `as_stream` on the engine.io socket.
- Add macro `async_callback` and `async_any_callback` for async callbacks ([#399](https://github.com/1c3t3a/rust-socketio/pull/399).
Credits to shenjackyuanjie@.
## <a name="044">[0.4.4] - _Bump dependencies_ </a>
_2023.11.18_
- Bump tungstenite version to v0.20.1 (avoiding security vulnerability) [#368](https://github.com/1c3t3a/rust-socketio/pull/368)
- Updating other dependencies
## <a name="043">[0.4.3] - _Bugfix!_ </a>
_2023.07.08_
- Fix of [#323](https://github.com/1c3t3a/rust-socketio/issues/323)
- Marking the async feature optional
## <a name="042">[0.4.2] - _Stabilizing the async interface!_ </a>
_2023.06.25_
- Fix "Error while parsing an incomplete packet socketio" on first heartbeat killing the connection async client
([#311](https://github.com/1c3t3a/rust-socketio/issues/311)). Credits to [@sirkrypt0](https://github.com/sirkrypt0)
- Fix allow awaiting async callbacks ([#313](https://github.com/1c3t3a/rust-socketio/issues/313)). Credits to [@felix-gohla](https://github.com/felix-gohla)
- Various performance improvements especially in packet parsing. Credits to [@MaxOhn](https://github.com/MaxOhn)
- API for setting the reconnect URL on a connected client ([#251](https://github.com/1c3t3a/rust-socketio/issues/251)).
Credits to [@tyilo](https://github.com/tyilo)
## <a name="041a2">[0.4.0-alpha.2] - _Async socket.io fixes_ </a>
_2023.03.26_
- Add `on_any` method for async `ClientBuilder`. This adds the capability to
react to all incoming events (custom and otherwise).
- Add `auth` option to async `ClientBuilder`. This allows for specifying JSON
data that is sent with the first open packet, which is commonly used for
authentication.
- Bump dependencies and remove calls to deprecated library functions.
## <a name="041a1">[0.4.0-alpha.1] - _Async socket.io version_ </a>
_2023.01.05_
- Add an async socket.io interface under the `async` feature flag, relevant PR: [#180](https://github.com/1c3t3a/rust-socketio/pull/180).
- See example code under `socketio/examples/async.rs` and in the `async` section of the README.
## <a name="041">[0.4.1] - _Minor enhancements_ </a>
_2023.01.05_
- As of [#264](https://github.com/1c3t3a/rust-socketio/pull/264), the callbacks
are now allowed to be `?Sync`.
- As of [#265](https://github.com/1c3t3a/rust-socketio/pull/265), the `Payload`
type now implements `AsRef<u8>`.
## <a name="040">[0.4.0] - _Bugfixes and Reconnection feature_ </a>
_2022.10.20_
### Changes
- Fix [#214](https://github.com/1c3t3a/rust-socketio/issues/214).
- Fix [#215](https://github.com/1c3t3a/rust-socketio/issues/215).
- Fix [#219](https://github.com/1c3t3a/rust-socketio/issues/219).
- Fix [#221](https://github.com/1c3t3a/rust-socketio/issues/221).
- Fix [#222](https://github.com/1c3t3a/rust-socketio/issues/222).
- BREAKING: The default Client returned by the builder will automatically reconnect to the server unless stopped manually.
The new `ReconnectClient` encapsulates this behaviour.
Special thanks to [@SSebo](https://github.com/SSebo) for his major contribution to this release.
## <a name="031">[0.3.1] - _Bugfix_ </a>
_2022.03.19_
### Changes
- Fixes regarding [#166](https://github.com/1c3t3a/rust-socketio/issues/166).
## <a name="030">[0.3.0] - _Stabilize alpha version_ </a>
_2021.12.16_
### Changes
- Stabilized alpha features.
- Fixes regarding [#133](https://github.com/1c3t3a/rust-socketio/issues/133).
## <a name="030a3">[0.3.0-alpha.3] - _Bugfixes_ </a>
_2021.12.04_
### Changes
- fix a bug that resulted in a blocking `emit` method (see [#133](https://github.com/1c3t3a/rust-socketio/issues/133)).
- Bump dependencies.
## <a name="030a2">[0.3.0-alpha.2] - _Further refactoring_ </a>
_2021.10.14_
### Changes
* Rename `Socket` to `Client` and `SocketBuilder` to `ClientBuilder`
* Removed headermap from pub use, internal type only
* Deprecations:
* crate::payload (use crate::Payload instead)
* crate::error (use crate::Error instead)
* crate::event (use crate::Event instead)
## <a name="030a1">[0.3.0-alpha.1] - _Refactoring_ </a>
_2021.09.20_
### Changes
* Refactored Errors
* Renamed EmptyPacket to EmptyPacket()
* Renamed IncompletePacket to IncompletePacket()
* Renamed InvalidPacket to InvalidPacket()
* Renamed Utf8Error to InvalidUtf8()
* Renamed Base64Error to InvalidBase64
* Renamed InvalidUrl to InvalidUrlScheme
* Renamed ReqwestError to IncompleteResponseFromReqwest
* Renamed HttpError to IncompleteHttp
* Renamed HandshakeError to InvalidHandshake
* Renamed ~ActionBeforeOpen to IllegalActionBeforeOpen()~
* Renamed DidNotReceiveProperAck to MissingAck
* Renamed PoisonedLockError to InvalidPoisonedLock
* Renamed FromWebsocketError to IncompleteResponseFromWebsocket
* Renamed FromWebsocketParseError to InvalidWebsocketURL
* Renamed FromIoError to IncompleteIo
* New error type InvalidUrl(UrlParseError)
* New error type InvalidInteger(ParseIntError)
* New error type IncompleteResponseFromEngineIo(rust_engineio::Error)
* New error type InvalidAttachmentPacketType(u8)
* Removed EmptyPacket
* Refactored Packet
* Renamed encode to From<&Packet>
* Renamed decode to TryFrom<&Bytes>
* Renamed attachments to attachments_count
* New struct member attachments: Option<Vec<Bytes>>
* Refactor PacketId
* Renamed u8_to_packet_id to TryFrom<u8> for PacketId
* Refactored SocketBuilder
* Renamed set_namespace to namespace
* Renamed set_tls_config to tls_config
* Renamed set_opening_header to opening_header
* namespace returns Self rather than Result<Self>
* opening_header accepts a Into<HeaderValue> rather than HeaderValue
* Allows for pure websocket connections
* Refactor EngineIO module
## <a name="024">[0.2.4] - _Bugfixes_ </a>
_2021.05.25_
### Changes
* Fixed a bug that prevented the client from receiving data for a message event issued on the server.
## <a name="023">[0.2.3] - _Disconnect methods on the Socket struct_ </a>
_2021.05.24_
### Changes
* Added a `disconnect` method to the `Socket` struct as requested in [#43](https://github.com/1c3t3a/rust-socketio/issues/43).
## <a name="022">[0.2.2] - _Safe websockets and custom headers_ </a>
_2021.05.13_
### Changes
* Added websocket communication over TLS when either `wss`, or `https` are specified in the URL.
* Added the ability to configure the TLS connection by providing an own `TLSConnector`.
* Added the ability to set custom headers as requested in [#35](https://github.com/1c3t3a/rust-socketio/issues/35).
## <a name="021">[0.2.1] - _Bugfixes_ </a>
_2021.04.27_
### Changes
* Corrected memory ordering issues which might have become an issue on certain platforms.
* Added this CHANGELOG to keep track of all changes.
* Small stylistic changes to the codebase in general.
## <a name="020">[0.2.0] - _Fully implemented the socket.io protocol 🎉_ </a>
_2021.03.13_
### Changes
* Moved from async rust to sync rust.
* Implemented the missing protocol features.
* Websocket as a transport layer.
* Binary payload.
* Added a `SocketBuilder` class to easily configure a connected client.
* Added a new `Payload` type to manage the binary and string payload.
## <a name="011">[0.1.1] - _Update to tokio 1.0_</a>
_2021.01.10_
### Changes
* Bumped `tokio` to version `1.0.*`, and therefore reqwest to `0.11.*`.
* Removed all `unsafe` code.
## <a name="010">[0.1.0] - _First release of rust-socketio 🎉_</a>
_2021.01.05_
* First version of the library written in async rust. The features included:
* connecting to a server.
* register callbacks for the following event types:
* open, close, error, message
* custom events like "foo", "on_payment", etc.
* send json-data to the server (recommended to use serde_json as it provides safe handling of json data).
* send json-data to the server and receive an ack with a possible message.
================================================
FILE: CONTRIBUTING.md
================================================
# Introduction
Contributions to this project are welcome!
This project is still being developed, our goal is to have a well-designed
thought-out project, so when you make a contribution, please think in those
terms. That is:
- For code:
- Is the contribution in the scope of this crate?
- Is it well documented?
- Is it well tested?
- For documentation:
- Is it clear and well-written?
- Can others understand it easily?
- For bugs:
- Does it test functionality of this crate?
- Do you have a minimal crate that causes the issue that we can use and test?
- For feature requests:
- Is the request within the scope of this crate?
- Is the request clearly explained?
## Licensing and other property rights.
All contributions that are made to this project are only accepted under the
terms in the [LICENSE](LICENSE) file. That is, if you contribute to this
project, you are certifying that you have all the necessary rights to make the
contribution under the terms of the [LICENSE](LICENSE) file, and you are in fact
licensing them to this project and anyone that uses this project under the terms
in the [LICENSE](LICENSE) file.
## Misc
- We are looking at adopting the [conventional commits 1.0](https://www.conventionalcommits.org/en/v1.0.0/) standard.
- This would make it easier for us to use tools like [jilu](https://crates.io/crates/jilu) to create change logs.
- Read [keep a changelog](https://keepachangelog.com/en/1.0.0/) for more information.
## Git hooks
> Git hooks are scripts that Git executes before or after events such as: commit, push, and receive. Git hooks are a built-in feature - no need to download anything. Git hooks are run locally.
- [githooks.com](https://githooks.com/)
These example hooks enforce some of these contributing guidelines so you don't need to remember them.
### pre-commit
Put the contents below in ./.git/hooks/pre-commit
```bash
#!/bin/bash
set -e
set -o pipefail
# Clippy recomendations
cargo clippy --verbose
# Check formatting
cargo fmt --all -- --check || {
cargo fmt
echo "Formatted some files make sure to check them in."
exit 1
}
# Make sure our build passes
cargo build --verbose
```
### commit-msg
Put the contents below in ./.git/hooks/commit-msg
```bash
#!/bin/bash
# https://dev.to/craicoverflow/enforcing-conventional-commits-using-git-hooks-1o5p
regexp="^(revert: )?(fix|feat|docs|ci|refactor|style|test)(\(.*?\))?(\!)?: .+$"
msg="$(head -1 $1)"
if [[ ! $msg =~ $regexp ]]
then
echo -e "INVALID COMMIT MESSAGE"
echo -e "------------------------"
echo -e "Valid types: fix, feat, docs, ci, style, test, refactor"
echo -e "Such as: 'feat: add new feature'"
echo -e "See https://www.conventionalcommits.org/en/v1.0.0/ for details"
echo
# exit with an error
exit 1
fi
while read line
do
if [[ $(echo "$line" | wc -c) -gt 51 ]]
then
echo "Line '$line' is longer than 50 characters."
echo "Consider splitting into these two lines"
echo "$line" | head -c 50
echo
echo "$line" | tail -c +51
echo
exit 1
fi
done < $1
```
================================================
FILE: Cargo.toml
================================================
[workspace]
members = ["engineio", "socketio"]
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2021 Bastian Kersting
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Makefile
================================================
.PHONY: build test-fast run-test-servers test-all clippy format checks pipeline
build:
@cargo build --verbose --all-features
keys:
@./ci/keygen.sh node-engine-io-secure 127.0.0.1
test-fast: keys
@cargo test --verbose --package rust_socketio --lib -- engineio::packet && cargo test --verbose --package rust_socketio --lib -- socketio::packet
run-test-servers:
cd ci && docker build -t test_suite:latest . && cd ..
docker run -d -p 4200:4200 -p 4201:4201 -p 4202:4202 -p 4203:4203 -p 4204:4204 -p 4205:4205 -p 4206:4206 --name socketio_test test_suite:latest
test-all: keys run-test-servers
@cargo test --verbose --all-features
docker stop socketio_test
clippy:
@cargo clippy --verbose --all-features
format:
@cargo fmt --all -- --check
checks: build test-fast clippy format
@echo "### Don't forget to add untracked files! ###"
@git status
@echo "### Awesome work! 😍 ###"""
pipeline: build test-all clippy format
@echo "### Don't forget to add untracked files! ###"
@git status
@echo "### Awesome work! 😍 ###"""
================================================
FILE: README.md
================================================
[](https://crates.io/crates/rust_socketio)
[](https://docs.rs/rust_socketio)
[](https://github.com/1c3t3a/rust-socketio/actions/workflows/build.yml)
[](https://github.com/1c3t3a/rust-socketio/actions/workflows/test.yml)
[](https://codecov.io/gh/1c3t3a/rust-socketio)
# Rust-socketio-client
An implementation of a socket.io client written in the rust programming language. This implementation currently supports revision 5 of the socket.io protocol and therefore revision 4 of the engine.io protocol. If you have any connection issues with this client, make sure the server uses at least revision 4 of the engine.io protocol.
Information on the [`async`](#async) version can be found below.
## Example usage
Add the following to your `Cargo.toml` file:
```toml
rust_socketio = "*"
```
Then you're able to run the following example code:
``` rust
use rust_socketio::{ClientBuilder, Payload, RawClient};
use serde_json::json;
use std::time::Duration;
// define a callback which is called when a payload is received
// this callback gets the payload as well as an instance of the
// socket to communicate with the server
let callback = |payload: Payload, socket: RawClient| {
match payload {
Payload::String(str) => println!("Received: {}", str),
Payload::Binary(bin_data) => println!("Received bytes: {:#?}", bin_data),
}
socket.emit("test", json!({"got ack": true})).expect("Server unreachable")
};
// get a socket that is connected to the admin namespace
let socket = ClientBuilder::new("http://localhost:4200")
.namespace("/admin")
.on("test", callback)
.on("error", |err, _| eprintln!("Error: {:#?}", err))
.connect()
.expect("Connection failed");
// emit to the "foo" event
let json_payload = json!({"token": 123});
socket.emit("foo", json_payload).expect("Server unreachable");
// define a callback, that's executed when the ack got acked
let ack_callback = |message: Payload, _| {
println!("Yehaa! My ack got acked?");
println!("Ack data: {:#?}", message);
};
let json_payload = json!({"myAckData": 123});
// emit with an ack
socket
.emit_with_ack("test", json_payload, Duration::from_secs(2), ack_callback)
.expect("Server unreachable");
socket.disconnect().expect("Disconnect failed")
```
The main entry point for using this crate is the `ClientBuilder` which provides a way to easily configure a socket in the needed way. When the `connect` method is called on the builder, it returns a connected client which then could be used to emit messages to certain events. One client can only be connected to one namespace. If you need to listen to the messages in different namespaces you need to allocate multiple sockets.
## Documentation
Documentation of this crate can be found up on [docs.rs](https://docs.rs/rust_socketio).
## Current features
This implementation now supports all of the features of the socket.io protocol mentioned [here](https://github.com/socketio/socket.io-protocol).
It generally tries to make use of websockets as often as possible. This means most times
only the opening request uses http and as soon as the server mentions that he is able to upgrade to
websockets, an upgrade is performed. But if this upgrade is not successful or the server
does not mention an upgrade possibility, http-long polling is used (as specified in the protocol specs).
Here's an overview of possible use-cases:
- connecting to a server.
- register callbacks for the following event types:
- open
- close
- error
- message
- custom events like "foo", "on_payment", etc.
- send JSON data to the server (via `serde_json` which provides safe
handling).
- send JSON data to the server and receive an `ack`.
- send and handle Binary data.
## <a name="async"> Async version
This library provides an ability for being executed in an asynchronous context using `tokio` as
the execution runtime.
Please note that the current async implementation is still experimental, the interface can be object to
changes at any time.
The async `Client` and `ClientBuilder` support a similar interface to the sync version and live
in the `asynchronous` module. In order to enable the support, you need to enable the `async`
feature flag:
```toml
rust_socketio = { version = "*", features = ["async"] }
```
The following code shows the example above in async fashion:
``` rust
use futures_util::FutureExt;
use rust_socketio::{
asynchronous::{Client, ClientBuilder},
Payload,
};
use serde_json::json;
use std::time::Duration;
#[tokio::main]
async fn main() {
// define a callback which is called when a payload is received
// this callback gets the payload as well as an instance of the
// socket to communicate with the server
let callback = |payload: Payload, socket: Client| {
async move {
match payload {
Payload::String(str) => println!("Received: {}", str),
Payload::Binary(bin_data) => println!("Received bytes: {:#?}", bin_data),
}
socket
.emit("test", json!({"got ack": true}))
.await
.expect("Server unreachable");
}
.boxed()
};
// get a socket that is connected to the admin namespace
let socket = ClientBuilder::new("http://localhost:4200/")
.namespace("/admin")
.on("test", callback)
.on("error", |err, _| {
async move { eprintln!("Error: {:#?}", err) }.boxed()
})
.connect()
.await
.expect("Connection failed");
// emit to the "foo" event
let json_payload = json!({"token": 123});
socket
.emit("foo", json_payload)
.await
.expect("Server unreachable");
// define a callback, that's executed when the ack got acked
let ack_callback = |message: Payload, _: Client| {
async move {
println!("Yehaa! My ack got acked?");
println!("Ack data: {:#?}", message);
}
.boxed()
};
let json_payload = json!({"myAckData": 123});
// emit with an ack
socket
.emit_with_ack("test", json_payload, Duration::from_secs(2), ack_callback)
.await
.expect("Server unreachable");
socket.disconnect().await.expect("Disconnect failed");
}
```
## Content of this repository
This repository contains a rust implementation of the socket.io protocol as well as the underlying engine.io protocol.
The details about the engine.io protocol can be found here:
* <https://github.com/socketio/engine.io-protocol>
The specification for the socket.io protocol here:
* <https://github.com/socketio/socket.io-protocol>
Looking at the component chart, the following parts are implemented (Source: https://socket.io/images/dependencies.jpg):
<img src="docs/res/dependencies.jpg" width="50%"/>
## Licence
MIT
================================================
FILE: Roadmap.md
================================================
# Roadmap
- 0.2.5 deprecation begins
- 0.3.0 refactor with breaking changes (Conform to api recommendations wherever reasonable)
- 0.4.0 Release with basic server
- 0.5.0 Release with async
- 0.6.0 Release with redis
- ????? Rooms
- ????? Refactor Engine.IO to separate crate
- 1.0.0 Stable?
================================================
FILE: ci/.dockerignore
================================================
node_modules
================================================
FILE: ci/Dockerfile
================================================
FROM node:12.18.1
WORKDIR /test
COPY . ./
COPY start_test_server.sh ./
RUN cp cert/ca.crt /usr/local/share/ca-certificates/ && update-ca-certificates
RUN npm install
RUN chmod u+x start_test_server.sh
CMD ./start_test_server.sh
================================================
FILE: ci/README.md
================================================
# How the CI pipelines are set up
This document explains how the CI pipeline is set up. Generally, the pipeline runs on ever push to the `main` branch or on a pull request.
There are three different pipelines:
* Build and Codestyle: Tries to build the application and checks the code quality and formatting through `cargo clippy` and `cargo fmt`.
If you'd like to trigger this manually, run `make checks` in the project root.
* Build and test: Builds the code and kicks of a docker container containing the socket.io and engine.io servers. Then the tests run against the servers. The servers code should not be changed as the clients' tests assume certain events to fire e.g. an ack gets acked or a certain namespace exists. Two servers are started:
* An engine.io server with some callbacks that send normal string data.
* A _safe_ engine.io server with some callbacks that send normal string data. Generate keys for TLS with `./ci/keygen.sh localhost 127.0.0.1`. This server is used for tests using `wss://` and `https://`.
* A socket.io server which sends string and binary data, handles acks, etc.
* Generate coverage: This action acts like the `Build and test` action, but generates a coverage report as well. Afterward the coverage report is uploaded to codecov.io.
This action also collects the docker server logs and uploads them as an artifact.
# How to run the tests locally
To run the tests locally, simply use `cargo test`, or the various Make targets in the `Makefile`. For example
`make pipeline` runs all tests, but also `clippy` and `rustfmt`.
As some tests depend on a running engine.io/socket.io server, you will need to provide those locally, too. See the
sections below for multiple options to do this.
You will also need to create a self-signed certificate for the secure engine.io server. This can be done with the
helper script `/ci/keygen.sh`. Please make sure to also import the generated ca and server certificates into your
system (i.e. Keychain Access for MacOS, /usr/local/share/ca-certificates/ for linux) and make sure they are "always
trusted".
## Running server processes manually via nodejs
If you'd like to run the full test suite locally, you need to run the five server instances as well (see files in `ci/`
folder). You could do this manually by running them all with node:
```
node engine-io.js
node engine-io-polling.js
node engine-io-secure.js
node socket-io.js
node socket-io-auth.js
node socket-io-restart.js
node socket-io-restart-url-auth.js
```
If you'd like to see debug log as well, export this environment variable beforehand:
```
export DEBUG=*
```
You will need to have the two node packages `socket.io` and `engine.io` installed, if this is not the case, fetch them
via:
```
npm install socket.io engine.io
```
## Running server processes in a Docker container
As running all the node scripts manually is pretty tedious, you can also use a prepared docker container, which can be
built with the Dockerfile located in the `ci/` folder:
```
docker build -t test_suite:latest ci
```
Then you can run the container and forward all the needed ports with the following command:
```
docker run -d --name test_suite -p 4200:4200 -p 4201:4201 -p 4202:4202 -p 4203:4203 -p 4204:4204 -p 4205:4205 -p 4206:4206 test_suite:latest
```
The docker container runs a shell script that starts the two servers in the background and checks if the processes are
still alive.
## Using the Visual Studio Code devcontainer
If you are using Visual Studio Code, the easiest method to get up and running would be to simply use the devcontainer
prepared in the `.devcontainer/` directory. This will also launch the needed server processes and set up networking etc.
Please refer to the vscode [documentation](https://code.visualstudio.com/docs/remote/containers) for more information
on how to use devcontainers.
# Polling vs. Websockets
The underlying engine.io protocol provides two mechanisms for transporting: polling and websockets. In order to test both in the pipeline, the two servers are configured differently. The socket.io test suite always upgrades to websockets as fast as possible while one of the engine.io suites just uses long-polling, the other one uses websockets but is reachable via `https://` and `wss://`. This assures that both the websocket connection code and the long-polling code gets tested (as seen on codecov.io). Keep that in mind while expanding the tests.
================================================
FILE: ci/engine-io-polling.js
================================================
/**
* This is an example server, used to test the current code.
*/
const engine = require('engine.io');
const http = require('http').createServer().listen(4203);
// the engine.io client runs on port 4203
const server = engine.attach(http, {
allowUpgrades: false,
transports: ["polling"]
});
console.log("Started")
server.on('connection', socket => {
console.log("Connected");
socket.on('message', message => {
if (message !== undefined) {
console.log(message.toString());
if (message == "respond") {
socket.send("Roger Roger");
} else if (message == "close") {
socket.close();
}
} else {
console.log("empty message received")
}
});
socket.on('heartbeat', () => {
console.log("heartbeat");
});
socket.on('error', message => {
// Notify the client if there is an error so it's tests will fail
socket.send("ERROR: Received error")
console.log(message.toString());
});
socket.on('close', () => {
console.log("Close");
socket.close();
});
socket.send('hello client');
});
================================================
FILE: ci/engine-io-secure.js
================================================
const fs = require('fs');
const https = require('https');
const eio = require('engine.io');
const serverOpts = {
key: fs.readFileSync("cert/server.key"),
cert: fs.readFileSync("cert/server.crt"),
ca: fs.readFileSync("cert/ca.crt"),
};
const http = https.createServer(serverOpts);
const server = eio.attach(http);
console.log("Started")
http.listen(4202, () => {
server.on('connection', socket => {
console.log("Connected");
socket.on('message', message => {
if (message !== undefined) {
console.log(message.toString());
if (message == "respond") {
socket.send("Roger Roger");
} else if (message == "close") {
socket.close();
}
} else {
console.log("empty message received")
}
});
socket.on('heartbeat', () => {
console.log("heartbeat");
});
socket.on('error', message => {
// Notify the client if there is an error so it's tests will fail
socket.send("ERROR: Received error")
console.log(message.toString());
});
socket.on('close', () => {
console.log("Close");
socket.close();
});
socket.send('hello client');
});
});
================================================
FILE: ci/engine-io.js
================================================
/**
* This is an example server, used to test the current code.
*/
const engine = require('engine.io');
const http = require('http').createServer().listen(4201);
// the engine.io client runs on port 4201
const server = engine.attach(http);
console.log("Started")
server.on('connection', socket => {
console.log("Connected");
socket.on('message', message => {
if (message !== undefined) {
console.log(message.toString());
if (message == "respond") {
socket.send("Roger Roger");
} else if (message == "close") {
socket.close();
}
} else {
console.log("empty message received")
}
});
socket.on('heartbeat', () => {
console.log("heartbeat");
});
socket.on('error', message => {
// Notify the client if there is an error so it's tests will fail
socket.send("ERROR: Received error")
console.log(message.toString());
});
socket.on('close', () => {
console.log("Close");
socket.close();
});
socket.send('hello client');
});
================================================
FILE: ci/keygen.sh
================================================
#!/bin/sh
cd $(dirname $0)
if [ "$1" = "" ] || [ "$2" = "" ]
then
echo "Usage: keygen.sh [DOMAIN] [IP]"
exit 1
fi
DOMAIN="$1"
IP="$2"
CA_NAME=${CA_NAME:-"rust-socketio-dev"}
mkdir cert || true
cd cert
# Credit https://scriptcrunch.com/create-ca-tls-ssl-certificates-keys/
if [ ! -f ca.key ]
then
echo "Generating CA key"
openssl genrsa -out ca.key 4096
fi
if [ ! -f "ca.crt" ]
then
echo "Generating CA cert"
openssl req -x509 -new -nodes -key ca.key -subj "/CN=${CA_NAME}/C=??/L=Varius" -out ca.crt
fi
if [ ! -f "server.key" ]
then
echo "Generating server key"
openssl genrsa -out server.key 4096
fi
if [ ! -f "csr.conf" ]
then
echo """
[ req ]
default_bits = 4096
prompt = no
default_md = sha256
req_extensions = req_ext
distinguished_name = dn
[ dn ]
C = ??
ST = Varius
L = Varius
O = ${DOMAIN}
OU = ${DOMAIN}
CN = ${DOMAIN}
[ req_ext ]
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = ${DOMAIN}
DNS.2 = localhost
IP.1 = ${IP}
""" > csr.conf
fi
if [ ! -f "server.csr" ]
then
echo "Generating server signing request"
openssl req -new -key server.key -out server.csr -config csr.conf
fi
if [ ! -f "server.crt" ]
then
echo "Generating signed server certifcicate"
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -extfile csr.conf
fi
================================================
FILE: ci/package.json
================================================
{
"name": "rust-socketio-test",
"version": "1.0.0",
"description": "A test environment for a socketio client",
"author": "Bastian Kersting",
"license": "MIT",
"dependencies": {
"engine.io": "6.4.2",
"socket.io": "4.0.0"
}
}
================================================
FILE: ci/socket-io-auth.js
================================================
const server = require('http').createServer();
const io = require('socket.io')(server);
console.log('Started');
var callback = client => {
console.log('Connected!');
client.emit('auth', client.handshake.auth.password === '123' ? 'success' : 'failed')
};
io.on('connection', callback);
io.of('/admin').on('connection', callback);
// the socket.io client runs on port 4204
server.listen(4204);
================================================
FILE: ci/socket-io-restart-url-auth.js
================================================
let createServer = require("http").createServer;
let server = createServer();
const io = require("socket.io")(server);
const port = 4206;
const timeout = 200;
const TIMESTAMP_SLACK_ALLOWED = 1000;
function isValidTimestamp(timestampStr) {
if (timestampStr === undefined) return false;
const timestamp = parseInt(timestampStr);
if (isNaN(timestamp)) return false;
const diff = Date.now() - timestamp;
return Math.abs(diff) <= TIMESTAMP_SLACK_ALLOWED;
}
console.log("Started");
var callback = (client) => {
const timestamp = client.request._query.timestamp;
console.log("Connected, timestamp:", timestamp);
if (!isValidTimestamp(timestamp)) {
console.log("Invalid timestamp!");
client.disconnect();
return;
}
client.emit("message", "test");
client.on("restart_server", () => {
console.log("will restart in ", timeout, "ms");
io.close();
setTimeout(() => {
server = createServer();
server.listen(port);
io.attach(server);
console.log("do restart");
}, timeout);
});
};
io.on("connection", callback);
server.listen(port);
================================================
FILE: ci/socket-io-restart.js
================================================
let createServer = require("http").createServer;
let server = createServer();
const io = require("socket.io")(server);
const port = 4205;
const timeout = 2000;
console.log("Started");
var callback = (client) => {
const headers = client.request.headers;
console.log("headers", headers);
const message = headers.message_back || "test";
console.log("Connected!");
client.emit("message", message);
client.on("restart_server", () => {
console.log("will restart in ", timeout, "ms");
io.close();
setTimeout(() => {
server = createServer();
server.listen(port);
io.attach(server);
console.log("do restart");
}, timeout);
});
};
io.on("connection", callback);
server.listen(port);
================================================
FILE: ci/socket-io.js
================================================
const server = require('http').createServer();
const io = require('socket.io')(server);
console.log('Started');
var callback = client => {
console.log('Connected!');
client.on('test', data => {
// Send a message back to the server to confirm the message was received
client.emit('test-received', data);
console.log(['test', data]);
});
client.on('message', data => {
client.emit('message-received', data);
console.log(['message', data]);
});
client.on('test', function (arg, ack) {
console.log('Ack received')
if (ack) {
ack('woot');
}
});
client.on('binary', data => {
var bufView = new Uint8Array(data);
console.log(['binary', 'Yehaa binary payload!']);
for (elem in bufView) {
console.log(['binary', elem]);
}
client.emit('binary-received', data);
console.log(['binary', data]);
});
client.on('binary', function (arg, ack) {
console.log(['binary', 'Ack received, answer with binary'])
if (ack) {
ack(Buffer.from([1, 2, 3]));
}
});
// This event allows the test framework to arbitrarily close the underlying connection
client.on('close_transport', data => {
console.log(['close_transport', 'Request to close transport received'])
// Close underlying websocket connection
client.client.conn.close();
})
client.emit('Hello from the message event!');
client.emit('test', 'Hello from the test event!');
client.emit(Buffer.from([4, 5, 6]));
client.emit('test', Buffer.from([1, 2, 3]));
client.emit('This is the first argument', 'This is the second argument', {
argCount: 3
});
client.emit('on_abc_event', '', {
abc: 0,
some_other: 'value',
});
};
io.on('connection', callback);
io.of('/admin').on('connection', callback);
// the socket.io client runs on port 4201
server.listen(4200);
================================================
FILE: ci/start_test_server.sh
================================================
echo "Starting test environment"
DEBUG=* node engine-io.js &
status=$?
if [ $status -ne 0 ]; then
echo "Failed to start engine.io: $status"
exit $status
fi
echo "Successfully started engine.io instance"
DEBUG=* node engine-io-polling.js &
status=$?
if [ $status -ne 0 ]; then
echo "Failed to start polling engine.io: $status"
exit $status
fi
echo "Successfully started polling engine.io instance"
DEBUG=* node socket-io.js &
status=$?
if [ $status -ne 0 ]; then
echo "Failed to start socket.io: $status"
exit $status
fi
echo "Successfully started socket.io instance"
DEBUG=* node socket-io-auth.js &
status=$?
if [ $status -ne 0 ]; then
echo "Failed to start socket.io auth: $status"
exit $status
fi
echo "Successfully started socket.io auth instance"
DEBUG=* node socket-io-restart.js &
status=$?
if [ $status -ne 0 ]; then
echo "Failed to start socket.io restart: $status"
exit $status
fi
echo "Successfully started socket.io restart instance"
DEBUG=* node socket-io-restart-url-auth.js &
status=$?
if [ $status -ne 0 ]; then
echo "Failed to start socket.io restart url auth: $status"
exit $status
fi
echo "Successfully started socket.io restart url auth instance"
DEBUG=* node engine-io-secure.js &
status=$?
if [ $status -ne 0 ]; then
echo "Failed to start secure engine.io: $status"
exit $status
fi
echo "Successfully started secure engine.io instance"
while sleep 60; do
ps aux | grep socket | grep -q -v grep
PROCESS_1_STATUS=$?
ps aux | grep engine-io.js | grep -q -v grep
PROCESS_2_STATUS=$?
ps aux | grep engine-io-secure.js | grep -q -v grep
PROCESS_3_STATUS=$?
# If the greps above find anything, they exit with 0 status
# If they are not both 0, then something is wrong
if [ $PROCESS_1_STATUS -ne 0 -o $PROCESS_2_STATUS -ne 0 -o $PROCESS_3_STATUS -ne 0 ]; then
echo "One of the processes has already exited."
exit 1
fi
done
================================================
FILE: codecov.yml
================================================
coverage:
range: 50..90 # coverage lower than 50 is red, higher than 90 green, between color code
status:
project: # settings affecting project coverage
default:
target: auto # auto % coverage target
threshold: 5% # allow for 5% reduction of coverage without failing
# do not run coverage on patch nor changes
patch: false
================================================
FILE: engineio/Cargo.toml
================================================
[package]
name = "rust_engineio"
version = "0.6.0"
authors = ["Bastian Kersting <bastian@cmbt.de>"]
edition = "2021"
description = "An implementation of a engineio client written in rust."
readme = "README.md"
repository = "https://github.com/1c3t3a/rust-socketio"
keywords = ["engineio", "network", "protocol", "client"]
categories = ["network-programming", "web-programming", "web-programming::websocket"]
license = "MIT"
[package.metadata.docs.rs]
all-features = true
[dependencies]
base64 = "0.22.1"
bytes = "1"
reqwest = { version = "0.12.4", features = ["blocking", "native-tls", "stream"] }
adler32 = "1.2.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
http = "1.1.0"
tokio-tungstenite = { version = "0.21.0", features = ["native-tls"] }
tungstenite = "0.21.0"
tokio = "1.40.0"
futures-util = { version = "0.3", default-features = false, features = ["sink"] }
async-trait = "0.1.83"
async-stream = "0.3.6"
thiserror = "1.0"
native-tls = "0.2.12"
url = "2.5.4"
[dev-dependencies]
criterion = { version = "0.5.1", features = ["async_tokio"] }
lazy_static = "1.4.0"
[dev-dependencies.tokio]
version = "1.40.0"
# we need the `#[tokio::test]` macro
features = ["macros"]
[[bench]]
name = "engineio"
harness = false
# needs to be present in order to support the benchmark
# ci job
# source: https://bheisler.github.io/criterion.rs/book/faq.html#cargo-bench-gives-unrecognized-option-errors-for-valid-command-line-options
[lib]
bench = false
[features]
default = ["async"]
async-callbacks = []
async = ["async-callbacks"]
================================================
FILE: engineio/README.md
================================================
[](https://crates.io/crates/rust_engineio)
[](https://docs.rs/rust_engineio)
# Rust-engineio-client
An implementation of a engine.io client written in the rust programming language. This implementation currently supports revision 4 of the engine.io protocol. If you have any connection issues with this client, make sure the server uses at least revision 4 of the engine.io protocol.
## Example usage
``` rust
use rust_engineio::{ClientBuilder, Client, packet::{Packet, PacketId}};
use url::Url;
use bytes::Bytes;
// get a client with an `on_open` callback
let client: Client = ClientBuilder::new(Url::parse("http://localhost:4201").unwrap())
.on_open(|_| println!("Connection opened!"))
.build()
.expect("Connection failed");
// connect to the server
client.connect().expect("Connection failed");
// create a packet, in this case a message packet and emit it
let packet = Packet::new(PacketId::Message, Bytes::from_static(b"Hello World"));
client.emit(packet).expect("Server unreachable");
// disconnect from the server
client.disconnect().expect("Disconnect failed")
```
The main entry point for using this crate is the `ClientBuilder` (or `asynchronous::ClientBuilder` respectively)
which provides the opportunity to define how you want to connect to a certain endpoint.
The following connection methods are available:
* `build`: Build websocket if allowed, if not fall back to polling. Standard configuration.
* `build_polling`: enforces a `polling` transport.
* `build_websocket_with_upgrade`: Build socket with a polling transport then upgrade to websocket transport (if possible).
* `build_websocket`: Build socket with only a websocket transport, crashes when websockets are not allowed.
## Current features
This implementation now supports all of the features of the engine.io protocol mentioned [here](https://github.com/socketio/engine.io-protocol).
This includes various transport options, the possibility of sending engine.io packets and registering the
common engine.io event callbacks:
* on_open
* on_close
* on_data
* on_error
* on_packet
It is also possible to pass in custom tls configurations via the `TlsConnector` as well
as custom headers for the opening request.
## Documentation
Documentation of this crate can be found up on [docs.rs](https://docs.rs/rust_engineio).
## Async version
The crate also ships with an asynchronous version that can be enabled with a feature flag.
The async version implements the same features mentioned above.
The asynchronous version has a similar API, just with async functions. Currently the futures
can only be executed with [`tokio`](https://tokio.rs). In the first benchmarks the async version
showed improvements of up to 93% in speed.
To make use of the async version, import the crate as follows:
```toml
[depencencies]
rust-engineio = { version = "0.3.1", features = ["async"] }
```
================================================
FILE: engineio/benches/engineio.rs
================================================
use criterion::{criterion_group, criterion_main};
use native_tls::Certificate;
use native_tls::TlsConnector;
use rust_engineio::error::Error;
use std::fs::File;
use std::io::Read;
use url::Url;
pub use criterion_wrappers::*;
pub use tests::*;
pub use util::*;
pub mod util {
use super::*;
pub fn engine_io_url() -> Result<Url, Error> {
let url = std::env::var("ENGINE_IO_SERVER")
.unwrap_or_else(|_| "http://localhost:4201".to_owned());
Ok(Url::parse(&url)?)
}
pub fn engine_io_url_secure() -> Result<Url, Error> {
let url = std::env::var("ENGINE_IO_SECURE_SERVER")
.unwrap_or_else(|_| "https://localhost:4202".to_owned());
Ok(Url::parse(&url)?)
}
pub fn tls_connector() -> Result<TlsConnector, Error> {
let cert_path = "../".to_owned()
+ &std::env::var("CA_CERT_PATH").unwrap_or_else(|_| "ci/cert/ca.crt".to_owned());
let mut cert_file = File::open(cert_path)?;
let mut buf = vec![];
cert_file.read_to_end(&mut buf)?;
let cert: Certificate = Certificate::from_pem(&buf[..]).unwrap();
Ok(TlsConnector::builder()
// ONLY USE FOR TESTING!
.danger_accept_invalid_hostnames(true)
.add_root_certificate(cert)
.build()
.unwrap())
}
}
/// sync benches
#[cfg(not(feature = "async"))]
pub mod tests {
use bytes::Bytes;
use reqwest::Url;
use rust_engineio::{Client, ClientBuilder, Error, Packet, PacketId};
use crate::tls_connector;
pub fn engine_io_socket_build(url: Url) -> Result<Client, Error> {
ClientBuilder::new(url).build()
}
pub fn engine_io_socket_build_polling(url: Url) -> Result<Client, Error> {
ClientBuilder::new(url).build_polling()
}
pub fn engine_io_socket_build_polling_secure(url: Url) -> Result<Client, Error> {
ClientBuilder::new(url)
.tls_config(tls_connector()?)
.build_polling()
}
pub fn engine_io_socket_build_websocket(url: Url) -> Result<Client, Error> {
ClientBuilder::new(url).build_websocket()
}
pub fn engine_io_socket_build_websocket_secure(url: Url) -> Result<Client, Error> {
ClientBuilder::new(url)
.tls_config(tls_connector()?)
.build_websocket()
}
pub fn engine_io_packet() -> Packet {
Packet::new(PacketId::Message, Bytes::from("hello world"))
}
pub fn engine_io_emit(socket: &Client, packet: Packet) -> Result<(), Error> {
socket.emit(packet)
}
}
#[cfg(not(feature = "async"))]
mod criterion_wrappers {
use criterion::{black_box, Criterion};
use super::*;
pub fn criterion_engine_io_socket_build(c: &mut Criterion) {
let url = engine_io_url().unwrap();
c.bench_function("engine io build", |b| {
b.iter(|| {
engine_io_socket_build(black_box(url.clone()))
.unwrap()
.close()
})
});
}
pub fn criterion_engine_io_socket_build_polling(c: &mut Criterion) {
let url = engine_io_url().unwrap();
c.bench_function("engine io build polling", |b| {
b.iter(|| {
engine_io_socket_build_polling(black_box(url.clone()))
.unwrap()
.close()
})
});
}
pub fn criterion_engine_io_socket_build_polling_secure(c: &mut Criterion) {
let url = engine_io_url_secure().unwrap();
c.bench_function("engine io build polling secure", |b| {
b.iter(|| {
engine_io_socket_build_polling_secure(black_box(url.clone()))
.unwrap()
.close()
})
});
}
pub fn criterion_engine_io_socket_build_websocket(c: &mut Criterion) {
let url = engine_io_url().unwrap();
c.bench_function("engine io build websocket", |b| {
b.iter(|| {
engine_io_socket_build_websocket(black_box(url.clone()))
.unwrap()
.close()
})
});
}
pub fn criterion_engine_io_socket_build_websocket_secure(c: &mut Criterion) {
let url = engine_io_url_secure().unwrap();
c.bench_function("engine io build websocket secure", |b| {
b.iter(|| {
engine_io_socket_build_websocket_secure(black_box(url.clone()))
.unwrap()
.close()
})
});
}
pub fn criterion_engine_io_packet(c: &mut Criterion) {
c.bench_function("engine io packet", |b| b.iter(|| engine_io_packet()));
}
pub fn criterion_engine_io_emit_polling(c: &mut Criterion) {
let url = engine_io_url().unwrap();
let socket = engine_io_socket_build_polling(url).unwrap();
socket.connect().unwrap();
let packet = engine_io_packet();
c.bench_function("engine io polling emit", |b| {
b.iter(|| engine_io_emit(black_box(&socket), black_box(packet.clone())).unwrap())
});
socket.close().unwrap();
}
pub fn criterion_engine_io_emit_polling_secure(c: &mut Criterion) {
let url = engine_io_url_secure().unwrap();
let socket = engine_io_socket_build_polling_secure(url).unwrap();
socket.connect().unwrap();
let packet = engine_io_packet();
c.bench_function("engine io polling secure emit", |b| {
b.iter(|| engine_io_emit(black_box(&socket), black_box(packet.clone())).unwrap())
});
socket.close().unwrap();
}
pub fn criterion_engine_io_emit_websocket(c: &mut Criterion) {
let url = engine_io_url().unwrap();
let socket = engine_io_socket_build_websocket(url).unwrap();
socket.connect().unwrap();
let packet = engine_io_packet();
c.bench_function("engine io websocket emit", |b| {
b.iter(|| engine_io_emit(black_box(&socket), black_box(packet.clone())).unwrap())
});
socket.close().unwrap();
}
pub fn criterion_engine_io_emit_websocket_secure(c: &mut Criterion) {
let url = engine_io_url_secure().unwrap();
let socket = engine_io_socket_build_websocket_secure(url).unwrap();
socket.connect().unwrap();
let packet = engine_io_packet();
c.bench_function("engine io websocket secure emit", |b| {
b.iter(|| engine_io_emit(black_box(&socket), black_box(packet.clone())).unwrap())
});
socket.close().unwrap();
}
}
/// async benches
#[cfg(feature = "async")]
pub mod tests {
use bytes::Bytes;
use rust_engineio::{
asynchronous::{Client, ClientBuilder},
Error, Packet, PacketId,
};
use url::Url;
use crate::tls_connector;
pub async fn engine_io_socket_build(url: Url) -> Result<Client, Error> {
ClientBuilder::new(url).build().await
}
pub async fn engine_io_socket_build_polling(url: Url) -> Result<Client, Error> {
ClientBuilder::new(url).build_polling().await
}
pub async fn engine_io_socket_build_polling_secure(url: Url) -> Result<Client, Error> {
ClientBuilder::new(url)
.tls_config(tls_connector()?)
.build_polling()
.await
}
pub async fn engine_io_socket_build_websocket(url: Url) -> Result<Client, Error> {
ClientBuilder::new(url).build_websocket().await
}
pub async fn engine_io_socket_build_websocket_secure(url: Url) -> Result<Client, Error> {
ClientBuilder::new(url)
.tls_config(tls_connector()?)
.build_websocket()
.await
}
pub fn engine_io_packet() -> Packet {
Packet::new(PacketId::Message, Bytes::from("hello world"))
}
pub async fn engine_io_emit(socket: &Client, packet: Packet) -> Result<(), Error> {
socket.emit(packet).await
}
}
#[cfg(feature = "async")]
mod criterion_wrappers {
use std::sync::Arc;
use bytes::Bytes;
use criterion::{black_box, Criterion};
use lazy_static::lazy_static;
use rust_engineio::{Packet, PacketId};
use tokio::runtime::{Builder, Runtime};
use super::tests::{
engine_io_emit, engine_io_packet, engine_io_socket_build, engine_io_socket_build_polling,
engine_io_socket_build_polling_secure, engine_io_socket_build_websocket,
engine_io_socket_build_websocket_secure,
};
use super::util::{engine_io_url, engine_io_url_secure};
lazy_static! {
static ref RUNTIME: Arc<Runtime> =
Arc::new(Builder::new_multi_thread().enable_all().build().unwrap());
}
pub fn criterion_engine_io_socket_build(c: &mut Criterion) {
let url = engine_io_url().unwrap();
c.bench_function("engine io build", move |b| {
b.to_async(RUNTIME.as_ref()).iter(|| async {
engine_io_socket_build(black_box(url.clone()))
.await
.unwrap()
.close()
.await
})
});
}
pub fn criterion_engine_io_socket_build_polling(c: &mut Criterion) {
let url = engine_io_url().unwrap();
c.bench_function("engine io build polling", move |b| {
b.to_async(RUNTIME.as_ref()).iter(|| async {
engine_io_socket_build_polling(black_box(url.clone()))
.await
.unwrap()
.close()
.await
})
});
}
pub fn criterion_engine_io_socket_build_polling_secure(c: &mut Criterion) {
let url = engine_io_url_secure().unwrap();
c.bench_function("engine io build polling secure", move |b| {
b.to_async(RUNTIME.as_ref()).iter(|| async {
engine_io_socket_build_polling_secure(black_box(url.clone()))
.await
.unwrap()
.close()
.await
})
});
}
pub fn criterion_engine_io_socket_build_websocket(c: &mut Criterion) {
let url = engine_io_url().unwrap();
c.bench_function("engine io build websocket", move |b| {
b.to_async(RUNTIME.as_ref()).iter(|| async {
engine_io_socket_build_websocket(black_box(url.clone()))
.await
.unwrap()
.close()
.await
})
});
}
pub fn criterion_engine_io_socket_build_websocket_secure(c: &mut Criterion) {
let url = engine_io_url_secure().unwrap();
c.bench_function("engine io build websocket secure", move |b| {
b.to_async(RUNTIME.as_ref()).iter(|| async {
engine_io_socket_build_websocket_secure(black_box(url.clone()))
.await
.unwrap()
.close()
.await
})
});
}
pub fn criterion_engine_io_packet(c: &mut Criterion) {
c.bench_function("engine io packet", move |b| {
b.iter(|| Packet::new(PacketId::Message, Bytes::from("hello world")))
});
}
pub fn criterion_engine_io_emit_polling(c: &mut Criterion) {
let url = engine_io_url().unwrap();
let socket = RUNTIME.block_on(async {
let socket = engine_io_socket_build_polling(url).await.unwrap();
socket.connect().await.unwrap();
socket
});
let packet = engine_io_packet();
c.bench_function("engine io polling emit", |b| {
b.to_async(RUNTIME.as_ref()).iter(|| async {
engine_io_emit(black_box(&socket), black_box(packet.clone()))
.await
.unwrap()
})
});
}
pub fn criterion_engine_io_emit_polling_secure(c: &mut Criterion) {
let url = engine_io_url_secure().unwrap();
let socket = RUNTIME.block_on(async {
let socket = engine_io_socket_build_polling_secure(url).await.unwrap();
socket.connect().await.unwrap();
socket
});
let packet = engine_io_packet();
c.bench_function("engine io polling secure emit", |b| {
b.to_async(RUNTIME.as_ref()).iter(|| async {
engine_io_emit(black_box(&socket), black_box(packet.clone()))
.await
.unwrap()
})
});
}
pub fn criterion_engine_io_emit_websocket(c: &mut Criterion) {
let url = engine_io_url().unwrap();
let socket = RUNTIME.block_on(async {
let socket = engine_io_socket_build_websocket(url).await.unwrap();
socket.connect().await.unwrap();
socket
});
let packet = engine_io_packet();
c.bench_function("engine io websocket emit", |b| {
b.to_async(RUNTIME.as_ref()).iter(|| async {
engine_io_emit(black_box(&socket), black_box(packet.clone()))
.await
.unwrap()
})
});
}
pub fn criterion_engine_io_emit_websocket_secure(c: &mut Criterion) {
let url = engine_io_url_secure().unwrap();
let socket = RUNTIME.block_on(async {
let socket = engine_io_socket_build_websocket_secure(url).await.unwrap();
socket.connect().await.unwrap();
socket
});
let packet = engine_io_packet();
c.bench_function("engine io websocket secure emit", |b| {
b.to_async(RUNTIME.as_ref()).iter(|| async {
engine_io_emit(black_box(&socket), black_box(packet.clone()))
.await
.unwrap()
})
});
}
}
criterion_group!(
benches,
criterion_engine_io_socket_build_polling,
criterion_engine_io_socket_build_polling_secure,
criterion_engine_io_socket_build_websocket,
criterion_engine_io_socket_build_websocket_secure,
criterion_engine_io_socket_build,
criterion_engine_io_packet,
criterion_engine_io_emit_polling,
criterion_engine_io_emit_polling_secure,
criterion_engine_io_emit_websocket,
criterion_engine_io_emit_websocket_secure
);
criterion_main!(benches);
================================================
FILE: engineio/src/asynchronous/async_socket.rs
================================================
use std::{
fmt::Debug,
pin::Pin,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
};
use async_stream::try_stream;
use bytes::Bytes;
use futures_util::{stream, Stream, StreamExt};
use tokio::{runtime::Handle, sync::Mutex, time::Instant};
use crate::{
asynchronous::{callback::OptionalCallback, transport::AsyncTransportType},
error::Result,
packet::{HandshakePacket, Payload},
Error, Packet, PacketId,
};
#[derive(Clone)]
pub struct Socket {
handle: Handle,
transport: Arc<Mutex<AsyncTransportType>>,
transport_raw: AsyncTransportType,
on_close: OptionalCallback<()>,
on_data: OptionalCallback<Bytes>,
on_error: OptionalCallback<String>,
on_open: OptionalCallback<()>,
on_packet: OptionalCallback<Packet>,
connected: Arc<AtomicBool>,
last_ping: Arc<Mutex<Instant>>,
last_pong: Arc<Mutex<Instant>>,
connection_data: Arc<HandshakePacket>,
max_ping_timeout: u64,
}
impl Socket {
pub(crate) fn new(
transport: AsyncTransportType,
handshake: HandshakePacket,
on_close: OptionalCallback<()>,
on_data: OptionalCallback<Bytes>,
on_error: OptionalCallback<String>,
on_open: OptionalCallback<()>,
on_packet: OptionalCallback<Packet>,
) -> Self {
let max_ping_timeout = handshake.ping_interval + handshake.ping_timeout;
Socket {
handle: Handle::current(),
on_close,
on_data,
on_error,
on_open,
on_packet,
transport: Arc::new(Mutex::new(transport.clone())),
transport_raw: transport,
connected: Arc::new(AtomicBool::default()),
last_ping: Arc::new(Mutex::new(Instant::now())),
last_pong: Arc::new(Mutex::new(Instant::now())),
connection_data: Arc::new(handshake),
max_ping_timeout,
}
}
/// Opens the connection to a specified server. The first Pong packet is sent
/// to the server to trigger the Ping-cycle.
pub async fn connect(&self) -> Result<()> {
// SAFETY: Has valid handshake due to type
self.connected.store(true, Ordering::Release);
if let Some(on_open) = self.on_open.as_ref() {
let on_open = on_open.clone();
self.handle.spawn(async move { on_open(()).await });
}
// set the last ping to now and set the connected state
*self.last_ping.lock().await = Instant::now();
// emit a pong packet to keep trigger the ping cycle on the server
self.emit(Packet::new(PacketId::Pong, Bytes::new())).await?;
Ok(())
}
/// A helper method that distributes
pub(super) async fn handle_incoming_packet(&self, packet: Packet) -> Result<()> {
// check for the appropriate action or callback
self.handle_packet(packet.clone());
match packet.packet_id {
PacketId::MessageBinary => {
self.handle_data(packet.data.clone());
}
PacketId::Message => {
self.handle_data(packet.data.clone());
}
PacketId::Close => {
self.handle_close();
}
PacketId::Upgrade => {
// this is already checked during the handshake, so just do nothing here
}
PacketId::Ping => {
self.pinged().await;
self.emit(Packet::new(PacketId::Pong, Bytes::new())).await?;
}
PacketId::Pong | PacketId::Open => {
// this will never happen as the pong and open
// packets are only sent by the client
return Err(Error::InvalidPacket());
}
PacketId::Noop => (),
}
Ok(())
}
/// Helper method that parses bytes and returns an iterator over the elements.
fn parse_payload(bytes: Bytes) -> impl Stream<Item = Result<Packet>> {
try_stream! {
let payload = Payload::try_from(bytes);
for elem in payload?.into_iter() {
yield elem;
}
}
}
/// Creates a stream over the incoming packets, uses the streams provided by the
/// underlying transport types.
fn stream(
mut transport: AsyncTransportType,
) -> Pin<Box<impl Stream<Item = Result<Packet>> + 'static + Send>> {
// map the byte stream of the underlying transport
// to a packet stream
Box::pin(try_stream! {
for await payload in transport.as_pin_box() {
for await packet in Self::parse_payload(payload?) {
yield packet?;
}
}
})
}
pub async fn disconnect(&self) -> Result<()> {
if let Some(on_close) = self.on_close.as_ref() {
let on_close = on_close.clone();
self.handle.spawn(async move { on_close(()).await });
}
self.emit(Packet::new(PacketId::Close, Bytes::new()))
.await?;
self.connected.store(false, Ordering::Release);
Ok(())
}
/// Sends a packet to the server.
pub async fn emit(&self, packet: Packet) -> Result<()> {
if !self.connected.load(Ordering::Acquire) {
let error = Error::IllegalActionBeforeOpen();
self.call_error_callback(format!("{}", error));
return Err(error);
}
let is_binary = packet.packet_id == PacketId::MessageBinary;
// send a post request with the encoded payload as body
// if this is a binary attachment, then send the raw bytes
let data: Bytes = if is_binary {
packet.data
} else {
packet.into()
};
let lock = self.transport.lock().await;
let fut = lock.as_transport().emit(data, is_binary);
if let Err(error) = fut.await {
self.call_error_callback(error.to_string());
return Err(error);
}
Ok(())
}
/// Calls the error callback with a given message.
#[inline]
fn call_error_callback(&self, text: String) {
if let Some(on_error) = self.on_error.as_ref() {
let on_error = on_error.clone();
self.handle.spawn(async move { on_error(text).await });
}
}
// Check if the underlying transport client is connected.
pub(crate) fn is_connected(&self) -> bool {
self.connected.load(Ordering::Acquire)
}
pub(crate) async fn pinged(&self) {
*self.last_ping.lock().await = Instant::now();
}
/// Returns the time in milliseconds that is left until a new ping must be received.
/// This is used to detect whether we have been disconnected from the server.
/// See https://socket.io/docs/v4/how-it-works/#disconnection-detection
async fn time_to_next_ping(&self) -> u64 {
match Instant::now().checked_duration_since(*self.last_ping.lock().await) {
Some(since_last_ping) => {
let since_last_ping = since_last_ping.as_millis() as u64;
if since_last_ping > self.max_ping_timeout {
0
} else {
self.max_ping_timeout - since_last_ping
}
}
None => 0,
}
}
pub(crate) fn handle_packet(&self, packet: Packet) {
if let Some(on_packet) = self.on_packet.as_ref() {
let on_packet = on_packet.clone();
self.handle.spawn(async move { on_packet(packet).await });
}
}
pub(crate) fn handle_data(&self, data: Bytes) {
if let Some(on_data) = self.on_data.as_ref() {
let on_data = on_data.clone();
self.handle.spawn(async move { on_data(data).await });
}
}
pub(crate) fn handle_close(&self) {
if let Some(on_close) = self.on_close.as_ref() {
let on_close = on_close.clone();
self.handle.spawn(async move { on_close(()).await });
}
self.connected.store(false, Ordering::Release);
}
/// Returns the packet stream for the client.
pub(crate) fn as_stream<'a>(
&'a self,
) -> Pin<Box<dyn Stream<Item = Result<Packet>> + Send + 'a>> {
stream::unfold(
Self::stream(self.transport_raw.clone()),
|mut stream| async {
// Wait for the next payload or until we should have received the next ping.
match tokio::time::timeout(
std::time::Duration::from_millis(self.time_to_next_ping().await),
stream.next(),
)
.await
{
Ok(result) => result.map(|result| (result, stream)),
// We didn't receive a ping in time and now consider the connection as closed.
Err(_) => {
// Be nice and disconnect properly.
if let Err(e) = self.disconnect().await {
Some((Err(e), stream))
} else {
Some((Err(Error::PingTimeout()), stream))
}
}
}
},
)
.boxed()
}
}
#[cfg_attr(tarpaulin, ignore)]
impl Debug for Socket {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Socket")
.field("transport", &self.transport)
.field("on_close", &self.on_close)
.field("on_data", &self.on_data)
.field("on_error", &self.on_error)
.field("on_open", &self.on_open)
.field("on_packet", &self.on_packet)
.field("connected", &self.connected)
.field("last_ping", &self.last_ping)
.field("last_pong", &self.last_pong)
.field("connection_data", &self.connection_data)
.finish()
}
}
================================================
FILE: engineio/src/asynchronous/async_transports/mod.rs
================================================
mod polling;
mod websocket;
mod websocket_general;
mod websocket_secure;
pub use self::polling::PollingTransport;
pub use self::websocket::WebsocketTransport;
pub use self::websocket_secure::WebsocketSecureTransport;
================================================
FILE: engineio/src/asynchronous/async_transports/polling.rs
================================================
use adler32::adler32;
use async_stream::try_stream;
use async_trait::async_trait;
use base64::{engine::general_purpose, Engine as _};
use bytes::{BufMut, Bytes, BytesMut};
use futures_util::{Stream, StreamExt};
use http::HeaderMap;
use native_tls::TlsConnector;
use reqwest::{Client, ClientBuilder, Response};
use std::fmt::Debug;
use std::time::SystemTime;
use std::{pin::Pin, sync::Arc};
use tokio::sync::RwLock;
use url::Url;
use crate::asynchronous::generator::StreamGenerator;
use crate::{asynchronous::transport::AsyncTransport, error::Result, Error};
/// An asynchronous polling type. Makes use of the nonblocking reqwest types and
/// methods.
#[derive(Clone)]
pub struct PollingTransport {
client: Client,
base_url: Arc<RwLock<Url>>,
generator: StreamGenerator<Bytes>,
}
impl PollingTransport {
pub fn new(
base_url: Url,
tls_config: Option<TlsConnector>,
opening_headers: Option<HeaderMap>,
) -> Self {
let client = match (tls_config, opening_headers) {
(Some(config), Some(map)) => ClientBuilder::new()
.use_preconfigured_tls(config)
.default_headers(map)
.build()
.unwrap(),
(Some(config), None) => ClientBuilder::new()
.use_preconfigured_tls(config)
.build()
.unwrap(),
(None, Some(map)) => ClientBuilder::new().default_headers(map).build().unwrap(),
(None, None) => Client::new(),
};
let mut url = base_url;
url.query_pairs_mut().append_pair("transport", "polling");
PollingTransport {
client: client.clone(),
base_url: Arc::new(RwLock::new(url.clone())),
generator: StreamGenerator::new(Self::stream(url, client)),
}
}
fn address(mut url: Url) -> Result<Url> {
let reader = format!("{:#?}", SystemTime::now());
let hash = adler32(reader.as_bytes()).unwrap();
url.query_pairs_mut().append_pair("t", &hash.to_string());
Ok(url)
}
fn send_request(url: Url, client: Client) -> impl Stream<Item = Result<Response>> {
try_stream! {
let address = Self::address(url);
yield client
.get(address?)
.send().await?
}
}
fn stream(
url: Url,
client: Client,
) -> Pin<Box<dyn Stream<Item = Result<Bytes>> + 'static + Send>> {
Box::pin(try_stream! {
loop {
for await elem in Self::send_request(url.clone(), client.clone()) {
for await bytes in elem?.bytes_stream() {
yield bytes?;
}
}
}
})
}
}
impl Stream for PollingTransport {
type Item = Result<Bytes>;
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
self.generator.poll_next_unpin(cx)
}
}
#[async_trait]
impl AsyncTransport for PollingTransport {
async fn emit(&self, data: Bytes, is_binary_att: bool) -> Result<()> {
let data_to_send = if is_binary_att {
// the binary attachment gets `base64` encoded
let mut packet_bytes = BytesMut::with_capacity(data.len() + 1);
packet_bytes.put_u8(b'b');
let encoded_data = general_purpose::STANDARD.encode(data);
packet_bytes.put(encoded_data.as_bytes());
packet_bytes.freeze()
} else {
data
};
let status = self
.client
.post(self.address().await?)
.body(data_to_send)
.send()
.await?
.status()
.as_u16();
if status != 200 {
let error = Error::IncompleteHttp(status);
return Err(error);
}
Ok(())
}
async fn base_url(&self) -> Result<Url> {
Ok(self.base_url.read().await.clone())
}
async fn set_base_url(&self, base_url: Url) -> Result<()> {
let mut url = base_url;
if !url
.query_pairs()
.any(|(k, v)| k == "transport" && v == "polling")
{
url.query_pairs_mut().append_pair("transport", "polling");
}
*self.base_url.write().await = url;
Ok(())
}
}
impl Debug for PollingTransport {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PollingTransport")
.field("client", &self.client)
.field("base_url", &self.base_url)
.finish()
}
}
#[cfg(test)]
mod test {
use crate::asynchronous::transport::AsyncTransport;
use super::*;
use std::str::FromStr;
#[tokio::test]
async fn polling_transport_base_url() -> Result<()> {
let url = crate::test::engine_io_server()?.to_string();
let transport = PollingTransport::new(Url::from_str(&url[..]).unwrap(), None, None);
assert_eq!(
transport.base_url().await?.to_string(),
url.clone() + "?transport=polling"
);
transport
.set_base_url(Url::parse("https://127.0.0.1")?)
.await?;
assert_eq!(
transport.base_url().await?.to_string(),
"https://127.0.0.1/?transport=polling"
);
assert_ne!(transport.base_url().await?.to_string(), url);
transport
.set_base_url(Url::parse("http://127.0.0.1/?transport=polling")?)
.await?;
assert_eq!(
transport.base_url().await?.to_string(),
"http://127.0.0.1/?transport=polling"
);
assert_ne!(transport.base_url().await?.to_string(), url);
Ok(())
}
}
================================================
FILE: engineio/src/asynchronous/async_transports/websocket.rs
================================================
use std::fmt::Debug;
use std::pin::Pin;
use std::sync::Arc;
use crate::asynchronous::transport::AsyncTransport;
use crate::error::Result;
use async_trait::async_trait;
use bytes::Bytes;
use futures_util::stream::StreamExt;
use futures_util::Stream;
use http::HeaderMap;
use tokio::sync::RwLock;
use tokio_tungstenite::connect_async;
use tungstenite::client::IntoClientRequest;
use url::Url;
use super::websocket_general::AsyncWebsocketGeneralTransport;
/// An asynchronous websocket transport type.
/// This type only allows for plain websocket
/// connections ("ws://").
#[derive(Clone)]
pub struct WebsocketTransport {
inner: AsyncWebsocketGeneralTransport,
base_url: Arc<RwLock<Url>>,
}
impl WebsocketTransport {
/// Creates a new instance over a request that might hold additional headers and an URL.
pub async fn new(base_url: Url, headers: Option<HeaderMap>) -> Result<Self> {
let mut url = base_url;
url.query_pairs_mut().append_pair("transport", "websocket");
url.set_scheme("ws").unwrap();
let mut req = url.clone().into_client_request()?;
if let Some(map) = headers {
// SAFETY: this unwrap never panics as the underlying request is just initialized and in proper state
req.headers_mut().extend(map);
}
let (ws_stream, _) = connect_async(req).await?;
let (sen, rec) = ws_stream.split();
let inner = AsyncWebsocketGeneralTransport::new(sen, rec).await;
Ok(WebsocketTransport {
inner,
base_url: Arc::new(RwLock::new(url)),
})
}
/// Sends probe packet to ensure connection is valid, then sends upgrade
/// request
pub(crate) async fn upgrade(&self) -> Result<()> {
self.inner.upgrade().await
}
pub(crate) async fn poll_next(&self) -> Result<Option<Bytes>> {
self.inner.poll_next().await
}
}
#[async_trait]
impl AsyncTransport for WebsocketTransport {
async fn emit(&self, data: Bytes, is_binary_att: bool) -> Result<()> {
self.inner.emit(data, is_binary_att).await
}
async fn base_url(&self) -> Result<Url> {
Ok(self.base_url.read().await.clone())
}
async fn set_base_url(&self, base_url: Url) -> Result<()> {
let mut url = base_url;
if !url
.query_pairs()
.any(|(k, v)| k == "transport" && v == "websocket")
{
url.query_pairs_mut().append_pair("transport", "websocket");
}
url.set_scheme("ws").unwrap();
*self.base_url.write().await = url;
Ok(())
}
}
impl Stream for WebsocketTransport {
type Item = Result<Bytes>;
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
self.inner.poll_next_unpin(cx)
}
}
impl Debug for WebsocketTransport {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AsyncWebsocketTransport")
.field(
"base_url",
&self
.base_url
.try_read()
.map_or("Currently not available".to_owned(), |url| url.to_string()),
)
.finish()
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::ENGINE_IO_VERSION;
use std::str::FromStr;
async fn new() -> Result<WebsocketTransport> {
let url = crate::test::engine_io_server()?.to_string()
+ "engine.io/?EIO="
+ &ENGINE_IO_VERSION.to_string();
WebsocketTransport::new(Url::from_str(&url[..])?, None).await
}
#[tokio::test]
async fn websocket_transport_base_url() -> Result<()> {
let transport = new().await?;
let mut url = crate::test::engine_io_server()?;
url.set_path("/engine.io/");
url.query_pairs_mut()
.append_pair("EIO", &ENGINE_IO_VERSION.to_string())
.append_pair("transport", "websocket");
url.set_scheme("ws").unwrap();
assert_eq!(transport.base_url().await?.to_string(), url.to_string());
transport
.set_base_url(reqwest::Url::parse("https://127.0.0.1")?)
.await?;
assert_eq!(
transport.base_url().await?.to_string(),
"ws://127.0.0.1/?transport=websocket"
);
assert_ne!(transport.base_url().await?.to_string(), url.to_string());
transport
.set_base_url(reqwest::Url::parse(
"http://127.0.0.1/?transport=websocket",
)?)
.await?;
assert_eq!(
transport.base_url().await?.to_string(),
"ws://127.0.0.1/?transport=websocket"
);
assert_ne!(transport.base_url().await?.to_string(), url.to_string());
Ok(())
}
#[tokio::test]
async fn websocket_secure_debug() -> Result<()> {
let mut transport = new().await?;
assert_eq!(
format!("{:?}", transport),
format!(
"AsyncWebsocketTransport {{ base_url: {:?} }}",
transport.base_url().await?.to_string()
)
);
println!("{:?}", transport.next().await.unwrap());
println!("{:?}", transport.next().await.unwrap());
Ok(())
}
}
================================================
FILE: engineio/src/asynchronous/async_transports/websocket_general.rs
================================================
use std::{borrow::Cow, str::from_utf8, sync::Arc, task::Poll};
use crate::{error::Result, Error, Packet, PacketId};
use bytes::{BufMut, Bytes, BytesMut};
use futures_util::{
ready,
stream::{SplitSink, SplitStream},
FutureExt, SinkExt, Stream, StreamExt,
};
use tokio::{net::TcpStream, sync::Mutex};
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
use tungstenite::Message;
type AsyncWebsocketSender = SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>;
type AsyncWebsocketReceiver = SplitStream<WebSocketStream<MaybeTlsStream<TcpStream>>>;
/// A general purpose asynchronous websocket transport type. Holds
/// the sender and receiver stream of a websocket connection
/// and implements the common methods `update` and `emit`. This also
/// implements `Stream`.
#[derive(Clone)]
pub(crate) struct AsyncWebsocketGeneralTransport {
sender: Arc<Mutex<AsyncWebsocketSender>>,
receiver: Arc<Mutex<AsyncWebsocketReceiver>>,
}
impl AsyncWebsocketGeneralTransport {
pub(crate) async fn new(
sender: SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>,
receiver: SplitStream<WebSocketStream<MaybeTlsStream<TcpStream>>>,
) -> Self {
AsyncWebsocketGeneralTransport {
sender: Arc::new(Mutex::new(sender)),
receiver: Arc::new(Mutex::new(receiver)),
}
}
/// Sends probe packet to ensure connection is valid, then sends upgrade
/// request
pub(crate) async fn upgrade(&self) -> Result<()> {
let mut receiver = self.receiver.lock().await;
let mut sender = self.sender.lock().await;
sender
.send(Message::text(Cow::Borrowed(from_utf8(&Bytes::from(
Packet::new(PacketId::Ping, Bytes::from("probe")),
))?)))
.await?;
let msg = receiver
.next()
.await
.ok_or(Error::IllegalWebsocketUpgrade())??;
if msg.into_data() != Bytes::from(Packet::new(PacketId::Pong, Bytes::from("probe"))) {
return Err(Error::InvalidPacket());
}
sender
.send(Message::text(Cow::Borrowed(from_utf8(&Bytes::from(
Packet::new(PacketId::Upgrade, Bytes::from("")),
))?)))
.await?;
Ok(())
}
pub(crate) async fn emit(&self, data: Bytes, is_binary_att: bool) -> Result<()> {
let mut sender = self.sender.lock().await;
let message = if is_binary_att {
Message::binary(Cow::Borrowed(data.as_ref()))
} else {
Message::text(Cow::Borrowed(std::str::from_utf8(data.as_ref())?))
};
sender.send(message).await?;
Ok(())
}
pub(crate) async fn poll_next(&self) -> Result<Option<Bytes>> {
loop {
let mut receiver = self.receiver.lock().await;
let next = receiver.next().await;
match next {
Some(Ok(Message::Text(str))) => return Ok(Some(Bytes::from(str))),
Some(Ok(Message::Binary(data))) => {
let mut msg = BytesMut::with_capacity(data.len() + 1);
msg.put_u8(PacketId::Message as u8);
msg.put(data.as_ref());
return Ok(Some(msg.freeze()));
}
// ignore packets other than text and binary
Some(Ok(_)) => (),
Some(Err(err)) => return Err(err.into()),
None => return Ok(None),
}
}
}
}
impl Stream for AsyncWebsocketGeneralTransport {
type Item = Result<Bytes>;
fn poll_next(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
loop {
let mut lock = ready!(Box::pin(self.receiver.lock()).poll_unpin(cx));
let next = ready!(lock.poll_next_unpin(cx));
match next {
Some(Ok(Message::Text(str))) => return Poll::Ready(Some(Ok(Bytes::from(str)))),
Some(Ok(Message::Binary(data))) => {
let mut msg = BytesMut::with_capacity(data.len() + 1);
msg.put_u8(PacketId::Message as u8);
msg.put(data.as_ref());
return Poll::Ready(Some(Ok(msg.freeze())));
}
// ignore packets other than text and binary
Some(Ok(_)) => (),
Some(Err(err)) => return Poll::Ready(Some(Err(err.into()))),
None => return Poll::Ready(None),
}
}
}
}
================================================
FILE: engineio/src/asynchronous/async_transports/websocket_secure.rs
================================================
use std::fmt::Debug;
use std::pin::Pin;
use std::sync::Arc;
use crate::asynchronous::transport::AsyncTransport;
use crate::error::Result;
use async_trait::async_trait;
use bytes::Bytes;
use futures_util::Stream;
use futures_util::StreamExt;
use http::HeaderMap;
use native_tls::TlsConnector;
use tokio::sync::RwLock;
use tokio_tungstenite::connect_async_tls_with_config;
use tokio_tungstenite::Connector;
use tungstenite::client::IntoClientRequest;
use url::Url;
use super::websocket_general::AsyncWebsocketGeneralTransport;
/// An asynchronous websocket transport type.
/// This type only allows for secure websocket
/// connections ("wss://").
#[derive(Clone)]
pub struct WebsocketSecureTransport {
inner: AsyncWebsocketGeneralTransport,
base_url: Arc<RwLock<Url>>,
}
impl WebsocketSecureTransport {
/// Creates a new instance over a request that might hold additional headers, a possible
/// Tls connector and an URL.
pub(crate) async fn new(
base_url: Url,
tls_config: Option<TlsConnector>,
headers: Option<HeaderMap>,
) -> Result<Self> {
let mut url = base_url;
url.query_pairs_mut().append_pair("transport", "websocket");
url.set_scheme("wss").unwrap();
let mut req = url.clone().into_client_request()?;
if let Some(map) = headers {
// SAFETY: this unwrap never panics as the underlying request is just initialized and in proper state
req.headers_mut().extend(map);
}
// `disable_nagle` Sets the value of the TCP_NODELAY option on this socket.
//
// If set to `true`, this option disables the Nagle algorithm.
// This means that segments are always sent as soon as possible, even if there is only a small amount of data.
// When `false`, data is buffered until there is a sufficient amount to send out, thereby avoiding the frequent sending of small packets.
//
// See the docs: https://docs.rs/tokio/latest/tokio/net/struct.TcpStream.html#method.set_nodelay
let (ws_stream, _) = connect_async_tls_with_config(
req,
None,
/*disable_nagle=*/ false,
tls_config.map(Connector::NativeTls),
)
.await?;
let (sen, rec) = ws_stream.split();
let inner = AsyncWebsocketGeneralTransport::new(sen, rec).await;
Ok(WebsocketSecureTransport {
inner,
base_url: Arc::new(RwLock::new(url)),
})
}
/// Sends probe packet to ensure connection is valid, then sends upgrade
/// request
pub(crate) async fn upgrade(&self) -> Result<()> {
self.inner.upgrade().await
}
pub(crate) async fn poll_next(&self) -> Result<Option<Bytes>> {
self.inner.poll_next().await
}
}
impl Stream for WebsocketSecureTransport {
type Item = Result<Bytes>;
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
self.inner.poll_next_unpin(cx)
}
}
#[async_trait]
impl AsyncTransport for WebsocketSecureTransport {
async fn emit(&self, data: Bytes, is_binary_att: bool) -> Result<()> {
self.inner.emit(data, is_binary_att).await
}
async fn base_url(&self) -> Result<Url> {
Ok(self.base_url.read().await.clone())
}
async fn set_base_url(&self, base_url: Url) -> Result<()> {
let mut url = base_url;
if !url
.query_pairs()
.any(|(k, v)| k == "transport" && v == "websocket")
{
url.query_pairs_mut().append_pair("transport", "websocket");
}
url.set_scheme("wss").unwrap();
*self.base_url.write().await = url;
Ok(())
}
}
impl Debug for WebsocketSecureTransport {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AsyncWebsocketSecureTransport")
.field(
"base_url",
&self
.base_url
.try_read()
.map_or("Currently not available".to_owned(), |url| url.to_string()),
)
.finish()
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::ENGINE_IO_VERSION;
use std::str::FromStr;
async fn new() -> Result<WebsocketSecureTransport> {
let url = crate::test::engine_io_server_secure()?.to_string()
+ "engine.io/?EIO="
+ &ENGINE_IO_VERSION.to_string();
WebsocketSecureTransport::new(
Url::from_str(&url[..])?,
Some(crate::test::tls_connector()?),
None,
)
.await
}
#[tokio::test]
async fn websocket_secure_transport_base_url() -> Result<()> {
let transport = new().await?;
let mut url = crate::test::engine_io_server_secure()?;
url.set_path("/engine.io/");
url.query_pairs_mut()
.append_pair("EIO", &ENGINE_IO_VERSION.to_string())
.append_pair("transport", "websocket");
url.set_scheme("wss").unwrap();
assert_eq!(transport.base_url().await?.to_string(), url.to_string());
transport
.set_base_url(reqwest::Url::parse("https://127.0.0.1")?)
.await?;
assert_eq!(
transport.base_url().await?.to_string(),
"wss://127.0.0.1/?transport=websocket"
);
assert_ne!(transport.base_url().await?.to_string(), url.to_string());
transport
.set_base_url(reqwest::Url::parse(
"http://127.0.0.1/?transport=websocket",
)?)
.await?;
assert_eq!(
transport.base_url().await?.to_string(),
"wss://127.0.0.1/?transport=websocket"
);
assert_ne!(transport.base_url().await?.to_string(), url.to_string());
Ok(())
}
#[tokio::test]
async fn websocket_secure_debug() -> Result<()> {
let transport = new().await?;
assert_eq!(
format!("{:?}", transport),
format!(
"AsyncWebsocketSecureTransport {{ base_url: {:?} }}",
transport.base_url().await?.to_string()
)
);
Ok(())
}
}
================================================
FILE: engineio/src/asynchronous/callback.rs
================================================
use bytes::Bytes;
use futures_util::future::BoxFuture;
use std::{fmt::Debug, ops::Deref, sync::Arc};
use crate::Packet;
/// Internal type, provides a way to store futures and return them in a boxed manner.
pub(crate) type DynAsyncCallback<I> = dyn 'static + Send + Sync + Fn(I) -> BoxFuture<'static, ()>;
/// Internal type, might hold an async callback.
#[derive(Clone)]
pub(crate) struct OptionalCallback<I> {
inner: Option<Arc<DynAsyncCallback<I>>>,
}
impl<I> OptionalCallback<I> {
pub(crate) fn new<T>(callback: T) -> Self
where
T: 'static + Send + Sync + Fn(I) -> BoxFuture<'static, ()>,
{
OptionalCallback {
inner: Some(Arc::new(callback)),
}
}
pub(crate) fn default() -> Self {
OptionalCallback { inner: None }
}
}
#[cfg_attr(tarpaulin, ignore)]
impl Debug for OptionalCallback<String> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.write_fmt(format_args!(
"Callback({:?})",
if self.inner.is_some() {
"Fn(String)"
} else {
"None"
}
))
}
}
#[cfg_attr(tarpaulin, ignore)]
impl Debug for OptionalCallback<()> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.write_fmt(format_args!(
"Callback({:?})",
if self.inner.is_some() {
"Fn(())"
} else {
"None"
}
))
}
}
#[cfg_attr(tarpaulin, ignore)]
impl Debug for OptionalCallback<Packet> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.write_fmt(format_args!(
"Callback({:?})",
if self.inner.is_some() {
"Fn(Packet)"
} else {
"None"
}
))
}
}
#[cfg_attr(tarpaulin, ignore)]
impl Debug for OptionalCallback<Bytes> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.write_fmt(format_args!(
"Callback({:?})",
if self.inner.is_some() {
"Fn(Bytes)"
} else {
"None"
}
))
}
}
impl<I> Deref for OptionalCallback<I> {
type Target = Option<Arc<DynAsyncCallback<I>>>;
fn deref(&self) -> &<Self as std::ops::Deref>::Target {
&self.inner
}
}
================================================
FILE: engineio/src/asynchronous/client/async_client.rs
================================================
use std::{fmt::Debug, pin::Pin};
use crate::{
asynchronous::{async_socket::Socket as InnerSocket, generator::StreamGenerator},
error::Result,
Packet,
};
use async_stream::try_stream;
use futures_util::{Stream, StreamExt};
/// An engine.io client that allows interaction with the connected engine.io
/// server. This client provides means for connecting, disconnecting and sending
/// packets to the server.
///
/// ## Note:
/// There is no need to put this Client behind an `Arc`, as the type uses `Arc`
/// internally and provides a shared state beyond all cloned instances.
#[derive(Clone)]
pub struct Client {
pub(super) socket: InnerSocket,
generator: StreamGenerator<Packet>,
}
impl Client {
pub(super) fn new(socket: InnerSocket) -> Self {
Client {
socket: socket.clone(),
generator: StreamGenerator::new(Self::stream(socket)),
}
}
pub async fn close(&self) -> Result<()> {
self.socket.disconnect().await
}
/// Opens the connection to a specified server. The first Pong packet is sent
/// to the server to trigger the Ping-cycle.
pub async fn connect(&self) -> Result<()> {
self.socket.connect().await
}
/// Disconnects the connection.
pub async fn disconnect(&self) -> Result<()> {
self.socket.disconnect().await
}
/// Sends a packet to the server.
pub async fn emit(&self, packet: Packet) -> Result<()> {
self.socket.emit(packet).await
}
/// Static method that returns a generator for each element of the stream.
fn stream(
socket: InnerSocket,
) -> Pin<Box<impl Stream<Item = Result<Packet>> + 'static + Send>> {
Box::pin(try_stream! {
let socket = socket.clone();
for await item in socket.as_stream() {
let packet = item?;
socket.handle_incoming_packet(packet.clone()).await?;
yield packet;
}
})
}
/// Check if the underlying transport client is connected.
pub fn is_connected(&self) -> bool {
self.socket.is_connected()
}
}
impl Stream for Client {
type Item = Result<Packet>;
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
self.generator.poll_next_unpin(cx)
}
}
impl Debug for Client {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Client")
.field("socket", &self.socket)
.finish()
}
}
#[cfg(all(test))]
mod test {
use super::*;
use crate::{asynchronous::ClientBuilder, header::HeaderMap, packet::PacketId, Error};
use bytes::Bytes;
use futures_util::StreamExt;
use native_tls::TlsConnector;
use url::Url;
/// The purpose of this test is to check whether the Client is properly cloneable or not.
/// As the documentation of the engine.io client states, the object needs to maintain it's internal
/// state when cloned and the cloned object should reflect the same state throughout the lifetime
/// of both objects (initial and cloned).
#[tokio::test]
async fn test_client_cloneable() -> Result<()> {
let url = crate::test::engine_io_server()?;
let mut sut = builder(url).build().await?;
let mut cloned = sut.clone();
sut.connect().await?;
// when the underlying socket is connected, the
// state should also change on the cloned one
assert!(sut.is_connected());
assert!(cloned.is_connected());
// both clients should reflect the same messages.
assert_eq!(
sut.next().await.unwrap()?,
Packet::new(PacketId::Message, "hello client")
);
sut.emit(Packet::new(PacketId::Message, "respond")).await?;
assert_eq!(
cloned.next().await.unwrap()?,
Packet::new(PacketId::Message, "Roger Roger")
);
cloned.disconnect().await?;
// when the underlying socket is disconnected, the
// state should also change on the cloned one
assert!(!sut.is_connected());
assert!(!cloned.is_connected());
Ok(())
}
#[tokio::test]
async fn test_illegal_actions() -> Result<()> {
let url = crate::test::engine_io_server()?;
let mut sut = builder(url.clone()).build().await?;
assert!(sut
.emit(Packet::new(PacketId::Close, Bytes::new()))
.await
.is_err());
sut.connect().await?;
assert!(sut.next().await.unwrap().is_ok());
assert!(builder(Url::parse("fake://fake.fake").unwrap())
.build_websocket()
.await
.is_err());
sut.disconnect().await?;
Ok(())
}
use reqwest::header::HOST;
use crate::packet::Packet;
fn builder(url: Url) -> ClientBuilder {
ClientBuilder::new(url)
.on_open(|_| {
Box::pin(async {
println!("Open event!");
})
})
.on_packet(|packet| {
Box::pin(async move {
println!("Received packet: {:?}", packet);
})
})
.on_data(|data| {
Box::pin(async move {
println!("Received data: {:?}", std::str::from_utf8(&data));
})
})
.on_close(|_| {
Box::pin(async {
println!("Close event!");
})
})
.on_error(|error| {
Box::pin(async move {
println!("Error {}", error);
})
})
}
async fn test_connection(socket: Client) -> Result<()> {
let mut socket = socket;
socket.connect().await.unwrap();
assert_eq!(
socket.next().await.unwrap()?,
Packet::new(PacketId::Message, "hello client")
);
println!("received msg, about to send");
socket
.emit(Packet::new(PacketId::Message, "respond"))
.await?;
println!("send msg");
assert_eq!(
socket.next().await.unwrap()?,
Packet::new(PacketId::Message, "Roger Roger")
);
println!("received 2");
socket.close().await
}
#[tokio::test]
async fn test_connection_long() -> Result<()> {
// Long lived socket to receive pings
let url = crate::test::engine_io_server()?;
let mut socket = builder(url).build().await?;
socket.connect().await?;
// hello client
assert!(matches!(
socket.next().await.unwrap()?,
Packet {
packet_id: PacketId::Message,
..
}
));
// Ping
assert!(matches!(
socket.next().await.unwrap()?,
Packet {
packet_id: PacketId::Ping,
..
}
));
socket.disconnect().await?;
assert!(!socket.is_connected());
Ok(())
}
#[tokio::test]
async fn test_connection_dynamic() -> Result<()> {
let url = crate::test::engine_io_server()?;
let socket = builder(url).build().await?;
test_connection(socket).await?;
let url = crate::test::engine_io_polling_server()?;
let socket = builder(url).build().await?;
test_connection(socket).await
}
#[tokio::test]
async fn test_connection_fallback() -> Result<()> {
let url = crate::test::engine_io_server()?;
let socket = builder(url).build_with_fallback().await?;
test_connection(socket).await?;
let url = crate::test::engine_io_polling_server()?;
let socket = builder(url).build_with_fallback().await?;
test_connection(socket).await
}
#[tokio::test]
async fn test_connection_dynamic_secure() -> Result<()> {
let url = crate::test::engine_io_server_secure()?;
let mut socket_builder = builder(url);
socket_builder = socket_builder.tls_config(crate::test::tls_connector()?);
let socket = socket_builder.build().await?;
test_connection(socket).await
}
#[tokio::test]
async fn test_connection_polling() -> Result<()> {
let url = crate::test::engine_io_server()?;
let socket = builder(url).build_polling().await?;
test_connection(socket).await
}
#[tokio::test]
async fn test_connection_wss() -> Result<()> {
let url = crate::test::engine_io_polling_server()?;
assert!(builder(url).build_websocket_with_upgrade().await.is_err());
let host =
std::env::var("ENGINE_IO_SECURE_HOST").unwrap_or_else(|_| "localhost".to_owned());
let mut url = crate::test::engine_io_server_secure()?;
let mut headers = HeaderMap::default();
headers.insert(HOST, host);
let mut builder = builder(url.clone());
builder = builder.tls_config(crate::test::tls_connector()?);
builder = builder.headers(headers.clone());
let socket = builder.clone().build_websocket_with_upgrade().await?;
test_connection(socket).await?;
let socket = builder.build_websocket().await?;
test_connection(socket).await?;
url.set_scheme("wss").unwrap();
let builder = self::builder(url)
.tls_config(crate::test::tls_connector()?)
.headers(headers);
let socket = builder.clone().build_websocket().await?;
test_connection(socket).await?;
assert!(builder.build_websocket_with_upgrade().await.is_err());
Ok(())
}
#[tokio::test]
async fn test_connection_ws() -> Result<()> {
let url = crate::test::engine_io_polling_server()?;
assert!(builder(url.clone()).build_websocket().await.is_err());
assert!(builder(url).build_websocket_with_upgrade().await.is_err());
let mut url = crate::test::engine_io_server()?;
let builder = builder(url.clone());
let socket = builder.clone().build_websocket().await?;
test_connection(socket).await?;
let socket = builder.build_websocket_with_upgrade().await?;
test_connection(socket).await?;
url.set_scheme("ws").unwrap();
let builder = self::builder(url);
let socket = builder.clone().build_websocket().await?;
test_connection(socket).await?;
assert!(builder.build_websocket_with_upgrade().await.is_err());
Ok(())
}
#[tokio::test]
async fn test_open_invariants() -> Result<()> {
let url = crate::test::engine_io_server()?;
let illegal_url = "this is illegal";
assert!(Url::parse(illegal_url).is_err());
let invalid_protocol = "file:///tmp/foo";
assert!(builder(Url::parse(invalid_protocol).unwrap())
.build()
.await
.is_err());
let sut = builder(url.clone()).build().await?;
let _error = sut
.emit(Packet::new(PacketId::Close, Bytes::new()))
.await
.expect_err("error");
assert!(matches!(Error::IllegalActionBeforeOpen(), _error));
// test missing match arm in socket constructor
let mut headers = HeaderMap::default();
let host =
std::env::var("ENGINE_IO_SECURE_HOST").unwrap_or_else(|_| "localhost".to_owned());
headers.insert(HOST, host);
let _ = builder(url.clone())
.tls_config(
TlsConnector::builder()
.danger_accept_invalid_certs(true)
.build()
.unwrap(),
)
.build()
.await?;
let _ = builder(url).headers(headers).build().await?;
Ok(())
}
}
================================================
FILE: engineio/src/asynchronous/client/builder.rs
================================================
use crate::{
asynchronous::{
async_socket::Socket as InnerSocket,
async_transports::{PollingTransport, WebsocketSecureTransport, WebsocketTransport},
callback::OptionalCallback,
transport::AsyncTransport,
},
error::Result,
header::HeaderMap,
packet::HandshakePacket,
Error, Packet, ENGINE_IO_VERSION,
};
use bytes::Bytes;
use futures_util::{future::BoxFuture, StreamExt};
use native_tls::TlsConnector;
use url::Url;
use super::Client;
#[derive(Clone, Debug)]
pub struct ClientBuilder {
url: Url,
tls_config: Option<TlsConnector>,
headers: Option<HeaderMap>,
handshake: Option<HandshakePacket>,
on_error: OptionalCallback<String>,
on_open: OptionalCallback<()>,
on_close: OptionalCallback<()>,
on_data: OptionalCallback<Bytes>,
on_packet: OptionalCallback<Packet>,
}
impl ClientBuilder {
pub fn new(url: Url) -> Self {
let mut url = url;
url.query_pairs_mut()
.append_pair("EIO", &ENGINE_IO_VERSION.to_string());
// No path add engine.io
if url.path() == "/" {
url.set_path("/engine.io/");
}
ClientBuilder {
url,
headers: None,
tls_config: None,
handshake: None,
on_close: OptionalCallback::default(),
on_data: OptionalCallback::default(),
on_error: OptionalCallback::default(),
on_open: OptionalCallback::default(),
on_packet: OptionalCallback::default(),
}
}
/// Specify transport's tls config
pub fn tls_config(mut self, tls_config: TlsConnector) -> Self {
self.tls_config = Some(tls_config);
self
}
/// Specify transport's HTTP headers
pub fn headers(mut self, headers: HeaderMap) -> Self {
self.headers = Some(headers);
self
}
/// Registers the `on_close` callback.
#[cfg(feature = "async-callbacks")]
pub fn on_close<T>(mut self, callback: T) -> Self
where
T: 'static + Send + Sync + Fn(()) -> BoxFuture<'static, ()>,
{
self.on_close = OptionalCallback::new(callback);
self
}
/// Registers the `on_data` callback.
#[cfg(feature = "async-callbacks")]
pub fn on_data<T>(mut self, callback: T) -> Self
where
T: 'static + Send + Sync + Fn(Bytes) -> BoxFuture<'static, ()>,
{
self.on_data = OptionalCallback::new(callback);
self
}
/// Registers the `on_error` callback.
#[cfg(feature = "async-callbacks")]
pub fn on_error<T>(mut self, callback: T) -> Self
where
T: 'static + Send + Sync + Fn(String) -> BoxFuture<'static, ()>,
{
self.on_error = OptionalCallback::new(callback);
self
}
/// Registers the `on_open` callback.
#[cfg(feature = "async-callbacks")]
pub fn on_open<T>(mut self, callback: T) -> Self
where
T: 'static + Send + Sync + Fn(()) -> BoxFuture<'static, ()>,
{
self.on_open = OptionalCallback::new(callback);
self
}
/// Registers the `on_packet` callback.
#[cfg(feature = "async-callbacks")]
pub fn on_packet<T>(mut self, callback: T) -> Self
where
T: 'static + Send + Sync + Fn(Packet) -> BoxFuture<'static, ()>,
{
self.on_packet = OptionalCallback::new(callback);
self
}
/// Performs the handshake
async fn handshake_with_transport<T: AsyncTransport + Unpin>(
&mut self,
transport: &mut T,
) -> Result<()> {
// No need to handshake twice
if self.handshake.is_some() {
return Ok(());
}
let mut url = self.url.clone();
let handshake: HandshakePacket =
Packet::try_from(transport.next().await.ok_or(Error::IncompletePacket())??)?
.try_into()?;
// update the base_url with the new sid
url.query_pairs_mut().append_pair("sid", &handshake.sid[..]);
self.handshake = Some(handshake);
self.url = url;
Ok(())
}
async fn handshake(&mut self) -> Result<()> {
if self.handshake.is_some() {
return Ok(());
}
let headers = if let Some(map) = self.headers.clone() {
Some(map.try_into()?)
} else {
None
};
// Start with polling transport
let mut transport =
PollingTransport::new(self.url.clone(), self.tls_config.clone(), headers);
self.handshake_with_transport(&mut transport).await
}
/// Build websocket if allowed, if not fall back to polling
pub async fn build(mut self) -> Result<Client> {
self.handshake().await?;
if self.websocket_upgrade()? {
self.build_websocket_with_upgrade().await
} else {
self.build_polling().await
}
}
/// Build socket with polling transport
pub async fn build_polling(mut self) -> Result<Client> {
self.handshake().await?;
// Make a polling transport with new sid
let transport = PollingTransport::new(
self.url,
self.tls_config,
self.headers.map(|v| v.try_into().unwrap()),
);
// SAFETY: handshake function called previously.
Ok(Client::new(InnerSocket::new(
transport.into(),
self.handshake.unwrap(),
self.on_close,
self.on_data,
self.on_error,
self.on_open,
self.on_packet,
)))
}
/// Build socket with a polling transport then upgrade to websocket transport
pub async fn build_websocket_with_upgrade(mut self) -> Result<Client> {
self.handshake().await?;
if self.websocket_upgrade()? {
self.build_websocket().await
} else {
Err(Error::IllegalWebsocketUpgrade())
}
}
/// Build socket with only a websocket transport
pub async fn build_websocket(mut self) -> Result<Client> {
let headers = if let Some(map) = self.headers.clone() {
Some(map.try_into()?)
} else {
None
};
match self.url.scheme() {
"http" | "ws" => {
let mut transport = WebsocketTransport::new(self.url.clone(), headers).await?;
if self.handshake.is_some() {
transport.upgrade().await?;
} else {
self.handshake_with_transport(&mut transport).await?;
}
// NOTE: Although self.url contains the sid, it does not propagate to the transport
// SAFETY: handshake function called previously.
Ok(Client::new(InnerSocket::new(
transport.into(),
self.handshake.unwrap(),
self.on_close,
self.on_data,
self.on_error,
self.on_open,
self.on_packet,
)))
}
"https" | "wss" => {
let mut transport = WebsocketSecureTransport::new(
self.url.clone(),
self.tls_config.clone(),
headers,
)
.await?;
if self.handshake.is_some() {
transport.upgrade().await?;
} else {
self.handshake_with_transport(&mut transport).await?;
}
// NOTE: Although self.url contains the sid, it does not propagate to the transport
// SAFETY: handshake function called previously.
Ok(Client::new(InnerSocket::new(
transport.into(),
self.handshake.unwrap(),
self.on_close,
self.on_data,
self.on_error,
self.on_open,
self.on_packet,
)))
}
_ => Err(Error::InvalidUrlScheme(self.url.scheme().to_string())),
}
}
/// Build websocket if allowed, if not allowed or errored fall back to polling.
/// WARNING: websocket errors suppressed, no indication of websocket success or failure.
pub async fn build_with_fallback(self) -> Result<Client> {
let result = self.clone().build().await;
if result.is_err() {
self.build_polling().await
} else {
result
}
}
/// Checks the handshake to see if websocket upgrades are allowed
fn websocket_upgrade(&mut self) -> Result<bool> {
if self.handshake.is_none() {
return Ok(false);
}
Ok(self
.handshake
.as_ref()
.unwrap()
.upgrades
.iter()
.any(|upgrade| upgrade.to_lowercase() == *"websocket"))
}
}
================================================
FILE: engineio/src/asynchronous/client/mod.rs
================================================
mod async_client;
mod builder;
pub use async_client::Client;
pub use builder::ClientBuilder;
================================================
FILE: engineio/src/asynchronous/generator.rs
================================================
use std::{pin::Pin, sync::Arc};
use crate::error::Result;
use futures_util::{ready, FutureExt, Stream, StreamExt};
use tokio::sync::Mutex;
/// A generator is an internal type that represents a [`Send`] [`futures_util::Stream`]
/// that yields a certain type `T` whenever it's polled.
pub(crate) type Generator<T> = Pin<Box<dyn Stream<Item = T> + 'static + Send>>;
/// An internal type that implements stream by repeatedly calling [`Stream::poll_next`] on an
/// underlying stream. Note that the generic parameter will be wrapped in a [`Result`].
#[derive(Clone)]
pub(crate) struct StreamGenerator<T> {
inner: Arc<Mutex<Generator<Result<T>>>>,
}
impl<T> Stream for StreamGenerator<T> {
type Item = Result<T>;
fn poll_next(
self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
let mut lock = ready!(Box::pin(self.inner.lock()).poll_unpin(cx));
lock.poll_next_unpin(cx)
}
}
impl<T> StreamGenerator<T> {
pub(crate) fn new(generator_stream: Generator<Result<T>>) -> Self {
StreamGenerator {
inner: Arc::new(Mutex::new(generator_stream)),
}
}
}
================================================
FILE: engineio/src/asynchronous/mod.rs
================================================
pub mod async_transports;
pub mod transport;
pub(self) mod async_socket;
#[cfg(feature = "async-callbacks")]
mod callback;
#[cfg(feature = "async")]
pub mod client;
mod generator;
#[cfg(feature = "async")]
pub use client::Client;
#[cfg(feature = "async")]
pub use client::ClientBuilder;
================================================
FILE: engineio/src/asynchronous/transport.rs
================================================
use crate::error::Result;
use adler32::adler32;
use async_trait::async_trait;
use bytes::Bytes;
use futures_util::Stream;
use std::{pin::Pin, time::SystemTime};
use url::Url;
use super::async_transports::{PollingTransport, WebsocketSecureTransport, WebsocketTransport};
#[async_trait]
pub trait AsyncTransport: Stream<Item = Result<Bytes>> + Unpin {
/// Sends a packet to the server. This optionally handles sending of a
/// socketio binary attachment via the boolean attribute `is_binary_att`.
async fn emit(&self, data: Bytes, is_binary_att: bool) -> Result<()>;
/// Returns start of the url. ex. http://localhost:2998/engine.io/?EIO=4&transport=polling
/// Must have EIO and transport already set.
async fn base_url(&self) -> Result<Url>;
/// Used to update the base path, like when adding the sid.
async fn set_base_url(&self, base_url: Url) -> Result<()>;
/// Full query address
async fn address(&self) -> Result<Url>
where
Self: Sized,
{
let reader = format!("{:#?}", SystemTime::now());
let hash = adler32(reader.as_bytes()).unwrap();
let mut url = self.base_url().await?;
url.query_pairs_mut().append_pair("t", &hash.to_string());
Ok(url)
}
}
#[derive(Debug, Clone)]
pub enum AsyncTransportType {
Polling(PollingTransport),
Websocket(WebsocketTransport),
WebsocketSecure(WebsocketSecureTransport),
}
impl From<PollingTransport> for AsyncTransportType {
fn from(transport: PollingTransport) -> Self {
AsyncTransportType::Polling(transport)
}
}
impl From<WebsocketTransport> for AsyncTransportType {
fn from(transport: WebsocketTransport) -> Self {
AsyncTransportType::Websocket(transport)
}
}
impl From<WebsocketSecureTransport> for AsyncTransportType {
fn from(transport: WebsocketSecureTransport) -> Self {
AsyncTransportType::WebsocketSecure(transport)
}
}
#[cfg(feature = "async")]
impl AsyncTransportType {
pub fn as_transport(&self) -> &(dyn AsyncTransport + Send) {
match self {
AsyncTransportType::Polling(transport) => transport,
AsyncTransportType::Websocket(transport) => transport,
AsyncTransportType::WebsocketSecure(transport) => transport,
}
}
pub fn as_pin_box(&mut self) -> Pin<Box<&mut (dyn AsyncTransport + Send)>> {
match self {
AsyncTransportType::Polling(transport) => Box::pin(transport),
AsyncTransportType::Websocket(transport) => Box::pin(transport),
AsyncTransportType::WebsocketSecure(transport) => Box::pin(transport),
}
}
}
================================================
FILE: engineio/src/callback.rs
================================================
use crate::Packet;
use bytes::Bytes;
use std::fmt::Debug;
use std::ops::Deref;
use std::sync::Arc;
pub(crate) type DynCallback<I> = dyn Fn(I) + 'static + Sync + Send;
#[derive(Clone)]
/// Internal type, only implements debug on fixed set of generics
pub(crate) struct OptionalCallback<I> {
inner: Arc<Option<Box<DynCallback<I>>>>,
}
impl<I> OptionalCallback<I> {
pub(crate) fn new<T>(callback: T) -> Self
where
T: Fn(I) + 'static + Sync + Send,
{
OptionalCallback {
inner: Arc::new(Some(Box::new(callback))),
}
}
pub(crate) fn default() -> Self {
OptionalCallback {
inner: Arc::new(None),
}
}
}
#[cfg_attr(tarpaulin, ignore)]
impl Debug for OptionalCallback<String> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.write_fmt(format_args!(
"Callback({:?})",
if self.inner.is_some() {
"Fn(String)"
} else {
"None"
}
))
}
}
#[cfg_attr(tarpaulin, ignore)]
impl Debug for OptionalCallback<()> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.write_fmt(format_args!(
"Callback({:?})",
if self.inner.is_some() {
"Fn(())"
} else {
"None"
}
))
}
}
#[cfg_attr(tarpaulin, ignore)]
impl Debug for OptionalCallback<Packet> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.write_fmt(format_args!(
"Callback({:?})",
if self.inner.is_some() {
"Fn(Packet)"
} else {
"None"
}
))
}
}
#[cfg_attr(tarpaulin, ignore)]
impl Debug for OptionalCallback<Bytes> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.write_fmt(format_args!(
"Callback({:?})",
if self.inner.is_some() {
"Fn(Bytes)"
} else {
"None"
}
))
}
}
impl<I> Deref for OptionalCallback<I> {
type Target = Option<Box<DynCallback<I>>>;
fn deref(&self) -> &<Self as std::ops::Deref>::Target {
self.inner.as_ref()
}
}
================================================
FILE: engineio/src/client/client.rs
================================================
use super::super::socket::Socket as InnerSocket;
use crate::callback::OptionalCallback;
use crate::socket::DEFAULT_MAX_POLL_TIMEOUT;
use crate::transport::Transport;
use crate::error::{Error, Result};
use crate::header::HeaderMap;
use crate::packet::{HandshakePacket, Packet, PacketId};
use crate::transports::{PollingTransport, WebsocketSecureTransport, WebsocketTransport};
use crate::ENGINE_IO_VERSION;
use bytes::Bytes;
use native_tls::TlsConnector;
use std::convert::TryFrom;
use std::convert::TryInto;
use std::fmt::Debug;
use url::Url;
/// An engine.io client that allows interaction with the connected engine.io
/// server. This client provides means for connecting, disconnecting and sending
/// packets to the server.
///
/// ## Note:
/// There is no need to put this Client behind an `Arc`, as the type uses `Arc`
/// internally and provides a shared state beyond all cloned instances.
#[derive(Clone, Debug)]
pub struct Client {
socket: InnerSocket,
}
#[derive(Clone, Debug)]
pub struct ClientBuilder {
url: Url,
tls_config: Option<TlsConnector>,
headers: Option<HeaderMap>,
handshake: Option<HandshakePacket>,
on_error: OptionalCallback<String>,
on_open: OptionalCallback<()>,
on_close: OptionalCallback<()>,
on_data: OptionalCallback<Bytes>,
on_packet: OptionalCallback<Packet>,
}
impl ClientBuilder {
pub fn new(url: Url) -> Self {
let mut url = url;
url.query_pairs_mut()
.append_pair("EIO", &ENGINE_IO_VERSION.to_string());
// No path add engine.io
if url.path() == "/" {
url.set_path("/engine.io/");
}
ClientBuilder {
url,
headers: None,
tls_config: None,
handshake: None,
on_close: OptionalCallback::default(),
on_data: OptionalCallback::default(),
on_error: OptionalCallback::default(),
on_open: OptionalCallback::default(),
on_packet: OptionalCallback::default(),
}
}
/// Specify transport's tls config
pub fn tls_config(mut self, tls_config: TlsConnector) -> Self {
self.tls_config = Some(tls_config);
self
}
/// Specify transport's HTTP headers
pub fn headers(mut self, headers: HeaderMap) -> Self {
self.headers = Some(headers);
self
}
/// Registers the `on_close` callback.
pub fn on_close<T>(mut self, callback: T) -> Self
where
T: Fn(()) + 'static + Sync + Send,
{
self.on_close = OptionalCallback::new(callback);
self
}
/// Registers the `on_data` callback.
pub fn on_data<T>(mut self, callback: T) -> Self
where
T: Fn(Bytes) + 'static + Sync + Send,
{
self.on_data = OptionalCallback::new(callback);
self
}
/// Registers the `on_error` callback.
pub fn on_error<T>(mut self, callback: T) -> Self
where
T: Fn(String) + 'static + Sync + Send,
{
self.on_error = OptionalCallback::new(callback);
self
}
/// Registers the `on_open` callback.
pub fn on_open<T>(mut self, callback: T) -> Self
where
T: Fn(()) + 'static + Sync + Send,
{
self.on_open = OptionalCallback::new(callback);
self
}
/// Registers the `on_packet` callback.
pub fn on_packet<T>(mut self, callback: T) -> Self
where
T: Fn(Packet) + 'static + Sync + Send,
{
self.on_packet = OptionalCallback::new(callback);
self
}
/// Performs the handshake
fn handshake_with_transport<T: Transport>(&mut self, transport: &T) -> Result<()> {
// No need to handshake twice
if self.handshake.is_some() {
return Ok(());
}
let mut url = self.url.clone();
let handshake: HandshakePacket =
Packet::try_from(transport.poll(DEFAULT_MAX_POLL_TIMEOUT)?)?.try_into()?;
// update the base_url with the new sid
url.query_pairs_mut().append_pair("sid", &handshake.sid[..]);
self.handshake = Some(handshake);
self.url = url;
Ok(())
}
fn handshake(&mut self) -> Result<()> {
if self.handshake.is_some() {
return Ok(());
}
// Start with polling transport
let transport = PollingTransport::new(
self.url.clone(),
self.tls_config.clone(),
self.headers.clone().map(|v| v.try_into().unwrap()),
);
self.handshake_with_transport(&transport)
}
/// Build websocket if allowed, if not fall back to polling
pub fn build(mut self) -> Result<Client> {
self.handshake()?;
if self.websocket_upgrade()? {
self.build_websocket_with_upgrade()
} else {
self.build_polling()
}
}
/// Build socket with polling transport
pub fn build_polling(mut self) -> Result<Client> {
self.handshake()?;
// Make a polling transport with new sid
let transport = PollingTransport::new(
self.url,
self.tls_config,
self.headers.map(|v| v.try_into().unwrap()),
);
// SAFETY: handshake function called previously.
Ok(Client {
socket: InnerSocket::new(
transport.into(),
self.handshake.unwrap(),
self.on_close,
self.on_data,
self.on_error,
self.on_open,
self.on_packet,
),
})
}
/// Build socket with a polling transport then upgrade to websocket transport
pub fn build_websocket_with_upgrade(mut self) -> Result<Client> {
self.handshake()?;
if self.websocket_upgrade()? {
self.build_websocket()
} else {
Err(Error::IllegalWebsocketUpgrade())
}
}
/// Build socket with only a websocket transport
pub fn build_websocket(mut self) -> Result<Client> {
// SAFETY: Already a Url
let url = url::Url::parse(self.url.as_ref())?;
let headers: Option<http::HeaderMap> = if let Some(map) = self.headers.clone() {
Some(map.try_into()?)
} else {
None
};
match url.scheme() {
"http" | "ws" => {
let transport = WebsocketTransport::new(url, headers)?;
if self.handshake.is_some() {
transport.upgrade()?;
} else {
self.handshake_with_transport(&transport)?;
}
// NOTE: Although self.url contains the sid, it does not propagate to the transport
// SAFETY: handshake function called previously.
Ok(Client {
socket: InnerSocket::new(
transport.into(),
self.handshake.unwrap(),
self.on_close,
self.on_data,
self.on_error,
self.on_open,
self.on_packet,
),
})
}
"https" | "wss" => {
let transport =
WebsocketSecureTransport::new(url, self.tls_config.clone(), headers)?;
if self.handshake.is_some() {
transport.upgrade()?;
} else {
self.handshake_with_transport(&transport)?;
}
// NOTE: Although self.url contains the sid, it does not propagate to the transport
// SAFETY: handshake function called previously.
Ok(Client {
socket: InnerSocket::new(
transport.into(),
self.handshake.unwrap(),
self.on_close,
self.on_data,
self.on_error,
self.on_open,
self.on_packet,
),
})
}
_ => Err(Error::InvalidUrlScheme(url.scheme().to_string())),
}
}
/// Build websocket if allowed, if not allowed or errored fall back to polling.
/// WARNING: websocket errors suppressed, no indication of websocket success or failure.
pub fn build_with_fallback(self) -> Result<Client> {
let result = self.clone().build();
if result.is_err() {
self.build_polling()
} else {
result
}
}
/// Checks the handshake to see if websocket upgrades are allowed
fn websocket_upgrade(&mut self) -> Result<bool> {
// SAFETY: handshake set by above function.
Ok(self
.handshake
.as_ref()
.unwrap()
.upgrades
.iter()
.any(|upgrade| upgrade.to_lowercase() == *"websocket"))
}
}
impl Client {
pub fn close(&self) -> Result<()> {
self.socket.disconnect()
}
/// Opens the connection to a specified server. The first Pong packet is sent
/// to the server to trigger the Ping-cycle.
pub fn connect(&self) -> Result<()> {
self.socket.connect()
}
/// Disconnects the connection.
pub fn disconnect(&self) -> Result<()> {
self.socket.disconnect()
}
/// Sends a packet to the server.
pub fn emit(&self, packet: Packet) -> Result<()> {
self.socket.emit(packet)
}
/// Polls for next payload
#[doc(hidden)]
pub fn poll(&self) -> Result<Option<Packet>> {
let packet = self.socket.poll()?;
if let Some(packet) = packet {
// check for the appropriate action or callback
self.socket.handle_packet(packet.clone());
match packet.packet_id {
PacketId::MessageBinary => {
self.socket.handle_data(packet.data.clone());
}
PacketId::Message => {
self.socket.handle_data(packet.data.clone());
}
PacketId::Close => {
self.socket.handle_close();
}
PacketId::Open => {
unreachable!("Won't happen as we open the connection beforehand");
}
PacketId::Upgrade => {
// this is already checked during the handshake, so just do nothing here
}
PacketId::Ping => {
self.socket.pinged()?;
self.emit(Packet::new(PacketId::Pong, Bytes::new()))?;
}
PacketId::Pong => {
// this will never happen as the pong packet is
// only sent by the client
unreachable!();
}
PacketId::Noop => (),
}
Ok(Some(packet))
} else {
Ok(None)
}
}
/// Check if the underlying transport client is connected.
pub fn is_connected(&self) -> Result<bool> {
self.socket.is_connected()
}
pub fn iter(&self) -> Iter {
Iter { socket: self }
}
}
#[derive(Clone)]
pub struct Iter<'a> {
socket: &'a Client,
}
impl<'a> Iterator for Iter<'a> {
type Item = Result<Packet>;
fn next(&mut self) -> std::option::Option<<Self as std::iter::Iterator>::Item> {
match self.socket.poll() {
Ok(Some(packet)) => Some(Ok(packet)),
Ok(None) => None,
Err(err) => Some(Err(err)),
}
}
}
#[cfg(test)]
mod test {
use crate::packet::PacketId;
use super::*;
/// The purpose of this test is to check whether the Client is properly cloneable or not.
/// As the documentation of the engine.io client states, the object needs to maintain it's internal
/// state when cloned and the cloned object should reflect the same state throughout the lifetime
/// of both objects (initial and cloned).
#[test]
fn test_client_cloneable() -> Result<()> {
let url = crate::test::engine_io_server()?;
let sut = builder(url).build()?;
let cloned = sut.clone();
sut.connect()?;
// when the underlying socket is connected, the
// state should also change on the cloned one
assert!(sut.is_connected()?);
assert!(cloned.is_connected()?);
// both clients should reflect the same messages.
let mut iter = sut
.iter()
.map(|packet| packet.unwrap())
.filter(|packet| packet.packet_id != PacketId::Ping);
let mut iter_cloned = cloned
.iter()
.map(|packet| packet.unwrap())
.filter(|packet| packet.packet_id != PacketId::Ping);
assert_eq!(
iter.next(),
Some(Packet::new(PacketId::Message, "hello client"))
);
sut.emit(Packet::new(PacketId::Message, "respond"))?;
assert_eq!(
iter_cloned.next(),
Some(Packet::new(PacketId::Message, "Roger Roger"))
);
cloned.disconnect()?;
// when the underlying socket is disconnected, the
// state should also change on the cloned one
assert!(!sut.is_connected()?);
assert!(!cloned.is_connected()?);
Ok(())
}
#[test]
fn test_illegal_actions() -> Result<()> {
let url = crate::test::engine_io_server()?;
let sut = builder(url.clone()).build()?;
assert!(sut
.emit(Packet::new(PacketId::Close, Bytes::new()))
.is_err());
sut.connect()?;
assert!(sut.poll().is_ok());
assert!(builder(Url::parse("fake://fake.fake").unwrap())
.build_websocket()
.is_err());
Ok(())
}
use reqwest::header::HOST;
use crate::packet::Packet;
fn builder(url: Url) -> ClientBuilder {
ClientBuilder::new(url)
.on_open(|_| {
println!("Open event!");
})
.on_packet(|packet| {
println!("Received packet: {:?}", packet);
})
.on_data(|data| {
println!("Received data: {:?}", std::str::from_utf8(&data));
})
.on_close(|_| {
println!("Close event!");
})
.on_error(|error| {
println!("Error {}", error);
})
}
fn test_connection(socket: Client) -> Result<()> {
let socket = socket;
socket.connect().unwrap();
// TODO: 0.3.X better tests
let mut iter = socket
.iter()
.map(|packet| packet.unwrap())
.filter(|packet| packet.packet_id != PacketId::Ping);
assert_eq!(
iter.next(),
Some(Packet::new(PacketId::Message, "hello client"))
);
socket.emit(Packet::new(PacketId::Message, "respond"))?;
assert_eq!(
iter.next(),
Some(Packet::new(PacketId::Message, "Roger Roger"))
);
socket.close()
}
#[test]
fn test_connection_long() -> Result<()> {
// Long lived socket to receive pings
let url = crate::test::engine_io_server()?;
let socket = builder(url).build()?;
socket.connect()?;
let mut iter = socket.iter();
// hello client
iter.next();
// Ping
iter.next();
socket.disconnect()?;
assert!(!socket.is_connected()?);
Ok(())
}
#[test]
fn test_connection_dynamic() -> Result<()> {
let url = crate::test::engine_io_server()?;
let socket = builder(url).build()?;
test_connection(socket)?;
let url = crate::test::engine_io_polling_server()?;
let socket = builder(url).build()?;
test_connection(socket)
}
#[test]
fn test_connection_fallback() -> Result<()> {
let url = crate::test::engine_io_server()?;
let socket = builder(url).build_with_fallback()?;
test_connection(socket)?;
let url = crate::test::engine_io_polling_server()?;
let socket = builder(url).build_with_fallback()?;
test_connection(socket)
}
#[test]
fn test_connection_dynamic_secure() -> Result<()> {
let url = crate::test::engine_io_server_secure()?;
let mut builder = builder(url);
builder = builder.tls_config(crate::test::tls_connector()?);
let socket = builder.build()?;
test_connection(socket)
}
#[test]
fn test_connection_polling() -> Result<()> {
let url = crate::test::engine_io_server()?;
let socket = builder(url).build_polling()?;
test_connection(socket)
}
#[test]
fn test_connection_wss() -> Result<()> {
let url = crate::test::engine_io_polling_server()?;
assert!(builder(url).build_websocket_with_upgrade().is_err());
let host =
std::env::var("ENGINE_IO_SECURE_HOST").unwrap_or_else(|_| "localhost".to_owned());
let mut url = crate::test::engine_io_server_secure()?;
let mut headers = HeaderMap::default();
headers.insert(HOST, host);
let mut builder = builder(url.clone());
builder = builder.tls_config(crate::test::tls_connector()?);
builder = builder.headers(headers.clone());
let socket = builder.clone().build_websocket_with_upgrade()?;
test_connection(socket)?;
let socket = builder.build_websocket()?;
test_connection(socket)?;
url.set_scheme("wss").unwrap();
let builder = self::builder(url)
.tls_config(crate::test::tls_connector()?)
.headers(headers);
let socket = builder.clone().build_websocket()?;
test_connection(socket)?;
assert!(builder.build_websocket_with_upgrade().is_err());
Ok(())
}
#[test]
fn test_connection_ws() -> Result<()> {
let url = crate::test::engine_io_polling_server()?;
assert!(builder(url.clone()).build_websocket().is_err());
assert!(builder(url).build_websocket_with_upgrade().is_err());
let mut url = crate::test::engine_io_server()?;
let builder = builder(url.clone());
let socket = builder.clone().build_websocket()?;
test_connection(socket)?;
let socket = builder.build_websocket_with_upgrade()?;
test_connection(socket)?;
url.set_scheme("ws").unwrap();
let builder = self::builder(url);
let socket = builder.clone().build_websocket()?;
test_connection(socket)?;
assert!(builder.build_websocket_with_upgrade().is_err());
Ok(())
}
#[test]
fn test_open_invariants() -> Result<()> {
let url = crate::test::engine_io_server()?;
let illegal_url = "this is illegal";
assert!(Url::parse(illegal_url).is_err());
let invalid_protocol = "file:///tmp/foo";
assert!(builder(Url::parse(invalid_protocol).unwrap())
.build()
.is_err());
let sut = builder(url.clone()).build()?;
let _error = sut
.emit(Packet::new(PacketId::Close, Bytes::new()))
.expect_err("error");
assert!(matches!(Error::IllegalActionBeforeOpen(), _error));
// test missing match arm in socket constructor
let mut headers = HeaderMap::default();
let host =
std::env::var("ENGINE_IO_SECURE_HOST").unwrap_or_else(|_| "localhost".to_owned());
headers.insert(HOST, host);
let _ = builder(url.clone())
.tls_config(
TlsConnector::builder()
.danger_accept_invalid_certs(true)
.build()
.unwrap(),
)
.build()?;
let _ = builder(url).headers(headers).build()?;
Ok(())
}
}
================================================
FILE: engineio/src/client/mod.rs
================================================
mod client;
pub use client::Iter;
pub use {client::Client, client::ClientBuilder, client::Iter as SocketIter};
================================================
FILE: engineio/src/error.rs
================================================
use base64::DecodeError;
use reqwest::Error as ReqwestError;
use serde_json::Error as JsonError;
use std::io::Error as IoError;
use std::str::Utf8Error;
use thiserror::Error;
use tungstenite::Error as TungsteniteError;
use url::ParseError as UrlParseError;
/// Enumeration of all possible errors in the `socket.io` context.
#[derive(Error, Debug)]
#[non_exhaustive]
#[cfg_attr(tarpaulin, ignore)]
pub enum Error {
// Conform to https://rust-lang.github.io/api-guidelines/naming.html#names-use-a-consistent-word-order-c-word-order
// Negative verb-object
#[error("Invalid packet id: {0}")]
InvalidPacketId(u8),
#[error("Error while parsing an incomplete packet")]
IncompletePacket(),
#[error("Got an invalid packet which did not follow the protocol format")]
InvalidPacket(),
#[error("An error occurred while decoding the utf-8 text: {0}")]
InvalidUtf8(#[from] Utf8Error),
#[error("An error occurred while encoding/decoding base64: {0}")]
InvalidBase64(#[from] DecodeError),
#[error("Invalid Url during parsing")]
InvalidUrl(#[from] UrlParseError),
#[error("Invalid Url Scheme: {0}")]
InvalidUrlScheme(String),
#[error("Error during connection via http: {0}")]
IncompleteResponseFromReqwest(#[from] ReqwestError),
#[error("Error with websocket connection: {0}")]
WebsocketError(#[from] TungsteniteError),
#[error("Network request returned with status code: {0}")]
IncompleteHttp(u16),
#[error("Got illegal handshake response: {0}")]
InvalidHandshake(String),
#[error("Called an action before the connection was established")]
IllegalActionBeforeOpen(),
#[error("Error setting up the http request: {0}")]
InvalidHttpConfiguration(#[from] http::Error),
#[error("string is not json serializable: {0}")]
InvalidJson(#[from] JsonError),
#[error("A lock was poisoned")]
InvalidPoisonedLock(),
#[error("Got an IO-Error: {0}")]
IncompleteIo(#[from] IoError),
#[error("Server did not allow upgrading to websockets")]
IllegalWebsocketUpgrade(),
#[error("Invalid header name")]
InvalidHeaderNameFromReqwest(#[from] reqwest::header::InvalidHeaderName),
#[error("Invalid header value")]
InvalidHeaderValueFromReqwest(#[from] reqwest::header::InvalidHeaderValue),
#[error("The server did not send a PING packet in time")]
PingTimeout(),
}
pub(crate) type Result<T> = std::result::Result<T, Error>;
impl<T> From<std::sync::PoisonError<T>> for Error {
fn from(_: std::sync::PoisonError<T>) -> Self {
Self::InvalidPoisonedLock()
}
}
impl From<Error> for std::io::Error {
fn from(err: Error) -> std::io::Error {
std::io::Error::new(std::io::ErrorKind::Other, err)
}
}
#[cfg(test)]
mod tests {
use std::sync::{Mutex, PoisonError};
use super::*;
/// This just tests the own implementations and relies on `thiserror` for the others.
#[test]
fn test_error_conversion() {
let mutex = Mutex::new(0);
let _error = Error::from(PoisonError::new(mutex.lock()));
assert!(matches!(Error::InvalidPoisonedLock(), _error));
let _io_error = std::io::Error::from(Error::IllegalWebsocketUpgrade());
let _error =
std::io::Error::new(std::io::ErrorKind::Other, Error::IllegalWebsocketUpgrade());
assert!(matches!(_io_error, _error));
}
}
================================================
FILE: engineio/src/header.rs
================================================
use crate::Error;
use bytes::Bytes;
use http::{
header::HeaderName as HttpHeaderName, HeaderMap as HttpHeaderMap,
HeaderValue as HttpHeaderValue,
};
use std::collections::HashMap;
use std::convert::TryFrom;
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::str::FromStr;
#[derive(Eq, PartialEq, Hash, Debug, Clone)]
pub struct HeaderName {
inner: Box<str>,
}
#[derive(Eq, PartialEq, Hash, Debug, Clone)]
pub struct HeaderValue {
inner: Bytes,
}
#[derive(Eq, PartialEq, Debug, Clone, Default)]
pub struct HeaderMap {
map: HashMap<HeaderName, HeaderValue>,
}
pub struct IntoIter {
inner: std::collections::hash_map::IntoIter<HeaderName, HeaderValue>,
}
impl Display for HeaderName {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
f.write_str(self.inner.as_ref())
}
}
impl From<String> for HeaderName {
fn from(string: String) -> Self {
HeaderName {
inner: string.into_boxed_str(),
}
}
}
impl TryFrom<HeaderName> for HttpHeaderName {
type Error = Error;
fn try_from(
header: HeaderName,
) -> std::result::Result<Self, <Self as std::convert::TryFrom<HeaderName>>::Error> {
Ok(HttpHeaderName::from_str(header.inner.as_ref())?)
}
}
impl From<HttpHeaderName> for HeaderName {
fn from(header: HttpHeaderName) -> Self {
HeaderName::from(header.to_string())
}
}
impl From<String> for HeaderValue {
fn from(string: String) -> Self {
HeaderValue {
inner: Bytes::from(string),
}
}
}
impl TryFrom<HeaderValue> for HttpHeaderValue {
type Error = Error;
fn try_from(
value: HeaderValue,
) -> std::result::Result<Self, <Self as std::convert::TryFrom<HeaderValue>>::Error> {
Ok(HttpHeaderValue::from_bytes(&value.inner[..])?)
}
}
impl From<HttpHeaderValue> for HeaderValue {
fn from(value: HttpHeaderValue) -> Self {
HeaderValue {
inner: Bytes::copy_from_slice(value.as_bytes()),
}
}
}
impl From<&str> for HeaderValue {
fn from(string: &str) -> Self {
Self::from(string.to_owned())
}
}
impl TryFrom<HeaderMap> for HttpHeaderMap {
type Error = Error;
fn try_from(
headers: HeaderMap,
) -> std::result::Result<Self, <Self as std::convert::TryFrom<HeaderMap>>::Error> {
headers
.into_iter()
.map(|(key, value)| {
Ok((
HttpHeaderName::try_from(key)?,
HttpHeaderValue::try_from(value)?,
))
})
.collect()
}
}
impl IntoIterator for HeaderMap {
type Item = (HeaderName, HeaderValue);
type IntoIter = IntoIter;
fn into_iter(self) -> <Self as std::iter::IntoIterator>::IntoIter {
IntoIter {
inner: self.map.into_iter(),
}
}
}
impl HeaderMap {
pub fn new() -> Self {
HeaderMap {
map: HashMap::new(),
}
}
pub fn insert<T: Into<HeaderName>, U: Into<HeaderValue>>(
&mut self,
key: T,
value: U,
) -> Option<HeaderValue> {
self.map.insert(key.into(), value.into())
}
}
impl Iterator for IntoIter {
type Item = (HeaderName, HeaderValue);
fn next(&mut self) -> std::option::Option<<Self as std::iter::Iterator>::Item> {
self.inner.next()
}
}
================================================
FILE: engineio/src/lib.rs
================================================
//! # Rust-engineio-client
//!
//! An implementation of a engine.io client written in the rust programming language. This implementation currently
//! supports revision 4 of the engine.io protocol. If you have any connection issues with this client,
//! make sure the server uses at least revision 4 of the engine.io protocol.
//!
//! ## Example usage
//!
//! ``` rust
//! use rust_engineio::{ClientBuilder, Client, packet::{Packet, PacketId}};
//! use url::Url;
//! use bytes::Bytes;
//!
//! // get a client with an `on_open` callback
//! let client: Client = ClientBuilder::new(Url::parse("http://localhost:4201").unwrap())
//! .on_open(|_| println!("Connection opened!"))
//! .build()
//! .expect("Creating client failed");
//!
//! // connect to the server
//! client.connect().expect("Connection failed");
//!
//! // create a packet, in this case a message packet and emit it
//! let packet = Packet::new(PacketId::Message, Bytes::from_static(b"Hello World"));
//! client.emit(packet).expect("Server unreachable");
//!
//! // disconnect from the server
//! client.disconnect().expect("Disconnect failed")
//! ```
//!
//! The main entry point for using this crate is the [`ClientBuilder`] (or [`asynchronous::ClientBuilder`] respectively)
//! which provides the opportunity to define how you want to connect to a certain endpoint.
//! The following connection methods are available:
//! * `build`: Build websocket if allowed, if not fall back to polling. Standard configuration.
//! * `build_polling`: enforces a `polling` transport.
//! * `build_websocket_with_upgrade`: Build socket with a polling transport then upgrade to websocket transport (if possible).
//! * `build_websocket`: Build socket with only a websocket transport, crashes when websockets are not allowed.
//!
//!
//! ## Current features
//!
//! This implementation now supports all of the features of the engine.io protocol mentioned [here](https://github.com/socketio/engine.io-protocol).
//! This includes various transport options, the possibility of sending engine.io packets and registering the
//! common engine.io event callbacks:
//! * on_open
//! * on_close
//! * on_data
//! * on_error
//! * on_packet
//!
//! It is also possible to pass in custom tls configurations via the `TlsConnector` as well
//! as custom headers for the opening request.
//!
//! ## Async version
//!
//! The crate also ships with an asynchronous version that can be enabled with a feature flag.
//! The async version implements the same features mentioned above.
//! The asynchronous version has a similar API, just with async functions. Currently the futures
//! can only be executed with [`tokio`](https://tokio.rs). In the first benchmarks the async version
//! showed improvements of up to 93% in speed.
//! To make use of the async version, import the crate as follows:
//! ```toml
//! [depencencies]
//! rust-engineio = { version = "0.3.1", features = ["async"] }
//! ```
//!
#![allow(clippy::rc_buffer)]
#![warn(clippy::complexity)]
#![warn(clippy::style)]
#![warn(clippy::perf)]
#![warn(clippy::correctness)]
/// A small macro that spawns a scoped thread. Used for calling the callback
/// functions.
macro_rules! spawn_scoped {
($e:expr) => {
std::thread::scope(|s| {
s.spawn(|| $e);
});
};
}
pub mod asynchronous;
mod callback;
pub mod client;
/// Generic header map
pub mod header;
pub mod packet;
pub(self) mod socket;
pub mod transport;
pub mod transports;
pub const ENGINE_IO_VERSION: i32 = 4;
/// Contains the error type which will be returned with every result in this
/// crate. Handles all kinds of errors.
pub mod error;
pub use client::{Client, ClientBuilder};
pub use error::Error;
pub use packet::{Packet, PacketId};
#[cfg(test)]
pub(crate) mod test {
use super::*;
use native_tls::TlsConnector;
const CERT_PATH: &str = "../ci/cert/ca.crt";
use native_tls::Certificate;
use std::fs::File;
use std::io::Read;
pub(crate) fn tls_connector() -> error::Result<TlsConnector> {
let cert_path = std::env::var("CA_CERT_PATH").unwrap_or_else(|_| CERT_PATH.to_owned());
let mut cert_file = File::open(cert_path)?;
let mut buf = vec![];
cert_file.read_to_end(&mut buf)?;
let cert: Certificate = Certificate::from_pem(&buf[..]).unwrap();
Ok(TlsConnector::builder()
// ONLY USE FOR TESTING!
.danger_accept_invalid_hostnames(true)
.add_root_certificate(cert)
.build()
.unwrap())
}
/// The `engine.io` server for testing runs on port 4201
const SERVER_URL: &str = "http://localhost:4201";
/// The `engine.io` server that refuses upgrades runs on port 4203
const SERVER_POLLING_URL: &str = "http://localhost:4203";
const SERVER_URL_SECURE: &str = "https://localhost:4202";
use url::Url;
pub(crate) fn engine_io_server() -> crate::error::Result<Url> {
let url = std::env::var("ENGINE_IO_SERVER").unwrap_or_else(|_| SERVER_URL.to_owned());
Ok(Url::parse(&url)?)
}
pub(crate) fn engine_io_polling_server() -> crate::error::Result<Url> {
let url = std::env::var("ENGINE_IO_POLLING_SERVER")
.unwrap_or_else(|_| SERVER_POLLING_URL.to_owned());
Ok(Url::parse(&url)?)
}
pub(crate) fn engine_io_server_secure() -> crate::error::Result<Url> {
let url = std::env::var("ENGINE_IO_SECURE_SERVER")
.unwrap_or_else(|_| SERVER_URL_SECURE.to_owned());
Ok(Url::parse(&url)?)
}
}
================================================
FILE: engineio/src/packet.rs
================================================
use base64::{engine::general_purpose, Engine as _};
use bytes::{BufMut, Bytes, BytesMut};
use serde::{Deserialize, Serialize};
use std::char;
use std::convert::TryFrom;
use std::convert::TryInto;
use std::fmt::{Display, Formatter, Result as FmtResult, Write};
use std::ops::Index;
use crate::error::{Error, Result};
/// Enumeration of the `engine.io` `Packet` types.
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum PacketId {
Open,
Close,
Ping,
Pong,
Message,
// A type of message that is base64 encoded
MessageBinary,
Upgrade,
Noop,
}
impl PacketId {
/// Returns the byte that represents the [`PacketId`] as a [`char`].
fn to_string_byte(self) -> u8 {
match self {
Self::MessageBinary => b'b',
_ => u8::from(self) + b'0',
}
}
}
impl Display for PacketId {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
f.write_char(self.to_string_byte() as char)
}
}
impl From<PacketId> for u8 {
fn from(packet_id: PacketId) -> Self {
match packet_id {
PacketId::Open => 0,
PacketId::Close => 1,
PacketId::Ping => 2,
PacketId::Pong => 3,
PacketId::Message => 4,
PacketId::MessageBinary => 4,
PacketId::Upgrade => 5,
PacketId::Noop => 6,
}
}
}
impl TryFrom<u8> for PacketId {
type Error = Error;
/// Converts a byte into the corresponding `PacketId`.
fn try_from(b: u8) -> Result<PacketId> {
match b {
0 | b'0' => Ok(PacketId::Open),
1 | b'1' => Ok(PacketId::Close),
2 | b'2' => Ok(PacketId::Ping),
3 | b'3' => Ok(PacketId::Pong),
4 | b'4' => Ok(PacketId::Message),
5 | b'5' => Ok(PacketId::Upgrade),
6 | b'6' => Ok(PacketId::Noop),
_ => Err(Error::InvalidPacketId(b)),
}
}
}
/// A `Packet` sent via the `engine.io` protocol.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Packet {
pub packet_id: PacketId,
pub data: Bytes,
}
/// Data which gets exchanged in a handshake as defined by the server.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct HandshakePacket {
pub sid: String,
pub upgrades: Vec<String>,
#[serde(rename = "pingInterval")]
pub ping_interval: u64,
#[serde(rename = "pingTimeout")]
pub ping_timeout: u64,
}
impl TryFrom<Packet> for HandshakePacket {
type Error = Error;
fn try_from(packet: Packet) -> Result<HandshakePacket> {
Ok(serde_json::from_slice(packet.data[..].as_ref())?)
}
}
impl Packet {
/// Creates a new `Packet`.
pub fn new<T: Into<Bytes>>(packet_id: PacketId, data: T) -> Self {
Packet {
packet_id,
data: data.into(),
}
}
}
impl TryFrom<Bytes> for Packet {
type Error = Error;
/// Decodes a single `Packet` from an `u8` byte stream.
fn try_from(
bytes: Bytes,
) -> std::result::Result<Self, <Self as std::convert::TryFrom<Bytes>>::Error> {
if bytes.is_empty() {
return Err(Error::IncompletePacket());
}
let is_base64 = *bytes.first().ok_or(Error::IncompletePacket())? == b'b';
// only 'messages' packets could be encoded
let packet_id = if is_base64 {
PacketId::MessageBinary
} else {
(*bytes.first().ok_or(Error::IncompletePacket())?).try_into()?
};
if bytes.len() == 1 && packet_id == PacketId::Message {
return Err(Error::IncompletePacket());
}
let data: Bytes = bytes.slice(1..);
Ok(Packet {
packet_id,
data: if is_base64 {
Bytes::from(general_purpose::STANDARD.decode(data.as_ref())?)
} else {
data
},
})
}
}
impl From<Packet> for Bytes {
/// Encodes a `Packet` into an `u8` byte stream.
fn from(packet: Packet) -> Self {
let mut result = BytesMut::with_capacity(packet.data.len() + 1);
result.put_u8(packet.packet_id.to_string_byte());
if packet.packet_id == PacketId::MessageBinary {
result.extend(general_purpose::STANDARD.encode(packet.data).into_bytes());
} else {
result.put(packet.data);
}
result.freeze()
}
}
#[derive(Debug, Clone)]
pub(crate) struct Payload(Vec<Packet>);
impl Payload {
// see https://en.wikipedia.org/wiki/Delimiter#ASCII_delimited_text
const SEPARATOR: char = '\x1e';
#[cfg(test)]
pub fn len(&self) -> usize {
self.0.len()
}
}
impl TryFrom<Bytes> for Payload {
type Error = Error;
/// Decodes a `payload` which in the `engine.io` context means a chain of normal
/// packets separated by a certain SEPARATOR, in this case the delimiter `\x30`.
fn try_from(payload: Bytes) -> Result<Self> {
payload
.split(|&c| c as char == Self::SEPARATOR)
.map(|slice| Packet::try_from(payload.slice_ref(slice)))
.collect::<Result<Vec<_>>>()
.map(Self)
}
}
impl TryFrom<Payload> for Bytes {
type Error = Error;
/// Encodes a payload. Payload in the `engine.io` context means a chain of
/// normal `packets` separated by a SEPARATOR, in this case the delimiter
/// `\x30`.
fn try_from(packets: Payload) -> Result<Self> {
let mut buf = BytesMut::new();
for packet in packets {
// at the moment no base64 encoding is used
buf.extend(Bytes::from(packet.clone()));
buf.put_u8(Payload::SEPARATOR as u8);
}
// remove the last separator
let _ = buf.split_off(buf.len() - 1);
Ok(buf.freeze())
}
}
#[derive(Clone, Debug)]
pub struct IntoIter {
iter: std::vec::IntoIter<Packet>,
}
impl Iterator for IntoIter {
type Item = Packet;
fn next(&mut self) -> std::option::Option<<Self as std::iter::Iterator>::Item> {
self.iter.next()
}
}
impl IntoIterator for Payload {
type Item = Packet;
type IntoIter = IntoIter;
fn into_iter(self) -> <Self as std::iter::IntoIterator>::IntoIter {
IntoIter {
iter: self.0.into_iter(),
}
}
}
impl Index<usize> for Payload {
type Output = Packet;
fn index(&self, index: usize) -> &Packet {
&self.0[index]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_packet_error() {
let err = Packet::try_from(BytesMut::with_capacity(10).freeze());
assert!(err.is_err())
}
#[test]
fn test_is_reflexive() {
let data = Bytes::from_static(b"1Hello World");
let packet = Packet::try_from(data).unwrap();
assert_eq!(packet.packet_id, PacketId::Close);
assert_eq!(packet.data, Bytes::from_static(b"Hello World"));
let data = Bytes::from_static(b"1Hello World");
assert_eq!(Bytes::from(packet), data);
}
#[test]
fn test_binary_packet() {
// SGVsbG8= is the encoded string for 'Hello'
let data = Bytes::from_static(b"bSGVsbG8=");
let packet = Packet::try_from(data.clone()).unwrap();
assert_eq!(packet.packet_id, PacketId::MessageBinary);
assert_eq!(packet.data, Bytes::from_static(b"Hello"));
assert_eq!(Bytes::from(packet), data);
}
#[test]
fn test_decode_payload() -> Result<()> {
let data = Bytes::from_static(b"1Hello\x1e1HelloWorld");
let packets = Payload::try_from(data)?;
assert_eq!(packets[0].packet_id, PacketId::Close);
assert_eq!(packets[0].data, Bytes::from_static(b"Hello"));
assert_eq!(packets[1].packet_id, PacketId::Close);
assert_eq!(packets[1].data, Bytes::from_static(b"HelloWorld"));
let data = "1Hello\x1e1HelloWorld".to_owned().into_bytes();
assert_eq!(Bytes::try_from(packets).unwrap(), data);
Ok(())
}
#[test]
fn test_binary_payload() {
let data = Bytes::from_static(b"bSGVsbG8=\x1ebSGVsbG9Xb3JsZA==\x1ebSGVsbG8=");
let packets = Payload::try_from(data.clone()).unwrap();
assert!(packets.len() == 3);
assert_eq!(packets[0].packet_id, PacketId::MessageBinary);
assert_eq!(packets[0].data, Bytes::from_static(b"Hello"));
assert_eq!(packets[1].packet_id, PacketId::MessageBinary);
assert_eq!(packets[1].data, Bytes::from_static(b"HelloWorld"));
assert_eq!(packets[2].packet_id, PacketId::MessageBinary);
assert_eq!(packets[2].data, Bytes::from_static(b"Hello"));
assert_eq!(Bytes::try_from(packets).unwrap(), data);
}
#[test]
fn test_packet_id_conversion_and_incompl_packet() -> Result<()> {
let sut = Packet::try_from(Bytes::from_static(b"4"));
assert!(sut.is_err());
let _sut = sut.unwrap_err();
assert!(matches!(Error::IncompletePacket, _sut));
assert_eq!(PacketId::MessageBinary.to_string(), "b");
let sut = PacketId::try_from(b'0')?;
assert_eq!(sut, PacketId::Open);
assert_eq!(sut.to_string(), "0");
let sut = PacketId::try_from(b'1')?;
assert_eq!(sut, PacketId::Close);
assert_eq!(sut.to_string(), "1");
let sut = PacketId::try_from(b'2')?;
assert_eq!(sut, PacketId::Ping);
assert_eq!(sut.to_string(), "2");
let sut = PacketId::try_from(b'3')?;
assert_eq!(sut, PacketId::Pong);
assert_eq!(sut.to_string(), "3");
let sut = PacketId::try_from(b'4')?;
assert_eq!(sut, PacketId::Message);
assert_eq!(sut.to_string(), "4");
let sut = PacketId::try_from(b'5')?;
assert_eq!(sut, PacketId::Upgrade);
assert_eq!(sut.to_string(), "5");
let sut = PacketId::try_from(b'6')?;
assert_eq!(sut, PacketId::Noop);
assert_eq!(sut.to_string(), "6");
let sut = PacketId::try_from(42);
assert!(sut.is_err());
assert!(matches!(sut.unwrap_err(), Error::InvalidPacketId(42)));
Ok(())
}
#[test]
fn test_handshake_packet() {
assert!(
HandshakePacket::try_from(Packet::new(PacketId::Message, Bytes::from("test"))).is_err()
);
let packet = HandshakePacket {
ping_interval: 10000,
ping_timeout: 1000,
sid: "Test".to_owned(),
upgrades: vec!["websocket".to_owned(), "test".to_owned()],
};
let encoded: String = serde_json::to_string(&packet).unwrap();
assert_eq!(
packet,
HandshakePacket::try_from(Packet::new(PacketId::Message, Bytes::from(encoded)))
.unwrap()
);
}
}
================================================
FILE: engineio/src/socket.rs
================================================
use crate::callback::OptionalCallback;
use crate::transport::TransportType;
use crate::error::{Error, Result};
use crate::packet::{HandshakePacket, Packet, PacketId, Payload};
use bytes::Bytes;
use std::convert::TryFrom;
use std::sync::RwLock;
use std::time::Duration;
use std::{fmt::Debug, sync::atomic::Ordering};
use std::{
sync::{atomic::AtomicBool, Arc, Mutex},
time::Instant,
};
/// The default maximum ping timeout as calculated from the pingInterval and pingTimeout.
/// See https://socket.io/docs/v4/server-options/#pinginterval and
/// https://socket.io/docs/v4/server-options/#pingtimeout
pub const DEFAULT_MAX_POLL_TIMEOUT: Duration = Duration::from_secs(45);
/// An `engine.io` socket which manages a connection with the server and allows
/// it to register common callbacks.
#[derive(Clone)]
pub struct Socket {
transport: Arc<TransportType>,
on_close: OptionalCallback<()>,
on_data: OptionalCallback<Bytes>,
on_error: OptionalCallback<String>,
on_open: OptionalCallback<()>,
on_packet: OptionalCallback<Packet>,
connected: Arc<AtomicBool>,
last_ping: Arc<Mutex<Instant>>,
last_pong: Arc<Mutex<Instant>>,
connection_data: Arc<HandshakePacket>,
/// Since we get packets in payloads it's possible to have a state where only some of the packets have been consumed.
remaining_packets: Arc<RwLock<Option<crate::packet::IntoIter>>>,
max_ping_timeout: u64,
}
impl Socket {
pub(crate) fn new(
transport: TransportType,
handshake: HandshakePacket,
on_close: OptionalCallback<()>,
on_data: OptionalCallback<Bytes>,
on_error: OptionalCallback<String>,
on_open: OptionalCallback<()>,
on_packet: OptionalCallback<Packet>,
) -> Self {
let max_ping_timeout = handshake.ping_interval + handshake.ping_timeout;
Socket {
on_close,
on_data,
on_error,
on_open,
on_packet,
transport: Arc::new(transport),
connected: Arc::new(AtomicBool::default()),
last_ping: Arc::new(Mutex::new(Instant::now())),
last_pong: Arc::new(Mutex::new(Instant::now())),
connection_data: Arc::new(handshake),
remaining_packets: Arc::new(RwLock::new(None)),
max_ping_timeout,
}
}
/// Opens the connection to a specified server. The first Pong packet is sent
/// to the server to trigger the Ping-cycle.
pub fn connect(&self) -> Result<()> {
// SAFETY: Has valid handshake due to type
self.connected.store(true, Ordering::Release);
if let Some(on_open) = self.on_open.as_ref() {
spawn_scoped!(on_open(()));
}
// set the last ping to now and set the connected state
*self.last_ping.lock()? = Instant::now();
// emit a pong packet to keep trigger the ping cycle on the server
self.emit(Packet::new(PacketId::Pong, Bytes::new()))?;
Ok(())
}
pub fn disconnect(&self) -> Result<()> {
if let Some(on_close) = self.on_close.as_ref() {
spawn_scoped!(on_close(()));
}
// will not succeed when connection to the server is interrupted
let _ = self.emit(Packet::new(PacketId::Close, Bytes::new()));
self.connected.store(false, Ordering::Release);
Ok(())
}
/// Sends a packet to the server.
pub fn emit(&self, packet: Packet) -> Result<()> {
if !self.connected.load(Ordering::Acquire) {
let error = Error::IllegalActionBeforeOpen();
self.call_error_callback(format!("{}", error));
return Err(error);
}
let is_binary = packet.packet_id == PacketId::MessageBinary;
// send a post request with the encoded payload as body
// if this is a binary attachment, then send the raw bytes
let data: Bytes = if is_binary {
packet.data
} else {
packet.into()
};
if let Err(error) = self.transport.as_transport().emit(data, is_binary) {
self.call_error_callback(error.to_string());
return Err(error);
}
Ok(())
}
/// Polls for next payload
pub(crate) fn poll(&self) -> Result<Option<Packet>> {
loop {
if self.connected.load(Ordering::Acquire) {
if self.remaining_packets.read()?.is_some() {
// SAFETY: checked is some above
let mut iter = self.remaining_packets.write()?;
let iter = iter.as_mut().unwrap();
if let Some(packet) = iter.next() {
return Ok(Some(packet));
}
}
// Iterator has run out of packets, get a new payload.
// Make sure that payload is received within time_to_next_ping, as otherwise the heart
// stopped beating and we disconnect.
let data = self
.transport
.as_transport()
.poll(Duration::from_millis(self.time_to_next_ping()?))?;
if data.is_empty() {
continue;
}
let payload = Payload::try_from(data)?;
let mut iter = payload.into_iter();
if let Some(packet) = iter.next() {
*self.remaining_packets.write()? = Some(iter);
return Ok(Some(packet));
}
} else {
return Ok(None);
}
}
}
/// Calls the error callback with a given message.
#[inline]
fn call_error_callback(&self, text: String) {
if let Some(function) = self.on_error.as_ref() {
spawn_scoped!(function(text));
}
}
// Check if the underlying transport client is connected.
pub(crate) fn is_connected(&self) -> Result<bool> {
Ok(self.connected.load(Ordering::Acquire))
}
pub(crate) fn pinged(&self) -> Result<()> {
*self.last_ping.lock()? = Instant::now();
Ok(())
}
/// Returns the time in milliseconds that is left until a new ping must be received.
/// This is used to detect whether we have been disconnected from the server.
/// See https://socket.io/docs/v4/how-it-works/#disconnection-detection
fn time_to_next_ping(&self) -> Result<u64> {
match Instant::now().checked_duration_since(*self.last_ping.lock()?) {
Some(since_last_ping) => {
let since_last_ping = since_last_ping.as_millis() as u64;
if since_last_ping > self.max_ping_timeout {
Ok(0)
} else {
Ok(self.max_ping_timeout - since_last_ping)
}
}
None => Ok(0),
}
}
pub(crate) fn handle_packet(&self, packet: Packet) {
if let Some(on_packet) = self.on_packet.as_ref() {
spawn_scoped!(on_packet(packet));
}
}
pub(crate) fn handle_data(&self, data: Bytes) {
if let Some(on_data) = self.on_data.as_ref() {
spawn_scoped!(on_data(data));
}
}
pub(crate) fn handle_close(&self) {
if let Some(on_close) = self.on_close.as_ref() {
spawn_scoped!(on_close(()));
}
self.connected.store(false, Ordering::Release);
}
}
#[cfg_attr(tarpaulin, ignore)]
impl Debug for Socket {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_fmt(format_args!(
"EngineSocket(transport: {:?}, on_error: {:?}, on_open: {:?}, on_close: {:?}, on_packet: {:?}, on_data: {:?}, connected: {:?}, last_ping: {:?}, last_pong: {:?}, connection_data: {:?})",
self.transport,
self.on_error,
self.on_open,
self.on_close,
self.on_packet,
self.on_data,
self.connected,
self.last_ping,
self.last_pong,
self.connection_data,
))
}
}
================================================
FILE: engineio/src/transport.rs
================================================
use super::transports::{PollingTransport, WebsocketSecureTransport, WebsocketTransport};
use crate::error::Result;
use adler32::adler32;
use bytes::Bytes;
use std::time::{Duration, SystemTime};
use url::Url;
pub trait Transport {
/// Sends a packet to the server. This optionally handles sending of a
/// socketio binary attachment via the boolean attribute `is_binary_att`.
fn emit(&self, data: Bytes, is_binary_att: bool) -> Result<()>;
/// Performs the server long polling procedure as long as the client is
/// connected. This should run separately at all time to ensure proper
/// response handling from the server.
fn poll(&self, timeout: Duration) -> Result<Bytes>;
/// Returns start of the url. ex. http://localhost:2998/engine.io/?EIO=4&transport=polling
/// Must have EIO and transport already set.
fn base_url(&self) -> Result<Url>;
/// Used to update the base path, like when adding the sid.
fn set_base_url(&self, base_url: Url) -> Result<()>;
/// Full query address
fn address(&self) -> Result<Url> {
let reader = format!("{:#?}", SystemTime::now());
let hash = adler32(reader.as_bytes()).unwrap();
let mut url = self.base_url()?;
url.query_pairs_mut().append_pair("t", &hash.to_string());
Ok(url)
}
}
#[derive(Debug)]
pub enum TransportType {
Polling(PollingTransport),
WebsocketSecure(WebsocketSecureTransport),
Websocket(WebsocketTransport),
}
impl From<PollingTransport> for TransportType {
fn from(transport: PollingTransport) -> Self {
TransportType::Polling(transport)
}
}
impl From<WebsocketSecureTransport> for TransportType {
fn from(transport: WebsocketSecureTransport) -> Self {
TransportType::WebsocketSecure(transport)
}
}
impl From<WebsocketTransport> for TransportType {
fn from(transport: WebsocketTransport) -> Self {
TransportType::Websocket(transport)
}
}
impl TransportType {
pub fn as_transport(&self) -> &dyn Transport {
match self {
TransportType::Polling(transport) => transport,
TransportType::Websocket(transport) => transport,
TransportType::WebsocketSecure(transport) => transport,
}
}
}
impl std::fmt::Debug for dyn Transport {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_fmt(format_args!("Transport(base_url: {:?})", self.base_url(),))
}
}
================================================
FILE: engineio/src/transports/mod.rs
================================================
mod polling;
mod websocket;
mod websocket_secure;
pub use self::polling::PollingTransport;
pub use self::websocket::WebsocketTransport;
pub use self::websocket_secure::WebsocketSecureTransport;
================================================
FILE: engineio/src/transports/polling.rs
================================================
use crate::error::{Error, Result};
use crate::transport::Transport;
use base64::{engine::general_purpose, Engine as _};
use bytes::{BufMut, Bytes, BytesMut};
use native_tls::TlsConnector;
use reqwest::{
blocking::{Client, ClientBuilder},
header::HeaderMap,
};
use std::sync::{Arc, RwLock};
use std::time::Duration;
use url::Url;
#[derive(Debug, Clone)]
pub struct PollingTransport {
client: Arc<Client>,
base_url: Arc<RwLock<Url>>,
}
impl PollingTransport {
/// Creates an instance of `PollingTransport`.
pub fn new(
base_url: Url,
tls_config: Option<TlsConnector>,
opening_headers: Option<HeaderMap>,
) -> Self {
let client = match (tls_config, opening_headers) {
(Some(config), Some(map)) => ClientBuilder::new()
.use_preconfigured_tls(config)
.default_headers(map)
.build()
.unwrap(),
(Some(config), None) => ClientBuilder::new()
.use_preconfigured_tls(config)
.build()
.unwrap(),
(None, Some(map)) => ClientBuilder::new().default_headers(map).build().unwrap(),
(None, None) => Client::new(),
};
let mut url = base_url;
url.query_pairs_mut().append_pair("transport", "polling");
PollingTransport {
client: Arc::new(client),
base_url: Arc::new(RwLock::new(url)),
}
}
}
impl Transport for PollingTransport {
fn emit(&self, data: Bytes, is_binary_att: bool) -> Result<()> {
let data_to_send = if is_binary_att {
// the binary attachment gets `base64` encoded
let mut packet_bytes = BytesMut::with_capacity(data.len() + 1);
packet_bytes.put_u8(b'b');
let encoded_data = general_purpose::STANDARD.encode(data);
packet_bytes.put(encoded_data.as_bytes());
packet_bytes.freeze()
} else {
data
};
let status = self
.client
.post(self.address()?)
.body(data_to_send)
.send()?
.status()
.as_u16();
if status != 200 {
let error = Error::IncompleteHttp(status);
return Err(error);
}
Ok(())
}
fn poll(&self, timeout: Duration) -> Result<Bytes> {
Ok(self
.client
.get(self.address()?)
.timeout(timeout)
.send()?
.bytes()?)
}
fn base_url(&self) -> Result<Url> {
Ok(self.base_url.read()?.clone())
}
fn set_base_url(&self, base_url: Url) -> Result<()> {
let mut url = base_url;
if !url
.query_pairs()
.any(|(k, v)| k == "transport" && v == "polling")
{
url.query_pairs_mut().append_pair("transport", "polling");
}
*self.base_url.write()? = url;
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
use std::str::FromStr;
#[test]
fn polling_transport_base_url() -> Result<()> {
let url = crate::test::engine_io_server()?.to_string();
let transport = PollingTransport::new(Url::from_str(&url[..]).unwrap(), None, None);
assert_eq!(
transport.base_url()?.to_string(),
url.clone() + "?transport=polling"
);
transport.set_base_url(Url::parse("https://127.0.0.1")?)?;
assert_eq!(
transport.base_url()?.to_string(),
"https://127.0.0.1/?transport=polling"
);
assert_ne!(transport.base_url()?.to_string(), url);
transport.set_base_url(Url::parse("http://127.0.0.1/?transport=polling")?)?;
assert_eq!(
transport.base_url()?.to_string(),
"http://127.0.0.1/?transport=polling"
);
assert_ne!(transport.base_url()?.to_string(), url);
Ok(())
}
#[test]
fn transport_debug() -> Result<()> {
let mut url = crate::test::engine_io_server()?;
let transport =
PollingTransport::new(Url::from_str(&url.to_string()[..]).unwrap(), None, None);
url.query_pairs_mut().append_pair("transport", "polling");
assert_eq!(format!("PollingTransport {{ client: {:?}, base_url: RwLock {{ data: {:?}, poisoned: false, .. }} }}", transport.client, url), format!("{:?}", transport));
let test: Box<dyn Transport> = Box::new(transport);
assert_eq!(
format!("Transport(base_url: Ok({:?}))", url),
format!("{:?}", test)
);
Ok(())
}
}
================================================
FILE: engineio/src/transports/websocket.rs
================================================
use crate::{
asynchronous::{
async_transports::WebsocketTransport as AsyncWebsocketTransport, transport::AsyncTransport,
},
error::Result,
transport::Transport,
Error,
};
use bytes::Bytes;
use http::Header
gitextract_9tvnxqqn/
├── .devcontainer/
│ ├── Dockerfile
│ ├── devcontainer.json
│ └── docker-compose.yaml
├── .github/
│ ├── dependabot.yml
│ └── workflows/
│ ├── benchmark.yml
│ ├── build.yml
│ ├── coverage.yml
│ ├── publish-dry-run.yml
│ ├── publish.yml
│ └── test.yml
├── .gitignore
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Cargo.toml
├── LICENSE
├── Makefile
├── README.md
├── Roadmap.md
├── ci/
│ ├── .dockerignore
│ ├── Dockerfile
│ ├── README.md
│ ├── engine-io-polling.js
│ ├── engine-io-secure.js
│ ├── engine-io.js
│ ├── keygen.sh
│ ├── package.json
│ ├── socket-io-auth.js
│ ├── socket-io-restart-url-auth.js
│ ├── socket-io-restart.js
│ ├── socket-io.js
│ └── start_test_server.sh
├── codecov.yml
├── engineio/
│ ├── Cargo.toml
│ ├── README.md
│ ├── benches/
│ │ └── engineio.rs
│ └── src/
│ ├── asynchronous/
│ │ ├── async_socket.rs
│ │ ├── async_transports/
│ │ │ ├── mod.rs
│ │ │ ├── polling.rs
│ │ │ ├── websocket.rs
│ │ │ ├── websocket_general.rs
│ │ │ └── websocket_secure.rs
│ │ ├── callback.rs
│ │ ├── client/
│ │ │ ├── async_client.rs
│ │ │ ├── builder.rs
│ │ │ └── mod.rs
│ │ ├── generator.rs
│ │ ├── mod.rs
│ │ └── transport.rs
│ ├── callback.rs
│ ├── client/
│ │ ├── client.rs
│ │ └── mod.rs
│ ├── error.rs
│ ├── header.rs
│ ├── lib.rs
│ ├── packet.rs
│ ├── socket.rs
│ ├── transport.rs
│ └── transports/
│ ├── mod.rs
│ ├── polling.rs
│ ├── websocket.rs
│ └── websocket_secure.rs
└── socketio/
├── Cargo.toml
├── examples/
│ ├── async.rs
│ ├── callback.rs
│ ├── readme.rs
│ └── secure.rs
└── src/
├── asynchronous/
│ ├── client/
│ │ ├── ack.rs
│ │ ├── builder.rs
│ │ ├── callback.rs
│ │ ├── client.rs
│ │ └── mod.rs
│ ├── generator.rs
│ ├── mod.rs
│ └── socket.rs
├── client/
│ ├── builder.rs
│ ├── callback.rs
│ ├── client.rs
│ ├── mod.rs
│ └── raw_client.rs
├── error.rs
├── event.rs
├── lib.rs
├── packet.rs
├── payload.rs
└── socket.rs
SYMBOL INDEX (584 symbols across 43 files)
FILE: ci/socket-io-restart-url-auth.js
constant TIMESTAMP_SLACK_ALLOWED (line 7) | const TIMESTAMP_SLACK_ALLOWED = 1000;
function isValidTimestamp (line 9) | function isValidTimestamp(timestampStr) {
FILE: engineio/benches/engineio.rs
function engine_io_url (line 15) | pub fn engine_io_url() -> Result<Url, Error> {
function engine_io_url_secure (line 21) | pub fn engine_io_url_secure() -> Result<Url, Error> {
function tls_connector (line 27) | pub fn tls_connector() -> Result<TlsConnector, Error> {
function engine_io_socket_build (line 53) | pub fn engine_io_socket_build(url: Url) -> Result<Client, Error> {
function engine_io_socket_build_polling (line 57) | pub fn engine_io_socket_build_polling(url: Url) -> Result<Client, Error> {
function engine_io_socket_build_polling_secure (line 61) | pub fn engine_io_socket_build_polling_secure(url: Url) -> Result<Client,...
function engine_io_socket_build_websocket (line 67) | pub fn engine_io_socket_build_websocket(url: Url) -> Result<Client, Erro...
function engine_io_socket_build_websocket_secure (line 71) | pub fn engine_io_socket_build_websocket_secure(url: Url) -> Result<Clien...
function engine_io_packet (line 77) | pub fn engine_io_packet() -> Packet {
function engine_io_emit (line 81) | pub fn engine_io_emit(socket: &Client, packet: Packet) -> Result<(), Err...
function criterion_engine_io_socket_build (line 92) | pub fn criterion_engine_io_socket_build(c: &mut Criterion) {
function criterion_engine_io_socket_build_polling (line 103) | pub fn criterion_engine_io_socket_build_polling(c: &mut Criterion) {
function criterion_engine_io_socket_build_polling_secure (line 114) | pub fn criterion_engine_io_socket_build_polling_secure(c: &mut Criterion) {
function criterion_engine_io_socket_build_websocket (line 125) | pub fn criterion_engine_io_socket_build_websocket(c: &mut Criterion) {
function criterion_engine_io_socket_build_websocket_secure (line 136) | pub fn criterion_engine_io_socket_build_websocket_secure(c: &mut Criteri...
function criterion_engine_io_packet (line 147) | pub fn criterion_engine_io_packet(c: &mut Criterion) {
function criterion_engine_io_emit_polling (line 151) | pub fn criterion_engine_io_emit_polling(c: &mut Criterion) {
function criterion_engine_io_emit_polling_secure (line 163) | pub fn criterion_engine_io_emit_polling_secure(c: &mut Criterion) {
function criterion_engine_io_emit_websocket (line 175) | pub fn criterion_engine_io_emit_websocket(c: &mut Criterion) {
function criterion_engine_io_emit_websocket_secure (line 187) | pub fn criterion_engine_io_emit_websocket_secure(c: &mut Criterion) {
function engine_io_socket_build (line 213) | pub async fn engine_io_socket_build(url: Url) -> Result<Client, Error> {
function engine_io_socket_build_polling (line 217) | pub async fn engine_io_socket_build_polling(url: Url) -> Result<Client, ...
function engine_io_socket_build_polling_secure (line 221) | pub async fn engine_io_socket_build_polling_secure(url: Url) -> Result<C...
function engine_io_socket_build_websocket (line 228) | pub async fn engine_io_socket_build_websocket(url: Url) -> Result<Client...
function engine_io_socket_build_websocket_secure (line 232) | pub async fn engine_io_socket_build_websocket_secure(url: Url) -> Result...
function engine_io_packet (line 239) | pub fn engine_io_packet() -> Packet {
function engine_io_emit (line 243) | pub async fn engine_io_emit(socket: &Client, packet: Packet) -> Result<(...
function criterion_engine_io_socket_build (line 270) | pub fn criterion_engine_io_socket_build(c: &mut Criterion) {
function criterion_engine_io_socket_build_polling (line 283) | pub fn criterion_engine_io_socket_build_polling(c: &mut Criterion) {
function criterion_engine_io_socket_build_polling_secure (line 296) | pub fn criterion_engine_io_socket_build_polling_secure(c: &mut Criterion) {
function criterion_engine_io_socket_build_websocket (line 309) | pub fn criterion_engine_io_socket_build_websocket(c: &mut Criterion) {
function criterion_engine_io_socket_build_websocket_secure (line 322) | pub fn criterion_engine_io_socket_build_websocket_secure(c: &mut Criteri...
function criterion_engine_io_packet (line 335) | pub fn criterion_engine_io_packet(c: &mut Criterion) {
function criterion_engine_io_emit_polling (line 341) | pub fn criterion_engine_io_emit_polling(c: &mut Criterion) {
function criterion_engine_io_emit_polling_secure (line 360) | pub fn criterion_engine_io_emit_polling_secure(c: &mut Criterion) {
function criterion_engine_io_emit_websocket (line 380) | pub fn criterion_engine_io_emit_websocket(c: &mut Criterion) {
function criterion_engine_io_emit_websocket_secure (line 400) | pub fn criterion_engine_io_emit_websocket_secure(c: &mut Criterion) {
FILE: engineio/src/asynchronous/async_socket.rs
type Socket (line 23) | pub struct Socket {
method new (line 40) | pub(crate) fn new(
method connect (line 70) | pub async fn connect(&self) -> Result<()> {
method handle_incoming_packet (line 89) | pub(super) async fn handle_incoming_packet(&self, packet: Packet) -> R...
method parse_payload (line 120) | fn parse_payload(bytes: Bytes) -> impl Stream<Item = Result<Packet>> {
method stream (line 132) | fn stream(
method disconnect (line 146) | pub async fn disconnect(&self) -> Result<()> {
method emit (line 161) | pub async fn emit(&self, packet: Packet) -> Result<()> {
method call_error_callback (line 191) | fn call_error_callback(&self, text: String) {
method is_connected (line 199) | pub(crate) fn is_connected(&self) -> bool {
method pinged (line 203) | pub(crate) async fn pinged(&self) {
method time_to_next_ping (line 210) | async fn time_to_next_ping(&self) -> u64 {
method handle_packet (line 224) | pub(crate) fn handle_packet(&self, packet: Packet) {
method handle_data (line 231) | pub(crate) fn handle_data(&self, data: Bytes) {
method handle_close (line 238) | pub(crate) fn handle_close(&self) {
method as_stream (line 248) | pub(crate) fn as_stream<'a>(
method fmt (line 280) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
FILE: engineio/src/asynchronous/async_transports/polling.rs
type PollingTransport (line 22) | pub struct PollingTransport {
method new (line 29) | pub fn new(
method address (line 58) | fn address(mut url: Url) -> Result<Url> {
method send_request (line 65) | fn send_request(url: Url, client: Client) -> impl Stream<Item = Result...
method stream (line 75) | fn stream(
type Item (line 92) | type Item = Result<Bytes>;
method poll_next (line 94) | fn poll_next(
method emit (line 104) | async fn emit(&self, data: Bytes, is_binary_att: bool) -> Result<()> {
method base_url (line 135) | async fn base_url(&self) -> Result<Url> {
method set_base_url (line 139) | async fn set_base_url(&self, base_url: Url) -> Result<()> {
method fmt (line 153) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
function polling_transport_base_url (line 169) | async fn polling_transport_base_url() -> Result<()> {
FILE: engineio/src/asynchronous/async_transports/websocket.rs
type WebsocketTransport (line 23) | pub struct WebsocketTransport {
method new (line 30) | pub async fn new(base_url: Url, headers: Option<HeaderMap>) -> Result<...
method upgrade (line 53) | pub(crate) async fn upgrade(&self) -> Result<()> {
method poll_next (line 57) | pub(crate) async fn poll_next(&self) -> Result<Option<Bytes>> {
method emit (line 64) | async fn emit(&self, data: Bytes, is_binary_att: bool) -> Result<()> {
method base_url (line 68) | async fn base_url(&self) -> Result<Url> {
method set_base_url (line 72) | async fn set_base_url(&self, base_url: Url) -> Result<()> {
type Item (line 87) | type Item = Result<Bytes>;
method poll_next (line 89) | fn poll_next(
method fmt (line 98) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
function new (line 117) | async fn new() -> Result<WebsocketTransport> {
function websocket_transport_base_url (line 125) | async fn websocket_transport_base_url() -> Result<()> {
function websocket_secure_debug (line 157) | async fn websocket_secure_debug() -> Result<()> {
FILE: engineio/src/asynchronous/async_transports/websocket_general.rs
type AsyncWebsocketSender (line 14) | type AsyncWebsocketSender = SplitSink<WebSocketStream<MaybeTlsStream<Tcp...
type AsyncWebsocketReceiver (line 15) | type AsyncWebsocketReceiver = SplitStream<WebSocketStream<MaybeTlsStream...
type AsyncWebsocketGeneralTransport (line 22) | pub(crate) struct AsyncWebsocketGeneralTransport {
method new (line 28) | pub(crate) async fn new(
method upgrade (line 40) | pub(crate) async fn upgrade(&self) -> Result<()> {
method emit (line 68) | pub(crate) async fn emit(&self, data: Bytes, is_binary_att: bool) -> R...
method poll_next (line 82) | pub(crate) async fn poll_next(&self) -> Result<Option<Bytes>> {
type Item (line 105) | type Item = Result<Bytes>;
method poll_next (line 107) | fn poll_next(
FILE: engineio/src/asynchronous/async_transports/websocket_secure.rs
type WebsocketSecureTransport (line 25) | pub struct WebsocketSecureTransport {
method new (line 33) | pub(crate) async fn new(
method upgrade (line 74) | pub(crate) async fn upgrade(&self) -> Result<()> {
method poll_next (line 78) | pub(crate) async fn poll_next(&self) -> Result<Option<Bytes>> {
type Item (line 84) | type Item = Result<Bytes>;
method poll_next (line 86) | fn poll_next(
method emit (line 96) | async fn emit(&self, data: Bytes, is_binary_att: bool) -> Result<()> {
method base_url (line 100) | async fn base_url(&self) -> Result<Url> {
method set_base_url (line 104) | async fn set_base_url(&self, base_url: Url) -> Result<()> {
method fmt (line 119) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
function new (line 138) | async fn new() -> Result<WebsocketSecureTransport> {
function websocket_secure_transport_base_url (line 151) | async fn websocket_secure_transport_base_url() -> Result<()> {
function websocket_secure_debug (line 183) | async fn websocket_secure_debug() -> Result<()> {
FILE: engineio/src/asynchronous/callback.rs
type DynAsyncCallback (line 8) | pub(crate) type DynAsyncCallback<I> = dyn 'static + Send + Sync + Fn(I) ...
type OptionalCallback (line 12) | pub(crate) struct OptionalCallback<I> {
function new (line 17) | pub(crate) fn new<T>(callback: T) -> Self
function default (line 26) | pub(crate) fn default() -> Self {
method fmt (line 33) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<()...
method fmt (line 47) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<()...
method fmt (line 61) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<()...
method fmt (line 75) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<()...
type Target (line 88) | type Target = Option<Arc<DynAsyncCallback<I>>>;
method deref (line 89) | fn deref(&self) -> &<Self as std::ops::Deref>::Target {
FILE: engineio/src/asynchronous/client/async_client.rs
type Client (line 19) | pub struct Client {
method new (line 25) | pub(super) fn new(socket: InnerSocket) -> Self {
method close (line 32) | pub async fn close(&self) -> Result<()> {
method connect (line 38) | pub async fn connect(&self) -> Result<()> {
method disconnect (line 43) | pub async fn disconnect(&self) -> Result<()> {
method emit (line 48) | pub async fn emit(&self, packet: Packet) -> Result<()> {
method stream (line 53) | fn stream(
method is_connected (line 67) | pub fn is_connected(&self) -> bool {
type Item (line 73) | type Item = Result<Packet>;
method poll_next (line 75) | fn poll_next(
method fmt (line 84) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
function test_client_cloneable (line 106) | async fn test_client_cloneable() -> Result<()> {
function test_illegal_actions (line 143) | async fn test_illegal_actions() -> Result<()> {
function builder (line 170) | fn builder(url: Url) -> ClientBuilder {
function test_connection (line 199) | async fn test_connection(socket: Client) -> Result<()> {
function test_connection_long (line 226) | async fn test_connection_long() -> Result<()> {
function test_connection_dynamic (line 258) | async fn test_connection_dynamic() -> Result<()> {
function test_connection_fallback (line 269) | async fn test_connection_fallback() -> Result<()> {
function test_connection_dynamic_secure (line 280) | async fn test_connection_dynamic_secure() -> Result<()> {
function test_connection_polling (line 289) | async fn test_connection_polling() -> Result<()> {
function test_connection_wss (line 296) | async fn test_connection_wss() -> Result<()> {
function test_connection_ws (line 333) | async fn test_connection_ws() -> Result<()> {
function test_open_invariants (line 360) | async fn test_open_invariants() -> Result<()> {
FILE: engineio/src/asynchronous/client/builder.rs
type ClientBuilder (line 21) | pub struct ClientBuilder {
method new (line 34) | pub fn new(url: Url) -> Self {
method tls_config (line 57) | pub fn tls_config(mut self, tls_config: TlsConnector) -> Self {
method headers (line 63) | pub fn headers(mut self, headers: HeaderMap) -> Self {
method on_close (line 70) | pub fn on_close<T>(mut self, callback: T) -> Self
method on_data (line 80) | pub fn on_data<T>(mut self, callback: T) -> Self
method on_error (line 90) | pub fn on_error<T>(mut self, callback: T) -> Self
method on_open (line 100) | pub fn on_open<T>(mut self, callback: T) -> Self
method on_packet (line 110) | pub fn on_packet<T>(mut self, callback: T) -> Self
method handshake_with_transport (line 119) | async fn handshake_with_transport<T: AsyncTransport + Unpin>(
method handshake (line 144) | async fn handshake(&mut self) -> Result<()> {
method build (line 163) | pub async fn build(mut self) -> Result<Client> {
method build_polling (line 174) | pub async fn build_polling(mut self) -> Result<Client> {
method build_websocket_with_upgrade (line 197) | pub async fn build_websocket_with_upgrade(mut self) -> Result<Client> {
method build_websocket (line 208) | pub async fn build_websocket(mut self) -> Result<Client> {
method build_with_fallback (line 267) | pub async fn build_with_fallback(self) -> Result<Client> {
method websocket_upgrade (line 277) | fn websocket_upgrade(&mut self) -> Result<bool> {
FILE: engineio/src/asynchronous/generator.rs
type Generator (line 9) | pub(crate) type Generator<T> = Pin<Box<dyn Stream<Item = T> + 'static + ...
type StreamGenerator (line 14) | pub(crate) struct StreamGenerator<T> {
type Item (line 19) | type Item = Result<T>;
method poll_next (line 21) | fn poll_next(
function new (line 31) | pub(crate) fn new(generator_stream: Generator<Result<T>>) -> Self {
FILE: engineio/src/asynchronous/transport.rs
type AsyncTransport (line 12) | pub trait AsyncTransport: Stream<Item = Result<Bytes>> + Unpin {
method emit (line 15) | async fn emit(&self, data: Bytes, is_binary_att: bool) -> Result<()>;
method base_url (line 19) | async fn base_url(&self) -> Result<Url>;
method set_base_url (line 22) | async fn set_base_url(&self, base_url: Url) -> Result<()>;
method address (line 25) | async fn address(&self) -> Result<Url>
type AsyncTransportType (line 38) | pub enum AsyncTransportType {
method from (line 45) | fn from(transport: PollingTransport) -> Self {
method from (line 51) | fn from(transport: WebsocketTransport) -> Self {
method from (line 57) | fn from(transport: WebsocketSecureTransport) -> Self {
method as_transport (line 64) | pub fn as_transport(&self) -> &(dyn AsyncTransport + Send) {
method as_pin_box (line 72) | pub fn as_pin_box(&mut self) -> Pin<Box<&mut (dyn AsyncTransport + Sen...
FILE: engineio/src/callback.rs
type DynCallback (line 7) | pub(crate) type DynCallback<I> = dyn Fn(I) + 'static + Sync + Send;
type OptionalCallback (line 11) | pub(crate) struct OptionalCallback<I> {
function new (line 16) | pub(crate) fn new<T>(callback: T) -> Self
function default (line 25) | pub(crate) fn default() -> Self {
method fmt (line 34) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<()...
method fmt (line 48) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<()...
method fmt (line 62) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<()...
method fmt (line 76) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<()...
type Target (line 89) | type Target = Option<Box<DynCallback<I>>>;
method deref (line 90) | fn deref(&self) -> &<Self as std::ops::Deref>::Target {
FILE: engineio/src/client/client.rs
type Client (line 26) | pub struct Client {
method close (line 291) | pub fn close(&self) -> Result<()> {
method connect (line 297) | pub fn connect(&self) -> Result<()> {
method disconnect (line 302) | pub fn disconnect(&self) -> Result<()> {
method emit (line 307) | pub fn emit(&self, packet: Packet) -> Result<()> {
method poll (line 313) | pub fn poll(&self) -> Result<Option<Packet>> {
method is_connected (line 352) | pub fn is_connected(&self) -> Result<bool> {
method iter (line 356) | pub fn iter(&self) -> Iter {
type ClientBuilder (line 31) | pub struct ClientBuilder {
method new (line 44) | pub fn new(url: Url) -> Self {
method tls_config (line 67) | pub fn tls_config(mut self, tls_config: TlsConnector) -> Self {
method headers (line 73) | pub fn headers(mut self, headers: HeaderMap) -> Self {
method on_close (line 79) | pub fn on_close<T>(mut self, callback: T) -> Self
method on_data (line 88) | pub fn on_data<T>(mut self, callback: T) -> Self
method on_error (line 97) | pub fn on_error<T>(mut self, callback: T) -> Self
method on_open (line 106) | pub fn on_open<T>(mut self, callback: T) -> Self
method on_packet (line 115) | pub fn on_packet<T>(mut self, callback: T) -> Self
method handshake_with_transport (line 124) | fn handshake_with_transport<T: Transport>(&mut self, transport: &T) ->...
method handshake (line 145) | fn handshake(&mut self) -> Result<()> {
method build (line 161) | pub fn build(mut self) -> Result<Client> {
method build_polling (line 172) | pub fn build_polling(mut self) -> Result<Client> {
method build_websocket_with_upgrade (line 197) | pub fn build_websocket_with_upgrade(mut self) -> Result<Client> {
method build_websocket (line 208) | pub fn build_websocket(mut self) -> Result<Client> {
method build_with_fallback (line 268) | pub fn build_with_fallback(self) -> Result<Client> {
method websocket_upgrade (line 278) | fn websocket_upgrade(&mut self) -> Result<bool> {
type Iter (line 362) | pub struct Iter<'a> {
type Item (line 367) | type Item = Result<Packet>;
method next (line 368) | fn next(&mut self) -> std::option::Option<<Self as std::iter::Iterator>:...
function test_client_cloneable (line 389) | fn test_client_cloneable() -> Result<()> {
function test_illegal_actions (line 436) | fn test_illegal_actions() -> Result<()> {
function builder (line 458) | fn builder(url: Url) -> ClientBuilder {
function test_connection (line 477) | fn test_connection(socket: Client) -> Result<()> {
function test_connection_long (line 505) | fn test_connection_long() -> Result<()> {
function test_connection_dynamic (line 526) | fn test_connection_dynamic() -> Result<()> {
function test_connection_fallback (line 537) | fn test_connection_fallback() -> Result<()> {
function test_connection_dynamic_secure (line 548) | fn test_connection_dynamic_secure() -> Result<()> {
function test_connection_polling (line 557) | fn test_connection_polling() -> Result<()> {
function test_connection_wss (line 564) | fn test_connection_wss() -> Result<()> {
function test_connection_ws (line 601) | fn test_connection_ws() -> Result<()> {
function test_open_invariants (line 628) | fn test_open_invariants() -> Result<()> {
FILE: engineio/src/error.rs
type Error (line 14) | pub enum Error {
method from (line 62) | fn from(_: std::sync::PoisonError<T>) -> Self {
type Result (line 59) | pub(crate) type Result<T> = std::result::Result<T, Error>;
function from (line 68) | fn from(err: Error) -> std::io::Error {
function test_error_conversion (line 81) | fn test_error_conversion() {
FILE: engineio/src/header.rs
type HeaderName (line 13) | pub struct HeaderName {
method from (line 38) | fn from(string: String) -> Self {
method from (line 55) | fn from(header: HttpHeaderName) -> Self {
type HeaderValue (line 18) | pub struct HeaderValue {
method from (line 61) | fn from(string: String) -> Self {
method from (line 78) | fn from(value: HttpHeaderValue) -> Self {
method from (line 86) | fn from(string: &str) -> Self {
type HeaderMap (line 23) | pub struct HeaderMap {
method new (line 119) | pub fn new() -> Self {
method insert (line 125) | pub fn insert<T: Into<HeaderName>, U: Into<HeaderValue>>(
type IntoIter (line 27) | pub struct IntoIter {
method fmt (line 32) | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
type Error (line 46) | type Error = Error;
method try_from (line 47) | fn try_from(
type Error (line 69) | type Error = Error;
method try_from (line 70) | fn try_from(
type Error (line 92) | type Error = Error;
method try_from (line 93) | fn try_from(
type Item (line 109) | type Item = (HeaderName, HeaderValue);
type IntoIter (line 110) | type IntoIter = IntoIter;
method into_iter (line 111) | fn into_iter(self) -> <Self as std::iter::IntoIterator>::IntoIter {
type Item (line 135) | type Item = (HeaderName, HeaderValue);
method next (line 136) | fn next(&mut self) -> std::option::Option<<Self as std::iter::Iterator>:...
FILE: engineio/src/lib.rs
constant ENGINE_IO_VERSION (line 92) | pub const ENGINE_IO_VERSION: i32 = 4;
constant CERT_PATH (line 106) | const CERT_PATH: &str = "../ci/cert/ca.crt";
function tls_connector (line 111) | pub(crate) fn tls_connector() -> error::Result<TlsConnector> {
constant SERVER_URL (line 125) | const SERVER_URL: &str = "http://localhost:4201";
constant SERVER_POLLING_URL (line 127) | const SERVER_POLLING_URL: &str = "http://localhost:4203";
constant SERVER_URL_SECURE (line 128) | const SERVER_URL_SECURE: &str = "https://localhost:4202";
function engine_io_server (line 131) | pub(crate) fn engine_io_server() -> crate::error::Result<Url> {
function engine_io_polling_server (line 136) | pub(crate) fn engine_io_polling_server() -> crate::error::Result<Url> {
function engine_io_server_secure (line 142) | pub(crate) fn engine_io_server_secure() -> crate::error::Result<Url> {
FILE: engineio/src/packet.rs
type PacketId (line 13) | pub enum PacketId {
method to_string_byte (line 27) | fn to_string_byte(self) -> u8 {
type Error (line 57) | type Error = Error;
method try_from (line 59) | fn try_from(b: u8) -> Result<PacketId> {
method fmt (line 36) | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
function from (line 42) | fn from(packet_id: PacketId) -> Self {
type Packet (line 75) | pub struct Packet {
method new (line 100) | pub fn new<T: Into<Bytes>>(packet_id: PacketId, data: T) -> Self {
type Error (line 109) | type Error = Error;
method try_from (line 111) | fn try_from(
type HandshakePacket (line 82) | pub struct HandshakePacket {
type Error (line 92) | type Error = Error;
method try_from (line 93) | fn try_from(packet: Packet) -> Result<HandshakePacket> {
method from (line 146) | fn from(packet: Packet) -> Self {
type Payload (line 159) | pub(crate) struct Payload(Vec<Packet>);
constant SEPARATOR (line 163) | const SEPARATOR: char = '\x1e';
method len (line 166) | pub fn len(&self) -> usize {
type Error (line 172) | type Error = Error;
method try_from (line 175) | fn try_from(payload: Bytes) -> Result<Self> {
type Output (line 226) | type Output = Packet;
method index (line 227) | fn index(&self, index: usize) -> &Packet {
type Error (line 185) | type Error = Error;
method try_from (line 189) | fn try_from(packets: Payload) -> Result<Self> {
type IntoIter (line 204) | pub struct IntoIter {
type Item (line 209) | type Item = Packet;
method next (line 210) | fn next(&mut self) -> std::option::Option<<Self as std::iter::Iterator>:...
type Item (line 216) | type Item = Packet;
type IntoIter (line 217) | type IntoIter = IntoIter;
method into_iter (line 218) | fn into_iter(self) -> <Self as std::iter::IntoIterator>::IntoIter {
function test_packet_error (line 237) | fn test_packet_error() {
function test_is_reflexive (line 243) | fn test_is_reflexive() {
function test_binary_packet (line 255) | fn test_binary_packet() {
function test_decode_payload (line 267) | fn test_decode_payload() -> Result<()> {
function test_binary_payload (line 283) | fn test_binary_payload() {
function test_packet_id_conversion_and_incompl_packet (line 299) | fn test_packet_id_conversion_and_incompl_packet() -> Result<()> {
function test_handshake_packet (line 343) | fn test_handshake_packet() {
FILE: engineio/src/socket.rs
constant DEFAULT_MAX_POLL_TIMEOUT (line 19) | pub const DEFAULT_MAX_POLL_TIMEOUT: Duration = Duration::from_secs(45);
type Socket (line 24) | pub struct Socket {
method new (line 41) | pub(crate) fn new(
method connect (line 70) | pub fn connect(&self) -> Result<()> {
method disconnect (line 87) | pub fn disconnect(&self) -> Result<()> {
method emit (line 101) | pub fn emit(&self, packet: Packet) -> Result<()> {
method poll (line 127) | pub(crate) fn poll(&self) -> Result<Option<Packet>> {
method call_error_callback (line 166) | fn call_error_callback(&self, text: String) {
method is_connected (line 173) | pub(crate) fn is_connected(&self) -> Result<bool> {
method pinged (line 177) | pub(crate) fn pinged(&self) -> Result<()> {
method time_to_next_ping (line 185) | fn time_to_next_ping(&self) -> Result<u64> {
method handle_packet (line 199) | pub(crate) fn handle_packet(&self, packet: Packet) {
method handle_data (line 205) | pub(crate) fn handle_data(&self, data: Bytes) {
method handle_close (line 211) | pub(crate) fn handle_close(&self) {
method fmt (line 222) | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
FILE: engineio/src/transport.rs
type Transport (line 8) | pub trait Transport {
method emit (line 11) | fn emit(&self, data: Bytes, is_binary_att: bool) -> Result<()>;
method poll (line 16) | fn poll(&self, timeout: Duration) -> Result<Bytes>;
method base_url (line 20) | fn base_url(&self) -> Result<Url>;
method set_base_url (line 23) | fn set_base_url(&self, base_url: Url) -> Result<()>;
method address (line 26) | fn address(&self) -> Result<Url> {
type TransportType (line 36) | pub enum TransportType {
method from (line 43) | fn from(transport: PollingTransport) -> Self {
method from (line 49) | fn from(transport: WebsocketSecureTransport) -> Self {
method from (line 55) | fn from(transport: WebsocketTransport) -> Self {
method as_transport (line 61) | pub fn as_transport(&self) -> &dyn Transport {
function fmt (line 71) | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
FILE: engineio/src/transports/polling.rs
type PollingTransport (line 15) | pub struct PollingTransport {
method new (line 22) | pub fn new(
method emit (line 52) | fn emit(&self, data: Bytes, is_binary_att: bool) -> Result<()> {
method poll (line 81) | fn poll(&self, timeout: Duration) -> Result<Bytes> {
method base_url (line 90) | fn base_url(&self) -> Result<Url> {
method set_base_url (line 94) | fn set_base_url(&self, base_url: Url) -> Result<()> {
function polling_transport_base_url (line 112) | fn polling_transport_base_url() -> Result<()> {
function transport_debug (line 136) | fn transport_debug() -> Result<()> {
FILE: engineio/src/transports/websocket.rs
type WebsocketTransport (line 16) | pub struct WebsocketTransport {
method new (line 23) | pub fn new(base_url: Url, headers: Option<HeaderMap>) -> Result<Self> {
method upgrade (line 38) | pub(crate) fn upgrade(&self) -> Result<()> {
method fmt (line 73) | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
method emit (line 44) | fn emit(&self, data: Bytes, is_binary_att: bool) -> Result<()> {
method poll (line 49) | fn poll(&self, timeout: Duration) -> Result<Bytes> {
method base_url (line 62) | fn base_url(&self) -> Result<url::Url> {
method set_base_url (line 66) | fn set_base_url(&self, url: url::Url) -> Result<()> {
constant TIMEOUT_DURATION (line 87) | const TIMEOUT_DURATION: Duration = Duration::from_secs(45);
function new (line 89) | fn new() -> Result<WebsocketTransport> {
function websocket_transport_base_url (line 97) | fn websocket_transport_base_url() -> Result<()> {
function websocket_secure_debug (line 125) | fn websocket_secure_debug() -> Result<()> {
FILE: engineio/src/transports/websocket_secure.rs
type WebsocketSecureTransport (line 18) | pub struct WebsocketSecureTransport {
method new (line 25) | pub fn new(
method upgrade (line 46) | pub(crate) fn upgrade(&self) -> Result<()> {
method fmt (line 81) | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
method emit (line 52) | fn emit(&self, data: Bytes, is_binary_att: bool) -> Result<()> {
method poll (line 57) | fn poll(&self, timeout: Duration) -> Result<Bytes> {
method base_url (line 70) | fn base_url(&self) -> Result<url::Url> {
method set_base_url (line 74) | fn set_base_url(&self, url: url::Url) -> Result<()> {
function new (line 94) | fn new() -> Result<WebsocketSecureTransport> {
function websocket_secure_transport_base_url (line 106) | fn websocket_secure_transport_base_url() -> Result<()> {
function websocket_secure_debug (line 134) | fn websocket_secure_debug() -> Result<()> {
FILE: socketio/examples/async.rs
function main (line 10) | async fn main() {
FILE: socketio/examples/callback.rs
function handle_foo (line 4) | fn handle_foo(payload: Payload, socket: RawClient) -> () {
function main (line 8) | fn main() {
FILE: socketio/examples/readme.rs
function main (line 5) | fn main() {
FILE: socketio/examples/secure.rs
function main (line 7) | fn main() {
FILE: socketio/src/asynchronous/client/ack.rs
type Ack (line 13) | pub(crate) struct Ack {
FILE: socketio/src/asynchronous/client/builder.rs
type ClientBuilder (line 25) | pub struct ClientBuilder {
method new (line 83) | pub fn new<T: Into<String>>(address: T) -> Self {
method namespace (line 107) | pub fn namespace<T: Into<String>>(mut self, namespace: T) -> Self {
method on (line 187) | pub fn on<T: Into<Event>, F>(mut self, event: T, callback: F) -> Self
method on_reconnect (line 225) | pub fn on_reconnect<F>(mut self, callback: F) -> Self
method on_any (line 258) | pub fn on_any<F>(mut self, callback: F) -> Self
method tls_config (line 289) | pub fn tls_config(mut self, tls_config: TlsConnector) -> Self {
method opening_header (line 312) | pub fn opening_header<T: Into<HeaderValue>, K: Into<String>>(mut self,...
method auth (line 343) | pub fn auth<T: Into<serde_json::Value>>(mut self, auth: T) -> Self {
method transport_type (line 365) | pub fn transport_type(mut self, transport_type: TransportType) -> Self {
method reconnect (line 373) | pub fn reconnect(mut self, reconnect: bool) -> Self {
method reconnect_on_disconnect (line 380) | pub fn reconnect_on_disconnect(mut self, reconnect_on_disconnect: bool...
method reconnect_delay (line 386) | pub fn reconnect_delay(mut self, min: u64, max: u64) -> Self {
method max_reconnect_attempts (line 394) | pub fn max_reconnect_attempts(mut self, reconnect_attempts: u8) -> Self {
method connect (line 426) | pub async fn connect(self) -> Result<Client> {
method inner_create (line 434) | pub(crate) async fn inner_create(&self) -> Result<InnerSocket> {
method connect_manual (line 462) | pub(crate) async fn connect_manual(self) -> Result<Client> {
FILE: socketio/src/asynchronous/client/callback.rs
type DynAsyncCallback (line 12) | pub(crate) type DynAsyncCallback =
type DynAsyncAnyCallback (line 15) | pub(crate) type DynAsyncAnyCallback = Box<
type DynAsyncReconnectSettingsCallback (line 19) | pub(crate) type DynAsyncReconnectSettingsCallback =
type Callback (line 22) | pub(crate) struct Callback<T> {
method fmt (line 27) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
type Target (line 33) | type Target =
method deref (line 36) | fn deref(&self) -> &Self::Target {
method deref_mut (line 42) | fn deref_mut(&mut self) -> &mut Self::Target {
function new (line 48) | pub(crate) fn new<T>(callback: T) -> Self
type Target (line 59) | type Target =
method deref (line 62) | fn deref(&self) -> &Self::Target {
method deref_mut (line 68) | fn deref_mut(&mut self) -> &mut Self::Target {
function new (line 74) | pub(crate) fn new<T>(callback: T) -> Self
type Target (line 85) | type Target =
method deref (line 88) | fn deref(&self) -> &Self::Target {
method deref_mut (line 94) | fn deref_mut(&mut self) -> &mut Self::Target {
function new (line 100) | pub(crate) fn new<T>(callback: T) -> Self
FILE: socketio/src/asynchronous/client/client.rs
type DisconnectReason (line 27) | enum DisconnectReason {
type ReconnectSettings (line 39) | pub struct ReconnectSettings {
method new (line 46) | pub fn new() -> Self {
method address (line 51) | pub fn address<T>(&mut self, address: T) -> &mut Self
method auth (line 60) | pub fn auth(&mut self, auth: serde_json::Value) {
method opening_header (line 66) | pub fn opening_header<T: Into<HeaderValue>, K: Into<String>>(
type Client (line 82) | pub struct Client {
method new (line 99) | pub(crate) fn new(socket: InnerSocket, builder: ClientBuilder) -> Resu...
method connect (line 112) | pub(crate) async fn connect(&self) -> Result<()> {
method reconnect (line 125) | pub(crate) async fn reconnect(&mut self) -> Result<()> {
method poll_stream (line 160) | pub(crate) async fn poll_stream(&mut self) -> Result<()> {
method emit (line 276) | pub async fn emit<E, D>(&self, event: E, data: D) -> Result<()>
method disconnect (line 321) | pub async fn disconnect(&self) -> Result<()> {
method emit_with_ack (line 381) | pub async fn emit_with_ack<F, E, D>(
method callback (line 413) | async fn callback<P: Into<Payload>>(&self, event: &Event, payload: P) ...
method handle_ack (line 436) | async fn handle_ack(&self, socket_packet: &Packet) -> Result<()> {
method handle_binary_event (line 474) | async fn handle_binary_event(&self, packet: &Packet) -> Result<()> {
method handle_event (line 492) | async fn handle_event(&self, packet: &Packet) -> Result<()> {
method handle_socketio_packet (line 527) | async fn handle_socketio_packet(&self, packet: &Packet) -> Result<()> {
method as_stream (line 572) | pub(crate) async fn as_stream<'a>(
function socket_io_integration (line 632) | async fn socket_io_integration() -> Result<()> {
function socket_io_async_callback (line 687) | async fn socket_io_async_callback() -> Result<()> {
function socket_io_builder_integration (line 722) | async fn socket_io_builder_integration() -> Result<()> {
function socket_io_reconnect_integration (line 774) | async fn socket_io_reconnect_integration() -> Result<()> {
function socket_io_builder_integration_iterator (line 881) | async fn socket_io_builder_integration_iterator() -> Result<()> {
function socket_io_on_any_integration (line 930) | async fn socket_io_on_any_integration() -> Result<()> {
function socket_io_auth_builder_integration (line 961) | async fn socket_io_auth_builder_integration() -> Result<()> {
function socket_io_transport_close (line 991) | async fn socket_io_transport_close() -> Result<()> {
function socketio_polling_integration (line 1035) | async fn socketio_polling_integration() -> Result<()> {
function socket_io_websocket_integration (line 1045) | async fn socket_io_websocket_integration() -> Result<()> {
function socket_io_websocket_upgrade_integration (line 1055) | async fn socket_io_websocket_upgrade_integration() -> Result<()> {
function socket_io_any_integration (line 1065) | async fn socket_io_any_integration() -> Result<()> {
function test_socketio_socket (line 1074) | async fn test_socketio_socket(socket: Client, nsp: String) -> Result<()> {
function load (line 1255) | fn load(num: &AtomicUsize) -> usize {
FILE: socketio/src/asynchronous/generator.rs
type Generator (line 9) | pub(crate) type Generator<T> = Pin<Box<dyn Stream<Item = T> + 'static + ...
type StreamGenerator (line 14) | pub(crate) struct StreamGenerator<T> {
type Item (line 19) | type Item = Result<T>;
method poll_next (line 21) | fn poll_next(
function new (line 31) | pub(crate) fn new(generator_stream: Generator<Result<T>>) -> Self {
FILE: socketio/src/asynchronous/socket.rs
type Socket (line 23) | pub(crate) struct Socket {
method new (line 31) | pub(super) fn new(engine_client: EngineClient) -> Result<Self> {
method connect (line 42) | pub async fn connect(&self) -> Result<()> {
method disconnect (line 54) | pub async fn disconnect(&self) -> Result<()> {
method send (line 65) | pub async fn send(&self, packet: Packet) -> Result<()> {
method emit (line 86) | pub async fn emit(&self, nsp: &str, event: Event, data: Payload) -> Re...
method stream (line 92) | fn stream(
method handle_socketio_packet (line 114) | fn handle_socketio_packet(socket_packet: &Packet, is_connected: Arc<At...
method handle_engineio_packet (line 130) | async fn handle_engineio_packet(
method is_engineio_connected (line 164) | fn is_engineio_connected(&self) -> bool {
type Item (line 170) | type Item = Result<Packet>;
method poll_next (line 172) | fn poll_next(
method fmt (line 181) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
FILE: socketio/src/client/builder.rs
type TransportType (line 19) | pub enum TransportType {
type ClientBuilder (line 35) | pub struct ClientBuilder {
method new (line 85) | pub fn new<T: Into<String>>(address: T) -> Self {
method namespace (line 106) | pub fn namespace<T: Into<String>>(mut self, namespace: T) -> Self {
method reconnect (line 115) | pub fn reconnect(mut self, reconnect: bool) -> Self {
method reconnect_on_disconnect (line 132) | pub fn reconnect_on_disconnect(mut self, reconnect_on_disconnect: bool...
method reconnect_delay (line 137) | pub fn reconnect_delay(mut self, min: u64, max: u64) -> Self {
method max_reconnect_attempts (line 144) | pub fn max_reconnect_attempts(mut self, reconnect_attempts: u8) -> Self {
method on (line 173) | pub fn on<T: Into<Event>, F>(mut self, event: T, callback: F) -> Self
method on_any (line 201) | pub fn on_any<F>(mut self, callback: F) -> Self
method tls_config (line 230) | pub fn tls_config(mut self, tls_config: TlsConnector) -> Self {
method opening_header (line 250) | pub fn opening_header<T: Into<HeaderValue>, K: Into<String>>(mut self,...
method auth (line 278) | pub fn auth(mut self, auth: serde_json::Value) -> Self {
method transport_type (line 303) | pub fn transport_type(mut self, transport_type: TransportType) -> Self {
method connect (line 332) | pub fn connect(self) -> Result<Client> {
method connect_raw (line 336) | pub fn connect_raw(self) -> Result<RawClient> {
FILE: socketio/src/client/callback.rs
type SocketCallback (line 9) | pub(crate) type SocketCallback = Box<dyn FnMut(Payload, RawClient) + 'st...
type SocketAnyCallback (line 10) | pub(crate) type SocketAnyCallback = Box<dyn FnMut(Event, Payload, RawCli...
type Callback (line 12) | pub(crate) struct Callback<T> {
method fmt (line 19) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
type Target (line 25) | type Target = dyn FnMut(Payload, RawClient) + 'static + Send;
method deref (line 27) | fn deref(&self) -> &Self::Target {
method deref_mut (line 33) | fn deref_mut(&mut self) -> &mut Self::Target {
function new (line 39) | pub(crate) fn new<T>(callback: T) -> Self
method fmt (line 52) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
type Target (line 58) | type Target = dyn FnMut(Event, Payload, RawClient) + 'static + Send;
method deref (line 60) | fn deref(&self) -> &Self::Target {
method deref_mut (line 66) | fn deref_mut(&mut self) -> &mut Self::Target {
function new (line 72) | pub(crate) fn new<T>(callback: T) -> Self
FILE: socketio/src/client/client.rs
type Client (line 17) | pub struct Client {
method new (line 24) | pub(crate) fn new(builder: ClientBuilder) -> Result<Self> {
method set_reconnect_url (line 44) | pub fn set_reconnect_url<T: Into<String>>(&self, address: T) -> Result...
method emit (line 74) | pub fn emit<E, D>(&self, event: E, data: D) -> Result<()>
method emit_with_ack (line 121) | pub fn emit_with_ack<F, E, D>(
method disconnect (line 163) | pub fn disconnect(&self) -> Result<()> {
method reconnect (line 168) | fn reconnect(&mut self) -> Result<()> {
method do_reconnect (line 197) | fn do_reconnect(&self) -> Result<()> {
method iter (line 206) | pub(crate) fn iter(&self) -> Iter {
method poll_callback (line 212) | fn poll_callback(&self) {
type Iter (line 245) | pub(crate) struct Iter {
type Item (line 250) | type Item = Result<Packet>;
method next (line 252) | fn next(&mut self) -> Option<Self::Item> {
function socket_io_reconnect_integration (line 287) | fn socket_io_reconnect_integration() -> Result<()> {
function socket_io_reconnect_url_auth_integration (line 348) | fn socket_io_reconnect_url_auth_integration() -> Result<()> {
function socket_io_iterator_integration (line 422) | fn socket_io_iterator_integration() -> Result<()> {
function load (line 475) | fn load(num: &AtomicUsize) -> usize {
FILE: socketio/src/client/raw_client.rs
type Ack (line 23) | pub struct Ack {
type RawClient (line 34) | pub struct RawClient {
method new (line 51) | pub(crate) fn new<T: Into<String>>(
method connect (line 71) | pub(crate) fn connect(&self) -> Result<()> {
method emit (line 111) | pub fn emit<E, D>(&self, event: E, data: D) -> Result<()>
method disconnect (line 144) | pub fn disconnect(&self) -> Result<()> {
method emit_with_ack (line 194) | pub fn emit_with_ack<F, E, D>(
method poll (line 224) | pub(crate) fn poll(&self) -> Result<Option<Packet>> {
method iter (line 245) | pub(crate) fn iter(&self) -> Iter {
method callback (line 249) | fn callback<P: Into<Payload>>(&self, event: &Event, payload: P) -> Res...
method handle_ack (line 275) | fn handle_ack(&self, socket_packet: &Packet) -> Result<()> {
method handle_binary_event (line 310) | fn handle_binary_event(&self, packet: &Packet) -> Result<()> {
method handle_event (line 327) | fn handle_event(&self, packet: &Packet) -> Result<()> {
method handle_socketio_packet (line 361) | fn handle_socketio_packet(&self, packet: &Packet) -> Result<()> {
type Iter (line 402) | pub struct Iter<'a> {
type Item (line 407) | type Item = Result<Packet>;
method next (line 408) | fn next(&mut self) -> std::option::Option<<Self as std::iter::Iterator>:...
function socket_io_integration (line 430) | fn socket_io_integration() -> Result<()> {
function socket_io_builder_integration (line 475) | fn socket_io_builder_integration() -> Result<()> {
function socket_io_builder_integration_iterator (line 516) | fn socket_io_builder_integration_iterator() -> Result<()> {
function socket_io_on_any_integration (line 555) | fn socket_io_on_any_integration() -> Result<()> {
function socket_io_auth_builder_integration (line 588) | fn socket_io_auth_builder_integration() -> Result<()> {
function socketio_polling_integration (line 622) | fn socketio_polling_integration() -> Result<()> {
function socket_io_websocket_integration (line 631) | fn socket_io_websocket_integration() -> Result<()> {
function socket_io_websocket_upgrade_integration (line 640) | fn socket_io_websocket_upgrade_integration() -> Result<()> {
function socket_io_any_integration (line 649) | fn socket_io_any_integration() -> Result<()> {
function test_socketio_socket (line 657) | fn test_socketio_socket(socket: RawClient, nsp: String) -> Result<()> {
FILE: socketio/src/error.rs
type Error (line 14) | pub enum Error {
method from (line 54) | fn from(_: std::sync::PoisonError<T>) -> Self {
type Result (line 51) | pub(crate) type Result<T> = std::result::Result<T, Error>;
function from (line 60) | fn from(err: Error) -> std::io::Error {
function test_error_conversion (line 73) | fn test_error_conversion() {
FILE: socketio/src/event.rs
type Event (line 5) | pub enum Event {
method as_str (line 14) | pub fn as_str(&self) -> &str {
method from (line 26) | fn from(string: String) -> Self {
method from (line 38) | fn from(string: &str) -> Self {
method from (line 44) | fn from(event: Event) -> Self {
method fmt (line 56) | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
type CloseReason (line 66) | pub enum CloseReason {
method as_str (line 73) | pub fn as_str(&self) -> &str {
method from (line 85) | fn from(event: CloseReason) -> Self {
method fmt (line 91) | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
FILE: socketio/src/lib.rs
constant SERVER_URL (line 207) | const SERVER_URL: &str = "http://localhost:4200";
function socket_io_server (line 209) | pub(crate) fn socket_io_server() -> Url {
constant AUTH_SERVER_URL (line 221) | const AUTH_SERVER_URL: &str = "http://localhost:4204";
function socket_io_auth_server (line 223) | pub(crate) fn socket_io_auth_server() -> Url {
constant RESTART_SERVER_URL (line 236) | const RESTART_SERVER_URL: &str = "http://localhost:4205";
function socket_io_restart_server (line 238) | pub(crate) fn socket_io_restart_server() -> Url {
constant RESTART_URL_AUTH_SERVER_URL (line 251) | const RESTART_URL_AUTH_SERVER_URL: &str = "http://localhost:4206";
function socket_io_restart_url_auth_server (line 253) | pub(crate) fn socket_io_restart_url_auth_server() -> Url {
FILE: socketio/src/packet.rs
type PacketId (line 12) | pub enum PacketId {
type Error (line 107) | type Error = Error;
method try_from (line 108) | fn try_from(b: u8) -> Result<Self> {
type Error (line 114) | type Error = Error;
method try_from (line 115) | fn try_from(b: char) -> Result<Self> {
type Packet (line 24) | pub struct Packet {
method new_from_payload (line 37) | pub(crate) fn new_from_payload<'a>(
method new (line 131) | pub const fn new(
type Error (line 203) | type Error = Error;
method try_from (line 204) | fn try_from(value: Bytes) -> Result<Self> {
type Error (line 210) | type Error = Error;
method try_from (line 218) | fn try_from(payload: &Bytes) -> Result<Packet> {
method default (line 94) | fn default() -> Self {
method from (line 151) | fn from(packet: Packet) -> Self {
method from (line 160) | fn from(packet: &Packet) -> Bytes {
function test_decode (line 287) | fn test_decode() {
function test_encode (line 456) | fn test_encode() {
function test_illegal_packet_id (line 606) | fn test_illegal_packet_id() {
function new_from_payload_binary (line 612) | fn new_from_payload_binary() {
function new_from_payload_string (line 632) | fn new_from_payload_string() {
function new_from_payload_json (line 655) | fn new_from_payload_json() {
FILE: socketio/src/payload.rs
type Payload (line 9) | pub enum Payload {
method string_to_value (line 18) | pub(crate) fn string_to_value(string: String) -> serde_json::Value {
method from (line 28) | fn from(string: &str) -> Self {
method from (line 34) | fn from(string: String) -> Self {
method from (line 40) | fn from(arr: Vec<String>) -> Self {
method from (line 46) | fn from(values: Vec<serde_json::Value>) -> Self {
method from (line 52) | fn from(value: serde_json::Value) -> Self {
method from (line 58) | fn from(val: Vec<u8>) -> Self {
method from (line 64) | fn from(val: &'static [u8]) -> Self {
method from (line 70) | fn from(bytes: Bytes) -> Self {
function test_from_string (line 82) | fn test_from_string() {
function test_from_multiple_strings (line 104) | fn test_from_multiple_strings() {
function test_from_multiple_json (line 122) | fn test_from_multiple_json() {
function test_from_json (line 129) | fn test_from_json() {
function test_from_binary (line 144) | fn test_from_binary() {
FILE: socketio/src/socket.rs
type Socket (line 13) | pub(crate) struct Socket {
method new (line 22) | pub(super) fn new(engine_client: EngineClient) -> Result<Self> {
method connect (line 31) | pub fn connect(&self) -> Result<()> {
method disconnect (line 43) | pub fn disconnect(&self) -> Result<()> {
method send (line 54) | pub fn send(&self, packet: Packet) -> Result<()> {
method emit (line 75) | pub fn emit(&self, nsp: &str, event: Event, data: Payload) -> Result<(...
method poll (line 81) | pub(crate) fn poll(&self) -> Result<Option<Packet>> {
method handle_socketio_packet (line 105) | fn handle_socketio_packet(&self, socket_packet: &Packet) {
method handle_engineio_packet (line 121) | fn handle_engineio_packet(&self, packet: EnginePacket) -> Result<Packe...
method is_engineio_connected (line 155) | fn is_engineio_connected(&self) -> Result<bool> {
Condensed preview — 85 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (404K chars).
[
{
"path": ".devcontainer/Dockerfile",
"chars": 215,
"preview": "FROM mcr.microsoft.com/vscode/devcontainers/rust:0-1\n\n# Install socat needed for TCP proxy\nRUN apt update && apt install"
},
{
"path": ".devcontainer/devcontainer.json",
"chars": 2124,
"preview": "// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:\n// https://github.co"
},
{
"path": ".devcontainer/docker-compose.yaml",
"chars": 2366,
"preview": "version: '3'\nservices:\n node-engine-io-secure:\n build:\n context: ../ci\n command: [ \"node\", \""
},
{
"path": ".github/dependabot.yml",
"chars": 359,
"preview": "version: 2\nupdates:\n - package-ecosystem: \"cargo\" # See documentation for possible values\n directory: \"/\" # Location"
},
{
"path": ".github/workflows/benchmark.yml",
"chars": 1861,
"preview": "on:\n pull_request:\n types: [opened]\n issue_comment:\n types: [created]\n\nname: benchmark engine.io\njobs:\n ru"
},
{
"path": ".github/workflows/build.yml",
"chars": 955,
"preview": "name: Build and code style\n\non:\n push:\n branches: [main, refactoring]\n pull_request:\n branches: [main]\n\nenv:\n C"
},
{
"path": ".github/workflows/coverage.yml",
"chars": 1772,
"preview": "on:\n push:\n branches: [main]\n pull_request:\n branches: [main]\n\nname: generate coverage\n\njobs:\n check:\n name:"
},
{
"path": ".github/workflows/publish-dry-run.yml",
"chars": 562,
"preview": "name: Publish dry run\n\non:\n workflow_dispatch\n\njobs:\n publish:\n name: Publish dry run\n runs-on: ubuntu-latest\n "
},
{
"path": ".github/workflows/publish.yml",
"chars": 622,
"preview": "name: Publish\n\non:\n workflow_dispatch\n\njobs:\n publish:\n name: Publish\n runs-on: ubuntu-latest\n steps:\n -"
},
{
"path": ".github/workflows/test.yml",
"chars": 1224,
"preview": "name: Test\n\non:\n push:\n branches: [main]\n pull_request:\n branches: [main, refactoring]\n\nenv:\n CARGO_TERM_COLOR:"
},
{
"path": ".gitignore",
"chars": 67,
"preview": "target\n.vscode\n.idea\nci/node_modules\nci/package-lock.json\nci/cert\n\n"
},
{
"path": "CHANGELOG.md",
"chars": 10657,
"preview": "# Changelog\n\nAll notable changes to this project are documented in this file.\n\nThe format is based on [Keep a Changelog]"
},
{
"path": "CONTRIBUTING.md",
"chars": 3147,
"preview": "# Introduction\n\nContributions to this project are welcome! \n\nThis project is still being developed, our goal is to have "
},
{
"path": "Cargo.toml",
"chars": 47,
"preview": "[workspace]\nmembers = [\"engineio\", \"socketio\"]\n"
},
{
"path": "LICENSE",
"chars": 1073,
"preview": "MIT License\n\nCopyright (c) 2021 Bastian Kersting\n\nPermission is hereby granted, free of charge, to any person obtaining "
},
{
"path": "Makefile",
"chars": 1035,
"preview": ".PHONY: build test-fast run-test-servers test-all clippy format checks pipeline\n\nbuild:\n\t@cargo build --verbose --all-fe"
},
{
"path": "README.md",
"chars": 7284,
"preview": "[](https://crates.io/crates/rust_socketio)\n[;\nconst http = r"
},
{
"path": "ci/engine-io-secure.js",
"chars": 1376,
"preview": "const fs = require('fs');\nconst https = require('https');\nconst eio = require('engine.io');\n\nconst serverOpts = {\n ke"
},
{
"path": "ci/engine-io.js",
"chars": 1128,
"preview": "/**\n * This is an example server, used to test the current code.\n */\nconst engine = require('engine.io');\nconst http = r"
},
{
"path": "ci/keygen.sh",
"chars": 1338,
"preview": "#!/bin/sh\ncd $(dirname $0)\n\nif [ \"$1\" = \"\" ] || [ \"$2\" = \"\" ]\nthen\n echo \"Usage: keygen.sh [DOMAIN] [IP]\"\n exit 1\n"
},
{
"path": "ci/package.json",
"chars": 246,
"preview": "{\n \"name\": \"rust-socketio-test\",\n \"version\": \"1.0.0\",\n \"description\": \"A test environment for a socketio client\",\n \""
},
{
"path": "ci/socket-io-auth.js",
"chars": 402,
"preview": "const server = require('http').createServer();\nconst io = require('socket.io')(server);\n\nconsole.log('Started');\nvar cal"
},
{
"path": "ci/socket-io-restart-url-auth.js",
"chars": 1098,
"preview": "let createServer = require(\"http\").createServer;\nlet server = createServer();\nconst io = require(\"socket.io\")(server);\nc"
},
{
"path": "ci/socket-io-restart.js",
"chars": 729,
"preview": "let createServer = require(\"http\").createServer;\nlet server = createServer();\nconst io = require(\"socket.io\")(server);\nc"
},
{
"path": "ci/socket-io.js",
"chars": 1995,
"preview": "const server = require('http').createServer();\nconst io = require('socket.io')(server);\n\nconsole.log('Started');\nvar cal"
},
{
"path": "ci/start_test_server.sh",
"chars": 1900,
"preview": "echo \"Starting test environment\"\nDEBUG=* node engine-io.js &\nstatus=$?\nif [ $status -ne 0 ]; then\n echo \"Failed to star"
},
{
"path": "codecov.yml",
"chars": 364,
"preview": "coverage:\n range: 50..90 # coverage lower than 50 is red, higher than 90 green, between color code\n\n status:\n proje"
},
{
"path": "engineio/Cargo.toml",
"chars": 1555,
"preview": "[package]\nname = \"rust_engineio\"\nversion = \"0.6.0\"\nauthors = [\"Bastian Kersting <bastian@cmbt.de>\"]\nedition = \"2021\"\ndes"
},
{
"path": "engineio/README.md",
"chars": 2995,
"preview": "[](https://crates.io/crates/rust_engineio)\n[ mod async_socket;\n#[cfg(feature = \"async-callbacks\")]\nmod callba"
},
{
"path": "engineio/src/asynchronous/transport.rs",
"chars": 2652,
"preview": "use crate::error::Result;\nuse adler32::adler32;\nuse async_trait::async_trait;\nuse bytes::Bytes;\nuse futures_util::Stream"
},
{
"path": "engineio/src/callback.rs",
"chars": 2381,
"preview": "use crate::Packet;\nuse bytes::Bytes;\nuse std::fmt::Debug;\nuse std::ops::Deref;\nuse std::sync::Arc;\n\npub(crate) type DynC"
},
{
"path": "engineio/src/client/client.rs",
"chars": 20000,
"preview": "use super::super::socket::Socket as InnerSocket;\nuse crate::callback::OptionalCallback;\nuse crate::socket::DEFAULT_MAX_P"
},
{
"path": "engineio/src/client/mod.rs",
"chars": 111,
"preview": "mod client;\npub use client::Iter;\npub use {client::Client, client::ClientBuilder, client::Iter as SocketIter};\n"
},
{
"path": "engineio/src/error.rs",
"chars": 3391,
"preview": "use base64::DecodeError;\nuse reqwest::Error as ReqwestError;\nuse serde_json::Error as JsonError;\nuse std::io::Error as I"
},
{
"path": "engineio/src/header.rs",
"chars": 3384,
"preview": "use crate::Error;\nuse bytes::Bytes;\nuse http::{\n header::HeaderName as HttpHeaderName, HeaderMap as HttpHeaderMap,\n "
},
{
"path": "engineio/src/lib.rs",
"chars": 5541,
"preview": "//! # Rust-engineio-client\n//!\n//! An implementation of a engine.io client written in the rust programming language. Thi"
},
{
"path": "engineio/src/packet.rs",
"chars": 10739,
"preview": "use base64::{engine::general_purpose, Engine as _};\nuse bytes::{BufMut, Bytes, BytesMut};\nuse serde::{Deserialize, Seria"
},
{
"path": "engineio/src/socket.rs",
"chars": 8124,
"preview": "use crate::callback::OptionalCallback;\nuse crate::transport::TransportType;\n\nuse crate::error::{Error, Result};\nuse crat"
},
{
"path": "engineio/src/transport.rs",
"chars": 2467,
"preview": "use super::transports::{PollingTransport, WebsocketSecureTransport, WebsocketTransport};\nuse crate::error::Result;\nuse a"
},
{
"path": "engineio/src/transports/mod.rs",
"chars": 195,
"preview": "mod polling;\nmod websocket;\nmod websocket_secure;\n\npub use self::polling::PollingTransport;\npub use self::websocket::Web"
},
{
"path": "engineio/src/transports/polling.rs",
"chars": 4612,
"preview": "use crate::error::{Error, Result};\nuse crate::transport::Transport;\nuse base64::{engine::general_purpose, Engine as _};\n"
},
{
"path": "engineio/src/transports/websocket.rs",
"chars": 4246,
"preview": "use crate::{\n asynchronous::{\n async_transports::WebsocketTransport as AsyncWebsocketTransport, transport::Asy"
},
{
"path": "engineio/src/transports/websocket_secure.rs",
"chars": 4418,
"preview": "use crate::{\n asynchronous::{\n async_transports::WebsocketSecureTransport as AsyncWebsocketSecureTransport,\n "
},
{
"path": "socketio/Cargo.toml",
"chars": 1395,
"preview": "[package]\nname = \"rust_socketio\"\nversion = \"0.6.0\"\nauthors = [\"Bastian Kersting <bastian@cmbt.de>\"]\nedition = \"2021\"\ndes"
},
{
"path": "socketio/examples/async.rs",
"chars": 2093,
"preview": "use futures_util::FutureExt;\nuse rust_socketio::{\n asynchronous::{Client, ClientBuilder},\n Payload,\n};\nuse serde_j"
},
{
"path": "socketio/examples/callback.rs",
"chars": 1923,
"preview": "use rust_socketio::{ClientBuilder, Event, Payload, RawClient};\nuse serde_json::json;\n\nfn handle_foo(payload: Payload, so"
},
{
"path": "socketio/examples/readme.rs",
"chars": 1707,
"preview": "use rust_socketio::{ClientBuilder, Payload, RawClient};\nuse serde_json::json;\nuse std::time::Duration;\n\nfn main() {\n "
},
{
"path": "socketio/examples/secure.rs",
"chars": 1036,
"preview": "use native_tls::Certificate;\nuse native_tls::TlsConnector;\nuse rust_socketio::ClientBuilder;\nuse std::fs::File;\nuse std:"
},
{
"path": "socketio/src/asynchronous/client/ack.rs",
"chars": 568,
"preview": "use std::time::Duration;\n\nuse crate::asynchronous::client::callback::Callback;\nuse tokio::time::Instant;\n\nuse super::cal"
},
{
"path": "socketio/src/asynchronous/client/builder.rs",
"chars": 17522,
"preview": "use futures_util::future::BoxFuture;\nuse log::trace;\nuse native_tls::TlsConnector;\nuse rust_engineio::{\n asynchronous"
},
{
"path": "socketio/src/asynchronous/client/callback.rs",
"chars": 2854,
"preview": "use futures_util::future::BoxFuture;\nuse std::{\n fmt::Debug,\n ops::{Deref, DerefMut},\n};\n\nuse crate::{Event, Paylo"
},
{
"path": "socketio/src/asynchronous/client/client.rs",
"chars": 43466,
"preview": "use std::{ops::DerefMut, pin::Pin, sync::Arc};\n\nuse backoff::{backoff::Backoff, ExponentialBackoffBuilder};\nuse futures_"
},
{
"path": "socketio/src/asynchronous/client/mod.rs",
"chars": 106,
"preview": "mod ack;\npub(crate) mod builder;\n#[cfg(feature = \"async-callbacks\")]\nmod callback;\npub(crate) mod client;\n"
},
{
"path": "socketio/src/asynchronous/generator.rs",
"chars": 1150,
"preview": "use std::{pin::Pin, sync::Arc};\n\nuse crate::error::Result;\nuse futures_util::{ready, FutureExt, Stream, StreamExt};\nuse "
},
{
"path": "socketio/src/asynchronous/mod.rs",
"chars": 1769,
"preview": "mod client;\nmod generator;\nmod socket;\n\n#[cfg(feature = \"async\")]\npub use client::builder::ClientBuilder;\npub use client"
},
{
"path": "socketio/src/asynchronous/socket.rs",
"chars": 6402,
"preview": "use super::generator::StreamGenerator;\nuse crate::{\n error::Result,\n packet::{Packet, PacketId},\n Error, Event,"
},
{
"path": "socketio/src/client/builder.rs",
"chars": 13114,
"preview": "use super::super::{event::Event, payload::Payload};\nuse super::callback::Callback;\nuse super::client::Client;\nuse crate:"
},
{
"path": "socketio/src/client/callback.rs",
"chars": 1841,
"preview": "use std::{\n fmt::Debug,\n ops::{Deref, DerefMut},\n};\n\nuse super::RawClient;\nuse crate::{Event, Payload};\n\npub(crate"
},
{
"path": "socketio/src/client/client.rs",
"chars": 16347,
"preview": "use std::{\n sync::{Arc, Mutex, RwLock},\n time::Duration,\n};\n\nuse super::{ClientBuilder, RawClient};\nuse crate::{\n "
},
{
"path": "socketio/src/client/mod.rs",
"chars": 203,
"preview": "mod builder;\nmod raw_client;\n\npub use builder::ClientBuilder;\npub use builder::TransportType;\npub use client::Client;\npu"
},
{
"path": "socketio/src/client/raw_client.rs",
"chars": 26225,
"preview": "use super::callback::Callback;\nuse crate::packet::{Packet, PacketId};\nuse crate::Error;\npub(crate) use crate::{event::Cl"
},
{
"path": "socketio/src/error.rs",
"chars": 2964,
"preview": "use base64::DecodeError;\nuse serde_json::Error as JsonError;\nuse std::io::Error as IoError;\nuse std::num::ParseIntError;"
},
{
"path": "socketio/src/event.rs",
"chars": 2745,
"preview": "use std::fmt::{Display, Formatter, Result as FmtResult};\n\n/// An `Event` in `socket.io` could either (`Message`, `Error`"
},
{
"path": "socketio/src/lib.rs",
"chars": 9255,
"preview": "//! Rust-socket.io is a socket.io client written in the Rust Programming Language.\n//! ## Example usage\n//!\n//! ``` rust"
},
{
"path": "socketio/src/packet.rs",
"chars": 19395,
"preview": "use crate::error::{Error, Result};\nuse crate::{Event, Payload};\nuse bytes::Bytes;\nuse serde::de::IgnoredAny;\n\nuse std::c"
},
{
"path": "socketio/src/payload.rs",
"chars": 4185,
"preview": "use bytes::Bytes;\n\n/// A type which represents a `payload` in the `socket.io` context.\n/// A payload could either be of "
},
{
"path": "socketio/src/socket.rs",
"chars": 5723,
"preview": "use crate::error::{Error, Result};\nuse crate::packet::{Packet, PacketId};\nuse bytes::Bytes;\nuse rust_engineio::{Client a"
}
]
About this extraction
This page contains the full source code of the 1c3t3a/rust-socketio GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 85 files (375.4 KB), approximately 90.6k tokens, and a symbol index with 584 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.