├── .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 | 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>) -> Result { 6 | Ok(HttpResponse::Ok().finish()) 7 | } 8 | 9 | pub async fn add_part(_new_part: web::Json) -> Result { 10 | Ok(HttpResponse::Ok().finish()) 11 | } 12 | 13 | pub async fn get_part_detail(_id: web::Path) -> Result { 14 | Ok(HttpResponse::Ok().finish()) 15 | } 16 | 17 | pub async fn remove_part(_id: web::Path) -> Result { 18 | Ok(HttpResponse::Ok().finish()) 19 | } 20 | -------------------------------------------------------------------------------- /basics/nested-routing/src/handlers/products.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{Error, HttpResponse, web}; 2 | 3 | use crate::common::{Part, Product}; 4 | 5 | pub async fn get_products(_query: web::Query>) -> Result { 6 | Ok(HttpResponse::Ok().finish()) 7 | } 8 | 9 | pub async fn add_product(_new_product: web::Json) -> Result { 10 | Ok(HttpResponse::Ok().finish()) 11 | } 12 | 13 | pub async fn get_product_detail(_id: web::Path) -> Result { 14 | Ok(HttpResponse::Ok().finish()) 15 | } 16 | 17 | pub async fn remove_product(_id: web::Path) -> Result { 18 | Ok(HttpResponse::Ok().finish()) 19 | } 20 | 21 | #[cfg(test)] 22 | mod tests { 23 | use actix_web::{ 24 | App, 25 | dev::Service, 26 | http::{StatusCode, header}, 27 | test, 28 | }; 29 | 30 | use crate::app_config::config_app; 31 | 32 | #[actix_web::test] 33 | async fn test_add_product() { 34 | let app = test::init_service(App::new().configure(config_app)).await; 35 | 36 | let payload = r#"{"id":12345,"product_type":"fancy","name":"test"}"#.as_bytes(); 37 | 38 | let req = test::TestRequest::post() 39 | .uri("/products") 40 | .insert_header((header::CONTENT_TYPE, "application/json")) 41 | .set_payload(payload) 42 | .to_request(); 43 | 44 | let resp = app.call(req).await.unwrap(); 45 | 46 | assert_eq!(resp.status(), StatusCode::OK); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /basics/nested-routing/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod app_config; 2 | pub mod common; 3 | pub mod handlers; 4 | -------------------------------------------------------------------------------- /basics/nested-routing/src/main.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{App, HttpServer, middleware}; 2 | use nested_routing::app_config::config_app; 3 | 4 | #[actix_web::main] 5 | async fn main() -> std::io::Result<()> { 6 | env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); 7 | 8 | log::info!("starting HTTP server at http://localhost:8080"); 9 | 10 | HttpServer::new(|| { 11 | App::new() 12 | .configure(config_app) 13 | .wrap(middleware::Logger::default()) 14 | }) 15 | .bind(("127.0.0.1", 8080))? 16 | .run() 17 | .await 18 | } 19 | -------------------------------------------------------------------------------- /basics/state/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "state" 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/state/README.md: -------------------------------------------------------------------------------- 1 | # state 2 | 3 | ## Usage 4 | 5 | ### server 6 | 7 | ```sh 8 | cd basics/state 9 | cargo run 10 | # Started http server: 127.0.0.1:8080 11 | ``` 12 | 13 | ### web client 14 | 15 | - [http://localhost:8080/](http://localhost:8080/) 16 | -------------------------------------------------------------------------------- /basics/static-files/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "static-files" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | actix-web.workspace = true 8 | actix-files.workspace = true 9 | env_logger.workspace = true 10 | log.workspace = true 11 | -------------------------------------------------------------------------------- /basics/static-files/README.md: -------------------------------------------------------------------------------- 1 | # Static Files 2 | 3 | Demonstrates how to serve static files. Inside the `./static` folder you will find 2 subfolders: 4 | 5 | - `root`: A tree of files that will be served at the web root `/`. This includes the `css` and `js` folders, each containing an example file. 6 | - `images`: A list of images that will be served at `/images` path, with file listing enabled. 7 | 8 | ## Usage 9 | 10 | ```sh 11 | cd basics/static-files 12 | cargo run 13 | ``` 14 | 15 | ### Available Routes 16 | 17 | - [GET /](http://localhost:8080/) 18 | - [GET /images](http://localhost:8080/images) 19 | -------------------------------------------------------------------------------- /basics/static-files/src/main.rs: -------------------------------------------------------------------------------- 1 | use actix_files::Files; 2 | use actix_web::{App, HttpServer, middleware::Logger}; 3 | 4 | #[actix_web::main] 5 | async fn main() -> std::io::Result<()> { 6 | env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); 7 | 8 | log::info!("starting HTTP server at http://localhost:8080"); 9 | 10 | HttpServer::new(|| { 11 | App::new() 12 | // We allow the visitor to see an index of the images at `/images`. 13 | .service(Files::new("/images", "static/images/").show_files_listing()) 14 | // Serve a tree of static files at the web root and specify the index file. 15 | // Note that the root path should always be defined as the last item. The paths are 16 | // resolved in the order they are defined. If this would be placed before the `/images` 17 | // path then the service for the static images would never be reached. 18 | .service(Files::new("/", "./static/root/").index_file("index.html")) 19 | // Enable the logger. 20 | .wrap(Logger::default()) 21 | }) 22 | .bind(("127.0.0.1", 8080))? 23 | .run() 24 | .await 25 | } 26 | -------------------------------------------------------------------------------- /basics/static-files/static/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actix/examples/c8a3c9bcc91186d72b8e37fb84eb929da5b9235b/basics/static-files/static/images/logo.png -------------------------------------------------------------------------------- /basics/static-files/static/root/css/example.css: -------------------------------------------------------------------------------- 1 | body { 2 | text-align: center; 3 | } 4 | 5 | h1 { 6 | font-family: sans-serif; 7 | } 8 | 9 | img { 10 | transition: .5s ease-in-out; 11 | } 12 | -------------------------------------------------------------------------------- /basics/static-files/static/root/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actix/examples/c8a3c9bcc91186d72b8e37fb84eb929da5b9235b/basics/static-files/static/root/favicon.ico -------------------------------------------------------------------------------- /basics/static-files/static/root/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

Actix-web

11 | Actix logo 12 | 13 | 14 | -------------------------------------------------------------------------------- /basics/static-files/static/root/js/example.js: -------------------------------------------------------------------------------- 1 | jQuery(document).ready(function () { 2 | let rotation = 0 3 | jQuery("img").click(function () { 4 | rotation += 360 5 | jQuery("img").css({ transform: "rotate(" + rotation + "deg)" }) 6 | }) 7 | }) 8 | -------------------------------------------------------------------------------- /basics/todo/.env: -------------------------------------------------------------------------------- 1 | DATABASE_URL=sqlite://${CARGO_MANIFEST_DIR}/todo.db 2 | SQLX_OFFLINE=true 3 | RUST_LOG=info 4 | -------------------------------------------------------------------------------- /basics/todo/.gitignore: -------------------------------------------------------------------------------- 1 | todo.db 2 | todo.db-* 3 | -------------------------------------------------------------------------------- /basics/todo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "todo" 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 | 11 | dotenvy.workspace = true 12 | env_logger.workspace = true 13 | log.workspace = true 14 | serde.workspace = true 15 | sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "sqlite"] } 16 | tera = "1.5" 17 | -------------------------------------------------------------------------------- /basics/todo/README.md: -------------------------------------------------------------------------------- 1 | # Todo 2 | 3 | A simple Todo project using a SQLite database. 4 | 5 | ## Prerequisites 6 | 7 | - SQLite 3 8 | 9 | ## Change Into The Examples Workspace Root Directory 10 | 11 | All instructions assume you have changed into the examples workspace root: 12 | 13 | ```console 14 | $ pwd 15 | .../examples 16 | ``` 17 | 18 | ## Set Up The Database 19 | 20 | Install the [sqlx](https://github.com/launchbadge/sqlx/tree/HEAD/sqlx-cli) command-line tool with the required features: 21 | 22 | ```sh 23 | cargo install sqlx-cli --no-default-features --features=rustls,sqlite 24 | ``` 25 | 26 | Then to create and set-up the database run: 27 | 28 | ```sh 29 | sqlx database create --database-url=sqlite://./todo.db 30 | sqlx migrate run --database-url=sqlite://./todo.db 31 | ``` 32 | 33 | ## Run The Application 34 | 35 | Start the application with: 36 | 37 | ```sh 38 | cargo run --bin=todo 39 | ``` 40 | 41 | The app will be viewable in the browser at . 42 | 43 | ## Modifying The Example Database 44 | 45 | For simplicity, this example uses SQLx's offline mode. If you make any changes to the database schema, this must be turned off or the `sqlx-data.json` file regenerated using the following command: 46 | 47 | ```sh 48 | cargo sqlx prepare --database-url=sqlite://./todo.db 49 | ``` 50 | -------------------------------------------------------------------------------- /basics/todo/migrations/20220205163207_create_tasks_table.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE tasks; 2 | -------------------------------------------------------------------------------- /basics/todo/migrations/20220205163207_create_tasks_table.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE tasks ( 2 | id INTEGER PRIMARY KEY, 3 | description VARCHAR NOT NULL, 4 | completed BOOLEAN NOT NULL DEFAULT 'f' 5 | ); 6 | -------------------------------------------------------------------------------- /basics/todo/src/db.rs: -------------------------------------------------------------------------------- 1 | use sqlx::sqlite::{SqlitePool, SqlitePoolOptions}; 2 | 3 | use crate::model::{NewTask, Task}; 4 | 5 | pub async fn init_pool(database_url: &str) -> Result { 6 | SqlitePoolOptions::new() 7 | .acquire_timeout(std::time::Duration::from_secs(1)) 8 | .connect(database_url) 9 | .await 10 | } 11 | 12 | pub async fn get_all_tasks(pool: &SqlitePool) -> Result, &'static str> { 13 | Task::all(pool).await.map_err(|_| "Error retrieving tasks") 14 | } 15 | 16 | pub async fn create_task(todo: String, pool: &SqlitePool) -> Result<(), &'static str> { 17 | let new_task = NewTask { description: todo }; 18 | Task::insert(new_task, pool) 19 | .await 20 | .map(|_| ()) 21 | .map_err(|_| "Error inserting task") 22 | } 23 | 24 | pub async fn toggle_task(id: i32, pool: &SqlitePool) -> Result<(), &'static str> { 25 | Task::toggle_with_id(id, pool) 26 | .await 27 | .map(|_| ()) 28 | .map_err(|_| "Error toggling task completion") 29 | } 30 | 31 | pub async fn delete_task(id: i32, pool: &SqlitePool) -> Result<(), &'static str> { 32 | Task::delete_with_id(id, pool) 33 | .await 34 | .map(|_| ()) 35 | .map_err(|_| "Error deleting task") 36 | } 37 | -------------------------------------------------------------------------------- /basics/todo/src/session.rs: -------------------------------------------------------------------------------- 1 | use actix_session::Session; 2 | use actix_web::error::Result; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | const FLASH_KEY: &str = "flash"; 6 | 7 | pub fn set_flash(session: &Session, flash: FlashMessage) -> Result<()> { 8 | Ok(session.insert(FLASH_KEY, flash)?) 9 | } 10 | 11 | pub fn get_flash(session: &Session) -> Result> { 12 | Ok(session.get::(FLASH_KEY)?) 13 | } 14 | 15 | pub fn clear_flash(session: &Session) { 16 | session.remove(FLASH_KEY); 17 | } 18 | 19 | #[derive(Deserialize, Serialize)] 20 | pub struct FlashMessage { 21 | pub kind: String, 22 | pub message: String, 23 | } 24 | 25 | impl FlashMessage { 26 | pub fn success(message: &str) -> Self { 27 | Self { 28 | kind: "success".to_owned(), 29 | message: message.to_owned(), 30 | } 31 | } 32 | 33 | pub fn error(message: &str) -> Self { 34 | Self { 35 | kind: "error".to_owned(), 36 | message: message.to_owned(), 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /basics/todo/static/css/style.css: -------------------------------------------------------------------------------- 1 | .field-error { 2 | border: 1px solid #ff0000 !important; 3 | } 4 | 5 | .field-error-msg { 6 | color: #ff0000; 7 | display: block; 8 | margin: -10px 0 10px 0; 9 | } 10 | 11 | .field-success { 12 | border: 1px solid #5AB953 !important; 13 | } 14 | 15 | .field-success-msg { 16 | color: #5AB953; 17 | display: block; 18 | margin: -10px 0 10px 0; 19 | } 20 | 21 | span.completed { 22 | text-decoration: line-through; 23 | } 24 | 25 | form.inline { 26 | display: inline; 27 | } 28 | 29 | form.link, 30 | button.link { 31 | display: inline; 32 | color: #1EAEDB; 33 | border: none; 34 | outline: none; 35 | background: none; 36 | cursor: pointer; 37 | padding: 0; 38 | margin: 0 0 0 0; 39 | height: inherit; 40 | text-decoration: underline; 41 | font-size: inherit; 42 | text-transform: none; 43 | font-weight: normal; 44 | line-height: inherit; 45 | letter-spacing: inherit; 46 | } 47 | 48 | form.link:hover, button.link:hover { 49 | color: #0FA0CE; 50 | } 51 | 52 | button.small { 53 | height: 20px; 54 | padding: 0 10px; 55 | font-size: 10px; 56 | line-height: 20px; 57 | margin: 0 2.5px; 58 | } 59 | -------------------------------------------------------------------------------- /basics/todo/static/errors/400.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | The server could not understand the request (400) 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |

The server could not understand the request

18 |
19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /basics/todo/static/errors/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | The page you were looking for doesn't exist (404) 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |

The page you were looking for doesn't exist.

18 |

You may have mistyped the address or the page may have moved.

19 |
20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /basics/todo/static/errors/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Ooops (500) 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |

Ooops ...

18 |

How embarrassing!

19 |

Looks like something weird happened while processing your request.

20 |

Please try again in a few moments.

