├── todo ├── migrations │ ├── .gitkeep │ ├── 2018-07-05-163612_create_tasks_table │ │ ├── down.sql │ │ └── up.sql │ └── 00000000000000_diesel_initial_setup │ │ ├── down.sql │ │ └── up.sql ├── .env ├── src │ ├── schema.rs │ ├── session.rs │ ├── model.rs │ ├── db.rs │ └── main.rs ├── diesel.toml ├── Cargo.toml ├── static │ ├── errors │ │ ├── 400.html │ │ ├── 404.html │ │ └── 500.html │ └── css │ │ └── style.css ├── README.md └── templates │ └── index.html.tera ├── diesel ├── .gitignore ├── .env ├── migrations │ └── 20170124012402_create_users │ │ ├── down.sql │ │ └── up.sql ├── src │ ├── schema.rs │ ├── models.rs │ ├── actions.rs │ └── main.rs ├── Cargo.toml └── README.md ├── rustfmt.toml ├── simple-auth-server ├── migrations │ ├── .gitkeep │ ├── 2018-10-09-101948_users │ │ ├── down.sql │ │ └── up.sql │ ├── 2018-10-16-095633_invitations │ │ ├── down.sql │ │ └── up.sql │ └── 00000000000000_diesel_initial_setup │ │ ├── down.sql │ │ └── up.sql ├── diesel.toml ├── src │ ├── schema.rs │ ├── utils.rs │ ├── models.rs │ ├── invitation_handler.rs │ ├── errors.rs │ ├── main.rs │ ├── email_service.rs │ ├── register_handler.rs │ └── auth_handler.rs ├── static │ ├── main.css │ ├── main.js │ ├── index.html │ └── register.html ├── Cargo.toml └── README.md ├── static_index ├── README.md ├── .gitattributes ├── static │ ├── actixLogo.png │ ├── favicon.ico │ └── index.html ├── Cargo.toml └── src │ └── main.rs ├── error_handling ├── .gitignore ├── README.md └── Cargo.toml ├── async_pg ├── .gitignore ├── sql │ ├── get_users.sql │ ├── add_user.sql │ └── schema.sql ├── Cargo.toml └── README.md ├── multipart ├── .gitignore ├── README.md ├── Cargo.toml ├── LICENSE └── src │ └── main.rs ├── template_yarte ├── templates │ ├── deep │ │ └── more │ │ │ ├── doc │ │ │ ├── t.hbs │ │ │ └── head.hbs │ │ │ ├── card │ │ │ ├── hi.hbs │ │ │ └── form.hbs │ │ │ └── deep │ │ │ └── welcome.hbs │ ├── base.hbs │ └── index.hbs ├── build.rs ├── yarte.toml ├── Cargo.toml └── README.md ├── async_ex2 ├── src │ ├── handlers │ │ ├── mod.rs │ │ ├── parts.rs │ │ └── products.rs │ ├── lib.rs │ ├── common.rs │ ├── bin │ │ └── main.rs │ └── appconfig.rs ├── README.md └── Cargo.toml ├── graphql-demo ├── .env.example ├── src │ ├── schemas │ │ ├── mod.rs │ │ ├── product.rs │ │ └── user.rs │ ├── db.rs │ ├── main.rs │ └── handlers.rs ├── Cargo.toml ├── README.md └── mysql-schema.sql ├── README.md ├── basics ├── static │ ├── favicon.ico │ ├── ntexLogo.png │ ├── 404.html │ └── welcome.html ├── Cargo.toml └── README.md ├── tokio ├── static │ ├── favicon.ico │ ├── ntexLogo.png │ ├── 404.html │ └── welcome.html ├── Cargo.toml └── README.md ├── websocket ├── static │ ├── favicon.ico │ ├── actixLogo.png │ └── index.html ├── Cargo.toml ├── README.md ├── websocket-client.py └── src │ └── client.rs ├── async_db ├── db │ ├── GHCND_documentation.pdf │ ├── setup_db.sh │ ├── README.md │ └── db.sql ├── Cargo.toml ├── README.md └── src │ └── main.rs ├── websocket-lowlevel ├── static │ ├── favicon.ico │ └── index.html ├── Cargo.toml ├── README.md └── src │ └── client.rs ├── form ├── README.md ├── Cargo.toml └── static │ └── form.html ├── cookie-session ├── README.md ├── Cargo.toml └── src │ └── main.rs ├── state ├── README.md ├── Cargo.toml └── src │ └── main.rs ├── template_askama ├── templates │ ├── user.html │ └── index.html ├── Cargo.toml └── src │ └── main.rs ├── template_tera ├── templates │ ├── user.html │ └── index.html ├── Cargo.toml ├── README.md └── src │ └── main.rs ├── awc_https ├── Cargo.toml ├── README.md └── src │ └── main.rs ├── template_handlebars ├── static │ └── templates │ │ ├── user.html │ │ └── index.html ├── Cargo.toml └── src │ ├── README.md │ └── main.rs ├── hello-world ├── Cargo.toml └── src │ └── main.rs ├── unix-socket ├── Cargo.toml ├── README.md └── src │ └── main.rs ├── cookie-auth ├── Cargo.toml ├── README.md └── src │ └── main.rs ├── openssl ├── Cargo.toml ├── README.md ├── cert.pem ├── src │ └── main.rs └── key.pem ├── run-in-thread ├── Cargo.toml └── src │ └── main.rs ├── http-proxy ├── README.md └── Cargo.toml ├── server-sent-events ├── Cargo.toml ├── src │ └── index.html ├── drain.js ├── README.md └── benchmark.js ├── json_error ├── Cargo.toml └── src │ └── main.rs ├── juniper ├── Cargo.toml ├── README.md └── src │ ├── main.rs │ └── schema.rs ├── shutdown-server ├── Cargo.toml ├── README.md └── src │ └── main.rs ├── docker_sample ├── Cargo.toml ├── Dockerfile ├── README.MD └── src │ └── main.rs ├── middleware ├── Cargo.toml ├── README.md └── src │ ├── main.rs │ ├── simple.rs │ ├── read_request_body.rs │ ├── redirect.rs │ └── read_response_body.rs ├── json ├── Cargo.toml ├── client.py └── README.md ├── jsonrpc ├── Cargo.toml ├── README.md └── tests │ └── test_client.py ├── rustls ├── README.md ├── Cargo.toml ├── src │ └── main.rs └── cert.pem ├── r2d2 ├── Cargo.toml └── src │ └── main.rs ├── async_ex1 ├── Cargo.toml └── README.md ├── websocket-chat ├── Cargo.toml ├── README.md ├── client.py └── static │ └── websocket.html ├── .gitignore ├── websocket-tcp-chat ├── Cargo.toml ├── src │ ├── main.rs │ └── client-ws.rs ├── README.md ├── client.py └── static │ └── websocket.html ├── Cargo.toml └── .travis.yml /todo/migrations/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /diesel/.gitignore: -------------------------------------------------------------------------------- 1 | test.db 2 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 89 2 | -------------------------------------------------------------------------------- /diesel/.env: -------------------------------------------------------------------------------- 1 | DATABASE_URL=test.db 2 | -------------------------------------------------------------------------------- /simple-auth-server/migrations/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static_index/README.md: -------------------------------------------------------------------------------- 1 | # static_index 2 | -------------------------------------------------------------------------------- /error_handling/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /async_pg/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | .env 4 | -------------------------------------------------------------------------------- /multipart/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | /tmp 4 | -------------------------------------------------------------------------------- /todo/.env: -------------------------------------------------------------------------------- 1 | DATABASE_URL=postgres://localhost/ntex_todo 2 | -------------------------------------------------------------------------------- /template_yarte/templates/deep/more/doc/t.hbs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /async_ex2/src/handlers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod parts; 2 | pub mod products; 3 | -------------------------------------------------------------------------------- /async_pg/sql/get_users.sql: -------------------------------------------------------------------------------- 1 | SELECT $table_fields FROM testing.users; 2 | -------------------------------------------------------------------------------- /diesel/migrations/20170124012402_create_users/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE users 2 | -------------------------------------------------------------------------------- /graphql-demo/.env.example: -------------------------------------------------------------------------------- 1 | DATABASE_URL=mysql://user:password@127.0.0.1/dbname -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ntex examples 2 | 3 | A curated list of examples related to ntex. 4 | -------------------------------------------------------------------------------- /todo/migrations/2018-07-05-163612_create_tasks_table/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE tasks 2 | -------------------------------------------------------------------------------- /async_ex2/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod appconfig; 2 | pub mod common; 3 | pub mod handlers; 4 | -------------------------------------------------------------------------------- /graphql-demo/src/schemas/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod product; 2 | pub mod root; 3 | pub mod user; 4 | -------------------------------------------------------------------------------- /template_yarte/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | yarte_helpers::recompile::when_changed(); 3 | } 4 | -------------------------------------------------------------------------------- /basics/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ntex-rs/examples/HEAD/basics/static/favicon.ico -------------------------------------------------------------------------------- /error_handling/README.md: -------------------------------------------------------------------------------- 1 | This project illustrates custom error propagation through futures in ntex 2 | -------------------------------------------------------------------------------- /static_index/.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /tokio/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ntex-rs/examples/HEAD/tokio/static/favicon.ico -------------------------------------------------------------------------------- /tokio/static/ntexLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ntex-rs/examples/HEAD/tokio/static/ntexLogo.png -------------------------------------------------------------------------------- /basics/static/ntexLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ntex-rs/examples/HEAD/basics/static/ntexLogo.png -------------------------------------------------------------------------------- /websocket/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ntex-rs/examples/HEAD/websocket/static/favicon.ico -------------------------------------------------------------------------------- /websocket/static/actixLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ntex-rs/examples/HEAD/websocket/static/actixLogo.png -------------------------------------------------------------------------------- /static_index/static/actixLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ntex-rs/examples/HEAD/static_index/static/actixLogo.png -------------------------------------------------------------------------------- /static_index/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ntex-rs/examples/HEAD/static_index/static/favicon.ico -------------------------------------------------------------------------------- /async_db/db/GHCND_documentation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ntex-rs/examples/HEAD/async_db/db/GHCND_documentation.pdf -------------------------------------------------------------------------------- /diesel/src/schema.rs: -------------------------------------------------------------------------------- 1 | table! { 2 | users (id) { 3 | id -> Text, 4 | name -> Text, 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /websocket-lowlevel/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ntex-rs/examples/HEAD/websocket-lowlevel/static/favicon.ico -------------------------------------------------------------------------------- /form/README.md: -------------------------------------------------------------------------------- 1 | ## Form example 2 | 3 | ```bash 4 | cd form 5 | cargo run 6 | # Started http server: 127.0.0.1:8080 7 | ``` 8 | 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /async_pg/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /cookie-session/README.md: -------------------------------------------------------------------------------- 1 | ## Cookie session example 2 | 3 | ```sh 4 | cd cookie-session 5 | cargo run 6 | # Starting http server: 127.0.0.1:8080 7 | ``` 8 | -------------------------------------------------------------------------------- /todo/src/schema.rs: -------------------------------------------------------------------------------- 1 | table! { 2 | tasks (id) { 3 | id -> Int4, 4 | description -> Varchar, 5 | completed -> Bool, 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /async_ex2/README.md: -------------------------------------------------------------------------------- 1 | This example illustrates how to use nested resource registration through application-level configuration. 2 | The endpoints do nothing. 3 | 4 | -------------------------------------------------------------------------------- /template_yarte/templates/base.hbs: -------------------------------------------------------------------------------- 1 | {{! Simple example !}} 2 | {{> doc/t }} 3 | 4 | {{> doc/head }} 5 | 6 | {{> @partial-block }} 7 | 8 | 9 | -------------------------------------------------------------------------------- /todo/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 | -------------------------------------------------------------------------------- /async_db/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /todo/migrations/2018-07-05-163612_create_tasks_table/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE tasks ( 2 | id SERIAL PRIMARY KEY, 3 | description VARCHAR NOT NULL, 4 | completed BOOLEAN NOT NULL DEFAULT 'f' 5 | ); 6 | -------------------------------------------------------------------------------- /template_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /template_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /async_db/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 sqlite_db.sh` 6 | -------------------------------------------------------------------------------- /state/README.md: -------------------------------------------------------------------------------- 1 | # state 2 | 3 | ## Usage 4 | 5 | ### server 6 | 7 | ```bash 8 | cd examples/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 | -------------------------------------------------------------------------------- /template_askama/templates/user.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ntex 6 | 7 | 8 |

Hi, {{ name }}!

9 |

10 | {{ text }} 11 |

12 | 13 | 14 | -------------------------------------------------------------------------------- /template_tera/templates/user.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ntex 6 | 7 | 8 |

Hi, {{ name }}!

9 |

10 | {{ text }} 11 |

