├── .cspell.yml
├── .envrc
├── .github
├── FUNDING.yml
├── dependabot.yml
└── workflows
│ ├── ci-nightly.yml
│ ├── ci.yml
│ └── clippy-fmt.yml
├── .gitignore
├── .prettierrc.yml
├── .rustfmt.toml
├── .sqlx
├── query-2f4086eab47b13298a107ed8c0feaadab903468b8e4fbd39e465f581c7189272.json
├── query-37443376215969e31bc6bc1ee00f01044647d95caf8efc61df7bf333b6a2149d.json
├── query-3a5b2a81088aa91ade3106165f926c337a88f4e29ae3bb170c4b687208aba20d.json
└── query-ab058898a8a1b13174d03c9972af33214619f8aa3080bc27f88bd5b9212b8c0f.json
├── .taplo.toml
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
├── auth
├── casbin
│ ├── Cargo.toml
│ ├── README.md
│ ├── rbac
│ │ ├── rbac_model.conf
│ │ └── rbac_policy.csv
│ └── src
│ │ └── main.rs
├── cookie-auth
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ └── main.rs
├── cookie-session
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ └── main.rs
├── oauth-github
│ └── README.md
├── redis-session
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ └── main.rs
└── simple-auth-server
│ ├── Cargo.toml
│ ├── README.md
│ ├── diesel.toml
│ ├── migrations
│ ├── .gitkeep
│ ├── 00000000000000_diesel_initial_setup
│ │ ├── down.sql
│ │ └── up.sql
│ ├── 2018-10-09-101948_users
│ │ ├── down.sql
│ │ └── up.sql
│ └── 2018-10-16-095633_invitations
│ │ ├── down.sql
│ │ └── up.sql
│ ├── src
│ ├── auth_handler.rs
│ ├── email_service.rs
│ ├── errors.rs
│ ├── invitation_handler.rs
│ ├── main.rs
│ ├── models.rs
│ ├── register_handler.rs
│ ├── schema.rs
│ └── utils.rs
│ └── static
│ ├── index.html
│ ├── main.css
│ ├── main.js
│ └── register.html
├── background-jobs
├── Cargo.toml
├── README.md
└── src
│ ├── ephemeral_jobs.rs
│ ├── main.rs
│ ├── persistent_jobs.rs
│ └── routes.rs
├── basics
├── basics
│ ├── Cargo.toml
│ ├── README.md
│ ├── src
│ │ └── main.rs
│ └── static
│ │ ├── 404.html
│ │ ├── actixLogo.png
│ │ ├── favicon.ico
│ │ └── welcome.html
├── error-handling
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ └── main.rs
├── hello-world
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ └── main.rs
├── nested-routing
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ ├── app_config.rs
│ │ ├── common.rs
│ │ ├── handlers
│ │ ├── mod.rs
│ │ ├── parts.rs
│ │ └── products.rs
│ │ ├── lib.rs
│ │ └── main.rs
├── state
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ └── main.rs
├── static-files
│ ├── Cargo.toml
│ ├── README.md
│ ├── src
│ │ └── main.rs
│ └── static
│ │ ├── images
│ │ └── logo.png
│ │ └── root
│ │ ├── css
│ │ └── example.css
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ └── js
│ │ └── example.js
└── todo
│ ├── .env
│ ├── .gitignore
│ ├── Cargo.toml
│ ├── README.md
│ ├── migrations
│ ├── 20220205163207_create_tasks_table.down.sql
│ └── 20220205163207_create_tasks_table.up.sql
│ ├── src
│ ├── api.rs
│ ├── db.rs
│ ├── main.rs
│ ├── model.rs
│ └── session.rs
│ ├── static
│ ├── css
│ │ ├── normalize.css
│ │ ├── skeleton.css
│ │ └── style.css
│ └── errors
│ │ ├── 400.html
│ │ ├── 404.html
│ │ └── 500.html
│ └── templates
│ └── index.html.tera
├── codecov.yml
├── cors
├── README.md
├── backend
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ ├── main.rs
│ │ └── user.rs
└── frontend
│ ├── .gitignore
│ ├── index.html
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ ├── app.vue
│ └── main.js
│ └── vite.config.js
├── data-factory
├── Cargo.toml
├── README.md
└── src
│ └── main.rs
├── databases
├── diesel-async
│ ├── Cargo.toml
│ ├── README.md
│ ├── diesel.toml
│ ├── migrations
│ │ ├── .keep
│ │ ├── 00000000000000_diesel_initial_setup
│ │ │ ├── down.sql
│ │ │ └── up.sql
│ │ └── 2025-01-18-144029_create_items
│ │ │ ├── down.sql
│ │ │ └── up.sql
│ └── src
│ │ ├── actions.rs
│ │ ├── main.rs
│ │ ├── models.rs
│ │ └── schema.rs
├── diesel
│ ├── .gitignore
│ ├── Cargo.toml
│ ├── README.md
│ ├── migrations
│ │ └── 20170124012402_create_users
│ │ │ ├── down.sql
│ │ │ └── up.sql
│ └── src
│ │ ├── actions.rs
│ │ ├── main.rs
│ │ ├── models.rs
│ │ └── schema.rs
├── mongodb
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ ├── main.rs
│ │ ├── model.rs
│ │ └── test.rs
├── mysql
│ ├── Cargo.toml
│ ├── README.md
│ ├── apis
│ │ ├── bank.md
│ │ ├── branch.md
│ │ ├── customer.md
│ │ └── teller.md
│ ├── sql
│ │ ├── 0_create_database.sql
│ │ ├── 1_bank_details.sql
│ │ ├── 2_branch_details.sql
│ │ ├── 3_customer_details.sql
│ │ └── 4_teller_details.sql
│ └── src
│ │ ├── main.rs
│ │ ├── models.rs
│ │ ├── persistence.rs
│ │ └── routes.rs
├── postgres
│ ├── .gitignore
│ ├── Cargo.toml
│ ├── README.md
│ ├── sql
│ │ ├── add_user.sql
│ │ ├── get_users.sql
│ │ └── schema.sql
│ └── src
│ │ ├── config.rs
│ │ ├── db.rs
│ │ ├── errors.rs
│ │ ├── main.rs
│ │ └── models.rs
├── rbatis
│ └── README.md
├── redis
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ └── main.rs
├── sea-orm
│ └── README.md
└── sqlite
│ ├── .gitignore
│ ├── Cargo.toml
│ ├── README.md
│ ├── db
│ ├── GHCND_documentation.pdf
│ ├── README.md
│ ├── db.sql
│ ├── nyc_centralpark_weather.csv
│ └── setup_db.sh
│ └── src
│ ├── db.rs
│ └── main.rs
├── docker
├── .dockerignore
├── Cargo.toml
├── Dockerfile
├── README.md
└── src
│ └── main.rs
├── examples-common
├── Cargo.toml
└── src
│ └── lib.rs
├── flake.lock
├── flake.nix
├── forms
├── form
│ ├── Cargo.toml
│ ├── README.md
│ ├── src
│ │ └── main.rs
│ └── static
│ │ └── form.html
├── multipart-s3
│ ├── .env.example
│ ├── .gitignore
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ ├── client.rs
│ │ ├── index.html
│ │ ├── main.rs
│ │ └── upload_file.rs
└── multipart
│ ├── .gitignore
│ ├── Cargo.toml
│ ├── LICENSE
│ ├── README.md
│ └── src
│ └── main.rs
├── graphql
├── async-graphql
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ ├── main.rs
│ │ └── star_wars
│ │ ├── mod.rs
│ │ └── model.rs
├── juniper-advanced
│ ├── .env.example
│ ├── Cargo.toml
│ ├── README.md
│ ├── mysql-schema.sql
│ └── src
│ │ ├── db.rs
│ │ ├── handlers.rs
│ │ ├── main.rs
│ │ └── schemas
│ │ ├── mod.rs
│ │ ├── product.rs
│ │ ├── root.rs
│ │ └── user.rs
└── juniper
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ ├── main.rs
│ └── schema.rs
├── guards
├── Cargo.toml
├── README.md
└── src
│ └── main.rs
├── http-proxy
├── Cargo.toml
├── README.md
└── src
│ └── main.rs
├── https-tls
├── acme-letsencrypt
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ └── main.rs
├── awc-https
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ └── main.rs
├── cert-watch
│ ├── Cargo.toml
│ ├── README.md
│ ├── cert.pem
│ ├── key.pem
│ └── src
│ │ └── main.rs
├── hot-reload
│ ├── Cargo.toml
│ ├── README.md
│ ├── cert.pem
│ ├── key.pem
│ └── src
│ │ └── main.rs
├── openssl
│ ├── Cargo.toml
│ ├── README.md
│ ├── cert.pem
│ ├── key.pem
│ └── src
│ │ └── main.rs
├── rustls-client-cert
│ ├── Cargo.toml
│ ├── README.md
│ ├── certs
│ │ ├── client-cert.pem
│ │ ├── client-key.pem
│ │ ├── rootCA-key.pem
│ │ ├── rootCA.pem
│ │ ├── server-cert.pem
│ │ └── server-key.pem
│ └── src
│ │ └── main.rs
└── rustls
│ ├── Cargo.toml
│ ├── README.md
│ ├── cert.pem
│ ├── key.pem
│ └── src
│ └── main.rs
├── json
├── json-decode-error
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ └── main.rs
├── json-error
│ ├── Cargo.toml
│ └── src
│ │ └── main.rs
├── json-validation
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ └── main.rs
├── json
│ ├── Cargo.toml
│ ├── README.md
│ ├── client.py
│ └── src
│ │ └── main.rs
└── jsonrpc
│ ├── Cargo.toml
│ ├── README.md
│ ├── src
│ ├── convention.rs
│ └── main.rs
│ └── tests
│ └── test_client.py
├── justfile
├── middleware
├── encrypted-payloads
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ └── main.rs
├── http-to-https
│ ├── Cargo.toml
│ ├── README.md
│ ├── cert.pem
│ ├── key.pem
│ └── src
│ │ └── main.rs
├── rate-limit
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ ├── main.rs
│ │ └── rate_limit.rs
├── request-extensions
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ ├── add_msg.rs
│ │ └── main.rs
└── various
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ ├── main.rs
│ ├── read_request_body.rs
│ ├── read_response_body.rs
│ ├── redirect.rs
│ └── simple.rs
├── protobuf
├── .gitignore
├── Cargo.toml
├── README.md
├── client.py
├── src
│ └── main.rs
├── test.proto
└── test_pb2.py
├── run-in-thread
├── Cargo.toml
└── src
│ └── main.rs
├── server-sent-events
├── Cargo.toml
├── README.md
├── benchmark.js
├── drain.js
└── src
│ ├── broadcast.rs
│ ├── index.html
│ └── main.rs
├── shutdown-server
├── Cargo.toml
├── README.md
└── src
│ └── main.rs
├── templating
├── askama
│ ├── Cargo.toml
│ ├── src
│ │ └── main.rs
│ └── templates
│ │ ├── index.html
│ │ └── user.html
├── fluent
│ ├── Cargo.toml
│ ├── README.md
│ ├── locales
│ │ ├── en
│ │ │ └── main.ftl
│ │ └── fr
│ │ │ └── main.ftl
│ ├── src
│ │ ├── lang_choice.rs
│ │ └── main.rs
│ └── templates
│ │ ├── error.html
│ │ ├── index.html
│ │ └── user.html
├── handlebars
│ ├── Cargo.toml
│ ├── README.md
│ ├── src
│ │ └── main.rs
│ └── templates
│ │ ├── error.html
│ │ ├── index.html
│ │ └── user.html
├── minijinja
│ ├── Cargo.toml
│ ├── README.md
│ ├── src
│ │ └── main.rs
│ └── templates
│ │ ├── error.html
│ │ ├── index.html
│ │ └── user.html
├── sailfish
│ ├── Cargo.toml
│ ├── README.md
│ ├── src
│ │ └── main.rs
│ └── templates
│ │ ├── actix.stpl
│ │ └── page.stpl
├── tera
│ ├── Cargo.toml
│ ├── README.md
│ ├── src
│ │ └── main.rs
│ └── templates
│ │ ├── error.html
│ │ ├── index.html
│ │ └── user.html
├── tinytemplate
│ ├── Cargo.toml
│ ├── README.md
│ ├── src
│ │ └── main.rs
│ └── templates
│ │ ├── error.html
│ │ ├── index.html
│ │ └── user.html
└── yarte
│ ├── Cargo.toml
│ ├── README.md
│ ├── build.rs
│ ├── src
│ └── main.rs
│ ├── templates
│ ├── base.hbs
│ ├── deep
│ │ └── more
│ │ │ ├── card
│ │ │ ├── form.hbs
│ │ │ └── hi.hbs
│ │ │ ├── deep
│ │ │ └── welcome.hbs
│ │ │ └── doc
│ │ │ ├── head.hbs
│ │ │ └── t.hbs
│ └── index.hbs
│ └── yarte.toml
├── tracing
└── mainmatter-workshop
│ ├── .env.example
│ ├── .gitignore
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ ├── logging.rs
│ ├── main.rs
│ ├── metric_names.rs
│ ├── middleware.rs
│ ├── prometheus.rs
│ └── routes.rs
├── unix-socket
├── Cargo.toml
├── README.md
└── src
│ └── main.rs
├── weather.db
└── websockets
├── autobahn
├── Cargo.toml
├── README.md
├── config
│ └── fuzzingclient.json
├── reports
│ └── .gitignore
└── src
│ └── main.rs
├── chat-actorless
├── Cargo.toml
├── README.md
├── src
│ ├── handler.rs
│ ├── main.rs
│ └── server.rs
└── static
│ └── index.html
├── chat-broker
├── Cargo.toml
├── README.md
├── client.py
├── src
│ ├── main.rs
│ ├── message.rs
│ ├── server.rs
│ └── session.rs
└── static
│ └── index.html
├── chat-tcp
├── Cargo.toml
├── README.md
├── client.py
├── src
│ ├── client.rs
│ ├── codec.rs
│ ├── main.rs
│ ├── server.rs
│ └── session.rs
└── static
│ └── index.html
├── chat
├── Cargo.toml
├── README.md
├── client.py
├── requirements.txt
├── src
│ ├── main.rs
│ ├── server.rs
│ └── session.rs
└── static
│ └── index.html
├── echo-actorless
├── Cargo.toml
├── README.md
├── src
│ ├── client.rs
│ ├── handler.rs
│ └── main.rs
└── static
│ └── index.html
└── echo
├── Cargo.toml
├── README.md
├── src
├── client.rs
├── main.rs
└── server.rs
├── static
└── index.html
└── websocket-client.py
/.cspell.yml:
--------------------------------------------------------------------------------
1 | version: "0.2"
2 | words:
3 | - actix
4 | - addrs
5 | - apalis
6 | - askama
7 | - autoclean
8 | - autoreload
9 | - binstall
10 | - casbin
11 | - chrono
12 | - clippy
13 | - deadpool
14 | - dotenv
15 | - dotenvy
16 | - graphiql
17 | - mainmatter
18 | - minijinja
19 | - nixpkgs
20 | - nocapture
21 | - oneshot
22 | - opentelemetry
23 | - pemfile
24 | - pkgs
25 | - prost
26 | - protobuf
27 | - ractor
28 | - reqwest
29 | - rustls
30 | - rustup
31 | - serde
32 | - sparklepost
33 | - sparkpost
34 | - sqlx
35 | - taplo
36 | - tera
37 | - tmpl
38 | - webpki
39 | - websockets
40 |
--------------------------------------------------------------------------------
/.envrc:
--------------------------------------------------------------------------------
1 | use flake
2 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [robjtede]
4 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: github-actions
4 | directory: /
5 | schedule:
6 | interval: weekly
7 | - package-ecosystem: cargo
8 | directory: /
9 | schedule:
10 | interval: weekly
11 | versioning-strategy: lockfile-only
12 | groups:
13 | aws:
14 | patterns:
15 | - aws-*
16 |
--------------------------------------------------------------------------------
/.github/workflows/clippy-fmt.yml:
--------------------------------------------------------------------------------
1 | name: lint
2 |
3 | on:
4 | pull_request:
5 | types: [opened, synchronize, reopened]
6 |
7 | jobs:
8 | fmt:
9 | name: rustfmt check
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - uses: actions/checkout@v4
14 |
15 | - name: Install Rust
16 | uses: actions-rust-lang/setup-rust-toolchain@v1.12.0
17 | with:
18 | toolchain: stable
19 | components: rustfmt
20 |
21 | - name: rustfmt
22 | uses: actions-rust-lang/rustfmt@v1
23 |
24 | clippy:
25 | name: clippy check
26 | runs-on: ubuntu-latest
27 |
28 | env:
29 | CI: 1
30 | CARGO_INCREMENTAL: false
31 |
32 | steps:
33 | - uses: actions/checkout@v4
34 |
35 | - name: Install Rust
36 | uses: actions-rust-lang/setup-rust-toolchain@v1.12.0
37 | with:
38 | toolchain: stable
39 | components: clippy
40 |
41 | # - name: Create test DBs
42 | # run: |
43 | # sudo apt-get update && sudo apt-get install sqlite3
44 | # cargo install sqlx-cli --no-default-features --features=rustls,sqlite
45 | # cd basics/todo
46 | # DATABASE_URL="sqlite://./todo.db" sqlx database create
47 | # chmod a+rwx todo.db
48 | # DATABASE_URL="sqlite://./todo.db" sqlx migrate run
49 |
50 | - name: clippy
51 | timeout-minutes: 30
52 | uses: giraffate/clippy-action@v1
53 | with:
54 | reporter: "github-pr-check"
55 | github_token: ${{ secrets.GITHUB_TOKEN }}
56 | clippy_flags: --workspace --all-features --tests
57 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Generated by Cargo
2 | # will have compiled files and executables
3 | **/target
4 |
5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
6 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
7 | **/Cargo.lock
8 | !/Cargo.lock
9 |
10 | # These are backup files generated by rustfmt
11 | **/*.rs.bk
12 |
13 | # intellij files
14 | **/.idea/**
15 |
16 | # direnv
17 | /.direnv/
18 |
19 | .history/
20 |
21 | # For multipart example
22 | upload.png
23 |
24 | # any dotenv files
25 | .env
26 |
27 | # file uploads
28 | /tmp
29 |
30 | # todo example database
31 | todo.db
32 | todo.db-*
33 |
--------------------------------------------------------------------------------
/.prettierrc.yml:
--------------------------------------------------------------------------------
1 | arrowParens: "avoid"
2 | semi: false
3 | overrides:
4 | - files: "*.md"
5 | options:
6 | printWidth: 9999
7 | proseWrap: "never"
8 |
--------------------------------------------------------------------------------
/.rustfmt.toml:
--------------------------------------------------------------------------------
1 | group_imports = "StdExternalCrate"
2 | imports_granularity = "Crate"
3 | use_field_init_shorthand = true
4 |
--------------------------------------------------------------------------------
/.sqlx/query-2f4086eab47b13298a107ed8c0feaadab903468b8e4fbd39e465f581c7189272.json:
--------------------------------------------------------------------------------
1 | {
2 | "db_name": "SQLite",
3 | "query": "\n DELETE FROM tasks\n WHERE id = $1\n ",
4 | "describe": {
5 | "columns": [],
6 | "parameters": {
7 | "Right": 1
8 | },
9 | "nullable": []
10 | },
11 | "hash": "2f4086eab47b13298a107ed8c0feaadab903468b8e4fbd39e465f581c7189272"
12 | }
13 |
--------------------------------------------------------------------------------
/.sqlx/query-37443376215969e31bc6bc1ee00f01044647d95caf8efc61df7bf333b6a2149d.json:
--------------------------------------------------------------------------------
1 | {
2 | "db_name": "SQLite",
3 | "query": "\n UPDATE tasks\n SET completed = NOT completed\n WHERE id = $1\n ",
4 | "describe": {
5 | "columns": [],
6 | "parameters": {
7 | "Right": 1
8 | },
9 | "nullable": []
10 | },
11 | "hash": "37443376215969e31bc6bc1ee00f01044647d95caf8efc61df7bf333b6a2149d"
12 | }
13 |
--------------------------------------------------------------------------------
/.sqlx/query-3a5b2a81088aa91ade3106165f926c337a88f4e29ae3bb170c4b687208aba20d.json:
--------------------------------------------------------------------------------
1 | {
2 | "db_name": "SQLite",
3 | "query": "\n SELECT *\n FROM tasks\n ",
4 | "describe": {
5 | "columns": [
6 | {
7 | "name": "id",
8 | "ordinal": 0,
9 | "type_info": "Int64"
10 | },
11 | {
12 | "name": "description",
13 | "ordinal": 1,
14 | "type_info": "Text"
15 | },
16 | {
17 | "name": "completed",
18 | "ordinal": 2,
19 | "type_info": "Bool"
20 | }
21 | ],
22 | "parameters": {
23 | "Right": 0
24 | },
25 | "nullable": [false, false, false]
26 | },
27 | "hash": "3a5b2a81088aa91ade3106165f926c337a88f4e29ae3bb170c4b687208aba20d"
28 | }
29 |
--------------------------------------------------------------------------------
/.sqlx/query-ab058898a8a1b13174d03c9972af33214619f8aa3080bc27f88bd5b9212b8c0f.json:
--------------------------------------------------------------------------------
1 | {
2 | "db_name": "SQLite",
3 | "query": "\n INSERT INTO tasks (description)\n VALUES ($1)\n ",
4 | "describe": {
5 | "columns": [],
6 | "parameters": {
7 | "Right": 1
8 | },
9 | "nullable": []
10 | },
11 | "hash": "ab058898a8a1b13174d03c9972af33214619f8aa3080bc27f88bd5b9212b8c0f"
12 | }
13 |
--------------------------------------------------------------------------------
/.taplo.toml:
--------------------------------------------------------------------------------
1 | exclude = ["target/*"]
2 | include = ["**/*.toml"]
3 |
4 | [formatting]
5 | column_width = 100
6 |
7 | [[rule]]
8 | include = ["**/Cargo.toml"]
9 | keys = [
10 | "dependencies",
11 | "*-dependencies",
12 | "workspace.dependencies",
13 | "workspace.*-dependencies",
14 | "target.*.dependencies",
15 | "target.*.*-dependencies",
16 | ]
17 | formatting.reorder_keys = true
18 |
19 | [[rule]]
20 | include = ["**/Cargo.toml"]
21 | keys = [
22 | "dependencies.*",
23 | "*-dependencies.*",
24 | "workspace.dependencies.*",
25 | "workspace.*-dependencies.*",
26 | "target.*.dependencies",
27 | "target.*.*-dependencies",
28 | ]
29 | formatting.reorder_keys = false
30 |
--------------------------------------------------------------------------------
/auth/casbin/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "actix-casbin-example"
3 | edition.workspace = true
4 | rust-version.workspace = true
5 |
6 | [dependencies]
7 | actix-web = { workspace = true }
8 | casbin = "2"
9 | env_logger = { workspace = true }
10 |
--------------------------------------------------------------------------------
/auth/casbin/README.md:
--------------------------------------------------------------------------------
1 | # Casbin
2 |
3 | Basic integration of [Casbin-RS](https://github.com/casbin/casbin-rs) with [RBAC](https://en.wikipedia.org/wiki/Role-based_access_control) for Actix Web.
4 |
5 | ## Usage
6 |
7 | ```sh
8 | cd auth/casbin
9 | ```
10 |
11 | Modify the files in the `rbac` directory and the code in the `src` directory as required.
12 |
13 | ## Running Server
14 |
15 | ```sh
16 | cd auth/casbin
17 | cargo run (or ``cargo watch -x run``)
18 |
19 | # Started http server: 127.0.0.1:8080
20 | ```
21 |
22 | In this example, you can get the:
23 |
24 | - successful result at [http://localhost:8080/success](http://localhost:8080/success) (accessible)
25 | - failed result at [http://localhost:8080/fail](http://localhost:8080/fail) (inaccessible, `ERR_EMPTY_RESPONSE`).
26 |
27 | ## Others
28 |
29 | - For more related examples of [Casbin-RS](https://github.com/casbin/casbin-rs):
30 |
--------------------------------------------------------------------------------
/auth/casbin/rbac/rbac_model.conf:
--------------------------------------------------------------------------------
1 | [request_definition]
2 | r = sub, obj, act
3 |
4 | [policy_definition]
5 | p = sub, obj, act
6 |
7 | [role_definition]
8 | g = _, _
9 |
10 | [policy_effect]
11 | e = some(where (p.eft == allow))
12 |
13 | [matchers]
14 | m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
--------------------------------------------------------------------------------
/auth/casbin/rbac/rbac_policy.csv:
--------------------------------------------------------------------------------
1 | p, alice, data1, read
2 | p, bob, data2, write
3 | p, data2_admin, data2, read
4 | p, data2_admin, data2, write
5 | g, alice, data2_admin
--------------------------------------------------------------------------------
/auth/cookie-auth/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "cookie-auth"
3 | edition.workspace = true
4 | rust-version.workspace = true
5 |
6 | [dependencies]
7 | actix-identity.workspace = true
8 | actix-session = { workspace = true, features = ["cookie-session"] }
9 | actix-web.workspace = true
10 |
11 | env_logger.workspace = true
12 | log.workspace = true
13 |
--------------------------------------------------------------------------------
/auth/cookie-auth/README.md:
--------------------------------------------------------------------------------
1 | # cookie-auth
2 |
3 | ```sh
4 | cd auth/cookie-auth
5 | cargo run
6 | # Starting http server: 127.0.0.1:8080
7 | ```
8 |
9 | Testing with cookie auth with [curl](https://curl.haxx.se).
10 |
11 | Login:
12 |
13 | curl -v -b "auth-example=user1" -X POST http://localhost:8080/login
14 | < HTTP/1.1 302 Found
15 | < set-cookie: auth-example=GRm2Vku0UpFbJ3CNTKbndzIYHVGi8wc8eoXm/Axtf2BO; HttpOnly; Path=/
16 | < location: /
17 |
18 | Uses a POST request with a Useridentity `user1`. A cookie is set and a redirect to home `/` follows.
19 |
20 | Get:
21 |
22 | Now with the cookie `auth-example` sent in a GET request, the `user1` is recognized.
23 |
24 | curl -v -b "auth-example=GRm2Vku0UpFbJ3CNTKbndzIYHVGi8wc8eoXm/Axtf2BO" http://localhost:8080/
25 | * Connected to localhost (127.0.0.1) port 8080 (#0)
26 | > GET / HTTP/1.1
27 | > Host: localhost:8080
28 | > Cookie: auth-example=GRm2Vku0UpFbJ3CNTKbndzIYHVGi8wc8eoXm/Axtf2BO
29 | >
30 | < HTTP/1.1 200 OK
31 | <
32 | Hello user1
33 |
--------------------------------------------------------------------------------
/auth/cookie-session/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "cookie-session"
3 | edition.workspace = true
4 | rust-version.workspace = true
5 |
6 | [dependencies]
7 | actix-web.workspace = true
8 | actix-session = { workspace = true, features = ["cookie-session"] }
9 | log.workspace = true
10 | env_logger.workspace = true
11 |
--------------------------------------------------------------------------------
/auth/cookie-session/README.md:
--------------------------------------------------------------------------------
1 | ## Cookie session example
2 |
3 | ```sh
4 | cd auth/cookie-session
5 | cargo run
6 | # Starting http server: 127.0.0.1:8080
7 | ```
8 |
--------------------------------------------------------------------------------
/auth/oauth-github/README.md:
--------------------------------------------------------------------------------
1 | # OAuth (GitHub)
2 |
3 | See .
4 |
5 | It is a sample app demonstrating GitHub OAuth login using Actix Web. It needs to live in it's own Git repo because Shuttle (the deployment service used) tries to deploy the whole examples repo if it were here.
6 |
7 | Live deployment test here: .
8 |
--------------------------------------------------------------------------------
/auth/redis-session/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "redis-session-example"
3 | edition.workspace = true
4 | rust-version.workspace = true
5 |
6 | [dependencies]
7 | actix-session = { workspace = true, features = ["redis-session"] }
8 | actix-web.workspace = true
9 |
10 | env_logger.workspace = true
11 | log.workspace = true
12 | serde.workspace = true
13 | serde_json.workspace = true
14 |
15 | [dev-dependencies]
16 | actix-test.workspace = true
17 | time.workspace = true
18 |
--------------------------------------------------------------------------------
/auth/redis-session/README.md:
--------------------------------------------------------------------------------
1 | # redis-session
2 |
3 | ```sh
4 | cd auth/redis-sessions
5 | cargo run
6 | # Starting http server: 127.0.0.1:8080
7 | ```
8 |
9 | ## Available Routes
10 |
11 | - [GET /](http://localhost:8080/)
12 | - [POST /do_something](http://localhost:8080/do_something)
13 | - [POST /login](http://localhost:8080/login)
14 | - [POST /logout](http://localhost:8080/logout)
15 |
--------------------------------------------------------------------------------
/auth/simple-auth-server/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "simple-auth-server"
3 | edition.workspace = true
4 | rust-version.workspace = true
5 |
6 | [dependencies]
7 | actix-identity.workspace = true
8 | actix-session = { workspace = true, features = ["cookie-session"] }
9 | actix-web.workspace = true
10 |
11 | chrono.workspace = true
12 | derive_more = { workspace = true, features = ["display"] }
13 | diesel = { version = "2", features = ["postgres", "r2d2", "uuid", "chrono"] }
14 | dotenvy.workspace = true
15 | env_logger.workspace = true
16 | log = "0.4"
17 | once_cell = "1"
18 | r2d2 = "0.8"
19 | rust-argon2 = "2"
20 | serde_json.workspace = true
21 | serde.workspace = true
22 | sparklepost = "0.5"
23 | temp-env.workspace = true
24 | time.workspace = true
25 | uuid.workspace = true
26 |
--------------------------------------------------------------------------------
/auth/simple-auth-server/diesel.toml:
--------------------------------------------------------------------------------
1 | # For documentation on how to configure this file,
2 | # see diesel.rs/guides/configuring-diesel-cli
3 |
4 | [print_schema]
5 | file = "src/schema.rs"
6 |
--------------------------------------------------------------------------------
/auth/simple-auth-server/migrations/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/actix/examples/c8a3c9bcc91186d72b8e37fb84eb929da5b9235b/auth/simple-auth-server/migrations/.gitkeep
--------------------------------------------------------------------------------
/auth/simple-auth-server/migrations/00000000000000_diesel_initial_setup/down.sql:
--------------------------------------------------------------------------------
1 | -- This file was automatically created by Diesel to setup helper functions
2 | -- and other internal bookkeeping. This file is safe to edit, any future
3 | -- changes will be added to existing projects as new migrations.
4 |
5 | DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass);
6 | DROP FUNCTION IF EXISTS diesel_set_updated_at();
7 |
--------------------------------------------------------------------------------
/auth/simple-auth-server/migrations/00000000000000_diesel_initial_setup/up.sql:
--------------------------------------------------------------------------------
1 | -- This file was automatically created by Diesel to setup helper functions
2 | -- and other internal bookkeeping. This file is safe to edit, any future
3 | -- changes will be added to existing projects as new migrations.
4 |
5 |
6 |
7 |
8 | -- Sets up a trigger for the given table to automatically set a column called
9 | -- `updated_at` whenever the row is modified (unless `updated_at` was included
10 | -- in the modified columns)
11 | --
12 | -- # Example
13 | --
14 | -- ```sql
15 | -- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW());
16 | --
17 | -- SELECT diesel_manage_updated_at('users');
18 | -- ```
19 | CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$
20 | BEGIN
21 | EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s
22 | FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl);
23 | END;
24 | $$ LANGUAGE plpgsql;
25 |
26 | CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$
27 | BEGIN
28 | IF (
29 | NEW IS DISTINCT FROM OLD AND
30 | NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at
31 | ) THEN
32 | NEW.updated_at := current_timestamp;
33 | END IF;
34 | RETURN NEW;
35 | END;
36 | $$ LANGUAGE plpgsql;
37 |
--------------------------------------------------------------------------------
/auth/simple-auth-server/migrations/2018-10-09-101948_users/down.sql:
--------------------------------------------------------------------------------
1 | -- This file should undo anything in `up.sql`
2 | DROP TABLE users;
3 |
--------------------------------------------------------------------------------
/auth/simple-auth-server/migrations/2018-10-09-101948_users/up.sql:
--------------------------------------------------------------------------------
1 | -- Your SQL goes here
2 | CREATE TABLE users (
3 | email VARCHAR(100) NOT NULL UNIQUE PRIMARY KEY,
4 | hash VARCHAR(122) NOT NULL, --argon hash
5 | created_at TIMESTAMP NOT NULL
6 | );
7 |
--------------------------------------------------------------------------------
/auth/simple-auth-server/migrations/2018-10-16-095633_invitations/down.sql:
--------------------------------------------------------------------------------
1 | -- This file should undo anything in `up.sql`
2 | DROP TABLE invitations;
3 |
--------------------------------------------------------------------------------
/auth/simple-auth-server/migrations/2018-10-16-095633_invitations/up.sql:
--------------------------------------------------------------------------------
1 | -- Your SQL goes here
2 | CREATE TABLE invitations (
3 | id UUID NOT NULL UNIQUE PRIMARY KEY,
4 | email VARCHAR(100) NOT NULL,
5 | expires_at TIMESTAMP NOT NULL
6 | );
7 |
--------------------------------------------------------------------------------
/auth/simple-auth-server/src/invitation_handler.rs:
--------------------------------------------------------------------------------
1 | use actix_web::{HttpResponse, web};
2 | use diesel::prelude::*;
3 | use serde::Deserialize;
4 |
5 | use crate::{
6 | email_service::send_invitation,
7 | models::{Invitation, Pool},
8 | };
9 |
10 | #[derive(Deserialize)]
11 | pub struct InvitationData {
12 | pub email: String,
13 | }
14 |
15 | pub async fn post_invitation(
16 | invitation_data: web::Json,
17 | pool: web::Data,
18 | ) -> Result {
19 | // run diesel blocking code
20 | web::block(move || create_invitation(invitation_data.into_inner().email, pool)).await??;
21 |
22 | Ok(HttpResponse::Ok().finish())
23 | }
24 |
25 | fn create_invitation(
26 | eml: String,
27 | pool: web::Data,
28 | ) -> Result<(), crate::errors::ServiceError> {
29 | let invitation = dbg!(query(eml, pool)?);
30 | send_invitation(&invitation)
31 | }
32 |
33 | /// Diesel query
34 | fn query(eml: String, pool: web::Data) -> Result {
35 | use crate::schema::invitations::dsl::invitations;
36 |
37 | let mut conn = pool.get().unwrap();
38 |
39 | let new_invitation = Invitation::from(eml);
40 |
41 | let inserted_invitation = diesel::insert_into(invitations)
42 | .values(&new_invitation)
43 | .get_result(&mut conn)?;
44 |
45 | Ok(inserted_invitation)
46 | }
47 |
--------------------------------------------------------------------------------
/auth/simple-auth-server/src/schema.rs:
--------------------------------------------------------------------------------
1 | table! {
2 | users (email) {
3 | email -> Varchar,
4 | hash -> Varchar,
5 | created_at -> Timestamp,
6 | }
7 | }
8 |
9 | table! {
10 | invitations (id) {
11 | id -> Uuid,
12 | email -> Varchar,
13 | expires_at -> Timestamp,
14 | }
15 | }
16 |
17 | allow_tables_to_appear_in_same_query!(users, invitations);
18 |
--------------------------------------------------------------------------------
/auth/simple-auth-server/src/utils.rs:
--------------------------------------------------------------------------------
1 | use once_cell::sync::Lazy;
2 |
3 | use crate::errors::ServiceError;
4 |
5 | pub static SECRET_KEY: Lazy =
6 | Lazy::new(|| std::env::var("SECRET_KEY").unwrap_or_else(|_| "0123".repeat(16)));
7 |
8 | const SALT: &[u8] = b"supersecuresalt";
9 |
10 | // PLEASE NOTE THIS IS ONLY FOR DEMO PLEASE DO MORE RESEARCH FOR PRODUCTION USE
11 | pub fn hash_password(password: &str) -> Result {
12 | let config = argon2::Config {
13 | secret: SECRET_KEY.as_bytes(),
14 | ..argon2::Config::rfc9106_low_mem()
15 | };
16 | argon2::hash_encoded(password.as_bytes(), SALT, &config).map_err(|err| {
17 | dbg!(err);
18 | ServiceError::InternalServerError
19 | })
20 | }
21 |
22 | pub fn verify(hash: &str, password: &str) -> Result {
23 | argon2::verify_encoded_ext(hash, password.as_bytes(), SECRET_KEY.as_bytes(), &[]).map_err(
24 | |err| {
25 | dbg!(err);
26 | ServiceError::Unauthorized
27 | },
28 | )
29 | }
30 |
31 | #[cfg(test)]
32 | mod tests {
33 | use actix_web::cookie::Key;
34 |
35 | use super::SECRET_KEY;
36 |
37 | #[test]
38 | fn secret_key_default() {
39 | temp_env::with_var("SECRET_KEY", None::<&str>, || {
40 | assert!(Key::try_from(SECRET_KEY.as_bytes()).is_ok());
41 | });
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/auth/simple-auth-server/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Actix Web - Auth App
7 |
8 |
9 |
10 |
11 |
12 |
13 |
Email Invitation
14 |
15 |
Please enter your email receive Invitation
16 |
17 |
18 |
19 |
20 |
21 |
32 |
--------------------------------------------------------------------------------
/auth/simple-auth-server/static/main.css:
--------------------------------------------------------------------------------
1 | /* CSSTerm.com Easy CSS login form */
2 |
3 | .login {
4 | width:600px;
5 | margin:auto;
6 | border:1px #CCC solid;
7 | padding:0px 30px;
8 | background-color: #3b6caf;
9 | color:#FFF;
10 | }
11 |
12 | .field {
13 | background: #1e4f8a;
14 | border:1px #03306b solid;
15 | padding:10px;
16 | margin:5px 25px;
17 | width:215px;
18 | color:#FFF;
19 | }
20 |
21 | .login h1, p, .chbox, .btn {
22 | margin-left:25px;
23 | color:#fff;
24 | }
25 |
26 | .btn {
27 | background-color: #00CCFF;
28 | border:1px #03306b solid;
29 | padding:10px 30px;
30 | font-weight:bold;
31 | margin:25px 25px;
32 | cursor: pointer;
33 | }
34 |
35 | .forgot {
36 | color:#fff;
37 | }
38 |
--------------------------------------------------------------------------------
/auth/simple-auth-server/static/main.js:
--------------------------------------------------------------------------------
1 | function post(url = ``, data = {}) {
2 | // Default options are marked with *
3 | return fetch(url, {
4 | method: "POST", // *GET, POST, PUT, DELETE, etc.
5 | mode: "cors", // no-cors, cors, *same-origin
6 | cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached
7 | headers: {
8 | "Content-Type": "application/json; charset=utf-8",
9 | },
10 | redirect: "follow", // manual, *follow, error
11 | referrer: "no-referrer", // no-referrer, *client
12 | body: JSON.stringify(data), // body data type must match "Content-Type" header
13 | }).then(response => response.json()) // parses response to JSON
14 | }
15 |
16 | // window.addEventListener('load', function() {
17 | // console.log('All assets are loaded');
18 | // console.log(getUrlVars());
19 | // });
20 |
--------------------------------------------------------------------------------
/auth/simple-auth-server/static/register.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Actix Web - Auth App
7 |
8 |
9 |
10 |
11 |
12 |
20 |
21 |
22 |
45 |
--------------------------------------------------------------------------------
/background-jobs/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "background-jobs"
3 | edition.workspace = true
4 | rust-version.workspace = true
5 |
6 | [dependencies]
7 | actix-web.workspace = true
8 |
9 | apalis = { version = "0.6", features = ["limit"] }
10 | apalis-redis = { version = "0.6" }
11 | chrono.workspace = true
12 | color-eyre.workspace = true
13 | dotenvy.workspace = true
14 | env_logger.workspace = true
15 | eyre.workspace = true
16 | log.workspace = true
17 | rand.workspace = true
18 | serde.workspace = true
19 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
20 | tokio-util.workspace = true
21 |
--------------------------------------------------------------------------------
/background-jobs/README.md:
--------------------------------------------------------------------------------
1 | # background-jobs
2 |
3 | ## Usage
4 |
5 | ### server
6 |
7 | ```sh
8 | cd background-jobs
9 | cargo run
10 | # starting HTTP server at http://localhost:8080
11 | ```
12 |
13 | ### Available Routes
14 |
15 | - [GET /cache](http://localhost:8080/cache)
16 | - [POST /cache](http://localhost:8080/cache)
17 | - [POST /email](http://localhost:8080/email)
18 | - [POST /email-spam](http://localhost:8080/email-spam)
19 |
--------------------------------------------------------------------------------
/basics/basics/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "example-basics"
3 | edition.workspace = true
4 | rust-version.workspace = true
5 |
6 | [dependencies]
7 | actix-files = { workspace = true }
8 | actix-session = { workspace = true, features = ["cookie-session"] }
9 | actix-web = { workspace = true }
10 | actix-web-lab = { workspace = true }
11 | async-stream = { workspace = true }
12 | examples-common = { workspace = true }
13 | tracing = { workspace = true }
14 |
--------------------------------------------------------------------------------
/basics/basics/README.md:
--------------------------------------------------------------------------------
1 | # Basics
2 |
3 | ## Usage
4 |
5 | ### Server
6 |
7 | ```sh
8 | cd basics/basics
9 | cargo run
10 | # Started http server: 127.0.0.1:8080
11 | ```
12 |
13 | ### Browser
14 |
15 | - [http://localhost:8080/async-body/bob](http://localhost:8080/async-body/bob)
16 | - [http://localhost:8080/user/bob/](http://localhost:8080/user/bob) text/plain download
17 | - [http://localhost:8080/test](http://localhost:8080/test) (return status switch GET or POST or other)
18 | - [http://localhost:8080/favicon](http://localhost:8080/favicon)
19 | - [http://localhost:8080/welcome](http://localhost:8080/static/welcome.html)
20 | - [http://localhost:8080/notexit](http://localhost:8080/static/404.html) display 404 page
21 | - [http://localhost:8080/error](http://localhost:8080/error) Panic after request
22 |
--------------------------------------------------------------------------------
/basics/basics/static/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | actix - basics
6 |
7 |
8 |
9 |
10 | back to home
11 | 404
12 |
13 |
14 |
--------------------------------------------------------------------------------
/basics/basics/static/actixLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/actix/examples/c8a3c9bcc91186d72b8e37fb84eb929da5b9235b/basics/basics/static/actixLogo.png
--------------------------------------------------------------------------------
/basics/basics/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/actix/examples/c8a3c9bcc91186d72b8e37fb84eb929da5b9235b/basics/basics/static/favicon.ico
--------------------------------------------------------------------------------
/basics/basics/static/welcome.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | actix - basics
6 |
7 |
8 |
9 |
10 | Welcome 
11 |
12 |
13 |
--------------------------------------------------------------------------------
/basics/error-handling/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "error-handling"
3 | edition.workspace = true
4 | rust-version.workspace = true
5 |
6 | [dependencies]
7 | actix-web.workspace = true
8 |
9 | derive_more = { workspace = true, features = ["display"] }
10 | env_logger.workspace = true
11 | log.workspace = true
12 | rand.workspace = true
13 | serde = { workspace = true, features = ["derive"] }
14 | serde_json.workspace = true
15 |
--------------------------------------------------------------------------------
/basics/error-handling/README.md:
--------------------------------------------------------------------------------
1 | This project illustrates custom error propagation through futures in Actix Web.
2 |
3 | ## Usage
4 |
5 | ### server
6 |
7 | ```sh
8 | cd basics/error-handling
9 | cargo run
10 | # starting HTTP server at http://localhost:8080
11 | ```
12 |
13 | ### web client
14 |
15 | - [http://localhost:8080/something](http://localhost:8080/something)
16 |
--------------------------------------------------------------------------------
/basics/hello-world/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "hello-world"
3 | edition.workspace = true
4 | rust-version.workspace = true
5 |
6 | [dependencies]
7 | actix-web.workspace = true
8 | env_logger.workspace = true
9 | log.workspace = true
10 |
--------------------------------------------------------------------------------
/basics/hello-world/README.md:
--------------------------------------------------------------------------------
1 | # hello-world
2 |
3 | ## Usage
4 |
5 | ### server
6 |
7 | ```sh
8 | cd basics/hello-world
9 | cargo run
10 | # starting HTTP server at http://localhost:8080
11 | ```
12 |
13 | ### web client
14 |
15 | - [http://localhost:8080](http://localhost:8080)
16 | - [http://localhost:8080/index.html](http://localhost:8080/index.html)
17 |
--------------------------------------------------------------------------------
/basics/hello-world/src/main.rs:
--------------------------------------------------------------------------------
1 | use actix_web::{App, HttpRequest, HttpServer, middleware, web};
2 |
3 | async fn index(req: HttpRequest) -> &'static str {
4 | println!("REQ: {req:?}");
5 | "Hello world!"
6 | }
7 |
8 | #[actix_web::main]
9 | async fn main() -> std::io::Result<()> {
10 | env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
11 |
12 | log::info!("starting HTTP server at http://localhost:8080");
13 |
14 | HttpServer::new(|| {
15 | App::new()
16 | // enable logger
17 | .wrap(middleware::Logger::default())
18 | .service(web::resource("/index.html").to(|| async { "Hello world!" }))
19 | .service(web::resource("/").to(index))
20 | })
21 | .bind(("127.0.0.1", 8080))?
22 | .run()
23 | .await
24 | }
25 |
26 | #[cfg(test)]
27 | mod tests {
28 | use actix_web::{Error, body::to_bytes, dev::Service, http, test};
29 |
30 | use super::*;
31 |
32 | #[actix_web::test]
33 | async fn test_index() -> Result<(), Error> {
34 | let app = App::new().route("/", web::get().to(index));
35 | let app = test::init_service(app).await;
36 |
37 | let req = test::TestRequest::get().uri("/").to_request();
38 | let resp = app.call(req).await?;
39 |
40 | assert_eq!(resp.status(), http::StatusCode::OK);
41 |
42 | let response_body = resp.into_body();
43 | assert_eq!(to_bytes(response_body).await?, r##"Hello world!"##);
44 |
45 | Ok(())
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/basics/nested-routing/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "nested-routing"
3 | edition.workspace = true
4 | rust-version.workspace = true
5 |
6 | [dependencies]
7 | actix-web.workspace = true
8 | env_logger.workspace = true
9 | log.workspace = true
10 | serde.workspace = true
11 |
--------------------------------------------------------------------------------
/basics/nested-routing/README.md:
--------------------------------------------------------------------------------
1 | This example illustrates how to use nested resource registration through application-level configuration.
2 | The endpoints do nothing.
3 |
4 | ## Usage
5 |
6 | ```sh
7 | cd basics/nested-routing
8 | cargo run
9 | # Started http server: 127.0.0.1:8080
10 | ```
11 |
12 | ### Available Routes
13 |
14 | - [POST /products](http://localhost:8080/products)
15 | - [GET /products](http://localhost:8080/products)
16 | - [GET /products/:product_id](http://localhost:8080/products/:product_id)
17 | - [DELETE /products/:product_id](http://localhost:8080/products/:product_id)
18 | - [GET /products/:product_id/parts](http://localhost:8080/products/:product_id/parts)
19 | - [POST /products/:product_id/parts](http://localhost:8080/products/:product_id/parts)
20 | - [GET /products/:product_id/parts/:part_id](http://localhost:8080/products/:product_id/parts/:part_id)
21 |
--------------------------------------------------------------------------------
/basics/nested-routing/src/app_config.rs:
--------------------------------------------------------------------------------
1 | use actix_web::web;
2 |
3 | use crate::handlers::{parts, products};
4 |
5 | pub fn config_app(cfg: &mut web::ServiceConfig) {
6 | // domain includes: /products/{product_id}/parts/{part_id}
7 | cfg.service(
8 | web::scope("/products")
9 | .service(
10 | web::resource("")
11 | .route(web::get().to(products::get_products))
12 | .route(web::post().to(products::add_product)),
13 | )
14 | .service(
15 | web::scope("/{product_id}")
16 | .service(
17 | web::resource("")
18 | .route(web::get().to(products::get_product_detail))
19 | .route(web::delete().to(products::remove_product)),
20 | )
21 | .service(
22 | web::scope("/parts")
23 | .service(
24 | web::resource("")
25 | .route(web::get().to(parts::get_parts))
26 | .route(web::post().to(parts::add_part)),
27 | )
28 | .service(
29 | web::resource("/{part_id}")
30 | .route(web::get().to(parts::get_part_detail))
31 | .route(web::delete().to(parts::remove_part)),
32 | ),
33 | ),
34 | ),
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/basics/nested-routing/src/common.rs:
--------------------------------------------------------------------------------
1 | use serde::{Deserialize, Serialize};
2 |
3 | #[derive(Deserialize, Serialize)]
4 | pub struct Product {
5 | id: Option,
6 | product_type: Option,
7 | name: Option,
8 | }
9 |
10 | #[derive(Deserialize, Serialize)]
11 | pub struct Part {
12 | id: Option,
13 | part_type: Option,
14 | name: Option,
15 | }
16 |
--------------------------------------------------------------------------------
/basics/nested-routing/src/handlers/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod parts;
2 | pub mod products;
3 |
--------------------------------------------------------------------------------
/basics/nested-routing/src/handlers/parts.rs:
--------------------------------------------------------------------------------
1 | use actix_web::{Error, HttpResponse, web};
2 |
3 | use crate::common::{Part, Product};
4 |
5 | pub async fn get_parts(_query: web::Query
16 |
17 |
18 |
--------------------------------------------------------------------------------
/templating/askama/templates/user.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Actix Web
6 |
7 |
8 | Hi, {{ name }}!
9 |
10 | {{ text }}
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/templating/fluent/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "templating-fluent"
3 | edition.workspace = true
4 | rust-version.workspace = true
5 |
6 | [dependencies]
7 | actix-web.workspace = true
8 | actix-web-lab.workspace = true
9 | env_logger.workspace = true
10 | fluent-templates = { version = "0.9", features = ["handlebars"] }
11 | handlebars = { version = "5", features = ["dir_source"] }
12 | serde.workspace = true
13 | serde_json.workspace = true
14 |
--------------------------------------------------------------------------------
/templating/fluent/README.md:
--------------------------------------------------------------------------------
1 | # Fluent Templates
2 |
3 | This is an example of how to integrate [Fluent Templates](https://crates.io/crates/fluent-templates) with Actix Web using the Handlebars templating engine.
4 |
5 | # Directory Structure
6 |
7 | - `src`: Rust source code for web server and endpoint configuration
8 | - `templates`: Handlebars templates (no text content is stored in the templates)
9 | - `locales`: Fluent files containing translations for English (en) and French (fr)
10 |
11 | ## Usage
12 |
13 | ```sh
14 | cd templating/fluent
15 | cargo run
16 | ```
17 |
18 | After starting the server, you may visit the following pages:
19 |
20 | - http://localhost:8080
21 | - http://localhost:8080/Alice/documents
22 | - http://localhost:8080/Bob/passwords
23 | - http://localhost:8080/some-non-existing-page - 404 error rendered using template
24 |
25 | This example implements language selection using the standard Accept-Language header, which is sent by browsers according to OS/browser settings. To view the translated pages, pass the Accept-Encoding header with `en` or `fr`. Values which do not have associated translation files will fall back to English.
26 |
27 | ```
28 | # using HTTPie
29 | http :8080/Alice/documents Accept-Language:fr
30 |
31 | # using cURL
32 | curl http://localhost:8080/Alice/documents -H 'accept-language: fr'
33 | ```
34 |
--------------------------------------------------------------------------------
/templating/fluent/locales/en/main.ftl:
--------------------------------------------------------------------------------
1 | home-title = Fluent Templates Example
2 | home-description = This is an example of how to use Fluent templates with Actix Web.
3 |
4 | user-title = { $name }'s Homepage
5 | user-welcome = Welcome back, { $name }
6 | user-data = Here's your { $data }.
7 |
8 | error-not-found = Page not found
9 |
--------------------------------------------------------------------------------
/templating/fluent/locales/fr/main.ftl:
--------------------------------------------------------------------------------
1 | home-title = Un Exemple de Fluent Templates
2 | home-description = Ceci est un exemple d'utilisation des modèles Fluent avec Actix Web.
3 |
4 | user-title = La page d'accueil de { $name }
5 | user-welcome = Bienvenue, { $name }
6 | user-data = Voici votre { $data }.
7 |
8 | error-not-found = Page non trouvée
9 |
--------------------------------------------------------------------------------
/templating/fluent/src/lang_choice.rs:
--------------------------------------------------------------------------------
1 | use std::{
2 | convert::Infallible,
3 | future::{Ready, ready},
4 | };
5 |
6 | use actix_web::{FromRequest, HttpMessage as _, HttpRequest, dev, http::header::AcceptLanguage};
7 | use fluent_templates::LanguageIdentifier;
8 | use serde::Serialize;
9 |
10 | /// A convenient extractor that finds the clients's preferred language based on an Accept-Language
11 | /// header and falls back to English if header is not found. Serializes easily in Handlebars data.
12 | #[derive(Debug, Serialize)]
13 | #[serde(transparent)]
14 | pub struct LangChoice(String);
15 |
16 | impl LangChoice {
17 | pub(crate) fn from_req(req: &HttpRequest) -> Self {
18 | let lang = req
19 | .get_header::()
20 | .and_then(|lang| lang.preference().into_item())
21 | .map_or_else(|| "en".to_owned(), |lang| lang.to_string());
22 |
23 | Self(lang)
24 | }
25 |
26 | pub fn lang_id(&self) -> LanguageIdentifier {
27 | // unwrap: lang ID should be valid given extraction method
28 | self.0.parse().unwrap()
29 | }
30 | }
31 |
32 | impl FromRequest for LangChoice {
33 | type Error = Infallible;
34 | type Future = Ready>;
35 |
36 | fn from_request(req: &HttpRequest, _pl: &mut dev::Payload) -> Self::Future {
37 | ready(Ok(Self::from_req(req)))
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/templating/fluent/templates/error.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ error }}
6 |
7 |
8 | {{ status_code }} {{ error }}
9 |
10 |
11 |
--------------------------------------------------------------------------------
/templating/fluent/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ fluent "home-title" }}
5 |
6 |
7 |
8 | {{ fluent "home-title" }}
9 | {{ fluent "home-description" }}
10 |
11 |
12 |
--------------------------------------------------------------------------------
/templating/fluent/templates/user.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ fluent "user-title" name=user }}
6 |
7 |
8 |
9 | {{ fluent "user-welcome" name=user }}
10 | {{ fluent "user-data" data=data }}
11 |
12 |
13 |
--------------------------------------------------------------------------------
/templating/handlebars/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "templating-handlebars"
3 | edition.workspace = true
4 | rust-version.workspace = true
5 |
6 | [dependencies]
7 | actix-web.workspace = true
8 | env_logger.workspace = true
9 | handlebars = { version = "5", features = ["dir_source"] }
10 | serde_json.workspace = true
11 |
--------------------------------------------------------------------------------
/templating/handlebars/README.md:
--------------------------------------------------------------------------------
1 | # Handlebars
2 |
3 | This is an example of how to use Actix Web with the [Handlebars](https://crates.io/crates/handlebars) templating language, which is currently the most popular crate that achieves this.
4 |
5 | ## Usage
6 |
7 | ```sh
8 | cd templating/handlebars
9 | cargo run
10 | ```
11 |
12 | After starting the server, you may visit the following pages:
13 |
14 | - http://localhost:8080
15 | - http://localhost:8080/Emma/documents
16 | - http://localhost:8080/Bob/passwords
17 | - http://localhost:8080/some-non-existing-page - 404 error rendered using template
18 |
--------------------------------------------------------------------------------
/templating/handlebars/templates/error.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{error}}
6 |
7 |
8 | {{status_code}} {{error}}
9 |
10 |
11 |
--------------------------------------------------------------------------------
/templating/handlebars/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{name}} Example
6 |
7 |
8 |
9 | {{name}} example
10 | This is an example of how to use {{name}} with Actix Web.
11 |
12 |
13 |
--------------------------------------------------------------------------------
/templating/handlebars/templates/user.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{user}}'s homepage
6 |
7 |
8 |
9 | Welcome back, {{user}}
10 | Here's your {{data}}.
11 |
12 |
13 |
--------------------------------------------------------------------------------
/templating/minijinja/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "templating-minijinja"
3 | edition.workspace = true
4 | rust-version.workspace = true
5 |
6 | [dependencies]
7 | actix-utils.workspace = true
8 | actix-web.workspace = true
9 | env_logger.workspace = true
10 | log.workspace = true
11 | minijinja = { version = "2", features = ["loader"] }
12 | minijinja-autoreload = "2"
13 |
--------------------------------------------------------------------------------
/templating/minijinja/README.md:
--------------------------------------------------------------------------------
1 | # MiniJinja
2 |
3 | Minimal example of using the template engine [MiniJinja](https://github.com/mitsuhiko/minijinja) that displays a form.
4 |
5 | ## Usage
6 |
7 | ```sh
8 | cd templating/minijinja
9 | TEMPLATE_AUTORELOAD=true cargo run
10 | ```
11 |
12 | -
13 | - - 404 page rendered using template
14 |
--------------------------------------------------------------------------------
/templating/minijinja/templates/error.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ error }}
6 |
7 |
8 | {{ status_code }} {{ error }}
9 |
10 |
11 |
--------------------------------------------------------------------------------
/templating/minijinja/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Actix Web
6 |
7 |
8 | Welcome!
9 |
10 |
What is your name?
11 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/templating/minijinja/templates/user.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Actix Web
6 |
7 |
8 | Hi, {{ name }}!
9 |
10 | {{ text }}
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/templating/sailfish/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "templating-sailfish"
3 | edition.workspace = true
4 | rust-version.workspace = true
5 |
6 | [dependencies]
7 | actix-web.workspace = true
8 | env_logger.workspace = true
9 | log.workspace = true
10 | sailfish = "0.8"
11 |
--------------------------------------------------------------------------------
/templating/sailfish/README.md:
--------------------------------------------------------------------------------
1 | # Sailfish
2 |
3 | This is an example of how to use Actix Web with the [Sailfish](https://sailfish.netlify.app/) templating language.
4 |
5 | ## Usage
6 |
7 | ```sh
8 | cd templating/sailfish
9 | cargo run
10 | ```
11 |
12 | After starting the server, you may visit the following pages:
13 |
14 | - http://localhost:8080/page-1
15 | - http://localhost:8080/page-678
16 | - http://localhost:8080/Username
17 |
--------------------------------------------------------------------------------
/templating/sailfish/src/main.rs:
--------------------------------------------------------------------------------
1 | use actix_web::{
2 | App, HttpServer, Responder, error, get,
3 | middleware::{Compress, Logger},
4 | web,
5 | };
6 | use sailfish::TemplateOnce;
7 |
8 | #[derive(TemplateOnce)]
9 | #[template(path = "actix.stpl")]
10 | struct Greet<'a> {
11 | name: &'a str,
12 | }
13 |
14 | #[derive(TemplateOnce)]
15 | #[template(path = "page.stpl")]
16 | struct Page<'a> {
17 | id: &'a i32,
18 | }
19 |
20 | #[get("/{name}")]
21 | async fn greet(params: web::Path<(String,)>) -> actix_web::Result {
22 | let body = Greet { name: ¶ms.0 }
23 | .render_once()
24 | .map_err(error::ErrorInternalServerError)?;
25 |
26 | Ok(web::Html::new(body))
27 | }
28 |
29 | #[get("/page-{id:\\d+}")]
30 | async fn page(params: web::Path<(i32,)>) -> actix_web::Result {
31 | let body = Page { id: ¶ms.0 }
32 | .render_once()
33 | .map_err(error::ErrorInternalServerError)?;
34 |
35 | Ok(web::Html::new(body))
36 | }
37 |
38 | #[get("/")]
39 | async fn hello() -> impl Responder {
40 | web::Html::new("Hello world!
".to_owned())
41 | }
42 |
43 | #[actix_web::main]
44 | async fn main() -> std::io::Result<()> {
45 | env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
46 |
47 | log::info!("starting HTTP server at http://localhost:8080");
48 |
49 | HttpServer::new(|| {
50 | App::new()
51 | .service(hello)
52 | .service(page)
53 | .service(greet)
54 | .wrap(Compress::default())
55 | .wrap(Logger::default())
56 | })
57 | .bind(("127.0.0.1", 8080))?
58 | .workers(1)
59 | .run()
60 | .await
61 | }
62 |
--------------------------------------------------------------------------------
/templating/sailfish/templates/actix.stpl:
--------------------------------------------------------------------------------
1 | Hello user, <%= name %>!!!
2 |
--------------------------------------------------------------------------------
/templating/sailfish/templates/page.stpl:
--------------------------------------------------------------------------------
1 | Page id is <%= id %>!!!
2 |
--------------------------------------------------------------------------------
/templating/tera/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "templating-tera"
3 | edition.workspace = true
4 | rust-version.workspace = true
5 |
6 | [dependencies]
7 | actix-web.workspace = true
8 | env_logger.workspace = true
9 | log.workspace = true
10 | tera = "1.8"
11 |
--------------------------------------------------------------------------------
/templating/tera/README.md:
--------------------------------------------------------------------------------
1 | # template_tera
2 |
3 | Minimal example of using the template [tera](https://github.com/Keats/tera) that displays a form.
4 |
5 | ## Usage
6 |
7 | ```sh
8 | cd templating/tera
9 | cargo run
10 | ```
11 |
12 | -
13 | - - 404 page rendered using template
14 |
--------------------------------------------------------------------------------
/templating/tera/templates/error.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ error }}
6 |
7 |
8 | {{ status_code }} {{ error }}
9 |
10 |
11 |
--------------------------------------------------------------------------------
/templating/tera/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Actix Web
6 |
7 |
8 | Welcome!
9 |
10 |
What is your name?
11 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/templating/tera/templates/user.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Actix Web
6 |
7 |
8 | Hi, {{ name }}!
9 |
10 | {{ text }}
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/templating/tinytemplate/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "templating-tinytemplate"
3 | edition.workspace = true
4 | rust-version.workspace = true
5 |
6 | [dependencies]
7 | actix-web.workspace = true
8 | env_logger.workspace = true
9 | log.workspace = true
10 | serde_json.workspace = true
11 | tinytemplate = "1.1"
12 |
--------------------------------------------------------------------------------
/templating/tinytemplate/README.md:
--------------------------------------------------------------------------------
1 | # template_tinytemplate
2 |
3 | See the documentation for the minimalist template engine [tiny_template](https://docs.rs/tinytemplate/1.1.0/tinytemplate/).
4 |
5 | ## Usage
6 |
7 | ### Server
8 |
9 | ```sh
10 | cd templating/tinytemplate
11 | cargo run # (or ``cargo watch -x run``)
12 | # Started http server: 127.0.0.1:8080
13 | ```
14 |
15 | ### Web Client
16 |
17 | - [http://localhost:8080](http://localhost:8080)
18 | - [http://localhost:8080/non-existing-page](http://localhost:8080/non-existing-page) - 404 page rendered using template
19 |
--------------------------------------------------------------------------------
/templating/tinytemplate/templates/error.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {error}
6 |
7 |
8 | {status_code} {error}
9 |
10 |
11 |
--------------------------------------------------------------------------------
/templating/tinytemplate/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Actix Web
6 |
7 |
8 | Welcome!
9 |
10 |
What is your name?
11 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/templating/tinytemplate/templates/user.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Actix Web
6 |
7 |
8 | Hi, {name}!
9 | {text}
10 |
11 |
12 |
--------------------------------------------------------------------------------
/templating/yarte/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "templating-yarte"
3 | edition.workspace = true
4 | rust-version.workspace = true
5 | publish = false
6 |
7 | [package.metadata.cargo-machete]
8 | ignored = [
9 | "yarte_helpers", # only used as build dependency
10 | ]
11 |
12 | [dependencies]
13 | actix-web.workspace = true
14 | derive_more = { workspace = true, features = ["display"] }
15 | env_logger.workspace = true
16 | log.workspace = true
17 | yarte = { version = "0.15", features = ["bytes-buf", "html-min"] }
18 |
19 | [build-dependencies]
20 | yarte_helpers = { version = "0.15", default-features = false, features = ["config"] }
21 |
--------------------------------------------------------------------------------
/templating/yarte/README.md:
--------------------------------------------------------------------------------
1 | # yarte
2 |
3 | Minimal example of using template [yarte](https://github.com/botika/yarte) that displays a form.
4 |
5 | ```sh
6 | cd templating/yarte
7 | cargo test
8 | cargo run
9 | ```
10 |
11 | > open `localhost:8080`
12 |
--------------------------------------------------------------------------------
/templating/yarte/build.rs:
--------------------------------------------------------------------------------
1 | fn main() {
2 | yarte_helpers::recompile::when_changed();
3 | }
4 |
--------------------------------------------------------------------------------
/templating/yarte/templates/base.hbs:
--------------------------------------------------------------------------------
1 | {{! Simple example !}}
2 | {{> doc/t }}
3 |
4 | {{> doc/head }}
5 |
6 | {{> @partial-block }}
7 |
8 |
9 |
--------------------------------------------------------------------------------
/templating/yarte/templates/deep/more/card/form.hbs:
--------------------------------------------------------------------------------
1 | {{!
2 | Form: What is your name?
3 | !}}
4 | {{> ../deep/welcome id = "welcome", tag = "h1", tail = '!' ~}}
5 |
6 |
What is your name?
7 |
16 |
17 |
--------------------------------------------------------------------------------
/templating/yarte/templates/deep/more/card/hi.hbs:
--------------------------------------------------------------------------------
1 | {{!
2 | Hi message:
3 | args:
4 | - lastname
5 | - name
6 | !}}
7 | Hi, {{ name }} {{ lastname }}!
8 | {{~> alias/welcome id = "hi", tag = 'p', tail = "" }}
9 |
--------------------------------------------------------------------------------
/templating/yarte/templates/deep/more/deep/welcome.hbs:
--------------------------------------------------------------------------------
1 | {{!
2 | Welcome card:
3 | args:
4 | - tag
5 | - id
6 | - tail
7 | !}}
8 | {{#unless tag.is_match(r"^p|(h[1-6])$") && !id.is_empty() }}
9 | {{$ "Need static args: tag: str /^h[1-6]$/, id: str" }}
10 | {{/unless }}
11 | <{{ tag }} id="{{ id }}" class="welcome">Welcome{{ tail }}{{ tag }}>
12 |
--------------------------------------------------------------------------------
/templating/yarte/templates/deep/more/doc/head.hbs:
--------------------------------------------------------------------------------
1 | {{# unless title.is_str() && !title.is_empty() }}
2 | {{$ "Need static args: title: str" }}
3 | {{/unless}}
4 |
5 |
6 | {{ title }}
7 |
8 |
--------------------------------------------------------------------------------
/templating/yarte/templates/deep/more/doc/t.hbs:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/templating/yarte/templates/index.hbs:
--------------------------------------------------------------------------------
1 | {{#> base title = "Actix Web" }}
2 | {{~#if let Some(name) = query.get("name") }}
3 | {{ let lastname = query.get("lastname").ok_or(MyErr("Bad query"))? }}
4 | {{> card/hi ~}}
5 | {{ else ~}}
6 | {{> card/form ~}}
7 | {{/if ~}}
8 | {{/base }}
9 |
--------------------------------------------------------------------------------
/templating/yarte/yarte.toml:
--------------------------------------------------------------------------------
1 | # root dir of templates
2 | [main]
3 | dir = "templates"
4 |
5 | # Alias for partials. In call, change the start of partial path with one of this, if exist.
6 | [partials]
7 | alias = "./deep/more/deep"
8 | doc = "./deep/more/doc"
9 | card = "./deep/more/card"
10 |
--------------------------------------------------------------------------------
/tracing/mainmatter-workshop/.env.example:
--------------------------------------------------------------------------------
1 | RUST_LOG="info,mainmatter_workshop=trace"
2 | HONEYCOMB_API_KEY="..."
3 |
--------------------------------------------------------------------------------
/tracing/mainmatter-workshop/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 |
--------------------------------------------------------------------------------
/tracing/mainmatter-workshop/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "tracing-mainmatter-workshop"
3 | edition.workspace = true
4 | rust-version.workspace = true
5 |
6 | [dependencies]
7 | actix-web.workspace = true
8 | dotenvy.workspace = true
9 | metrics = "0.23"
10 | metrics-exporter-prometheus = { version = "0.15", default-features = false }
11 | opentelemetry = "0.23"
12 | opentelemetry_sdk = { version = "0.23", features = ["rt-tokio-current-thread"] }
13 | opentelemetry-otlp = { version = "0.16", features = ["tls-roots"] }
14 | tonic = "0.11"
15 | tracing-actix-web = { version = "0.7.11", features = ["opentelemetry_0_23", "uuid_v7"] }
16 | tracing-opentelemetry = "0.24"
17 | tracing-subscriber.workspace = true
18 | tracing-bunyan-formatter = "0.3"
19 | tracing.workspace = true
20 |
--------------------------------------------------------------------------------
/tracing/mainmatter-workshop/README.md:
--------------------------------------------------------------------------------
1 | # Telemetry Workshop Solution
2 |
3 | ## Overview
4 |
5 | A solution to the capstone project at the end of [Mainmatter's telemetry workshop](https://github.com/mainmatter/rust-telemetry-workshop).
6 |
7 | As stated in the exercise brief, this example will:
8 |
9 | - Configure a `tracing` subscriber that exports data to both Honeycomb and stdout, in JSON format;
10 | - Configure a suitable panic hook;
11 | - Configure a `metric` recorder that exposes metric data at `/metrics`~~, using a different port than your API endpoints~~ (this example shows how to use the existing HTTP server);
12 | - Add one or more middleware that:
13 | - Create a top-level INFO span for each incoming request;
14 | - Track the number of concurrent requests using a gauge;
15 | - Track request duration using a histogram;
16 | - Track the number of handled requests
17 | - All metrics should include success/failure as a label.
18 |
19 | ## Usage
20 |
21 | ```console
22 | $ cd tracing/mainmatter-workshop
23 | $ cargo run
24 | ```
25 |
--------------------------------------------------------------------------------
/tracing/mainmatter-workshop/src/main.rs:
--------------------------------------------------------------------------------
1 | use std::io;
2 |
3 | use actix_web::{App, HttpServer, middleware::from_fn, web::ThinData};
4 | use tracing_actix_web::TracingLogger;
5 |
6 | mod logging;
7 | mod metric_names;
8 | mod middleware;
9 | mod prometheus;
10 | mod routes;
11 |
12 | #[actix_web::main]
13 | async fn main() -> io::Result<()> {
14 | dotenvy::dotenv().ok();
15 | logging::init();
16 | let handle = prometheus::init();
17 |
18 | HttpServer::new(move || {
19 | App::new()
20 | .app_data(ThinData(handle.clone()))
21 | .service(routes::hello)
22 | .service(routes::sleep)
23 | .service(routes::metrics)
24 | .wrap(from_fn(middleware::request_telemetry))
25 | .wrap(TracingLogger::default())
26 | })
27 | .workers(2)
28 | .bind(("127.0.0.1", 8080))?
29 | .run()
30 | .await?;
31 |
32 | actix_web::web::block(move || {
33 | opentelemetry::global::shutdown_tracer_provider();
34 | })
35 | .await
36 | .unwrap();
37 |
38 | Ok(())
39 | }
40 |
--------------------------------------------------------------------------------
/tracing/mainmatter-workshop/src/metric_names.rs:
--------------------------------------------------------------------------------
1 | pub(crate) const HISTOGRAM_HTTP_REQUEST_DURATION: &str = "http_request_duration";
2 | pub(crate) const GAUGE_HTTP_CONCURRENT_REQUESTS: &str = "http_concurrent_requests";
3 |
--------------------------------------------------------------------------------
/tracing/mainmatter-workshop/src/middleware.rs:
--------------------------------------------------------------------------------
1 | use std::time::Instant;
2 |
3 | use actix_web::{
4 | HttpMessage as _,
5 | body::MessageBody,
6 | dev::{ServiceRequest, ServiceResponse},
7 | http::header::{HeaderName, HeaderValue},
8 | middleware::Next,
9 | };
10 | use tracing_actix_web::RequestId;
11 |
12 | use crate::metric_names::*;
13 |
14 | pub(crate) async fn request_telemetry(
15 | req: ServiceRequest,
16 | next: Next,
17 | ) -> actix_web::Result> {
18 | let now = Instant::now();
19 |
20 | metrics::gauge!(GAUGE_HTTP_CONCURRENT_REQUESTS).increment(1);
21 |
22 | let mut res = next.call(req).await?;
23 |
24 | let req_id = res.request().extensions().get::().copied();
25 |
26 | if let Some(req_id) = req_id {
27 | res.headers_mut().insert(
28 | HeaderName::from_static("request-id"),
29 | // this unwrap never fails, since UUIDs are valid ASCII strings
30 | HeaderValue::from_str(&req_id.to_string()).unwrap(),
31 | );
32 | };
33 |
34 | let outcome = if res.status().is_success() || res.status().is_redirection() {
35 | "success"
36 | } else {
37 | "failure"
38 | };
39 |
40 | let diff = now.elapsed();
41 | metrics::histogram!(HISTOGRAM_HTTP_REQUEST_DURATION, "outcome" => outcome).record(diff);
42 |
43 | metrics::gauge!(GAUGE_HTTP_CONCURRENT_REQUESTS).decrement(1);
44 |
45 | Ok(res)
46 | }
47 |
--------------------------------------------------------------------------------
/tracing/mainmatter-workshop/src/prometheus.rs:
--------------------------------------------------------------------------------
1 | use std::array;
2 |
3 | use metrics::Unit;
4 | use metrics_exporter_prometheus::{PrometheusBuilder, PrometheusHandle};
5 |
6 | use crate::metric_names::*;
7 |
8 | pub(crate) fn init() -> PrometheusHandle {
9 | metrics::describe_histogram!(
10 | HISTOGRAM_HTTP_REQUEST_DURATION,
11 | Unit::Seconds,
12 | "Duration (in seconds) a request took to be processed"
13 | );
14 |
15 | PrometheusBuilder::new()
16 | .set_buckets_for_metric(
17 | metrics_exporter_prometheus::Matcher::Full(HISTOGRAM_HTTP_REQUEST_DURATION.to_owned()),
18 | &exp_buckets::<28>(0.001), // values from ~0.3ms -> ~33s
19 | )
20 | .unwrap()
21 | .install_recorder()
22 | .unwrap()
23 | }
24 |
25 | fn exp_buckets(base: f64) -> [f64; N] {
26 | const RATIO: f64 = 1.5;
27 | array::from_fn(|i| base * RATIO.powi(i as i32 - 3)).map(|val| (val * 1_e7).round() / 1_e7)
28 | }
29 |
--------------------------------------------------------------------------------
/tracing/mainmatter-workshop/src/routes.rs:
--------------------------------------------------------------------------------
1 | use std::time::Duration;
2 |
3 | use actix_web::{HttpResponse, Responder, get, web::ThinData};
4 | use metrics_exporter_prometheus::PrometheusHandle;
5 |
6 | #[get("/hello")]
7 | pub(crate) async fn hello() -> impl Responder {
8 | "Hello, World!"
9 | }
10 |
11 | #[get("/sleep")]
12 | pub(crate) async fn sleep() -> impl Responder {
13 | actix_web::rt::time::sleep(Duration::from_millis(500)).await;
14 | HttpResponse::Ok()
15 | }
16 |
17 | #[get("/metrics")]
18 | pub(crate) async fn metrics(metrics_handle: ThinData) -> impl Responder {
19 | metrics_handle.render()
20 | }
21 |
--------------------------------------------------------------------------------
/unix-socket/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "unix-socket"
3 | edition.workspace = true
4 | rust-version.workspace = true
5 |
6 | [dependencies]
7 | actix-web.workspace = true
8 | env_logger.workspace = true
9 | log.workspace = true
10 |
--------------------------------------------------------------------------------
/unix-socket/README.md:
--------------------------------------------------------------------------------
1 | ## Unix domain socket example
2 |
3 | ```sh
4 | cd unix-socket
5 | cargo run
6 |
7 | # in another shell
8 | curl --unix-socket /tmp/actix-uds.socket http://localhost/
9 | Hello world!
10 | ```
11 |
12 | Although this will only one thread for handling incoming connections according to the [documentation](https://actix.github.io/actix-web/actix_web/struct.HttpServer.html#method.bind_uds).
13 |
14 | And it does not delete the socket file (`/tmp/actix-uds.socket`) when stopping the server, so it will fail to start next time you run it unless you delete the socket file manually.
15 |
--------------------------------------------------------------------------------
/unix-socket/src/main.rs:
--------------------------------------------------------------------------------
1 | use actix_web::{App, HttpRequest, HttpServer, middleware, web};
2 |
3 | async fn index(_req: HttpRequest) -> &'static str {
4 | "Hello world!"
5 | }
6 |
7 | #[actix_web::main]
8 | #[cfg(unix)]
9 | async fn main() -> std::io::Result<()> {
10 | env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
11 |
12 | log::info!("starting HTTP server at unix:/tmp/actix-uds.socket");
13 |
14 | HttpServer::new(|| {
15 | App::new()
16 | // enable logger - always register Actix Web Logger middleware last
17 | .wrap(middleware::Logger::default())
18 | .service(web::resource("/index.html").route(web::get().to(|| async { "Hello world!" })))
19 | .service(web::resource("/").to(index))
20 | })
21 | .bind_uds("/tmp/actix-uds.socket")?
22 | .run()
23 | .await
24 | }
25 |
26 | #[cfg(not(unix))]
27 | fn main() -> std::io::Result<()> {
28 | log::info!("Example only runs on UNIX");
29 | Ok(())
30 | }
31 |
--------------------------------------------------------------------------------
/weather.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/actix/examples/c8a3c9bcc91186d72b8e37fb84eb929da5b9235b/weather.db
--------------------------------------------------------------------------------
/websockets/autobahn/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "websocket-autobahn"
3 | edition.workspace = true
4 | rust-version.workspace = true
5 |
6 | [[bin]]
7 | name = "websocket-autobahn-server"
8 | path = "src/main.rs"
9 |
10 | [dependencies]
11 | actix.workspace = true
12 | actix-web.workspace = true
13 | actix-web-actors.workspace = true
14 |
15 | env_logger.workspace = true
16 | log.workspace = true
17 |
--------------------------------------------------------------------------------
/websockets/autobahn/README.md:
--------------------------------------------------------------------------------
1 | # WebSocket Autobahn Test Server
2 |
3 | WebSocket server for the [Autobahn WebSocket protocol testsuite](https://github.com/crossbario/autobahn-testsuite).
4 |
5 | ## Usage
6 |
7 | ### Server
8 |
9 | ```sh
10 | cd websockets/autobahn
11 | cargo run
12 | ```
13 |
14 | ### Running Autobahn Test Suite
15 |
16 | Running the autobahn test suite is easiest using the docker image as explained on the [autobahn test suite repo](https://github.com/crossbario/autobahn-testsuite#using-the-testsuite-docker-image).
17 |
18 | After starting the server, in the same directory, run the test suite in "fuzzing client" mode:
19 |
20 | ```sh
21 | docker run -it --rm \
22 | -v "${PWD}/config:/config" \
23 | -v "${PWD}/reports:/reports" \
24 | --network host \
25 | --name autobahn \
26 | crossbario/autobahn-testsuite \
27 | wstest \
28 | --spec /config/fuzzingclient.json \
29 | --mode fuzzingclient
30 | ```
31 |
32 | Results are written to the `reports/servers` directory for viewing.
33 |
--------------------------------------------------------------------------------
/websockets/autobahn/config/fuzzingclient.json:
--------------------------------------------------------------------------------
1 | {
2 | "outdir": "./reports/servers",
3 | "servers": [
4 | {
5 | "agent": "actix-web-actors",
6 | "url": "ws://host.docker.internal:9001"
7 | }
8 | ],
9 | "cases": ["*"],
10 | "exclude-cases": [
11 | "9.*",
12 | "12.*",
13 | "13.*"
14 | ],
15 | "exclude-agent-cases": {}
16 | }
17 |
--------------------------------------------------------------------------------
/websockets/autobahn/reports/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/websockets/chat-actorless/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "websocket-chat-actorless-example"
3 | edition.workspace = true
4 | rust-version.workspace = true
5 |
6 | [dependencies]
7 | actix-files.workspace = true
8 | actix-web.workspace = true
9 | actix-ws.workspace = true
10 | env_logger.workspace = true
11 | futures-util.workspace = true
12 | log.workspace = true
13 | rand.workspace = true
14 | tokio = { workspace = true, features = ["rt", "time", "macros"] }
15 |
--------------------------------------------------------------------------------
/websockets/chat-actorless/README.md:
--------------------------------------------------------------------------------
1 | # WebSocket Chat (actor-less)
2 |
3 | > Multi-room WebSocket chat server using [`actix-ws`].
4 |
5 | ## Usage
6 |
7 | ### Server
8 |
9 | ```sh
10 | cd websockets/echo-actorless
11 | cargo run
12 | # starting HTTP server at http://localhost:8080
13 | ```
14 |
15 | ### Browser Client
16 |
17 | Go to in a browser.
18 |
19 | ### CLI Client
20 |
21 | ```sh
22 | # using `websocat` (https://github.com/vi/websocat)
23 | websocat -v --ping-interval=2 ws://127.0.0.1:8080/ws
24 | ```
25 |
26 | ## Chat Commands
27 |
28 | Once connected, the following slash commands can be sent:
29 |
30 | - `/list`: list all available rooms
31 | - `/join name`: join room, if room does not exist, create new one
32 | - `/name name`: set session name
33 |
34 | Sending a plain string will broadcast that message to all peers in same room.
35 |
36 | [`actix-ws`]: https://crates.io/crates/actix-ws
37 |
--------------------------------------------------------------------------------
/websockets/chat-actorless/static/index.html:
--------------------------------------------------------------------------------
1 | ../../chat/static/index.html
--------------------------------------------------------------------------------
/websockets/chat-broker/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "websocket-chat-broker"
3 | edition.workspace = true
4 | rust-version.workspace = true
5 |
6 | [[bin]]
7 | name = "server"
8 | path = "src/main.rs"
9 |
10 | [dependencies]
11 | actix.workspace = true
12 | actix-broker.workspace = true
13 | actix-files.workspace = true
14 | actix-web.workspace = true
15 | actix-web-actors.workspace = true
16 |
17 | env_logger.workspace = true
18 | log.workspace = true
19 | rand.workspace = true
20 |
--------------------------------------------------------------------------------
/websockets/chat-broker/README.md:
--------------------------------------------------------------------------------
1 | # Websocket chat broker example
2 |
3 | This is a different implementation of the [websocket chat example](https://github.com/actix/examples/tree/master/websockets/chat)
4 |
5 | Differences:
6 |
7 | - Chat Server Actor is a System Service that runs in the same thread as the HttpServer/WS Listener.
8 | - The [actix-broker](https://github.com/Chris-Ricketts/actix-broker) crate is used to facilitate the sending of some messages between the Chat Session and Server Actors where the session does not require a response.
9 | - The Client is not required to send Ping messages. The Chat Server Actor auto-clears dead sessions.
10 |
11 | Possible Improvements:
12 |
13 | - Could the Chat Server Actor be simultaneously a System Service (accessible to the Chat Session via the System Registry) and also run in a separate thread?
14 |
15 | ## Server
16 |
17 | Chat server listens for incoming tcp connections. Server can access several types of message:
18 |
19 | - `/list` - list all available rooms
20 | - `/join name` - join room, if room does not exist, create new one
21 | - `/name name` - set session name
22 | - `some message` - just string, send message to all peers in same room
23 |
24 | To start server use command: `cargo run`
25 |
26 | ## WebSocket Browser Client
27 |
28 | Open url: [http://localhost:8080/](http://localhost:8080/)
29 |
--------------------------------------------------------------------------------
/websockets/chat-broker/src/main.rs:
--------------------------------------------------------------------------------
1 | use actix_files::{Files, NamedFile};
2 | use actix_web::{App, Error, HttpRequest, HttpServer, Responder, middleware::Logger, web};
3 | use actix_web_actors::ws;
4 |
5 | mod message;
6 | mod server;
7 | mod session;
8 |
9 | use session::WsChatSession;
10 |
11 | async fn index() -> impl Responder {
12 | NamedFile::open_async("./static/index.html").await.unwrap()
13 | }
14 |
15 | async fn chat_ws(req: HttpRequest, stream: web::Payload) -> Result {
16 | ws::start(WsChatSession::default(), &req, stream)
17 | }
18 |
19 | // the actor-based WebSocket examples REQUIRE `actix_web::main` for actor support
20 | #[actix_web::main]
21 | async fn main() -> std::io::Result<()> {
22 | env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
23 |
24 | log::info!("starting HTTP server at http://localhost:8080");
25 |
26 | HttpServer::new(move || {
27 | App::new()
28 | .service(web::resource("/").to(index))
29 | .service(web::resource("/ws").to(chat_ws))
30 | .service(Files::new("/static", "./static"))
31 | .wrap(Logger::default())
32 | })
33 | .workers(2)
34 | .bind(("127.0.0.1", 8080))?
35 | .run()
36 | .await
37 | }
38 |
--------------------------------------------------------------------------------
/websockets/chat-broker/src/message.rs:
--------------------------------------------------------------------------------
1 | use actix::prelude::*;
2 |
3 | #[derive(Clone, Message)]
4 | #[rtype(result = "()")]
5 | pub struct ChatMessage(pub String);
6 |
7 | #[derive(Clone, Message)]
8 | #[rtype(result = "u64")]
9 | pub struct JoinRoom(pub String, pub Option, pub Recipient);
10 |
11 | #[derive(Clone, Message)]
12 | #[rtype(result = "()")]
13 | pub struct LeaveRoom(pub String, pub u64);
14 |
15 | #[derive(Clone, Message)]
16 | #[rtype(result = "Vec")]
17 | pub struct ListRooms;
18 |
19 | #[derive(Clone, Message)]
20 | #[rtype(result = "()")]
21 | pub struct SendMessage(pub String, pub u64, pub String);
22 |
--------------------------------------------------------------------------------
/websockets/chat-tcp/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "websocket-tcp-example"
3 | edition.workspace = true
4 | rust-version.workspace = true
5 |
6 | [[bin]]
7 | name = "websocket-tcp-server"
8 | path = "src/main.rs"
9 |
10 | [[bin]]
11 | name = "websocket-tcp-client"
12 | path = "src/client.rs"
13 |
14 | [dependencies]
15 | actix.workspace = true
16 | actix-codec.workspace = true
17 | actix-files.workspace = true
18 | actix-web.workspace = true
19 | actix-web-actors.workspace = true
20 |
21 | byteorder = "1.2"
22 | env_logger.workspace = true
23 | futures-util = { workspace = true, features = ["sink"] }
24 | log.workspace = true
25 | rand.workspace = true
26 | serde.workspace = true
27 | serde_json.workspace = true
28 | tokio = { workspace = true, features = ["full"] }
29 | tokio-stream = "0.1.8"
30 | tokio-util.workspace = true
31 |
--------------------------------------------------------------------------------
/websockets/chat-tcp/README.md:
--------------------------------------------------------------------------------
1 | # Websocket chat example
2 |
3 | This is extension of the [actix chat example](https://github.com/actix/examples/tree/HEAD/websockets/chat)
4 |
5 | Added features:
6 |
7 | - Browser WebSocket client
8 | - Chat server runs in separate thread
9 | - TCP listener runs in separate thread
10 |
11 | ## Server
12 |
13 | Chat server listens for incoming tcp connections. Server can access several types of message:
14 |
15 | - `/list` - list all available rooms
16 | - `/join name` - join room, if room does not exist, create new one
17 | - `/name name` - set session name
18 | - `some message` - just string, send message to all peers in same room
19 | - client has to send heartbeat `Ping` messages, if server does not receive a heartbeat message for 10 seconds connection gets dropped
20 |
21 | To start server run
22 |
23 | ```sh
24 | cd websockets/chat-tcp
25 | cargo run --bin websocket-tcp-server`
26 | ```
27 |
28 | If the current directory is not correct, the server will look for `index.html` in the wrong place.
29 |
30 | ## Client
31 |
32 | Client connects to server. Reads input from stdin and sends to server.
33 |
34 | To run client use command: `cargo run --bin websocket-tcp-client`
35 |
36 | ## WebSocket Browser Client
37 |
38 | Open url:
39 |
--------------------------------------------------------------------------------
/websockets/chat/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "websocket-example"
3 | edition.workspace = true
4 | rust-version.workspace = true
5 |
6 | [[bin]]
7 | name = "websocket-chat-server"
8 | path = "src/main.rs"
9 |
10 | [dependencies]
11 | actix.workspace = true
12 | actix-files.workspace = true
13 | actix-web.workspace = true
14 | actix-web-actors.workspace = true
15 |
16 | env_logger.workspace = true
17 | log.workspace = true
18 | rand.workspace = true
19 |
--------------------------------------------------------------------------------
/websockets/chat/requirements.txt:
--------------------------------------------------------------------------------
1 | aiohttp
2 |
--------------------------------------------------------------------------------
/websockets/echo-actorless/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "websockets-echo-actorless-example"
3 | edition.workspace = true
4 | rust-version.workspace = true
5 |
6 | [[bin]]
7 | name = "websocket-server"
8 | path = "src/main.rs"
9 |
10 | [[bin]]
11 | name = "websocket-client"
12 | path = "src/client.rs"
13 |
14 | [dependencies]
15 | actix-files.workspace = true
16 | actix-web.workspace = true
17 | actix-ws.workspace = true
18 | awc.workspace = true
19 | env_logger.workspace = true
20 | futures-util = { workspace = true, features = ["sink"] }
21 | log.workspace = true
22 | tokio = { workspace = true, features = ["rt", "time", "macros"] }
23 | tokio-stream.workspace = true
24 |
--------------------------------------------------------------------------------
/websockets/echo-actorless/README.md:
--------------------------------------------------------------------------------
1 | # Echo WebSocket (actor-less)
2 |
3 | Simple echo websocket server using [`actix-ws`].
4 |
5 | ## Usage
6 |
7 | ### Server
8 |
9 | ```sh
10 | cd websockets/echo-actorless
11 | cargo run --bin websocket-server
12 | # starting HTTP server at http://localhost:8080
13 | ```
14 |
15 | ### Browser Client
16 |
17 | Go to in a browser.
18 |
19 | ### rust client
20 |
21 | ```sh
22 | cd websockets/echo-actorless
23 | cargo run --bin websocket-client
24 | ```
25 |
26 | ### CLI Client
27 |
28 | ```sh
29 | # using `websocat` (https://github.com/vi/websocat)
30 | websocat -v --ping-interval=2 ws://127.0.0.1:8080/ws
31 | ```
32 |
33 | [`actix-ws`]: https://crates.io/crates/actix-ws
34 |
--------------------------------------------------------------------------------
/websockets/echo-actorless/static/index.html:
--------------------------------------------------------------------------------
1 | ../../echo/static/index.html
--------------------------------------------------------------------------------
/websockets/echo/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "websocket"
3 | edition.workspace = true
4 | rust-version.workspace = true
5 |
6 | [[bin]]
7 | name = "websocket-server"
8 | path = "src/main.rs"
9 |
10 | [[bin]]
11 | name = "websocket-client"
12 | path = "src/client.rs"
13 |
14 | [dependencies]
15 | actix-files.workspace = true
16 | actix-web.workspace = true
17 | actix-ws.workspace = true
18 | awc.workspace = true
19 |
20 | env_logger.workspace = true
21 | futures-util = { workspace = true, features = ["sink"] }
22 | log.workspace = true
23 | ractor = { version = "0.15", default-features = false }
24 | tokio = { workspace = true, features = ["full"] }
25 | tokio-stream.workspace = true
26 |
--------------------------------------------------------------------------------
/websockets/echo/README.md:
--------------------------------------------------------------------------------
1 | # websocket
2 |
3 | Simple echo websocket server.
4 |
5 | ## Usage
6 |
7 | ### server
8 |
9 | ```sh
10 | cd websockets/echo
11 | cargo run --bin websocket-server
12 | # Started http server: 127.0.0.1:8080
13 | ```
14 |
15 | ### web client
16 |
17 | - [http://localhost:8080/index.html](http://localhost:8080/index.html)
18 |
19 | ### rust client
20 |
21 | ```sh
22 | cd websockets/echo
23 | cargo run --bin websocket-client
24 | ```
25 |
26 | ### python client
27 |
28 | - `pip install aiohttp`
29 | - `python websocket-client.py`
30 |
31 | if ubuntu :
32 |
33 | - `pip3 install aiohttp`
34 | - `python3 websocket-client.py`
35 |
--------------------------------------------------------------------------------
/websockets/echo/src/client.rs:
--------------------------------------------------------------------------------
1 | ../../echo-actorless/src/client.rs
--------------------------------------------------------------------------------