21 |
22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | threshold: 1% # make CI green 6 | patch: 7 | default: 8 | threshold: 1% # make CI green 9 | -------------------------------------------------------------------------------- /cors/README.md: -------------------------------------------------------------------------------- 1 | # Cross-Origin Resource Sharing (CORS) 2 | 3 | ## [Run Server](backend/README.md) 4 | 5 | ```sh 6 | cd cors/backend 7 | cargo run 8 | ``` 9 | 10 | ## Run Frontend 11 | 12 | In a separate terminal, also run: 13 | 14 | ```sh 15 | cd cors/frontend 16 | npm install 17 | npm run dev 18 | ``` 19 | 20 | then open browser at: http://localhost:8080 21 | -------------------------------------------------------------------------------- /cors/backend/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cors" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | actix-web.workspace = true 8 | actix-cors.workspace = true 9 | 10 | env_logger.workspace = true 11 | log.workspace = true 12 | serde.workspace = true 13 | -------------------------------------------------------------------------------- /cors/backend/README.md: -------------------------------------------------------------------------------- 1 | # actix_cors::Cors 2 | 3 | ## Running Server 4 | 5 | ```sh 6 | cd cors/backend 7 | cargo run 8 | # starting HTTP server at http://localhost:8080 9 | ``` 10 | 11 | ### web client 12 | 13 | - [http://localhost:8080/user/info](http://localhost:8080/user/info) 14 | ```json 15 | // payload structure 16 | { 17 | "username": "username", 18 | "email": "email", 19 | "password": "password", 20 | "confirm_password": "password" 21 | } 22 | ``` 23 | 24 | ## Others 25 | 26 | - For more related examples of [actix_cors](https://docs.rs/actix-cors/latest/actix_cors/struct.Cors.html) 27 | -------------------------------------------------------------------------------- /cors/backend/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use actix_cors::Cors; 4 | use actix_web::{App, HttpServer, http::header, middleware::Logger}; 5 | 6 | mod user; 7 | 8 | #[actix_web::main] 9 | async fn main() -> io::Result<()> { 10 | env_logger::init_from_env(env_logger::Env::default().default_filter_or("info")); 11 | 12 | log::info!("starting HTTP server at http://localhost:8080"); 13 | 14 | HttpServer::new(move || { 15 | App::new() 16 | .wrap( 17 | Cors::default() 18 | .allowed_origin("http://localhost:8081") 19 | .allowed_methods(vec!["GET", "POST"]) 20 | .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT]) 21 | .allowed_header(header::CONTENT_TYPE) 22 | .supports_credentials() 23 | .max_age(3600), 24 | ) 25 | .wrap(Logger::default()) 26 | .service(user::info) 27 | }) 28 | .bind(("127.0.0.1", 8080))? 29 | .workers(2) 30 | .run() 31 | .await 32 | } 33 | -------------------------------------------------------------------------------- /cors/backend/src/user.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{post, web}; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | #[derive(Deserialize, Serialize, Debug)] 5 | pub struct Info { 6 | username: String, 7 | email: String, 8 | password: String, 9 | confirm_password: String, 10 | } 11 | 12 | #[post("/user/info")] 13 | pub async fn info(info: web::Json) -> web::Json { 14 | println!("=========={info:?}========="); 15 | web::Json(Info { 16 | username: info.username.clone(), 17 | email: info.email.clone(), 18 | password: info.password.clone(), 19 | confirm_password: info.confirm_password.clone(), 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /cors/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | # Editor directories and files 18 | .idea 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /cors/frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | CORS Example 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /cors/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "actix-cors-example-ui", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite --port=8081", 7 | "build": "vite build", 8 | "preview": "vite preview --port=8081" 9 | }, 10 | "dependencies": { 11 | "vue": "^3.3.4" 12 | }, 13 | "devDependencies": { 14 | "@vitejs/plugin-vue": "^5.2.3", 15 | "vite": "^6.3.4" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /cors/frontend/src/main.js: -------------------------------------------------------------------------------- 1 | // import './assets/main.css' 2 | 3 | import { createApp } from "vue" 4 | import App from "./App.vue" 5 | 6 | createApp(App).mount("#app") 7 | -------------------------------------------------------------------------------- /cors/frontend/vite.config.js: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from "node:url" 2 | 3 | import { defineConfig } from "vite" 4 | import vue from "@vitejs/plugin-vue" 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | plugins: [vue()], 9 | resolve: { 10 | alias: { 11 | "@": fileURLToPath(new URL("./src", import.meta.url)), 12 | }, 13 | }, 14 | }) 15 | -------------------------------------------------------------------------------- /data-factory/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "async-data" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | actix-web.workspace = true 8 | 9 | env_logger.workspace = true 10 | log.workspace = true 11 | -------------------------------------------------------------------------------- /data-factory/README.md: -------------------------------------------------------------------------------- 1 | # Async Data Factory 2 | 3 | This is an example demonstrating the construction of async state with `App::data_factory`. 4 | 5 | ## Reason: 6 | 7 | Use of a `data_factory` would make sense in these situations: 8 | 9 | - When async state does not necessarily have to be shared between workers/threads. 10 | - When an async state would spawn tasks. If state was centralized there could be a possibility the tasks get an unbalanced distribution on the workers/threads. 11 | 12 | ## Context 13 | 14 | The real world difference can be vary by the work you are doing but in general it's a good idea to spread your similarly-expensive async tasks evenly between threads and have as little cross threads synchronization as possible. 15 | -------------------------------------------------------------------------------- /databases/diesel-async/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "db-diesel-async" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [features] 7 | postgres_tests = [] 8 | 9 | [dependencies] 10 | actix-web = { workspace = true } 11 | diesel = { version = "2", default-features = false, features = ["uuid"] } 12 | diesel-async = { version = "0.5", features = ["postgres", "bb8", "async-connection-wrapper"] } 13 | dotenvy = { workspace = true } 14 | env_logger = { workspace = true } 15 | log = { workspace = true } 16 | serde = { workspace = true } 17 | uuid = { workspace = true } 18 | -------------------------------------------------------------------------------- /databases/diesel-async/diesel.toml: -------------------------------------------------------------------------------- 1 | # For documentation on how to configure this file, 2 | # see https://diesel.rs/guides/configuring-diesel-cli 3 | 4 | [print_schema] 5 | file = "src/schema.rs" 6 | custom_type_derives = ["diesel::query_builder::QueryId", "Clone"] 7 | -------------------------------------------------------------------------------- /databases/diesel-async/migrations/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actix/examples/c8a3c9bcc91186d72b8e37fb84eb929da5b9235b/databases/diesel-async/migrations/.keep -------------------------------------------------------------------------------- /databases/diesel-async/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 | -------------------------------------------------------------------------------- /databases/diesel-async/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 | -- Sets up a trigger for the given table to automatically set a column called 7 | -- `updated_at` whenever the row is modified (unless `updated_at` was included 8 | -- in the modified columns) 9 | -- 10 | -- # Example 11 | -- 12 | -- ```sql 13 | -- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW()); 14 | -- 15 | -- SELECT diesel_manage_updated_at('users'); 16 | -- ``` 17 | CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS 18 | $$ 19 | BEGIN 20 | EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s 21 | FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl); 22 | END; 23 | $$ LANGUAGE plpgsql; 24 | 25 | CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS 26 | $$ 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 | -------------------------------------------------------------------------------- /databases/diesel-async/migrations/2025-01-18-144029_create_items/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS items; 2 | -------------------------------------------------------------------------------- /databases/diesel-async/migrations/2025-01-18-144029_create_items/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS items 2 | ( 3 | id uuid DEFAULT gen_random_uuid() PRIMARY KEY, 4 | name VARCHAR NOT NULL 5 | ); 6 | -------------------------------------------------------------------------------- /databases/diesel-async/src/models.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use uuid::Uuid; 3 | 4 | use super::schema::items; 5 | 6 | /// Item details. 7 | #[derive(Debug, Clone, Serialize, Deserialize, Queryable, Selectable, Insertable)] 8 | #[diesel(table_name = items)] 9 | pub struct Item { 10 | pub id: Uuid, 11 | pub name: String, 12 | } 13 | 14 | /// New item details. 15 | #[derive(Debug, Clone, Default, Serialize, Deserialize)] 16 | pub struct NewItem { 17 | pub name: String, 18 | } 19 | 20 | #[cfg(not(feature = "postgres_tests"))] 21 | impl NewItem { 22 | /// Constructs new item details from name. 23 | #[cfg(test)] // only needed in tests 24 | pub fn new(name: impl Into) -> Self { 25 | Self { name: name.into() } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /databases/diesel-async/src/schema.rs: -------------------------------------------------------------------------------- 1 | // @generated automatically by Diesel CLI. 2 | 3 | diesel::table! { 4 | items (id) { 5 | id -> Uuid, 6 | name -> Varchar, 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /databases/diesel/.gitignore: -------------------------------------------------------------------------------- 1 | test.db 2 | -------------------------------------------------------------------------------- /databases/diesel/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "db-diesel" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | actix-web.workspace = true 8 | diesel = { version = "2", features = ["sqlite", "r2d2", "uuid"] } 9 | dotenvy.workspace = true 10 | env_logger.workspace = true 11 | log.workspace = true 12 | serde.workspace = true 13 | uuid.workspace = true 14 | -------------------------------------------------------------------------------- /databases/diesel/migrations/20170124012402_create_users/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE users 2 | -------------------------------------------------------------------------------- /databases/diesel/migrations/20170124012402_create_users/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE users ( 2 | id VARCHAR NOT NULL PRIMARY KEY, 3 | name VARCHAR NOT NULL 4 | ) 5 | -------------------------------------------------------------------------------- /databases/diesel/src/actions.rs: -------------------------------------------------------------------------------- 1 | use diesel::prelude::*; 2 | use uuid::Uuid; 3 | 4 | use crate::models; 5 | 6 | type DbError = Box; 7 | 8 | /// Run query using Diesel to find user by uid and return it. 9 | pub fn find_user_by_uid( 10 | conn: &mut SqliteConnection, 11 | uid: Uuid, 12 | ) -> Result, DbError> { 13 | use crate::schema::users::dsl::*; 14 | 15 | let user = users 16 | .filter(id.eq(uid.to_string())) 17 | .first::(conn) 18 | .optional()?; 19 | 20 | Ok(user) 21 | } 22 | 23 | /// Run query using Diesel to insert a new database row and return the result. 24 | pub fn insert_new_user( 25 | conn: &mut SqliteConnection, 26 | nm: &str, // prevent collision with `name` column imported inside the function 27 | ) -> Result { 28 | // It is common when using Diesel with Actix Web to import schema-related 29 | // modules inside a function's scope (rather than the normal module's scope) 30 | // to prevent import collisions and namespace pollution. 31 | use crate::schema::users::dsl::*; 32 | 33 | let new_user = models::User { 34 | id: Uuid::new_v4().to_string(), 35 | name: nm.to_owned(), 36 | }; 37 | 38 | diesel::insert_into(users).values(&new_user).execute(conn)?; 39 | 40 | Ok(new_user) 41 | } 42 | -------------------------------------------------------------------------------- /databases/diesel/src/models.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use crate::schema::users; 4 | 5 | /// User details. 6 | #[derive(Debug, Clone, Serialize, Deserialize, Queryable, Insertable)] 7 | #[diesel(table_name = users)] 8 | pub struct User { 9 | pub id: String, 10 | pub name: String, 11 | } 12 | 13 | /// New user details. 14 | #[derive(Debug, Clone, Serialize, Deserialize)] 15 | pub struct NewUser { 16 | pub name: String, 17 | } 18 | 19 | impl NewUser { 20 | /// Constructs new user details from name. 21 | #[cfg(test)] // only needed in tests 22 | pub fn new(name: impl Into) -> Self { 23 | Self { name: name.into() } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /databases/diesel/src/schema.rs: -------------------------------------------------------------------------------- 1 | table! { 2 | users (id) { 3 | id -> Text, 4 | name -> Text, 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /databases/mongodb/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "db-mongo" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | actix-web.workspace = true 8 | mongodb = "3" 9 | serde.workspace = true 10 | -------------------------------------------------------------------------------- /databases/mongodb/README.md: -------------------------------------------------------------------------------- 1 | # MongoDB 2 | 3 | Simple example of MongoDB usage with Actix Web. For more information on the MongoDB Rust driver, visit the [documentation](https://docs.rs/mongodb/3) and [source code](https://github.com/mongodb/mongo-rust-driver). 4 | 5 | ## Usage 6 | 7 | ### Install MongoDB 8 | 9 | Visit the [MongoDB Download Center](https://www.mongodb.com/try) for instructions on how to use MongoDB Atlas or set up MongoDB locally. 10 | 11 | ### Set an environment variable 12 | 13 | The example code creates a client with the URI set by the `MONGODB_URI` environment variable. The default URI for a standalone `mongod` running on localhost is "mongodb://localhost:27017". For more information on MongoDB URIs, visit the [connection string](https://docs.mongodb.com/manual/reference/connection-string/) entry in the MongoDB manual. 14 | 15 | ### Run the example 16 | 17 | To execute the example code, run `cargo run` in the `databases/mongodb` directory. 18 | -------------------------------------------------------------------------------- /databases/mongodb/src/model.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] 4 | pub struct User { 5 | pub first_name: String, 6 | pub last_name: String, 7 | pub username: String, 8 | pub email: String, 9 | } 10 | -------------------------------------------------------------------------------- /databases/mongodb/src/test.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{ 2 | test::{TestRequest, call_and_read_body, call_and_read_body_json, init_service}, 3 | web::Bytes, 4 | }; 5 | 6 | use super::*; 7 | 8 | #[actix_web::test] 9 | #[ignore = "requires MongoDB instance running"] 10 | async fn test() { 11 | let uri = std::env::var("MONGODB_URI").unwrap_or_else(|_| "mongodb://localhost:27017".into()); 12 | 13 | let client = Client::with_uri_str(uri).await.expect("failed to connect"); 14 | 15 | // Clear any data currently in the users collection. 16 | client 17 | .database(DB_NAME) 18 | .collection::(COLL_NAME) 19 | .drop() 20 | .await 21 | .expect("drop collection should succeed"); 22 | 23 | let app = init_service( 24 | App::new() 25 | .app_data(web::Data::new(client)) 26 | .service(add_user) 27 | .service(get_user), 28 | ) 29 | .await; 30 | 31 | let user = User { 32 | first_name: "Jane".into(), 33 | last_name: "Doe".into(), 34 | username: "janedoe".into(), 35 | email: "example@example.com".into(), 36 | }; 37 | 38 | let req = TestRequest::post() 39 | .uri("/add_user") 40 | .set_form(&user) 41 | .to_request(); 42 | 43 | let response = call_and_read_body(&app, req).await; 44 | assert_eq!(response, Bytes::from_static(b"user added")); 45 | 46 | let req = TestRequest::get() 47 | .uri(&format!("/get_user/{}", &user.username)) 48 | .to_request(); 49 | 50 | let response: User = call_and_read_body_json(&app, req).await; 51 | assert_eq!(response, user); 52 | } 53 | -------------------------------------------------------------------------------- /databases/mysql/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mysql" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | actix-web.workspace = true 8 | derive_more = { workspace = true, features = ["display", "error", "from"] } 9 | dotenvy.workspace = true 10 | env_logger.workspace = true 11 | log.workspace = true 12 | mysql = "24" 13 | serde.workspace = true 14 | -------------------------------------------------------------------------------- /databases/mysql/apis/bank.md: -------------------------------------------------------------------------------- 1 | # Banks API 2 | 3 | All examples show cURL and [HTTPie](https://httpie.io/cli) snippets. 4 | 5 | ## Adding A Bank 6 | 7 | ```sh 8 | curl -d '{"bank_name":"Bank ABC","country":"Kenya"}' -H 'Content-Type: application/json' http://localhost:8080/bank 9 | 10 | http POST :8080/bank bank_name="Bank ABC" country="Kenya" 11 | ``` 12 | 13 | You should expect a 204 No Content response. 14 | 15 | ## Listing Banks 16 | 17 | ```sh 18 | curl http://localhost:8080/bank 19 | 20 | http :8080/bank 21 | ``` 22 | 23 | The response should be a 200 OK with the following JSON body: 24 | 25 | ```json 26 | { 27 | "bank_data": [ 28 | { 29 | "bank_name": "bank abc", 30 | "country": "kenya" 31 | } 32 | ] 33 | } 34 | ``` 35 | -------------------------------------------------------------------------------- /databases/mysql/apis/branch.md: -------------------------------------------------------------------------------- 1 | # Branches API 2 | 3 | All examples show cURL and [HTTPie](https://httpie.io/cli) snippets. 4 | 5 | ## Adding A Branch 6 | 7 | ```sh 8 | curl -d '{"branch_name":"HQ branch", "location":"Central Business District"}' -H 'Content-Type: application/json' http://localhost:8080/branch 9 | 10 | http POST :8080/branch branch_name="HQ branch" branch_name="Central Business District" 11 | ``` 12 | 13 | You should expect a 204 No Content response. 14 | 15 | ## Listing Branches 16 | 17 | ```sh 18 | curl http://localhost:8080/branch 19 | 20 | http :8080/branch 21 | ``` 22 | 23 | The response should be a 200 OK with the following JSON body: 24 | 25 | ```json 26 | { 27 | "branch_data": [ 28 | { 29 | "branch_name": "hq branch", 30 | "location": "central business district" 31 | } 32 | ] 33 | } 34 | ``` 35 | -------------------------------------------------------------------------------- /databases/mysql/apis/customer.md: -------------------------------------------------------------------------------- 1 | # Customers API 2 | 3 | All examples show cURL and [HTTPie](https://httpie.io/cli) snippets. 4 | 5 | ## Adding A Customer 6 | 7 | ```sh 8 | curl -d '{"customer_name":"Peter Paul", "branch_name":"Central Business District"}' -H 'Content-Type: application/json' http://localhost:8080/customer 9 | 10 | http POST :8080/customer customer_name="Peter Paul" branch_name="Central Business District" 11 | ``` 12 | 13 | You should expect a 204 No Content response. 14 | 15 | ## Listing Customers 16 | 17 | ```sh 18 | curl http://localhost:8080/customer 19 | 20 | http :8080/customer 21 | ``` 22 | 23 | The response should be a 200 OK with the following JSON body: 24 | 25 | ```json 26 | { 27 | "customer_data": [ 28 | { 29 | "customer_name": "peter paul", 30 | "branch_name": "central business district" 31 | } 32 | ] 33 | } 34 | ``` 35 | -------------------------------------------------------------------------------- /databases/mysql/apis/teller.md: -------------------------------------------------------------------------------- 1 | # Tellers API 2 | 3 | All examples show cURL and [HTTPie](https://httpie.io/cli) snippets. 4 | 5 | ## Adding A Teller 6 | 7 | ```sh 8 | curl -d '{"teller_name":"John Doe", "branch_name":"Central Business District"}' -H 'Content-Type: application/json' http://localhost:8080/teller 9 | 10 | http POST :8080/teller teller_name="John Doe" branch_name="Central Business District" 11 | ``` 12 | 13 | You should expect a 204 No Content response. 14 | 15 | ## Listing Tellers 16 | 17 | ```sh 18 | curl http://localhost:8080/teller 19 | 20 | http :8080/teller 21 | ``` 22 | 23 | The response should be a 200 OK with the following JSON body: 24 | 25 | ```json 26 | { 27 | "teller_data": [ 28 | { 29 | "teller_name": "john doe", 30 | "branch_name": "central business district" 31 | } 32 | ] 33 | } 34 | ``` 35 | -------------------------------------------------------------------------------- /databases/mysql/sql/0_create_database.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE my_bank CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 2 | -------------------------------------------------------------------------------- /databases/mysql/sql/1_bank_details.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `bank_details` ( 2 | `id` BIGINT AUTO_INCREMENT PRIMARY KEY, 3 | `bank_name` VARCHAR(30) NOT NULL, 4 | `country` VARCHAR(30) NOT NULL, 5 | `date_added` DATETIME DEFAULT CURRENT_TIMESTAMP 6 | ) CHARSET=utf8mb4; 7 | -------------------------------------------------------------------------------- /databases/mysql/sql/2_branch_details.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `branch_details` ( 2 | `id` BIGINT AUTO_INCREMENT PRIMARY KEY, 3 | `branch_name` VARCHAR(30) NOT NULL, 4 | `location` VARCHAR(30) NOT NULL, 5 | `date_added` DATETIME DEFAULT CURRENT_TIMESTAMP 6 | ) CHARSET=utf8mb4; 7 | -------------------------------------------------------------------------------- /databases/mysql/sql/3_customer_details.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `customer_details` ( 2 | `id` BIGINT AUTO_INCREMENT PRIMARY KEY, 3 | `customer_name` VARCHAR(100) NOT NULL, 4 | `branch_name` VARCHAR(30) NOT NULL, 5 | `date_added` DATETIME DEFAULT CURRENT_TIMESTAMP 6 | ) CHARSET=utf8mb4; 7 | -------------------------------------------------------------------------------- /databases/mysql/sql/4_teller_details.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `teller_details` ( 2 | `id` BIGINT AUTO_INCREMENT PRIMARY KEY, 3 | `teller_name` VARCHAR(100) NOT NULL, 4 | `branch_name` VARCHAR(30) NOT NULL, 5 | `date_added` DATETIME DEFAULT CURRENT_TIMESTAMP 6 | ) CHARSET=utf8mb4; 7 | -------------------------------------------------------------------------------- /databases/mysql/src/models.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Debug, Deserialize)] 4 | pub struct BankData { 5 | pub bank_name: String, 6 | pub country: String, 7 | } 8 | 9 | #[derive(Debug, Deserialize)] 10 | pub struct BranchData { 11 | pub branch_name: String, 12 | pub location: String, 13 | } 14 | 15 | #[derive(Debug, Deserialize)] 16 | pub struct TellerData { 17 | pub teller_name: String, 18 | pub branch_name: String, 19 | } 20 | 21 | #[derive(Debug, Deserialize)] 22 | pub struct CustomerData { 23 | pub customer_name: String, 24 | pub branch_name: String, 25 | } 26 | 27 | #[derive(Debug, Serialize)] 28 | pub struct BankDetails { 29 | pub bank_name: String, 30 | pub country: String, 31 | } 32 | 33 | #[derive(Debug, Serialize)] 34 | pub struct BankResponseData { 35 | pub bank_data: Vec, 36 | } 37 | 38 | #[derive(Debug, Serialize)] 39 | pub struct BranchDetails { 40 | pub branch_name: String, 41 | pub location: String, 42 | } 43 | 44 | #[derive(Debug, Serialize)] 45 | pub struct BranchResponseData { 46 | pub branch_data: Vec, 47 | } 48 | 49 | #[derive(Debug, Serialize)] 50 | pub struct TellerDetails { 51 | pub teller_name: String, 52 | pub branch_name: String, 53 | } 54 | 55 | #[derive(Debug, Serialize)] 56 | pub struct TellerResponseData { 57 | pub teller_data: Vec, 58 | } 59 | 60 | #[derive(Debug, Serialize)] 61 | pub struct CustomerDetails { 62 | pub customer_name: String, 63 | pub branch_name: String, 64 | } 65 | 66 | #[derive(Debug, Serialize)] 67 | pub struct CustomerResponseData { 68 | pub customer_data: Vec, 69 | } 70 | -------------------------------------------------------------------------------- /databases/postgres/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | .env 4 | -------------------------------------------------------------------------------- /databases/postgres/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "db-postgres" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | actix-web.workspace = true 8 | confik = "0.11" 9 | deadpool-postgres = { version = "0.12", features = ["serde"] } 10 | derive_more = { workspace = true, features = ["display", "error", "from"] } 11 | dotenvy.workspace = true 12 | serde.workspace = true 13 | tokio-pg-mapper = "0.2.0" 14 | tokio-pg-mapper-derive = "0.2.0" 15 | tokio-postgres = "0.7.6" 16 | -------------------------------------------------------------------------------- /databases/postgres/sql/add_user.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO testing.users(email, first_name, last_name, username) 2 | VALUES ($1, $2, $3, $4) 3 | RETURNING $table_fields; 4 | -------------------------------------------------------------------------------- /databases/postgres/sql/get_users.sql: -------------------------------------------------------------------------------- 1 | SELECT $table_fields FROM testing.users; 2 | -------------------------------------------------------------------------------- /databases/postgres/sql/schema.sql: -------------------------------------------------------------------------------- 1 | DROP SCHEMA IF EXISTS testing CASCADE; 2 | CREATE SCHEMA testing; 3 | 4 | CREATE TABLE testing.users ( 5 | id BIGSERIAL PRIMARY KEY, 6 | email VARCHAR(200) NOT NULL, 7 | first_name VARCHAR(200) NOT NULL, 8 | last_name VARCHAR(200) NOT NULL, 9 | username VARCHAR(50) UNIQUE NOT NULL, 10 | UNIQUE (username) 11 | ); 12 | -------------------------------------------------------------------------------- /databases/postgres/src/config.rs: -------------------------------------------------------------------------------- 1 | use confik::Configuration; 2 | use serde::Deserialize; 3 | 4 | #[derive(Debug, Default, Configuration)] 5 | pub struct ExampleConfig { 6 | pub server_addr: String, 7 | #[confik(from = DbConfig)] 8 | pub pg: deadpool_postgres::Config, 9 | } 10 | 11 | #[derive(Debug, Deserialize)] 12 | #[serde(transparent)] 13 | struct DbConfig(deadpool_postgres::Config); 14 | 15 | impl From for deadpool_postgres::Config { 16 | fn from(value: DbConfig) -> Self { 17 | value.0 18 | } 19 | } 20 | 21 | impl confik::Configuration for DbConfig { 22 | type Builder = Option; 23 | } 24 | -------------------------------------------------------------------------------- /databases/postgres/src/db.rs: -------------------------------------------------------------------------------- 1 | use deadpool_postgres::Client; 2 | use tokio_pg_mapper::FromTokioPostgresRow; 3 | 4 | use crate::{errors::MyError, models::User}; 5 | 6 | pub async fn get_users(client: &Client) -> Result, MyError> { 7 | let stmt = include_str!("../sql/get_users.sql"); 8 | let stmt = stmt.replace("$table_fields", &User::sql_table_fields()); 9 | let stmt = client.prepare(&stmt).await.unwrap(); 10 | 11 | let results = client 12 | .query(&stmt, &[]) 13 | .await? 14 | .iter() 15 | .map(|row| User::from_row_ref(row).unwrap()) 16 | .collect::>(); 17 | 18 | Ok(results) 19 | } 20 | 21 | pub async fn add_user(client: &Client, user_info: User) -> Result { 22 | let _stmt = include_str!("../sql/add_user.sql"); 23 | let _stmt = _stmt.replace("$table_fields", &User::sql_table_fields()); 24 | let stmt = client.prepare(&_stmt).await.unwrap(); 25 | 26 | client 27 | .query( 28 | &stmt, 29 | &[ 30 | &user_info.email, 31 | &user_info.first_name, 32 | &user_info.last_name, 33 | &user_info.username, 34 | ], 35 | ) 36 | .await? 37 | .iter() 38 | .map(|row| User::from_row_ref(row).unwrap()) 39 | .collect::>() 40 | .pop() 41 | .ok_or(MyError::NotFound) // more applicable for SELECTs 42 | } 43 | -------------------------------------------------------------------------------- /databases/postgres/src/errors.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{HttpResponse, ResponseError}; 2 | use deadpool_postgres::PoolError; 3 | use derive_more::{Display, Error, From}; 4 | use tokio_pg_mapper::Error as PGMError; 5 | use tokio_postgres::error::Error as PGError; 6 | 7 | #[derive(Debug, Display, Error, From)] 8 | pub enum MyError { 9 | NotFound, 10 | PGError(PGError), 11 | PGMError(PGMError), 12 | PoolError(PoolError), 13 | } 14 | 15 | impl ResponseError for MyError { 16 | fn error_response(&self) -> HttpResponse { 17 | match *self { 18 | MyError::NotFound => HttpResponse::NotFound().finish(), 19 | MyError::PoolError(ref err) => { 20 | HttpResponse::InternalServerError().body(err.to_string()) 21 | } 22 | _ => HttpResponse::InternalServerError().finish(), 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /databases/postgres/src/models.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use tokio_pg_mapper_derive::PostgresMapper; 3 | 4 | #[derive(Deserialize, PostgresMapper, Serialize)] 5 | #[pg_mapper(table = "users")] // singular 'user' is a keyword.. 6 | pub struct User { 7 | pub email: String, 8 | pub first_name: String, 9 | pub last_name: String, 10 | pub username: String, 11 | } 12 | -------------------------------------------------------------------------------- /databases/rbatis/README.md: -------------------------------------------------------------------------------- 1 | The `rbatis` ORM developers keep their own example on usage with Actix Web. 2 | 3 | You can find the example in the [rbatis/example/src/actix_web](https://github.com/rbatis/rbatis/blob/a1c09af45d9827f658e9f7ef5b58419e95b25fe7/example/src/actix_web/main.rs) directory. 4 | 5 | See the [`example` folder readme](https://github.com/rbatis/rbatis/tree/master/example) for a guide on how to get started with the example. 6 | -------------------------------------------------------------------------------- /databases/redis/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "db-redis" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | actix-web.workspace = true 8 | env_logger.workspace = true 9 | redis = { workspace = true, features = ["tokio-comp", "connection-manager"] } 10 | serde.workspace = true 11 | tracing.workspace = true 12 | -------------------------------------------------------------------------------- /databases/redis/README.md: -------------------------------------------------------------------------------- 1 | # Redis 2 | 3 | This project illustrates how to send multiple cache requests to Redis in bulk, asynchronously. This approach resembles traditional Redis pipelining. [See here for more details about this approach.](https://github.com/benashford/redis-async-rs/issues/19#issuecomment-412208018) 4 | 5 | ## Start Server 6 | 7 | ```sh 8 | cd databases/redis 9 | cargo run 10 | ``` 11 | 12 | ## Endpoints 13 | 14 | ### `POST /stuff` 15 | 16 | To test the demo, POST a json object containing three strings to the `/stuff` endpoint: 17 | 18 | ```json 19 | { 20 | "one": "first entry", 21 | "two": "second entry", 22 | "three": "third entry" 23 | } 24 | ``` 25 | 26 | These three entries will cache to redis, keyed accordingly. 27 | 28 | Using [HTTPie]: 29 | 30 | ```sh 31 | http :8080/stuff one="first entry" two="second entry" three="third entry" 32 | ``` 33 | 34 | Using [cURL]: 35 | 36 | ```sh 37 | curl localhost:8080/stuff -H 'content-type: application/json' -d '{"one":"first entry","two":"second entry","three":"third entry"}' 38 | ``` 39 | 40 | ### `DELETE /stuff` 41 | 42 | To delete these, simply issue a DELETE http request to /stuff endpoint 43 | 44 | Using [HTTPie]: 45 | 46 | ```sh 47 | http DELETE :8080/stuff 48 | ``` 49 | 50 | Using [cURL]: 51 | 52 | ```sh 53 | curl -XDELETE 127.0.0.1:8080/stuff 54 | ``` 55 | 56 | ## Verify Redis Contents 57 | 58 | At any time, verify the contents of Redis using its CLI: 59 | 60 | ```sh 61 | echo "MGET mydomain:one mydomain:two mydomain:three" | redis-cli 62 | ``` 63 | 64 | [httpie]: https://httpie.org 65 | [curl]: https://curl.haxx.se 66 | -------------------------------------------------------------------------------- /databases/sea-orm/README.md: -------------------------------------------------------------------------------- 1 | # [SeaORM](https://github.com/SeaQL/sea-orm) 2 | 3 | You can find the SeaORM with Actix 4 example in the [sea-orm/examples/actix_example/](https://github.com/SeaQL/sea-orm/tree/master/examples/actix_example) 4 | 5 | And with the Actix 3 here: [sea-orm/examples/actix3_example/](https://github.com/SeaQL/sea-orm/tree/master/examples/actix3_example) 6 | -------------------------------------------------------------------------------- /databases/sqlite/.gitignore: -------------------------------------------------------------------------------- 1 | weather.db 2 | weather.db-* 3 | -------------------------------------------------------------------------------- /databases/sqlite/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "db-sqlite" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | # Do not use workspace deps as this package isn't part of the workspace. 7 | [dependencies] 8 | actix-web = "4" 9 | env_logger = "0.11" 10 | futures-util = { version = "0.3.17", default-features = false, features = ["std"] } 11 | log = "0.4" 12 | r2d2 = "0.8" 13 | r2d2_sqlite = "0.24" 14 | rusqlite = "0.31" 15 | serde = { version = "1", features = ["derive"] } 16 | -------------------------------------------------------------------------------- /databases/sqlite/README.md: -------------------------------------------------------------------------------- 1 | Getting started using databases with Actix Web, asynchronously. 2 | 3 | ## Usage 4 | 5 | ### init database sqlite 6 | 7 | From the root directory of this project: 8 | 9 | ```sh 10 | bash db/setup_db.sh 11 | ``` 12 | 13 | This creates a sqlite database, weather.db, in the root. 14 | 15 | ### server 16 | 17 | ```sh 18 | # if ubuntu : sudo apt-get install libsqlite3-dev 19 | # if fedora : sudo dnf install libsqlite3x-devel 20 | cargo run 21 | ``` 22 | 23 | ### web client 24 | 25 | [http://127.0.0.1:8080/asyncio_weather](http://127.0.0.1:8080/asyncio_weather) 26 | 27 | [http://127.0.0.1:8080/parallel_weather](http://127.0.0.1:8080/parallel_weather) 28 | 29 | ### sqlite client 30 | 31 | ```sh 32 | # if ubuntu : sudo apt-get install sqlite3 33 | # if fedora : sudo dnf install sqlite3x 34 | sqlite3 weather.db 35 | sqlite> .tables 36 | sqlite> select * from nyc_weather; 37 | ``` 38 | 39 | ## Dependencies 40 | 41 | On Ubuntu 19.10: 42 | 43 | ``` 44 | sudo apt install libsqlite3-dev 45 | ``` 46 | -------------------------------------------------------------------------------- /databases/sqlite/db/GHCND_documentation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actix/examples/c8a3c9bcc91186d72b8e37fb84eb929da5b9235b/databases/sqlite/db/GHCND_documentation.pdf -------------------------------------------------------------------------------- /databases/sqlite/db/README.md: -------------------------------------------------------------------------------- 1 | This directory includes weather information obtained from NOAA for NYC Central Park: https://www.ncdc.noaa.gov/cdo-web/ 2 | 3 | # Setup Instructions 4 | 5 | Set up a sqlite3 database by executing the setup_db.sh file: `bash setup_db.sh` 6 | -------------------------------------------------------------------------------- /databases/sqlite/db/db.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE nyc_weather( 2 | STATION TEXT, 3 | NAME TEXT, 4 | DATE TEXT, 5 | ACMH DOUBLE, 6 | AWND DOUBLE, 7 | FMTM DOUBLE, 8 | PGTM DOUBLE, 9 | PRCP DOUBLE, 10 | PSUN DOUBLE, 11 | SNOW DOUBLE, 12 | SNWD DOUBLE, 13 | TAVG DOUBLE, 14 | TMAX DOUBLE, 15 | TMIN DOUBLE, 16 | TSUN DOUBLE, 17 | WDF1 DOUBLE, 18 | WDF2 DOUBLE, 19 | WDF5 DOUBLE, 20 | WDFG DOUBLE, 21 | WDFM DOUBLE, 22 | WSF1 DOUBLE, 23 | WSF2 DOUBLE, 24 | WSF5 DOUBLE, 25 | WSFG DOUBLE, 26 | WSFM DOUBLE 27 | ); 28 | 29 | -------------------------------------------------------------------------------- /databases/sqlite/db/setup_db.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cd $(dirname "$0") 3 | sqlite3 ../weather.db < db.sql 4 | sqlite3 -csv ../weather.db ".import nyc_centralpark_weather.csv nyc_weather" 5 | -------------------------------------------------------------------------------- /docker/.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | -------------------------------------------------------------------------------- /docker/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "docker_sample" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | # Do not use workspace deps as they won't work in docker context. 7 | [dependencies] 8 | actix-web = "4" 9 | env_logger = "0.11" 10 | log = "0.4" 11 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # NB: This is not a production-grade Dockerfile. 2 | 3 | ################# 4 | ## build stage ## 5 | ################# 6 | FROM rust:1-slim-bookworm AS builder 7 | WORKDIR /code 8 | 9 | # Download crates-io index and fetch dependency code. 10 | # This step avoids needing to spend time on every build downloading the index 11 | # which can take a long time within the docker context. Docker will cache it. 12 | RUN USER=root cargo init 13 | COPY Cargo.toml Cargo.toml 14 | RUN cargo fetch 15 | 16 | # copy app files 17 | COPY src src 18 | 19 | # compile app 20 | RUN cargo build --release 21 | 22 | ############### 23 | ## run stage ## 24 | ############### 25 | FROM bitnami/minideb:bookworm 26 | WORKDIR /app 27 | 28 | # copy server binary from build stage 29 | COPY --from=builder /code/target/release/docker_sample docker_sample 30 | 31 | # set user to non-root unless root is required for your app 32 | USER 1001 33 | 34 | # indicate what port the server is running on 35 | EXPOSE 8080 36 | 37 | # run server 38 | CMD [ "/app/docker_sample" ] 39 | -------------------------------------------------------------------------------- /docker/README.md: -------------------------------------------------------------------------------- 1 | # Docker sample 2 | 3 | ## Build image 4 | 5 | ```shell 6 | docker build -t actix-docker . 7 | ``` 8 | 9 | ## Run built image 10 | 11 | ```shell 12 | docker run -d -p 8080:8080 actix-docker 13 | # and the server should start instantly 14 | curl http://localhost:8080 15 | ``` 16 | 17 | ## Running unit tests 18 | 19 | ```shell 20 | docker build -t actix-docker:test . 21 | docker run --rm actix-docker:test 22 | ``` 23 | -------------------------------------------------------------------------------- /docker/src/main.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{App, HttpResponse, HttpServer, Responder, get, middleware::Logger}; 2 | 3 | #[get("/")] 4 | async fn index() -> impl Responder { 5 | HttpResponse::Ok().body("Hello world!") 6 | } 7 | 8 | #[get("/again")] 9 | async fn again() -> impl Responder { 10 | HttpResponse::Ok().body("Hello world again!") 11 | } 12 | 13 | #[actix_web::main] 14 | async fn main() -> std::io::Result<()> { 15 | env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); 16 | 17 | log::info!("Starting HTTP server: go to http://localhost:8080"); 18 | 19 | HttpServer::new(|| { 20 | App::new() 21 | .wrap(Logger::default()) 22 | .service(index) 23 | .service(again) 24 | }) 25 | .bind(("0.0.0.0", 8080))? 26 | .run() 27 | .await 28 | } 29 | -------------------------------------------------------------------------------- /examples-common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "examples-common" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | rustls = { workspace = true } 8 | tracing = { workspace = true } 9 | tracing-subscriber = { workspace = true } 10 | -------------------------------------------------------------------------------- /examples-common/src/lib.rs: -------------------------------------------------------------------------------- 1 | use tracing::level_filters::LevelFilter; 2 | use tracing_subscriber::{EnvFilter, prelude::*}; 3 | 4 | /// Initializes standard tracing subscriber. 5 | pub fn init_standard_logger() { 6 | tracing_subscriber::registry() 7 | .with( 8 | EnvFilter::builder() 9 | .with_default_directive(LevelFilter::INFO.into()) 10 | .from_env_lossy(), 11 | ) 12 | .with(tracing_subscriber::fmt::layer().without_time()) 13 | .init(); 14 | } 15 | 16 | /// Load default rustls provider, to be done once for the process. 17 | pub fn init_rustls_provider() { 18 | rustls::crypto::aws_lc_rs::default_provider() 19 | .install_default() 20 | .unwrap(); 21 | } 22 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; 4 | flake-parts.url = "github:hercules-ci/flake-parts"; 5 | }; 6 | 7 | outputs = inputs@{ flake-parts, ... }: 8 | flake-parts.lib.mkFlake { inherit inputs; } { 9 | systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; 10 | perSystem = { pkgs, config, inputs', system, lib, ... }: { 11 | formatter = pkgs.nixpkgs-fmt; 12 | 13 | devShells.default = pkgs.mkShell { 14 | packages = [ 15 | config.formatter 16 | pkgs.nodePackages.prettier 17 | pkgs.taplo 18 | pkgs.just 19 | ] ++ lib.optional pkgs.stdenv.isDarwin [ 20 | pkgs.pkgsBuildHost.libiconv 21 | pkgs.pkgsBuildHost.darwin.apple_sdk.frameworks.Security 22 | pkgs.pkgsBuildHost.darwin.apple_sdk.frameworks.CoreFoundation 23 | pkgs.pkgsBuildHost.darwin.apple_sdk.frameworks.SystemConfiguration 24 | ]; 25 | }; 26 | }; 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /forms/form/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "form-example" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | actix-web.workspace = true 8 | serde.workspace = true 9 | -------------------------------------------------------------------------------- /forms/form/README.md: -------------------------------------------------------------------------------- 1 | ## Form example 2 | 3 | ```sh 4 | cd form 5 | cargo run 6 | # Started http server: 127.0.0.1:8080 7 | ``` 8 | -------------------------------------------------------------------------------- /forms/form/static/form.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Forms 6 | 7 | 8 |