12 | 13 | 14 | -------------------------------------------------------------------------------- /template_yarte/templates/index.hbs: -------------------------------------------------------------------------------- 1 | {{#> base title = "Ntex" }} 2 | {{~#if let Some(name) = query.get("name") }} 3 | {{ let lastname = query.get("lastname").ok_or(yarte::Error)? }} 4 | {{> card/hi ~}} 5 | {{ else ~}} 6 | {{> card/form ~}} 7 | {{/if ~}} 8 | {{/base }} -------------------------------------------------------------------------------- /awc_https/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "awc_https" 3 | version = "3.0.0" 4 | authors = ["dowwie "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ntex = { version = "3.0.0-pre.6", features = ["openssl", "tokio"] } 9 | openssl = "0.10" 10 | -------------------------------------------------------------------------------- /template_handlebars/static/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 | -------------------------------------------------------------------------------- /hello-world/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello-world" 3 | version = "4.0.0" 4 | authors = ["Nikolay Kim "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 9 | env_logger = "0.11" 10 | -------------------------------------------------------------------------------- /unix-socket/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "unix-socket" 3 | version = "3.0.0" 4 | authors = ["Messense Lv "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 9 | env_logger = "0.11" 10 | -------------------------------------------------------------------------------- /state/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "state" 3 | version = "3.0.0" 4 | authors = ["Nikolay Kim "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 9 | futures = "0.3" 10 | env_logger = "0.11" 11 | -------------------------------------------------------------------------------- /form/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "form-example" 3 | version = "3.0.0" 4 | authors = ["Gorm Casper "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 9 | serde = { version = "1.0", features = ["derive"] } 10 | -------------------------------------------------------------------------------- /template_handlebars/static/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 Ntex.

11 | 12 | -------------------------------------------------------------------------------- /template_tera/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "template-tera" 3 | version = "3.0.0" 4 | authors = ["Nikolay Kim "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | env_logger = "0.11" 9 | tera = "1.0" 10 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 11 | -------------------------------------------------------------------------------- /cookie-auth/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cookie-auth" 3 | version = "4.0.0" 4 | authors = ["Nikolay Kim "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 9 | ntex-identity = "3.0.0" 10 | env_logger = "0.11" 11 | -------------------------------------------------------------------------------- /template_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 | -------------------------------------------------------------------------------- /openssl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "openssl-example" 3 | version = "3.0.0" 4 | authors = ["Nikolay Kim "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ntex = { version = "3.0.0-pre.6", features = ["openssl", "tokio"] } 9 | env_logger = "0.11" 10 | openssl = "0.10" 11 | -------------------------------------------------------------------------------- /basics/static/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ntex - basics 6 | 7 | 8 | 9 | 10 | back to home 11 |

404

12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tokio/static/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ntex - basics 6 | 7 | 8 | 9 | 10 | back to home 11 |

404

12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /run-in-thread/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "run-in-thread" 3 | version = "3.0.0" 4 | authors = ["Nikolay Kim "] 5 | edition = "2018" 6 | description = "Run ntex in separate thread" 7 | 8 | [dependencies] 9 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 10 | env_logger = "0.11" 11 | -------------------------------------------------------------------------------- /tokio/static/welcome.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ntex - basics 6 | 7 | 8 | 9 | 10 |

Welcome

11 | 12 | 13 | -------------------------------------------------------------------------------- /basics/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "basics" 3 | version = "4.0.0" 4 | authors = ["Nikolay Kim "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 9 | ntex-files = "3" 10 | ntex-session = "3" 11 | futures = "0.3" 12 | env_logger = "0.11" 13 | -------------------------------------------------------------------------------- /basics/static/welcome.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ntex - basics 6 | 7 | 8 | 9 | 10 |

Welcome

11 | 12 | 13 | -------------------------------------------------------------------------------- /static_index/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "static_index" 3 | version = "3.0.0" 4 | authors = ["Jose Marinez "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | futures = "0.3" 9 | env_logger = "0.11" 10 | 11 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 12 | ntex-files = "3.0.0" 13 | -------------------------------------------------------------------------------- /template_askama/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "template-askama" 3 | version = "3.0.0" 4 | authors = ["Nikolay Kim "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 9 | askama = "0.9" 10 | 11 | [build-dependencies] 12 | askama = "0.9" 13 | -------------------------------------------------------------------------------- /http-proxy/README.md: -------------------------------------------------------------------------------- 1 | ## HTTP Full proxy example 2 | 3 | This is a relatively simple HTTP proxy, forwarding HTTP requests to another HTTP server, including 4 | request body, headers, and streaming uploads. 5 | 6 | To start: 7 | 8 | ``` shell 9 | cargo run 10 | ``` 11 | -------------------------------------------------------------------------------- /server-sent-events/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "server-sent-events" 3 | version = "3.0.0" 4 | authors = ["Arve Seljebu"] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 9 | env_logger = "0.11" 10 | futures = "0.3" 11 | tokio = { version = "1", features = ["sync"] } -------------------------------------------------------------------------------- /http-proxy/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "http-proxy" 3 | version = "4.0.0" 4 | authors = ["Nikolay Kim ", "Rotem Yaari "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 9 | clap = "2.32" 10 | futures = "0.3" 11 | url = "2.1" 12 | -------------------------------------------------------------------------------- /cookie-session/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cookie-session" 3 | version = "3.0.0" 4 | authors = ["Nikolay Kim "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 9 | ntex-session = "3.0.0" 10 | futures = "0.3" 11 | time = "0.3" 12 | env_logger = "0.11" 13 | -------------------------------------------------------------------------------- /json_error/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "json_error" 3 | version = "3.0.0" 4 | authors = ["Kai Yao "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 9 | futures = "0.3" 10 | serde = { version = "1.0", features = ["derive"] } 11 | serde_json = "1.0" 12 | -------------------------------------------------------------------------------- /multipart/README.md: -------------------------------------------------------------------------------- 1 | # Ntex Web File Upload with Async/Await 2 | 3 | ### Run 4 | 5 | ``` open web browser to localhost:3000 and upload file(s) ``` 6 | 7 | ### Result 8 | 9 | ``` file(s) will show up in ./tmp in the same directory as the running process ``` 10 | 11 | Note: this is a naive implementation and will panic on any error 12 | -------------------------------------------------------------------------------- /juniper/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "juniper-example" 3 | version = "3.0.0" 4 | authors = ["pyros2097 "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 9 | env_logger = "0.11" 10 | serde = "1.0" 11 | serde_json = "1.0" 12 | serde_derive = "1.0" 13 | juniper = "0.14" 14 | -------------------------------------------------------------------------------- /shutdown-server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shutdown-server" 3 | version = "3.0.0" 4 | authors = ["Rob Ede "] 5 | edition = "2018" 6 | description = "Send a request to the server to shut it down" 7 | 8 | [dependencies] 9 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 10 | env_logger = "0.11" 11 | futures = "0.3" 12 | -------------------------------------------------------------------------------- /async_pg/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 | -------------------------------------------------------------------------------- /diesel/src/models.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use crate::schema::users; 4 | 5 | #[derive(Debug, Clone, Serialize, Queryable, Insertable)] 6 | pub struct User { 7 | pub id: String, 8 | pub name: String, 9 | } 10 | 11 | #[derive(Debug, Clone, Serialize, Deserialize)] 12 | pub struct NewUser { 13 | pub name: String, 14 | } 15 | -------------------------------------------------------------------------------- /docker_sample/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "docker_sample" 3 | version = "3.0.0" 4 | authors = ["docker_sample "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 11 | -------------------------------------------------------------------------------- /middleware/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "middleware-example" 3 | version = "3.0.0" 4 | authors = ["Gorm Casper ", "Sven-Hendrik Haase "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 9 | env_logger = "0.11" 10 | futures = "0.3" 11 | pin-project = "1.0" 12 | -------------------------------------------------------------------------------- /template_handlebars/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "template_handlebars" 3 | version = "3.0.0" 4 | authors = ["Alexandru Tiniuc "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 9 | handlebars = { version = "3.0.0", features = ["dir_source"] } 10 | serde_json = "1.0" 11 | -------------------------------------------------------------------------------- /error_handling/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "error_handling" 3 | version = "3.0.0" 4 | authors = ["dowwie "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 9 | thiserror = "1" 10 | derive_more = "0.99" 11 | futures = "0.3" 12 | serde = "1" 13 | rand = "0.8" 14 | env_logger = "0.11" 15 | -------------------------------------------------------------------------------- /async_ex2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "async_ex2" 3 | version = "3.0.0" 4 | authors = ["dowwie "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 9 | env_logger = "0.11" 10 | futures = "0.3" 11 | serde = { version = "^1.0", features = ["derive"] } 12 | serde_json = "1.0" 13 | time = "0.3" 14 | -------------------------------------------------------------------------------- /docker_sample/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:1 as builder 2 | WORKDIR /app 3 | ADD . /app 4 | RUN rustup target add x86_64-unknown-linux-musl 5 | RUN CARGO_HTTP_MULTIPLEXING=false cargo build --release --target x86_64-unknown-linux-musl 6 | 7 | FROM scratch 8 | COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/docker_sample /app 9 | EXPOSE 5000 10 | CMD ["/app"] 11 | -------------------------------------------------------------------------------- /json/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "json-example" 3 | version = "3.0.0" 4 | authors = ["Nikolay Kim "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 9 | futures = "0.3" 10 | env_logger = "0.11" 11 | serde = { version = "1.0", features = ["derive"] } 12 | serde_json = "1.0" 13 | json = "0.12" 14 | -------------------------------------------------------------------------------- /jsonrpc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsonrpc-example" 3 | version = "3.0.0" 4 | authors = ["mohanson "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 9 | env_logger = "0.11" 10 | futures = "0.3" 11 | log = "0.4" 12 | serde = { version = "1.0", features = ["derive"] } 13 | serde_json = "1.0" 14 | -------------------------------------------------------------------------------- /template_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 | -------------------------------------------------------------------------------- /template_tera/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ntex 6 | 7 | 8 |

Welcome!

9 |

10 |

What is your name?

11 |
12 |
13 |

14 |
15 |

16 | 17 | 18 | -------------------------------------------------------------------------------- /template_askama/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ntex 6 | 7 | 8 |

Welcome!

9 |

10 |

What is your name?

11 |
12 |
13 |

14 |
15 |

16 | 17 | 18 | -------------------------------------------------------------------------------- /tokio/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tokio" 3 | version = "3.0.0" 4 | authors = ["Nikolay Kim "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 9 | ntex-files = "3" 10 | ntex-session = "3" 11 | ntex-rt = "3" 12 | futures = "0.3" 13 | env_logger = "0.11" 14 | tokio = { version = "1", features = ["full"] } 15 | -------------------------------------------------------------------------------- /async_ex2/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 | -------------------------------------------------------------------------------- /openssl/README.md: -------------------------------------------------------------------------------- 1 | # tls example 2 | 3 | ## Usage 4 | 5 | ### server 6 | 7 | ```bash 8 | cd examples/openssl 9 | cargo run (or ``cargo watch -x run``) 10 | # Started http server: 127.0.0.1:8443 11 | ``` 12 | 13 | ### web client 14 | 15 | - curl: ``curl -v https://127.0.0.1:8443/index.html --compressed -k`` 16 | - browser: [https://127.0.0.1:8443/index.html](https://127.0.0.1:8443/index.html) 17 | -------------------------------------------------------------------------------- /rustls/README.md: -------------------------------------------------------------------------------- 1 | # tls example 2 | 3 | ## Usage 4 | 5 | ### server 6 | 7 | ```bash 8 | cd examples/rustls 9 | cargo run (or ``cargo watch -x run``) 10 | # Started http server: 127.0.0.1:8443 11 | ``` 12 | 13 | ### web client 14 | 15 | - curl: ``curl -v https://127.0.0.1:8443/index.html --compressed -k`` 16 | - browser: [https://127.0.0.1:8443/index.html](https://127.0.0.1:8443/index.html) 17 | -------------------------------------------------------------------------------- /todo/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 | -------------------------------------------------------------------------------- /r2d2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "r2d2-example" 3 | version = "3.0.0" 4 | authors = ["Nikolay Kim "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 9 | 10 | futures = "0.3" 11 | env_logger = "0.11" 12 | uuid = { version = "1", features = ["v4"] } 13 | 14 | r2d2 = "0.8" 15 | r2d2_sqlite = "0.14" 16 | rusqlite = "0.21" 17 | -------------------------------------------------------------------------------- /async_ex1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "awc_examples" 3 | version = "5.0.0" 4 | authors = ["dowwie "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 9 | env_logger = "0.11" 10 | futures = "0.3" 11 | serde = { version = "1.0", features = ["derive"] } 12 | serde_json = "1.0" 13 | validator = "0.10" 14 | validator_derive = "0.10" 15 | -------------------------------------------------------------------------------- /simple-auth-server/src/schema.rs: -------------------------------------------------------------------------------- 1 | table! { 2 | invitations (id) { 3 | id -> Uuid, 4 | email -> Varchar, 5 | expires_at -> Timestamp, 6 | } 7 | } 8 | 9 | table! { 10 | users (email) { 11 | email -> Varchar, 12 | hash -> Varchar, 13 | created_at -> Timestamp, 14 | } 15 | } 16 | 17 | allow_tables_to_appear_in_same_query!(invitations, users,); 18 | -------------------------------------------------------------------------------- /rustls/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustls-example" 3 | version = "3.0.0" 4 | authors = ["Nikolay Kim "] 5 | edition = "2018" 6 | 7 | [[bin]] 8 | name = "rustls-server" 9 | path = "src/main.rs" 10 | 11 | [dependencies] 12 | env_logger = "0.11" 13 | rustls = "0.23" 14 | rustls-pemfile = "2" 15 | ntex = { version = "3.0.0-pre.6", features = ["rustls", "tokio"] } 16 | ntex-files = "3.0.0" 17 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /template_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 | ### server 8 | 9 | ```bash 10 | cd examples/template_tera 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 | -------------------------------------------------------------------------------- /template_handlebars/src/README.md: -------------------------------------------------------------------------------- 1 | # Handlebars 2 | 3 | This is an example of how to use Ntex with the [Handlebars templating language](https://crates.io/crates/handlebars), which is currently the most popular crate that achieves this. After starting the server with `cargo run`, you may visit the following pages: 4 | 5 | - http://localhost:8080 6 | - http://localhost:8080/Emma/documents 7 | - http://localhost:8080/Bob/passwords 8 | -------------------------------------------------------------------------------- /awc_https/README.md: -------------------------------------------------------------------------------- 1 | The goal of this example is to show you how to use the ntex client (awc) 2 | for https related communication. As of ntex 2.0.0, one must be very 3 | careful about setting up https communication. **You could use the default 4 | awc api without configuring ssl but performance will be severely diminished**. 5 | 6 | This example downloads a 10MB image from wikipedia. 7 | 8 | To run: 9 | > curl http://localhost:3000 -o image.jpg 10 | -------------------------------------------------------------------------------- /websocket/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "websocket" 3 | version = "3.0.0" 4 | authors = ["Nikolay Kim "] 5 | edition = "2018" 6 | 7 | [[bin]] 8 | name = "websocket-server" 9 | path = "src/main.rs" 10 | 11 | [[bin]] 12 | name = "websocket-client" 13 | path = "src/client.rs" 14 | 15 | [dependencies] 16 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 17 | ntex-files = "3" 18 | env_logger = "0.11" 19 | futures = "0.3" 20 | -------------------------------------------------------------------------------- /websocket-chat/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "websocket-chat" 3 | version = "3.0.0" 4 | authors = ["Nikolay Kim "] 5 | edition = "2018" 6 | 7 | [[bin]] 8 | name = "websocket-chat-server" 9 | path = "src/main.rs" 10 | 11 | [dependencies] 12 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 13 | ntex-files = "3.0.0" 14 | 15 | rand = "0.8" 16 | futures = "0.3" 17 | env_logger = "0.11" 18 | serde = "1.0" 19 | serde_json = "1.0" 20 | -------------------------------------------------------------------------------- /async_db/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "async_db" 3 | version = "5.0.0" 4 | authors = ["Darin Gordon "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 9 | env_logger = "0.11" 10 | futures = "0.3" 11 | derive_more = "0.99" 12 | num_cpus = "1.13" 13 | r2d2 = "0.8" 14 | r2d2_sqlite = "0.14" 15 | rusqlite = "0.21" 16 | serde = { version = "1.0", features = ["derive"] } 17 | serde_json = "1.0" 18 | -------------------------------------------------------------------------------- /docker_sample/README.MD: -------------------------------------------------------------------------------- 1 | 2 | Build image: 3 | 4 | ```shell 5 | docker build -t docker_sample --force-rm --no-cache -f Dockerfile . 6 | ``` 7 | 8 | Run image: 9 | 10 | ```shell 11 | 12 | echo "== start sample_docker" 13 | docker run -d -p 5000:5000 docker_sample & 14 | docker ps 15 | 16 | echo "== wait 3s for startup" 17 | sleep 3s 18 | 19 | echo "== curl both routes" 20 | curl http://localhost:5000 21 | curl http://localhost:5000/again 22 | 23 | ``` -------------------------------------------------------------------------------- /multipart/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "multipart-example" 3 | version = "3.0.0" 4 | authors = ["Bevan Hunt "] 5 | edition = "2018" 6 | license = "MIT" 7 | description = "Simple file uploader in Ntex with Async/Await" 8 | keywords = ["ntex", "multipart"] 9 | repository = "https://github.com/ntex/examples" 10 | readme = "README.md" 11 | 12 | [dependencies] 13 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 14 | ntex-multipart = "3.0.0" 15 | futures = "0.3" 16 | -------------------------------------------------------------------------------- /async_ex2/src/bin/main.rs: -------------------------------------------------------------------------------- 1 | use ntex::web::{self, middleware, App}; 2 | 3 | use async_ex2::appconfig::config_app; 4 | 5 | #[ntex::main] 6 | async fn main() -> std::io::Result<()> { 7 | std::env::set_var("RUST_LOG", "ntex=info"); 8 | env_logger::init(); 9 | 10 | web::server(async || { 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 | -------------------------------------------------------------------------------- /template_yarte/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "template_yarte" 3 | version = "1.0.0" 4 | authors = ["Juan Aguilar Santillana "] 5 | publish = false 6 | edition = "2018" 7 | 8 | workspace = ".." 9 | 10 | [dependencies] 11 | env_logger = "0.11" 12 | yarte = { version = "0.15", features = ["html-min"] } 13 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 14 | 15 | [build-dependencies.yarte_helpers] 16 | version = "0.8" 17 | default-features = false 18 | features = ["config"] 19 | -------------------------------------------------------------------------------- /template_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 | -------------------------------------------------------------------------------- /async_pg/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "async_pg" 3 | version = "3.0.0" 4 | authors = ["dowwie "] 5 | edition = "2018" 6 | workspace = ".." 7 | 8 | [dependencies] 9 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 10 | config = "0.10" 11 | deadpool-postgres = { version = "0.10", features = ["serde"] } 12 | derive_more = "0.99" 13 | dotenv = "0.15" 14 | serde = { version = "1.0", features = ["derive"] } 15 | tokio-pg-mapper = "0.2" 16 | tokio-pg-mapper-derive = "0.2" 17 | tokio-postgres = "0.7" 18 | -------------------------------------------------------------------------------- /graphql-demo/src/db.rs: -------------------------------------------------------------------------------- 1 | use r2d2_mysql::mysql::{Opts, OptsBuilder}; 2 | use r2d2_mysql::MysqlConnectionManager; 3 | 4 | pub type Pool = r2d2::Pool; 5 | 6 | pub fn get_db_pool() -> Pool { 7 | let db_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set"); 8 | let opts = Opts::from_url(&db_url).unwrap(); 9 | let builder = OptsBuilder::from_opts(opts); 10 | let manager = MysqlConnectionManager::new(builder); 11 | r2d2::Pool::new(manager).expect("Failed to create DB Pool") 12 | } 13 | -------------------------------------------------------------------------------- /async_db/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 | -------------------------------------------------------------------------------- /todo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Dan Munckton "] 3 | name = "todo" 4 | version = "3.0.0" 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 9 | ntex-files = "3" 10 | ntex-session = "3" 11 | dotenv = "0.15" 12 | env_logger = "0.11" 13 | futures = "0.3" 14 | log = "0.4" 15 | serde = { version = "1.0", features = ["derive"] } 16 | serde_json = "1.0" 17 | tera = "1.0" 18 | 19 | [dependencies.diesel] 20 | features = ["postgres", "r2d2"] 21 | version = "1.3.2" 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /*/target/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock 8 | /*/Cargo.lock 9 | Cargo.lock 10 | 11 | # These are backup files generated by rustfmt 12 | **/*.rs.bk 13 | 14 | # intellij files 15 | .idea/** 16 | 17 | .history/ 18 | 19 | # VS Code workspace config 20 | .vscode 21 | 22 | # For multipart example 23 | upload.png 24 | -------------------------------------------------------------------------------- /unix-socket/README.md: -------------------------------------------------------------------------------- 1 | ## Unix domain socket example 2 | 3 | ```bash 4 | $ curl --unix-socket /tmp/ntex-uds.socket http://localhost/ 5 | Hello world! 6 | ``` 7 | 8 | Although this will only one thread for handling incoming connections 9 | according to the 10 | [documentation](https://docs.rs/ntex/latest/ntex/web/struct.HttpServer.html#method.bind_uds). 11 | 12 | And it does not delete the socket file (`/tmp/ntex-uds.socket`) when stopping 13 | the server so it will fail to start next time you run it unless you delete 14 | the socket file manually. 15 | -------------------------------------------------------------------------------- /diesel/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "diesel-example" 3 | version = "3.0.0" 4 | authors = [ 5 | "Nikolay Kim ", 6 | "Rob Ede ", 7 | ] 8 | edition = "2018" 9 | 10 | [dependencies] 11 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 12 | diesel = { version = "1.1", features = ["sqlite", "r2d2"] } 13 | dotenv = "0.15" 14 | env_logger = "0.11" 15 | futures = "0.3" 16 | r2d2 = "0.8" 17 | serde = { version = "1.0", features = ["derive"] } 18 | serde_json = "1.0" 19 | uuid = { version = "1", features = ["serde", "v4"] } 20 | -------------------------------------------------------------------------------- /websocket-lowlevel/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "websocket-lowlevel" 3 | version = "3.0.0" 4 | authors = ["Nikolay Kim "] 5 | edition = "2018" 6 | 7 | [[bin]] 8 | name = "websocket-lowlevel-server" 9 | path = "src/main.rs" 10 | 11 | [[bin]] 12 | name = "websocket-lowlevel-client" 13 | path = "src/client.rs" 14 | 15 | [dependencies] 16 | ntex = { version = "3.0.0-pre.6", features = ["tokio", "openssl"] } 17 | ntex-files = "3.0.0" 18 | env_logger = "0.11" 19 | futures = "0.3" 20 | 21 | openssl = "0.10" 22 | ntex-tls = { version= "3.0.0", features = ["openssl"] } -------------------------------------------------------------------------------- /json/client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # This script could be used for ntex 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 | -------------------------------------------------------------------------------- /static_index/src/main.rs: -------------------------------------------------------------------------------- 1 | use ntex::web::{self, middleware, App}; 2 | use ntex_files as fs; 3 | 4 | #[ntex::main] 5 | async fn main() -> std::io::Result<()> { 6 | std::env::set_var("RUST_LOG", "info"); 7 | env_logger::init(); 8 | 9 | web::server(async || { 10 | App::new() 11 | // enable logger 12 | .wrap(middleware::Logger::default()) 13 | .service( 14 | // static files 15 | fs::Files::new("/", "./static/").index_file("index.html"), 16 | ) 17 | }) 18 | .bind("127.0.0.1:8080")? 19 | .run() 20 | .await 21 | } 22 | -------------------------------------------------------------------------------- /docker_sample/src/main.rs: -------------------------------------------------------------------------------- 1 | use ntex::web::{self, App, HttpResponse}; 2 | 3 | #[web::get("/")] 4 | async fn index() -> HttpResponse { 5 | println!("GET: /"); 6 | HttpResponse::Ok().body("Hello world!") 7 | } 8 | 9 | #[web::get("/again")] 10 | async fn again() -> HttpResponse { 11 | println!("GET: /again"); 12 | HttpResponse::Ok().body("Hello world again!") 13 | } 14 | 15 | #[ntex::main] 16 | async fn main() -> std::io::Result<()> { 17 | println!("Starting ntex-web server"); 18 | 19 | web::server(async || App::new().service((index, again))) 20 | .bind("0.0.0.0:5000")? 21 | .run() 22 | .await 23 | } 24 | -------------------------------------------------------------------------------- /websocket/README.md: -------------------------------------------------------------------------------- 1 | # websocket 2 | 3 | Simple echo websocket server. 4 | 5 | ## Usage 6 | 7 | ### server 8 | 9 | ```bash 10 | cd examples/websocket 11 | cargo run --bin 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 | ```bash 22 | cd examples/websocket 23 | cargo run --bin 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 | -------------------------------------------------------------------------------- /graphql-demo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ntex-graphql-demo" 3 | version = "4.0.0" 4 | authors = ["Dwi Sulfahnur "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 11 | futures = "0.3" 12 | juniper = "0.14" 13 | mysql = "17" 14 | r2d2 = "0.8" 15 | r2d2_mysql = "17.0" 16 | dotenv = "0.15" 17 | env_logger = "0.11" 18 | log = "0.4" 19 | serde = { version = "1.0", features = ["derive"] } 20 | serde_json = "1.0" 21 | uuid = { version = "0.8", features = ["serde", "v4"] } 22 | -------------------------------------------------------------------------------- /websocket-lowlevel/README.md: -------------------------------------------------------------------------------- 1 | # websocket 2 | 3 | Simple echo websocket server. 4 | 5 | ## Usage 6 | 7 | ### server 8 | 9 | ```bash 10 | cd examples/websocket 11 | cargo run --bin 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 | ```bash 22 | cd examples/websocket 23 | cargo run --bin 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 | -------------------------------------------------------------------------------- /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 | 7 | 2. sending a SIGINT signal to the server (control-c) 8 | - ntex server natively supports SIGINT 9 | 10 | 11 | ## Usage 12 | 13 | ### Running The Server 14 | 15 | ```bash 16 | cargo run --bin shutdown-server 17 | 18 | # Starting 8 workers 19 | # Starting "ntex-service-127.0.0.1:8080" service on 127.0.0.1:8080 20 | ``` 21 | 22 | ### Available Routes 23 | 24 | - [GET /hello](http://localhost:8080/hello) 25 | - Regular hello world route 26 | - [POST /stop](http://localhost:8080/stop) 27 | - Calling this will shutdown the server and exit 28 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /simple-auth-server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "simple-auth-server" 3 | version = "3.0.0" 4 | authors = ["mygnu "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ntex-identity = "3" 9 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 10 | argonautica = "0.2" 11 | chrono = { version = "0.4.6", features = ["serde"] } 12 | derive_more = "0.99" 13 | diesel = { version = "1.4", features = ["postgres", "uuidv07", "r2d2", "chrono"] } 14 | dotenv = "0.15" 15 | env_logger = "0.11" 16 | futures = "0.3" 17 | r2d2 = "0.8" 18 | lazy_static = "1.3" 19 | serde = { version = "1.0", features = ["derive"] } 20 | serde_json = "1.0" 21 | sparkpost = "0.5" 22 | uuid = { version = "0.8", features = ["serde", "v4"] } 23 | time = "0.3" -------------------------------------------------------------------------------- /websocket-tcp-chat/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chat-example" 3 | version = "3.0.0" 4 | authors = ["Nikolay Kim "] 5 | edition = "2018" 6 | 7 | [[bin]] 8 | name = "websocket-tcp-chat-server" 9 | path = "src/main.rs" 10 | 11 | [[bin]] 12 | name = "websocket-tcp-chat-client" 13 | path = "src/client-ws.rs" 14 | 15 | [[bin]] 16 | name = "websocket-tcp-chat-tcp-client" 17 | path = "src/client-tcp.rs" 18 | 19 | [dependencies] 20 | ntex = { version = "3.0.0-pre.6", features = ["tokio"] } 21 | ntex-mqtt = "6.2.0" 22 | ntex-amqp = "5.1.0" 23 | ntex-files = "3.0.0" 24 | 25 | rand = "0.8" 26 | byteorder = "1.4" 27 | futures = "0.3" 28 | env_logger = "0.11" 29 | serde = { version = "1.0", features = ["derive"] } 30 | serde_json = "1.0" 31 | -------------------------------------------------------------------------------- /async_ex2/src/handlers/parts.rs: -------------------------------------------------------------------------------- 1 | use ntex::web::{self, Error, HttpResponse}; 2 | 3 | use crate::common::{Part, Product}; 4 | 5 | pub async fn get_parts( 6 | _query: web::types::Query>, 7 | ) -> Result { 8 | Ok(HttpResponse::Ok().finish()) 9 | } 10 | 11 | pub async fn add_part( 12 | _new_part: web::types::Json, 13 | ) -> Result { 14 | Ok(HttpResponse::Ok().finish()) 15 | } 16 | 17 | pub async fn get_part_detail( 18 | _id: web::types::Path, 19 | ) -> Result { 20 | Ok(HttpResponse::Ok().finish()) 21 | } 22 | 23 | pub async fn remove_part(_id: web::types::Path) -> Result { 24 | Ok(HttpResponse::Ok().finish()) 25 | } 26 | -------------------------------------------------------------------------------- /graphql-demo/README.md: -------------------------------------------------------------------------------- 1 | # ntex-graphql-demo 2 | 3 | GraphQL Implementation in Rust using Ntex, Juniper, and Mysql as Database 4 | 5 | # Prerequites 6 | - Rust Installed 7 | - MySql as Database 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 on there. 14 | 15 | # Run 16 | 17 | 18 | ```sh 19 | # go to the root dir 20 | cd graphql-demo 21 | 22 | # Run 23 | cargo run 24 | ``` 25 | 26 | ### GraphQL Playground 27 | 28 | http://127.0.0.1:8080/graphiql 29 | -------------------------------------------------------------------------------- /graphql-demo/src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate juniper; 3 | 4 | use ntex::web::{self, middleware, App}; 5 | 6 | use crate::db::get_db_pool; 7 | use crate::handlers::register; 8 | 9 | mod db; 10 | mod handlers; 11 | mod schemas; 12 | 13 | #[ntex::main] 14 | async fn main() -> std::io::Result<()> { 15 | dotenv::dotenv().ok(); 16 | std::env::set_var("RUST_LOG", "ntex=info,info"); 17 | env_logger::init(); 18 | 19 | let pool = get_db_pool(); 20 | 21 | web::server(async move || { 22 | App::new() 23 | .state(pool.clone()) 24 | .wrap(middleware::Logger::default()) 25 | .configure(register) 26 | .default_service(web::to(|| async { "404" })) 27 | }) 28 | .bind("127.0.0.1:8080")? 29 | .run() 30 | .await 31 | } 32 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /websocket-tcp-chat/src/main.rs: -------------------------------------------------------------------------------- 1 | mod codec; 2 | mod server; 3 | mod tcp; 4 | mod web; 5 | 6 | #[ntex::main] 7 | async fn main() -> std::io::Result<()> { 8 | std::env::set_var("RUST_LOG", "ntex=info,server=trace"); 9 | env_logger::init(); 10 | 11 | println!("Started chat server"); 12 | 13 | // Start chat server 14 | let server = server::start(); 15 | 16 | let ws_srv = server.clone(); 17 | let tcp_srv = server.clone(); 18 | 19 | // Create server 20 | ntex::server::build() 21 | .bind("tcp", "127.0.0.1:12345", async move |_| { 22 | tcp::server(tcp_srv.clone()) 23 | })? 24 | .bind("websockets", "127.0.0.1:8080", async move |_| { 25 | web::server(ws_srv.clone()) 26 | })? 27 | .run() 28 | .await 29 | } 30 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /jsonrpc/README.md: -------------------------------------------------------------------------------- 1 | A simple demo for building a `JSONRPC over HTTP` server in [ntex](https://github.com/ntex-rs/ntex). 2 | 3 | # Server 4 | 5 | ```sh 6 | $ cargo run 7 | # Starting server on 127.0.0.1:8080 8 | ``` 9 | 10 | # Client 11 | 12 | **curl** 13 | 14 | ```sh 15 | $ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0", "method": "ping", "params": [], "id": 1}' http://127.0.0.1:8080 16 | # {"jsonrpc":"2.0","result":"pong","error":null,"id":1} 17 | ``` 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 | -------------------------------------------------------------------------------- /async_ex1/README.md: -------------------------------------------------------------------------------- 1 | This is a contrived example intended to illustrate a few important Ntex features. 2 | 3 | *Imagine* that you have a process that involves 3 steps. The steps here 4 | are dumb in that they do nothing other than call an 5 | httpbin endpoint that returns the json that was posted to it. The intent here 6 | is to illustrate how to chain these steps together as futures and return 7 | a final result in a response. 8 | 9 | Ntex features illustrated here include: 10 | 11 | 1. handling json input param 12 | 2. validating user-submitted parameters using the 'validator' crate 13 | 2. Ntex client features: 14 | - POSTing json body 15 | 3. chaining futures into a single response used by an asynch endpoint 16 | 17 | 18 | Example query from the command line using httpie: 19 | ```echo '{"id":"1", "name": "JohnDoe"}' | http 127.0.0.1:8080/something``` 20 | -------------------------------------------------------------------------------- /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/README.md: -------------------------------------------------------------------------------- 1 | # basics 2 | 3 | ## Usage 4 | 5 | ### server 6 | 7 | ```bash 8 | cd examples/basics 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/static/index.html) 16 | - [http://localhost:8080/async-body/bob](http://localhost:8080/async-body/bob) 17 | - [http://localhost:8080/user/bob/](http://localhost:8080/user/bob/) text/plain download 18 | - [http://localhost:8080/test](http://localhost:8080/test) (return status switch GET or POST or other) 19 | - [http://localhost:8080/favicon](http://localhost:8080/static/favicon.htmicol) 20 | - [http://localhost:8080/welcome](http://localhost:8080/static/welcome.html) 21 | - [http://localhost:8080/notexit](http://localhost:8080/static/404.html) display 404 page 22 | - [http://localhost:8080/error](http://localhost:8080/error) Panic after request 23 | -------------------------------------------------------------------------------- /tokio/README.md: -------------------------------------------------------------------------------- 1 | # basics 2 | 3 | ## Usage 4 | 5 | ### server 6 | 7 | ```bash 8 | cd examples/basics 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/static/index.html) 16 | - [http://localhost:8080/async-body/bob](http://localhost:8080/async-body/bob) 17 | - [http://localhost:8080/user/bob/](http://localhost:8080/user/bob/) text/plain download 18 | - [http://localhost:8080/test](http://localhost:8080/test) (return status switch GET or POST or other) 19 | - [http://localhost:8080/favicon](http://localhost:8080/static/favicon.htmicol) 20 | - [http://localhost:8080/welcome](http://localhost:8080/static/welcome.html) 21 | - [http://localhost:8080/notexit](http://localhost:8080/static/404.html) display 404 page 22 | - [http://localhost:8080/error](http://localhost:8080/error) Panic after request 23 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /server-sent-events/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Server-sent events 7 | 13 | 14 | 15 |
16 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /unix-socket/src/main.rs: -------------------------------------------------------------------------------- 1 | use ntex::web::{self, middleware, App, HttpRequest}; 2 | 3 | async fn index(_req: HttpRequest) -> &'static str { 4 | "Hello world!" 5 | } 6 | 7 | #[ntex::main] 8 | #[cfg(unix)] 9 | async fn main() -> std::io::Result<()> { 10 | ::std::env::set_var("RUST_LOG", "info"); 11 | env_logger::init(); 12 | 13 | web::server(async || { 14 | App::new() 15 | // enable logger 16 | .wrap(middleware::Logger::default()) 17 | .service(( 18 | web::resource("/index.html") 19 | .route(web::get().to(|| async { "Hello world!" })), 20 | web::resource("/").to(index), 21 | )) 22 | }) 23 | .bind_uds("/tmp/ntex-uds.socket")? 24 | .run() 25 | .await 26 | } 27 | 28 | #[cfg(not(unix))] 29 | fn main() -> std::io::Result<()> { 30 | println!("not supported"); 31 | Ok(()) 32 | } 33 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /cookie-auth/README.md: -------------------------------------------------------------------------------- 1 | # cookie-auth 2 | 3 | Testing with cookie auth with [curl](https://curl.haxx.se). 4 | 5 | Login: 6 | 7 | curl -v -b "auth-example=user1" -X POST http://localhost:8080/login 8 | < HTTP/1.1 302 Found 9 | < set-cookie: auth-example=GRm2Vku0UpFbJ3CNTKbndzIYHVGi8wc8eoXm/Axtf2BO; HttpOnly; Path=/ 10 | < location: / 11 | 12 | Uses a POST request with a Useridentity `user1`. A cookie is set and a redirect to home `/` follows. 13 | 14 | Get: 15 | 16 | Now with the cookie `auth-example` sent in a GET request, the `user1` is recognized. 17 | 18 | curl -v -b "auth-example=GRm2Vku0UpFbJ3CNTKbndzIYHVGi8wc8eoXm/Axtf2BO" http://localhost:8080/ 19 | * Connected to localhost (127.0.0.1) port 8080 (#0) 20 | > GET / HTTP/1.1 21 | > Host: localhost:8080 22 | > Cookie: auth-example=GRm2Vku0UpFbJ3CNTKbndzIYHVGi8wc8eoXm/Axtf2BO 23 | > 24 | < HTTP/1.1 200 OK 25 | < 26 | Hello user1 27 | -------------------------------------------------------------------------------- /openssl/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICljCCAX4CCQDztMNlxk6oeTANBgkqhkiG9w0BAQsFADANMQswCQYDVQQIDAJj 3 | YTAeFw0xOTAzMDcwNzEyNThaFw0yMDAzMDYwNzEyNThaMA0xCzAJBgNVBAgMAmNh 4 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0GMP3YzDVFWgNhRiHnfe 5 | d192131Zi23p8WiutneD9I5WO42c79fOXsxLWn+2HSqPvCPHIBLoMX8o9lgCxt2P 6 | /JUCAWbrE2EuvhkMrWk6/q7xB211XZYfnkqdt7mA0jMUC5o32AX3ew456TAq5P8Y 7 | dq9H/qXdRtAvKD0QdkFfq8ePCiqOhcqacZ/NWva7R4HdgTnbL1DRQjGBXszI07P9 8 | 1yw8GOym46uxNHRujQp3lYEhc1V3JTF9kETpSBHyEAkQ8WHxGf8UBHDhh7hcc+KI 9 | JHMlVYy5wDv4ZJeYsY1rD6/n4tyd3r0yzBM57UGf6qrVZEYmLB7Jad+8Df5vIoGh 10 | WwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQB1DEu9NiShCfQuA17MG5O0Jr2/PS1z 11 | /+HW7oW15WXpqDKOEJalid31/Bzwvwq0bE12xKE4ZLdbqJHmJTdSUoGfOfBZKka6 12 | R2thOjqH7hFvxjfgS7kBy5BrRZewM9xKIJ6zU6+6mxR64x9vmkOmppV0fx5clZjH 13 | c7qn5kSNWTMsFbjPnb5BeJJwZdqpMLs99jgoMvGtCUmkyVYODGhh65g6tR9kIPvM 14 | zu/Cw122/y7tFfkuknMSYwGEYF3XcZpXt54a6Lu5hk6PuOTsK+7lC+HX7CSF1dpv 15 | u1szL5fDgiCBFCnyKeOqF61mxTCUht3U++37VDFvhzN1t6HIVTYm2JJ7 16 | -----END CERTIFICATE----- 17 | -------------------------------------------------------------------------------- /simple-auth-server/src/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::ServiceError; 2 | use argonautica::{Hasher, Verifier}; 3 | 4 | lazy_static::lazy_static! { 5 | pub static ref SECRET_KEY: String = std::env::var("SECRET_KEY").unwrap_or_else(|_| "0123".repeat(8)); 6 | } 7 | 8 | // WARNING THIS IS ONLY FOR DEMO PLEASE DO MORE RESEARCH FOR PRODUCTION USE 9 | pub fn hash_password(password: &str) -> Result { 10 | Hasher::default() 11 | .with_password(password) 12 | .with_secret_key(SECRET_KEY.as_str()) 13 | .hash() 14 | .map_err(|err| { 15 | dbg!(err); 16 | ServiceError::InternalServerError 17 | }) 18 | } 19 | 20 | pub fn verify(hash: &str, password: &str) -> Result { 21 | Verifier::default() 22 | .with_hash(hash) 23 | .with_password(password) 24 | .with_secret_key(SECRET_KEY.as_str()) 25 | .verify() 26 | .map_err(|err| { 27 | dbg!(err); 28 | ServiceError::Unauthorized 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /todo/src/session.rs: -------------------------------------------------------------------------------- 1 | use ntex::web::Error; 2 | use ntex_session::Session; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | const FLASH_KEY: &str = "flash"; 6 | 7 | pub fn set_flash(session: &Session, flash: FlashMessage) -> Result<(), Error> { 8 | session.set(FLASH_KEY, flash) 9 | } 10 | 11 | pub fn get_flash(session: &Session) -> Result, Error> { 12 | 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 | -------------------------------------------------------------------------------- /middleware/README.md: -------------------------------------------------------------------------------- 1 | # middleware examples 2 | 3 | This example showcases a bunch of different uses of middlewares. See also the [Middleware guide](https://ntex.rs/docs/middleware).. 4 | 5 | ## Usage 6 | 7 | ```bash 8 | cd middleware 9 | cargo run 10 | # Started http server: 127.0.0.1:8080 11 | ``` 12 | 13 | Look in `src/main.rs` and comment the different middlewares in/out to see how 14 | they function. 15 | 16 | ## Middlewares 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 ntex middleware. 33 | There is a second version of the same middleware using `wrap_fn` which shows how easily a middleware can be implemented in ntex. 34 | -------------------------------------------------------------------------------- /async_db/README.md: -------------------------------------------------------------------------------- 1 | Getting started using databases with Ntex, asynchronously. 2 | 3 | ## Usage 4 | 5 | ### init database sqlite 6 | 7 | From the root directory of this project: 8 | ```bash 9 | bash db/setup_db.sh 10 | ``` 11 | 12 | This creates a sqlite database, weather.db, in the root. 13 | 14 | 15 | ### server 16 | 17 | ```bash 18 | # if ubuntu : sudo apt-get install libsqlite3-dev 19 | # if fedora : sudo dnf install libsqlite3x-devel 20 | cargo run (or ``cargo watch -x run``) 21 | # Started http server: 127.0.0.1:8080 22 | ``` 23 | 24 | ### web client 25 | 26 | [http://127.0.0.1:8080/asyncio_weather](http://127.0.0.1:8080/asyncio_weather) 27 | 28 | [http://127.0.0.1:8080/parallel_weather](http://127.0.0.1:8080/parallel_weather) 29 | 30 | 31 | ### sqlite client 32 | 33 | ```bash 34 | # if ubuntu : sudo apt-get install sqlite3 35 | # if fedora : sudo dnf install sqlite3x 36 | sqlite3 weather.db 37 | sqlite> .tables 38 | sqlite> select * from nyc_weather; 39 | ``` 40 | 41 | ## Dependencies 42 | 43 | On Ubuntu 19.10: 44 | 45 | ``` 46 | sudo apt install libsqlite3-dev 47 | ``` 48 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "3" 3 | members = [ 4 | "async_db", 5 | "async_ex1", 6 | "async_ex2", 7 | "async_pg", 8 | "awc_https", 9 | "basics", 10 | "cookie-auth", 11 | "cookie-session", 12 | # "diesel", 13 | "docker_sample", 14 | "error_handling", 15 | "form", 16 | "graphql-demo", 17 | "hello-world", 18 | "http-proxy", 19 | "json", 20 | "json_error", 21 | "jsonrpc", 22 | "juniper", 23 | "middleware", 24 | "multipart", 25 | "openssl", 26 | "r2d2", 27 | "run-in-thread", 28 | "rustls", 29 | "server-sent-events", 30 | "shutdown-server", 31 | # "simple-auth-server", 32 | "state", 33 | "static_index", 34 | "template_askama", 35 | "template_handlebars", 36 | "template_tera", 37 | "template_yarte", 38 | "todo", 39 | "tokio", 40 | "unix-socket", 41 | "websocket", 42 | "websocket-lowlevel", 43 | "websocket-chat", 44 | "websocket-tcp-chat", 45 | ] 46 | 47 | [workspace.package] 48 | edition = "2024" 49 | rust-version = "1.85" 50 | 51 | [profile.release] 52 | panic = 'abort' 53 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /template_askama/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use askama::Template; 4 | use ntex::web::{self, App, Error, HttpResponse}; 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 | #[web::get("/")] 18 | async fn index( 19 | query: web::types::Query>, 20 | ) -> Result { 21 | let s = if let Some(name) = query.get("name") { 22 | UserTemplate { 23 | name, 24 | text: "Welcome!", 25 | } 26 | .render() 27 | .unwrap() 28 | } else { 29 | Index.render().unwrap() 30 | }; 31 | Ok(HttpResponse::Ok().content_type("text/html").body(s)) 32 | } 33 | 34 | #[ntex::main] 35 | async fn main() -> std::io::Result<()> { 36 | // start http server 37 | web::server(async move || App::new().service(index)) 38 | .bind("127.0.0.1:8080")? 39 | .run() 40 | .await 41 | } 42 | -------------------------------------------------------------------------------- /server-sent-events/drain.js: -------------------------------------------------------------------------------- 1 | const http = require('http') 2 | 3 | let drop_goal = 10_000; 4 | let dropped = 0; 5 | 6 | let query = { 7 | host: 'localhost', 8 | port: 8080, 9 | path: '/events' 10 | } 11 | 12 | setInterval(() => { 13 | if (dropped < drop_goal) { 14 | let request = http.get(query, response => { 15 | response.on('data', data => { 16 | if (data.includes("data: connected\n")) { 17 | // drop connection after welcome message 18 | dropped += 1; 19 | request.abort() 20 | } 21 | }) 22 | }) 23 | .on('error', () => {}) 24 | } 25 | }, 1) 26 | 27 | setInterval(() => { 28 | http.get('http://localhost:8080/', () => print_status(true)) 29 | .setTimeout(100, () => print_status(false)) 30 | .on('error', () => {}) 31 | }, 20) 32 | 33 | function print_status(accepting_connections) { 34 | process.stdout.write("\r\x1b[K"); 35 | process.stdout.write(`Connections dropped: ${dropped}, accepting connections: ${accepting_connections}`); 36 | } 37 | -------------------------------------------------------------------------------- /simple-auth-server/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ntex - Auth App 7 | 8 | 9 | 10 | 11 | 12 | 19 | 20 | 21 | 32 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | sudo: false 3 | dist: trusty 4 | 5 | services: 6 | - redis-server 7 | 8 | cache: 9 | apt: true 10 | directories: 11 | - $HOME/.cargo 12 | - $HOME/.rustup 13 | 14 | before_cache: 15 | - rm -rf $HOME/.cargo/registry 16 | 17 | matrix: 18 | include: 19 | - rust: stable 20 | - rust: beta 21 | - rust: nightly 22 | allow_failures: 23 | - rust: nightly 24 | 25 | env: 26 | global: 27 | # - RUSTFLAGS="-C link-dead-code" 28 | - OPENSSL_VERSION=openssl-1.0.2 29 | 30 | before_install: 31 | - sudo add-apt-repository -y ppa:0k53d-karl-f830m/openssl 32 | - sudo apt-get update -qq 33 | - sudo apt-get install -qq libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev 34 | 35 | # Add clippy 36 | before_script: 37 | - | 38 | if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then 39 | rustup component add clippy --toolchain=nightly 40 | fi 41 | - export PATH=$PATH:~/.cargo/bin 42 | 43 | script: 44 | - cargo check --all 45 | - cargo test --all --no-fail-fast 46 | - | 47 | if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then 48 | cargo clippy 49 | fi 50 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /websocket-chat/README.md: -------------------------------------------------------------------------------- 1 | # Websocket chat example 2 | 3 | This is extension of the 4 | [ntex chat example](https://github.com/ntex-rs/examples/tree/master/websocket-chat) 5 | 6 | Added features: 7 | 8 | * Browser WebSocket client 9 | * Chat server runs in separate thread 10 | * Tcp listener runs in separate thread 11 | 12 | ## Server 13 | 14 | Chat server listens for incoming tcp connections. Server can access several types of message: 15 | 16 | * `/list` - list all available rooms 17 | * `/join name` - join room, if room does not exist, create new one 18 | * `/name name` - set session name 19 | * `some message` - just string, send message to all peers in same room 20 | * client has to send heartbeat `Ping` messages, if server does not receive a heartbeat message for 10 seconds connection gets dropped 21 | 22 | To start server use command: `cargo run --bin websocket-chat-server` 23 | 24 | ## Client 25 | 26 | Client connects to server. Reads input from stdin and sends to server. 27 | 28 | To run client use command: `cargo run --bin websocket-chat-client` 29 | 30 | ## WebSocket Browser Client 31 | 32 | Open url: [http://localhost:8080/](http://localhost:8080/) 33 | -------------------------------------------------------------------------------- /websocket-tcp-chat/README.md: -------------------------------------------------------------------------------- 1 | # Websocket chat example 2 | 3 | This is extension of the 4 | [ntex chat example](https://github.com/ntex-rs/examples/tree/master/websocket-chat) 5 | 6 | Added features: 7 | 8 | * Browser WebSocket client 9 | * Chat server runs in separate thread 10 | * Tcp listener runs in separate thread 11 | 12 | ## Server 13 | 14 | Chat server listens for incoming tcp connections. Server can access several types of message: 15 | 16 | * `/list` - list all available rooms 17 | * `/join name` - join room, if room does not exist, create new one 18 | * `/name name` - set session name 19 | * `some message` - just string, send message to all peers in same room 20 | * client has to send heartbeat `Ping` messages, if server does not receive a heartbeat message for 10 seconds connection gets dropped 21 | 22 | To start server use command: `cargo run --bin websocket-tcp-server` 23 | 24 | ## Client 25 | 26 | Client connects to server. Reads input from stdin and sends to server. 27 | 28 | To run client use command: `cargo run --bin websocket-tcp-client` 29 | 30 | ## WebSocket Browser Client 31 | 32 | Open url: [http://localhost:8080/](http://localhost:8080/) 33 | -------------------------------------------------------------------------------- /middleware/src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code, clippy::type_complexity)] 2 | 3 | use ntex::web; 4 | 5 | mod read_request_body; 6 | mod read_response_body; 7 | mod redirect; 8 | mod simple; 9 | 10 | #[ntex::main] 11 | async fn main() -> std::io::Result<()> { 12 | std::env::set_var("RUST_LOG", "debug"); 13 | env_logger::init(); 14 | 15 | web::server(async || { 16 | web::App::new() 17 | .filter(|req: web::WebRequest<_>| async move { 18 | println!("Hi from start. You requested: {}", req.path()); 19 | Ok(req) 20 | }) 21 | .wrap(simple::SayHi) 22 | .wrap(read_request_body::Logging) 23 | .wrap(read_response_body::Logging) 24 | .wrap(redirect::CheckLogin) 25 | .service(web::resource("/login").to(|| async { 26 | "You are on /login. Go to src/redirect.rs to change this behavior." 27 | })) 28 | .service(web::resource("/").to(|| async { 29 | "Hello, middleware! Check the console where the server is run." 30 | })) 31 | }) 32 | .bind("127.0.0.1:8080")? 33 | .run() 34 | .await 35 | } 36 | -------------------------------------------------------------------------------- /server-sent-events/README.md: -------------------------------------------------------------------------------- 1 | # ntex-sse 2 | Example of server-sent events, aka `EventSource`, with ntex. 3 | 4 | ```sh 5 | cargo run 6 | ``` 7 | 8 | Open http://localhost:8080/ with a browser, then send events with another HTTP client: 9 | 10 | ```sh 11 | curl localhost:8080/broadcast/my_message 12 | ``` 13 | 14 | *my_message* should appear in the browser with a timestamp. 15 | 16 | ## Performance 17 | This implementation serve thousand of clients on a 2013 macbook air without problems. 18 | 19 | Run [benchmark.js](benchmark.js) to benchmark your own system: 20 | 21 | ```sh 22 | $ node benchmark.js 23 | Connected: 1000, connection time: 867 ms, total broadcast time: 23 ms^C⏎ 24 | ``` 25 | 26 | ### Error *Too many open files* 27 | You may be limited to a maximal number of connections (open file descriptors). Setting maximum number of open file descriptors to 2048: 28 | 29 | ```sh 30 | ulimit -n 2048 31 | ``` 32 | 33 | Test maximum number of open connections with [drain.js](drain.js): 34 | 35 | ```sh 36 | $ node drain.js 37 | Connections dropped: 5957, accepting connections: false^C⏎ 38 | ``` 39 | 40 | _Accepting connections_ indicates wheter resources for the server have been exhausted. -------------------------------------------------------------------------------- /async_ex2/src/appconfig.rs: -------------------------------------------------------------------------------- 1 | use ntex::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").service(( 9 | web::resource("") 10 | .route(web::get().to(products::get_products)) 11 | .route(web::post().to(products::add_product)), 12 | web::scope("/{product_id}").service(( 13 | web::resource("") 14 | .route(web::get().to(products::get_product_detail)) 15 | .route(web::delete().to(products::remove_product)), 16 | web::scope("/parts").service(( 17 | web::resource("") 18 | .route(web::get().to(parts::get_parts)) 19 | .route(web::post().to(parts::add_part)), 20 | web::resource("/{part_id}") 21 | .route(web::get().to(parts::get_part_detail)) 22 | .route(web::delete().to(parts::remove_part)), 23 | )), 24 | )), 25 | )), 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /todo/README.md: -------------------------------------------------------------------------------- 1 | # ntex-todo 2 | 3 | A port of the [Rocket Todo example](https://github.com/SergioBenitez/Rocket/tree/master/examples/todo) into [ntex](https://ntex.rs/). Except this uses PostgreSQL instead of SQLite. 4 | 5 | # Usage 6 | 7 | ## Prerequisites 8 | 9 | * Rust >= 1.26 10 | * PostgreSQL >= 9.5 11 | 12 | ## Change into the project sub-directory 13 | 14 | All instructions assume you have changed into this folder: 15 | 16 | ```bash 17 | cd examples/todo 18 | ``` 19 | 20 | ## Set up the database 21 | 22 | Install the [diesel](http://diesel.rs) command-line tool including the `postgres` feature: 23 | 24 | ```bash 25 | cargo install diesel_cli --no-default-features --features postgres 26 | ``` 27 | 28 | Check the contents of the `.env` file. If your database requires a password, update `DATABASE_URL` to be of the form: 29 | 30 | ```.env 31 | DATABASE_URL=postgres://username:password@localhost/ntex_todo 32 | ``` 33 | 34 | Then to create and set-up the database run: 35 | 36 | ```bash 37 | diesel database setup 38 | ``` 39 | 40 | ## Run the application 41 | 42 | To run the application execute: 43 | 44 | ```bash 45 | cargo run 46 | ``` 47 | 48 | Then to view it in your browser navigate to: [http://localhost:8088/](http://localhost:8088/) 49 | -------------------------------------------------------------------------------- /juniper/README.md: -------------------------------------------------------------------------------- 1 | # Juniper 2 | 3 | [Juniper](https://github.com/graphql-rust/juniper) integration for Ntex 4 | 5 | ## Usage 6 | 7 | ### server 8 | 9 | ```bash 10 | cd examples/juniper 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://127.0.0.1:8080/graphiql](http://127.0.0.1:8080/graphiql) 18 | 19 | _Query example:_ 20 | ```graphql 21 | { 22 | human(id: "1234") { 23 | name 24 | appearsIn 25 | homePlanet 26 | } 27 | } 28 | ``` 29 | _Result:_ 30 | ```json 31 | { 32 | "data": { 33 | "human": { 34 | "name": "Luke", 35 | "appearsIn": [ 36 | "NEW_HOPE" 37 | ], 38 | "homePlanet": "Mars" 39 | } 40 | } 41 | } 42 | ``` 43 | 44 | _Mutation example:_ 45 | 46 | ```graphql 47 | mutation { 48 | createHuman(newHuman: {name: "Fresh Kid Ice", appearsIn: EMPIRE, homePlanet: "earth"}) { 49 | id 50 | name 51 | appearsIn 52 | homePlanet 53 | } 54 | } 55 | ``` 56 | 57 | _Result:_ 58 | ```json 59 | { 60 | "data": { 61 | "createHuman": { 62 | "id": "1234", 63 | "name": "Fresh Kid Ice", 64 | "appearsIn": [ 65 | "EMPIRE" 66 | ], 67 | "homePlanet": "earth" 68 | } 69 | } 70 | } 71 | ``` 72 | -------------------------------------------------------------------------------- /diesel/src/actions.rs: -------------------------------------------------------------------------------- 1 | use diesel::prelude::*; 2 | use uuid::Uuid; 3 | 4 | use crate::models; 5 | 6 | /// Run query using Diesel to insert a new database row and return the result. 7 | pub fn find_user_by_uid( 8 | uid: Uuid, 9 | conn: &SqliteConnection, 10 | ) -> Result, diesel::result::Error> { 11 | use crate::schema::users::dsl::*; 12 | 13 | let user = users 14 | .filter(id.eq(uid.to_string())) 15 | .first::(conn) 16 | .optional()?; 17 | 18 | Ok(user) 19 | } 20 | 21 | /// Run query using Diesel to insert a new database row and return the result. 22 | pub fn insert_new_user( 23 | // prevent collision with `name` column imported inside the function 24 | nm: &str, 25 | conn: &SqliteConnection, 26 | ) -> Result { 27 | // It is common when using Diesel with Ntex to import schema-related 28 | // modules inside a function's scope (rather than the normal module's scope) 29 | // to prevent import collisions and namespace pollution. 30 | use crate::schema::users::dsl::*; 31 | 32 | let new_user = models::User { 33 | id: Uuid::new_v4().to_string(), 34 | name: nm.to_owned(), 35 | }; 36 | 37 | diesel::insert_into(users).values(&new_user).execute(conn)?; 38 | 39 | Ok(new_user) 40 | } 41 | -------------------------------------------------------------------------------- /json/README.md: -------------------------------------------------------------------------------- 1 | # json 2 | 3 | Json's `Getting Started` guide using json (serde-json or json-rust) for Ntex 4 | 5 | ## Usage 6 | 7 | ### server 8 | 9 | ```bash 10 | cd examples/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 | -------------------------------------------------------------------------------- /todo/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /graphql-demo/src/handlers.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use juniper::http::graphiql::graphiql_source; 4 | use juniper::http::GraphQLRequest; 5 | use ntex::web::{self, Error, HttpResponse}; 6 | 7 | use crate::db::Pool; 8 | use crate::schemas::root::{create_schema, Context, Schema}; 9 | 10 | pub async fn graphql( 11 | pool: web::types::State, 12 | schema: web::types::State>, 13 | data: web::types::Json, 14 | ) -> Result { 15 | let schema = (*schema).clone(); 16 | let ctx = Context { 17 | dbpool: pool.get_ref().to_owned(), 18 | }; 19 | let res = web::block(move || { 20 | let res = data.execute(&schema, &ctx); 21 | serde_json::to_string(&res) 22 | }) 23 | .await?; 24 | 25 | Ok(HttpResponse::Ok() 26 | .content_type("application/json") 27 | .body(res)) 28 | } 29 | 30 | pub async fn graphql_playground() -> HttpResponse { 31 | HttpResponse::Ok() 32 | .content_type("text/html; charset=utf-8") 33 | .body(graphiql_source("/graphql")) 34 | } 35 | 36 | pub fn register(config: &mut web::ServiceConfig) { 37 | let schema = std::sync::Arc::new(create_schema()); 38 | config 39 | .state(schema) 40 | .route("/graphql", web::post().to(graphql)) 41 | .route("/graphiql", web::get().to(graphql_playground)); 42 | } 43 | -------------------------------------------------------------------------------- /graphql-demo/src/schemas/product.rs: -------------------------------------------------------------------------------- 1 | use mysql::{from_row, params, Error as DBError, Row}; 2 | 3 | use crate::schemas::root::Context; 4 | use crate::schemas::user::User; 5 | 6 | /// Product 7 | #[derive(Default, Debug)] 8 | pub struct Product { 9 | pub id: String, 10 | pub user_id: String, 11 | pub name: String, 12 | pub price: f64, 13 | } 14 | 15 | #[juniper::object(Context = Context)] 16 | impl Product { 17 | fn id(&self) -> &str { 18 | &self.id 19 | } 20 | fn user_id(&self) -> &str { 21 | &self.user_id 22 | } 23 | fn name(&self) -> &str { 24 | &self.name 25 | } 26 | fn price(&self) -> f64 { 27 | self.price 28 | } 29 | 30 | fn user(&self, context: &Context) -> Option { 31 | let mut conn = context.dbpool.get().unwrap(); 32 | let user: Result, DBError> = conn.first_exec( 33 | "SELECT * FROM user WHERE id=:id", 34 | params! {"id" => &self.user_id}, 35 | ); 36 | if let Err(err) = user { 37 | None 38 | } else { 39 | let (id, name, email) = from_row(user.unwrap().unwrap()); 40 | Some(User { id, name, email }) 41 | } 42 | } 43 | } 44 | 45 | #[derive(GraphQLInputObject)] 46 | #[graphql(description = "Product Input")] 47 | pub struct ProductInput { 48 | pub user_id: String, 49 | pub name: String, 50 | pub price: f64, 51 | } 52 | -------------------------------------------------------------------------------- /todo/src/model.rs: -------------------------------------------------------------------------------- 1 | use diesel::pg::PgConnection; 2 | use diesel::prelude::*; 3 | use serde::Serialize; 4 | 5 | use crate::schema::{ 6 | tasks, 7 | tasks::dsl::{completed as task_completed, tasks as all_tasks}, 8 | }; 9 | 10 | #[derive(Debug, Insertable)] 11 | #[table_name = "tasks"] 12 | pub struct NewTask { 13 | pub description: String, 14 | } 15 | 16 | #[derive(Debug, Queryable, Serialize)] 17 | pub struct Task { 18 | pub id: i32, 19 | pub description: String, 20 | pub completed: bool, 21 | } 22 | 23 | impl Task { 24 | pub fn all(conn: &PgConnection) -> QueryResult> { 25 | all_tasks.order(tasks::id.desc()).load::(conn) 26 | } 27 | 28 | pub fn insert(todo: NewTask, conn: &PgConnection) -> QueryResult { 29 | diesel::insert_into(tasks::table) 30 | .values(&todo) 31 | .execute(conn) 32 | } 33 | 34 | pub fn toggle_with_id(id: i32, conn: &PgConnection) -> QueryResult { 35 | let task = all_tasks.find(id).get_result::(conn)?; 36 | 37 | let new_status = !task.completed; 38 | let updated_task = diesel::update(all_tasks.find(id)); 39 | updated_task 40 | .set(task_completed.eq(new_status)) 41 | .execute(conn) 42 | } 43 | 44 | pub fn delete_with_id(id: i32, conn: &PgConnection) -> QueryResult { 45 | diesel::delete(all_tasks.find(id)).execute(conn) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /cookie-auth/src/main.rs: -------------------------------------------------------------------------------- 1 | use ntex::web::{self, middleware, App, HttpResponse}; 2 | use ntex_identity::{CookieIdentityPolicy, Identity, IdentityService}; 3 | 4 | async fn index(id: Identity) -> String { 5 | format!( 6 | "Hello {}", 7 | id.identity().unwrap_or_else(|| "Anonymous".to_owned()) 8 | ) 9 | } 10 | 11 | async fn login(id: Identity) -> HttpResponse { 12 | id.remember("user1".to_owned()); 13 | HttpResponse::Found().header("location", "/").finish() 14 | } 15 | 16 | async fn logout(id: Identity) -> HttpResponse { 17 | id.forget(); 18 | HttpResponse::Found().header("location", "/").finish() 19 | } 20 | 21 | #[ntex::main] 22 | async fn main() -> std::io::Result<()> { 23 | std::env::set_var("RUST_LOG", "info"); 24 | env_logger::init(); 25 | 26 | web::server(async || { 27 | App::new() 28 | .wrap(IdentityService::new( 29 | CookieIdentityPolicy::new(&[0; 32]) 30 | .name("auth-example") 31 | .secure(false), 32 | )) 33 | // enable logger 34 | .wrap(middleware::Logger::default()) 35 | .service(( 36 | web::resource("/login").route(web::post().to(login)), 37 | web::resource("/logout").to(logout), 38 | web::resource("/").route(web::get().to(index)), 39 | )) 40 | }) 41 | .bind("127.0.0.1:8080")? 42 | .run() 43 | .await 44 | } 45 | -------------------------------------------------------------------------------- /awc_https/src/main.rs: -------------------------------------------------------------------------------- 1 | use ntex::client::{Client, Connector}; 2 | use ntex::web::{self, App, HttpResponse}; 3 | use ntex::SharedCfg; 4 | use openssl::ssl::{SslConnector, SslMethod}; 5 | 6 | async fn index(client: web::types::State) -> HttpResponse { 7 | let now = std::time::Instant::now(); 8 | let payload = 9 | client 10 | .get("https://upload.wikimedia.org/wikipedia/commons/f/ff/Pizigani_1367_Chart_10MB.jpg") 11 | .send() 12 | .await 13 | .unwrap() 14 | .body() 15 | .limit(20_000_000) // sets max allowable payload size 16 | .await 17 | .unwrap(); 18 | 19 | println!( 20 | "awc time elapsed while reading bytes into memory: {} ms", 21 | now.elapsed().as_millis() 22 | ); 23 | 24 | HttpResponse::Ok().content_type("image/jpeg").body(payload) 25 | } 26 | 27 | #[ntex::main] 28 | async fn main() -> std::io::Result<()> { 29 | let port = 3000; 30 | 31 | web::server(async || { 32 | let builder = SslConnector::builder(SslMethod::tls()).unwrap(); 33 | 34 | let client = Client::builder() 35 | .connector::<&str>(Connector::default().openssl(builder.build())) 36 | .build(SharedCfg::default()) 37 | .await 38 | .unwrap(); 39 | 40 | App::new() 41 | .state(client) 42 | .service(web::resource("/").to(index)) 43 | }) 44 | .bind(("0.0.0.0", port))? 45 | .run() 46 | .await 47 | } 48 | -------------------------------------------------------------------------------- /hello-world/src/main.rs: -------------------------------------------------------------------------------- 1 | use ntex::web::{self, middleware, App, HttpRequest}; 2 | 3 | async fn index(req: HttpRequest) -> &'static str { 4 | println!("REQ: {:?}", req); 5 | "Hello world!" 6 | } 7 | 8 | #[ntex::main] 9 | async fn main() -> std::io::Result<()> { 10 | std::env::set_var("RUST_LOG", "info"); 11 | env_logger::init(); 12 | 13 | web::server(async || { 14 | App::new() 15 | // enable logger 16 | .wrap(middleware::Logger::default()) 17 | .service(( 18 | web::resource("/index.html").to(|| async { "Hello world!" }), 19 | web::resource("/").to(index), 20 | )) 21 | }) 22 | .bind("127.0.0.1:8080")? 23 | .run() 24 | .await 25 | } 26 | 27 | #[cfg(test)] 28 | mod tests { 29 | use super::*; 30 | use ntex::util::Bytes; 31 | use ntex::web::{test, App, Error}; 32 | use ntex::{http, web}; 33 | 34 | #[ntex::test] 35 | async fn test_index() -> Result<(), Error> { 36 | let app = App::new().route("/", web::get().to(index)); 37 | let app = test::init_service(app).await; 38 | 39 | let req = test::TestRequest::get().uri("/").to_request(); 40 | let resp = app.call(req).await.unwrap(); 41 | 42 | assert_eq!(resp.status(), http::StatusCode::OK); 43 | 44 | let bytes = test::read_body(resp).await; 45 | 46 | assert_eq!(bytes, Bytes::from(r##"Hello world!"##)); 47 | 48 | Ok(()) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /cookie-session/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Example of cookie based session 2 | //! Session data is stored in cookie, it is limited to 4kb 3 | //! 4 | //! [Redis session example](https://github.com/ntex-rs/examples/tree/master/redis-session) 5 | 6 | use ntex::web::{self, middleware::Logger, App, Error, HttpRequest}; 7 | use ntex_session::{CookieSession, Session}; 8 | 9 | /// simple index handler with session 10 | #[web::get("/")] 11 | async fn index(session: Session, req: HttpRequest) -> Result<&'static str, Error> { 12 | println!("{:?}", req); 13 | 14 | // RequestSession trait is used for session access 15 | let mut counter = 1; 16 | if let Some(count) = session.get::("counter")? { 17 | println!("SESSION value: {}", count); 18 | counter = count + 1; 19 | session.set("counter", counter)?; 20 | } else { 21 | session.set("counter", counter)?; 22 | } 23 | 24 | Ok("welcome!") 25 | } 26 | 27 | #[ntex::main] 28 | async fn main() -> std::io::Result<()> { 29 | std::env::set_var("RUST_LOG", "info"); 30 | env_logger::init(); 31 | println!("Starting http server: 127.0.0.1:8080"); 32 | 33 | web::server(async || { 34 | App::new() 35 | // enable logger 36 | .wrap(Logger::default()) 37 | // cookie session middleware 38 | .wrap(CookieSession::signed(&[0; 32]).secure(false)) 39 | .service(index) 40 | }) 41 | .bind("127.0.0.1:8080")? 42 | .run() 43 | .await 44 | } 45 | -------------------------------------------------------------------------------- /run-in-thread/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::mpsc; 2 | use std::{thread, time}; 3 | 4 | use ntex::server::Server; 5 | use ntex::web::{self, middleware, App, HttpRequest}; 6 | 7 | async fn index(req: HttpRequest) -> &'static str { 8 | println!("REQ: {:?}", req); 9 | "Hello world!" 10 | } 11 | 12 | #[ntex::main] 13 | async fn run_app(tx: mpsc::Sender) -> std::io::Result<()> { 14 | // srv is server controller type, `dev::Server` 15 | let srv = web::server(async || { 16 | App::new() 17 | // enable logger 18 | .wrap(middleware::Logger::default()) 19 | .service(web::resource("/index.html").to(|| async { "Hello world!" })) 20 | .service(web::resource("/").to(index)) 21 | }) 22 | .bind("127.0.0.1:8080")? 23 | .run(); 24 | 25 | // send server controller to main thread 26 | let _ = tx.send(srv.clone()); 27 | 28 | // run future 29 | srv.await 30 | } 31 | 32 | #[ntex::main] 33 | async fn main() { 34 | std::env::set_var("RUST_LOG", "trace"); 35 | env_logger::init(); 36 | 37 | let (tx, rx) = mpsc::channel(); 38 | 39 | println!("START SERVER"); 40 | thread::spawn(move || { 41 | let _ = run_app(tx); 42 | }); 43 | 44 | let srv = rx.recv().unwrap(); 45 | 46 | println!("WATING 10 SECONDS"); 47 | thread::sleep(time::Duration::from_secs(10)); 48 | 49 | println!("STOPPING SERVER"); 50 | // init stop server and wait until server gracefully exit 51 | srv.stop(true).await; 52 | } 53 | -------------------------------------------------------------------------------- /template_tera/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use ntex::web::{self, error, middleware, App, Error, HttpResponse}; 4 | use tera::Tera; 5 | 6 | // store tera template in application state 7 | #[web::get("/")] 8 | async fn index( 9 | tmpl: web::types::State, 10 | query: web::types::Query>, 11 | ) -> Result { 12 | let s = if let Some(name) = query.get("name") { 13 | // submitted form 14 | let mut ctx = tera::Context::new(); 15 | ctx.insert("name", &name.to_owned()); 16 | ctx.insert("text", &"Welcome!".to_owned()); 17 | tmpl.render("user.html", &ctx) 18 | .map_err(|_| error::ErrorInternalServerError("Template error"))? 19 | } else { 20 | tmpl.render("index.html", &tera::Context::new()) 21 | .map_err(|_| error::ErrorInternalServerError("Template error"))? 22 | }; 23 | Ok(HttpResponse::Ok().content_type("text/html").body(s)) 24 | } 25 | 26 | #[ntex::main] 27 | async fn main() -> std::io::Result<()> { 28 | std::env::set_var("RUST_LOG", "info"); 29 | env_logger::init(); 30 | 31 | web::server(async || { 32 | let tera = 33 | Tera::new(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*")).unwrap(); 34 | 35 | App::new() 36 | .state(tera) 37 | .wrap(middleware::Logger::default()) // enable logger 38 | .service(index) 39 | }) 40 | .bind("127.0.0.1:8080")? 41 | .run() 42 | .await 43 | } 44 | -------------------------------------------------------------------------------- /openssl/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use ntex::web::{self, middleware, App, Error, HttpRequest, HttpResponse}; 4 | use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; 5 | 6 | /// simple handle 7 | async fn index(req: HttpRequest) -> Result { 8 | println!("{:?}", req); 9 | Ok(HttpResponse::Ok() 10 | .content_type("text/plain") 11 | .body("Welcome!")) 12 | } 13 | 14 | #[ntex::main] 15 | async fn main() -> io::Result<()> { 16 | std::env::set_var("RUST_LOG", "trace"); 17 | let _ = env_logger::try_init(); 18 | 19 | println!("Started http server: 127.0.0.1:8443"); 20 | 21 | // load ssl keys 22 | let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); 23 | builder 24 | .set_private_key_file("key.pem", SslFiletype::PEM) 25 | .unwrap(); 26 | builder.set_certificate_chain_file("cert.pem").unwrap(); 27 | 28 | web::server(async || { 29 | App::new() 30 | // enable logger 31 | .wrap(middleware::Logger::default()) 32 | // register simple handler, handle all methods 33 | .service(web::resource("/index.html").to(index)) 34 | // with path parameters 35 | .service(web::resource("/").route(web::get().to(|| async { 36 | HttpResponse::Found() 37 | .header("LOCATION", "/index.html") 38 | .finish() 39 | }))) 40 | }) 41 | .bind_openssl("127.0.0.1:8443", builder)? 42 | .run() 43 | .await 44 | } 45 | -------------------------------------------------------------------------------- /async_ex2/src/handlers/products.rs: -------------------------------------------------------------------------------- 1 | use ntex::web::{self, Error, HttpResponse}; 2 | 3 | use crate::common::{Part, Product}; 4 | 5 | pub async fn get_products( 6 | _query: web::types::Query>, 7 | ) -> Result { 8 | Ok(HttpResponse::Ok().finish()) 9 | } 10 | 11 | pub async fn add_product( 12 | _new_product: web::types::Json, 13 | ) -> Result { 14 | Ok(HttpResponse::Ok().finish()) 15 | } 16 | 17 | pub async fn get_product_detail( 18 | _id: web::types::Path, 19 | ) -> Result { 20 | Ok(HttpResponse::Ok().finish()) 21 | } 22 | 23 | pub async fn remove_product( 24 | _id: web::types::Path, 25 | ) -> Result { 26 | Ok(HttpResponse::Ok().finish()) 27 | } 28 | 29 | #[cfg(test)] 30 | mod tests { 31 | use crate::appconfig::config_app; 32 | use ntex::http::{header, StatusCode}; 33 | use ntex::web::{test, App}; 34 | 35 | #[ntex::test] 36 | async fn test_add_product() { 37 | let app = test::init_service(App::new().configure(config_app)).await; 38 | 39 | let payload = r#"{"id":12345,"product_type":"fancy","name":"test"}"#.as_bytes(); 40 | 41 | let req = test::TestRequest::post() 42 | .uri("/products") 43 | .header(header::CONTENT_TYPE, "application/json") 44 | .set_payload(payload) 45 | .to_request(); 46 | 47 | let resp = app.call(req).await.unwrap(); 48 | 49 | assert_eq!(resp.status(), StatusCode::OK); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /shutdown-server/src/main.rs: -------------------------------------------------------------------------------- 1 | use futures::executor; 2 | use ntex::web::{self, middleware, App, HttpResponse}; 3 | use std::{sync::mpsc, thread}; 4 | 5 | #[web::get("/hello")] 6 | async fn hello() -> &'static str { 7 | "Hello world!" 8 | } 9 | 10 | #[web::post("/stop")] 11 | async fn stop(stopper: web::types::State>) -> HttpResponse { 12 | // make request that sends message through the Sender 13 | stopper.send(()).unwrap(); 14 | 15 | HttpResponse::NoContent().finish() 16 | } 17 | 18 | #[ntex::main] 19 | async fn main() -> std::io::Result<()> { 20 | std::env::set_var("RUST_LOG", "debug"); 21 | env_logger::init(); 22 | 23 | // create a channel 24 | let (tx, rx) = mpsc::channel::<()>(); 25 | let _stopper = tx.clone(); 26 | 27 | let bind = "127.0.0.1:8080"; 28 | 29 | // start server as normal but don't .await after .run() yet 30 | let server = web::server(async move || { 31 | // give the server a Sender in .data 32 | let stopper = tx.clone(); 33 | 34 | App::new() 35 | .state(stopper) 36 | .wrap(middleware::Logger::default()) 37 | .service((hello, stop)) 38 | }) 39 | .bind(bind)? 40 | .run(); 41 | 42 | // clone the Server handle 43 | let srv = server.clone(); 44 | thread::spawn(move || { 45 | // wait for shutdown signal 46 | rx.recv().unwrap(); 47 | 48 | // stop server gracefully 49 | executor::block_on(srv.stop(true)) 50 | }); 51 | 52 | // run server 53 | server.await 54 | } 55 | -------------------------------------------------------------------------------- /middleware/src/simple.rs: -------------------------------------------------------------------------------- 1 | use ntex::service::{Middleware2, Service, ServiceCtx}; 2 | use ntex::web::{Error, WebRequest, WebResponse}; 3 | 4 | // There are two steps in middleware processing. 5 | // 1. Middleware initialization, middleware factory gets called with 6 | // next service in chain as parameter. 7 | // 2. Middleware's call method gets called with normal request. 8 | pub struct SayHi; 9 | 10 | // Middleware factory is `Middleware` trait from ntex-service crate 11 | // `S` - type of the next service 12 | // `B` - type of response's body 13 | impl Middleware2 for SayHi { 14 | type Service = SayHiMiddleware; 15 | 16 | fn create(&self, service: S, _: C) -> Self::Service { 17 | SayHiMiddleware { service } 18 | } 19 | } 20 | 21 | pub struct SayHiMiddleware { 22 | service: S, 23 | } 24 | 25 | impl Service> for SayHiMiddleware 26 | where 27 | S: Service, Response = WebResponse, Error = Error>, 28 | { 29 | type Response = WebResponse; 30 | type Error = Error; 31 | 32 | ntex::forward_ready!(service); 33 | ntex::forward_poll!(service); 34 | ntex::forward_shutdown!(service); 35 | 36 | async fn call( 37 | &self, 38 | req: WebRequest, 39 | ctx: ServiceCtx<'_, Self>, 40 | ) -> Result { 41 | println!("Hi from start. You requested: {}", req.path()); 42 | 43 | let res = ctx.call(&self.service, req).await?; 44 | println!("Hi from response"); 45 | Ok(res) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /todo/src/db.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | 3 | use diesel::pg::PgConnection; 4 | use diesel::r2d2::{ConnectionManager, Pool, PoolError, PooledConnection}; 5 | 6 | use crate::model::{NewTask, Task}; 7 | 8 | pub type PgPool = Pool>; 9 | type PgPooledConnection = PooledConnection>; 10 | 11 | pub fn init_pool(database_url: &str) -> Result { 12 | let manager = ConnectionManager::::new(database_url); 13 | Pool::builder().build(manager) 14 | } 15 | 16 | fn get_conn(pool: &PgPool) -> Result { 17 | pool.get().map_err(|_| "Can't get connection") 18 | } 19 | 20 | pub fn get_all_tasks(pool: &PgPool) -> Result, &'static str> { 21 | Task::all(get_conn(pool)?.deref()).map_err(|_| "Error inserting task") 22 | } 23 | 24 | pub fn create_task(todo: String, pool: &PgPool) -> Result<(), &'static str> { 25 | let new_task = NewTask { description: todo }; 26 | Task::insert(new_task, get_conn(pool)?.deref()) 27 | .map(|_| ()) 28 | .map_err(|_| "Error inserting task") 29 | } 30 | 31 | pub fn toggle_task(id: i32, pool: &PgPool) -> Result<(), &'static str> { 32 | Task::toggle_with_id(id, get_conn(pool)?.deref()) 33 | .map(|_| ()) 34 | .map_err(|_| "Error inserting task") 35 | } 36 | 37 | pub fn delete_task(id: i32, pool: &PgPool) -> Result<(), &'static str> { 38 | Task::delete_with_id(id, get_conn(pool)?.deref()) 39 | .map(|_| ()) 40 | .map_err(|_| "Error inserting task") 41 | } 42 | -------------------------------------------------------------------------------- /graphql-demo/src/schemas/user.rs: -------------------------------------------------------------------------------- 1 | use mysql::{from_row, params}; 2 | 3 | use crate::schemas::product::Product; 4 | use crate::schemas::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 | #[derive(GraphQLInputObject)] 15 | #[graphql(description = "User Input")] 16 | pub struct UserInput { 17 | pub name: String, 18 | pub email: String, 19 | } 20 | 21 | #[juniper::object(Context = Context)] 22 | impl User { 23 | fn id(&self) -> &str { 24 | &self.id 25 | } 26 | fn name(&self) -> &str { 27 | &self.name 28 | } 29 | fn email(&self) -> &str { 30 | &self.email 31 | } 32 | 33 | fn products(&self, context: &Context) -> Vec { 34 | let mut conn = context.dbpool.get().unwrap(); 35 | 36 | conn.prep_exec( 37 | "select * from product where user_id=:user_id", 38 | params! { 39 | "user_id" => &self.id 40 | }, 41 | ) 42 | .map(|result| { 43 | result 44 | .map(|x| x.unwrap()) 45 | .map(|mut row| { 46 | let (id, user_id, name, price) = from_row(row); 47 | Product { 48 | id, 49 | user_id, 50 | name, 51 | price, 52 | } 53 | }) 54 | .collect() 55 | }) 56 | .unwrap() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /middleware/src/read_request_body.rs: -------------------------------------------------------------------------------- 1 | use futures::stream::StreamExt; 2 | use ntex::service::{Middleware2, Service, ServiceCtx}; 3 | use ntex::util::BytesMut; 4 | use ntex::web::{Error, ErrorRenderer, WebRequest, WebResponse}; 5 | 6 | pub struct Logging; 7 | 8 | impl Middleware2 for Logging { 9 | type Service = LoggingMiddleware; 10 | 11 | fn create(&self, service: S, _: C) -> Self::Service { 12 | LoggingMiddleware { service } 13 | } 14 | } 15 | 16 | pub struct LoggingMiddleware { 17 | // This is special: We need this to avoid lifetime issues. 18 | service: S, 19 | } 20 | 21 | impl Service> for LoggingMiddleware 22 | where 23 | S: Service, Response = WebResponse, Error = Error> + 'static, 24 | Err: ErrorRenderer + 'static, 25 | { 26 | type Response = WebResponse; 27 | type Error = Error; 28 | 29 | ntex::forward_ready!(service); 30 | ntex::forward_shutdown!(service); 31 | 32 | async fn call( 33 | &self, 34 | mut req: WebRequest, 35 | ctx: ServiceCtx<'_, Self>, 36 | ) -> Result { 37 | let mut body = BytesMut::new(); 38 | let mut stream = req.take_payload(); 39 | while let Some(chunk) = stream.next().await { 40 | body.extend_from_slice(&chunk?); 41 | } 42 | 43 | println!("request body: {:?}", body); 44 | let res = ctx.call(&self.service, req).await?; 45 | 46 | println!("response: {:?}", res.headers()); 47 | Ok(res) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /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 | use std::fmt::{Display, Formatter, Result as FmtResult}; 3 | use std::io; 4 | 5 | use ntex::http::StatusCode; 6 | use ntex::web::{self, App, HttpRequest, WebResponseError}; 7 | use serde::Serialize; 8 | use serde_json::{json, to_string_pretty}; 9 | 10 | #[derive(Debug, Serialize)] 11 | struct Error { 12 | msg: String, 13 | status: u16, 14 | } 15 | 16 | impl Display for Error { 17 | fn fmt(&self, f: &mut Formatter) -> FmtResult { 18 | write!(f, "{}", to_string_pretty(self).unwrap()) 19 | } 20 | } 21 | 22 | impl WebResponseError for Error { 23 | // builds the actual response to send back when an error occurs 24 | fn error_response(&self, _: &HttpRequest) -> web::HttpResponse { 25 | let err_json = json!({ "error": self.msg }); 26 | web::HttpResponse::build(StatusCode::from_u16(self.status).unwrap()) 27 | .json(&err_json) 28 | } 29 | } 30 | 31 | #[web::get("/")] 32 | async fn index() -> Result { 33 | Err(Error { 34 | msg: "an example error message".to_string(), 35 | status: 400, 36 | }) 37 | } 38 | 39 | #[ntex::main] 40 | async fn main() -> io::Result<()> { 41 | let ip_address = "127.0.0.1:8000"; 42 | println!("Running server on {}", ip_address); 43 | 44 | web::server(async || App::new().service(index)) 45 | .bind(ip_address) 46 | .expect("Can not bind to port 8000") 47 | .run() 48 | .await 49 | } 50 | -------------------------------------------------------------------------------- /simple-auth-server/static/register.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ntex - Auth App 7 | 8 | 9 | 10 | 11 | 12 | 20 | 21 | 22 | 45 | -------------------------------------------------------------------------------- /juniper/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Ntex juniper example 2 | //! 3 | //! A simple example integrating juniper in ntex 4 | use std::{io, sync::Arc}; 5 | 6 | use juniper::http::graphiql::graphiql_source; 7 | use juniper::http::GraphQLRequest; 8 | use ntex::web::{self, middleware, App, Error, HttpResponse}; 9 | 10 | mod schema; 11 | 12 | use crate::schema::{create_schema, Schema}; 13 | 14 | #[web::get("/graphiql")] 15 | async fn graphiql() -> HttpResponse { 16 | let html = graphiql_source("http://127.0.0.1:8080/graphql"); 17 | HttpResponse::Ok() 18 | .content_type("text/html; charset=utf-8") 19 | .body(html) 20 | } 21 | 22 | #[web::post("/graphql")] 23 | async fn graphql( 24 | st: web::types::State>, 25 | data: web::types::Json, 26 | ) -> Result { 27 | let st = (*st).clone(); 28 | let user = web::block(move || { 29 | let res = data.execute(&st, &()); 30 | serde_json::to_string(&res) 31 | }) 32 | .await?; 33 | Ok(HttpResponse::Ok() 34 | .content_type("application/json") 35 | .body(user)) 36 | } 37 | 38 | #[ntex::main] 39 | async fn main() -> io::Result<()> { 40 | std::env::set_var("RUST_LOG", "info"); 41 | env_logger::init(); 42 | 43 | // Create Juniper schema 44 | let schema = Arc::new(create_schema()); 45 | 46 | // Start http server 47 | web::server(async move || { 48 | App::new() 49 | .state(schema.clone()) 50 | .wrap(middleware::Logger::default()) 51 | .service((graphql, graphiql)) 52 | }) 53 | .bind("127.0.0.1:8080")? 54 | .run() 55 | .await 56 | } 57 | -------------------------------------------------------------------------------- /juniper/src/schema.rs: -------------------------------------------------------------------------------- 1 | use juniper::FieldResult; 2 | use juniper::RootNode; 3 | 4 | #[derive(GraphQLEnum)] 5 | enum Episode { 6 | NewHope, 7 | Empire, 8 | Jedi, 9 | } 10 | 11 | use juniper::{GraphQLEnum, GraphQLInputObject, GraphQLObject}; 12 | 13 | #[derive(GraphQLObject)] 14 | #[graphql(description = "A humanoid creature in the Star Wars universe")] 15 | struct Human { 16 | id: String, 17 | name: String, 18 | appears_in: Vec, 19 | home_planet: String, 20 | } 21 | 22 | #[derive(GraphQLInputObject)] 23 | #[graphql(description = "A humanoid creature in the Star Wars universe")] 24 | struct NewHuman { 25 | name: String, 26 | appears_in: Vec, 27 | home_planet: String, 28 | } 29 | 30 | pub struct QueryRoot; 31 | 32 | #[juniper::object] 33 | impl QueryRoot { 34 | fn human(id: String) -> FieldResult { 35 | Ok(Human { 36 | id: "1234".to_owned(), 37 | name: "Luke".to_owned(), 38 | appears_in: vec![Episode::NewHope], 39 | home_planet: "Mars".to_owned(), 40 | }) 41 | } 42 | } 43 | 44 | pub struct MutationRoot; 45 | 46 | #[juniper::object] 47 | impl MutationRoot { 48 | fn createHuman(new_human: NewHuman) -> FieldResult { 49 | Ok(Human { 50 | id: "1234".to_owned(), 51 | name: new_human.name, 52 | appears_in: new_human.appears_in, 53 | home_planet: new_human.home_planet, 54 | }) 55 | } 56 | } 57 | 58 | pub type Schema = RootNode<'static, QueryRoot, MutationRoot>; 59 | 60 | pub fn create_schema() -> Schema { 61 | Schema::new(QueryRoot {}, MutationRoot {}) 62 | } 63 | -------------------------------------------------------------------------------- /template_handlebars/src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate serde_json; 3 | 4 | use handlebars::Handlebars; 5 | use ntex::web::{self, App, HttpResponse}; 6 | use std::{io, sync::Arc}; 7 | 8 | // Macro documentation can be found in the ntex_macros crate 9 | #[web::get("/")] 10 | async fn index(hb: web::types::State>>) -> HttpResponse { 11 | let data = json!({ 12 | "name": "Handlebars" 13 | }); 14 | let body = hb.render("index", &data).unwrap(); 15 | 16 | HttpResponse::Ok().body(body) 17 | } 18 | 19 | #[web::get("/{user}/{data}")] 20 | async fn user( 21 | hb: web::types::State>>, 22 | info: web::types::Path<(String, String)>, 23 | ) -> HttpResponse { 24 | let data = json!({ 25 | "user": info.0, 26 | "data": info.1 27 | }); 28 | let body = hb.render("user", &data).unwrap(); 29 | 30 | HttpResponse::Ok().body(body) 31 | } 32 | 33 | #[ntex::main] 34 | async fn main() -> io::Result<()> { 35 | // Handlebars uses a repository for the compiled templates. This object must be 36 | // shared between the application threads, and is therefore passed to the 37 | // Application Builder as an atomic reference-counted pointer. 38 | let mut handlebars = Handlebars::new(); 39 | handlebars 40 | .register_templates_directory(".html", "./static/templates") 41 | .unwrap(); 42 | let handlebars_ref = Arc::new(handlebars); 43 | 44 | web::server(async move || { 45 | App::new() 46 | .state(handlebars_ref.clone()) 47 | .service((index, user)) 48 | }) 49 | .bind("127.0.0.1:8080")? 50 | .run() 51 | .await 52 | } 53 | -------------------------------------------------------------------------------- /todo/src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate diesel; 3 | #[macro_use] 4 | extern crate log; 5 | 6 | use std::{env, io}; 7 | 8 | use dotenv::dotenv; 9 | use ntex::web; 10 | use ntex::web::middleware::Logger; 11 | use ntex_files as fs; 12 | use ntex_session::CookieSession; 13 | use tera::Tera; 14 | 15 | mod api; 16 | mod db; 17 | mod model; 18 | mod schema; 19 | mod session; 20 | 21 | static SESSION_SIGNING_KEY: &[u8] = &[0; 32]; 22 | 23 | #[ntex::main] 24 | async fn main() -> io::Result<()> { 25 | dotenv().ok(); 26 | 27 | env::set_var("RUST_LOG", "todo=debug,ntex=info"); 28 | env_logger::init(); 29 | 30 | let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); 31 | let pool = db::init_pool(&database_url).expect("Failed to create pool"); 32 | 33 | let app = async move || { 34 | debug!("Constructing the App"); 35 | 36 | let templates: Tera = Tera::new("templates/**/*").unwrap(); 37 | 38 | let session_store = CookieSession::signed(SESSION_SIGNING_KEY).secure(false); 39 | 40 | web::App::new() 41 | .state(templates) 42 | .state(pool.clone()) 43 | .wrap(Logger::default()) 44 | .wrap(session_store) 45 | .service(( 46 | web::resource("/").route(web::get().to(api::index)), 47 | web::resource("/todo").route(web::post().to(api::create)), 48 | web::resource("/todo/{id}").route(web::post().to(api::update)), 49 | fs::Files::new("/static", "static/"), 50 | )) 51 | }; 52 | 53 | debug!("Starting server"); 54 | web::server(app).bind("localhost:8088")?.run().await 55 | } 56 | -------------------------------------------------------------------------------- /simple-auth-server/src/models.rs: -------------------------------------------------------------------------------- 1 | use super::schema::*; 2 | use diesel::{r2d2::ConnectionManager, PgConnection}; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | // type alias to use in multiple places 6 | pub type Pool = r2d2::Pool>; 7 | 8 | #[derive(Debug, Serialize, Deserialize, Queryable, Insertable)] 9 | #[table_name = "users"] 10 | pub struct User { 11 | pub email: String, 12 | pub hash: String, 13 | pub created_at: chrono::NaiveDateTime, 14 | } 15 | 16 | impl User { 17 | pub fn from_details, T: Into>(email: S, pwd: T) -> Self { 18 | User { 19 | email: email.into(), 20 | hash: pwd.into(), 21 | created_at: chrono::Local::now().naive_local(), 22 | } 23 | } 24 | } 25 | 26 | #[derive(Debug, Serialize, Deserialize, Queryable, Insertable)] 27 | #[table_name = "invitations"] 28 | pub struct Invitation { 29 | pub id: uuid::Uuid, 30 | pub email: String, 31 | pub expires_at: chrono::NaiveDateTime, 32 | } 33 | 34 | // any type that implements Into can be used to create Invitation 35 | impl From for Invitation 36 | where 37 | T: Into, 38 | { 39 | fn from(email: T) -> Self { 40 | Invitation { 41 | id: uuid::Uuid::new_v4(), 42 | email: email.into(), 43 | expires_at: chrono::Local::now().naive_local() + chrono::Duration::hours(24), 44 | } 45 | } 46 | } 47 | 48 | #[derive(Debug, Serialize, Deserialize)] 49 | pub struct SlimUser { 50 | pub email: String, 51 | } 52 | 53 | impl From for SlimUser { 54 | fn from(user: User) -> Self { 55 | SlimUser { email: user.email } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /r2d2/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Ntex r2d2 example 2 | use std::io; 3 | 4 | use ntex::web::{self, error, middleware, App, Error, HttpResponse}; 5 | use r2d2::Pool; 6 | use r2d2_sqlite::SqliteConnectionManager; 7 | 8 | /// Async request handler. Ddb pool is stored in application state. 9 | async fn index( 10 | path: web::types::Path, 11 | db: web::types::State>, 12 | ) -> Result { 13 | // execute sync code in threadpool 14 | let db = db.get_ref().clone(); 15 | let res = web::block(move || { 16 | let conn = db.get().unwrap(); 17 | let uuid = format!("{}", uuid::Uuid::new_v4()); 18 | conn.execute( 19 | "INSERT INTO users (id, name) VALUES ($1, $2)", 20 | &[&uuid, &path.into_inner()], 21 | ) 22 | .unwrap(); 23 | 24 | conn.query_row("SELECT name FROM users WHERE id=$1", &[&uuid], |row| { 25 | row.get::<_, String>(0) 26 | }) 27 | }) 28 | .await 29 | .map(|user| HttpResponse::Ok().json(&user)) 30 | .map_err(error::ErrorInternalServerError)?; 31 | Ok(res) 32 | } 33 | 34 | #[ntex::main] 35 | async fn main() -> io::Result<()> { 36 | std::env::set_var("RUST_LOG", "ntex=debug"); 37 | env_logger::init(); 38 | 39 | // r2d2 pool 40 | let manager = SqliteConnectionManager::file("test.db"); 41 | let pool = r2d2::Pool::new(manager).unwrap(); 42 | 43 | // start http server 44 | web::server(async move || { 45 | App::new() 46 | .state(pool.clone()) // <- store db pool in app state 47 | .wrap(middleware::Logger::default()) 48 | .route("/{name}", web::get().to(index)) 49 | }) 50 | .bind("127.0.0.1:8080")? 51 | .run() 52 | .await 53 | } 54 | -------------------------------------------------------------------------------- /simple-auth-server/src/invitation_handler.rs: -------------------------------------------------------------------------------- 1 | use diesel::{prelude::*, PgConnection}; 2 | use ntex::web::{self, error::BlockingError, HttpResponse}; 3 | use serde::Deserialize; 4 | 5 | use crate::email_service::send_invitation; 6 | use crate::errors::ServiceError; 7 | use crate::models::{Invitation, Pool}; 8 | 9 | #[derive(Deserialize)] 10 | pub struct InvitationData { 11 | pub email: String, 12 | } 13 | 14 | pub async fn post_invitation( 15 | invitation_data: web::types::Json, 16 | pool: web::types::State, 17 | ) -> Result { 18 | // run diesel blocking code 19 | let pool = (*pool).clone(); 20 | let res = 21 | web::block(move || create_invitation(invitation_data.into_inner().email, pool)) 22 | .await; 23 | 24 | match res { 25 | Ok(_) => Ok(HttpResponse::Ok().finish()), 26 | Err(err) => match err { 27 | BlockingError::Error(service_error) => Err(service_error), 28 | BlockingError::Canceled => Err(ServiceError::InternalServerError), 29 | }, 30 | } 31 | } 32 | 33 | fn create_invitation( 34 | eml: String, 35 | pool: Pool, 36 | ) -> Result<(), crate::errors::ServiceError> { 37 | let invitation = dbg!(query(eml, pool)?); 38 | send_invitation(&invitation) 39 | } 40 | 41 | /// Diesel query 42 | fn query(eml: String, pool: Pool) -> Result { 43 | use crate::schema::invitations::dsl::invitations; 44 | 45 | let new_invitation: Invitation = eml.into(); 46 | let conn: &PgConnection = &pool.get().unwrap(); 47 | 48 | let inserted_invitation = diesel::insert_into(invitations) 49 | .values(&new_invitation) 50 | .get_result(conn)?; 51 | 52 | Ok(inserted_invitation) 53 | } 54 | -------------------------------------------------------------------------------- /server-sent-events/benchmark.js: -------------------------------------------------------------------------------- 1 | const http = require('http') 2 | 3 | const n = 1000; 4 | let connected = 0; 5 | let messages = 0; 6 | let start = Date.now(); 7 | let phase = 'connecting'; 8 | let connection_time; 9 | let broadcast_time; 10 | 11 | let message = process.argv[2] || 'msg'; 12 | let expected_data = "data: " + message; 13 | 14 | for (let i = 0; i < n; i++) { 15 | http.get({ 16 | host: 'localhost', 17 | port: 8080, 18 | path: '/events' 19 | }, response => { 20 | response.on('data', data => { 21 | if (data.includes(expected_data)) { 22 | messages += 1; 23 | } else if (data.includes("data: connected\n")) { 24 | connected += 1; 25 | } 26 | }) 27 | }).on('error', (_) => {}); 28 | } 29 | 30 | setInterval(() => { 31 | if (phase === 'connecting' && connected === n) { 32 | // done connecting 33 | phase = 'messaging'; 34 | connection_time = Date.now() - start; 35 | } 36 | 37 | if (phase === 'messaging') { 38 | phase = 'waiting'; 39 | start = Date.now(); 40 | 41 | http.get({ 42 | host: 'localhost', 43 | port: 8080, 44 | path: '/broadcast/' + message 45 | }, response => { 46 | response.on('data', _ => {}) 47 | }) 48 | } 49 | 50 | if (phase === 'waiting' && messages >= n) { 51 | // all messages received 52 | broadcast_time = Date.now() - start; 53 | phase = 'paused'; 54 | messages = 0; 55 | phase = 'messaging'; 56 | } 57 | 58 | process.stdout.write("\r\x1b[K"); 59 | process.stdout.write(`Connected: ${connected}, connection time: ${connection_time} ms, total broadcast time: ${broadcast_time} ms`); 60 | }, 20) 61 | -------------------------------------------------------------------------------- /openssl/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDQYw/djMNUVaA2 3 | FGIed953X3bXfVmLbenxaK62d4P0jlY7jZzv185ezEtaf7YdKo+8I8cgEugxfyj2 4 | WALG3Y/8lQIBZusTYS6+GQytaTr+rvEHbXVdlh+eSp23uYDSMxQLmjfYBfd7Djnp 5 | MCrk/xh2r0f+pd1G0C8oPRB2QV+rx48KKo6Fyppxn81a9rtHgd2BOdsvUNFCMYFe 6 | zMjTs/3XLDwY7Kbjq7E0dG6NCneVgSFzVXclMX2QROlIEfIQCRDxYfEZ/xQEcOGH 7 | uFxz4ogkcyVVjLnAO/hkl5ixjWsPr+fi3J3evTLMEzntQZ/qqtVkRiYsHslp37wN 8 | /m8igaFbAgMBAAECggEAJI278rkGany6pcHdlEqik34DcrliQ7r8FoSuYQOF+hgd 9 | uESXCttoL+jWLwHICEW3AOGlxFKMuGH95Xh6xDeJUl0xBN3wzm11rZLnTmPvHU3C 10 | qfLha5Ex6qpcECZSGo0rLv3WXeZuCv/r2KPCYnj86ZTFpD2kGw/Ztc1AXf4Jsi/1 11 | 478Mf23QmAvCAPimGCyjLQx2c9/vg/6K7WnDevY4tDuDKLeSJxKZBSHUn3cM1Bwj 12 | 2QzaHfSFA5XljOF5PLeR3cY5ncrrVLWChT9XuGt9YMdLAcSQxgE6kWV1RSCq+lbj 13 | e6OOe879IrrqwBvMQfKQqnm1kl8OrfPMT5CNWKvEgQKBgQD8q5E4x9taDS9RmhRO 14 | 07ptsr/I795tX8CaJd/jc4xGuCGBqpNw/hVebyNNYQvpiYzDNBSEhtd59957VyET 15 | hcrGyxD0ByKm8F/lPgFw5y6wi3RUnucCV/jxkMHmxVzYMbFUEGCQ0pIU9/GFS7RZ 16 | 9VjqRDeE86U3yHO+WCFoHtd8aQKBgQDTIhi0uq0oY87bUGnWbrrkR0UVRNPDG1BT 17 | cuXACYlv/DV/XpxPC8iPK1UwG4XaOVxodtIRjdBqvb8fUM6HSY6qll64N/4/1jre 18 | Ho+d4clE4tK6a9WU96CKxwHn2BrWUZJPtoldaCZJFJ7SfiHuLlqW7TtYFrOfPIjN 19 | ADiqK+bHIwKBgQCpfIiAVwebo0Z/bWR77+iZFxMwvT4tjdJLVGaXUvXgpjjLmtkm 20 | LTm2S8SZbiSodfz3H+M3dp/pj8wsXiiwyMlZifOITZT/+DPLOUmMK3cVM6ZH8QMy 21 | fkJd/+UhYHhECSlTI10zKByXdi4LZNnIkhwfoLzBMRI9lfeV0dYu2qlfKQKBgEVI 22 | kRbtk1kHt5/ceX62g3nZsV/TYDJMSkW4FJC6EHHBL8UGRQDjewMQUzogLgJ4hEx7 23 | gV/lS5lbftZF7CAVEU4FXjvRlAtav6KYIMTMjQGf9UrbjBEAWZxwxb1Q+y2NQxgJ 24 | bHZMcRPWQnAMmBHTAEM6whicCoGcmb+77Nxa37ZFAoGBALBuUNeD3fKvQR8v6GoA 25 | spv+RYL9TB4wz2Oe9EYSp9z5EiWlTmuvFz3zk8pHDSpntxYH5O5HJ/3OzwhHz9ym 26 | +DNE9AP9LW9hAzMuu7Gob1h8ShGwJVYwrQN3q/83ooUL7WSAuVOLpzJ7BFFlcCjp 27 | MhFvd9iOt/R0N30/3AbQXkOp 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /middleware/src/redirect.rs: -------------------------------------------------------------------------------- 1 | use ntex::http; 2 | use ntex::service::{Middleware2, Service, ServiceCtx}; 3 | use ntex::web::{Error, ErrorRenderer, HttpResponse, WebRequest, WebResponse}; 4 | 5 | pub struct CheckLogin; 6 | 7 | impl Middleware2 for CheckLogin { 8 | type Service = CheckLoginMiddleware; 9 | 10 | fn create(&self, service: S, _: C) -> Self::Service { 11 | CheckLoginMiddleware { service } 12 | } 13 | } 14 | 15 | pub struct CheckLoginMiddleware { 16 | service: S, 17 | } 18 | 19 | impl Service> for CheckLoginMiddleware 20 | where 21 | S: Service, Response = WebResponse, Error = Error>, 22 | Err: ErrorRenderer, 23 | { 24 | type Response = WebResponse; 25 | type Error = Error; 26 | 27 | ntex::forward_ready!(service); 28 | ntex::forward_shutdown!(service); 29 | 30 | async fn call( 31 | &self, 32 | req: WebRequest, 33 | ctx: ServiceCtx<'_, Self>, 34 | ) -> Result { 35 | // We only need to hook into the `start` for this middleware. 36 | 37 | let is_logged_in = false; // Change this to see the change in outcome in the browser 38 | 39 | if is_logged_in { 40 | ctx.call(&self.service, req).await 41 | } else { 42 | // Don't forward to /login if we are already on /login 43 | if req.path() == "/login" { 44 | ctx.call(&self.service, req).await 45 | } else { 46 | Ok(req.into_response( 47 | HttpResponse::Found() 48 | .header(http::header::LOCATION, "/login") 49 | .finish() 50 | .into_body(), 51 | )) 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /rustls/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::BufReader; 3 | 4 | use ntex::web::{self, middleware, App, HttpRequest, HttpResponse}; 5 | use ntex_files::Files; 6 | use rustls::ServerConfig; 7 | use rustls_pemfile::certs; 8 | 9 | /// simple handle 10 | async fn index(req: HttpRequest) -> HttpResponse { 11 | println!("{:?}", req); 12 | HttpResponse::Ok() 13 | .content_type("text/plain") 14 | .body("Welcome!") 15 | } 16 | 17 | #[ntex::main] 18 | async fn main() -> std::io::Result<()> { 19 | if std::env::var("RUST_LOG").is_err() { 20 | std::env::set_var("RUST_LOG", "info"); 21 | } 22 | env_logger::init(); 23 | 24 | // load ssl keys 25 | let key_file = &mut BufReader::new(File::open("key.pem").unwrap()); 26 | let key = rustls_pemfile::private_key(key_file).unwrap().unwrap(); 27 | let cert_file = &mut BufReader::new(File::open("cert.pem").unwrap()); 28 | let cert_chain = certs(cert_file).map(|r| r.unwrap()).collect(); 29 | let config = ServerConfig::builder() 30 | .with_no_client_auth() 31 | .with_single_cert(cert_chain, key) 32 | .unwrap(); 33 | 34 | web::server(async || { 35 | App::new() 36 | // enable logger 37 | .wrap(middleware::Logger::default()) 38 | // register simple handler, handle all methods 39 | .service(web::resource("/index.html").to(index)) 40 | // with path parameters 41 | .service(web::resource("/").route(web::get().to(|| async { 42 | HttpResponse::Found() 43 | .header("LOCATION", "/index.html") 44 | .finish() 45 | }))) 46 | .service(Files::new("/static", "static")) 47 | }) 48 | .bind_rustls("127.0.0.1:8443", config)? 49 | .run() 50 | .await 51 | } 52 | -------------------------------------------------------------------------------- /rustls/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFPjCCAyYCCQDvLYiYD+jqeTANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJV 3 | UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMRAwDgYDVQQKDAdDb21wYW55MQww 4 | CgYDVQQLDANPcmcxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xODAxMjUx 5 | NzQ2MDFaFw0xOTAxMjUxNzQ2MDFaMGExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJD 6 | QTELMAkGA1UEBwwCU0YxEDAOBgNVBAoMB0NvbXBhbnkxDDAKBgNVBAsMA09yZzEY 7 | MBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A 8 | MIICCgKCAgEA2WzIA2IpVR9Tb9EFhITlxuhE5rY2a3S6qzYNzQVgSFggxXEPn8k1 9 | sQEcer5BfAP986Sck3H0FvB4Bt/I8PwOtUCmhwcc8KtB5TcGPR4fjXnrpC+MIK5U 10 | NLkwuyBDKziYzTdBj8kUFX1WxmvEHEgqToPOZfBgsS71cJAR/zOWraDLSRM54jXy 11 | voLZN4Ti9rQagQrvTQ44Vz5ycDQy7UxtbUGh1CVv69vNVr7/SOOh/Nw5FNOZWLWr 12 | odGyoec5wh9iqRZgRqiTUc6Lt7V2RWc2X2gjwST2UfI+U46Ip3oaQ7ZD4eAkoqND 13 | xdniBZAykVG3c/99ux4BAESTF8fsNch6UticBxYMuTu+ouvP0psfI9wwwNliJDmA 14 | CRUTB9AgRynbL1AzhqQoDfsb98IZfjfNOpwnwuLwpMAPhbgd5KNdZaIJ4Hb6/stI 15 | yFElOExxd3TAxF2Gshd/lq1JcNHAZ1DSXV5MvOWT/NWgXwbIzUgQ8eIi+HuDYX2U 16 | UuaB6R8tbd52H7rbUv6HrfinuSlKWqjSYLkiKHkwUpoMw8y9UycRSzs1E9nPwPTO 17 | vRXb0mNCQeBCV9FvStNVXdCUTT8LGPv87xSD2pmt7LijlE6mHLG8McfcWkzA69un 18 | CEHIFAFDimTuN7EBljc119xWFTcHMyoZAfFF+oTqwSbBGImruCxnaJECAwEAATAN 19 | BgkqhkiG9w0BAQsFAAOCAgEApavsgsn7SpPHfhDSN5iZs1ILZQRewJg0Bty0xPfk 20 | 3tynSW6bNH3nSaKbpsdmxxomthNSQgD2heOq1By9YzeOoNR+7Pk3s4FkASnf3ToI 21 | JNTUasBFFfaCG96s4Yvs8KiWS/k84yaWuU8c3Wb1jXs5Rv1qE1Uvuwat1DSGXSoD 22 | JNluuIkCsC4kWkyq5pWCGQrabWPRTWsHwC3PTcwSRBaFgYLJaR72SloHB1ot02zL 23 | d2age9dmFRFLLCBzP+D7RojBvL37qS/HR+rQ4SoQwiVc/JzaeqSe7ZbvEH9sZYEu 24 | ALowJzgbwro7oZflwTWunSeSGDSltkqKjvWvZI61pwfHKDahUTmZ5h2y67FuGEaC 25 | CIOUI8dSVSPKITxaq3JL4ze2e9/0Lt7hj19YK2uUmtMAW5Tirz4Yx5lyGH9U8Wur 26 | y/X8VPxTc4A9TMlJgkyz0hqvhbPOT/zSWB10zXh0glKAsSBryAOEDxV1UygmSir7 27 | YV8Qaq+oyKUTMc1MFq5vZ07M51EPaietn85t8V2Y+k/8XYltRp32NxsypxAJuyxh 28 | g/ko6RVTrWa1sMvz/F9LFqAdKiK5eM96lh9IU4xiLg4ob8aS/GRAA8oIFkZFhLrt 29 | tOwjIUPmEPyHWFi8dLpNuQKYalLYhuwZftG/9xV+wqhKGZO9iPrpHSYBRTap8w2y 30 | 1QU= 31 | -----END CERTIFICATE----- 32 | -------------------------------------------------------------------------------- /multipart/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | use futures::{StreamExt, TryStreamExt}; 4 | use ntex::web::{self, middleware, App, Error, HttpResponse}; 5 | use ntex_multipart::Multipart; 6 | 7 | async fn save_file(mut payload: Multipart) -> Result { 8 | // iterate over multipart stream 9 | while let Ok(Some(mut field)) = payload.try_next().await { 10 | // let content_type = field.content_disposition().unwrap(); 11 | // let filename = content_type.get_filename().unwrap(); 12 | let filename = "somename"; 13 | let filepath = format!("./tmp/{}", filename); 14 | // File::create is blocking operation, use threadpool 15 | let mut f = web::block(|| std::fs::File::create(filepath)) 16 | .await 17 | .unwrap(); 18 | // Field in turn is stream of *Bytes* object 19 | while let Some(chunk) = field.next().await { 20 | let data = chunk.unwrap(); 21 | // filesystem operations are blocking, we have to use threadpool 22 | f = web::block(move || f.write_all(&data).map(|_| f)).await?; 23 | } 24 | } 25 | Ok(HttpResponse::Ok().into()) 26 | } 27 | 28 | async fn index() -> HttpResponse { 29 | let html = r#" 30 | Upload Test 31 | 32 |
33 | 34 | 35 |
36 | 37 | "#; 38 | 39 | HttpResponse::Ok().body(html) 40 | } 41 | 42 | #[ntex::main] 43 | async fn main() -> std::io::Result<()> { 44 | std::env::set_var("RUST_LOG", "info"); 45 | std::fs::create_dir_all("./tmp").unwrap(); 46 | 47 | let ip = "0.0.0.0:3000"; 48 | 49 | web::server(async || { 50 | App::new().wrap(middleware::Logger::default()).service( 51 | web::resource("/") 52 | .route(web::get().to(index)) 53 | .route(web::post().to(save_file)), 54 | ) 55 | }) 56 | .bind(ip)? 57 | .run() 58 | .await 59 | } 60 | -------------------------------------------------------------------------------- /async_pg/README.md: -------------------------------------------------------------------------------- 1 | # async_pg example 2 | 3 | ## This example illustrates 4 | 5 | - `tokio_postgres` 6 | - use of `tokio_pg_mapper` for postgres data mapping 7 | - `deadpool_postgres` for connection pooling 8 | - `dotenv` + `config` for configuration 9 | 10 | ## Instructions 11 | 12 | 1. Create database user 13 | 14 | ```shell 15 | createuser -P test_user 16 | ``` 17 | 18 | Enter a password of your choice. The following instructions assume you 19 | used `testing` as password. 20 | 21 | This step is **optional** and you can also use an existing database user 22 | for that. Just make sure to replace `test_user` by the database user 23 | of your choice in the following steps and change the `.env` file 24 | containing the configuration accordingly. 25 | 26 | 2. Create database 27 | 28 | ```shell 29 | createdb -O test_user testing_db 30 | ``` 31 | 32 | 3. Initialize database 33 | 34 | ```shell 35 | psql -f sql/schema.sql testing_db 36 | ``` 37 | 38 | This step can be repeated and clears the database as it drops and 39 | recreates the schema `testing` which is used within the database. 40 | 41 | 4. Create `.env` file: 42 | 43 | ```ini 44 | SERVER_ADDR=127.0.0.1:8080 45 | PG.USER=test_user 46 | PG.PASSWORD=testing 47 | PG.HOST=127.0.0.1 48 | PG.PORT=5432 49 | PG.DBNAME=testing_db 50 | PG.POOL.MAX_SIZE=16 51 | ``` 52 | 53 | 5. Run the server: 54 | 55 | ```shell 56 | cargo run 57 | ``` 58 | 59 | 6. Using a different terminal send an HTTP POST request to the running server: 60 | 61 | ```shell 62 | echo '{"email": "ferris@thecrab.com", "first_name": "ferris", "last_name": "crab", "username": "ferreal"}' | http -f --json --print h POST http://127.0.0.1:8080/users 63 | ``` 64 | 65 | **...or using curl...** 66 | 67 | ```shell 68 | curl -d '{"email": "ferris@thecrab.com", "first_name": "ferris", "last_name": "crab", "username": "ferreal"}' -H 'Content-Type: application/json' http://127.0.0.1:8080/users 69 | ``` 70 | 71 | A unique constraint exists for username, so sending this request twice 72 | will return an internal server error (HTTP 500). 73 | -------------------------------------------------------------------------------- /simple-auth-server/src/errors.rs: -------------------------------------------------------------------------------- 1 | use derive_more::Display; 2 | use diesel::result::{DatabaseErrorKind, Error as DBError}; 3 | use ntex::web::{HttpRequest, HttpResponse, WebResponseError}; 4 | use std::convert::From; 5 | use uuid::Error as ParseError; 6 | 7 | #[derive(Debug, Display)] 8 | pub enum ServiceError { 9 | #[display(fmt = "Internal Server Error")] 10 | InternalServerError, 11 | 12 | #[display(fmt = "BadRequest: {}", _0)] 13 | BadRequest(String), 14 | 15 | #[display(fmt = "Unauthorized")] 16 | Unauthorized, 17 | } 18 | 19 | // impl ResponseError trait allows to convert our errors into http responses with appropriate data 20 | impl WebResponseError for ServiceError { 21 | fn error_response(&self, _: &HttpRequest) -> HttpResponse { 22 | match self { 23 | ServiceError::InternalServerError => HttpResponse::InternalServerError() 24 | .json(&"Internal Server Error, Please try later"), 25 | ServiceError::BadRequest(ref message) => { 26 | HttpResponse::BadRequest().json(message) 27 | } 28 | ServiceError::Unauthorized => { 29 | HttpResponse::Unauthorized().json(&"Unauthorized") 30 | } 31 | } 32 | } 33 | } 34 | 35 | // we can return early in our handlers if UUID provided by the user is not valid 36 | // and provide a custom message 37 | impl From for ServiceError { 38 | fn from(_: ParseError) -> ServiceError { 39 | ServiceError::BadRequest("Invalid UUID".into()) 40 | } 41 | } 42 | 43 | impl From for ServiceError { 44 | fn from(error: DBError) -> ServiceError { 45 | // Right now we just care about UniqueViolation from diesel 46 | // But this would be helpful to easily map errors as our app grows 47 | match error { 48 | DBError::DatabaseError(kind, info) => { 49 | if let DatabaseErrorKind::UniqueViolation = kind { 50 | let message = 51 | info.details().unwrap_or_else(|| info.message()).to_string(); 52 | return ServiceError::BadRequest(message); 53 | } 54 | ServiceError::InternalServerError 55 | } 56 | _ => ServiceError::InternalServerError, 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /middleware/src/read_response_body.rs: -------------------------------------------------------------------------------- 1 | use std::{rc::Rc, task::Context, task::Poll}; 2 | 3 | use ntex::http::body::{Body, BodySize, MessageBody, ResponseBody}; 4 | use ntex::service::{Middleware2, Service, ServiceCtx}; 5 | use ntex::util::{Bytes, BytesMut}; 6 | use ntex::web::{Error, WebRequest, WebResponse}; 7 | 8 | pub struct Logging; 9 | 10 | impl Middleware2 for Logging { 11 | type Service = LoggingMiddleware; 12 | 13 | fn create(&self, service: S, _: C) -> Self::Service { 14 | LoggingMiddleware { service } 15 | } 16 | } 17 | 18 | pub struct LoggingMiddleware { 19 | service: S, 20 | } 21 | 22 | impl Service> for LoggingMiddleware 23 | where 24 | S: Service, Response = WebResponse, Error = Error>, 25 | { 26 | type Response = WebResponse; 27 | type Error = Error; 28 | 29 | ntex::forward_ready!(service); 30 | ntex::forward_shutdown!(service); 31 | 32 | async fn call( 33 | &self, 34 | req: WebRequest, 35 | ctx: ServiceCtx<'_, Self>, 36 | ) -> Result { 37 | ctx.call(&self.service, req).await.map(|res| { 38 | res.map_body(move |_, body| { 39 | Body::from_message(BodyLogger { 40 | body, 41 | body_accum: BytesMut::new(), 42 | }) 43 | .into() 44 | }) 45 | }) 46 | } 47 | } 48 | 49 | pub struct BodyLogger { 50 | body: ResponseBody, 51 | body_accum: BytesMut, 52 | } 53 | 54 | impl Drop for BodyLogger { 55 | fn drop(&mut self) { 56 | println!("response body: {:?}", self.body_accum); 57 | } 58 | } 59 | 60 | impl MessageBody for BodyLogger { 61 | fn size(&self) -> BodySize { 62 | self.body.size() 63 | } 64 | 65 | fn poll_next_chunk( 66 | &mut self, 67 | cx: &mut Context<'_>, 68 | ) -> Poll>>> { 69 | match self.body.poll_next_chunk(cx) { 70 | Poll::Ready(Some(Ok(chunk))) => { 71 | self.body_accum.extend_from_slice(&chunk); 72 | Poll::Ready(Some(Ok(chunk))) 73 | } 74 | Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(e))), 75 | Poll::Ready(None) => Poll::Ready(None), 76 | Poll::Pending => Poll::Pending, 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /simple-auth-server/README.md: -------------------------------------------------------------------------------- 1 | ## Auth Web Microservice with rust using Ntex 2 | 3 | ##### Flow of the event would look like this: 4 | 5 | - Registers with email address ➡ Receive an 📨 with a link to verify 6 | - Follow the link ➡ register with same email and a password 7 | - Login with email and password ➡ Get verified and receive auth cookie 8 | 9 | ##### Crates Used 10 | 11 | - [ntex](https://crates.io/crates/ntex) // Ntex is a simple, pragmatic and extremely fast web framework for Rust. 12 | - [argonautica](https://docs.rs/argonautica) // crate for hashing passwords using the cryptographically-secure Argon2 hashing algorithm. 13 | - [chrono](https://crates.io/crates/chrono) // Date and time library for Rust. 14 | - [diesel](https://crates.io/crates/diesel) // A safe, extensible ORM and Query Builder for PostgreSQL, SQLite, and MySQL. 15 | - [dotenv](https://crates.io/crates/dotenv) // A dotenv implementation for Rust. 16 | - [derive_more](https://crates.io/crates/derive_more) // Convenience macros to derive tarits easily 17 | - [env_logger](https://crates.io/crates/env_logger) // A logging implementation for log which is configured via an environment variable. 18 | - [futures](https://crates.io/crates/futures) // An implementation of futures and streams featuring zero allocations, composability, and iterator-like interfaces. 19 | - [lazy_static](https://docs.rs/lazy_static) // A macro for declaring lazily evaluated statics. 20 | - [r2d2](https://crates.io/crates/r2d2) // A generic connection pool. 21 | - [serde](https://crates.io/crates/serde) // A generic serialization/deserialization framework. 22 | - [serde_json](https://crates.io/crates/serde_json) // A JSON serialization file format. 23 | - [serde_derive](https://crates.io/crates/serde_derive) // Macros 1.1 implementation of #[derive(Serialize, Deserialize)]. 24 | - [sparkpost](https://crates.io/crates/sparkpost) // Rust bindings for sparkpost email api v1. 25 | - [uuid](https://crates.io/crates/uuid) // A library to generate and parse UUIDs. 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | ## Dependencies 34 | 35 | On Ubuntu 19.10: 36 | 37 | ``` 38 | sudo apt install libclang-dev libpq-dev 39 | ``` 40 | -------------------------------------------------------------------------------- /websocket/websocket-client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """websocket cmd client for wssrv.py example.""" 3 | import argparse 4 | import asyncio 5 | import signal 6 | import sys 7 | 8 | import aiohttp 9 | 10 | 11 | def start_client(loop, url): 12 | name = input('Please enter your name: ') 13 | 14 | # send request 15 | ws = yield from aiohttp.ClientSession().ws_connect(url, autoclose=False, autoping=False) 16 | 17 | # input reader 18 | def stdin_callback(): 19 | line = sys.stdin.buffer.readline().decode('utf-8') 20 | if not line: 21 | loop.stop() 22 | else: 23 | ws.send_str(name + ': ' + line) 24 | loop.add_reader(sys.stdin.fileno(), stdin_callback) 25 | 26 | @asyncio.coroutine 27 | def dispatch(): 28 | while True: 29 | msg = yield from ws.receive() 30 | 31 | if msg.type == aiohttp.WSMsgType.TEXT: 32 | print('Text: ', msg.data.strip()) 33 | elif msg.type == aiohttp.WSMsgType.BINARY: 34 | print('Binary: ', msg.data) 35 | elif msg.type == aiohttp.WSMsgType.PING: 36 | ws.pong() 37 | elif msg.type == aiohttp.WSMsgType.PONG: 38 | print('Pong received') 39 | else: 40 | if msg.type == aiohttp.WSMsgType.CLOSE: 41 | yield from ws.close() 42 | elif msg.type == aiohttp.WSMsgType.ERROR: 43 | print('Error during receive %s' % ws.exception()) 44 | elif msg.type == aiohttp.WSMsgType.CLOSED: 45 | pass 46 | 47 | break 48 | 49 | yield from dispatch() 50 | 51 | 52 | ARGS = argparse.ArgumentParser( 53 | description="websocket console client for wssrv.py example.") 54 | ARGS.add_argument( 55 | '--host', action="store", dest='host', 56 | default='127.0.0.1', help='Host name') 57 | ARGS.add_argument( 58 | '--port', action="store", dest='port', 59 | default=8080, type=int, help='Port number') 60 | 61 | if __name__ == '__main__': 62 | args = ARGS.parse_args() 63 | if ':' in args.host: 64 | args.host, port = args.host.split(':', 1) 65 | args.port = int(port) 66 | 67 | url = 'http://{}:{}/ws/'.format(args.host, args.port) 68 | 69 | loop = asyncio.get_event_loop() 70 | loop.add_signal_handler(signal.SIGINT, loop.stop) 71 | asyncio.Task(start_client(loop, url)) 72 | loop.run_forever() 73 | -------------------------------------------------------------------------------- /template_yarte/README.md: -------------------------------------------------------------------------------- 1 | # yarte 2 | 3 | Minimal example of using template [yarte](https://github.com/botika/yarte) that displays a form. 4 | 5 | [Template benchmarks in stable](https://github.com/botika/template-bench-rs) 6 | 7 | ```bash 8 | cargo test 9 | 10 | cargo run 11 | ``` 12 | > open `localhost:8080` 13 | 14 | ## Generated code 15 | ```rust 16 | impl ::std::fmt::Display for IndexTemplate { 17 | fn fmt(&self, _fmt: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { 18 | _fmt.write_str( 19 | "Ntex \ 20 | ", 21 | )?; 22 | if let Some(name__0) = self.query.get("name") { 23 | let lastname__1 = self.query.get("lastname").ok_or(yarte::Error)?; 24 | _fmt.write_str("

Hi, ")?; 25 | ::yarte::Render::render(&(name__0), _fmt)?; 26 | _fmt.write_str(" ")?; 27 | ::yarte::Render::render(&(lastname__1), _fmt)?; 28 | _fmt.write_str("!

Welcome

")?; 29 | } else { 30 | _fmt.write_str( 31 | "

Welcome!

What is your \ 32 | name?

Name:
Last name: \ 33 |

", 35 | )?; 36 | } 37 | _fmt.write_str("")?; 38 | Ok(()) 39 | } 40 | } 41 | 42 | impl ::yarte::Template for IndexTemplate { 43 | fn mime() -> &'static str { 44 | "text/html; charset=utf-8" 45 | } 46 | 47 | fn size_hint() -> usize { 48 | 838usize 49 | } 50 | } 51 | 52 | impl ::yarte::aw::Responder for IndexTemplate { 53 | type Error = ::yarte::aw::Error; 54 | type Future = ::yarte::aw::Ready<::std::result::Result<::yarte::aw::HttpResponse, Self::Error>>; 55 | 56 | #[inline] 57 | fn respond_to(self, _req: &::yarte::aw::HttpRequest) -> Self::Future { 58 | match self.call() { 59 | Ok(body) => ::yarte::aw::ok( 60 | ::yarte::aw::HttpResponse::Ok() 61 | .content_type(Self::mime()) 62 | .body(body), 63 | ), 64 | Err(_) => ::yarte::aw::err(::yarte::aw::ErrorInternalServerError("Some error message")), 65 | } 66 | } 67 | } 68 | ``` 69 | -------------------------------------------------------------------------------- /simple-auth-server/src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate diesel; 3 | 4 | use diesel::prelude::*; 5 | use diesel::r2d2::{self, ConnectionManager}; 6 | use ntex::web::{self, middleware, App}; 7 | use ntex_identity::{CookieIdentityPolicy, IdentityService}; 8 | 9 | mod auth_handler; 10 | mod email_service; 11 | mod errors; 12 | mod invitation_handler; 13 | mod models; 14 | mod register_handler; 15 | mod schema; 16 | mod utils; 17 | 18 | #[ntex::main] 19 | async fn main() -> std::io::Result<()> { 20 | dotenv::dotenv().ok(); 21 | std::env::set_var("RUST_LOG", "debug"); 22 | env_logger::init(); 23 | let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set"); 24 | 25 | // create db connection pool 26 | let manager = ConnectionManager::::new(database_url); 27 | let pool: models::Pool = r2d2::Pool::builder() 28 | .build(manager) 29 | .expect("Failed to create pool."); 30 | let domain: String = 31 | std::env::var("DOMAIN").unwrap_or_else(|_| "localhost".to_string()); 32 | 33 | // Start http server 34 | web::server(async move || { 35 | App::new() 36 | .state(pool.clone()) 37 | // enable logger 38 | .wrap(middleware::Logger::default()) 39 | .wrap(IdentityService::new( 40 | CookieIdentityPolicy::new(utils::SECRET_KEY.as_bytes()) 41 | .name("auth") 42 | .path("/") 43 | .domain(domain.as_str()) 44 | .max_age_time(time::Duration::days(1)) 45 | .secure(false), // this can only be true if you have https 46 | )) 47 | .state(web::types::JsonConfig::default().limit(4096)) 48 | // everything under '/api/' route 49 | .service( 50 | web::scope("/api").service(( 51 | web::resource("/invitation") 52 | .route(web::post().to(invitation_handler::post_invitation)), 53 | web::resource("/register/{invitation_id}") 54 | .route(web::post().to(register_handler::register_user)), 55 | web::resource("/auth") 56 | .route(web::post().to(auth_handler::login)) 57 | .route(web::delete().to(auth_handler::logout)) 58 | .route(web::get().to(auth_handler::get_me)), 59 | )), 60 | ) 61 | }) 62 | .bind("127.0.0.1:3000")? 63 | .run() 64 | .await 65 | } 66 | -------------------------------------------------------------------------------- /simple-auth-server/src/email_service.rs: -------------------------------------------------------------------------------- 1 | // email_service.rs 2 | use crate::errors::ServiceError; 3 | use crate::models::Invitation; 4 | use sparkpost::transmission::{ 5 | EmailAddress, Message, Options, Recipient, Transmission, TransmissionResponse, 6 | }; 7 | 8 | lazy_static::lazy_static! { 9 | static ref API_KEY: String = std::env::var("SPARKPOST_API_KEY").expect("SPARKPOST_API_KEY must be set"); 10 | } 11 | 12 | pub fn send_invitation(invitation: &Invitation) -> Result<(), ServiceError> { 13 | let tm = Transmission::new_eu(API_KEY.as_str()); 14 | let sending_email = std::env::var("SENDING_EMAIL_ADDRESS") 15 | .expect("SENDING_EMAIL_ADDRESS must be set"); 16 | // new email message with sender name and email 17 | let mut email = Message::new(EmailAddress::new(sending_email, "Let's Organise")); 18 | 19 | let options = Options { 20 | open_tracking: false, 21 | click_tracking: false, 22 | transactional: true, 23 | sandbox: false, 24 | inline_css: false, 25 | start_time: None, 26 | }; 27 | 28 | // recipient from the invitation email 29 | let recipient: Recipient = invitation.email.as_str().into(); 30 | 31 | let email_body = format!( 32 | "Please click on the link below to complete registration.
33 | 34 | http://localhost:3030/register
35 | your Invitation expires on {}", 36 | invitation.id, 37 | invitation.email, 38 | invitation.expires_at.format("%I:%M %p %A, %-d %B, %C%y") 39 | ); 40 | 41 | // complete the email message with details 42 | email 43 | .add_recipient(recipient) 44 | .options(options) 45 | .subject("You have been invited to join Simple-Auth-Server Rust") 46 | .html(email_body); 47 | 48 | let result = tm.send(&email); 49 | 50 | // Note that we only print out the error response from email api 51 | match result { 52 | Ok(res) => match res { 53 | TransmissionResponse::ApiResponse(api_res) => { 54 | println!("API Response: \n {:#?}", api_res); 55 | Ok(()) 56 | } 57 | TransmissionResponse::ApiError(errors) => { 58 | println!("Response Errors: \n {:#?}", &errors); 59 | Err(ServiceError::InternalServerError) 60 | } 61 | }, 62 | Err(error) => { 63 | println!("Send Email Error: \n {:#?}", error); 64 | Err(ServiceError::InternalServerError) 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /todo/templates/index.html.tera: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Ntex Todo Example 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |

17 | 18 |
19 |

Ntex Todo

20 |
21 |
22 | 25 | {% if msg %} 26 | 27 | {{msg.1}} 28 | 29 | {% endif %} 30 |
31 |
32 | 33 |
34 |
35 |
36 | 37 |
38 |
39 |
    40 | {% for task in tasks %} 41 |
  • 42 | {% if task.completed %} 43 | {{task.description}} 44 |
    45 | 46 | 47 |
    48 |
    49 | 50 | 51 |
    52 | {% else %} 53 | 57 | {% endif %} 58 |
  • 59 | {% endfor %} 60 |
61 |
62 |
63 |
64 | 65 | 66 | -------------------------------------------------------------------------------- /websocket/src/client.rs: -------------------------------------------------------------------------------- 1 | //! Simple websocket client. 2 | use std::{io, thread, time::Duration}; 3 | 4 | use futures::{channel::mpsc, SinkExt, StreamExt}; 5 | use ntex::{rt, time, util::Bytes, ws, SharedCfg}; 6 | 7 | /// How often heartbeat pings are sent 8 | const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5); 9 | 10 | #[ntex::main] 11 | async fn main() -> Result<(), io::Error> { 12 | std::env::set_var("RUST_LOG", "ntex=trace"); 13 | env_logger::init(); 14 | 15 | // open websockets connection over http transport 16 | let con = ws::WsClient::build("http://127.0.0.1:8080/ws/") 17 | .finish(SharedCfg::default()) 18 | .await 19 | .unwrap() 20 | .connect() 21 | .await 22 | .unwrap(); 23 | 24 | println!("Got response: {:?}", con.response()); 25 | 26 | let (mut tx, mut rx) = mpsc::unbounded(); 27 | 28 | // start console read loop 29 | thread::spawn(move || loop { 30 | let mut cmd = String::new(); 31 | if io::stdin().read_line(&mut cmd).is_err() { 32 | println!("error"); 33 | return; 34 | } 35 | 36 | // send text to server 37 | if futures::executor::block_on(tx.send(ws::Message::Text(cmd.into()))).is_err() { 38 | return; 39 | } 40 | }); 41 | 42 | // read console commands 43 | let sink = con.sink(); 44 | rt::spawn(async move { 45 | while let Some(msg) = rx.next().await { 46 | if sink.send(msg).await.is_err() { 47 | return; 48 | } 49 | } 50 | }); 51 | 52 | // start heartbeat task 53 | let sink = con.sink(); 54 | rt::spawn(async move { 55 | loop { 56 | time::sleep(HEARTBEAT_INTERVAL).await; 57 | if sink.send(ws::Message::Ping(Bytes::new())).await.is_err() { 58 | return; 59 | } 60 | } 61 | }); 62 | 63 | // run ws dispatcher 64 | let sink = con.sink(); 65 | let mut rx = con.seal().receiver(); 66 | 67 | while let Some(frame) = rx.next().await { 68 | match frame { 69 | Ok(ws::Frame::Text(text)) => { 70 | println!("Server: {:?}", text); 71 | } 72 | Ok(ws::Frame::Ping(msg)) => { 73 | // send pong response 74 | println!("Got server ping: {:?}", msg); 75 | sink.send(ws::Message::Pong(msg)) 76 | .await 77 | .map_err(io::Error::other)?; 78 | } 79 | Err(_) => break, 80 | _ => (), 81 | } 82 | } 83 | 84 | println!("Disconnected"); 85 | Ok(()) 86 | } 87 | -------------------------------------------------------------------------------- /websocket-chat/client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """websocket cmd client for ntex/websocket-tcp-chat example.""" 3 | import argparse 4 | import asyncio 5 | import signal 6 | import sys 7 | 8 | import aiohttp 9 | 10 | queue = asyncio.Queue() 11 | 12 | 13 | async def start_client(url, loop): 14 | name = input('Please enter your name: ') 15 | 16 | ws = await aiohttp.ClientSession().ws_connect(url, autoclose=False, autoping=False) 17 | 18 | def stdin_callback(): 19 | line = sys.stdin.buffer.readline().decode('utf-8') 20 | if not line: 21 | loop.stop() 22 | else: 23 | # Queue.put is a coroutine, so you can't call it directly. 24 | asyncio.ensure_future(queue.put(ws.send_str(name + ': ' + line))) 25 | 26 | loop.add_reader(sys.stdin, stdin_callback) 27 | 28 | async def dispatch(): 29 | while True: 30 | msg = await ws.receive() 31 | if msg.type == aiohttp.WSMsgType.TEXT: 32 | print('Text: ', msg.data.strip()) 33 | elif msg.type == aiohttp.WSMsgType.BINARY: 34 | print('Binary: ', msg.data) 35 | elif msg.type == aiohttp.WSMsgType.PING: 36 | await ws.pong() 37 | elif msg.type == aiohttp.WSMsgType.PONG: 38 | print('Pong received') 39 | else: 40 | if msg.type == aiohttp.WSMsgType.CLOSE: 41 | await ws.close() 42 | elif msg.type == aiohttp.WSMsgType.ERROR: 43 | print('Error during receive %s' % ws.exception()) 44 | elif msg.type == aiohttp.WSMsgType.CLOSED: 45 | pass 46 | break 47 | 48 | await dispatch() 49 | 50 | 51 | async def tick(): 52 | while True: 53 | await (await queue.get()) 54 | 55 | 56 | async def main(url, loop): 57 | await asyncio.wait([start_client(url, loop), tick()]) 58 | 59 | 60 | ARGS = argparse.ArgumentParser( 61 | description="websocket console client for wssrv.py example.") 62 | ARGS.add_argument( 63 | '--host', action="store", dest='host', 64 | default='127.0.0.1', help='Host name') 65 | ARGS.add_argument( 66 | '--port', action="store", dest='port', 67 | default=8080, type=int, help='Port number') 68 | 69 | if __name__ == '__main__': 70 | args = ARGS.parse_args() 71 | if ':' in args.host: 72 | args.host, port = args.host.split(':', 1) 73 | args.port = int(port) 74 | 75 | url = 'http://{}:{}/ws/'.format(args.host, args.port) 76 | 77 | loop = asyncio.get_event_loop() 78 | loop.add_signal_handler(signal.SIGINT, loop.stop) 79 | asyncio.Task(main(url, loop)) 80 | loop.run_forever() 81 | -------------------------------------------------------------------------------- /websocket-tcp-chat/client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """websocket cmd client for ntex/websocket-tcp-chat example.""" 3 | import argparse 4 | import asyncio 5 | import signal 6 | import sys 7 | 8 | import aiohttp 9 | 10 | queue = asyncio.Queue() 11 | 12 | 13 | async def start_client(url, loop): 14 | name = input('Please enter your name: ') 15 | 16 | ws = await aiohttp.ClientSession().ws_connect(url, autoclose=False, autoping=False) 17 | 18 | def stdin_callback(): 19 | line = sys.stdin.buffer.readline().decode('utf-8') 20 | if not line: 21 | loop.stop() 22 | else: 23 | # Queue.put is a coroutine, so you can't call it directly. 24 | asyncio.ensure_future(queue.put(ws.send_str(name + ': ' + line))) 25 | 26 | loop.add_reader(sys.stdin, stdin_callback) 27 | 28 | async def dispatch(): 29 | while True: 30 | msg = await ws.receive() 31 | if msg.type == aiohttp.WSMsgType.TEXT: 32 | print('Text: ', msg.data.strip()) 33 | elif msg.type == aiohttp.WSMsgType.BINARY: 34 | print('Binary: ', msg.data) 35 | elif msg.type == aiohttp.WSMsgType.PING: 36 | await ws.pong() 37 | elif msg.type == aiohttp.WSMsgType.PONG: 38 | print('Pong received') 39 | else: 40 | if msg.type == aiohttp.WSMsgType.CLOSE: 41 | await ws.close() 42 | elif msg.type == aiohttp.WSMsgType.ERROR: 43 | print('Error during receive %s' % ws.exception()) 44 | elif msg.type == aiohttp.WSMsgType.CLOSED: 45 | pass 46 | break 47 | 48 | await dispatch() 49 | 50 | 51 | async def tick(): 52 | while True: 53 | await (await queue.get()) 54 | 55 | 56 | async def main(url, loop): 57 | await asyncio.wait([start_client(url, loop), tick()]) 58 | 59 | 60 | ARGS = argparse.ArgumentParser( 61 | description="websocket console client for wssrv.py example.") 62 | ARGS.add_argument( 63 | '--host', action="store", dest='host', 64 | default='127.0.0.1', help='Host name') 65 | ARGS.add_argument( 66 | '--port', action="store", dest='port', 67 | default=8080, type=int, help='Port number') 68 | 69 | if __name__ == '__main__': 70 | args = ARGS.parse_args() 71 | if ':' in args.host: 72 | args.host, port = args.host.split(':', 1) 73 | args.port = int(port) 74 | 75 | url = 'http://{}:{}/ws/'.format(args.host, args.port) 76 | 77 | loop = asyncio.get_event_loop() 78 | loop.add_signal_handler(signal.SIGINT, loop.stop) 79 | asyncio.Task(main(url, loop)) 80 | loop.run_forever() 81 | -------------------------------------------------------------------------------- /diesel/README.md: -------------------------------------------------------------------------------- 1 | # diesel 2 | 3 | Basic integration of [Diesel](https://diesel.rs/) using SQLite for Ntex. 4 | 5 | ## Usage 6 | 7 | ### Install SQLite 8 | 9 | ```sh 10 | # on OpenSUSE 11 | sudo zypper install sqlite3-devel libsqlite3-0 sqlite3 12 | 13 | # on Ubuntu 14 | sudo apt-get install libsqlite3-dev sqlite3 15 | 16 | # on Fedora 17 | sudo dnf install libsqlite3x-devel sqlite3x 18 | 19 | # on macOS (using homebrew) 20 | brew install sqlite3 21 | ``` 22 | 23 | ### Initialize SQLite Database 24 | 25 | ```sh 26 | cd examples/diesel 27 | cargo install diesel_cli --no-default-features --features sqlite 28 | 29 | echo "DATABASE_URL=test.db" > .env 30 | diesel migration run 31 | ``` 32 | 33 | There will now be a database file at `./test.db`. 34 | 35 | ### Running Server 36 | 37 | ```sh 38 | cd examples/diesel 39 | cargo run (or ``cargo watch -x run``) 40 | 41 | # Started http server: 127.0.0.1:8080 42 | ``` 43 | 44 | ### Available Routes 45 | 46 | #### `POST /user` 47 | 48 | Inserts a new user into the SQLite DB. 49 | 50 | Provide a JSON payload with a name. Eg: 51 | ```json 52 | { "name": "bill" } 53 | ``` 54 | 55 | On success, a response like the following is returned: 56 | ```json 57 | { 58 | "id": "9e46baba-a001-4bb3-b4cf-4b3e5bab5e97", 59 | "name": "bill" 60 | } 61 | ``` 62 | 63 |
64 | Client Examples 65 | 66 | Using [HTTPie](https://httpie.org/): 67 | ```sh 68 | http POST localhost:8080/user name=bill 69 | ``` 70 | 71 | Using cURL: 72 | ```sh 73 | curl -S -X POST --header "Content-Type: application/json" --data '{"name":"bill"}' http://localhost:8080/user 74 | ``` 75 |
76 | 77 | #### `GET /user/{user_uid}` 78 | 79 | Gets a user from the DB using its UID (returned from the insert request or taken from the DB directly). Returns a 404 when no user exists with that UID. 80 | 81 |
82 | Client Examples 83 | 84 | Using [HTTPie](https://httpie.org/): 85 | ```sh 86 | http localhost:8080/user/9e46baba-a001-4bb3-b4cf-4b3e5bab5e97 87 | ``` 88 | 89 | Using cURL: 90 | ```sh 91 | curl -S http://localhost:8080/user/9e46baba-a001-4bb3-b4cf-4b3e5bab5e97 92 | ``` 93 |
94 | 95 | ### Explore The SQLite DB 96 | 97 | ```sh 98 | sqlite3 test.db 99 | ``` 100 | 101 | ``` 102 | sqlite> .tables 103 | sqlite> SELECT * FROM users; 104 | ``` 105 | 106 | 107 | ## Using Other Databases 108 | 109 | You can find a complete example of Diesel + PostgreSQL at: [https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/frameworks/Rust/ntex](https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/frameworks/Rust/ntex) 110 | -------------------------------------------------------------------------------- /simple-auth-server/src/register_handler.rs: -------------------------------------------------------------------------------- 1 | use diesel::prelude::*; 2 | use ntex::web::{self, error::BlockingError, HttpResponse}; 3 | use serde::Deserialize; 4 | 5 | use crate::errors::ServiceError; 6 | use crate::models::{Invitation, Pool, SlimUser, User}; 7 | use crate::utils::hash_password; 8 | // UserData is used to extract data from a post request by the client 9 | #[derive(Debug, Deserialize)] 10 | pub struct UserData { 11 | pub password: String, 12 | } 13 | 14 | pub async fn register_user( 15 | invitation_id: web::types::Path, 16 | user_data: web::types::Json, 17 | pool: web::types::State, 18 | ) -> Result { 19 | let pool = (*pool).clone(); 20 | let res = web::block(move || { 21 | query( 22 | invitation_id.into_inner(), 23 | user_data.into_inner().password, 24 | pool, 25 | ) 26 | }) 27 | .await; 28 | 29 | match res { 30 | Ok(user) => Ok(HttpResponse::Ok().json(&user)), 31 | Err(err) => match err { 32 | BlockingError::Error(service_error) => Err(service_error), 33 | BlockingError::Canceled => Err(ServiceError::InternalServerError), 34 | }, 35 | } 36 | } 37 | 38 | fn query( 39 | invitation_id: String, 40 | password: String, 41 | pool: Pool, 42 | ) -> Result { 43 | use crate::schema::invitations::dsl::{id, invitations}; 44 | use crate::schema::users::dsl::users; 45 | let invitation_id = uuid::Uuid::parse_str(&invitation_id)?; 46 | 47 | let conn: &PgConnection = &pool.get().unwrap(); 48 | invitations 49 | .filter(id.eq(invitation_id)) 50 | .load::(conn) 51 | .map_err(|_db_error| ServiceError::BadRequest("Invalid Invitation".into())) 52 | .and_then(|mut result| { 53 | if let Some(invitation) = result.pop() { 54 | // if invitation is not expired 55 | if invitation.expires_at > chrono::Local::now().naive_local() { 56 | // try hashing the password, else return the error that will be converted to ServiceError 57 | let password: String = hash_password(&password)?; 58 | dbg!(&password); 59 | let user = User::from_details(invitation.email, password); 60 | let inserted_user: User = 61 | diesel::insert_into(users).values(&user).get_result(conn)?; 62 | dbg!(&inserted_user); 63 | return Ok(inserted_user.into()); 64 | } 65 | } 66 | Err(ServiceError::BadRequest("Invalid Invitation".into())) 67 | }) 68 | } 69 | -------------------------------------------------------------------------------- /websocket-tcp-chat/src/client-ws.rs: -------------------------------------------------------------------------------- 1 | //! Simple websocket client. 2 | use std::{convert::TryFrom, io, thread, time::Duration}; 3 | 4 | use futures::{channel::mpsc, SinkExt, StreamExt}; 5 | use ntex::{rt, time, util::ByteString, util::Bytes, ws, SharedCfg}; 6 | 7 | /// How often heartbeat pings are sent 8 | const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5); 9 | 10 | #[ntex::main] 11 | async fn main() -> Result<(), io::Error> { 12 | std::env::set_var("RUST_LOG", "ntex=trace,ntex_io=info,ntex_tokio=info"); 13 | env_logger::init(); 14 | 15 | // open websockets connection over http transport 16 | let con = ws::WsClient::build("http://127.0.0.1:8080/ws/") 17 | .finish(SharedCfg::default()) 18 | .await 19 | .unwrap() 20 | .connect() 21 | .await 22 | .unwrap(); 23 | 24 | println!("Got response: {:?}", con.response()); 25 | 26 | let (mut tx, mut rx) = mpsc::unbounded(); 27 | 28 | // start console read loop 29 | thread::spawn(move || loop { 30 | let mut cmd = String::new(); 31 | if io::stdin().read_line(&mut cmd).is_err() { 32 | println!("error"); 33 | return; 34 | } 35 | 36 | // send text to server 37 | if futures::executor::block_on(tx.send(ws::Message::Text(cmd.into()))).is_err() { 38 | return; 39 | } 40 | }); 41 | 42 | // read console commands 43 | let sink = con.sink(); 44 | rt::spawn(async move { 45 | while let Some(msg) = rx.next().await { 46 | if sink.send(msg).await.is_err() { 47 | return; 48 | } 49 | } 50 | }); 51 | 52 | // start heartbeat task 53 | let sink = con.sink(); 54 | rt::spawn(async move { 55 | loop { 56 | time::sleep(HEARTBEAT_INTERVAL).await; 57 | if sink.send(ws::Message::Ping(Bytes::new())).await.is_err() { 58 | return; 59 | } 60 | } 61 | }); 62 | 63 | // run ws dispatcher 64 | let sink = con.sink(); 65 | let mut rx = con.seal().receiver(); 66 | 67 | while let Some(frame) = rx.next().await { 68 | match frame { 69 | Ok(ws::Frame::Text(text)) => { 70 | println!("Server: {}", ByteString::try_from(text).unwrap()); 71 | } 72 | Ok(ws::Frame::Ping(msg)) => { 73 | // send pong response 74 | println!("Got server ping: {:?}", msg); 75 | sink.send(ws::Message::Pong(msg)) 76 | .await 77 | .map_err(io::Error::other)?; 78 | } 79 | Err(_) => break, 80 | _ => (), 81 | } 82 | } 83 | 84 | println!("Disconnected"); 85 | Ok(()) 86 | } 87 | -------------------------------------------------------------------------------- /websocket/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 75 | 76 | 77 |

Chat!

78 |
79 |  | Status: 80 | disconnected 81 |
82 |
84 |
85 |
86 | 87 | 88 |
89 | 90 | 91 | -------------------------------------------------------------------------------- /static_index/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 75 | 76 | 77 |

Chat!

78 |
79 |  | Status: 80 | disconnected 81 |
82 |
84 |
85 |
86 | 87 | 88 |
89 | 90 | 91 | -------------------------------------------------------------------------------- /websocket-chat/static/websocket.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 75 | 76 | 77 |

Chat!

78 |
79 |  | Status: 80 | disconnected 81 |
82 |
84 |
85 |
86 | 87 | 88 |
89 | 90 | 91 | -------------------------------------------------------------------------------- /websocket-lowlevel/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 75 | 76 | 77 |

Chat!

78 |
79 |  | Status: 80 | disconnected 81 |
82 |
84 |
85 |
86 | 87 | 88 |
89 | 90 | 91 | -------------------------------------------------------------------------------- /websocket-tcp-chat/static/websocket.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 75 | 76 | 77 |

Chat!

78 |
79 |  | Status: 80 | disconnected 81 |
82 |
84 |
85 |
86 | 87 | 88 |
89 | 90 | 91 | -------------------------------------------------------------------------------- /simple-auth-server/src/auth_handler.rs: -------------------------------------------------------------------------------- 1 | use diesel::prelude::*; 2 | use diesel::PgConnection; 3 | use futures::future::{ready, Ready}; 4 | use ntex::http::Payload; 5 | use ntex::web::{ 6 | self, error::BlockingError, Error, FromRequest, HttpRequest, HttpResponse, 7 | }; 8 | use ntex_identity::{Identity, RequestIdentity}; 9 | use serde::Deserialize; 10 | 11 | use crate::errors::ServiceError; 12 | use crate::models::{Pool, SlimUser, User}; 13 | use crate::utils::verify; 14 | 15 | #[derive(Debug, Deserialize)] 16 | pub struct AuthData { 17 | pub email: String, 18 | pub password: String, 19 | } 20 | 21 | // we need the same data 22 | // simple aliasing makes the intentions clear and its more readable 23 | pub type LoggedUser = SlimUser; 24 | 25 | impl FromRequest for LoggedUser { 26 | type Error = Error; 27 | type Future = Ready>; 28 | 29 | fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { 30 | let id = req.get_identity(); 31 | 32 | ready(if let Some(identity) = id { 33 | serde_json::from_str::(&identity).map_err(From::from) 34 | } else { 35 | Err(ServiceError::Unauthorized.into()) 36 | }) 37 | } 38 | } 39 | 40 | pub async fn logout(id: Identity) -> HttpResponse { 41 | id.forget(); 42 | HttpResponse::Ok().finish() 43 | } 44 | 45 | pub async fn login( 46 | auth_data: web::types::Json, 47 | id: Identity, 48 | pool: web::types::State, 49 | ) -> Result { 50 | let pool = (*pool).clone(); 51 | let res = web::block(move || query(auth_data.into_inner(), pool)).await; 52 | 53 | match res { 54 | Ok(user) => { 55 | let user_string = serde_json::to_string(&user).unwrap(); 56 | id.remember(user_string); 57 | Ok(HttpResponse::Ok().finish()) 58 | } 59 | Err(err) => match err { 60 | BlockingError::Error(service_error) => Err(service_error), 61 | BlockingError::Canceled => Err(ServiceError::InternalServerError), 62 | }, 63 | } 64 | } 65 | 66 | pub async fn get_me(logged_user: LoggedUser) -> HttpResponse { 67 | HttpResponse::Ok().json(&logged_user) 68 | } 69 | /// Diesel query 70 | fn query(auth_data: AuthData, pool: Pool) -> Result { 71 | use crate::schema::users::dsl::{email, users}; 72 | let conn: &PgConnection = &pool.get().unwrap(); 73 | let mut items = users 74 | .filter(email.eq(&auth_data.email)) 75 | .load::(conn)?; 76 | 77 | if let Some(user) = items.pop() { 78 | if let Ok(matching) = verify(&user.hash, &auth_data.password) { 79 | if matching { 80 | return Ok(user.into()); 81 | } 82 | } 83 | } 84 | Err(ServiceError::Unauthorized) 85 | } 86 | -------------------------------------------------------------------------------- /diesel/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Ntex Diesel integration example 2 | //! 3 | //! Diesel does not support tokio, so we have to run it in separate threads using the web::block 4 | //! function which offloads blocking code (like Diesel's) in order to not block the server's thread. 5 | 6 | #[macro_use] 7 | extern crate diesel; 8 | 9 | use diesel::prelude::*; 10 | use diesel::r2d2::{self, ConnectionManager}; 11 | use ntex::web::{self, middleware, App, Error, HttpResponse}; 12 | use uuid::Uuid; 13 | 14 | mod actions; 15 | mod models; 16 | mod schema; 17 | 18 | type DbPool = r2d2::Pool>; 19 | 20 | /// Finds user by UID. 21 | #[web::get("/user/{user_id}")] 22 | async fn get_user( 23 | pool: web::types::State, 24 | user_uid: web::types::Path, 25 | ) -> Result { 26 | let user_uid = user_uid.into_inner(); 27 | let conn = pool.get().expect("couldn't get db connection from pool"); 28 | 29 | // use web::block to offload blocking Diesel code without blocking server thread 30 | let user = web::block(move || actions::find_user_by_uid(user_uid, &conn)).await?; 31 | 32 | if let Some(user) = user { 33 | Ok(HttpResponse::Ok().json(&user)) 34 | } else { 35 | let res = HttpResponse::NotFound() 36 | .body(format!("No user found with uid: {}", user_uid)); 37 | Ok(res) 38 | } 39 | } 40 | 41 | /// Inserts new user with name defined in form. 42 | #[web::post("/user")] 43 | async fn add_user( 44 | pool: web::types::State, 45 | form: web::types::Json, 46 | ) -> Result { 47 | let conn = pool.get().expect("couldn't get db connection from pool"); 48 | 49 | // use web::block to offload blocking Diesel code without blocking server thread 50 | let user = web::block(move || actions::insert_new_user(&form.name, &conn)).await?; 51 | 52 | Ok(HttpResponse::Ok().json(&user)) 53 | } 54 | 55 | #[ntex::main] 56 | async fn main() -> std::io::Result<()> { 57 | std::env::set_var("RUST_LOG", "ntex=info,diesel=debug"); 58 | env_logger::init(); 59 | dotenv::dotenv().ok(); 60 | 61 | // set up database connection pool 62 | let connspec = std::env::var("DATABASE_URL").expect("DATABASE_URL"); 63 | let manager = ConnectionManager::::new(connspec); 64 | let pool = r2d2::Pool::builder() 65 | .build(manager) 66 | .expect("Failed to create pool."); 67 | 68 | let bind = "127.0.0.1:8080"; 69 | 70 | println!("Starting server at: {}", &bind); 71 | 72 | // Start HTTP server 73 | web::server(async move || { 74 | App::new() 75 | // set up DB pool to be used with web::State extractor 76 | .state(pool.clone()) 77 | .wrap(middleware::Logger::default()) 78 | .service((get_user, add_user)) 79 | }) 80 | .bind(&bind)? 81 | .run() 82 | .await 83 | } 84 | -------------------------------------------------------------------------------- /graphql-demo/mysql-schema.sql: -------------------------------------------------------------------------------- 1 | -- MySQL dump 10.13 Distrib 8.0.16, for osx10.14 (x86_64) 2 | -- 3 | -- Host: 127.0.0.1 Database: rust_graphql 4 | -- ------------------------------------------------------ 5 | -- Server version 8.0.15 6 | 7 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 8 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 9 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 10 | SET NAMES utf8mb4 ; 11 | /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; 12 | /*!40103 SET TIME_ZONE='+00:00' */; 13 | /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; 14 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; 15 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; 16 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; 17 | 18 | -- 19 | -- Table structure for table `product` 20 | -- 21 | 22 | DROP TABLE IF EXISTS `product`; 23 | /*!40101 SET @saved_cs_client = @@character_set_client */; 24 | SET character_set_client = utf8mb4 ; 25 | CREATE TABLE `product` ( 26 | `id` varchar(255) NOT NULL, 27 | `user_id` varchar(255) NOT NULL, 28 | `name` varchar(255) NOT NULL, 29 | `price` decimal(10,0) NOT NULL, 30 | PRIMARY KEY (`id`), 31 | KEY `product_fk0` (`user_id`), 32 | CONSTRAINT `product_fk0` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) 33 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; 34 | /*!40101 SET character_set_client = @saved_cs_client */; 35 | 36 | -- 37 | -- Dumping data for table `product` 38 | -- 39 | 40 | LOCK TABLES `product` WRITE; 41 | /*!40000 ALTER TABLE `product` DISABLE KEYS */; 42 | /*!40000 ALTER TABLE `product` ENABLE KEYS */; 43 | UNLOCK TABLES; 44 | 45 | -- 46 | -- Table structure for table `user` 47 | -- 48 | 49 | DROP TABLE IF EXISTS `user`; 50 | /*!40101 SET @saved_cs_client = @@character_set_client */; 51 | SET character_set_client = utf8mb4 ; 52 | CREATE TABLE `user` ( 53 | `id` varchar(255) NOT NULL, 54 | `name` varchar(255) NOT NULL, 55 | `email` varchar(255) NOT NULL, 56 | PRIMARY KEY (`id`), 57 | UNIQUE KEY `email` (`email`) 58 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; 59 | /*!40101 SET character_set_client = @saved_cs_client */; 60 | 61 | -- 62 | -- Dumping data for table `user` 63 | -- 64 | 65 | LOCK TABLES `user` WRITE; 66 | /*!40000 ALTER TABLE `user` DISABLE KEYS */; 67 | /*!40000 ALTER TABLE `user` ENABLE KEYS */; 68 | UNLOCK TABLES; 69 | /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; 70 | 71 | /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; 72 | /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; 73 | /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; 74 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 75 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 76 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 77 | /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; 78 | -------------------------------------------------------------------------------- /async_db/src/main.rs: -------------------------------------------------------------------------------- 1 | /* Ntex Asynchronous Database Example 2 | 3 | This project illustrates expensive and blocking database requests that runs 4 | in a thread-pool using `web::block` with two examples: 5 | 6 | 1. An asynchronous handler that executes 4 queries in *sequential order*, 7 | collecting the results and returning them as a single serialized json object 8 | 9 | 2. An asynchronous handler that executes 4 queries in *parallel*, 10 | collecting the results and returning them as a single serialized json object 11 | 12 | Note: The use of sleep(Duration::from_secs(2)); in db.rs is to make performance 13 | improvement with parallelism more obvious. 14 | */ 15 | use std::io; 16 | 17 | use futures::future::join_all; 18 | use ntex::web::{self, middleware, App, HttpResponse, HttpServer}; 19 | use r2d2_sqlite::{self, SqliteConnectionManager}; 20 | 21 | mod db; 22 | use db::{Error, Pool, Queries}; 23 | 24 | /// Version 1: Calls 4 queries in sequential order, as an asynchronous handler 25 | #[web::get("/asyncio_weather")] 26 | async fn asyncio_weather(db: web::types::State) -> Result { 27 | let result = vec![ 28 | db::execute(&db, Queries::GetTopTenHottestYears).await?, 29 | db::execute(&db, Queries::GetTopTenColdestYears).await?, 30 | db::execute(&db, Queries::GetTopTenHottestMonths).await?, 31 | db::execute(&db, Queries::GetTopTenColdestMonths).await?, 32 | ]; 33 | 34 | Ok(HttpResponse::Ok().json(&result)) 35 | } 36 | 37 | /// Version 2: Calls 4 queries in parallel, as an asynchronous handler 38 | /// Returning Error types turn into None values in the response 39 | #[web::get("/parallel_weather")] 40 | async fn parallel_weather(db: web::types::State) -> Result { 41 | let fut_result = vec![ 42 | Box::pin(db::execute(&db, Queries::GetTopTenHottestYears)), 43 | Box::pin(db::execute(&db, Queries::GetTopTenColdestYears)), 44 | Box::pin(db::execute(&db, Queries::GetTopTenHottestMonths)), 45 | Box::pin(db::execute(&db, Queries::GetTopTenColdestMonths)), 46 | ]; 47 | let result: Result, _> = join_all(fut_result).await.into_iter().collect(); 48 | 49 | Ok(HttpResponse::Ok().json(&result?)) 50 | } 51 | 52 | #[ntex::main] 53 | async fn main() -> io::Result<()> { 54 | std::env::set_var("RUST_LOG", "ntex=info"); 55 | env_logger::init(); 56 | 57 | // Start N db executor actors (N = number of cores avail) 58 | let manager = SqliteConnectionManager::file("weather.db"); 59 | let pool = Pool::new(manager).unwrap(); 60 | 61 | // Start http server 62 | HttpServer::new(async move || { 63 | App::new() 64 | // store db pool as Data object 65 | .state(pool.clone()) 66 | .wrap(middleware::Logger::default()) 67 | .service((asyncio_weather, parallel_weather)) 68 | }) 69 | .bind("127.0.0.1:8080")? 70 | .run() 71 | .await 72 | } 73 | -------------------------------------------------------------------------------- /state/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Application may have multiple data objects that are shared across 2 | //! all handlers within same Application. 3 | //! 4 | //! For global shared state, we wrap our state in a `ntex::web::types::State` and move it into 5 | //! the factory closure. The closure is called once-per-thread, and we clone our state 6 | //! and attach to each instance of the `App` with `.app_state(state.clone())`. 7 | //! 8 | //! For thread-local state, we construct our state within the factory closure and attach to 9 | //! the app with `.state(state)`. 10 | //! 11 | //! We retrieve our app state within our handlers with a `state: State<...>` argument. 12 | //! 13 | //! By default, `ntex` runs one `App` per logical cpu core. 14 | //! When running on cores, we see that the example will increment `counter1` (global state via 15 | //! Mutex) and `counter3` (global state via Atomic variable) each time the endpoint is called, 16 | //! but only appear to increment `counter2` every Nth time on average (thread-local state). This 17 | //! is because the workload is being shared equally among cores. 18 | 19 | use std::cell::Cell; 20 | use std::io; 21 | use std::sync::atomic::{AtomicUsize, Ordering}; 22 | use std::sync::{Arc, Mutex}; 23 | 24 | use ntex::web::{self, middleware, App, HttpRequest, HttpResponse}; 25 | 26 | /// simple handle 27 | async fn index( 28 | counter1: web::types::State>>, 29 | counter2: web::types::State>, 30 | counter3: web::types::State>, 31 | req: HttpRequest, 32 | ) -> HttpResponse { 33 | println!("{:?}", req); 34 | 35 | // Increment the counters 36 | *counter1.lock().unwrap() += 1; 37 | counter2.set(counter2.get() + 1); 38 | counter3.fetch_add(1, Ordering::SeqCst); 39 | 40 | let body = format!( 41 | "global mutex counter: {}, local counter: {}, global atomic counter: {}", 42 | *counter1.lock().unwrap(), 43 | counter2.get(), 44 | counter3.load(Ordering::SeqCst), 45 | ); 46 | HttpResponse::Ok().body(body) 47 | } 48 | 49 | #[ntex::main] 50 | async fn main() -> io::Result<()> { 51 | std::env::set_var("RUST_LOG", "info"); 52 | env_logger::init(); 53 | 54 | // Create some global state prior to building the server 55 | #[allow(clippy::mutex_atomic)] // it's intentional. 56 | let counter1 = Arc::new(Mutex::new(0usize)); 57 | let counter3 = Arc::new(AtomicUsize::new(0usize)); 58 | 59 | // move is necessary to give closure below ownership of counter1 60 | web::server(async move || { 61 | // Create some thread-local state 62 | let counter2 = Cell::new(0u32); 63 | 64 | App::new() 65 | .state(counter1.clone()) // add shared state 66 | .state(counter3.clone()) // add shared state 67 | .state(counter2) // add thread-local state 68 | // enable logger 69 | .wrap(middleware::Logger::default()) 70 | // register simple handler 71 | .service(web::resource("/").to(index)) 72 | }) 73 | .bind("127.0.0.1:8080")? 74 | .run() 75 | .await 76 | } 77 | -------------------------------------------------------------------------------- /websocket-lowlevel/src/client.rs: -------------------------------------------------------------------------------- 1 | //! Simple websocket client. 2 | use std::{io, thread, time::Duration}; 3 | 4 | use futures::{channel::mpsc, SinkExt, StreamExt}; 5 | use ntex::{io::IoConfig, rt, time, util::Bytes, ws, SharedCfg}; 6 | use openssl::ssl; 7 | 8 | /// How often heartbeat pings are sent 9 | const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5); 10 | 11 | #[ntex::main] 12 | async fn main() -> Result<(), io::Error> { 13 | std::env::set_var("RUST_LOG", "ntex=trace"); 14 | env_logger::init(); 15 | 16 | run().await 17 | } 18 | 19 | async fn run() -> Result<(), io::Error> { 20 | // ssl connector 21 | let mut builder = ssl::SslConnector::builder(ssl::SslMethod::tls()).unwrap(); 22 | builder.set_verify(ssl::SslVerifyMode::NONE); 23 | 24 | // open websockets connection over http transport 25 | let con = ws::WsClient::build("http://127.0.0.1:8080/ws/") 26 | .openssl(builder.build()) 27 | .finish( 28 | SharedCfg::new("WS") 29 | .add(IoConfig::new().set_keepalive_timeout(time::Seconds::ZERO)), 30 | ) 31 | .await 32 | .unwrap() 33 | .connect() 34 | .await 35 | .unwrap(); 36 | 37 | println!("Got response: {:?}", con.response()); 38 | 39 | let (mut tx, mut rx) = mpsc::unbounded(); 40 | 41 | // start console read loop 42 | thread::spawn(move || loop { 43 | let mut cmd = String::new(); 44 | if io::stdin().read_line(&mut cmd).is_err() { 45 | println!("error"); 46 | return; 47 | } 48 | 49 | // send text to server 50 | if futures::executor::block_on(tx.send(ws::Message::Text(cmd.into()))).is_err() { 51 | return; 52 | } 53 | }); 54 | 55 | // read console commands 56 | let sink = con.sink(); 57 | rt::spawn(async move { 58 | while let Some(msg) = rx.next().await { 59 | if sink.send(msg).await.is_err() { 60 | return; 61 | } 62 | } 63 | }); 64 | 65 | // start heartbeat task 66 | let sink = con.sink(); 67 | rt::spawn(async move { 68 | loop { 69 | time::sleep(HEARTBEAT_INTERVAL).await; 70 | if sink.send(ws::Message::Ping(Bytes::new())).await.is_err() { 71 | return; 72 | } 73 | } 74 | }); 75 | 76 | // run ws dispatcher 77 | let sink = con.sink(); 78 | let mut rx = con.seal().receiver(); 79 | 80 | let _ = sink.send(ws::Message::Ping(Bytes::new())).await; 81 | 82 | while let Some(frame) = rx.next().await { 83 | match frame { 84 | Ok(ws::Frame::Text(text)) => { 85 | println!("Server: {:?}", text); 86 | } 87 | Ok(ws::Frame::Ping(msg)) => { 88 | // send pong response 89 | println!("Got server ping: {:?}", msg); 90 | sink.send(ws::Message::Pong(msg)) 91 | .await 92 | .map_err(io::Error::other)?; 93 | } 94 | Err(_) => break, 95 | _ => (), 96 | } 97 | } 98 | 99 | println!("Disconnected"); 100 | Ok(()) 101 | } 102 | --------------------------------------------------------------------------------