Will hit handle_post_1

9 |
10 | 14 | 15 |
16 | 17 |
18 | 19 |

Will hit handle_post_2

20 |
21 | 25 | 26 |
27 | 28 |
29 | 30 |

Will hit handle_post_3

31 |
32 | 36 | 37 |
38 | 39 | 40 | -------------------------------------------------------------------------------- /forms/multipart-s3/.env.example: -------------------------------------------------------------------------------- 1 | RUST_LOG=multipart=info,aws=warn,info 2 | 3 | AWS_REGION=us-east-1 4 | AWS_ACCESS_KEY_ID= 5 | AWS_SECRET_ACCESS_KEY= 6 | AWS_S3_BUCKET_NAME= 7 | -------------------------------------------------------------------------------- /forms/multipart-s3/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .env 3 | /tmp -------------------------------------------------------------------------------- /forms/multipart-s3/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "multipart-s3-example" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | actix-multipart.workspace = true 8 | actix-web.workspace = true 9 | actix-web-lab.workspace = true 10 | 11 | aws-config = "1" 12 | aws-sdk-s3 = "1" 13 | 14 | dotenvy.workspace = true 15 | env_logger.workspace = true 16 | futures-util.workspace = true 17 | log.workspace = true 18 | serde.workspace = true 19 | serde_json.workspace = true 20 | tokio = { workspace = true, features = ["io-util", "fs"] } 21 | tokio-util = "0.7" 22 | -------------------------------------------------------------------------------- /forms/multipart-s3/README.md: -------------------------------------------------------------------------------- 1 | # Multipart + AWS S3 2 | 3 | Upload a file in multipart form to AWS S3 using [AWS S3 SDK](https://crates.io/crates/aws-sdk-s3). 4 | 5 | # Usage 6 | 7 | ```sh 8 | cd forms/multipart-s3 9 | ``` 10 | 11 | 1. copy `.env.example` to `.env` 12 | 1. edit `.env` key `AWS_REGION` = your_bucket_region 13 | 1. edit `.env` key `AWS_ACCESS_KEY_ID` = your_key_id 14 | 1. edit `.env` key `AWS_SECRET_ACCESS_KEY` = your_key_secret 15 | 1. edit `.env` key `AWS_S3_BUCKET_NAME` = your_bucket_name 16 | 17 | The AWS SDK automatically reads these environment variables to configure the S3 client. 18 | 19 | ```sh 20 | cargo run 21 | ``` 22 | 23 | Go to in you browser. 24 | 25 | Or, start the upload using [HTTPie]: 26 | 27 | ```sh 28 | http --form POST :8080/ file@Cargo.toml 29 | http --form POST :8080/ file@Cargo.toml file@README.md meta='{"namespace":"foo"}' 30 | 31 | http GET :8080/file/ 32 | ``` 33 | 34 | Or, using cURL: 35 | 36 | ```sh 37 | curl -X POST http://localhost:8080/ -F 'file=@Cargo.toml' 38 | curl -X POST http://localhost:8080/ -F 'file=@Cargo.toml' -F 'file=@README.md' -F 'meta={"namespace":"foo"}' 39 | 40 | curl http://localhost:8080/file/ 41 | ``` 42 | 43 | [httpie]: https://httpie.org 44 | [curl]: https://curl.haxx.se 45 | -------------------------------------------------------------------------------- /forms/multipart-s3/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | S3 Upload Test 5 | 6 |
7 | 11 | 12 | 13 |
14 | 15 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /forms/multipart-s3/src/upload_file.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | 3 | /// Information about an uploaded file. 4 | #[derive(Debug, Clone, Serialize)] 5 | #[serde(rename_all = "camelCase")] 6 | pub struct UploadedFile { 7 | filename: String, 8 | s3_key: String, 9 | s3_url: String, 10 | } 11 | 12 | impl UploadedFile { 13 | /// Construct new uploaded file info container. 14 | pub fn new( 15 | filename: impl Into, 16 | s3_key: impl Into, 17 | s3_url: impl Into, 18 | ) -> Self { 19 | Self { 20 | filename: filename.into(), 21 | s3_key: s3_key.into(), 22 | s3_url: s3_url.into(), 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /forms/multipart/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | /tmp 4 | -------------------------------------------------------------------------------- /forms/multipart/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "multipart-example" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | actix-multipart.workspace = true 8 | actix-web.workspace = true 9 | 10 | env_logger.workspace = true 11 | futures-util.workspace = true 12 | log.workspace = true 13 | sanitize-filename = "0.5" 14 | uuid.workspace = true 15 | -------------------------------------------------------------------------------- /forms/multipart/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2019] [Bevan Hunt] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /forms/multipart/README.md: -------------------------------------------------------------------------------- 1 | # Actix Web File Upload with Async/Await 2 | 3 | ### Run 4 | 5 | ```sh 6 | cd forms/multipart 7 | cargo run 8 | ``` 9 | 10 | `open web browser to localhost:8080 and upload file(s)` 11 | 12 | ### Result 13 | 14 | `file(s) will show up in ./tmp in the same directory as the running process` 15 | 16 | Note: this is a naive implementation and will panic on any error 17 | -------------------------------------------------------------------------------- /graphql/async-graphql/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "async-graphql-demo" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | actix-cors.workspace = true 8 | actix-web.workspace = true 9 | async-graphql = "6" 10 | async-graphql-actix-web = "6" 11 | env_logger.workspace = true 12 | log.workspace = true 13 | slab = "0.4" 14 | -------------------------------------------------------------------------------- /graphql/async-graphql/README.md: -------------------------------------------------------------------------------- 1 | ## GraphQL using `async-graphql` 2 | 3 | > Getting started using [async-graphql](https://github.com/async-graphql/async-graphql) with Actix Web. 4 | 5 | ## Usage 6 | 7 | ```sh 8 | cd graphql/graphql-demo 9 | cargo run 10 | ``` 11 | 12 | ## Endpoints 13 | 14 | ``` 15 | GET/POST http://localhost:8080/graphql GraphQL endpoint 16 | GET http://localhost:8080/graphiql GraphQL playground UI 17 | ``` 18 | 19 | ## Query Examples 20 | 21 | ```graphql 22 | { 23 | humans { 24 | edges { 25 | node { 26 | id 27 | name 28 | friends { 29 | id 30 | name 31 | } 32 | appearsIn 33 | homePlanet 34 | } 35 | } 36 | } 37 | } 38 | ``` 39 | -------------------------------------------------------------------------------- /graphql/juniper-advanced/.env.example: -------------------------------------------------------------------------------- 1 | DATABASE_URL=mysql://user:password@127.0.0.1/graphql_testing 2 | -------------------------------------------------------------------------------- /graphql/juniper-advanced/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "juniper-advanced" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | actix-web.workspace = true 8 | actix-cors.workspace = true 9 | juniper = "0.16" 10 | mysql = "24" 11 | r2d2 = "0.8" 12 | r2d2_mysql = "24" 13 | dotenvy.workspace = true 14 | env_logger.workspace = true 15 | log.workspace = true 16 | uuid.workspace = true 17 | -------------------------------------------------------------------------------- /graphql/juniper-advanced/README.md: -------------------------------------------------------------------------------- 1 | # GraphQL using Juniper and MySQL 2 | 3 | GraphQL Implementation in Rust using Actix, Juniper, and MySQL as Database 4 | 5 | ## Prerequisites 6 | 7 | - MySQL server 8 | 9 | ## Database Configuration 10 | 11 | Create a new database for this project, and import the existing database schema has been provided named `mysql-schema.sql`. 12 | 13 | Create `.env` file on the root directory of this project and set environment variable named `DATABASE_URL`, the example file has been provided named `.env.example`, you can see the format in there. 14 | 15 | ```sh 16 | cat mysql-schema.sql | mysql -u root -D graphql_testing 17 | ``` 18 | 19 | ## Usage 20 | 21 | ```sh 22 | cd graphql/juniper-advanced 23 | cp .env.example .env 24 | # edit .env and insert your DB credentials 25 | cargo run 26 | ``` 27 | 28 | ## GraphQL Playground 29 | 30 | GraphQL provides its own documentation. Click the "docs" link in the top right of the GraphiQL UI to see what types of queries and mutations are possible. 31 | 32 | ``` 33 | http://localhost:8080/graphiql 34 | ``` 35 | -------------------------------------------------------------------------------- /graphql/juniper-advanced/src/db.rs: -------------------------------------------------------------------------------- 1 | use r2d2_mysql::{ 2 | MySqlConnectionManager, 3 | mysql::{Opts, OptsBuilder}, 4 | }; 5 | 6 | pub type Pool = r2d2::Pool; 7 | 8 | pub fn get_db_pool() -> Pool { 9 | let db_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set"); 10 | let opts = Opts::from_url(&db_url).unwrap(); 11 | let builder = OptsBuilder::from_opts(opts); 12 | let manager = MySqlConnectionManager::new(builder); 13 | r2d2::Pool::new(manager).expect("Failed to create DB Pool") 14 | } 15 | -------------------------------------------------------------------------------- /graphql/juniper-advanced/src/handlers.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{Error, HttpResponse, Responder, get, route, web}; 2 | use juniper::http::{GraphQLRequest, graphiql::graphiql_source}; 3 | 4 | use crate::{ 5 | db::Pool, 6 | schemas::root::{Context, Schema, create_schema}, 7 | }; 8 | 9 | /// GraphQL endpoint 10 | #[route("/graphql", method = "GET", method = "POST")] 11 | pub async fn graphql( 12 | pool: web::Data, 13 | schema: web::Data, 14 | data: web::Json, 15 | ) -> Result { 16 | let ctx = Context { 17 | db_pool: pool.get_ref().to_owned(), 18 | }; 19 | 20 | let res = data.execute(&schema, &ctx).await; 21 | 22 | Ok(HttpResponse::Ok().json(res)) 23 | } 24 | 25 | /// GraphiQL UI 26 | #[get("/graphiql")] 27 | async fn graphql_playground() -> impl Responder { 28 | web::Html::new(graphiql_source("/graphql", None)) 29 | } 30 | 31 | pub fn register(config: &mut web::ServiceConfig) { 32 | config 33 | .app_data(web::Data::new(create_schema())) 34 | .service(graphql) 35 | .service(graphql_playground); 36 | } 37 | -------------------------------------------------------------------------------- /graphql/juniper-advanced/src/main.rs: -------------------------------------------------------------------------------- 1 | use actix_cors::Cors; 2 | use actix_web::{App, HttpServer, middleware::Logger, web::Data}; 3 | 4 | mod db; 5 | mod handlers; 6 | mod schemas; 7 | 8 | use self::{db::get_db_pool, handlers::register}; 9 | 10 | #[actix_web::main] 11 | async fn main() -> std::io::Result<()> { 12 | dotenvy::dotenv().ok(); 13 | env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); 14 | 15 | let pool = get_db_pool(); 16 | 17 | log::info!("starting HTTP server on port 8080"); 18 | log::info!("GraphiQL playground: http://localhost:8080/graphiql"); 19 | 20 | HttpServer::new(move || { 21 | App::new() 22 | .app_data(Data::new(pool.clone())) 23 | .configure(register) 24 | .wrap(Cors::permissive()) 25 | .wrap(Logger::default()) 26 | }) 27 | .workers(2) 28 | .bind(("127.0.0.1", 8080))? 29 | .run() 30 | .await 31 | } 32 | -------------------------------------------------------------------------------- /graphql/juniper-advanced/src/schemas/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod product; 2 | pub mod root; 3 | pub mod user; 4 | -------------------------------------------------------------------------------- /graphql/juniper-advanced/src/schemas/user.rs: -------------------------------------------------------------------------------- 1 | use juniper::{GraphQLInputObject, graphql_object}; 2 | use mysql::{Row, from_row, params, prelude::*}; 3 | 4 | use crate::schemas::{product::Product, root::Context}; 5 | 6 | /// User 7 | #[derive(Default, Debug)] 8 | pub struct User { 9 | pub id: String, 10 | pub name: String, 11 | pub email: String, 12 | } 13 | 14 | impl User { 15 | pub(crate) fn from_row(row: Row) -> Self { 16 | let (id, name, email) = from_row(row); 17 | User { id, name, email } 18 | } 19 | } 20 | 21 | #[derive(GraphQLInputObject)] 22 | #[graphql(description = "User Input")] 23 | pub struct UserInput { 24 | pub name: String, 25 | pub email: String, 26 | } 27 | 28 | #[graphql_object(Context = Context)] 29 | impl User { 30 | fn id(&self) -> &str { 31 | &self.id 32 | } 33 | fn name(&self) -> &str { 34 | &self.name 35 | } 36 | fn email(&self) -> &str { 37 | &self.email 38 | } 39 | 40 | fn products(&self, context: &Context) -> Vec { 41 | let mut conn = context.db_pool.get().unwrap(); 42 | 43 | conn.exec( 44 | "SELECT * FROM product WHERE user_id = :user_id", 45 | params! { "user_id" => &self.id }, 46 | ) 47 | .unwrap() 48 | .into_iter() 49 | .map(Product::from_row) 50 | .collect() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /graphql/juniper/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "juniper-example" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | actix-web.workspace = true 8 | actix-cors.workspace = true 9 | juniper = "0.16" 10 | env_logger.workspace = true 11 | log.workspace = true 12 | -------------------------------------------------------------------------------- /graphql/juniper/README.md: -------------------------------------------------------------------------------- 1 | # GraphQL using Juniper 2 | 3 | [Juniper](https://github.com/graphql-rust/juniper) integration for Actix Web. If you want more advanced example, see also the [juniper-advanced example]. 4 | 5 | [juniper-advanced example]: https://github.com/actix/examples/tree/master/graphql/juniper-advanced 6 | 7 | ## Usage 8 | 9 | ### Server 10 | 11 | ```sh 12 | cd graphql/juniper 13 | cargo run 14 | ``` 15 | 16 | ### Web Client 17 | 18 | Go to in your browser. 19 | 20 | _Query example:_ 21 | 22 | ```graphql 23 | { 24 | human(id: "1234") { 25 | name 26 | appearsIn 27 | homePlanet 28 | } 29 | } 30 | ``` 31 | 32 | _Result:_ 33 | 34 | ```json 35 | { 36 | "data": { 37 | "human": { 38 | "name": "Luke", 39 | "appearsIn": ["NEW_HOPE"], 40 | "homePlanet": "Mars" 41 | } 42 | } 43 | } 44 | ``` 45 | 46 | _Mutation example:_ 47 | 48 | ```graphql 49 | mutation { 50 | createHuman(newHuman: { name: "Fresh Kid Ice", appearsIn: EMPIRE, homePlanet: "earth" }) { 51 | id 52 | name 53 | appearsIn 54 | homePlanet 55 | } 56 | } 57 | ``` 58 | 59 | _Result:_ 60 | 61 | ```json 62 | { 63 | "data": { 64 | "createHuman": { 65 | "id": "1234", 66 | "name": "Fresh Kid Ice", 67 | "appearsIn": ["EMPIRE"], 68 | "homePlanet": "earth" 69 | } 70 | } 71 | } 72 | ``` 73 | -------------------------------------------------------------------------------- /guards/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "guards" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | actix-web.workspace = true 8 | -------------------------------------------------------------------------------- /guards/README.md: -------------------------------------------------------------------------------- 1 | # guards 2 | 3 | Shows how to set up custom routing guards. 4 | 5 | - Routing different API versions using a header instead of path. 6 | 7 | ## Usage 8 | 9 | ### Running The Server 10 | 11 | ```sh 12 | cd guards 13 | cargo run --bin=guards 14 | ``` 15 | 16 | ### Available Routes 17 | 18 | #### `GET /api/hello` 19 | 20 | Requires the `Accept-Version` header to be present and set to `1` or `2`. 21 | 22 | Using [HTTPie]: 23 | 24 | ```sh 25 | http :8080/api/hello Accept-Version:1 26 | ``` 27 | 28 | Using [cURL]: 29 | 30 | ```sh 31 | curl 'localhost:8080/api/hello' -H 'accept-version: 1' 32 | ``` 33 | 34 | [httpie]: https://httpie.org 35 | [curl]: https://curl.haxx.se 36 | -------------------------------------------------------------------------------- /http-proxy/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "http-proxy" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | actix-web = { workspace = true, features = ["openssl"] } 8 | awc.workspace = true 9 | 10 | clap = { version = "4", features = ["derive"] } 11 | env_logger.workspace = true 12 | futures-util.workspace = true 13 | log.workspace = true 14 | reqwest.workspace = true 15 | tokio.workspace = true 16 | tokio-stream = { version = "0.1.3", features = ["sync"] } 17 | url = "2.2" 18 | -------------------------------------------------------------------------------- /http-proxy/README.md: -------------------------------------------------------------------------------- 1 | ## HTTP naïve proxy example 2 | 3 | This is a relatively simple HTTP proxy, forwarding HTTP requests to another HTTP server, including request body, headers, and streaming uploads. 4 | 5 | To start: 6 | 7 | ```shell 8 | cargo run -- 9 | # example: 10 | cargo run -- 127.0.0.1 3333 127.0.0.1 8080 11 | ``` 12 | -------------------------------------------------------------------------------- /https-tls/acme-letsencrypt/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tls-acme" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | acme-rfc8555 = "0.1" 8 | actix-files.workspace = true 9 | actix-web = { workspace = true, features = ["rustls-0_23"] } 10 | color-eyre.workspace = true 11 | env_logger.workspace = true 12 | eyre.workspace = true 13 | log.workspace = true 14 | rustls.workspace = true 15 | tokio = { workspace = true, features = ["fs"] } 16 | -------------------------------------------------------------------------------- /https-tls/acme-letsencrypt/README.md: -------------------------------------------------------------------------------- 1 | # Automatic Let's Encrypt TLS/HTTPS using OpenSSL 2 | 3 | We use [`an ACME client library`](https://github.com/x52dev/acme-rfc8555) to auto-generate TLS/HTTPS certificates for a given domain and then start our real web server with the obtained certificate. 4 | 5 | Process is explained in code. 6 | -------------------------------------------------------------------------------- /https-tls/awc-https/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "awc_https" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | actix-web = { workspace = true } 8 | awc = { workspace = true, features = ["rustls-0_23"] } 9 | env_logger = { workspace = true } 10 | log = { workspace = true } 11 | mime = { workspace = true } 12 | rustls = { workspace = true } 13 | rustls-platform-verifier = "0.5" 14 | -------------------------------------------------------------------------------- /https-tls/awc-https/README.md: -------------------------------------------------------------------------------- 1 | # HTTPS Client 2 | 3 | The goal of this example is to show you how to use the `awc` for secure HTTPS communication using Rustls. 4 | 5 | It uses best practices for efficient client set up and demonstrates how to increase the default payload limit. 6 | 7 | This example downloads a 10MB image from Wikipedia when hitting a server route. `cargo run` this example and go to in your browser. 8 | -------------------------------------------------------------------------------- /https-tls/cert-watch/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-cert-watch" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | actix-web = { workspace = true, features = ["rustls-0_23"] } 8 | color-eyre.workspace = true 9 | env_logger.workspace = true 10 | eyre.workspace = true 11 | log.workspace = true 12 | notify = { workspace = true } 13 | rustls.workspace = true 14 | tokio = { workspace = true, features = ["time", "rt", "macros"] } 15 | -------------------------------------------------------------------------------- /https-tls/cert-watch/README.md: -------------------------------------------------------------------------------- 1 | # HTTPS Server With TLS Cert/Key File Watcher 2 | 3 | ## Usage 4 | 5 | ### Certificate 6 | 7 | We put the self-signed certificate in this directory as an example but your browser would complain that it isn't secure. So we recommend to use [`mkcert`] to trust it. To use local CA, you should run: 8 | 9 | ```sh 10 | mkcert -install 11 | ``` 12 | 13 | If you want to generate your own cert/private key file, then run: 14 | 15 | ```sh 16 | mkcert -key-file key.pem -cert-file cert.pem 127.0.0.1 localhost 17 | ``` 18 | 19 | ### Running The Example Server 20 | 21 | ```console 22 | $ cd https-tls/cert-watch 23 | $ cargo run 24 | starting HTTPS server at https://localhost:8443 25 | ``` 26 | 27 | Reload the server by modifying the certificate metadata: 28 | 29 | ```console 30 | $ touch cert.pem 31 | ``` 32 | 33 | ### Client 34 | 35 | - [HTTPie]: `http --verify=no :8443` 36 | - cURL: `curl -v --insecure https://127.0.0.1:8443` 37 | - Browser: navigate to 38 | 39 | [`mkcert`]: https://github.com/FiloSottile/mkcert 40 | [httpie]: https://httpie.io/cli 41 | -------------------------------------------------------------------------------- /https-tls/cert-watch/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEIjCCAoqgAwIBAgIRANtMOCE9la5aBBJkaPCckAUwDQYJKoZIhvcNAQELBQAw 3 | aTEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMR8wHQYDVQQLDBZyb2JA 4 | c29tYnJhLmxvY2FsIChSb2IpMSYwJAYDVQQDDB1ta2NlcnQgcm9iQHNvbWJyYS5s 5 | b2NhbCAoUm9iKTAeFw0yNDAyMDYwMTUzNTBaFw0yNjA1MDYwMDUzNTBaMEoxJzAl 6 | BgNVBAoTHm1rY2VydCBkZXZlbG9wbWVudCBjZXJ0aWZpY2F0ZTEfMB0GA1UECwwW 7 | cm9iQHNvbWJyYS5sb2NhbCAoUm9iKTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC 8 | AQoCggEBAJ+H+iCey/ET+cq3DxCMjkNaUS8M+LqWaEqGBGGGly+mRGfJ30BvaULf 9 | JIR1V0NkT38t9X/Y/7Qnv8ONa2WTnGNGfgrp+DQGGQBkfi2ggaARd8XoY8WlZBVg 10 | UzfZ/9tNqJKx41M2tYunL59ucmB7osmmU8m74oKqylA1TkYEKzyqvkg0AJ3RVslH 11 | /vmq2LKidbofXQnZWvs2PaePR2q7XXv7+bBaTvLeUIU4Vjww6o2kJTN4//kp2dtH 12 | iwfcUxTT6B22X512AXxojp6+X6B1Y1XhHqQcPuZUZ6ITOcS3zCXje/ijDXoiPn3o 13 | mQTM9UeD3zTlq2vZbSeRT1TQzZzRVPUCAwEAAaNkMGIwDgYDVR0PAQH/BAQDAgWg 14 | MBMGA1UdJQQMMAoGCCsGAQUFBwMBMB8GA1UdIwQYMBaAFBf9LZZrFz95p+wnPmJz 15 | Pavuiq9QMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG9w0BAQsF 16 | AAOCAYEAf3tXixdMuwjPVzrR8TImg7Oe9UcYIHxUN27HospX0gpvKRLuFmxNNhN+ 17 | aK0l765FaLH7acOY129BxAGzuG54+XQdeRcJH/SrNCeHfe5VCHc2+f/Fj6yjUrug 18 | ZRlsbS57LFPgOaPFyej2bR4EDbpwGczc/ghCN4PBRGXC4onFGT7TsaAw3GaXnlMx 19 | ToLuSnnZID9gcl6Wp0qR4baXJh5HrcxNOPIiO3k4U+70jcEty5mDSac0+mNsxgOp 20 | O0w1S/YsQ/W+tG2lsb0huPw2XaAPu+GkkX1KmAV5PSVssIqmRcy6eqv4CkqKq1ur 21 | WeKexHVO8VRQqwx8H/DZDnU1IxNx+khxxNSy16wo4LWU6BHfnEyOSW/S1DlsRUub 22 | JtFg5dIkFBUmIBnqAUVvzIcwNvBZ5KvQ//mwVrsxWCdfre0o8lSaesc7GLMeaeht 23 | jgP8h1ThjvilGfVHK0Skvnck46Ll2fPO5ZgNXUXlR8+1J3jYXPOYLWPgVLbMaLkQ 24 | fQB2xLiv 25 | -----END CERTIFICATE----- 26 | -------------------------------------------------------------------------------- /https-tls/hot-reload/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-tls-hot-reload" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | actix-web = { workspace = true, features = ["rustls-0_23"] } 8 | color-eyre = { workspace = true } 9 | examples-common = { workspace = true } 10 | eyre = { workspace = true } 11 | notify = { workspace = true } 12 | rustls = { workspace = true } 13 | rustls-channel-resolver = "0.3" 14 | tokio = { workspace = true, features = ["time", "rt", "macros"] } 15 | tracing = { workspace = true } 16 | -------------------------------------------------------------------------------- /https-tls/hot-reload/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIELDCCApSgAwIBAgIRAJV3HOzuRhQcriZIKUYROVkwDQYJKoZIhvcNAQELBQAw 3 | aTEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMR8wHQYDVQQLDBZyb2JA 4 | c29tYnJhLmxvY2FsIChSb2IpMSYwJAYDVQQDDB1ta2NlcnQgcm9iQHNvbWJyYS5s 5 | b2NhbCAoUm9iKTAeFw0yNTA1MTIwMzIwNTdaFw0yNzA4MTIwMzIwNTdaMFQxJzAl 6 | BgNVBAoTHm1rY2VydCBkZXZlbG9wbWVudCBjZXJ0aWZpY2F0ZTEpMCcGA1UECwwg 7 | cm9iQE1hY0Jvb2tQcm8ubG9jYWxkb21haW4gKFJvYikwggEiMA0GCSqGSIb3DQEB 8 | AQUAA4IBDwAwggEKAoIBAQDsKN1MBUrrXkhDDXIfhvciA03pVrhXLrla9slY2p/t 9 | VPgayFASUfRs1KcyD8+8tpn3z6BWsUup67vfJFM7u1HTHbkEAXWwnEBXOc+503le 10 | JAip33dXSXZQcLErcS0Ad1P26t/lVKctZgNuPiOCHG9OaU3BacGCFCECg0bc4lez 11 | c8B+jhGgPjKholBMA6lLJrBZPS/R0aPRTdUbiaG7pa7U3Au4/wnQmF62zGkpN7ZY 12 | nwExeLDixw/YZLO5Mc6sFzYTFFTfAVwzzYYWoej+tKoMyJFnA8vx+qY6HMVseNOM 13 | xXY+x8pCEmeEs7DUYbCIMHwihkg4T+XFVpvc9OvMUFsrAgMBAAGjZDBiMA4GA1Ud 14 | DwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAfBgNVHSMEGDAWgBQX/S2W 15 | axc/eafsJz5icz2r7oqvUDAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8AAAEwDQYJ 16 | KoZIhvcNAQELBQADggGBAEnR2sgqDpNYDX4CW+cbkpdKW5dwAO/fATM24vv+EcL2 17 | hI3nAaUAYKRixtGgbvBEVUmdlQmQHR0hj9L8MZ73/c/G0dakJFf3V5feVCDKXLGo 18 | pL2lP7m6O6H4Q+BErHHO2FTMILZNmxG6+EmCZCVz4FpVvXfMFGih4cMk7Wedb/0U 19 | yj0vcg2BqHFr8HaXbqu0AwkMkKTW0N4QkW2+6V+Uki8cm1m9a/KRtINGxd24ljwf 20 | xM/KOp1ifWvoVupiAVHeCgJNKs06frI4AMf1HbFmyAftnOGwp+fIrfUMZbnp+Coj 21 | NBXPoM5Zq1eheXtTEbny22EOy7IrtjDNSXgJNUUsbEL4w3GH8W2OdbXQMb0zH9Bj 22 | lBRSMNkKPXoQgTHum9UATqQPBhgcP2JD4g89BYqv0JHgPYWuA+LUvSfpObxXebS/ 23 | oLRHhQpDU3zXKem6Lr5GSMZGDz2gAECVVpGalIdvmyL1LHiHFmb7oTvswxaSPcfj 24 | OKnFl5b+4ezaAW4D6dLxhg== 25 | -----END CERTIFICATE----- 26 | -------------------------------------------------------------------------------- /https-tls/openssl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tls-openssl" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | actix-web = { workspace = true, features = ["openssl"] } 8 | env_logger.workspace = true 9 | log.workspace = true 10 | openssl.workspace = true 11 | -------------------------------------------------------------------------------- /https-tls/openssl/README.md: -------------------------------------------------------------------------------- 1 | # HTTPS Server using OpenSSL 2 | 3 | ## Usage 4 | 5 | ### Generating Trusted Certificate 6 | 7 | We put self-signed certificate in this directory as an example but your browser will complain that connections to the server aren't secure. We recommend to use [`mkcert`] to trust it. To use a local CA, you should run: 8 | 9 | ```sh 10 | mkcert -install 11 | ``` 12 | 13 | If you want to generate your own private key/certificate pair, then run: 14 | 15 | ```sh 16 | mkcert -key-file key.pem -cert-file cert.pem 127.0.0.1 localhost 17 | ``` 18 | 19 | A new `key.pem` and `cert.pem` will be saved to the current directory. You will then need to modify `main.rs` where indicated. 20 | 21 | ### Running Server 22 | 23 | ```console 24 | $ cd https-tls/openssl 25 | $ cargo run # (or `cargo watch -x run`) 26 | starting HTTPS server at 127.0.0.1:8443 27 | ``` 28 | 29 | ### Using Client 30 | 31 | - curl: `curl -vk https://127.0.0.1:8443` 32 | - curl (forced HTTP/1.1): `curl -vk --http1.1 https://127.0.0.1:8443` 33 | - browser: 34 | 35 | ## Self-Signed Encrypted Private Key Command 36 | 37 | ```sh 38 | openssl req -x509 -newkey rsa:4096 -keyout key-pass.pem -out cert-pass.pem -sha256 -days 365 39 | ``` 40 | 41 | [`mkcert`]: https://github.com/FiloSottile/mkcert 42 | -------------------------------------------------------------------------------- /https-tls/rustls-client-cert/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustls-client-cert" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | actix-tls = { workspace = true, features = ["rustls-0_23"] } 8 | actix-web = { workspace = true, features = ["rustls-0_23"] } 9 | env_logger = { workspace = true } 10 | log = { workspace = true } 11 | rustls = { workspace = true } 12 | -------------------------------------------------------------------------------- /https-tls/rustls-client-cert/README.md: -------------------------------------------------------------------------------- 1 | # TLS Client Certificate (using Rustls) 2 | 3 | ## Usage 4 | 5 | ### Certificate 6 | 7 | All the self-signed certificate are in the ./certs directory, including the CA certificate generated by [`mkcert`] that was used to create the server and client certs. 8 | 9 | ### Server 10 | 11 | ```sh 12 | cd https-tls/rustls-client-cert 13 | cargo run 14 | ``` 15 | 16 | The server runs HTTP on port 8080 and HTTPS on port 8443. 17 | 18 | ### Providing Client Cert 19 | 20 | Using [HTTPie]: 21 | 22 | ```sh 23 | http https://127.0.0.1:8443/ --verify=certs/rootCA.pem --cert=certs/client-cert.pem --cert-key=certs/client-key.pem 24 | ``` 25 | 26 | Using [cURL]: 27 | 28 | ```sh 29 | curl https://127.0.0.1:8443/ --cacert certs/rootCA.pem --cert certs/client-cert.pem --key certs/client-key.pem 30 | ``` 31 | 32 | [`mkcert`]: https://github.com/FiloSottile/mkcert 33 | [curl]: https://curl.haxx.se/ 34 | [httpie]: https://httpie.org/ 35 | -------------------------------------------------------------------------------- /https-tls/rustls-client-cert/certs/client-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEVDCCArygAwIBAgIRAOAgy58Y3ViVpV9G8DTyKzMwDQYJKoZIhvcNAQELBQAw 3 | bTEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMSEwHwYDVQQLDBhyb2JA 4 | c29tYnJhLng1Mi5kZXYgKFJvYikxKDAmBgNVBAMMH21rY2VydCByb2JAc29tYnJh 5 | Lng1Mi5kZXYgKFJvYikwHhcNMTkwNjAxMDAwMDAwWhcNMzAxMDI1MTczODMxWjBM 6 | MScwJQYDVQQKEx5ta2NlcnQgZGV2ZWxvcG1lbnQgY2VydGlmaWNhdGUxITAfBgNV 7 | BAsMGHJvYkBzb21icmEueDUyLmRldiAoUm9iKTCCASIwDQYJKoZIhvcNAQEBBQAD 8 | ggEPADCCAQoCggEBAJsp9QFEpfEWFB3CyFTA2Rv2tUMwnpQcDtiB1hwH03EYcMlG 9 | pEkMh1tPTK8WZo2igMJrBtP2Vf2AN0/hmFWUZV1ZEUUNXXW0QD2mHS8Rgz7nAgmq 10 | V5XvmLLeeo2vMdw1B2qsRxCPTjbInDsZsBqv2GyXWo5/9o3PD32h4LNk3w0VyA47 11 | f/jdpMWlcIXQoyJJV1U1FPLf92xYZvWc9Vf/+K6mStESEpoFll+b4uqjPpwrEz9Q 12 | KBY4eyXwhGCrjQC0+jJFNlIcbV5FgQSYd4DVMcw6SWdMeV/+VtQs+JQrENjNB3am 13 | nJ5xpoZ7mmNDOkWg4zvoYRL6o7LtqT+8EXptzMMCAwEAAaOBjzCBjDAOBgNVHQ8B 14 | Af8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMAwGA1UdEwEB 15 | /wQCMAAwHwYDVR0jBBgwFoAUfpWkO5lYpQJz4omoVdVzuuZnn80wLAYDVR0RBCUw 16 | I4IJbG9jYWxob3N0hwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3DQEB 17 | CwUAA4IBgQB8ulsrStRha8SRxoPRlTNIb9WMjNl5KdZF+ePpUctBX0lFmxqn0upu 18 | l3BoZkhMrpPRRcGlDPImuL7tfxk/IuTA6S1AEQtEHCC8WyZbq3RODPzVY6J/IOUv 19 | H2ZwZYo0c714FyNx8igBpVSjHT6yCIeQkSQlTXsWxddToSeKYvXg8VI+M5L0DH1Y 20 | jRb2u3GpMPdLhMqGNZPcwbLkVyMe5aj4hx334fa3uLf/CK6/5ev4+ozSNz3Qr8S7 21 | iPE8WXrny/qEJmTEme7eq6K/QPTC9ly3j+5Ms3Uepnk0Jez9/ksOrlbYrWXhwD0P 22 | Nwvn3HtiYy8q1HwRw5U+LMNyh6lIyfJUsu2tRmYz1fiH74tMDFb2pjDpcmvgaSJY 23 | eSwKksOiX1No6K980ECEkCX9iQFwD5edTCnD2lz+AVGDzZPkY/551Ohl+KMh/mFG 24 | uZNtgjUeMh0btfc9D+PLLLHjpSMVkRMEEmtm/Zi2nKeVCAdIomFfk+EFIEOdaMyk 25 | SUZMUFzBP+4= 26 | -----END CERTIFICATE----- 27 | -------------------------------------------------------------------------------- /https-tls/rustls-client-cert/certs/server-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIESjCCArKgAwIBAgIRAKNTHdWsrKKCpKt19C/sI4UwDQYJKoZIhvcNAQELBQAw 3 | bTEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMSEwHwYDVQQLDBhyb2JA 4 | c29tYnJhLng1Mi5kZXYgKFJvYikxKDAmBgNVBAMMH21rY2VydCByb2JAc29tYnJh 5 | Lng1Mi5kZXYgKFJvYikwHhcNMTkwNjAxMDAwMDAwWhcNMzAxMDI1MTczODM4WjBM 6 | MScwJQYDVQQKEx5ta2NlcnQgZGV2ZWxvcG1lbnQgY2VydGlmaWNhdGUxITAfBgNV 7 | BAsMGHJvYkBzb21icmEueDUyLmRldiAoUm9iKTCCASIwDQYJKoZIhvcNAQEBBQAD 8 | ggEPADCCAQoCggEBANDv0oRupAGtAl9ZZTeTwdToYh/Gtaj3bAyhQQz+dQ5GzLS3 9 | +Zvo3daepwnoNABoxuPwptb/jJ6Ec8rdvUiJkKb+jAuZ0vSuEqPTcgvbnqsV+7UT 10 | 8WleCUnOIY6FB+uTaLEptu5k+Pf7i3m1RqW1gRVlYcHsLuv3NBQW0bz+XUZvcirW 11 | KVCxz2ex2aFzWXrAZEuOwMX+x+Wicd4tRfcio4mOI+jCRCsbwW0TsyWStrimcbl0 12 | ldbRi8DeADf3VPQCMYmcJARYsTfRwK75OFMgmD0GTi8WURWQCbTJnwzop7Famno0 13 | /I/Ef65Dngleb6HwG91EtEVU7QHqSXlUK41Au9kCAwEAAaOBhTCBgjAOBgNVHQ8B 14 | Af8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAfBgNV 15 | HSMEGDAWgBR+laQ7mVilAnPiiahV1XO65mefzTAsBgNVHREEJTAjgglsb2NhbGhv 16 | c3SHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQADggGBAJJ0 17 | 7oDkVmdB/P0PX0vIn+CPmiE8IrO8stItOGlVRy9TDWFRdMpY13BOjlNY2efNU2Tz 18 | or+lyfV7D8PiCk5q3e8sLtOlIAT32IRIrGXl5E4q7zDnOckQjZIUZwSFAFVygy4F 19 | rPgJCS9uqz9vz086SyRD0krTM5u9yMAvG2uJLEk5oaVWAKdDRplStmMx7QQHMZ4G 20 | iNYMDpj4dU4gkrvZeC+JKwjbSJ3hje8CZCA1atzz/5WWEt2D8Yf9tw27T/hinrMi 21 | rrjPpEcA6C8wvHxqYpiptQC1FF5vaUyRqjF+irHgQdNUCE0nJSwAsIDW0TDiVGDD 22 | OJKs6EYuzJ8OcWw6IFx8YNvdxZJSr8SaavrH76myHieTkqhWvjSQERUw71iUwe0q 23 | 5Nev+J2N9U7oZBTCIsD/qKOQTD9mRjJpYLnXNqEyJhzRgCA5+TQIXbTa10eo9Svg 24 | CTkqxsixmdTKD8ZIlUPhLI9ehr0Spbt+2Xh3yAtlSrfAt5p2hgVCoxalEwWpwA== 25 | -----END CERTIFICATE----- 26 | -------------------------------------------------------------------------------- /https-tls/rustls/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustls-example" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | actix-web = { workspace = true, features = ["rustls-0_23"] } 8 | actix-files.workspace = true 9 | 10 | env_logger.workspace = true 11 | log.workspace = true 12 | rustls.workspace = true 13 | -------------------------------------------------------------------------------- /https-tls/rustls/README.md: -------------------------------------------------------------------------------- 1 | # HTTPS Server (using Rustls) 2 | 3 | ## Usage 4 | 5 | ### Certificate 6 | 7 | We put the self-signed certificate in this directory as an example but your browser would complain that it isn't secure. So we recommend to use [`mkcert`] to trust it. To use local CA, you should run: 8 | 9 | ```sh 10 | mkcert -install 11 | ``` 12 | 13 | If you want to generate your own cert/private key file, then run: 14 | 15 | ```sh 16 | mkcert -key-file key.pem -cert-file cert.pem 127.0.0.1 localhost 17 | ``` 18 | 19 | For `rsa` keys use `rsa_private_keys` function instead `pkcs8_private_keys` 20 | 21 | ```rs 22 | let mut keys = pkcs8_private_keys(key_file).unwrap(); // pkcs8 23 | let mut keys = rsa_private_keys(key_file).unwrap(); // rsa 24 | ``` 25 | 26 | [`mkcert`]: https://github.com/FiloSottile/mkcert 27 | 28 | ### Running the Example Server 29 | 30 | ```sh 31 | cd https-tls/rustls 32 | cargo run # (or ``cargo watch -x run``) 33 | # Started http server: 127.0.0.1:8443 34 | ``` 35 | 36 | If you prefer reloading you can substitute `cargo watch -x run`. That requires you install the `cargo-watch` crate. 37 | 38 | ### web client 39 | 40 | - curl: `curl -v https://127.0.0.1:8443/index.html --compressed -k` 41 | - browser: [https://127.0.0.1:8443/index.html](https://127.0.0.1:8443/index.html) 42 | -------------------------------------------------------------------------------- /https-tls/rustls/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEJzCCAo+gAwIBAgIQKu5MWHrdyO4HsnfIu8alTDANBgkqhkiG9w0BAQsFADBt 3 | MR4wHAYDVQQKExVta2NlcnQgZGV2ZWxvcG1lbnQgQ0ExITAfBgNVBAsMGHJvYkBz 4 | b21icmEueDUyLmRldiAoUm9iKTEoMCYGA1UEAwwfbWtjZXJ0IHJvYkBzb21icmEu 5 | eDUyLmRldiAoUm9iKTAeFw0yMTEwMDYyMTMxMzNaFw0yNDAxMDYyMjMxMzNaMEwx 6 | JzAlBgNVBAoTHm1rY2VydCBkZXZlbG9wbWVudCBjZXJ0aWZpY2F0ZTEhMB8GA1UE 7 | CwwYcm9iQHNvbWJyYS54NTIuZGV2IChSb2IpMIIBIjANBgkqhkiG9w0BAQEFAAOC 8 | AQ8AMIIBCgKCAQEAoI9BHaflPrNfnGKO6WmaEwhXfKKBH9sWlo4NKdP9ECZTC2Ef 9 | ubvQzhjcJsPWIwYj1NDiAa11WfD6ayKG7YleoNynsDKnsOEBfXtFHU2IPWaESX4Q 10 | rO8OaTXx001qdjwE3j/+K0AD43umXdnCeks3JYYlyG4/XxKa62pmpwu6KMgKbygA 11 | MS3dIMe7WcYbKX+qPNl4xoF5xkeqlp2urO3SWPkgIYB+cDNsWRHb5vsMWw9s7Zos 12 | W4mWAPZz0bLKw6w6imfo0rq0j5aoPJLNAyuH3/qhZIZC13tUCAxymIq0+pCeO+lZ 13 | f0OC05dB/Hw1zSLxAxHgDzpOsaq9/NXSkIwEzwIDAQABo2QwYjAOBgNVHQ8BAf8E 14 | BAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwHwYDVR0jBBgwFoAUto/ox0MqZShm 15 | QpViV/gjfJKrMDkwGgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3 16 | DQEBCwUAA4IBgQCxMiND9F3xsyGlIyqIgHc+fp+wzFI5Yz9qD/02RP558qXHAj2o 17 | 6zGECzc4PeiBLh7Y7wHjcu4TTLIXnRtrdVFBUT/s58l/uoK8NGVjky74Rc4A+djt 18 | zwcHS0snuj+FJ859Y+uS3rGKAmBAKWD22wmhB96UNRiZjG1QdJ/Or6hMZ3PVbELs 19 | Hgv69UG1jJiL8y7cn4foBXC6Wgb10tPXNoz7TpD3B14+Pd82yergAHswCp3nj9Ip 20 | D+9Ohko26OItO1dJYeDZWi0CurWdjP7xnEsZo2OaLIlSMiUbSyJOCMk/xWJCjuLW 21 | BEc1VzaFwhkGZJUa1F6TOIc70geLC4wQWOaqZoLbsQfihYgRoUMZJOmjcDXJrNZz 22 | wZofnBI+0tDsZfKjwXFyA4bzUD1I3lFY5Zy3wgQprUrZCm69uo8G4RtMWP9DmXCc 23 | SEw6CxBVPu/l/ljYoxdqCyJTLvdQ97OlGgLv3b0DDcWqi7e0zB8NqT0aCTPm7J/M 24 | OBWicNgMJ+1qL8M= 25 | -----END CERTIFICATE----- 26 | -------------------------------------------------------------------------------- /json/json-decode-error/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "json_decode_error" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | actix-web.workspace = true 8 | 9 | env_logger.workspace = true 10 | log.workspace = true 11 | serde.workspace = true 12 | -------------------------------------------------------------------------------- /json/json-decode-error/src/main.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{App, HttpRequest, HttpResponse, HttpServer, Responder, error, post, web}; 2 | use serde::Deserialize; 3 | 4 | #[derive(Debug, Deserialize)] 5 | struct Info { 6 | name: String, 7 | } 8 | 9 | #[post("/")] 10 | async fn greet(name: web::Json) -> impl Responder { 11 | HttpResponse::Ok().body(format!("Hello {}!", name.name)) 12 | } 13 | 14 | fn json_error_handler(err: error::JsonPayloadError, _req: &HttpRequest) -> error::Error { 15 | use actix_web::error::JsonPayloadError; 16 | 17 | let detail = err.to_string(); 18 | let resp = match &err { 19 | JsonPayloadError::ContentType => HttpResponse::UnsupportedMediaType().body(detail), 20 | JsonPayloadError::Deserialize(json_err) if json_err.is_data() => { 21 | HttpResponse::UnprocessableEntity().body(detail) 22 | } 23 | _ => HttpResponse::BadRequest().body(detail), 24 | }; 25 | error::InternalError::from_response(err, resp).into() 26 | } 27 | 28 | #[actix_web::main] 29 | async fn main() -> std::io::Result<()> { 30 | env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); 31 | 32 | log::info!("starting HTTP server at http://localhost:8080"); 33 | 34 | HttpServer::new(|| { 35 | App::new().service(greet).app_data( 36 | web::JsonConfig::default() 37 | // register error_handler for JSON extractors. 38 | .error_handler(json_error_handler), 39 | ) 40 | }) 41 | .workers(2) 42 | .bind(("127.0.0.1", 8080))? 43 | .run() 44 | .await 45 | } 46 | -------------------------------------------------------------------------------- /json/json-error/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "json_error" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | actix-web.workspace = true 8 | 9 | env_logger.workspace = true 10 | log.workspace = true 11 | serde.workspace = true 12 | serde_json.workspace = true 13 | -------------------------------------------------------------------------------- /json/json-error/src/main.rs: -------------------------------------------------------------------------------- 1 | //! This example is meant to show how to automatically generate a json error response when something goes wrong. 2 | 3 | use std::{fmt, io}; 4 | 5 | use actix_web::{App, HttpResponse, HttpServer, ResponseError, http::StatusCode, web}; 6 | use serde::Serialize; 7 | 8 | #[derive(Debug, Serialize)] 9 | struct Error { 10 | msg: String, 11 | status: u16, 12 | } 13 | 14 | impl fmt::Display for Error { 15 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 16 | write!(f, "{}", serde_json::to_string_pretty(self).unwrap()) 17 | } 18 | } 19 | 20 | impl ResponseError for Error { 21 | // builds the actual response to send back when an error occurs 22 | fn error_response(&self) -> HttpResponse { 23 | let err_json = serde_json::json!({ "error": self.msg }); 24 | HttpResponse::build(StatusCode::from_u16(self.status).unwrap()).json(err_json) 25 | } 26 | } 27 | 28 | async fn index() -> Result { 29 | Err(Error { 30 | msg: "an example error message".to_owned(), 31 | status: 400, 32 | }) 33 | } 34 | 35 | #[actix_web::main] 36 | async fn main() -> io::Result<()> { 37 | env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); 38 | 39 | log::info!("starting HTTP server at http://localhost:8080"); 40 | 41 | HttpServer::new(|| App::new().service(web::resource("/").route(web::get().to(index)))) 42 | .workers(2) 43 | .bind(("127.0.0.1", 8080))? 44 | .run() 45 | .await 46 | } 47 | -------------------------------------------------------------------------------- /json/json-validation/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "json-validation" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | actix-web.workspace = true 8 | awc = { workspace = true, features = ["openssl"] } 9 | 10 | env_logger.workspace = true 11 | futures-util.workspace = true 12 | log.workspace = true 13 | serde.workspace = true 14 | serde_json.workspace = true 15 | validator = { version = "0.18", features = ["derive"] } 16 | -------------------------------------------------------------------------------- /json/json-validation/README.md: -------------------------------------------------------------------------------- 1 | This is a contrived example intended to illustrate a few important Actix Web features. 2 | 3 | _Imagine_ that you have a process that involves 3 steps. The steps here are dumb in that they do nothing other than call an HTTP endpoint that returns the json that was posted to it. The intent here is to illustrate how to chain these steps together as futures and return a final result in a response. 4 | 5 | Actix Web features illustrated here include: 6 | 7 | 1. handling json input param 8 | 2. validating user-submitted parameters using the 'validator' crate 9 | 2. `awc` client features: 10 | - POSTing json body 11 | 3. chaining futures into a single response used by an asynch endpoint 12 | 13 | ### server 14 | 15 | ```sh 16 | cd basics/json-validation 17 | cargo run 18 | # Started http server: 127.0.0.1:8080 19 | ``` 20 | 21 | Example query from the command line using httpie: `echo '{"id":"1", "name": "JohnDoe"}' | http 127.0.0.1:8080/something` 22 | -------------------------------------------------------------------------------- /json/json/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "json-example" 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 | serde_json.workspace = true 12 | -------------------------------------------------------------------------------- /json/json/README.md: -------------------------------------------------------------------------------- 1 | # json 2 | 3 | Json's `Getting Started` guide using json (serde-json or json-rust) for Actix Web 4 | 5 | ## Usage 6 | 7 | ### server 8 | 9 | ```sh 10 | cd json/json 11 | cargo run 12 | # Started http server: 127.0.0.1:8080 13 | ``` 14 | 15 | ### web client 16 | 17 | With [Postman](https://www.getpostman.com/) or [Rested](moz-extension://60daeb1c-5b1b-4afd-9842-0579ed34dfcb/dist/index.html) 18 | 19 | - POST / (embed serde-json): 20 | 21 | - method : `POST` 22 | - url : `http://127.0.0.1:8080/` 23 | - header : `Content-Type` = `application/json` 24 | - body (raw) : `{"name": "Test user", "number": 100}` 25 | 26 | - POST /manual (manual serde-json): 27 | 28 | - method : `POST` 29 | - url : `http://127.0.0.1:8080/manual` 30 | - header : `Content-Type` = `application/json` 31 | - body (raw) : `{"name": "Test user", "number": 100}` 32 | 33 | - POST /mjsonrust (manual json-rust): 34 | 35 | - method : `POST` 36 | - url : `http://127.0.0.1:8080/mjsonrust` 37 | - header : `Content-Type` = `application/json` 38 | - body (raw) : `{"name": "Test user", "number": 100}` (you can also test `{notjson}`) 39 | 40 | ### python client 41 | 42 | - `pip install aiohttp` 43 | - `python client.py` 44 | 45 | if ubuntu : 46 | 47 | - `pip3 install aiohttp` 48 | - `python3 client.py` 49 | -------------------------------------------------------------------------------- /json/json/client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # This script could be used for Actix Web multipart example test 4 | # just start server and run client.py 5 | 6 | import json 7 | import asyncio 8 | import aiohttp 9 | 10 | async def req(): 11 | resp = await aiohttp.ClientSession().request( 12 | "post", 'http://localhost:8080/', 13 | data=json.dumps({"name": "Test user", "number": 100}), 14 | headers={"content-type": "application/json"}) 15 | print(str(resp)) 16 | print(await resp.text()) 17 | assert 200 == resp.status 18 | 19 | 20 | asyncio.get_event_loop().run_until_complete(req()) 21 | -------------------------------------------------------------------------------- /json/jsonrpc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsonrpc-example" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | actix-web.workspace = true 8 | 9 | bytes = "1.1.0" 10 | env_logger.workspace = true 11 | futures-util.workspace = true 12 | log.workspace = true 13 | serde.workspace = true 14 | serde_json.workspace = true 15 | -------------------------------------------------------------------------------- /json/jsonrpc/README.md: -------------------------------------------------------------------------------- 1 | A simple demo for building a `JSONRPC over HTTP` server using [Actix Web](https://github.com/actix/actix-web). 2 | 3 | # Server 4 | 5 | ```sh 6 | cd json/jsonrpc 7 | cargo run 8 | # Starting server on 127.0.0.1:8080 9 | ``` 10 | 11 | # Client 12 | 13 | **curl** 14 | 15 | ```sh 16 | $ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0", "method": "ping", "params": [], "id": 1}' http://127.0.0.1:8080 17 | # {"jsonrpc":"2.0","result":"pong","error":null,"id":1} 18 | ``` 19 | 20 | **python** 21 | 22 | ```sh 23 | $ python tests\test_client.py 24 | # {'jsonrpc': '2.0', 'result': 'pong', 'error': None, 'id': 1} 25 | ``` 26 | 27 | # Methods 28 | 29 | - `ping`: Pong immeditely 30 | - `wait`: Wait `n` seconds, and then pong 31 | - `get`: Get global count 32 | - `inc`: Increment global count 33 | 34 | See `tests\test_client.py` to get more information. 35 | -------------------------------------------------------------------------------- /json/jsonrpc/tests/test_client.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | print('ping: pong immediately') 4 | r = requests.post('http://127.0.0.1:8080/', json={ 5 | 'jsonrpc': '2.0', 6 | 'method': 'ping', 7 | 'params': [], 8 | 'id': 1 9 | }) 10 | print(r.json()) 11 | 12 | 13 | print('ping: pong after 4 secs') 14 | r = requests.post('http://127.0.0.1:8080/', json={ 15 | 'jsonrpc': '2.0', 16 | 'method': 'wait', 17 | 'params': [4], 18 | 'id': 1 19 | }) 20 | print(r.json()) 21 | 22 | for i in range(10): 23 | print(f'inc {i:>02}') 24 | r = requests.post('http://127.0.0.1:8080/', json={ 25 | 'jsonrpc': '2.0', 26 | 'method': 'inc', 27 | 'params': [], 28 | 'id': 1 29 | }) 30 | 31 | print(f'get') 32 | r = requests.post('http://127.0.0.1:8080/', json={ 33 | 'jsonrpc': '2.0', 34 | 'method': 'get', 35 | 'params': [], 36 | 'id': 1 37 | }) 38 | print(r.json()) 39 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | _list: 2 | @just --list 3 | 4 | # Format project. 5 | [group("lint")] 6 | fmt: 7 | fd --type=file --hidden --extension=yml --extension=md --extension=js --exec-batch prettier --write 8 | fd --hidden --extension=toml --exec-batch taplo format 9 | cargo +nightly fmt 10 | 11 | # Check project. 12 | [group("lint")] 13 | check: && clippy 14 | fd --type=file --hidden --extension=yml --extension=md --extension=js --exec-batch prettier --check 15 | fd --hidden --extension=toml --exec-batch taplo format --check 16 | fd --hidden --extension=toml --exec-batch taplo lint 17 | 18 | # Run Clippy over workspace. 19 | [group("lint")] 20 | clippy: 21 | cargo clippy --workspace --all-targets --all-features 22 | -------------------------------------------------------------------------------- /middleware/encrypted-payloads/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "middleware-encrypted-payloads" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | actix-http.workspace = true 8 | actix-web.workspace = true 9 | aes-gcm-siv = "0.11" 10 | base64 = "0.22" 11 | env_logger.workspace = true 12 | log.workspace = true 13 | serde.workspace = true 14 | serde_json.workspace = true 15 | -------------------------------------------------------------------------------- /middleware/http-to-https/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "middleware-http-to-https" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | actix-web = { workspace = true, features = ["rustls-0_23"] } 8 | env_logger.workspace = true 9 | futures-util.workspace = true 10 | log.workspace = true 11 | rustls.workspace = true 12 | -------------------------------------------------------------------------------- /middleware/http-to-https/README.md: -------------------------------------------------------------------------------- 1 | ## Middleware: Redirect Any HTTP Connection To Use HTTPS Connection 2 | 3 | ## Alternatives 4 | 5 | A pre-built solution is soon to be built-in. For now, see [`RedirectHttps`](https://docs.rs/actix-web-lab/0.20/actix_web_lab/middleware/struct.RedirectHttps.html) from [`actix-web-lab`](https://crates.io/crates/actix-web-lab). 6 | 7 | ## This Example 8 | 9 | This example is the next step after implementing this example : [Setup TLS via rustls](https://github.com/actix/examples/tree/master/https-tls/rustls). 10 | 11 | You might have already implemented TLS (using one of the ways mentioned in the example of security section), and have setup your server to listen to port 443 (for HTTPS). 12 | 13 | Now, the only problem left to solve is, to listen to **HTTP** connections as well and redirect them to use **HTTPS** 14 | 15 | ## Usage 16 | 17 | ```sh 18 | cd middleware/http-to-https 19 | cargo run 20 | ``` 21 | -------------------------------------------------------------------------------- /middleware/http-to-https/cert.pem: -------------------------------------------------------------------------------- 1 | ../../https-tls/rustls/cert.pem -------------------------------------------------------------------------------- /middleware/http-to-https/key.pem: -------------------------------------------------------------------------------- 1 | ../../https-tls/rustls/key.pem -------------------------------------------------------------------------------- /middleware/rate-limit/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "middleware-rate-limit" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | actix-governor = "0.5" 8 | actix-web.workspace = true 9 | chrono.workspace = true 10 | env_logger.workspace = true 11 | futures-util.workspace = true 12 | log.workspace = true 13 | -------------------------------------------------------------------------------- /middleware/rate-limit/README.md: -------------------------------------------------------------------------------- 1 | # Middleware: Rate Limiting 2 | 3 | This example showcases two middleware that achieve rate limiting for your API endpoints. One uses a simple leaky-bucket implementation and the other delegates to [`actix-governor`]. 4 | 5 | ## Usage 6 | 7 | ```sh 8 | cd middleware/rate-limit 9 | cargo run 10 | ``` 11 | 12 | Look in `src/rate_limit.rs` to see the leaky-bucket implementation. 13 | 14 | ## Routes 15 | 16 | - [GET /test/simple](http://localhost:8080/test/simple) - uses the hand-written leaky-bucket rate limiting. 17 | - [GET /test/governor](http://localhost:8080/test/governor) - uses [`actix-governor`]. 18 | 19 | Calling either of these endpoints too frequently will result in a 429 Too Many Requests response. 20 | 21 | [`actix-governor`]: https://crates.io/crates/actix-governor 22 | -------------------------------------------------------------------------------- /middleware/rate-limit/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use actix_governor::{Governor, GovernorConfigBuilder}; 4 | use actix_web::{ 5 | App, HttpResponse, HttpServer, middleware, 6 | web::{self}, 7 | }; 8 | 9 | mod rate_limit; 10 | 11 | async fn index() -> HttpResponse { 12 | HttpResponse::Ok().body("succeed") 13 | } 14 | 15 | #[actix_web::main] 16 | async fn main() -> io::Result<()> { 17 | env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); 18 | 19 | let governor_config = GovernorConfigBuilder::default() 20 | .per_second(10) 21 | .burst_size(2) 22 | .finish() 23 | .unwrap(); 24 | 25 | log::info!("starting HTTP server at http://localhost:8080"); 26 | 27 | HttpServer::new(move || { 28 | App::new() 29 | .service( 30 | web::resource("/test/governor") 31 | .wrap(Governor::new(&governor_config)) 32 | .route(web::get().to(index)), 33 | ) 34 | .service( 35 | web::resource("/test/simple") 36 | .wrap(rate_limit::RateLimit::new(2)) 37 | .route(web::get().to(index)), 38 | ) 39 | .wrap(middleware::NormalizePath::trim()) 40 | .wrap(middleware::Logger::default()) 41 | }) 42 | .workers(2) 43 | .bind(("127.0.0.1", 8080))? 44 | .run() 45 | .await 46 | } 47 | -------------------------------------------------------------------------------- /middleware/request-extensions/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "middleware-request-extensions" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | actix-web.workspace = true 8 | log.workspace = true 9 | env_logger.workspace = true 10 | -------------------------------------------------------------------------------- /middleware/request-extensions/README.md: -------------------------------------------------------------------------------- 1 | # Middleware: Add/Retrieve Request-Local Data 2 | 3 | This example showcases a middleware that adds and retrieves request-local data. See also the [middleware guide](https://actix.rs/docs/middleware). 4 | 5 | ## Usage 6 | 7 | ```sh 8 | cd middleware/request-extensions 9 | cargo run 10 | ``` 11 | 12 | Look in `src/add_msg.rs` to see how it works. 13 | 14 | ## Routes 15 | 16 | - [GET /on](http://localhost:8080/on) - `200 OK` with "hello from middleware" body and console log showing the request passed through the middleware 17 | - [GET /off](http://localhost:8080/off) - `500 Internal Server Error` with "no message found" body and console log showing the request passed through the middleware 18 | -------------------------------------------------------------------------------- /middleware/request-extensions/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use actix_web::{ 4 | App, HttpResponse, HttpServer, middleware, 5 | web::{self, ReqData}, 6 | }; 7 | 8 | mod add_msg; 9 | use self::add_msg::{AddMsg, Msg}; 10 | 11 | // wrap route in our middleware factory 12 | async fn index(msg: Option>) -> HttpResponse { 13 | if let Some(msg_data) = msg { 14 | let Msg(message) = msg_data.into_inner(); 15 | HttpResponse::Ok().body(message) 16 | } else { 17 | HttpResponse::InternalServerError().body("No message found.") 18 | } 19 | } 20 | 21 | #[actix_web::main] 22 | async fn main() -> io::Result<()> { 23 | env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); 24 | 25 | log::info!("starting HTTP server at http://localhost:8080"); 26 | 27 | HttpServer::new(|| { 28 | App::new() 29 | .wrap(middleware::Logger::default()) 30 | .service(web::resource("/on").wrap(AddMsg::enabled()).to(index)) 31 | .service(web::resource("/off").wrap(AddMsg::disabled()).to(index)) 32 | }) 33 | .bind(("127.0.0.1", 8080))? 34 | .run() 35 | .await 36 | } 37 | -------------------------------------------------------------------------------- /middleware/various/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "middleware-various" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | actix-http.workspace = true 8 | actix-web.workspace = true 9 | env_logger.workspace = true 10 | futures-util.workspace = true 11 | log.workspace = true 12 | pin-project-lite.workspace = true 13 | -------------------------------------------------------------------------------- /middleware/various/README.md: -------------------------------------------------------------------------------- 1 | # Middleware: Various 2 | 3 | This example showcases a bunch of different uses of middleware. 4 | 5 | See also the [Middleware guide](https://actix.rs/docs/middleware). 6 | 7 | ## Usage 8 | 9 | ```sh 10 | cd middleware/various 11 | cargo run 12 | ``` 13 | 14 | Look in `src/main.rs` and comment the different middleware in/out to see how they function. 15 | 16 | ## Middleware 17 | 18 | ### `redirect::CheckLogin` 19 | 20 | A middleware implementing a request guard which sketches a rough approximation of what a login could look like. 21 | 22 | ### `read_request_body::Logging` 23 | 24 | A middleware demonstrating how to read out the incoming request body. 25 | 26 | ### `read_response_body::Logging` 27 | 28 | A middleware demonstrating how to read out the outgoing response body. 29 | 30 | ### `simple::SayHi` 31 | 32 | A minimal middleware demonstrating the sequence of operations in an actix middleware. There is a second version of the same middleware using `wrap_fn` which shows how easily a middleware can be implemented in actix. 33 | 34 | ## See Also 35 | 36 | - The [`from_fn` middleware constructor](https://docs.rs/actix-web/4/actix_web/middleware/fn.from_fn.html). 37 | -------------------------------------------------------------------------------- /protobuf/.gitignore: -------------------------------------------------------------------------------- 1 | protobuf-python-3.11.2.zip 2 | protobuf-3.11.2/ 3 | __pycache__/ 4 | -------------------------------------------------------------------------------- /protobuf/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "protobuf-example" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | actix-protobuf.workspace = true 8 | actix-web.workspace = true 9 | env_logger.workspace = true 10 | log.workspace = true 11 | prost = { version = "0.13", features = ["prost-derive"] } 12 | -------------------------------------------------------------------------------- /protobuf/README.md: -------------------------------------------------------------------------------- 1 | # protobuf 2 | 3 | ## Usage 4 | 5 | ### Server 6 | 7 | ```shell 8 | cd protobuf 9 | cargo run 10 | ``` 11 | 12 | ### Client 13 | 14 | ```shell 15 | # Dependencies 16 | wget https://github.com/protocolbuffers/protobuf/releases/download/v3.11.2/protobuf-python-3.11.2.zip 17 | unzip protobuf-python-3.11.2.zip 18 | cd protobuf-3.11.2/python/ 19 | python3 setup.py install 20 | pip3 install --upgrade pip 21 | pip3 install aiohttp 22 | 23 | # Client 24 | cd ../.. 25 | python3 client.py 26 | ``` 27 | -------------------------------------------------------------------------------- /protobuf/src/main.rs: -------------------------------------------------------------------------------- 1 | use actix_protobuf::{ProtoBuf, ProtoBufResponseBuilder as _}; 2 | use actix_web::{App, HttpResponse, HttpServer, Result, middleware, web}; 3 | use prost::Message; 4 | 5 | #[derive(Clone, PartialEq, Eq, Message)] 6 | pub struct MyObj { 7 | #[prost(int32, tag = "1")] 8 | pub number: i32, 9 | 10 | #[prost(string, tag = "2")] 11 | pub name: String, 12 | } 13 | 14 | async fn index(msg: ProtoBuf) -> Result { 15 | log::info!("model: {msg:?}"); 16 | HttpResponse::Ok().protobuf(msg.0) // <- send response 17 | } 18 | 19 | #[actix_web::main] 20 | async fn main() -> std::io::Result<()> { 21 | env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); 22 | 23 | log::info!("starting HTTP server at http://localhost:8080"); 24 | 25 | HttpServer::new(|| { 26 | App::new() 27 | .service(web::resource("/").route(web::post().to(index))) 28 | .wrap(middleware::Logger::default()) 29 | }) 30 | .workers(1) 31 | .bind(("127.0.0.1", 8080))? 32 | .run() 33 | .await 34 | } 35 | -------------------------------------------------------------------------------- /protobuf/test.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | message MyObj { 4 | int32 number = 1; 5 | string name = 2; 6 | } -------------------------------------------------------------------------------- /run-in-thread/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "run-in-thread" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | description = "Run Actix Web in separate thread" 6 | 7 | [dependencies] 8 | actix-web.workspace = true 9 | 10 | env_logger.workspace = true 11 | log.workspace = true 12 | -------------------------------------------------------------------------------- /server-sent-events/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "server-sent-events" 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 | futures-util.workspace = true 11 | log.workspace = true 12 | parking_lot.workspace = true 13 | tokio = { workspace = true, features = ["sync"] } 14 | tokio-stream.workspace = true 15 | -------------------------------------------------------------------------------- /server-sent-events/README.md: -------------------------------------------------------------------------------- 1 | # actix-sse 2 | 3 | Example of server-sent events, aka `EventSource`, with actix web. 4 | 5 | ```sh 6 | cd server-sent-events 7 | cargo run 8 | ``` 9 | 10 | Open http://127.0.0.1:8080/ with a browser, then send events with another HTTP client: 11 | 12 | ```sh 13 | curl -X POST 127.0.0.1:8080/broadcast/my_message 14 | ``` 15 | 16 | _my_message_ should appear in the browser with a timestamp. 17 | 18 | ## Performance 19 | 20 | This implementation can serve thousands of clients on a 2021 MacBook with no problems. 21 | 22 | Run `node ./benchmark.js` to benchmark your own system: 23 | 24 | ```sh 25 | $ node benchmark.js 26 | Connected: 1000, connection time: 201 ms, total broadcast time: 20 ms^C⏎ 27 | ``` 28 | 29 | ### Error _Too many open files_ 30 | 31 | You may be limited to a maximal number of connections (open file descriptors). Setting maximum number of open file descriptors to 2048: 32 | 33 | ```sh 34 | ulimit -n 2048 35 | ``` 36 | 37 | Test maximum number of open connections with `node ./drain.js`: 38 | 39 | ```sh 40 | $ node drain.js 41 | Connections dropped: 10450, accepting connections: false^C⏎ 42 | ``` 43 | 44 | _Accepting connections_ indicates whether resources for the server have been exhausted. 45 | -------------------------------------------------------------------------------- /server-sent-events/drain.js: -------------------------------------------------------------------------------- 1 | const http = require("http") 2 | 3 | let drop_goal = 5_000 4 | let dropped = 0 5 | 6 | let query = { 7 | method: "POST", 8 | host: "127.0.0.1", 9 | port: 8080, 10 | path: "/events", 11 | } 12 | 13 | setInterval(() => { 14 | if (dropped < drop_goal) { 15 | let request = http 16 | .request(query, response => { 17 | response.on("data", data => { 18 | if (data.includes("data: connected\n")) { 19 | // drop connection after welcome message 20 | dropped += 1 21 | request.abort() 22 | } 23 | }) 24 | }) 25 | .on("error", () => {}) 26 | .end() 27 | } 28 | }, 0) 29 | 30 | setInterval(() => { 31 | http 32 | .request({ ...query, path: "/" }, () => print_status(true)) 33 | .setTimeout(100, () => print_status(false)) 34 | .on("error", () => {}) 35 | }, 20) 36 | 37 | function print_status(accepting_connections) { 38 | process.stdout.write("\r\x1b[K") 39 | process.stdout.write( 40 | `Connections dropped: ${dropped}, accepting connections: ${accepting_connections}`, 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /server-sent-events/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Server-sent events 8 | 14 | 15 | 16 |
17 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /server-sent-events/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{io, sync::Arc}; 2 | 3 | use actix_web::{App, HttpResponse, HttpServer, Responder, get, middleware::Logger, post, web}; 4 | use actix_web_lab::extract::Path; 5 | 6 | mod broadcast; 7 | use self::broadcast::Broadcaster; 8 | 9 | #[actix_web::main] 10 | async fn main() -> io::Result<()> { 11 | env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); 12 | 13 | let data = Broadcaster::create(); 14 | 15 | log::info!("starting HTTP server at http://localhost:8080"); 16 | 17 | HttpServer::new(move || { 18 | App::new() 19 | .app_data(web::Data::from(Arc::clone(&data))) 20 | .service(index) 21 | .service(event_stream) 22 | .service(broadcast_msg) 23 | .wrap(Logger::default()) 24 | }) 25 | .bind(("127.0.0.1", 8080))? 26 | .workers(2) 27 | .run() 28 | .await 29 | } 30 | 31 | #[get("/")] 32 | async fn index() -> impl Responder { 33 | web::Html::new(include_str!("index.html").to_owned()) 34 | } 35 | 36 | #[get("/events")] 37 | async fn event_stream(broadcaster: web::Data) -> impl Responder { 38 | broadcaster.new_client().await 39 | } 40 | 41 | #[post("/broadcast/{msg}")] 42 | async fn broadcast_msg( 43 | broadcaster: web::Data, 44 | Path((msg,)): Path<(String,)>, 45 | ) -> impl Responder { 46 | broadcaster.broadcast(&msg).await; 47 | HttpResponse::Ok().body("msg sent") 48 | } 49 | -------------------------------------------------------------------------------- /shutdown-server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shutdown-server" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | description = "Send a request to the server to shut it down" 6 | 7 | [dependencies] 8 | actix-web.workspace = true 9 | actix-web-lab.workspace = true 10 | 11 | env_logger.workspace = true 12 | log.workspace = true 13 | parking_lot.workspace = true 14 | -------------------------------------------------------------------------------- /shutdown-server/README.md: -------------------------------------------------------------------------------- 1 | # shutdown-server 2 | 3 | Demonstrates how to shutdown the web server in a couple of ways: 4 | 5 | 1. remotely, via HTTP request 6 | 1. sending a SIGINT signal to the server (control-c) 7 | - Actix Web servers support shutdown signals by default. [See here for more info.](https://actix.rs/docs/server#graceful-shutdown) 8 | 9 | ## Usage 10 | 11 | ### Running The Server 12 | 13 | ```console 14 | $ cd shutdown-server 15 | $ cargo run --bin shutdown-server 16 | [INFO] starting HTTP server at http://localhost:8080 17 | [INFO] Starting 2 workers 18 | [INFO] Actix runtime found; starting in Actix runtime 19 | ``` 20 | 21 | ### Available Routes 22 | 23 | - [`GET /hello`](http://localhost:8080/hello) 24 | - Test hello world 25 | - `POST /stop/true` 26 | - Gracefully shuts down the server and exit 27 | - `POST /stop/false` 28 | - Forces server shutdown and exits 29 | -------------------------------------------------------------------------------- /templating/askama/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "templating-askama" 3 | edition.workspace = true 4 | rust-version.workspace = true 5 | 6 | [dependencies] 7 | actix-web.workspace = true 8 | askama = "0.12" 9 | env_logger.workspace = true 10 | log.workspace = true 11 | 12 | [build-dependencies] 13 | askama = "0.12" 14 | -------------------------------------------------------------------------------- /templating/askama/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use actix_web::{App, HttpServer, Responder, Result, middleware, web}; 4 | use askama::Template; 5 | 6 | #[derive(Template)] 7 | #[template(path = "user.html")] 8 | struct UserTemplate<'a> { 9 | name: &'a str, 10 | text: &'a str, 11 | } 12 | 13 | #[derive(Template)] 14 | #[template(path = "index.html")] 15 | struct Index; 16 | 17 | async fn index(query: web::Query>) -> Result { 18 | let html = if let Some(name) = query.get("name") { 19 | UserTemplate { 20 | name, 21 | text: "Welcome!", 22 | } 23 | .render() 24 | .expect("template should be valid") 25 | } else { 26 | Index.render().expect("template should be valid") 27 | }; 28 | 29 | Ok(web::Html::new(html)) 30 | } 31 | 32 | #[actix_web::main] 33 | async fn main() -> std::io::Result<()> { 34 | env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); 35 | 36 | log::info!("starting HTTP server at http://localhost:8080"); 37 | 38 | HttpServer::new(move || { 39 | App::new() 40 | .wrap(middleware::Logger::default()) 41 | .service(web::resource("/").route(web::get().to(index))) 42 | }) 43 | .bind(("127.0.0.1", 8080))? 44 | .run() 45 | .await 46 | } 47 | -------------------------------------------------------------------------------- /templating/askama/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Actix Web 6 | 7 | 8 |

Welcome!

9 |

10 |

What is your name?

11 |
12 |
13 |

14 |
15 |

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 |
12 |
13 |

14 |
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 |
12 |
13 |

14 |
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 |
12 |
13 |

14 |
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 |
8 | {{! Input name !}} 9 | Name:
10 | {{! Input last name !}} 11 | Last name:
12 |

13 | 14 |

15 |
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 }} 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 --------------------------------------------------------------------------------