diff --git a/config.toml b/config.toml index 5f25122..3248153 100644 --- a/config.toml +++ b/config.toml @@ -16,4 +16,6 @@ baseURL = "https://actix.rs" [params] actixVersion = "0.7" -actixWebVersion = "0.7" +actixWebVersion = "1.0" +actixWebMinRustVersion = "1.34" +actixMinRustVersion = "1.31" diff --git a/content/docs/_index.md b/content/docs/_index.md index 87d1e87..8b2e33b 100644 --- a/content/docs/_index.md +++ b/content/docs/_index.md @@ -12,13 +12,16 @@ weight: 10 Actix is your door to developing web services with Rust and this documentation is going to guide you. -This documentation currently covers mostly the `actix-web` part which is the -high level web framework build on top of the `actix` actor framework and the -[Tokio](https://tokio.rs/) async IO system. This is the part that is from an -API stability point of view the most stable. +This documentation currently covers mostly the `actix-web` part which is the high level +web framework previously built on top of the `actix` actor framework and the [Tokio][tokio] +async IO system. This is the part that is from an API stability point of view the most stable. -If you haven't used actix yet it's best to start with the [getting started -guide](getting-started/). If you already know your ways around and you need -specific information you might want to read the [actix-web API -docs](https://actix.rs/api/actix-web/stable/actix_web/) (or the lower level [actix API -docs](https://docs.rs/actix)). +If you haven't used `actix-web` yet it's best to start with the [getting started +guide][gettingstarted]. If you already know your ways around and you need +specific information you might want to read the [actix-web API docs][actixwebdocs] +(or the lower level [actix API docs][actixdocs]). + +[gettingstarted]: ./getting-started +[actixwebdocs]: https://docs.rs/actix-web +[actixdocs]: https://docs.rs/actix +[tokio]: (https://tokio.rs/) diff --git a/content/docs/application.md b/content/docs/application.md index 7f96761..95df909 100644 --- a/content/docs/application.md +++ b/content/docs/application.md @@ -7,33 +7,32 @@ weight: 140 # Writing an Application `actix-web` provides various primitives to build web servers and applications with Rust. -It provides routing, middlewares, pre-processing of requests, post-processing of responses, -websocket protocol handling, multipart streams, etc. +It provides routing, middlewares, pre-processing of requests, post-processing of +responses, etc. -All actix web servers are built around the `App` instance. It is used for +All `actix-web` servers are built around the `App` instance. It is used for registering routes for resources and middlewares. It also stores application -state shared across all handlers within same application. +state shared across all handlers within same scope. -Applications act as a namespace for all routes, i.e. all routes for a specific application -have the same url path prefix. The application prefix always contains a leading "/" slash. -If a supplied prefix does not contain leading slash, it is automatically inserted. -The prefix should consist of value path segments. +An application's `scope` acts as a namespace for all routes, i.e. all routes for a +specific application scope have the same url path prefix. The application prefix always +contains a leading "/" slash. If a supplied prefix does not contain leading slash, +it is automatically inserted. The prefix should consist of value path segments. -> For an application with prefix `/app`, +> For an application with scope `/app`, > any request with the paths `/app`, `/app/`, or `/app/test` would match; > however, the path `/application` would not match. -{{< include-example example="application" section="make_app" >}} +{{< include-example example="application" file="app.rs" section="setup" >}} In this example, an application with the `/app` prefix and a `index.html` resource are created. This resource is available through the `/app/index.html` url. -> For more information, check the -> [URL Dispatch](/docs/url-dispatch/index.html#using-an-application-prefix-to-compose-applications) section. +> For more information, check the [URL Dispatch][usingappprefix] section. -Multiple applications can be served with one server: +Multiple application scopes can be served with one server: -{{< include-example example="application" section="run_server" >}} +{{< include-example example="application" file="main.rs" section="multi" >}} All `/app1` requests route to the first application, `/app2` to the second, and all other to the third. **Applications get matched based on registration order**. If an application with a more generic @@ -43,10 +42,10 @@ as the first application, it would match all incoming requests. ## State -Application state is shared with all routes and resources within the same application. -When using an http actor, state can be accessed with the `HttpRequest::state()` as read-only, -but interior mutability with `RefCell` can be used to achieve state mutability. -State is also available for route matching predicates and middlewares. +Application state is shared with all routes and resources within the same scope. State +can be accessed with the `web::Data` extractor as read-only, but interior mutability with +`Cell` can be used to achieve state mutability. State is also available for route +matching guards and middlewares. Let's write a simple application that uses shared state. We are going to store request count in the state: @@ -57,12 +56,12 @@ When the app is initialized it needs to be passed the initial state: {{< include-example example="application" file="state.rs" section="make_app" >}} -> **Note**: http server accepts an application factory rather than an application -> instance. Http server constructs an application instance for each thread, thus application state -> must be constructed multiple times. If you want to share state between different threads, a -> shared object should be used, e.g. `Arc`. There is also an [Example](https://github.com/actix/examples/blob/master/state/src/main.rs) using `Arc` for this. Application state does not need to be `Send` and `Sync`, -> but the application factory must be `Send` + `Sync`. -> +> **Note**: `HttpServer` accepts an application factory rather than an application +> instance. `HttpServer` constructs an application instance for each thread, thus +> application state must be constructed multiple times. If you want to share state between +> different threads, a shared object should be used, e.g. `Arc`. There is also an +> [Example][stateexample] using `Arc` for this. Application state does not need to be +> `Send` and `Sync`, but the application factory must be `Send` + `Sync`. To start the previous app, create it into a closure: @@ -72,41 +71,58 @@ To start the previous app, create it into a closure: Combining multiple applications with different state is possible as well. -[server::new](https://docs.rs/actix-web/*/actix_web/server/fn.new.html) requires the handler to have a single type. +{{< include-example example="application" file="combine.rs" section="combine" >}} -This limitation can easily be overcome with the [App::boxed](https://docs.rs/actix-web/*/actix_web/struct.App.html#method.boxed) method, which converts an App into a boxed trait object. +## Using an Application Scope to Compose Applications -{{< include-example example="application" file="state.rs" section="combine" >}} - -## Using an Application Prefix to Compose Applications - -The `App::prefix()` method allows to set a specific application prefix. -This prefix represents a resource prefix that will be prepended to all resource patterns added -by the resource configuration. This can be used to help mount a set of routes at a different -location than the included callable's author intended while still maintaining the same -resource names. +The `web::scope()` method allows to set a specific application prefix. This scope represents +a resource prefix that will be prepended to all resource patterns added by the resource +configuration. This can be used to help mount a set of routes at a different location +than the included callable's author intended while still maintaining the same resource names. For example: -{{< include-example example="url-dispatch" file="prefix.rs" section="prefix" >}} +{{< include-example example="application" file="scope.rs" section="scope" >}} In the above example, the *show_users* route will have an effective route pattern of -*/users/show* instead of */show* because the application's prefix argument will be prepended +*/users/show* instead of */show* because the application's scope argument will be prepended to the pattern. The route will then only match if the URL path is */users/show*, and when the `HttpRequest.url_for()` function is called with the route name show_users, it will generate a URL with that same path. -## Application predicates and virtual hosting +## Application guards and virtual hosting -You can think of a predicate as a simple function that accepts a *request* object reference -and returns *true* or *false*. Formally, a predicate is any object that implements the -[`Predicate`](../actix_web/pred/trait.Predicate.html) trait. Actix provides -several predicates, you can check -[functions section](../../actix-web/actix_web/pred/index.html#functions) of api docs. +You can think of a guard as a simple function that accepts a *request* object reference +and returns *true* or *false*. Formally, a guard is any object that implements the +[`Guard`][guardtrait] trait. Actix-web provides several guards, you can check +[functions section][guardfuncs] of api docs. -Any of this predicates could be used -with [`App::filter()`](../actix_web/struct.App.html#method.filter) method. One of the -provided predicates is [`Host`](../actix_web/pred/fn.Host.html), it can be used -as application's filter based on request's host information. +One of the provided guards is [`Header`][guardheader], it can be used as application's +filter based on request's header information. {{< include-example example="application" file="vh.rs" section="vh" >}} + +# Configure + +For simplicity and reusability both `App` and `web::scope` provide the `configure` method. +This function is useful for moving parts of configuration to a different module or even +library. For example, some of the resource's configuration could be moved to different +module. + +{{< include-example example="application" file="config.rs" section="config" >}} + +The result of the above example would be: + +``` +/ -> "/" +/app -> "app" +/api/test -> "test" +``` + +Each `ServiceConfig` can have it's own `data`, `routes`, and `services` + +[usingappprefix]: /docs/url-dispatch/index.html#using-an-application-prefix-to-compose-applications +[stateexample]: https://github.com/actix/examples/blob/master/state/src/main.rs +[guardtrait]: https://docs.rs/actix-web/1.0.2/actix_web/guard/trait.Guard.html +[guardfuncs]: https://docs.rs/actix-web/1.0.2/actix_web/guard/index.html#functions +[guardheader]: ((https://docs.rs/actix-web/1.0.2/actix_web/guard/fn.Header.html diff --git a/content/docs/autoreload.md b/content/docs/autoreload.md index 47e2cd6..0ec49dd 100644 --- a/content/docs/autoreload.md +++ b/content/docs/autoreload.md @@ -6,13 +6,11 @@ weight: 1000 # Auto-Reloading Development Server -During development it can be very handy to have cargo automatically recompile -the code on change. This can be accomplished by using -[cargo-watch](https://github.com/passcod/cargo-watch). Because an actix app -will typically bind to a port for listening for incoming HTTP requests it makes -sense to combine this with the [listenfd](https://crates.io/crates/listenfd) -crate and the [systemfd](https://github.com/mitsuhiko/systemfd) utility to -ensure the socket is kept open while the app is compiling and reloading. +During development it can be very handy to have cargo automatically recompile the code +on change. This can be accomplished by using [cargo-watch][cargowatch]. Because an +actix app will typically bind to a port for listening for incoming HTTP requests it makes +sense to combine this with the [listenfd][listenfd] crate and the [systemfd][systemfd] +utility to ensure the socket is kept open while the app is compiling and reloading. `systemfd` will open a socket and pass it to `cargo-watch` which will watch for changes and then invoke the compiler and run your actix app. The actix app @@ -30,8 +28,7 @@ cargo install systemfd cargo-watch ## Code Changes Additionally you need to slightly modify your actix app so that it can pick up -an external socket opened by `systemfd`. Add the listenfd dependency to your -app: +an external socket opened by `systemfd`. Add the listenfd dependency to your app: ```ini [dependencies] @@ -40,33 +37,7 @@ listenfd = "0.3" Then modify your server code to only invoke `bind` as a fallback: -```rust -extern crate actix_web; -extern crate listenfd; - -use listenfd::ListenFd; -use actix_web::{server, App, HttpRequest, Responder}; - -fn index(_req: &HttpRequest) -> impl Responder { - "Hello World!" -} - -fn main() { - let mut listenfd = ListenFd::from_env(); - let mut server = server::new(|| { - App::new() - .resource("/", |r| r.f(index)) - }); - - server = if let Some(l) = listenfd.take_tcp_listener(0).unwrap() { - server.listen(l) - } else { - server.bind("127.0.0.1:3000").unwrap() - }; - - server.run(); -} -``` +{{< include-example example="autoreload" file="main.rs" section="autoreload" >}} ## Running the Server @@ -75,3 +46,7 @@ To now run the development server invoke this command: ``` systemfd --no-pid -s http::3000 -- cargo watch -x run ``` + +[cargowatch]: https://github.com/passcod/cargo-watch +[listenfd]: https://crates.io/crates/listenfd +[systemfd]: https://github.com/mitsuhiko/systemfd diff --git a/content/docs/databases.md b/content/docs/databases.md index 63218fe..a453a65 100644 --- a/content/docs/databases.md +++ b/content/docs/databases.md @@ -16,117 +16,33 @@ Let's create a simple database api that can insert a new user row into a SQLite We must define a sync actor and a connection that this actor will use. The same approach can be used for other databases. -```rust -use actix::prelude::*; - -struct DbExecutor(SqliteConnection); - -impl Actor for DbExecutor { - type Context = SyncContext; -} -``` +{{< include-example example="og_databases" file="main.rs" section="actor" >}} This is the definition of our actor. Now, we must define the *create user* message and response. -```rust -struct CreateUser { - name: String, -} - -impl Message for CreateUser { - type Result = Result; -} -``` +{{< include-example example="og_databases" file="main.rs" section="message" >}} We can send a `CreateUser` message to the `DbExecutor` actor, and as a result, we will receive a `User` model instance. Next, we must define the handler implementation for this message. -```rust -impl Handler for DbExecutor { - type Result = Result; - - fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Self::Result - { - use self::schema::users::dsl::*; - - // Create insertion model - let uuid = format!("{}", uuid::Uuid::new_v4()); - let new_user = models::NewUser { - id: &uuid, - name: &msg.name, - }; - - // normal diesel operations - diesel::insert_into(users) - .values(&new_user) - .execute(&self.0) - .expect("Error inserting person"); - - let mut items = users - .filter(id.eq(&uuid)) - .load::(&self.0) - .expect("Error loading person"); - - Ok(items.pop().unwrap()) - } -} -``` +{{< include-example example="og_databases" file="main.rs" section="handler" >}} That's it! Now, we can use the *DbExecutor* actor from any http handler or middleware. All we need is to start *DbExecutor* actors and store the address in a state where http handler can access it. -```rust -/// This is state where we will store *DbExecutor* address. -struct State { - db: Addr, -} - -fn main() { - let sys = actix::System::new("diesel-example"); - - // Start 3 parallel db executors - let addr = SyncArbiter::start(3, || { - DbExecutor(SqliteConnection::establish("test.db").unwrap()) - }); - - // Start http server - HttpServer::new(move || { - App::with_state(State{db: addr.clone()}) - .resource("/{name}", |r| r.method(Method::GET).a(index))}) - .bind("127.0.0.1:8080").unwrap() - .start().unwrap(); - - println!("Started http server: 127.0.0.1:8080"); - let _ = sys.run(); -} -``` +{{< include-example example="og_databases" file="main.rs" section="main" >}} We will use the address in a request handler. The handle returns a future object; thus, we receive the message response asynchronously. `Route::a()` must be used for async handler registration. +{{< include-example example="og_databases" file="main.rs" section="index" >}} -```rust -/// Async handler -fn index(req: &HttpRequest) -> Box> { - let name = &req.match_info()["name"]; - - // Send message to `DbExecutor` actor - req.state().db.send(CreateUser{name: name.to_owned()}) - .from_err() - .and_then(|res| { - match res { - Ok(user) => Ok(HttpResponse::Ok().json(user)), - Err(_) => Ok(HttpResponse::InternalServerError().into()) - } - }) - .responder() -} -``` - -> A full example is available in the -> [examples directory](https://github.com/actix/examples/tree/master/diesel/). +> A full example is available in the [examples directory][examples]. > More information on sync actors can be found in the -> [actix documentation](https://docs.rs/actix/0.7.0/actix/sync/index.html). +> [actix documentation][actixdocs]. + +[examples]: https://github.com/actix/examples/tree/master/diesel/ +[actixdocs]: https://docs.rs/actix/0.7.0/actix/sync/index.html diff --git a/content/docs/errors.md b/content/docs/errors.md index f8683c4..6dd697a 100644 --- a/content/docs/errors.md +++ b/content/docs/errors.md @@ -6,13 +6,13 @@ weight: 180 # Errors -Actix uses its own [`actix_web::error::Error`][actixerror] type and +Actix-web uses its own [`actix_web::error::Error`][actixerror] type and [`actix_web::error::ResponseError`][responseerror] trait for error handling from web handlers. If a handler returns an `Error` (referring to the [general Rust trait `std::error::Error`][stderror]) in a `Result` that also implements the -`ResponseError` trait, actix will render that error as an HTTP response. +`ResponseError` trait, actix-web will render that error as an HTTP response. `ResponseError` has a single function called `error_response()` that returns `HttpResponse`: @@ -30,7 +30,7 @@ A `Responder` coerces compatible `Result`s into HTTP responses: impl> Responder for Result ``` -`Error` in the code above is actix's error definition, and any errors that +`Error` in the code above is actix-web's error definition, and any errors that implement `ResponseError` can be converted to one automatically. Actix-web provides `ResponseError` implementations for some common non-actix @@ -40,34 +40,19 @@ converted into an `HttpInternalServerError`: ```rust use std::io; -fn index(req: &HttpRequest) -> io::Result { +fn index(_req: HttpRequest) -> io::Result { Ok(fs::NamedFile::open("static/index.html")?) } ``` -See [the actix-web API documentation][responseerrorimpls] for a full list of -foreign implementations for `ResponseError`. +See [the actix-web API documentation][responseerrorimpls] for a full list of foreign +implementations for `ResponseError`. ## An example of a custom error response Here's an example implementation for `ResponseError`: -```rust -use actix_web::*; - -#[derive(Fail, Debug)] -#[fail(display="my error")] -struct MyError { - name: &'static str -} - -// Use default implementation for `error_response()` method -impl error::ResponseError for MyError {} - -fn index(req: &HttpRequest) -> Result<&'static str, MyError> { - Err(MyError{name: "test"}) -} -``` +{{< include-example example="errors" file="main.rs" section="response-error" >}} `ResponseError` has a default implementation for `error_response()` that will render a *500* (internal server error), and that's what will happen when the @@ -75,63 +60,19 @@ render a *500* (internal server error), and that's what will happen when the Override `error_response()` to produce more useful results: -```rust -#[macro_use] extern crate failure; -use actix_web::{App, HttpRequest, HttpResponse, http, error}; - -#[derive(Fail, Debug)] -enum MyError { - #[fail(display="internal error")] - InternalError, - #[fail(display="bad request")] - BadClientData, - #[fail(display="timeout")] - Timeout, -} - -impl error::ResponseError for MyError { - fn error_response(&self) -> HttpResponse { - match *self { - MyError::InternalError => HttpResponse::new( - http::StatusCode::INTERNAL_SERVER_ERROR), - MyError::BadClientData => HttpResponse::new( - http::StatusCode::BAD_REQUEST), - MyError::Timeout => HttpResponse::new( - http::StatusCode::GATEWAY_TIMEOUT), - } - } -} - -fn index(req: &HttpRequest) -> Result<&'static str, MyError> { - Err(MyError::BadClientData) -} -``` +{{< include-example example="errors" file="override_error.rs" section="override" >}} # Error helpers -Actix provides a set of error helper functions that are useful for generating +Actix-web provides a set of error helper functions that are useful for generating specific HTTP error codes from other errors. Here we convert `MyError`, which doesn't implement the `ResponseError` trait, to a *400* (bad request) using `map_err`: -```rust -# extern crate actix_web; -use actix_web::*; +{{< include-example example="errors" file="helpers.rs" section="helpers" >}} -#[derive(Debug)] -struct MyError { - name: &'static str -} - -fn index(req: &HttpRequest) -> Result<&'static str> { - let result: Result<&'static str, MyError> = Err(MyError{name: "test"}); - - Ok(result.map_err(|e| error::ErrorBadRequest(e.name))?) -} -``` - -See the [API documentation for actix-web's `error` module][errorhelpers] for a -full list of available error helpers. +See the [API documentation for actix-web's `error` module][actixerror] +for a full list of available error helpers. # Compatibility with failure @@ -165,27 +106,7 @@ An example of the former is that I might use failure to specify a `UserError` enum which encapsulates a `ValidationError` to return whenever a user sends bad input: -```rust -#[macro_use] extern crate failure; -use actix_web::{HttpResponse, http, error}; - -#[derive(Fail, Debug)] -enum UserError { - #[fail(display="Validation error on field: {}", field)] - ValidationError { - field: String, - } -} - -impl error::ResponseError for UserError { - fn error_response(&self) -> HttpResponse { - match *self { - UserError::ValidationError { .. } => HttpResponse::new( - http::StatusCode::BAD_REQUEST), - } - } -} -``` +{{< include-example example="errors" file="recommend_one.rs" section="recommend-one" >}} This will behave exactly as intended because the error message defined with `display` is written with the explicit intent to be read by a user. @@ -201,38 +122,21 @@ consumption. Here's an example that maps an internal error to a user-facing `InternalError` with a custom message: -```rust -#[macro_use] extern crate failure; -use actix_web::{App, HttpRequest, HttpResponse, http, error, fs}; - -#[derive(Fail, Debug)] -enum UserError { - #[fail(display="An internal error occurred. Please try again later.")] - InternalError, -} - -impl error::ResponseError for UserError { - fn error_response(&self) -> HttpResponse { - match *self { - UserError::InternalError => HttpResponse::new( - http::StatusCode::INTERNAL_SERVER_ERROR), - } - } -} - -fn index(_: &HttpRequest) -> Result<&'static str, UserError> { - fs::NamedFile::open("static/index.html").map_err(|_e| UserError::InternalError)?; - Ok("success!") -} -``` +{{< include-example example="errors" file="recommend_two.rs" section="recommend-two" >}} By dividing errors into those which are user facing and those which are not, we can ensure that we don't accidentally expose users to errors thrown by application internals which they weren't meant to see. -[actixerror]: ../../actix-web/actix_web/error/struct.Error.html -[errorhelpers]: ../../actix-web/actix_web/error/index.html#functions +# Error Logging + +This is a basic example using `middleware::Logger`: + +{{< include-example example="errors" file="logging.rs" section="logging" >}} + +[actixerror]: https://docs.rs/actix-web/1.0.2/actix_web/error/struct.Error.html +[errorhelpers]: https://docs.rs/actix-web/1.0.2/actix_web/trait.ResponseError.html [failure]: https://github.com/rust-lang-nursery/failure -[responseerror]: ../../actix-web/actix_web/error/trait.ResponseError.html -[responseerrorimpls]: ../../actix-web/actix_web/error/trait.ResponseError.html#foreign-impls +[responseerror]: https://docs.rs/actix-web/1.0.2/actix_web/error/trait.ResponseError.html +[responseerrorimpls]: https://docs.rs/actix-web/1.0.2/actix_web/error/trait.ResponseError.html#foreign-impls [stderror]: https://doc.rust-lang.org/std/error/trait.Error.html diff --git a/content/docs/extractors.md b/content/docs/extractors.md index 684d84d..e77adfd 100644 --- a/content/docs/extractors.md +++ b/content/docs/extractors.md @@ -6,280 +6,134 @@ weight: 170 # Type-safe information extraction -Actix provides facility for type-safe request information extraction. By default, -actix provides several extractor implementations. +Actix-web provides a facility for type-safe request information access called *extractors* +(ie, `impl FromRequest`). By default, actix-web provides several extractor implementations. -# Accessing Extractors +## Extractors Within Handler Functions -How you access an Extractor depends on whether you are using a handler function -or a custom Handler type. +An extractor can be accessed in a few different ways. -## Within Handler Functions +Option 1 - passed as a parameter to a handler function: -An Extractor can be passed to a handler function as a function parameter -*or* accessed within the function by calling the ExtractorType::<...>::extract(req) -function. -```rust +{{< include-example example="extractors" file="main.rs" section="option-one" >}} -// Option 1: passed as a parameter to a handler function -fn index((params, info): (Path<(String, String,)>, Json)) -> HttpResponse { - ... -} +Option 2 - accessed by calling `extract()` on the Extractor - -// Option 2: accessed by calling extract() on the Extractor - -use actix_web::FromRequest; - -fn index(req: &HttpRequest) -> HttpResponse { - let params = Path::<(String, String)>::extract(req); - let info = Json::::extract(req); - - ... -} -``` - -## Within Custom Handler Types - -Like a handler function, a custom Handler type can *access* an Extractor by -calling the ExtractorType::<...>::extract(&req) function. An Extractor -*cannot* be passed as a parameter to a custom Handler type because a custom -Handler type must follow the ``handle`` function signature specified by the -Handler trait it implements. - -```rust - -struct MyHandler(String); - -impl Handler for MyHandler { - type Result = HttpResponse; - - /// Handle request - fn handle(&self, req: &HttpRequest) -> Self::Result { - let params = Path::<(String, String)>::extract(req); - let info = Json::::extract(req); - - ... - - HttpResponse::Ok().into() - } -} - -``` +{{< include-example example="extractors" file="main.rs" section="option-two" >}} # Path -[*Path*](../../actix-web/actix_web/struct.Path.html) provides information that can -be extracted from the Request's path. You can deserialize any variable -segment from the path. +[*Path*][pathstruct] provides information that can be extracted from the Request's +path. You can deserialize any variable segment from the path. For instance, for resource that registered for the `/users/{userid}/{friend}` path -two segments could be deserialized, `userid` and `friend`. These segments -could be extracted into a `tuple`, i.e. `Path<(u32, String)>` or any structure -that implements the `Deserialize` trait from the *serde* crate. +two segments could be deserialized, `userid` and `friend`. These segments could be +extracted into a `tuple`, i.e. `Path<(u32, String)>` or any structure that implements +the `Deserialize` trait from the *serde* crate. -```rust -use actix_web::{App, Path, Result, http}; +{{< include-example example="extractors" file="path_one.rs" section="path-one" >}} -/// extract path info from "/users/{userid}/{friend}" url -/// {userid} - - deserializes to a u32 -/// {friend} - deserializes to a String -fn index(info: Path<(u32, String)>) -> Result { - Ok(format!("Welcome {}! {}", info.1, info.0)) -} - -fn main() { - let app = App::new().resource( - "/users/{userid}/{friend}", // <- define path parameters - |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor -} -``` - -Remember! A handler function that uses extractors has to be registered using the -[*Route::with()*](../../actix-web/actix_web/dev/struct.Route.html#method.with) method. - -It is also possible to extract path information to a specific type that -implements the `Deserialize` trait from *serde*. Here is an equivalent example that uses *serde* +It is also possible to extract path information to a specific type that implements the +`Deserialize` trait from *serde*. Here is an equivalent example that uses *serde* instead of a *tuple* type. -```rust -#[macro_use] extern crate serde_derive; -use actix_web::{App, Path, Result, http}; +{{< include-example example="extractors" file="path_two.rs" section="path-two" >}} -#[derive(Deserialize)] -struct Info { - userid: u32, - friend: String, -} +It is also possible to `get` or `query` the request for path parameters by name: -/// extract path info using serde -fn index(info: Path) -> Result { - Ok(format!("Welcome {}!", info.friend)) -} - -fn main() { - let app = App::new().resource( - "/users/{userid}/{friend}", // <- define path parameters - |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor -} -``` +{{< include-example example="extractors" file="path_three.rs" section="path-three" >}} # Query -Same can be done with the request's query. -The [*Query*](../../actix-web/actix_web/struct.Query.html) -type provides extraction functionality. Underneath it uses *serde_urlencoded* crate. +The [*Query*][querystruct] type provides extraction functionality for the request's +query parameters. Underneath it uses *serde_urlencoded* crate. -```rust -#[macro_use] extern crate serde_derive; -use actix_web::{App, Query, http}; - -#[derive(Deserialize)] -struct Info { - username: String, -} - -// this handler get called only if the request's query contains `username` field -fn index(info: Query) -> String { - format!("Welcome {}!", info.username) -} - -fn main() { - let app = App::new().resource( - "/index.html", - |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor -} -``` +{{< include-example example="extractors" file="query.rs" section="query" >}} # Json -[*Json*](../../actix-web/actix_web/struct.Json.html) allows to deserialize -a request body into a struct. To extract typed information from a request's body, -the type `T` must implement the `Deserialize` trait from *serde*. +[*Json*][jsonstruct] allows to deserialize a request body into a struct. To extract +typed information from a request's body, the type `T` must implement the `Deserialize` +trait from *serde*. -```rust -#[macro_use] extern crate serde_derive; -use actix_web::{App, Json, Result, http}; - -#[derive(Deserialize)] -struct Info { - username: String, -} - -/// deserialize `Info` from request's body -fn index(info: Json) -> Result { - Ok(format!("Welcome {}!", info.username)) -} - -fn main() { - let app = App::new().resource( - "/index.html", - |r| r.method(http::Method::POST).with(index)); // <- use `with` extractor -} -``` +{{< include-example example="extractors" file="json_one.rs" section="json-one" >}} Some extractors provide a way to configure the extraction process. Json extractor -[*JsonConfig*](../../actix-web/actix_web/dev/struct.JsonConfig.html) type for configuration. -When you register a handler using `Route::with()`, it returns a configuration instance. In case of -a *Json* extractor it returns a *JsonConfig*. You can configure the maximum size of the json -payload as well as a custom error handler function. +[*JsonConfig*][jsonconfig] type for configuration. To configure an extractor, pass it's +configuration object to the resource's `.data()` method. In case of a *Json* extractor +it returns a *JsonConfig*. You can configure the maximum size of the json payload as +well as a custom error handler function. The following example limits the size of the payload to 4kb and uses a custom error handler. -```rust -#[macro_use] extern crate serde_derive; -use actix_web::{App, Json, HttpResponse, Result, http, error}; - -#[derive(Deserialize)] -struct Info { - username: String, -} - -/// deserialize `Info` from request's body, max payload size is 4kb -fn index(info: Json) -> Result { - Ok(format!("Welcome {}!", info.username)) -} - -fn main() { - let app = App::new().resource( - "/index.html", |r| { - r.method(http::Method::POST) - .with_config(index, |cfg| { - cfg.limit(4096) // <- change json extractor configuration - cfg.error_handler(|err, req| { // <- create custom error response - error::InternalError::from_response( - err, HttpResponse::Conflict().finish()).into() - }) - }); - }); -} -``` +{{< include-example example="extractors" file="json_two.rs" section="json-two" >}} # Form -At the moment only url-encoded forms are supported. The url-encoded body -could be extracted to a specific type. This type must implement -the `Deserialize` trait from the *serde* crate. +At the moment only url-encoded forms are supported. The url-encoded body could be +extracted to a specific type. This type must implement the `Deserialize` trait from +the *serde* crate. -[*FormConfig*](../../actix-web/actix_web/dev/struct.FormConfig.html) allows -configuring the extraction process. +[*FormConfig*][formconfig] allows configuring the extraction process. -```rust -#[macro_use] extern crate serde_derive; -use actix_web::{App, Form, Result}; - -#[derive(Deserialize)] -struct FormData { - username: String, -} - -/// extract form data using serde -/// this handler gets called only if the content type is *x-www-form-urlencoded* -/// and the content of the request could be deserialized to a `FormData` struct -fn index(form: Form) -> Result { - Ok(format!("Welcome {}!", form.username)) -} -# fn main() {} -``` +{{< include-example example="extractors" file="form.rs" section="form" >}} # Multiple extractors -Actix provides extractor implementations for tuples (up to 10 elements) -whose elements implement `FromRequest`. +Actix-web provides extractor implementations for tuples (up to 10 elements) whose +elements implement `FromRequest`. For example we can use a path extractor and a query extractor at the same time. -```rust -#[macro_use] extern crate serde_derive; -use actix_web::{App, Query, Path, http}; - -#[derive(Deserialize)] -struct Info { - username: String, -} - -fn index((path, query): (Path<(u32, String)>, Query)) -> String { - format!("Welcome {}!", query.username) -} - -fn main() { - let app = App::new().resource( - "/users/{userid}/{friend}", // <- define path parameters - |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor -} -``` +{{< include-example example="extractors" file="multiple.rs" section="multi" >}} # Other -Actix also provides several other extractors: +Actix-web also provides several other extractors: -* [*State*](../../actix-web/actix_web/struct.State.html) - If you need - access to an application state. This is similar to a `HttpRequest::state()`. -* *HttpRequest* - *HttpRequest* itself is an extractor which returns self, - in case you need access to the request. -* *String* - You can convert a request's payload to a *String*. - [*Example*](../../actix-web/actix_web/trait.FromRequest.html#example-1) +* [*Data*][datastruct] - If you need access to an application state. +* *HttpRequest* - *HttpRequest* itself is an extractor which returns self, in case you + need access to the request. +* *String* - You can convert a request's payload to a *String*. [*Example*][stringexample] is available in doc strings. * *bytes::Bytes* - You can convert a request's payload into *Bytes*. - [*Example*](../../actix-web/actix_web/trait.FromRequest.html#example) + [*Example*][bytesexample] is available in doc strings. +* *Payload* - You can access a request's payload. + [*Example*][payloadexample] + +# Async Data Access + +Application state is accessible from the handler with the `web::Data` extractor; +however, state is accessible as a read-only reference. If you need mutable access to state, +it must be implemented. + +> **Beware**, actix creates multiple copies of the application state and the handlers, +> unique for each thread. If you run your application in several threads, actix will +> create the same amount as number of threads of application state objects and handler +> objects. + +Here is an example of a handler that stores the number of processed requests: + +{{< include-example example="request-handlers" file="main.rs" section="data" >}} + +Although this handler will work, `self.0` will be different depending on the number of threads and +number of requests processed per thread. A proper implementation would use `Arc` and `AtomicUsize`. + +{{< include-example example="request-handlers" file="handlers_arc.rs" section="arc" >}} + +> Be careful with synchronization primitives like `Mutex` or `RwLock`. The `actix-web` framework +> handles requests asynchronously. By blocking thread execution, all concurrent +> request handling processes would block. If you need to share or update some state +> from multiple threads, consider using the [actix][actix] actor system. + +[pathstruct]: (https://docs.rs/actix-web/1.0.2/actix_web/dev/struct.Path.html +[querystruct]: https://docs.rs/actix-web/1.0.2/actix_web/web/struct.Query.html +[jsonstruct]: https://docs.rs/actix-web/1.0.2/actix_web/web/struct.Json.html +[jsonconfig]: https://docs.rs/actix-web/1.0.2/actix_web/web/struct.JsonConfig.html +[formconfig]: https://docs.rs/actix-web/1.0.2/actix_web/web/struct.FormConfig.html +[datastruct]: https://docs.rs/actix-web/1.0.2/actix_web/web/struct.Data.html +[stringexample]: https://docs.rs/actix-web/1.0.2/actix_web/trait.FromRequest.html#example-2 +[bytesexample]: https://docs.rs/actix-web/1.0.2/actix_web/trait.FromRequest.html#example-4 +[payloadexample]: https://docs.rs/actix-web/1.0.2/actix_web/web/struct.Payload.html +[actix]: https://actix.github.io/actix/actix/ diff --git a/content/docs/getting-started.md b/content/docs/getting-started.md index b0833e6..3472e27 100644 --- a/content/docs/getting-started.md +++ b/content/docs/getting-started.md @@ -6,7 +6,7 @@ weight: 130 # Getting Started -Let’s write our first actix web application! +Let’s write our first `actix-web` application! ## Hello, world! @@ -27,19 +27,16 @@ actix-web = "{{< actix-version "actix-web" >}}" In order to implement a web server, we first need to create a request handler. -A request handler is a function that accepts an `HttpRequest` instance as its only parameter -and returns a type that can be converted into `HttpResponse`: - -Filename: `src/main.rs` +A request handler is a function that accepts zero or more parameters that can be +extracted from a request (ie, `impl FromRequest`) and returns a type that can be +converted into an `HttpResponse` (ie, `impl Responder`): {{< include-example example="getting-started" section="setup" >}} -Next, create an `Application` instance and register the request handler with -the application's `resource` on a particular *HTTP method* and *path* and -after that, the application instance can be used with `HttpServer` to listen -for incoming connections. The server accepts a function that should return an -`HttpHandler` instance. For simplicity `server::new` could be used, this -function is shortcut for `HttpServer::new`: +Next, create an `App` instance and register the request handler with the application's +`route` on a *path* and with a particular *HTTP method*. After that, the application +instance can be used with `HttpServer` to listen for incoming connections. The server +accepts a function that should return an application factory. {{< include-example example="getting-started" section="main" >}} @@ -48,4 +45,6 @@ Head over to ``http://localhost:8088/`` to see the results. If you want, you can have an automatic reloading server during development that recompiles on demand. To see how this can be accomplished have a look -at the [autoreload pattern](../autoreload/). +at the [autoreload pattern][autoload]. + +[autoload]: ../autoreload/ diff --git a/content/docs/handlers.md b/content/docs/handlers.md index f0bdc36..d67cb33 100644 --- a/content/docs/handlers.md +++ b/content/docs/handlers.md @@ -6,30 +6,29 @@ weight: 160 # Request Handlers -A request handler can be any object that implements -[*Handler*](../../actix-web/actix_web/dev/trait.Handler.html) trait. +A request handler is a function that accepts zero or more parameters that can be extracted +from a request (ie, [*impl FromRequest*][implfromrequest]) and returns a type that can +be converted into an HttpResponse (ie, [*impl Responder*][implresponder]). -Request handling happens in two stages. First the handler object is called, -returning any object that implements the -[*Responder*](../../actix-web/actix_web/trait.Responder.html#foreign-impls) trait. -Then, `respond_to()` is called on the returned object, converting itself to a `AsyncResult` or `Error`. +Request handling happens in two stages. First the handler object is called, returning any +object that implements the [*Responder*][respondertrait] trait. Then, `respond_to()` is +called on the returned object, converting itself to a `HttpResponse` or `Error`. -By default actix provides `Responder` implementations for some standard types, +By default actix-web provides `Responder` implementations for some standard types, such as `&'static str`, `String`, etc. -> For a complete list of implementations, check -> [*Responder documentation*](../../actix-web/actix_web/trait.Responder.html#foreign-impls). +> For a complete list of implementations, check [*Responder documentation*][responderimpls]. Examples of valid handlers: ```rust -fn index(req: &HttpRequest) -> &'static str { +fn index(_req: HttpRequest) -> &'static str { "Hello world!" } ``` ```rust -fn index(req: &HttpRequest) -> String { +fn index(_req: HttpRequest) -> String { "Hello world!".to_owned() } ``` @@ -38,255 +37,59 @@ You can also change the signature to return `impl Responder` which works well if complex types are involved. ```rust -fn index(req: &HttpRequest) -> impl Responder { +fn index(_req: HttpRequest) -> impl Responder { Bytes::from_static("Hello world!") } ``` -```rust,ignore -fn index(req: &HttpRequest) -> Box> { +```rust +fn index(req: HttpRequest) -> Box> { ... } ``` -*Handler* trait is generic over *S*, which defines the application state's type. -Application state is accessible from the handler with the `HttpRequest::state()` method; -however, state is accessible as a read-only reference. If you need mutable access to state, -it must be implemented. - -> **Note**: Alternatively, the handler can use interior mutably to access its own -> state. **Beware**, actix creates multiple copies -> of the application state and the handlers, unique for each thread. If you run your -> application in several threads, actix will create the same amount as number of threads -> of application state objects and handler objects. - -Here is an example of a handler that stores the number of processed requests: - -```rust -use actix_web::{App, HttpRequest, HttpResponse, dev::Handler}; - -struct MyHandler(Cell); - -impl Handler for MyHandler { - type Result = HttpResponse; - - /// Handle request - fn handle(&self, req: &HttpRequest) -> Self::Result { - let i = self.0.get(); - self.0.set(i + 1); - HttpResponse::Ok().into() - } -} - -fn main(){ - server::new(|| App::new() - .resource("/", |r| r.h(MyHandler(Cell::new(0))))) //use r.h() to bind handler, not the r.f() - .bind("127.0.0.1:8080") - .unwrap() - .run(); -} -``` - -Although this handler will work, `self.0` will be different depending on the number of threads and -number of requests processed per thread. A proper implementation would use `Arc` and `AtomicUsize`. - -```rust -use actix_web::{server, App, HttpRequest, HttpResponse, dev::Handler}; -use std::sync::Arc; -use std::sync::atomic::{AtomicUsize, Ordering}; - -struct MyHandler(Arc); - -impl Handler for MyHandler { - type Result = HttpResponse; - - /// Handle request - fn handle(&self, req: &HttpRequest) -> Self::Result { - self.0.fetch_add(1, Ordering::Relaxed); - HttpResponse::Ok().into() - } -} - -fn main() { - let sys = actix::System::new("example"); - - let inc = Arc::new(AtomicUsize::new(0)); - - server::new( - move || { - let cloned = inc.clone(); - App::new() - .resource("/", move |r| r.h(MyHandler(cloned))) - }) - .bind("127.0.0.1:8088").unwrap() - .start(); - - println!("Started http server: 127.0.0.1:8088"); - let _ = sys.run(); -} -``` - -> Be careful with synchronization primitives like `Mutex` or `RwLock`. The `actix-web` framework -> handles requests asynchronously. By blocking thread execution, all concurrent -> request handling processes would block. If you need to share or update some state -> from multiple threads, consider using the [actix](https://actix.github.io/actix/actix/) actor system. - ## Response with custom type To return a custom type directly from a handler function, the type needs to implement the `Responder` trait. Let's create a response for a custom type that serializes to an `application/json` response: -```rust -# extern crate actix; -# extern crate actix_web; -extern crate serde; -extern crate serde_json; -#[macro_use] extern crate serde_derive; -use actix_web::{server, App, HttpRequest, HttpResponse, Error, Responder, http}; - -#[derive(Serialize)] -struct MyObj { - name: &'static str, -} - -/// Responder -impl Responder for MyObj { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, req: &HttpRequest) -> Result { - let body = serde_json::to_string(&self)?; - - // Create response and set content type - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) - } -} - -fn index(req: &HttpRequest) -> impl Responder { - MyObj { name: "user" } -} - -fn main() { - let sys = actix::System::new("example"); - - server::new( - || App::new() - .resource("/", |r| r.method(http::Method::GET).f(index))) - .bind("127.0.0.1:8088").unwrap() - .start(); - - println!("Started http server: 127.0.0.1:8088"); - let _ = sys.run(); -} -``` +{{< include-example example="responder-trait" file="main.rs" section="responder-trait" >}} ## Async handlers There are two different types of async handlers. Response objects can be generated asynchronously -or more precisely, any type that implements the [*Responder*](../../actix-web/actix_web/trait.Responder.html) trait. +or more precisely, any type that implements the [*Responder*][respondertrait] trait. In this case, the handler must return a `Future` object that resolves to the *Responder* type, i.e: -```rust -use actix_web::*; -use bytes::Bytes; -use futures::stream::once; -use futures::future::{Future, result}; +{{< include-example example="async-handlers" file="main.rs" section="async-responder" >}} -fn index(req: &HttpRequest) -> Box> { +Or the response body can be generated asynchronously. In this case, body must implement +the stream trait `Stream`, i.e: - result(Ok(HttpResponse::Ok() - .content_type("text/html") - .body(format!("Hello!")))) - .responder() -} - -fn index2(req: &HttpRequest) -> Box> { - result(Ok("Welcome!")) - .responder() -} - -fn main() { - App::new() - .resource("/async", |r| r.route().a(index)) - .resource("/", |r| r.route().a(index2)) - .finish(); -} -``` - -Or the response body can be generated asynchronously. In this case, body -must implement the stream trait `Stream`, i.e: - -```rust -use actix_web::*; -use bytes::Bytes; -use futures::stream::once; - -fn index(req: &HttpRequest) -> HttpResponse { - let body = once(Ok(Bytes::from_static(b"test"))); - - HttpResponse::Ok() - .content_type("application/json") - .body(Body::Streaming(Box::new(body))) -} - -fn main() { - App::new() - .resource("/async", |r| r.f(index)) - .finish(); -} -``` +{{< include-example example="async-handlers" file="stream.rs" section="stream" >}} Both methods can be combined. (i.e Async response with streaming body) -It is possible to return a `Result` where the `Result::Item` type can be `Future`. -In this example, the `index` handler can return an error immediately or return a -future that resolves to a `HttpResponse`. +It is possible to return a `Result` where the `Result::Item` type can be `Future`. In +this example, the `index` handler can return an error immediately or return a future +that resolves to a `HttpResponse`. -```rust -use actix_web::*; -use bytes::Bytes; -use futures::stream::once; -use futures::future::{Future, result}; - -fn index(req: &HttpRequest) -> Result>, Error> { - if is_error() { - Err(error::ErrorBadRequest("bad request")) - } else { - Ok(Box::new( - result(Ok(HttpResponse::Ok() - .content_type("text/html") - .body(format!("Hello!")))))) - } -} -``` +{{< include-example example="async-handlers" file="async_stream.rs" section="async-stream" >}} ## Different return types (Either) -Sometimes, you need to return different types of responses. For example, -you can error check and return errors, return async responses, or any result that requires two different types. +Sometimes, you need to return different types of responses. For example, you can error +check and return errors, return async responses, or any result that requires two different types. -For this case, the [*Either*](../../actix-web/actix_web/enum.Either.html) type can be used. -`Either` allows combining two different responder types into a single type. +For this case, the [*Either*][either] type can be used. `Either` allows combining two +different responder types into a single type. -```rust -use futures::future::{Future, result}; -use actix_web::{Either, Error, HttpResponse}; +{{< include-example example="either" file="main.rs" section="either" >}} -type RegisterResult = Either>>; - -fn index(req: &HttpRequest) -> RegisterResult { - if is_a_variant() { // <- choose variant A - Either::A( - HttpResponse::BadRequest().body("Bad data")) - } else { - Either::B( // <- variant B - result(Ok(HttpResponse::Ok() - .content_type("text/html") - .body(format!("Hello!")))).responder()) - } -} -``` +[implfromrequest]: https://docs.rs/actix-web/1.0.2/actix_web/trait.FromRequest.html +[implresponder]: https://docs.rs/actix-web/1.0.2/actix_web/trait.Responder.html +[respondertrait]: https://docs.rs/actix-web/1.0.2/actix_web/trait.Responder.html +[responderimpls]: https://docs.rs/actix-web/1.0.2/actix_web/trait.Responder.html#foreign-impls +[either]: https://docs.rs/actix-web/1.0.2/actix_web/enum.Either.html diff --git a/content/docs/http2.md b/content/docs/http2.md index 85ab4a5..d64e395 100644 --- a/content/docs/http2.md +++ b/content/docs/http2.md @@ -8,43 +8,28 @@ weight: 250 # Negotiation -*HTTP/2.0* protocol over tls without prior knowledge requires -[tls alpn](https://tools.ietf.org/html/rfc7301). +*HTTP/2.0* protocol over tls without prior knowledge requires [tls alpn][tlsalpn]. > Currently, only `rust-openssl` has support. `alpn` negotiation requires enabling the feature. When enabled, `HttpServer` provides the -[serve_tls](../../actix-web/actix_web/server/struct.HttpServer.html#method.serve_tls) method. +[bind_ssl][bindssl] method. ```toml [dependencies] -actix-web = { version = "{{< actix-version "actix-web" >}}", features = ["alpn"] } +actix-web = { version = "{{< actix-version "actix-web" >}}", features = ["ssl"] } openssl = { version = "0.10", features = ["v110"] } ``` +{{< include-example example="http2" file="main.rs" section="main" >}} -```rust -use std::fs::File; -use actix_web::*; -use openssl::ssl::{SslMethod, SslAcceptor, SslFiletype}; +Upgrades to *HTTP/2.0* schema described in [rfc section 3.2][rfcsection32] is not +supported. Starting *HTTP/2* with prior knowledge is supported for both clear text +connection and tls connection. [rfc section 3.4][rfcsection34]. -fn main() { - // load ssl keys - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder.set_private_key_file("key.pem", SslFiletype::PEM).unwrap(); - builder.set_certificate_chain_file("cert.pem").unwrap(); +> Check out [examples/tls][examples] for a concrete example. - HttpServer::new( - || App::new() - .resource("/index.html", |r| r.f(index))) - .bind("127.0.0.1:8080").unwrap(); - .serve_ssl(builder).unwrap(); -} -``` - -Upgrades to *HTTP/2.0* schema described in -[rfc section 3.2](https://http2.github.io/http2-spec/#rfc.section.3.2) is not supported. -Starting *HTTP/2* with prior knowledge is supported for both clear text connection -and tls connection. [rfc section 3.4](https://http2.github.io/http2-spec/#rfc.section.3.4) - -> Check out [examples/tls](https://github.com/actix/examples/tree/master/tls) -> for a concrete example. +[rfcsection32]: https://http2.github.io/http2-spec/#rfc.section.3.2 +[rfcsection34]: https://http2.github.io/http2-spec/#rfc.section.3.4 +[bindssl]: https://docs.rs/actix-web/1.0.2/actix_web/struct.HttpServer.html#method.bind_ssl +[tlsalpn]: https://tools.ietf.org/html/rfc7301 +[examples]: https://github.com/actix/examples/tree/master/tls diff --git a/content/docs/installation.md b/content/docs/installation.md index 6664ffc..8e41323 100644 --- a/content/docs/installation.md +++ b/content/docs/installation.md @@ -6,21 +6,19 @@ weight: 110 # Installing Rust -Since `actix-web` is a Rust framework you will need Rust to get started with it. -If you don't have it yet we recommend you use `rustup` to manage your Rust -installation. The [official rust -guide](https://doc.rust-lang.org/book/second-edition/ch01-01-installation.html) -has a wonderful section on getting started. +Since `actix-web` is a Rust framework you will need Rust to get started with it. If you +don't have it yet we recommend you use `rustup` to manage your Rust installation. The +[official rust guide][rustguide] has a wonderful section on getting started. -We currently require at least Rust 1.24 so make sure you run `rustup update` -to have the latest and greatest Rust version available. In particular this -guide will assume that you actually run Rust 1.26 or later. +We currently require at least Rust {{< rust-version "actix-web" >}} so make sure you run +`rustup update` to have the latest and greatest Rust version available. In particular +this guide will assume that you actually run Rust {{< rust-version "actix-web" >}} or later. # Installing `actix-web` -Thank's to Rust's `cargo` package manager you won't need to explicitly install +Thanks to Rust's `cargo` package manager you won't need to explicitly install `actix-web`. Just depend on it and you're ready to go. For the unlikely -case that you want to use the development version of actix-web you can +case that you want to use the development version of `actix-web` you can depend on the git repository directly. Release version: @@ -39,10 +37,9 @@ actix-web = { git = "https://github.com/actix/actix-web" } # Diving In -There are two paths you can take here. You can follow the guide along or if -you are very impatient you might want to have a look at our -[extensive example repository](https://github.com/actix/examples) and run the -included examples. Here for instance is how you run the included `basics` +There are two paths you can take here. You can follow the guide along or if you are very +impatient you might want to have a look at our [extensive example repository][exmaples] +and run the included examples. Here for instance is how you run the included `basics` example: ``` @@ -50,3 +47,6 @@ git clone https://github.com/actix/examples cd examples/basics cargo run ``` + +[rustguide]: https://doc.rust-lang.org/book/ch01-01-installation.html +[examples]: https://github.com/actix/examples diff --git a/content/docs/middleware.md b/content/docs/middleware.md index c024289..0a9537e 100644 --- a/content/docs/middleware.md +++ b/content/docs/middleware.md @@ -6,9 +6,9 @@ weight: 220 # Middleware -Actix's middleware system allows us to add additional behavior to request/response processing. -Middleware can hook into an incoming request process, enabling us to modify requests -as well as halt request processing to return a response early. +Actix-web's middleware system allows us to add additional behavior to request/response +processing. Middleware can hook into an incoming request process, enabling us to modify +requests as well as halt request processing to return a response early. Middleware can also hook into response processing. @@ -19,87 +19,38 @@ Typically, middleware is involved in the following actions: * Modify application state * Access external services (redis, logging, sessions) -Middleware is registered for each application and executed in same order as -registration. In general, a *middleware* is a type that implements the -[*Middleware trait*](../../actix-web/actix_web/middleware/trait.Middleware.html). -Each method in this trait has a default implementation. Each method can return -a result immediately or a *future* object. +Middleware is registered for each `App`, `scope`, or `Resource` and executed in opposite +order as registration. In general, a *middleware* is a type that implements the +[*Service trait*][servicetrait] and [*Transform trait*][transformtrait]. Each method in +the traits has a default implementation. Each method can return a result immediately +or a *future* object. -The following demonstrates using middleware to add request and response headers: +The following demonstrates creating a simple middleware: -```rust -use http::{header, HttpTryFrom}; -use actix_web::{App, HttpRequest, HttpResponse, Result}; -use actix_web::middleware::{Middleware, Started, Response}; +{{< include-example example="middleware" file="main.rs" section="simple" >}} -struct Headers; // <- Our middleware - -/// Middleware implementation, middlewares are generic over application state, -/// so you can access state with `HttpRequest::state()` method. -impl Middleware for Headers { - - /// Method is called when request is ready. It may return - /// future, which should resolve before next middleware get called. - fn start(&self, req: &HttpRequest) -> Result { - Ok(Started::Done) - } - - /// Method is called when handler returns response, - /// but before sending http message to peer. - fn response(&self, req: &HttpRequest, mut resp: HttpResponse) - -> Result - { - resp.headers_mut().insert( - header::HeaderName::try_from("X-VERSION").unwrap(), - header::HeaderValue::from_static("0.2")); - Ok(Response::Done(resp)) - } -} - -fn main() { - App::new() - // Register middleware, this method can be called multiple times - .middleware(Headers) - .resource("/", |r| r.f(|_| HttpResponse::Ok())); -} -``` - -> Actix provides several useful middlewares, such as *logging*, *user sessions*, etc. +> Actix-web provides several useful middlewares, such as *logging*, *user sessions*, +> *compress*, etc. # Logging -Logging is implemented as a middleware. -It is common to register a logging middleware as the first middleware for the application. -Logging middleware must be registered for each application. +Logging is implemented as a middleware. It is common to register a logging middleware +as the first middleware for the application. Logging middleware must be registered for +each application. The `Logger` middleware uses the standard log crate to log information. You should enable logger -for *actix_web* package to see access log ([env_logger](https://docs.rs/env_logger/*/env_logger/) -or similar). +for *actix_web* package to see access log ([env_logger][envlogger] or similar). ## Usage -Create `Logger` middleware with the specified `format`. -Default `Logger` can be created with `default` method, it uses the default format: +Create `Logger` middleware with the specified `format`. Default `Logger` can be created +with `default` method, it uses the default format: ```ignore %a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T ``` -```rust -extern crate env_logger; -use actix_web::App; -use actix_web::middleware::Logger; - -fn main() { - std::env::set_var("RUST_LOG", "actix_web=info"); - env_logger::init(); - - App::new() - .middleware(Logger::default()) - .middleware(Logger::new("%a %{User-Agent}i")) - .finish(); -} -``` +{{< include-example example="middleware" file="logger.rs" section="logger" >}} The following is an example of the default logging format: @@ -110,29 +61,18 @@ INFO:actix_web::middleware::logger: 127.0.0.1:59947 [02/Dec/2017:00:22:40 -0800] ## Format - `%%` The percent sign - - `%a` Remote IP-address (IP-address of proxy if using reverse proxy) - - `%t` Time when the request was started to process - - `%P` The process ID of the child that serviced the request - - `%r` First line of request - - `%s` Response status code - - `%b` Size of response in bytes, including HTTP headers - - `%T` Time taken to serve the request, in seconds with floating fraction in .06f format - - `%D` Time taken to serve the request, in milliseconds - - `%{FOO}i` request.headers['FOO'] - - `%{FOO}o` response.headers['FOO'] - - `%{FOO}e` os.environ['FOO'] +- `%%` The percent sign +- `%a` Remote IP-address (IP-address of proxy if using reverse proxy) +- `%t` Time when the request was started to process +- `%P` The process ID of the child that serviced the request +- `%r` First line of request +- `%s` Response status code +- `%b` Size of response in bytes, including HTTP headers +- `%T` Time taken to serve the request, in seconds with floating fraction in .06f format +- `%D` Time taken to serve the request, in milliseconds +- `%{FOO}i` request.headers['FOO'] +- `%{FOO}o` response.headers['FOO'] +- `%{FOO}e` os.environ['FOO'] ## Default headers @@ -140,79 +80,37 @@ To set default response headers, the `DefaultHeaders` middleware can be used. Th *DefaultHeaders* middleware does not set the header if response headers already contain a specified header. -```rust -use actix_web::{http, middleware, App, HttpResponse}; - -fn main() { - let app = App::new() - .middleware( - middleware::DefaultHeaders::new() - .header("X-Version", "0.2")) - .resource("/test", |r| { - r.method(http::Method::GET).f(|req| HttpResponse::Ok()); - r.method(http::Method::HEAD).f(|req| HttpResponse::MethodNotAllowed()); - }) - .finish(); -} -``` +{{< include-example example="middleware" file="default_headers.rs" section="default-headers" >}} ## User sessions -Actix provides a general solution for session management. The -[**SessionStorage**](../../actix-web/actix_web/middleware/session/struct.SessionStorage.html) middleware can be -used with different backend types to store session data in different backends. +Actix-web provides a general solution for session management. The +[**actix-session**][actixsession] middleware can be used with different backend types +to store session data in different backends. > By default, only cookie session backend is implemented. Other backend implementations > can be added. -[**CookieSessionBackend**](../../actix-web/actix_web/middleware/session/struct.CookieSessionBackend.html) -uses cookies as session storage. `CookieSessionBackend` creates sessions which -are limited to storing fewer than 4000 bytes of data, as the payload must fit into a -single cookie. An internal server error is generated if a session contains more than 4000 bytes. +[**CookieSession**][cookiesession] uses cookies as session storage. `CookieSessionBackend` +creates sessions which are limited to storing fewer than 4000 bytes of data, as the payload +must fit into a single cookie. An internal server error is generated if a session +contains more than 4000 bytes. -A cookie may have a security policy of *signed* or *private*. Each has a respective `CookieSessionBackend` constructor. +A cookie may have a security policy of *signed* or *private*. Each has a respective +`CookieSession` constructor. -A *signed* cookie may be viewed but not modified by the client. A *private* cookie may neither be viewed nor modified by the client. +A *signed* cookie may be viewed but not modified by the client. A *private* cookie may +neither be viewed nor modified by the client. -The constructors take a key as an argument. This is the private key for cookie session - when this value is changed, all session data is lost. +The constructors take a key as an argument. This is the private key for cookie session - +when this value is changed, all session data is lost. -In general, you create a -`SessionStorage` middleware and initialize it with specific backend implementation, -such as a `CookieSessionBackend`. To access session data, -[*HttpRequest::session()*](../../actix-web/actix_web/middleware/session/trait.RequestSession.html#tymethod.session) - must be used. This method returns a -[*Session*](../../actix-web/actix_web/middleware/session/struct.Session.html) object, which allows us to get or set -session data. +In general, you create a `SessionStorage` middleware and initialize it with specific +backend implementation, such as a `CookieSession`. To access session data the +[`Session`][requestsession] extractor must be used. This method returns a +[*Session*][sessionobj] object, which allows us to get or set session data. -```rust -use actix_web::{server, App, HttpRequest, Result}; -use actix_web::middleware::session::{RequestSession, SessionStorage, CookieSessionBackend}; - -fn index(req: &HttpRequest) -> Result<&'static str> { - // access session data - if let Some(count) = req.session().get::("counter")? { - println!("SESSION value: {}", count); - req.session().set("counter", count+1)?; - } else { - req.session().set("counter", 1)?; - } - - Ok("Welcome!") -} - -fn main() { - let sys = actix::System::new("basic-example"); - server::new( - || App::new().middleware( - SessionStorage::new( - CookieSessionBackend::signed(&[0; 32]) - .secure(false) - ))) - .bind("127.0.0.1:59880").unwrap() - .start(); - let _ = sys.run(); -} -``` +{{< include-example example="middleware" file="user_sessions.rs" section="user-session" >}} # Error handlers @@ -223,26 +121,12 @@ for a specific status code. You can modify an existing response or create a comp one. The error handler can return a response immediately or return a future that resolves into a response. -```rust -use actix_web::{ - App, HttpRequest, HttpResponse, Result, - http, middleware::Response, middleware::ErrorHandlers}; +{{< include-example example="middleware" file="errorhandler.rs" section="error-handler" >}} -fn render_500(_: &HttpRequest, resp: HttpResponse) -> Result { - let mut builder = resp.into_builder(); - builder.header(http::header::CONTENT_TYPE, "application/json"); - Ok(Response::Done(builder.into())) -} - -fn main() { - let app = App::new() - .middleware( - ErrorHandlers::new() - .handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500)) - .resource("/test", |r| { - r.method(http::Method::GET).f(|_| HttpResponse::Ok()); - r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed()); - }) - .finish(); -} -``` +[sessionobj]: https://docs.rs/actix-session/0.1.1/actix_session/struct.Session.html +[requestsession]: https://docs.rs/actix-session/0.1.1/actix_session/struct.Session.html +[cookiesession]: https://docs.rs/actix-session/0.1.1/actix_session/struct.CookieSession.html +[actixsession]: https://docs.rs/actix-session/0.1.1/actix_session/ +[envlogger]: https://docs.rs/env_logger/*/env_logger/ +[servicetrait]: https://docs.rs/actix-web/1.0.2/actix_web/dev/trait.Service.html +[transformtrait]: https://docs.rs/actix-web/1.0.2/actix_web/dev/trait.Transform.html diff --git a/content/docs/request.md b/content/docs/request.md index 1f2e9f8..ad71c29 100644 --- a/content/docs/request.md +++ b/content/docs/request.md @@ -6,153 +6,58 @@ weight: 200 # Content Encoding -Actix automatically *decompresses* payloads. The following codecs are supported: +Actix-web automatically *decompresses* payloads. The following codecs are supported: * Brotli +* Chunked +* Compress * Gzip * Deflate * Identity +* Trailers +* EncodingExt If request headers contain a `Content-Encoding` header, the request payload is decompressed -according to the header value. Multiple codecs are not supported, -i.e: `Content-Encoding: br, gzip`. +according to the header value. Multiple codecs are not supported, i.e: `Content-Encoding: br, gzip`. # JSON Request There are several options for json body deserialization. The first option is to use *Json* extractor. First, you define a handler function -that accepts `Json` as a parameter, then, you use the `.with()` method for registering +that accepts `Json` as a parameter, then, you use the `.to()` method for registering this handler. It is also possible to accept arbitrary valid json object by using `serde_json::Value` as a type `T`. -```rust -#[macro_use] extern crate serde_derive; -use actix_web::{App, Json, Result, http}; - -#[derive(Deserialize)] -struct Info { - username: String, -} - -/// extract `Info` using serde -fn index(info: Json) -> Result { - Ok(format!("Welcome {}!", info.username)) -} - -fn main() { - let app = App::new().resource( - "/index.html", - |r| r.method(http::Method::POST).with(index)); // <- use `with` extractor -} -``` - -Another option is to use *HttpRequest::json()*. This method returns a -[*JsonBody*](../../actix-web/actix_web/dev/struct.JsonBody.html) object which resolves into -the deserialized value. - -```rust -#[derive(Debug, Serialize, Deserialize)] -struct MyObj { - name: String, - number: i32, -} - -fn index(req: &HttpRequest) -> Box> { - req.json().from_err() - .and_then(|val: MyObj| { - println!("model: {:?}", val); - Ok(HttpResponse::Ok().json(val)) // <- send response - }) - .responder() -} -``` +{{< include-example example="requests" file="main.rs" section="json-request" >}} You may also manually load the payload into memory and then deserialize it. In the following example, we will deserialize a *MyObj* struct. We need to load the request body first and then deserialize the json into an object. -```rust -extern crate serde_json; -use futures::{Future, Stream}; +{{< include-example example="requests" file="manual.rs" section="json-manual" >}} -#[derive(Serialize, Deserialize)] -struct MyObj {name: String, number: i32} - -fn index(req: &HttpRequest) -> Box> { - // `concat2` will asynchronously read each chunk of the request body and - // return a single, concatenated, chunk - req.concat2() - // `Future::from_err` acts like `?` in that it coerces the error type from - // the future into the final error type - .from_err() - // `Future::and_then` can be used to merge an asynchronous workflow with a - // synchronous workflow - .and_then(|body| { - let obj = serde_json::from_slice::(&body)?; - Ok(HttpResponse::Ok().json(obj)) - }) - .responder() -} -``` - -> A complete example for both options is available in -> [examples directory](https://github.com/actix/examples/tree/master/json/). +> A complete example for both options is available in [examples directory][examples]. # Chunked transfer encoding -Actix automatically decodes *chunked* encoding. `HttpRequest::payload()` already contains -the decoded byte stream. If the request payload is compressed with one of the supported -compression codecs (br, gzip, deflate), then the byte stream is decompressed. +Actix automatically decodes *chunked* encoding. The [`web::Payload`][payloadextractor] +extractor already contains the decoded byte stream. If the request payload is compressed +with one of the supported compression codecs (br, gzip, deflate), then the byte stream +is decompressed. # Multipart body -Actix provides multipart stream support. -[*Multipart*](../../actix-web/actix_web/multipart/struct.Multipart.html) is implemented as -a stream of multipart items. Each item can be a -[*Field*](../../actix-web/actix_web/multipart/struct.Field.html) or a nested -*Multipart* stream.`HttpResponse::multipart()` returns the *Multipart* stream -for the current request. +Actix-web provides multipart stream support with an external crate, [`actix-multipart`][multipartcrate]. -The following demonstrates multipart stream handling for a simple form: - -```rust -use actix_web::*; - -fn index(req: &HttpRequest) -> Box> { - // get multipart and iterate over multipart items - req.multipart() - .and_then(|item| { - match item { - multipart::MultipartItem::Field(field) => { - println!("==== FIELD ==== {:?} {:?}", - field.headers(), - field.content_type()); - Either::A( - field.map(|chunk| { - println!("-- CHUNK: \n{}", - std::str::from_utf8(&chunk).unwrap());}) - .fold((), |_, _| result(Ok(())))) - }, - multipart::MultipartItem::Nested(mp) => { - Either::B(result(Ok(()))) - } - } - }) -} -``` - -> A full example is available in the -> [examples directory](https://github.com/actix/examples/tree/master/multipart/). +> A full example is available in the [examples directory][multipartexample]. # Urlencoded body -Actix provides support for *application/x-www-form-urlencoded* encoded bodies. -`HttpResponse::urlencoded()` returns a -[*UrlEncoded*](../../actix-web/actix_web/dev/struct.UrlEncoded.html) future, which resolves -to the deserialized instance. The type of the instance must implement the -`Deserialize` trait from *serde*. +Actix-web provides support for *application/x-www-form-urlencoded* encoded bodies with +the [`web::Form`][formencoded] extractor which resolves to the deserialized instance. The +type of the instance must implement the `Deserialize` trait from *serde*. The *UrlEncoded* future can resolve into an error in several cases: @@ -161,27 +66,7 @@ The *UrlEncoded* future can resolve into an error in several cases: * content-length is greater than 256k * payload terminates with error. -```rust -#[macro_use] extern crate serde_derive; -use actix_web::*; -use futures::future::{Future, ok}; - -#[derive(Deserialize)] -struct FormData { - username: String, -} - -fn index(req: &HttpRequest) -> Box> { - req.urlencoded::() // <- get UrlEncoded future - .from_err() - .and_then(|data| { // <- deserialized instance - println!("USERNAME: {:?}", data.username); - ok(HttpResponse::Ok().into()) - }) - .responder() -} -# fn main() {} -``` +{{< include-example example="requests" file="urlencoded.rs" section="urlencoded" >}} # Streaming request @@ -190,20 +75,13 @@ body payload. In the following example, we read and print the request payload chunk by chunk: -```rust -use actix_web::*; -use futures::{Future, Stream}; +{{< include-example example="requests" file="streaming.rs" section="streaming" >}} - -fn index(req: &HttpRequest) -> Box> { - req - .payload() - .from_err() - .fold((), |_, chunk| { - println!("Chunk: {:?}", chunk); - result::<_, error::PayloadError>(Ok(())) - }) - .map(|_| HttpResponse::Ok().finish()) - .responder() -} -``` +[examples]: https://github.com/actix/examples/tree/master/json/ +[multipartstruct]: https://docs.rs/actix-multipart/0.1.2/actix_multipart/struct.Multipart.html +[fieldstruct]: https://docs.rs/actix-multipart/0.1.2/actix_multipart/struct.Field.html +[multipartexample]: https://github.com/actix/examples/tree/master/multipart/ +[urlencoded]: https://docs.rs/actix-web/1.0.2/actix_web/dev/struct.UrlEncoded.html +[payloadextractor]: https://docs.rs/actix-web/1.0.2/actix_web/web/struct.Payload.html +[multipartcrate]: https://crates.io/crates/actix-multipart +[formencoded]:Jhttps://docs.rs/actix-web/1.0.2/actix_web/web/struct.Form.html diff --git a/content/docs/response.md b/content/docs/response.md index 369b869..79681a1 100644 --- a/content/docs/response.md +++ b/content/docs/response.md @@ -6,112 +6,62 @@ weight: 210 # Response -A builder-like pattern is used to construct an instance of `HttpResponse`. -`HttpResponse` provides several methods that return a `HttpResponseBuilder` instance, -which implements various convenience methods for building responses. +A builder-like pattern is used to construct an instance of `HttpResponse`. `HttpResponse` +provides several methods that return a `HttpResponseBuilder` instance, which implements +various convenience methods for building responses. -> Check the [documentation](../../actix-web/actix_web/dev/struct.HttpResponseBuilder.html) -> for type descriptions. +> Check the [documentation][responsebuilder] for type descriptions. -The methods `.body`, `.finish`, and `.json` finalize response creation and -return a constructed *HttpResponse* instance. If this methods is called on the same -builder instance multiple times, the builder will panic. +The methods `.body`, `.finish`, and `.json` finalize response creation and return a +constructed *HttpResponse* instance. If this methods is called on the same builder +instance multiple times, the builder will panic. -```rust -use actix_web::{HttpRequest, HttpResponse, http::ContentEncoding}; - -fn index(req: &HttpRequest) -> HttpResponse { - HttpResponse::Ok() - .content_encoding(ContentEncoding::Br) - .content_type("plain/text") - .header("X-Hdr", "sample") - .body("data") -} -``` +{{< include-example example="responses" file="main.rs" section="builder" >}} # Content encoding -Actix automatically *compresses* payloads. The following codecs are supported: +Actix-web can automatically *compresses* payloads with the [*Compress middleware*][compressmidddleware]. +The following codecs are supported: * Brotli * Gzip * Deflate * Identity -Response payload is compressed based on the *content_encoding* parameter. -By default, `ContentEncoding::Auto` is used. If `ContentEncoding::Auto` is selected, -then the compression depends on the request's `Accept-Encoding` header. +{{< include-example example="responses" file="compress.rs" section="compress" >}} + +Response payload is compressed based on the *encoding* parameter from the +`middleware::BodyEncoding` trait. By default, `ContentEncoding::Auto` is used. If +`ContentEncoding::Auto` is selected, then the compression depends on the request's +`Accept-Encoding` header. > `ContentEncoding::Identity` can be used to disable compression. > If another content encoding is selected, the compression is enforced for that codec. -For example, to enable `brotli` use `ContentEncoding::Br`: +For example, to enable `brotli` for a single handler use `ContentEncoding::Br`: -```rust -use actix_web::{HttpRequest, HttpResponse, http::ContentEncoding}; +{{< include-example example="responses" file="brotli.rs" section="brotli" >}} -fn index(req: HttpRequest) -> HttpResponse { - HttpResponse::Ok() - .content_encoding(ContentEncoding::Br) - .body("data") -} -``` +or for the entire application: -In this case we explicitly disable content compression -by setting content encoding to a `Identity` value: +{{< include-example example="responses" file="brotli_two.rs" section="brotli-two" >}} -```rust -use actix_web::{HttpRequest, HttpResponse, http::ContentEncoding}; +In this case we explicitly disable content compression by setting content encoding to +an `Identity` value: -fn index(req: HttpRequest) -> HttpResponse { - HttpResponse::Ok() - // v- disable compression - .content_encoding(ContentEncoding::Identity) - .body("data") -} -``` +{{< include-example example="responses" file="identity.rs" section="identity" >}} When dealing with an already compressed body (for example when serving assets), -set the content encoding to `Identity` to avoid compressing the already compressed +set the content encoding to `Identity` to avoid compressing the already compressed data and set the `content-encoding` header manually: -```rust -use actix_web::{HttpRequest, HttpResponse, http::ContentEncoding}; - -static HELLO_WORLD: &[u8] = &[ - 0x1f,0x8b,0x08,0x00,0xa2,0x30,0x10,0x5c, - 0x00,0x03,0xcb,0x48,0xcd,0xc9,0xc9,0x57, - 0x28,0xcf,0x2f,0xca,0x49,0xe1,0x02,0x00, - 0x2d,0x3b,0x08,0xaf,0x0c,0x00,0x00,0x00 -]; - -pub fn index(req: HttpRequest) -> HttpResponse { - HttpResponse::Ok() - .content_encoding(ContentEncoding::Identity) - .header("content-encoding", "gzip") - .body(HELLO_WORLD) -} -``` +{{< include-example example="responses" file="identity_two.rs" section="identity-two" >}} Also it is possible to set default content encoding on application level, by default `ContentEncoding::Auto` is used, which implies automatic content compression negotiation. -```rust -use actix_web::{App, HttpRequest, HttpResponse, http::ContentEncoding}; - -fn index(req: HttpRequest) -> HttpResponse { - HttpResponse::Ok() - .body("data") -} - -fn main() { - let app = App::new() - // v- disable compression for all routes - .default_encoding(ContentEncoding::Identity) - .resource("/index.html", |r| r.with(index)); -} -``` +{{< include-example example="responses" file="auto.rs" section="auto" >}} # JSON Response @@ -119,26 +69,7 @@ The `Json` type allows to respond with well-formed JSON data: simply return a va type Json where `T` is the type of a structure to serialize into *JSON*. The type `T` must implement the `Serialize` trait from *serde*. -```rust -# extern crate actix_web; -#[macro_use] extern crate serde_derive; -use actix_web::{App, HttpRequest, Json, Result, http::Method}; - -#[derive(Serialize)] -struct MyObj { - name: String, -} - -fn index(req: &HttpRequest) -> Result> { - Ok(Json(MyObj{name: req.match_info().query("name")?})) -} - -fn main() { - App::new() - .resource(r"/a/{name}", |r| r.method(Method::GET).f(index)) - .finish(); -} -``` +{{< include-example example="responses" file="json_resp.rs" section="json-resp" >}} # Chunked transfer encoding @@ -149,14 +80,7 @@ is enabled automatically. > Enabling chunked encoding for *HTTP/2.0* responses is forbidden. -```rust -use actix_web::*; -use bytes::Bytes; -use futures::stream::once; +{{< include-example example="responses" file="chunked.rs" section="chunked" >}} -fn index(req: HttpRequest) -> HttpResponse { - HttpResponse::Ok() - .chunked() - .body(Body::Streaming(Box::new(once(Ok(Bytes::from_static(b"data")))))) -} -``` +[responsebuilder]: https://docs.rs/actix-web/1.0.2/actix_web/dev/struct.HttpResponseBuilder.html +[compressmidddleware]: https://docs.rs/actix-web/1.0.2/actix_web/middleware/struct.Compress.html diff --git a/content/docs/sentry.md b/content/docs/sentry.md index a93c2d4..813ddca 100644 --- a/content/docs/sentry.md +++ b/content/docs/sentry.md @@ -6,61 +6,33 @@ weight: 1020 # Sentry Crash Reporting -[Sentry](https://sentry.io/) is a crash reporting system that supports the -failure crate which is the base of the actix error reporting. With a -middleware it's possible to automatically report server errors to Sentry. +[Sentry][sentrysite] is a crash reporting system that supports the failure crate which +is the base of the actix error reporting. With a middleware it's possible to +automatically report server errors to Sentry. # Middleware This middleware captures any error in the server error range (500 - 599) and sends the attached error to sentry with its stacktrace. -To use the middleware the [sentry crate](https://crates.io/crates/sentry) needs to be -initialized and configured and the [sentry-actix middleware](https://crates.io/crates/sentry-actix) -needs to be added. Additionally it makes sense to also register the panic handler -to be informed about hard panics. +To use the middleware the [sentry crate][sentrycrate] needs to be initialized and configured +and the [sentry-actix middleware][sentrymiddleware] needs to be added. Additionally it +makes sense to also register the panic handler to be informed about hard panics. -```rust -extern crate sentry; -extern crate sentry_actix; - -use sentry_actix::SentryMiddleware; - -use std::env; - -fn main() { - sentry::init("SENTRY_DSN_GOES_HERE"); - env::set_var("RUST_BACKTRACE", "1"); - sentry::integrations::panic::register_panic_handler(); - - let mut app = App::with_state(state) - .middleware(SentryMiddleware::new()) - // ... -} -``` +{{< include-example example="sentry" file="main.rs" section="middleware" >}} # Reusing the Hub If you use this integration the default sentry hub (`Hub::current()`) is typically the wrong one. To get the request specific one you need to use the `ActixWebHubExt` trait: -```rust -use sentry::{Hub, Level}; -use sentry_actix::ActixWebHubExt; - -let hub = Hub::from_request(req); -hub.capture_message("Something is not well", Level::Warning); -``` +{{< include-example example="sentry" file="main.rs" section="hub" >}} The hub can also be made current for the duration of a call. Then `Hub::current()` works correctly until the end of the `run` block. -```rust -use sentry::{Hub, Level}; -use sentry_actix::ActixWebHubExt; +{{< include-example example="sentry" file="main.rs" section="hub2" >}} -let hub = Hub::from_request(req); -Hub::run(hub, || { - sentry::capture_message("Something is not well", Level::Warning); -}); -``` +[sentrysite]: https://sentry.io/ +[sentrycrate]: https://crates.io/crates/sentry +[sentrymiddleware]: https://crates.io/crates/sentry-actix diff --git a/content/docs/server.md b/content/docs/server.md index 697ac7b..fb2a558 100644 --- a/content/docs/server.md +++ b/content/docs/server.md @@ -6,25 +6,17 @@ weight: 150 # The HTTP Server -The [**HttpServer**](../../actix-web/actix_web/server/struct.HttpServer.html) type is responsible for -serving http requests. +The [**HttpServer**][httpserverstruct] type is responsible for serving http requests. -`HttpServer` accepts an application factory as a parameter, and the -application factory must have `Send` + `Sync` boundaries. More about that in the -*multi-threading* section. +`HttpServer` accepts an application factory as a parameter, and the application factory +must have `Send` + `Sync` boundaries. More about that in the *multi-threading* section. -To bind to a specific socket address, -[`bind()`](../../actix-web/actix_web/server/struct.HttpServer.html#method.bind) -must be used, and it may be called multiple times. To bind ssl socket, -[`bind_ssl()`](../../actix-web/actix_web/server/struct.HttpServer.html#method.bind_ssl) -or [`bind_tls()`](../../actix-web/actix_web/server/struct.HttpServer.html#method.bind_tls) -should be used. To start the http server, use one of the start methods. +To bind to a specific socket address, [`bind()`][bindmethod] must be used, and it may be +called multiple times. To bind ssl socket, [`bind_ssl()`][bindsslmethod] or +[`bind_rustls()`][bindrusttls] should be used. To start the http server, use one of the +start methods. -- use [`start()`](../../actix-web/actix_web/server/struct.HttpServer.html#method.start) -for a server - -`HttpServer` is an actix actor. It must be initialized within a properly -configured actix system: +- use [`start()`] for a server {{< include-example example="server" section="main" >}} @@ -33,8 +25,8 @@ configured actix system: > this server, send a `StopServer` message. `HttpServer` is implemented as an actix actor. It is possible to communicate with the server -via a messaging system. Start method, e.g. `start()`, returns the -address of the started http server. It accepts several messages: +via a messaging system. Start method, e.g. `start()`, returns the address of the started +http server. It accepts several messages: - `PauseServer` - Pause accepting incoming connections - `ResumeServer` - Resume accepting incoming connections @@ -44,43 +36,39 @@ address of the started http server. It accepts several messages: ## Multi-threading -`HttpServer` automatically starts a number of http workers, by default -this number is equal to number of logical CPUs in the system. This number -can be overridden with the -[`HttpServer::workers()`](../../actix-web/actix_web/server/struct.HttpServer.html#method.workers) method. +`HttpServer` automatically starts a number of http workers, by default this number is +equal to number of logical CPUs in the system. This number can be overridden with the +[`HttpServer::workers()`][workers] method. {{< include-example example="server" file="workers.rs" section="workers" >}} The server creates a separate application instance for each created worker. Application state is not shared between threads. To share state, `Arc` could be used. -> Application state does not need to be `Send` and `Sync`, -> but factories must be `Send` + `Sync`. +> Application state does not need to be `Send` and `Sync`, but factories must be `Send` + `Sync`. ## SSL -There are two features for ssl server: `tls` and `alpn`. The `tls` feature is -for `native-tls` integration and `alpn` is for `openssl`. +There are two features for ssl server: `rust-tls` and `ssl`. The `tls` feature is +for `rust-tls` integration and `ssl` is for `openssl`. ```toml [dependencies] -actix-web = { version = "{{< actix-version "actix-web" >}}", features = ["alpn"] } +actix-web = { version = "{{< actix-version "actix-web" >}}", features = ["ssl"] } ``` {{< include-example example="server" file="ssl.rs" section="ssl" >}} -> **Note**: the *HTTP/2.0* protocol requires -> [tls alpn](https://tools.ietf.org/html/rfc7301). +> **Note**: the *HTTP/2.0* protocol requires [tls alpn][tlsalpn]. > At the moment, only `openssl` has `alpn` support. -> For a full example, check out -> [examples/tls](https://github.com/actix/examples/tree/master/tls). +> For a full example, check out [examples/tls][exampletls]. To create the key.pem and cert.pem use the command. **Fill in your own subject** ```bash $ openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem \ -days 365 -sha256 -subj "/C=CN/ST=Fujian/L=Xiamen/O=TVlinux/OU=Org/CN=muro.lxd" ``` -To remove the password, then copy nopass.pem to key.pem +To remove the password, then copy nopass.pem to key.pem ```bash $ openssl rsa -in key.pem -out nopass.pem ``` @@ -95,39 +83,46 @@ Actix can wait for requests on a keep-alive connection. - `None` or `KeepAlive::Disabled` - disable *keep alive*. - `KeepAlive::Tcp(75)` - use `SO_KEEPALIVE` socket option. -{{< include-example example="server" file="ka.rs" section="ka" >}} +{{< include-example example="server" file="keep_alive.rs" section="keep-alive" >}} -If the first option is selected, then *keep alive* state is -calculated based on the response's *connection-type*. By default -`HttpResponse::connection_type` is not defined. In that case *keep alive* is -defined by the request's http version. +If the first option is selected, then *keep alive* state is calculated based on the +response's *connection-type*. By default `HttpResponse::connection_type` is not +defined. In that case *keep alive* is defined by the request's http version. > *keep alive* is **off** for *HTTP/1.0* and is **on** for *HTTP/1.1* and *HTTP/2.0*. *Connection type* can be changed with `HttpResponseBuilder::connection_type()` method. -{{< include-example example="server" file="ka_tp.rs" section="example" >}} +{{< include-example example="server" file="keep_alive_tp.rs" section="example" >}} ## Graceful shutdown `HttpServer` supports graceful shutdown. After receiving a stop signal, workers have a specific amount of time to finish serving requests. Any workers still alive after the -timeout are force-dropped. By default the shutdown timeout is set to 30 seconds. -You can change this parameter with the -[`HttpServer::shutdown_timeout()`](../../actix-web/actix_web/server/struct.HttpServer.html#method.shutdown_timeout) method. +timeout are force-dropped. By default the shutdown timeout is set to 30 seconds. You +can change this parameter with the [`HttpServer::shutdown_timeout()`][shutdowntimeout] +method. You can send a stop message to the server with the server address and specify if you want -graceful shutdown or not. The -[`start()`](../../actix-web/actix_web/server/struct.HttpServer.html#method.start) -method returns address of the server. +graceful shutdown or not. The [`start()`][startmethod] method returns address of the server. -`HttpServer` handles several OS signals. *CTRL-C* is available on all OSs, -other signals are available on unix systems. +`HttpServer` handles several OS signals. *CTRL-C* is available on all OSs, other signals +are available on unix systems. - *SIGINT* - Force shutdown workers - *SIGTERM* - Graceful shutdown workers - *SIGQUIT* - Force shutdown workers > It is possible to disable signal handling with -> [`HttpServer::disable_signals()`](../../actix-web/actix_web/server/struct.HttpServer.html#method.disable_signals) -> method. +[`HttpServer::disable_signals()`][disablesignals] method. + +[httpserverstruct]: https://docs.rs/actix-web/1.0.2/actix_web/struct.HttpServer.html +[bindmethod]: https://docs.rs/actix-web/1.0.2/actix_web/struct.HttpServer.html#method.bind +[bindsslmethod]: https://docs.rs/actix-web/1.0.2/actix_web/struct.HttpServer.html#method.bind_ssl +[bindrusttls]: https://docs.rs/actix-web/1.0.2/actix_web/struct.HttpServer.html#method.bind_rustls +[startmethod]: https://docs.rs/actix-web/1.0.2/actix_web/struct.HttpServer.html#method.start +[workers]: https://docs.rs/actix-web/1.0.2/actix_web/struct.HttpServer.html#method.workers +[tlsalpn]: https://tools.ietf.org/html/rfc7301 +[exampletls]: https://github.com/actix/examples/tree/master/tls +[shutdowntimeout]: https://docs.rs/actix-web/1.0.2/actix_web/struct.HttpServer.html#method.shutdown_timeout +[disablesignals]: https://docs.rs/actix-web/1.0.2/actix_web/struct.HttpServer.html#method.disable_signals diff --git a/content/docs/static-files.md b/content/docs/static-files.md index 6f3d385..f3512d5 100644 --- a/content/docs/static-files.md +++ b/content/docs/static-files.md @@ -9,125 +9,40 @@ weight: 230 It is possible to serve static files with a custom path pattern and `NamedFile`. To match a path tail, we can use a `[.*]` regex. -```rust -extern crate actix_web; -use std::path::PathBuf; -use actix_web::{App, HttpRequest, Result, http::Method, fs::NamedFile}; - -fn index(req: &HttpRequest) -> Result { - let path: PathBuf = req.match_info().query("tail")?; - Ok(NamedFile::open(path)?) -} - -fn main() { - App::new() - .resource(r"/a/{tail:.*}", |r| r.method(Method::GET).f(index)) - .finish(); -} -``` +{{< include-example example="static-files" file="main.rs" section="individual-file" >}} # Directory -To serve files from specific directories and sub-directories, `StaticFiles` can be used. -`StaticFiles` must be registered with an `App::handler()` method, otherwise +To serve files from specific directories and sub-directories, `Files` can be used. +`Files` must be registered with an `App::service()` method, otherwise it will be unable to serve sub-paths. -```rust -extern crate actix_web; -use actix_web::{App, fs}; +{{< include-example example="static-files" file="directory.rs" section="directory" >}} -fn main() { - App::new() - .handler( - "/static", - fs::StaticFiles::new(".") - .unwrap() - .show_files_listing()) - .finish(); -} -``` - -The parameter is the base directory. By default files listing for sub-directories -is disabled. Attempt to load directory listing will return *404 Not Found* response. -To enable files listing, use -[*StaticFiles::show_files_listing()*](../../actix-web/actix_web/fs/struct.StaticFiles.html#method.show_files_listing) +By default files listing for sub-directories is disabled. Attempt to load directory +listing will return *404 Not Found* response. To enable files listing, use +[*Files::show_files_listing()*][showfileslisting] method. -Instead of showing files listing for directory, it is possible to redirect -to a specific index file. Use the -[*StaticFiles::index_file()*](../../actix-web/actix_web/fs/struct.StaticFiles.html#method.index_file) -method to configure this redirect. +Instead of showing files listing for directory, it is possible to redirect to a specific +index file. Use the [*Files::index_file()*][indexfile] method to configure this redirect. # Configuration -Generic trait `StaticFileConfig` can be used to specify various options -for serving files: +`NamedFiles` can specify various options for serving files: -- `content_disposition_map` - function to be used for mapping file's mime to corresponding `Content-Disposition` type -- `is_use_etag` - specifies whether `ETag` shall be calculated and included in headers. -- `is_use_last_modifier` - specifies whether file modified timestamp should be used and added to `Last-Modified` header. -- `is_method_allowed` - allows to control which HTTP methods are allowed to be used when accessing file. +- `set_content_dispostion` - function to be used for mapping file's mime to corresponding `Content-Disposition` type +- `use_etag` - specifies whether `ETag` shall be calculated and included in headers. +- `use_last_modifier` - specifies whether file modified timestamp should be used and added to `Last-Modified` header. -All of the above methods are optional and provided with the best defaults. -But it is possible to customize any of them by implementing the trait onto own struct. +All of the above methods are optional and provided with the best defaults, But it is +possible to customize any of them. -```rust -extern crate mime; -extern crate actix_web; -use actix_web::{App, HttpRequest, Result, http::Method}; -use actix_web::fs::{StaticFileConfig, NamedFile}; -use actix_web::http::header::DispositionType; +{{< include-example example="static-files" file="configuration.rs" section="config-one" >}} -use std::path::PathBuf; +The Configuration can also be applied to directory service: -#[derive(Default)] -struct MyConfig; +{{< include-example example="static-files" file="configuration_two.rs" section="config-two" >}} -impl StaticFileConfig for MyConfig { - fn content_disposition_map(typ: mime::Name) -> DispositionType { - DispositionType::Attachment - } -} - -fn index(req: &HttpRequest) -> Result> { - let path: PathBuf = req.match_info().query("tail")?; - Ok(NamedFile::open_with_config(path, MyConfig)?) -} - -fn main() { - App::new() - .resource(r"/a/{tail:.*}", |r| r.method(Method::GET).f(index)) - .finish(); -} -``` - -The Configuration cal also be applied to directory service: - -```rust -extern crate actix_web; - -use actix_web::{App}; -use actix_web::fs::{StaticFileConfig, StaticFiles}; - -#[derive(Default)] -struct MyConfig; - -impl StaticFileConfig for MyConfig { - fn is_use_etag() -> bool { - false - } - - fn is_use_last_modifier() -> bool { - false - } -} - -fn main() { - App::new() - .handler( - "/static", - StaticFiles::with_config(".", MyConfig).unwrap() - .show_files_listing() - ).finish(); -} -``` +[showfileslisting]: https://docs.rs/actix-files/0.1.2/actix_files/struct.Files.html +[indexfile]: https://docs.rs/actix-files/0.1.2/actix_files/struct.Files.html#method.index_file diff --git a/content/docs/testing.md b/content/docs/testing.md index 2db98df..68bf7f8 100644 --- a/content/docs/testing.md +++ b/content/docs/testing.md @@ -6,237 +6,48 @@ weight: 210 # Testing -Every application should be well tested. Actix provides tools to perform unit and +Every application should be well tested. Actix-web provides tools to perform unit and integration tests. # Unit Tests -For unit testing, actix provides a request builder type and a simple handler runner. -[*TestRequest*](../../actix-web/actix_web/test/struct.TestRequest.html) -implements a builder-like pattern. -You can generate a `HttpRequest` instance with `finish()`, or you can -run your handler with `run()` or `run_async()`. +For unit testing, actix-web provides a request builder type and a simple handler runner. +[*TestRequest*][testrequest] implements a builder-like pattern. You can generate a +`HttpRequest` instance with `to_http_request()`, or you can +run your handler with `block_on()`. -```rust -use actix_web::{http, test, HttpRequest, HttpResponse, HttpMessage}; - -fn index(req: &HttpRequest) -> HttpResponse { - if let Some(hdr) = req.headers().get(http::header::CONTENT_TYPE) { - if let Ok(s) = hdr.to_str() { - return HttpResponse::Ok().into() - } - } - HttpResponse::BadRequest().into() -} - -fn main() { - let resp = test::TestRequest::with_header("content-type", "text/plain") - .run(&index) - .unwrap(); - assert_eq!(resp.status(), http::StatusCode::OK); - - let resp = test::TestRequest::default() - .run(&index) - .unwrap(); - assert_eq!(resp.status(), http::StatusCode::BAD_REQUEST); -} -``` +{{< include-example example="testing" file="main.rs" section="unit-tests" >}} # Integration tests -There are several methods for testing your application. Actix provides -[*TestServer*](../../actix-web/actix_web/test/struct.TestServer.html), which can be used +There a few methods for testing your application. Actix-web can be used to run the application with specific handlers in a real http server. -`TestServer::get()`, `TestServer::post()`, and `TestServer::client()` +`TestRequest::get()`, `TestRequest::post()`, and `TestRequest::client()` methods can be used to send requests to the test server. -A simple form `TestServer` can be configured to use a handler. -`TestServer::new` method accepts a configuration function, and the only argument -for this function is a *test application* instance. +To create a `Service` for testing, use the `test::init_serivce` method which accepts a +regular `App` builder. -> Check the [api documentation](../../actix-web/actix_web/test/struct.TestApp.html) -> for more information. +> Check the [api documentation][actixdocs] for more information. -```rust -use actix_web::{HttpRequest, HttpMessage}; -use actix_web::test::TestServer; -use std::str; +{{< include-example example="testing" file="integration_one.rs" section="integration-one" >}} -fn index(req: &HttpRequest) -> &'static str { - "Hello world!" -} - -fn main() { - // start new test server - let mut srv = TestServer::new(|app| app.handler(index)); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - let bytes = srv.execute(response.body()).unwrap(); - let body = str::from_utf8(&bytes).unwrap(); - assert_eq!(body, "Hello world!"); -} -``` - -The other option is to use an application factory. In this case, you need to pass the factory -function the same way as you would for real http server configuration. - -```rust -use actix_web::{http, test, App, HttpRequest, HttpResponse}; - -fn index(req: &HttpRequest) -> HttpResponse { - HttpResponse::Ok().into() -} - -/// This function get called by http server. -fn create_app() -> App { - App::new() - .resource("/test", |r| r.h(index)) -} - -fn main() { - let mut srv = test::TestServer::with_factory(create_app); - - let request = srv.client( - http::Method::GET, "/test").finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - - assert!(response.status().is_success()); -} -``` - -If you need more complex application configuration, use the `TestServer::build_with_state()` -method. For example, you may need to initialize application state or start `SyncActor`'s for diesel -interation. This method accepts a closure that constructs the application state, -and it runs when the actix system is configured. Thus, you can initialize any additional actors. - -```rust -#[test] -fn test() { - let srv = TestServer::build_with_state(|| { - // we can start diesel actors - let addr = SyncArbiter::start(3, || { - DbExecutor(SqliteConnection::establish("test.db").unwrap()) - }); - // then we can construct custom state, or it could be `()` - MyState{addr: addr} - }) - - // register server handlers and start test server - .start(|app| { - app.resource( - "/{username}/index.html", |r| r.with( - |p: Path| format!("Welcome {}!", p.username))); - }); - - // now we can run our test code -); -``` +If you need more complex application configuration testing should be very similar to creating +the normal application. For example, you may need to initialize application state. Create an +`App` with a `data` method and attach state just like you would from a normal application. +{{< include-example example="testing" file="integration_two.rs" section="integration-two" >}} # Stream response tests -If you need to test stream it would be enough to convert a [*ClientResponse*](../../actix-web/actix_web/client/struct.ClientResponse.html) to future and execute it. -For example of testing [*Server Sent Events*](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events). +If you need to test stream it would be enough to convert a [*ClientResponse*][clientresponse] +to future and execute it. +For example of testing [*Server Sent Events*][serversentevents]. -```rust -extern crate bytes; -extern crate futures; -extern crate actix_web; +{{< include-example example="testing" file="stream_response.rs" section="stream-response" >}} -use bytes::Bytes; -use futures::stream::poll_fn; -use futures::{Async, Poll, Stream}; - -use actix_web::{Error, HttpMessage, HttpRequest, HttpResponse}; -use actix_web::http::{ContentEncoding, StatusCode}; -use actix_web::test::TestServer; - - -fn sse(_req: &HttpRequest) -> HttpResponse { - let mut counter = 5usize; - // yields `data: N` where N in [5; 1] - let server_events = poll_fn(move || -> Poll, Error> { - if counter == 0 { - return Ok(Async::Ready(None)); - } - let payload = format!("data: {}\n\n", counter); - counter -= 1; - Ok(Async::Ready(Some(Bytes::from(payload)))) - }); - - HttpResponse::build(StatusCode::OK) - .content_encoding(ContentEncoding::Identity) - .content_type("text/event-stream") - .streaming(server_events) -} - - -fn main() { - // start new test server - let mut srv = TestServer::new(|app| app.handler(sse)); - - // request stream - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // convert ClientResponse to future, start read body and wait first chunk - let mut stream = response.payload(); - loop { - match srv.execute(stream.into_future()) { - Ok((Some(bytes), remain)) => { - println!("{:?}", bytes); - stream = remain - } - Ok((None, _)) => break, - Err(_) => panic!(), - } - } -} -``` - -# WebSocket server tests - -It is possible to register a *handler* with `TestApp::handler()`, which -initiates a web socket connection. *TestServer* provides the method `ws()`, which connects to -the websocket server and returns ws reader and writer objects. *TestServer* also -provides an `execute()` method, which runs future objects to completion and returns -result of the future computation. - -The following example demonstrates how to test a websocket handler: - -```rust -use actix::{Actor, StreamHandler}; -use actix_web::*; -use futures::Stream; - -struct Ws; // <- WebSocket actor - -impl Actor for Ws { - type Context = ws::WebsocketContext; -} - -impl StreamHandler for Ws { - fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { - match msg { - ws::Message::Text(text) => ctx.text(text), - _ => (), - } - } -} - -fn main() { - let mut srv = test::TestServer::new( - |app| app.handler(|req| ws::start(req, Ws))); - - let (reader, mut writer) = srv.ws().unwrap(); - writer.text("text"); - - let (item, reader) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Text("text".to_owned()))); -} -``` +[serversentevents]: https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events +[clientresponse]: https://docs.rs/actix-web/1.0.2/actix_web/client/struct.ClientResponse.html +[actixdocs]: (https://docs.rs/actix-web/1.0.2/actix_web/test/index.html) +[testrequest]: https://docs.rs/actix-web/1.0.2/actix_web/error/trait.ResponseError.html#foreign-impls diff --git a/content/docs/url-dispatch.md b/content/docs/url-dispatch.md index 3f76b39..c7f40e1 100644 --- a/content/docs/url-dispatch.md +++ b/content/docs/url-dispatch.md @@ -6,112 +6,95 @@ weight: 190 # URL Dispatch -URL dispatch provides a simple way for mapping URLs to `Handler` code using a simple pattern +URL dispatch provides a simple way for mapping URLs to handler code using a simple pattern matching language. If one of the patterns matches the path information associated with a request, a particular handler object is invoked. -> A handler is a specific object that implements the -> `Handler` trait, defined in your application, that receives the request and returns -> a response object. More information is available in the -> [handler section](sec-4-handler.html). +> A request handler is a function that accepts zero or more parameters that can be extracted +> from a request (ie, [*impl FromRequest*][implfromrequest]) and returns a type that can +> be converted into an HttpResponse (ie, [*impl Responder*][implresponder]). More information +> is available in the [handler section][handlersection]. # Resource configuration -Resource configuration is the act of adding a new resources to an application. -A resource has a name, which acts as an identifier to be used for URL generation. -The name also allows developers to add routes to existing resources. -A resource also has a pattern, meant to match against the *PATH* portion of a *URL* (the portion following the scheme and -port, e.g. */foo/bar* in the *URL* *http://localhost:8080/foo/bar?q=value*). -It does not match against the *QUERY* portion (the portion that follows *?*, e.g. *q=value* in *http://localhost:8080/foo/bar?q=value*). +Resource configuration is the act of adding a new resources to an application. A resource +has a name, which acts as an identifier to be used for URL generation. The name also +allows developers to add routes to existing resources. A resource also has a pattern, +meant to match against the *PATH* portion of a *URL* (the portion following the scheme and +port, e.g. */foo/bar* in the *URL* *http://localhost:8080/foo/bar?q=value*). It does not +match against the *QUERY* portion (the portion that follows *?*, e.g. *q=value* +in *http://localhost:8080/foo/bar?q=value*). -The [*App::route()*](../../actix-web/actix_web/struct.App.html#method.route) method provides -simple way of registering routes. This method adds a single route to application -routing table. This method accepts a *path pattern*, +The [*App::route()*][approute] method provides simple way of registering routes. This +method adds a single route to application routing table. This method accepts a *path pattern*, *http method* and a handler function. `route()` method could be called multiple times for the same path, in that case, multiple routes register for the same resource path. {{< include-example example="url-dispatch" section="main" >}} -While *App::route()* provides simple way of registering routes, to access -complete resource configuration, a different method has to be used. -The [*App::resource()*](../../actix-web/actix_web/struct.App.html#method.resource) method -adds a single resource to application routing table. This method accepts a *path pattern* -and a resource configuration function. +While *App::route()* provides simple way of registering routes, to access complete resource +configuration, a different method has to be used. The [*App::service()*][appservice] method +adds a single [resource][webresource] to application routing table. This method accepts a +*path pattern*, guards, and one or more routes. {{< include-example example="url-dispatch" file="resource.rs" section="resource" >}} -The *Configuration function* has the following type: - -```rust -FnOnce(&mut Resource<_>) -> () -``` - -The *Configuration function* can set a name and register specific routes. If a resource does not contain any route or does not have any matching routes, it returns *NOT FOUND* http response. ## Configuring a Route -Resource contains a set of routes. Each route in turn has a set of predicates and a handler. +Resource contains a set of routes. Each route in turn has a set of `guards` and a handler. New routes can be created with `Resource::route()` method which returns a reference -to new *Route* instance. By default the *route* does not contain any predicates, so matches +to new *Route* instance. By default the *route* does not contain any guards, so matches all requests and the default handler is `HttpNotFound`. The application routes incoming requests based on route criteria which are defined during resource registration and route registration. Resource matches all routes it contains in the order the routes were registered via `Resource::route()`. -> A *Route* can contain any number of *predicates* but only one handler. +> A *Route* can contain any number of *guards* but only one handler. {{< include-example example="url-dispatch" file="cfg.rs" section="cfg" >}} -In this example, `HttpResponse::Ok()` is returned for *GET* requests. -If a request contains `Content-Type` header, the value of this header is *text/plain*, -and path equals to `/path`, Resource calls handle of the first matching route. +In this example, `HttpResponse::Ok()` is returned for *GET* requests if the request +contains `Content-Type` header, the value of this header is *text/plain*, and path +equals to `/path`. If a resource can not match any route, a "NOT FOUND" response is returned. -[*ResourceHandler::route()*](../../actix-web/actix_web/dev/struct.ResourceHandler.html#method.route) returns a -[*Route*](../../actix-web/actix_web/dev/struct.Route.html) object. Route can be configured with a -builder-like pattern. Following configuration methods are available: +[*ResourceHandler::route()*][resourcehandler] returns a [*Route*][route] object. Route +can be configured with a builder-like pattern. Following configuration methods are available: -* [*Route::filter()*](../../actix-web/actix_web/dev/struct.Route.html#method.filter) - registers a new predicate. Any number of predicates can be registered for each route. -* [*Route::f()*](../../actix-web/actix_web/dev/struct.Route.html#method.f) registers - handler function for this route. Only one handler can be registered. - Usually handler registration - is the last config operation. Handler function can be a function or closure - and has the type - `Fn(&HttpRequest) -> R + 'static` -* [*Route::h()*](../../actix-web/actix_web/dev/struct.Route.html#method.h) registers - a handler object that implements the `Handler` trait. This is - similar to `f()` method - only one handler can - be registered. Handler registration is the last config operation. -* [*Route::a()*](../../actix-web/actix_web/dev/struct.Route.html#method.a) registers - an async handler function for this route. Only one handler can be registered. - Handler registration is the last config operation. Handler function can - be a function or closure and has the type - `Fn(&HttpRequest) -> Future + 'static` +* [*Route::guard()*][routeguard] registers a new guard. Any number of guards can be + registered for each route. +* [*Route::method()*][routemethod] registers a method guard. Any number of guards can be + registered for each route. +* [*Route::to()*][routeto] registers handler function for this route. Only one handler + can be registered. Usually handler registration is the last config operation. +* [*Route::to_async()*][routetoasync] registers an async handler function for this route. + Only one handler can be registered. Handler registration is the last config operation. # Route matching The main purpose of route configuration is to match (or not match) the request's `path` against a URL path pattern. `path` represents the path portion of the URL that was requested. -The way that *actix* does this is very simple. When a request enters the system, +The way that *actix-web* does this is very simple. When a request enters the system, for each resource configuration declaration present in the system, actix checks the request's path against the pattern declared. This checking happens in the order that -the routes were declared via `App::resource()` method. If resource can not be found, +the routes were declared via `App::service()` method. If resource can not be found, the *default resource* is used as the matched resource. -When a route configuration is declared, it may contain route predicate arguments. All route -predicates associated with a route declaration must be `true` for the route configuration to -be used for a given request during a check. If any predicate in the set of route predicate +When a route configuration is declared, it may contain route guard arguments. All route +guards associated with a route declaration must be `true` for the route configuration to +be used for a given request during a check. If any guard in the set of route guard arguments provided to a route configuration returns `false` during a check, that route is skipped and route matching continues through the ordered set of routes. If any route matches, the route matching process stops and the handler associated with -the route is invoked. If no route matches after all route patterns are exhausted, a *NOT FOUND* response get returned. +the route is invoked. If no route matches after all route patterns are exhausted, a +*NOT FOUND* response get returned. # Resource pattern syntax @@ -269,18 +252,12 @@ A *scoped* path can contain variable path segments as resources. Consistent with unscoped paths. You can get variable path segments from `HttpRequest::match_info()`. -`Path` extractor also is able to extract scope level variable segments. +[`Path` extractor][pathextractor] also is able to extract scope level variable segments. # Match information -All values representing matched path segments are available in -[`HttpRequest::match_info`](../actix_web/struct.HttpRequest.html#method.match_info). -Specific values can be retrieved with -[`Params::get()`](../actix_web/dev/struct.Params.html#method.get). - -Any matched parameter can be deserialized into a specific type if the type -implements the `FromParam` trait. For example most standard integer types -the trait, i.e.: +All values representing matched path segments are available in [`HttpRequest::match_info`][matchinfo]. +Specific values can be retrieved with [`Path::get()`][pathget]. {{< include-example example="url-dispatch" file="minfo.rs" section="minfo" >}} @@ -304,18 +281,13 @@ safe to interpolate within, or use as a suffix of, a path without additional che {{< include-example example="url-dispatch" file="pbuf.rs" section="pbuf" >}} -List of `FromParam` implementations can be found in -[api docs](../../actix-web/actix_web/dev/trait.FromParam.html#foreign-impls) - ## Path information extractor -Actix provides functionality for type safe path information extraction. -[*Path*](../../actix-web/actix_web/struct.Path.html) extracts information, destination type -could be defined in several different forms. Simplest approach is to use -`tuple` type. Each element in tuple must correpond to one element from +Actix provides functionality for type safe path information extraction. [*Path*][pathstruct] +extracts information, destination type could be defined in several different forms. Simplest +approach is to use `tuple` type. Each element in tuple must correpond to one element from path pattern. i.e. you can match path pattern `/{id}/{username}/` against -`Path<(u32, String)>` type, but `Path<(String, String, String)>` type will -always fail. +`Path<(u32, String)>` type, but `Path<(String, String, String)>` type will always fail. {{< include-example example="url-dispatch" file="path.rs" section="path" >}} @@ -324,21 +296,19 @@ this struct must implement *serde's *`Deserialize` trait. {{< include-example example="url-dispatch" file="path2.rs" section="path" >}} -[*Query*](../../actix-web/actix_web/struct.Query.html) provides similar -functionality for request query parameters. +[*Query*][query] provides similar functionality for request query parameters. # Generating resource URLs -Use the [*HttpRequest.url_for()*](../../actix-web/actix_web/struct.HttpRequest.html#method.url_for) -method to generate URLs based on resource patterns. For example, if you've configured a -resource with the name "foo" and the pattern "{a}/{b}/{c}", you might do this: +Use the [*HttpRequest.url_for()*][urlfor] method to generate URLs based on resource +patterns. For example, if you've configured a resource with the name "foo" and the +pattern "{a}/{b}/{c}", you might do this: {{< include-example example="url-dispatch" file="urls.rs" section="url" >}} This would return something like the string *http://example.com/test/1/2/3* (at least if -the current protocol and hostname implied http://example.com). -`url_for()` method returns [*Url object*](https://docs.rs/url/1.7.0/url/struct.Url.html) so you -can modify this url (add query parameters, anchor, etc). +the current protocol and hostname implied http://example.com). `url_for()` method +returns [*Url object*][urlobj] so you can modify this url (add query parameters, anchor, etc). `url_for()` could be called only for *named* resources otherwise error get returned. # External resources @@ -355,22 +325,14 @@ By normalizing it means: * To add a trailing slash to the path. * To replace multiple slashes with one. -The handler returns as soon as it finds a path that resolves -correctly. The order of normalization conditions, if all are enabled, is 1) merge, 2) both merge and append -and 3) append. If the path resolves with -at least one of those conditions, it will redirect to the new path. - -If *append* is *true*, append slash when needed. If a resource is -defined with trailing slash and the request doesn't have one, it will -be appended automatically. - -If *merge* is *true*, merge multiple consecutive slashes in the path into one. - -This handler designed to be used as a handler for application's *default resource*. +The handler returns as soon as it finds a path that resolves correctly. The order of +normalization conditions, if all are enabled, is 1) merge, 2) both merge and append and +3) append. If the path resolves with at least one of those conditions, it will redirect +to the new path. {{< include-example example="url-dispatch" file="norm.rs" section="norm" >}} -In this example `/resource`, `//resource///` will be redirected to `/resource/`. +In this example `//resource///` will be redirected to `/resource/`. In this example, the path normalization handler is registered for all methods, but you should not rely on this mechanism to redirect *POST* requests. The redirect of the @@ -383,68 +345,88 @@ It is possible to register path normalization only for *GET* requests only: ## Using an Application Prefix to Compose Applications -The `App::prefix()` method allows to set a specific application prefix. -This prefix represents a resource prefix that will be prepended to all resource patterns added -by the resource configuration. This can be used to help mount a set of routes at a different -location than the included callable's author intended while still maintaining the same -resource names. +The `web::scope()` method allows to set a specific application scope. This scope represents +a resource prefix that will be prepended to all resource patterns added by the resource +configuration. This can be used to help mount a set of routes at a different location +than the included callable's author intended while still maintaining the same resource names. For example: -{{< include-example example="url-dispatch" file="prefix.rs" section="prefix" >}} +{{< include-example example="url-dispatch" file="scope.rs" section="scope" >}} In the above example, the *show_users* route will have an effective route pattern of -*/users/show* instead of */show* because the application's prefix argument will be prepended +*/users/show* instead of */show* because the application's scope will be prepended to the pattern. The route will then only match if the URL path is */users/show*, and when the `HttpRequest.url_for()` function is called with the route name show_users, it will generate a URL with that same path. -# Custom route predicates +# Custom route guard -You can think of a predicate as a simple function that accepts a *request* object reference -and returns *true* or *false*. Formally, a predicate is any object that implements the -[`Predicate`](../actix_web/pred/trait.Predicate.html) trait. Actix provides -several predicates, you can check -[functions section](../../actix-web/actix_web/pred/index.html#functions) of api docs. +You can think of a guard as a simple function that accepts a *request* object reference +and returns *true* or *false*. Formally, a guard is any object that implements the +[`Guard`][guardtrait] trait. Actix provides several predicates, you can check +[functions section][guardfuncs] of api docs. -Here is a simple predicate that check that a request contains a specific *header*: +Here is a simple guard that check that a request contains a specific *header*: -{{< include-example example="url-dispatch" file="pred.rs" section="pred" >}} +{{< include-example example="url-dispatch" file="guard.rs" section="guard" >}} In this example, *index* handler will be called only if request contains *CONTENT-TYPE* header. -Predicates have access to the application's state via `HttpRequest::state()`. -Also predicates can store extra information in -[request extensions](../../actix-web/actix_web/struct.HttpRequest.html#method.extensions). +Guards can not access or modify the request object, but it is possible to store extra +information in [request extensions][requestextensions]. -## Modifying predicate values +## Modifying guard values You can invert the meaning of any predicate value by wrapping it in a `Not` predicate. For example, if you want to return "METHOD NOT ALLOWED" response for all methods except "GET": -{{< include-example example="url-dispatch" file="pred2.rs" section="pred" >}} +{{< include-example example="url-dispatch" file="guard2.rs" section="guard2" >}} -The `Any` predicate accepts a list of predicates and matches if any of the supplied -predicates match. i.e: +The `Any` guard accepts a list of guards and matches if any of the supplied +guards match. i.e: ```rust -pred::Any(pred::Get()).or(pred::Post()) +guard::Any(guard::Get()).or(guard::Post()) ``` -The `All` predicate accepts a list of predicates and matches if all of the supplied -predicates match. i.e: +The `All` guard accepts a list of guard and matches if all of the supplied +guards match. i.e: ```rust -pred::All(pred::Get()).and(pred::Header("content-type", "plain/text")) +guard::All(guard::Get()).and(guard::Header("content-type", "plain/text")) ``` # Changing the default Not Found response If the path pattern can not be found in the routing table or a resource can not find matching route, the default resource is used. The default response is *NOT FOUND*. -It is possible to override the *NOT FOUND* response with `App::default_resource()`. +It is possible to override the *NOT FOUND* response with `App::default_service()`. This method accepts a *configuration function* same as normal resource configuration -with `App::resource()` method. +with `App::service()` method. {{< include-example example="url-dispatch" file="dhandler.rs" section="default" >}} + +[handlersection]: ../handlers/ +[approute]: https://docs.rs/actix-web/1.0.2/actix_web/struct.App.html#method.route +[appservice]: https://docs.rs/actix-web/1.0.2/actix_web/struct.App.html?search=#method.service +[webresource]: https://docs.rs/actix-web/1.0.2/actix_web/struct.Resource.html +[resourcehandler]: https://docs.rs/actix-web/1.0.2/actix_web/struct.Resource.html#method.route +[route]: https://docs.rs/actix-web/1.0.2/actix_web/struct.Route.html +[routeguard]: https://docs.rs/actix-web/1.0.2/actix_web/struct.Route.html#method.guard +[routemethod]: https://docs.rs/actix-web/1.0.2/actix_web/struct.Route.html#method.method +[routeto]: https://docs.rs/actix-web/1.0.2/actix_web/struct.Route.html#method.to +[routetoasync]: https://docs.rs/actix-web/1.0.2/actix_web/struct.Route.html#method.to_async +[matchinfo]: https://docs.rs/actix-web/1.0.2/actix_web/struct.HttpRequest.html#method.match_info +[pathget]: https://docs.rs/actix-web/1.0.2/actix_web/dev/struct.Path.html#method.get +[pathstruct]: https://docs.rs/actix-web/1.0.2/actix_web/dev/struct.Path.html +[query]: https://docs.rs/actix-web/1.0.2/actix_web/web/struct.Query.html +[urlfor]: https://docs.rs/actix-web/1.0.2/actix_web/struct.HttpRequest.html#method.url_for +[urlobj]: https://docs.rs/url/1.7.2/url/struct.Url.html +[guardtrait]: https://docs.rs/actix-web/1.0.2/actix_web/guard/trait.Guard.html +[guardfuncs]: https://docs.rs/actix-web/1.0.2/actix_web/guard/index.html#functions +[requestextensions]: https://docs.rs/actix-web/1.0.2/actix_web/struct.HttpRequest.html#method.extensions +[implfromrequest]: https://docs.rs/actix-web/1.0.2/actix_web/trait.FromRequest.html +[implresponder]: https://docs.rs/actix-web/1.0.2/actix_web/trait.Responder.html +[pathextractor]: ../extractors diff --git a/content/docs/websockets.md b/content/docs/websockets.md index b284a36..d7e0f20 100644 --- a/content/docs/websockets.md +++ b/content/docs/websockets.md @@ -4,47 +4,21 @@ menu: docs_proto weight: 240 --- -Actix supports WebSockets out-of-the-box. It is possible to convert a request's `Payload` -to a stream of [*ws::Message*](../../actix-web/actix_web/ws/enum.Message.html) with -a [*ws::WsStream*](../../actix-web/actix_web/ws/struct.WsStream.html) and then use stream -combinators to handle actual messages, but it is simpler to handle websocket communications -with an http actor. +Actix-web supports WebSockets with the `actix-web-actors` crate. It is possible to convert a +request's `Payload` to a stream of [*ws::Message*][message] with a [*web::Payload*][payload] +and then use stream combinators to handle actual messages, but it is simpler to handle +websocket communications with an http actor. The following is an example of a simple websocket echo server: -```rust -use actix::*; -use actix_web::*; +{{< include-example example="websockets" file="main.rs" section="websockets" >}} -/// Define http actor -struct Ws; - -impl Actor for Ws { - type Context = ws::WebsocketContext; -} - -/// Handler for ws::Message message -impl StreamHandler for Ws { - - fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { - match msg { - ws::Message::Ping(msg) => ctx.pong(&msg), - ws::Message::Text(text) => ctx.text(text), - ws::Message::Binary(bin) => ctx.binary(bin), - _ => (), - } - } -} - -fn main() { - App::new() - .resource("/ws/", |r| r.f(|req| ws::start(req, Ws))) - .finish(); -} -``` - -> A simple websocket echo server example is available in the -> [examples directory](https://github.com/actix/examples/tree/master/websocket/). +> A simple websocket echo server example is available in the [examples directory][examples]. > An example chat server with the ability to chat over a websocket or tcp connection -> is available in [websocket-chat directory](https://github.com/actix/examples/tree/master/websocket-chat/) +> is available in [websocket-chat directory][chat] + +[message]: https://docs.rs/actix-web-actors/1.0.0/actix_web_actors/ws/enum.Message.html +[payload]: https://docs.rs/actix-web/1.0.2/actix_web/web/struct.Payload.html +[examples]: https://github.com/actix/examples/tree/master/websocket/ +[chat]: https://github.com/actix/examples/tree/master/websocket-chat/ diff --git a/content/docs/whatis.md b/content/docs/whatis.md index 0b60368..67765d6 100644 --- a/content/docs/whatis.md +++ b/content/docs/whatis.md @@ -7,14 +7,14 @@ weight: 100 # Actix is Multiple Things Actix is a few things. The base of it is a powerful actor system for Rust on -top of which the `actix-web` system is built. This is what you are most likely -going to work with. What `actix-web` gives you is a fun and very fast web +top of which the `actix-web` system was originally built. This is what you are most +likely going to work with. What `actix-web` gives you is a fun and very fast web development framework. We call `actix-web` a small and pragmatic framework. For all intents and purposes it's a microframework with a few twists. If you are already a Rust programmer you will probably find yourself at home quickly, but even if you are coming from -another programming language you should find actix-web easy to pick up. +another programming language you should find `actix-web` easy to pick up. An application developed with `actix-web` will expose an HTTP server contained within a native executable. You can either put this behind another HTTP server like @@ -23,5 +23,5 @@ server `actix-web` is powerful enough to provide HTTP 1 and HTTP 2 support as well as SSL/TLS. This makes it useful for building small services ready for distribution. -Most importantly: `actix-web` runs on Rust 1.26 or later and it works with -stable releases. +Most importantly: `actix-web` runs on Rust {{< rust-version "actix-web" >}} or later +and it works with stable releases. diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 78f920b..a9d2fb1 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -1,7 +1,30 @@ [workspace] members = [ "application", + "easy-form-handling", + "flexible-responders", "getting-started", + "main-example", + "powerful-extractors", + "request-routing", "server", "url-dispatch", + "responder-trait", + "either", + "extractors", + "autoreload", + "errors", + "requests", + "responses", + "middleware", + "static-files", + "http2", + "testing", + "async-handlers", + "websockets", + "request-handlers", +] +exclude = [ + "og_databases", + "sentry", ] diff --git a/examples/application/Cargo.toml b/examples/application/Cargo.toml index 9488e4d..0316c80 100644 --- a/examples/application/Cargo.toml +++ b/examples/application/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "application" -version = "0.7.0" +version = "1.0.0" +edition = "2018" workspace = "../" [dependencies] -actix-web = "0.7" +actix-web = "1.0" diff --git a/examples/application/src/app.rs b/examples/application/src/app.rs new file mode 100644 index 0000000..7dadc5c --- /dev/null +++ b/examples/application/src/app.rs @@ -0,0 +1,14 @@ +// +use actix_web::{web, App, Responder}; + +fn index() -> impl Responder { + "Hello world!" +} + +#[rustfmt::skip] +pub fn main() { + App::new().service( + web::scope("/app") + .route("/index.html", web::get().to(index))); +} +// diff --git a/examples/application/src/combine.rs b/examples/application/src/combine.rs new file mode 100644 index 0000000..0ce5a88 --- /dev/null +++ b/examples/application/src/combine.rs @@ -0,0 +1,25 @@ +use actix_web::{web, App, HttpResponse, HttpServer}; + +// +struct State1; +struct State2; + +#[rustfmt::skip] +pub fn main() { + HttpServer::new(|| { + App::new() + .service( + web::scope("/app1") + .data(State1) + .route("/", web::to(|| HttpResponse::Ok()))) + .service( + web::scope("/app2") + .data(State2) + .route("/", web::to(|| HttpResponse::Ok()))) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// diff --git a/examples/application/src/config.rs b/examples/application/src/config.rs new file mode 100644 index 0000000..6d1b9a3 --- /dev/null +++ b/examples/application/src/config.rs @@ -0,0 +1,34 @@ +// +use actix_web::{web, App, HttpResponse, HttpServer}; + +// this function could be located in different module +fn scoped_config(cfg: &mut web::ServiceConfig) { + cfg.service( + web::resource("/test") + .route(web::get().to(|| HttpResponse::Ok().body("test"))) + .route(web::head().to(|| HttpResponse::MethodNotAllowed())), + ); +} + +// this function could be located in different module +fn config(cfg: &mut web::ServiceConfig) { + cfg.service( + web::resource("/app") + .route(web::get().to(|| HttpResponse::Ok().body("app"))) + .route(web::head().to(|| HttpResponse::MethodNotAllowed())), + ); +} + +pub fn main() { + HttpServer::new(|| { + App::new() + .configure(config) + .service(web::scope("/api").configure(scoped_config)) + .route("/", web::get().to(|| HttpResponse::Ok().body("/"))) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// diff --git a/examples/application/src/main.rs b/examples/application/src/main.rs index 726f857..a8fe4e1 100644 --- a/examples/application/src/main.rs +++ b/examples/application/src/main.rs @@ -1,42 +1,22 @@ -#![allow(unused)] -extern crate actix_web; -use actix_web::{http::Method, server, App, HttpRequest, HttpResponse, Responder}; +use actix_web::{web, App, HttpResponse}; -mod state; -mod vh; - -fn make_app() { -// -fn index(req: &HttpRequest) -> impl Responder { - "Hello world!" -} - -let app = App::new() - .prefix("/app") - .resource("/index.html", |r| r.method(Method::GET).f(index)) - .finish() -// -; -} - -fn run_server() { -// -let server = server::new(|| { - vec![ - App::new() - .prefix("/app1") - .resource("/", |r| r.f(|r| HttpResponse::Ok())), - App::new() - .prefix("/app2") - .resource("/", |r| r.f(|r| HttpResponse::Ok())), - App::new().resource("/", |r| r.f(|r| HttpResponse::Ok())), - ] -}); -// -} +pub mod app; +pub mod combine; +pub mod config; +pub mod scope; +pub mod state; +pub mod vh; +#[rustfmt::skip] +// fn main() { - make_app(); - run_server(); - state::test(); + App::new() + .service( + web::scope("/app1") + .route("/", web::to(|| HttpResponse::Ok()))) + .service( + web::scope("/app2") + .route("/", web::to(|| HttpResponse::Ok()))) + .route("/", web::to(|| HttpResponse::Ok())); } +// diff --git a/examples/application/src/scope.rs b/examples/application/src/scope.rs new file mode 100644 index 0000000..b9c5742 --- /dev/null +++ b/examples/application/src/scope.rs @@ -0,0 +1,15 @@ +use actix_web::{web, App, HttpRequest, Responder}; + +fn show_users(_req: HttpRequest) -> impl Responder { + unimplemented!() +} + +#[rustfmt::skip] +// +pub fn main() { + App::new() + .service( + web::scope("/users") + .route("/show", web::get().to(show_users))); +} +// diff --git a/examples/application/src/state.rs b/examples/application/src/state.rs index 062006c..6f5c278 100644 --- a/examples/application/src/state.rs +++ b/examples/application/src/state.rs @@ -1,71 +1,42 @@ // -use actix_web::{http, App, HttpRequest}; +use actix_web::{web, App, HttpServer}; use std::cell::Cell; // This struct represents state struct AppState { - counter: Cell, + counter: Cell, } -fn index(req: &HttpRequest) -> String { - let count = req.state().counter.get() + 1; // <- get count - req.state().counter.set(count); // <- store new count in state +fn index(data: web::Data) -> String { + let count = data.counter.get() + 1; // <- get count + data.counter.set(count); // <- store new count in state format!("Request number: {}", count) // <- response with count } // -fn make_app() { // -App::with_state(AppState { counter: Cell::new(0) }) - .resource("/", |r| r.method(http::Method::GET).f(index)) - .finish() -// -; +fn _main() { + App::new() + .data(AppState { + counter: Cell::new(0), + }) + .route("/", web::get().to(index)); } +// -fn start_app() { // -server::new(|| { - App::with_state(AppState { counter: Cell::new(0) }) - .resource("/", |r| r.method(http::Method::GET).f(index)) -}).bind("127.0.0.1:8080") +pub fn main() { + HttpServer::new(|| { + App::new() + .data(AppState { + counter: Cell::new(0), + }) + .route("/", web::get().to(index)) + }) + .bind("127.0.0.1:8088") .unwrap() .run() + .unwrap(); +} // -; -} - -use actix_web::{server, HttpResponse}; -use std::thread; - -fn combine() { - thread::spawn(|| { -// -struct State1; -struct State2; - -fn main() { - server::new(|| { - vec![ - App::with_state(State1) - .prefix("/app1") - .resource("/", |r| r.f(|r| HttpResponse::Ok())) - .boxed(), - App::with_state(State2) - .prefix("/app2") - .resource("/", |r| r.f(|r| HttpResponse::Ok())) - .boxed(), - ] - }).bind("127.0.0.1:8080") - .unwrap() - .run() -} -// - }); -} - -pub fn test() { - make_app(); - combine(); -} diff --git a/examples/application/src/vh.rs b/examples/application/src/vh.rs index 12d3a56..bd8e34e 100644 --- a/examples/application/src/vh.rs +++ b/examples/application/src/vh.rs @@ -1,20 +1,24 @@ -#![allow(unused)] -use actix_web::{http::Method, pred, server, App, HttpRequest, HttpResponse, Responder}; +use actix_web::{guard, web, App, HttpResponse, HttpServer}; // -fn main() { - let server = server::new(|| { - vec![ - App::new() - .filter(pred::Host("www.rust-lang.org")) - .resource("/", |r| r.f(|r| HttpResponse::Ok())), - App::new() - .filter(pred::Host("users.rust-lang.org")) - .resource("/", |r| r.f(|r| HttpResponse::Ok())), - App::new().resource("/", |r| r.f(|r| HttpResponse::Ok())), - ] - }); - - server.run(); +pub fn main() { + HttpServer::new(|| { + App::new() + .service( + web::scope("/") + .guard(guard::Header("Host", "www.rust-lang.org")) + .route("", web::to(|| HttpResponse::Ok().body("www"))), + ) + .service( + web::scope("/") + .guard(guard::Header("Host", "users.rust-lang.org")) + .route("", web::to(|| HttpResponse::Ok().body("user"))), + ) + .route("/", web::to(|| HttpResponse::Ok())) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); } // diff --git a/examples/async-handlers/Cargo.toml b/examples/async-handlers/Cargo.toml new file mode 100644 index 0000000..afdf0dc --- /dev/null +++ b/examples/async-handlers/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "async-handlers" +version = "1.0.0" +edition = "2018" + +[dependencies] +actix-web = "1.0" +futures = "0.1" +bytes = "0.4" diff --git a/examples/async-handlers/src/async_stream.rs b/examples/async-handlers/src/async_stream.rs new file mode 100644 index 0000000..ddc6ad6 --- /dev/null +++ b/examples/async-handlers/src/async_stream.rs @@ -0,0 +1,28 @@ +fn is_error() -> bool { + false +} + +// +use actix_web::{error, Error, HttpResponse}; +use futures::future::{result, Future}; + +fn index() -> Result>, Error> { + if is_error() { + Err(error::ErrorBadRequest("bad request")) + } else { + Ok(Box::new(result(Ok(HttpResponse::Ok() + .content_type("text/html") + .body("Hello!"))))) + } +} +// + +pub fn main() { + use actix_web::{web, App, HttpServer}; + + HttpServer::new(|| App::new().route("/", web::to_async(index))) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} diff --git a/examples/async-handlers/src/main.rs b/examples/async-handlers/src/main.rs new file mode 100644 index 0000000..05a4ff4 --- /dev/null +++ b/examples/async-handlers/src/main.rs @@ -0,0 +1,30 @@ +pub mod async_stream; +pub mod stream; +// +use actix_web::{Error, HttpResponse}; +use futures::future::{ok, Future}; + +fn index() -> Box> { + Box::new(ok::<_, Error>( + HttpResponse::Ok().content_type("text/html").body("Hello!"), + )) +} + +fn index2() -> Box> { + Box::new(ok::<_, Error>("Welcome!")) +} + +fn main() { + use actix_web::{web, App, HttpServer}; + + HttpServer::new(|| { + App::new() + .route("/async", web::to_async(index)) + .route("/", web::to_async(index2)) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// diff --git a/examples/async-handlers/src/stream.rs b/examples/async-handlers/src/stream.rs new file mode 100644 index 0000000..79424cd --- /dev/null +++ b/examples/async-handlers/src/stream.rs @@ -0,0 +1,23 @@ +// +use actix_web::{Error, HttpResponse}; +use bytes::Bytes; +use futures::stream::once; + +fn index() -> HttpResponse { + let body = once::(Ok(Bytes::from_static(b"test"))); + + HttpResponse::Ok() + .content_type("application/json") + .streaming(Box::new(body)) +} + +pub fn main() { + use actix_web::{web, App, HttpServer}; + + HttpServer::new(|| App::new().route("/async", web::to_async(index))) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// diff --git a/examples/autoreload/Cargo.toml b/examples/autoreload/Cargo.toml new file mode 100644 index 0000000..55b4b62 --- /dev/null +++ b/examples/autoreload/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "autoreload" +version = "1.0.0" +edition = "2018" + +[dependencies] +actix-web = "1.0" +listenfd = "0.3" diff --git a/examples/autoreload/src/main.rs b/examples/autoreload/src/main.rs new file mode 100644 index 0000000..42ed302 --- /dev/null +++ b/examples/autoreload/src/main.rs @@ -0,0 +1,21 @@ +// +use actix_web::{web, App, HttpRequest, HttpServer, Responder}; +use listenfd::ListenFd; + +fn index(_req: HttpRequest) -> impl Responder { + "Hello World!" +} + +fn main() { + let mut listenfd = ListenFd::from_env(); + let mut server = HttpServer::new(|| App::new().route("/", web::get().to(index))); + + server = if let Some(l) = listenfd.take_tcp_listener(0).unwrap() { + server.listen(l).unwrap() + } else { + server.bind("127.0.0.1:3000").unwrap() + }; + + server.run().unwrap(); +} +// diff --git a/examples/easy-form-handling/Cargo.toml b/examples/easy-form-handling/Cargo.toml new file mode 100644 index 0000000..bbf8d22 --- /dev/null +++ b/examples/easy-form-handling/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "easy-form-handling" +version = "1.0.0" +edition = "2018" + +[dependencies] +actix-web = "1.0" +serde = "1.0" diff --git a/examples/easy-form-handling/src/main.rs b/examples/easy-form-handling/src/main.rs new file mode 100644 index 0000000..84f577a --- /dev/null +++ b/examples/easy-form-handling/src/main.rs @@ -0,0 +1,32 @@ +// +use actix_web::{web, App, HttpResponse, HttpServer, Responder}; +use serde::Deserialize; + +#[derive(Deserialize)] +struct Register { + username: String, + country: String, +} + +fn index() -> HttpResponse { + HttpResponse::Ok() + .content_type("text/html; charset=utf-8") + .body(include_str!("../static/form.html")) +} + +fn register(form: web::Form) -> impl Responder { + format!("Hello {} from {}!", form.username, form.country) +} + +fn main() { + HttpServer::new(|| { + App::new() + .route("/", web::get().to(index)) + .route("/register", web::post().to(register)) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// diff --git a/examples/easy-form-handling/static/form.html b/examples/easy-form-handling/static/form.html new file mode 100644 index 0000000..61ffceb --- /dev/null +++ b/examples/easy-form-handling/static/form.html @@ -0,0 +1,28 @@ + + + + + Forms + + + +

Its a form.

+ +
+ + + + + + + +
+ + + diff --git a/examples/either/Cargo.toml b/examples/either/Cargo.toml new file mode 100644 index 0000000..9f8a8f8 --- /dev/null +++ b/examples/either/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "either" +version = "1.0.0" +edition = "2018" + +[dependencies] +actix-web = "1.0" +futures = "0.1" diff --git a/examples/either/src/main.rs b/examples/either/src/main.rs new file mode 100644 index 0000000..3c6af05 --- /dev/null +++ b/examples/either/src/main.rs @@ -0,0 +1,35 @@ +// +use actix_web::{Either, Error, HttpResponse}; +use futures::future::{ok, Future}; + +type RegisterResult = + Either>>; + +fn index() -> RegisterResult { + if is_a_variant() { + // <- choose variant A + Either::A(HttpResponse::BadRequest().body("Bad data")) + } else { + Either::B( + // <- variant B + Box::new(ok(HttpResponse::Ok() + .content_type("text/html") + .body("Hello!".to_string()))), + ) + } +} + +fn main() { + use actix_web::{web, App, HttpServer}; + + HttpServer::new(|| App::new().route("/", web::get().to(index))) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// + +fn is_a_variant() -> bool { + true +} diff --git a/examples/errors/Cargo.toml b/examples/errors/Cargo.toml new file mode 100644 index 0000000..0b605e2 --- /dev/null +++ b/examples/errors/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "my_errors" +version = "1.0.0" +edition = "2018" + +[dependencies] +actix-web = "1.0" +failure = "0.1" +env_logger = "0.6" +log = "0.4" diff --git a/examples/errors/src/helpers.rs b/examples/errors/src/helpers.rs new file mode 100644 index 0000000..6dc01fd --- /dev/null +++ b/examples/errors/src/helpers.rs @@ -0,0 +1,25 @@ +use actix_web::{web, App}; +// +use actix_web::{error, Result}; + +#[derive(Debug)] +struct MyError { + name: &'static str, +} + +pub fn index() -> Result<&'static str> { + let result: Result<&'static str, MyError> = Err(MyError { name: "test error" }); + + Ok(result.map_err(|e| error::ErrorBadRequest(e.name))?) +} +// + +pub fn main() { + use actix_web::HttpServer; + + HttpServer::new(|| App::new().route("/", web::get().to(index))) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} diff --git a/examples/errors/src/logging.rs b/examples/errors/src/logging.rs new file mode 100644 index 0000000..9f5b880 --- /dev/null +++ b/examples/errors/src/logging.rs @@ -0,0 +1,38 @@ +// +use actix_web::{error, Result}; +use failure::Fail; +use log::debug; + +#[derive(Fail, Debug)] +#[fail(display = "my error")] +pub struct MyError { + name: &'static str, +} + +// Use default implementation for `error_response()` method +impl error::ResponseError for MyError {} + +pub fn index() -> Result<&'static str, MyError> { + let err = MyError { name: "test error" }; + debug!("{}", err); + Err(err) +} + +pub fn main() { + use actix_web::{middleware::Logger, web, App, HttpServer}; + + std::env::set_var("RUST_LOG", "my_errors=debug,actix_web=info"); + std::env::set_var("RUST_BACKTRACE", "1"); + env_logger::init(); + + HttpServer::new(|| { + App::new() + .wrap(Logger::default()) + .route("/", web::get().to(index)) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// diff --git a/examples/errors/src/main.rs b/examples/errors/src/main.rs new file mode 100644 index 0000000..c9e4984 --- /dev/null +++ b/examples/errors/src/main.rs @@ -0,0 +1,33 @@ +pub mod helpers; +pub mod logging; +pub mod override_error; +pub mod recommend_one; +pub mod recommend_two; + +// +use actix_web::{error, Result}; +use failure::Fail; + +#[derive(Fail, Debug)] +#[fail(display = "my error")] +pub struct MyError { + name: &'static str, +} + +// Use default implementation for `error_response()` method +impl error::ResponseError for MyError {} + +fn index() -> Result<&'static str, MyError> { + Err(MyError { name: "test" }) +} +// + +pub fn main() { + use actix_web::{web, App, HttpServer}; + + HttpServer::new(|| App::new().route("/", web::get().to(index))) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} diff --git a/examples/errors/src/override_error.rs b/examples/errors/src/override_error.rs new file mode 100644 index 0000000..0f354cd --- /dev/null +++ b/examples/errors/src/override_error.rs @@ -0,0 +1,54 @@ +use actix_web::{web, App}; +// +use actix_web::{error, http, HttpResponse}; +use failure::Fail; + +#[derive(Fail, Debug)] +enum MyError { + #[fail(display = "internal error")] + InternalError, + #[fail(display = "bad request")] + BadClientData, + #[fail(display = "timeout")] + Timeout, +} + +impl error::ResponseError for MyError { + fn error_response(&self) -> HttpResponse { + match *self { + MyError::InternalError => { + HttpResponse::new(http::StatusCode::INTERNAL_SERVER_ERROR) + } + MyError::BadClientData => HttpResponse::new(http::StatusCode::BAD_REQUEST), + MyError::Timeout => HttpResponse::new(http::StatusCode::GATEWAY_TIMEOUT), + } + } +} + +fn index() -> Result<&'static str, MyError> { + Err(MyError::BadClientData) +} +// + +fn error2() -> Result<&'static str, MyError> { + Err(MyError::InternalError) +} + +fn error3() -> Result<&'static str, MyError> { + Err(MyError::Timeout) +} + +pub fn main() { + use actix_web::HttpServer; + + HttpServer::new(|| { + App::new() + .route("/", web::get().to(index)) + .route("/e2", web::get().to(error2)) + .route("/e3", web::get().to(error3)) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} diff --git a/examples/errors/src/recommend_one.rs b/examples/errors/src/recommend_one.rs new file mode 100644 index 0000000..5e8e4f2 --- /dev/null +++ b/examples/errors/src/recommend_one.rs @@ -0,0 +1,35 @@ +// +use actix_web::{error, http, HttpResponse}; +use failure::Fail; + +#[derive(Fail, Debug)] +enum UserError { + #[fail(display = "Validation error on field: {}", field)] + ValidationError { field: String }, +} + +impl error::ResponseError for UserError { + fn error_response(&self) -> HttpResponse { + match *self { + UserError::ValidationError { .. } => { + HttpResponse::new(http::StatusCode::BAD_REQUEST) + } + } + } +} +// +fn index() -> Result<&'static str, UserError> { + Err(UserError::ValidationError { + field: "bad stuff".to_string(), + }) +} + +pub fn main() { + use actix_web::{web, App, HttpServer}; + + HttpServer::new(|| App::new().route("/", web::get().to(index))) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} diff --git a/examples/errors/src/recommend_two.rs b/examples/errors/src/recommend_two.rs new file mode 100644 index 0000000..cbb34de --- /dev/null +++ b/examples/errors/src/recommend_two.rs @@ -0,0 +1,39 @@ +// +use actix_web::{error, http, HttpResponse}; +use failure::Fail; + +#[derive(Fail, Debug)] +enum UserError { + #[fail(display = "An internal error occurred. Please try again later.")] + InternalError, +} + +impl error::ResponseError for UserError { + fn error_response(&self) -> HttpResponse { + match *self { + UserError::InternalError => { + HttpResponse::new(http::StatusCode::INTERNAL_SERVER_ERROR) + } + } + } +} + +fn index() -> Result<&'static str, UserError> { + do_thing_that_failes().map_err(|_e| UserError::InternalError)?; + Ok("success!") +} +// + +fn do_thing_that_failes() -> Result<(), std::io::Error> { + Err(std::io::Error::new(std::io::ErrorKind::Other, "some error")) +} + +pub fn main() { + use actix_web::{web, App, HttpServer}; + + HttpServer::new(|| App::new().route("/", web::get().to(index))) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} diff --git a/examples/extractors/Cargo.toml b/examples/extractors/Cargo.toml new file mode 100644 index 0000000..0e31c27 --- /dev/null +++ b/examples/extractors/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "extractors" +version = "1.0.0" +edition = "2018" + +[dependencies] +actix-web = "1.0" +serde = "1.0" +futures = "0.1" +serde_json = "1.0" diff --git a/examples/extractors/src/form.rs b/examples/extractors/src/form.rs new file mode 100644 index 0000000..daf58cf --- /dev/null +++ b/examples/extractors/src/form.rs @@ -0,0 +1,26 @@ +//
+use actix_web::{web, Result}; +use serde::Deserialize; + +#[derive(Deserialize)] +struct FormData { + username: String, +} + +/// extract form data using serde +/// this handler gets called only if the content type is *x-www-form-urlencoded* +/// and the content of the request could be deserialized to a `FormData` struct +fn index(form: web::Form) -> Result { + Ok(format!("Welcome {}!", form.username)) +} +// + +pub fn main() { + use actix_web::{App, HttpServer}; + + HttpServer::new(|| App::new().route("/", web::post().to(index))) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} diff --git a/examples/extractors/src/json_one.rs b/examples/extractors/src/json_one.rs new file mode 100644 index 0000000..d1600ce --- /dev/null +++ b/examples/extractors/src/json_one.rs @@ -0,0 +1,24 @@ +// +use actix_web::{web, Result}; +use serde::Deserialize; + +#[derive(Deserialize)] +struct Info { + username: String, +} + +/// deserialize `Info` from request's body +fn index(info: web::Json) -> Result { + Ok(format!("Welcome {}!", info.username)) +} +// + +pub fn main() { + use actix_web::{App, HttpServer}; + + HttpServer::new(|| App::new().route("/", web::post().to(index))) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} diff --git a/examples/extractors/src/json_two.rs b/examples/extractors/src/json_two.rs new file mode 100644 index 0000000..a61a6cf --- /dev/null +++ b/examples/extractors/src/json_two.rs @@ -0,0 +1,42 @@ +// +use actix_web::{error, web, FromRequest, HttpResponse, Responder}; +use serde::Deserialize; + +#[derive(Deserialize)] +struct Info { + username: String, +} + +/// deserialize `Info` from request's body, max payload size is 4kb +fn index(info: web::Json) -> impl Responder { + format!("Welcome {}!", info.username) +} + +pub fn main() { + use actix_web::{App, HttpServer}; + + HttpServer::new(|| { + App::new().service( + web::resource("/") + .data( + // change json extractor configuration + web::Json::::configure(|cfg| { + cfg.limit(4096).error_handler(|err, _req| { + // <- create custom error response + error::InternalError::from_response( + err, + HttpResponse::Conflict().finish(), + ) + .into() + }) + }), + ) + .route(web::post().to(index)), + ) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// diff --git a/examples/extractors/src/main.rs b/examples/extractors/src/main.rs new file mode 100644 index 0000000..0925a79 --- /dev/null +++ b/examples/extractors/src/main.rs @@ -0,0 +1,49 @@ +use actix_web::{web, App, FromRequest, HttpRequest, HttpServer, Responder}; +use futures::future::Future; +use serde::Deserialize; + +// pub mod custom_handler; +pub mod form; +pub mod json_one; +pub mod json_two; +pub mod multiple; +pub mod path_one; +pub mod path_three; +pub mod path_two; +pub mod query; + +#[derive(Deserialize, Debug)] +struct MyInfo { + username: String, + id: u32, +} + +// +fn index(path: web::Path<(String, String)>, json: web::Json) -> impl Responder { + format!("{} {} {} {}", path.0, path.1, json.id, json.username) +} +// + +// +fn extract(req: HttpRequest) -> impl Responder { + let params = web::Path::<(String, String)>::extract(&req).unwrap(); + + let info = web::Json::::extract(&req) + .wait() + .expect("Err with reading json."); + + format!("{} {} {} {}", params.0, params.1, info.username, info.id) +} +// + +fn main() { + HttpServer::new(|| { + App::new() + .route("/{name}/{id}", web::post().to(index)) + .route("/{name}/{id}/extract", web::post().to(extract)) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} diff --git a/examples/extractors/src/multiple.rs b/examples/extractors/src/multiple.rs new file mode 100644 index 0000000..ed9cad1 --- /dev/null +++ b/examples/extractors/src/multiple.rs @@ -0,0 +1,31 @@ +// +use actix_web::web; +use serde::Deserialize; + +#[derive(Deserialize)] +struct Info { + username: String, +} + +fn index((path, query): (web::Path<(u32, String)>, web::Query)) -> String { + format!( + "Welcome {}, friend {}, useri {}!", + query.username, path.1, path.0 + ) +} + +pub fn main() { + use actix_web::{App, HttpServer}; + + HttpServer::new(|| { + App::new().route( + "/users/{userid}/{friend}", // <- define path parameters + web::get().to(index), + ) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// diff --git a/examples/extractors/src/path_one.rs b/examples/extractors/src/path_one.rs new file mode 100644 index 0000000..1440b8e --- /dev/null +++ b/examples/extractors/src/path_one.rs @@ -0,0 +1,25 @@ +// +use actix_web::{web, Result}; + +/// extract path info from "/users/{userid}/{friend}" url +/// {userid} - - deserializes to a u32 +/// {friend} - deserializes to a String +fn index(info: web::Path<(u32, String)>) -> Result { + Ok(format!("Welcome {}, userid {}!", info.1, info.0)) +} + +pub fn main() { + use actix_web::{App, HttpServer}; + + HttpServer::new(|| { + App::new().route( + "/users/{userid}/{friend}", // <- define path parameters + web::get().to(index), + ) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// diff --git a/examples/extractors/src/path_three.rs b/examples/extractors/src/path_three.rs new file mode 100644 index 0000000..68badaa --- /dev/null +++ b/examples/extractors/src/path_three.rs @@ -0,0 +1,25 @@ +use actix_web::{web, HttpRequest, Result}; + +// +fn index(req: HttpRequest) -> Result { + let name: String = req.match_info().get("friend").unwrap().parse().unwrap(); + let userid: i32 = req.match_info().query("userid").parse().unwrap(); + + Ok(format!("Welcome {}, userid {}!", name, userid)) +} + +pub fn main() { + use actix_web::{App, HttpServer}; + + HttpServer::new(|| { + App::new().route( + "/users/{userid}/{friend}", // <- define path parameters + web::get().to(index), + ) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// diff --git a/examples/extractors/src/path_two.rs b/examples/extractors/src/path_two.rs new file mode 100644 index 0000000..ee5e357 --- /dev/null +++ b/examples/extractors/src/path_two.rs @@ -0,0 +1,30 @@ +// +use actix_web::{web, Result}; +use serde::Deserialize; + +#[derive(Deserialize)] +struct Info { + userid: u32, + friend: String, +} + +/// extract path info using serde +fn index(info: web::Path) -> Result { + Ok(format!("Welcome {}, userid {}!", info.friend, info.userid)) +} + +pub fn main() { + use actix_web::{App, HttpServer}; + + HttpServer::new(|| { + App::new().route( + "/users/{userid}/{friend}", // <- define path parameters + web::get().to(index), + ) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// diff --git a/examples/extractors/src/query.rs b/examples/extractors/src/query.rs new file mode 100644 index 0000000..74575ab --- /dev/null +++ b/examples/extractors/src/query.rs @@ -0,0 +1,24 @@ +// +use actix_web::web; +use serde::Deserialize; + +#[derive(Deserialize)] +struct Info { + username: String, +} + +// this handler get called only if the request's query contains `username` field +fn index(info: web::Query) -> String { + format!("Welcome {}!", info.username) +} +// + +pub fn main() { + use actix_web::{App, HttpServer}; + + HttpServer::new(|| App::new().route("/", web::get().to(index))) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} diff --git a/examples/flexible-responders/Cargo.toml b/examples/flexible-responders/Cargo.toml new file mode 100644 index 0000000..5aec859 --- /dev/null +++ b/examples/flexible-responders/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "flexible-responders" +version = "1.0.0" +edition = "2018" + +[dependencies] +actix-web = "1.0" +serde = "1.0" diff --git a/examples/flexible-responders/src/main.rs b/examples/flexible-responders/src/main.rs new file mode 100644 index 0000000..fd56f1e --- /dev/null +++ b/examples/flexible-responders/src/main.rs @@ -0,0 +1,29 @@ +use actix_web::{web, App, HttpServer, Responder}; +use serde::Serialize; + +// +#[derive(Serialize)] +struct Measurement { + temperature: f32, +} + +fn hello_world() -> impl Responder { + "Hello World!" +} + +fn current_temperature() -> impl Responder { + web::Json(Measurement { temperature: 42.3 }) +} + +fn main() { + HttpServer::new(|| { + App::new() + .service(web::resource("/").to(hello_world)) + .service(web::resource("/temp").to(current_temperature)) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// diff --git a/examples/getting-started/Cargo.toml b/examples/getting-started/Cargo.toml index 226c730..1632b62 100644 --- a/examples/getting-started/Cargo.toml +++ b/examples/getting-started/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "getting-started" -version = "0.7.0" +version = "1.0.0" +edition = "2018" workspace = "../" [dependencies] -actix-web = "0.7" +actix-web = "1.0" diff --git a/examples/getting-started/src/main.rs b/examples/getting-started/src/main.rs index 494bec4..4b2cfa6 100644 --- a/examples/getting-started/src/main.rs +++ b/examples/getting-started/src/main.rs @@ -1,16 +1,25 @@ // -extern crate actix_web; -use actix_web::{server, App, HttpRequest}; +use actix_web::{web, App, HttpResponse, HttpServer, Responder}; -fn index(_req: &HttpRequest) -> &'static str { - "Hello world!" +fn index() -> impl Responder { + HttpResponse::Ok().body("Hello world!") +} + +fn index2() -> impl Responder { + HttpResponse::Ok().body("Hello world again!") } // + //
fn main() { - server::new(|| App::new().resource("/", |r| r.f(index))) - .bind("127.0.0.1:8088") - .unwrap() - .run(); + HttpServer::new(|| { + App::new() + .route("/", web::get().to(index)) + .route("/again", web::get().to(index2)) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); } //
diff --git a/examples/http2/Cargo.toml b/examples/http2/Cargo.toml new file mode 100644 index 0000000..5749e20 --- /dev/null +++ b/examples/http2/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "http2" +version = "1.0.0" +edition = "2018" + +[dependencies] +actix-web = { version = "1.0", features = ["ssl"] } +openssl = { version = "0.10", features = ["v110"] } diff --git a/examples/http2/src/main.rs b/examples/http2/src/main.rs new file mode 100644 index 0000000..a985211 --- /dev/null +++ b/examples/http2/src/main.rs @@ -0,0 +1,25 @@ +//
+use actix_web::{web, App, HttpRequest, HttpServer, Responder}; +use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; + +fn index(_req: HttpRequest) -> impl Responder { + "Hello." +} + +fn main() { + // load ssl keys + // to create a self-signed temporary cert for testing: + // `openssl req -x509 -newkey rsa:4096 -nodes -keyout key.pem -out cert.pem -days 365 -subj '/CN=localhost'` + let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); + builder + .set_private_key_file("key.pem", SslFiletype::PEM) + .unwrap(); + builder.set_certificate_chain_file("cert.pem").unwrap(); + + HttpServer::new(|| App::new().route("/", web::get().to(index))) + .bind_ssl("127.0.0.1:8088", builder) + .unwrap() + .run() + .unwrap(); +} +//
diff --git a/examples/main-example/Cargo.toml b/examples/main-example/Cargo.toml new file mode 100644 index 0000000..03e0327 --- /dev/null +++ b/examples/main-example/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "main-example" +version = "1.0.0" +edition = "2018" + +[dependencies] +actix-web = "1.0" diff --git a/examples/main-example/src/main.rs b/examples/main-example/src/main.rs new file mode 100644 index 0000000..11f7025 --- /dev/null +++ b/examples/main-example/src/main.rs @@ -0,0 +1,20 @@ +// +use actix_web::{web, App, HttpRequest, HttpServer, Responder}; + +fn greet(req: HttpRequest) -> impl Responder { + let name = req.match_info().get("name").unwrap_or("World"); + format!("Hello {}!", &name) +} + +fn main() { + HttpServer::new(|| { + App::new() + .route("/", web::get().to(greet)) + .route("/{name}", web::get().to(greet)) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// diff --git a/examples/middleware/Cargo.toml b/examples/middleware/Cargo.toml new file mode 100644 index 0000000..3448545 --- /dev/null +++ b/examples/middleware/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "middleware" +version = "1.0.0" +edition = "2018" + +[dependencies] +actix-web = "1.0" +actix-service = "0.4" +actix-session = "0.1" +futures = "0.1" +env_logger = "0.6" diff --git a/examples/middleware/src/default_headers.rs b/examples/middleware/src/default_headers.rs new file mode 100644 index 0000000..d998a15 --- /dev/null +++ b/examples/middleware/src/default_headers.rs @@ -0,0 +1,24 @@ +// +use actix_web::{http, middleware, HttpResponse}; + +pub fn main() { + use actix_web::{web, App, HttpServer}; + + HttpServer::new(|| { + App::new() + .wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2")) + .service( + web::resource("/test") + .route(web::get().to(|| HttpResponse::Ok())) + .route( + web::method(http::Method::HEAD) + .to(|| HttpResponse::MethodNotAllowed()), + ), + ) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// diff --git a/examples/middleware/src/errorhandler.rs b/examples/middleware/src/errorhandler.rs new file mode 100644 index 0000000..dacef6e --- /dev/null +++ b/examples/middleware/src/errorhandler.rs @@ -0,0 +1,33 @@ +// +use actix_web::middleware::errhandlers::{ErrorHandlerResponse, ErrorHandlers}; +use actix_web::{dev, http, HttpResponse, Result}; + +fn render_500(mut res: dev::ServiceResponse) -> Result> { + res.response_mut().headers_mut().insert( + http::header::CONTENT_TYPE, + http::HeaderValue::from_static("Error"), + ); + Ok(ErrorHandlerResponse::Response(res)) +} + +pub fn main() { + use actix_web::{web, App, HttpServer}; + + HttpServer::new(|| { + App::new() + .wrap( + ErrorHandlers::new() + .handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500), + ) + .service( + web::resource("/test") + .route(web::get().to(|| HttpResponse::Ok())) + .route(web::head().to(|| HttpResponse::MethodNotAllowed())), + ) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// diff --git a/examples/middleware/src/logger.rs b/examples/middleware/src/logger.rs new file mode 100644 index 0000000..254e5d1 --- /dev/null +++ b/examples/middleware/src/logger.rs @@ -0,0 +1,21 @@ +// +use actix_web::middleware::Logger; +use env_logger; + +pub fn main() { + use actix_web::{App, HttpServer}; + + std::env::set_var("RUST_LOG", "actix_web=info"); + env_logger::init(); + + HttpServer::new(|| { + App::new() + .wrap(Logger::default()) + .wrap(Logger::new("%a %{User-Agent}i")) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// diff --git a/examples/middleware/src/main.rs b/examples/middleware/src/main.rs new file mode 100644 index 0000000..22ed711 --- /dev/null +++ b/examples/middleware/src/main.rs @@ -0,0 +1,82 @@ +pub mod default_headers; +pub mod errorhandler; +pub mod logger; +pub mod user_sessions; + +// +use actix_service::{Service, Transform}; +use actix_web::{dev::ServiceRequest, dev::ServiceResponse, Error}; +use futures::future::{ok, FutureResult}; +use futures::{Future, Poll}; + +// There are two steps in middleware processing. +// 1. Middleware initialization, middleware factory gets called with +// next service in chain as parameter. +// 2. Middleware's call method gets called with normal request. +pub struct SayHi; + +// Middleware factory is `Transform` trait from actix-service crate +// `S` - type of the next service +// `B` - type of response's body +impl Transform for SayHi +where + S: Service, Error = Error>, + S::Future: 'static, + B: 'static, +{ + type Request = ServiceRequest; + type Response = ServiceResponse; + type Error = Error; + type InitError = (); + type Transform = SayHiMiddleware; + type Future = FutureResult; + + fn new_transform(&self, service: S) -> Self::Future { + ok(SayHiMiddleware { service }) + } +} + +pub struct SayHiMiddleware { + service: S, +} + +impl Service for SayHiMiddleware +where + S: Service, Error = Error>, + S::Future: 'static, + B: 'static, +{ + type Request = ServiceRequest; + type Response = ServiceResponse; + type Error = Error; + type Future = Box>; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.service.poll_ready() + } + + fn call(&mut self, req: ServiceRequest) -> Self::Future { + println!("Hi from start. You requested: {}", req.path()); + + Box::new(self.service.call(req).and_then(|res| { + println!("Hi from response"); + Ok(res) + })) + } +} +// + +fn main() { + use actix_web::{web, App, HttpServer}; + + HttpServer::new(|| { + App::new().wrap(SayHi).service( + web::resource("/") + .to(|| "Hello, middleware! Check the console where the server is run."), + ) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} diff --git a/examples/middleware/src/user_sessions.rs b/examples/middleware/src/user_sessions.rs new file mode 100644 index 0000000..b4f99ff --- /dev/null +++ b/examples/middleware/src/user_sessions.rs @@ -0,0 +1,33 @@ +// +use actix_session::{CookieSession, Session}; +use actix_web::{web, App, Error, HttpResponse, HttpServer}; + +pub fn index(session: Session) -> Result { + // access session data + if let Some(count) = session.get::("counter")? { + session.set("counter", count + 1)?; + } else { + session.set("counter", 1)?; + } + + Ok(HttpResponse::Ok().body(format!( + "Count is {:?}!", + session.get::("counter")?.unwrap() + ))) +} + +pub fn main() { + HttpServer::new(|| { + App::new() + .wrap( + CookieSession::signed(&[0; 32]) // <- create cookie based session middleware + .secure(false), + ) + .service(web::resource("/").to(index)) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// diff --git a/examples/og_databases/Cargo.toml b/examples/og_databases/Cargo.toml new file mode 100644 index 0000000..cdf2ee3 --- /dev/null +++ b/examples/og_databases/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "og_databases" +version = "0.7.0" +edition = "2018" + +[dependencies] +actix-web = "0.7" diff --git a/examples/og_databases/src/main.rs b/examples/og_databases/src/main.rs new file mode 100644 index 0000000..c78d064 --- /dev/null +++ b/examples/og_databases/src/main.rs @@ -0,0 +1,98 @@ +// +// use actix::prelude::*; + +// struct DbExecutor(SqliteConnection); + +// impl Actor for DbExecutor { +// type Context = SyncContext; +// } +// + +// +// struct CreateUser { +// name: String, +// } + +// impl Message for CreateUser { +// type Result = Result; +// } +// + +// +// impl Handler for DbExecutor { +// type Result = Result; + +// fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Self::Result { +// use self::schema::users::dsl::*; + +// // Create insertion model +// let uuid = format!("{}", uuid::Uuid::new_v4()); +// let new_user = models::NewUser { +// id: &uuid, +// name: &msg.name, +// }; + +// // normal diesel operations +// diesel::insert_into(users) +// .values(&new_user) +// .execute(&self.0) +// .expect("Error inserting person"); + +// let mut items = users +// .filter(id.eq(&uuid)) +// .load::(&self.0) +// .expect("Error loading person"); + +// Ok(items.pop().unwrap()) +// } +// } +// + +//
+// /// This is state where we will store *DbExecutor* address. +// struct State { +// db: Addr, +// } + +// fn main() { +// let sys = actix::System::new("diesel-example"); + +// // Start 3 parallel db executors +// let addr = SyncArbiter::start(3, || { +// DbExecutor(SqliteConnection::establish("test.db").unwrap()) +// }); + +// // Start http server +// HttpServer::new(move || { +// App::with_state(State { db: addr.clone() }) +// .resource("/{name}", |r| r.method(Method::GET).a(index)) +// }) +// .bind("127.0.0.1:8080") +// .unwrap() +// .start() +// .unwrap(); + +// println!("Started http server: 127.0.0.1:8080"); +// let _ = sys.run(); +// } +//
+ +// +// /// Async handler +// fn index(req: &HttpRequest) -> Box> { +// let name = &req.match_info()["name"]; + +// // Send message to `DbExecutor` actor +// req.state() +// .db +// .send(CreateUser { +// name: name.to_owned(), +// }) +// .from_err() +// .and_then(|res| match res { +// Ok(user) => Ok(HttpResponse::Ok().json(user)), +// Err(_) => Ok(HttpResponse::InternalServerError().into()), +// }) +// .responder() +// } +// diff --git a/examples/powerful-extractors/Cargo.toml b/examples/powerful-extractors/Cargo.toml new file mode 100644 index 0000000..5f20144 --- /dev/null +++ b/examples/powerful-extractors/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "powerful-extractors" +version = "1.0.0" +edition = "2018" + +[dependencies] +actix-web = "1.0" +serde = "1.0" diff --git a/examples/powerful-extractors/src/main.rs b/examples/powerful-extractors/src/main.rs new file mode 100644 index 0000000..5d80d7f --- /dev/null +++ b/examples/powerful-extractors/src/main.rs @@ -0,0 +1,44 @@ +use actix_web::{web, App, HttpResponse, HttpServer, Responder}; +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, Serialize)] +struct Event { + id: Option, + timestamp: f64, + kind: String, + tags: Vec, +} + +fn store_in_db(timestamp: f64, kind: &str, tags: &[String]) -> Event { + // store item in db and get new_event + // use id to lookup item + Event { + id: Some(1), + timestamp, + kind: kind.to_string(), + tags: tags.to_vec(), + } +} + +fn capture_event(evt: web::Json) -> impl Responder { + let new_event = store_in_db(evt.timestamp, &evt.kind, &evt.tags); + format!("got event {}", new_event.id.unwrap()) +} + +fn index() -> HttpResponse { + HttpResponse::Ok() + .content_type("text/html; charset=utf-8") + .body(include_str!("../static/form.html")) +} + +fn main() { + HttpServer::new(|| { + App::new() + .route("/", web::get().to(index)) + .route("/event", web::post().to(capture_event)) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} diff --git a/examples/powerful-extractors/static/form.html b/examples/powerful-extractors/static/form.html new file mode 100644 index 0000000..a51229b --- /dev/null +++ b/examples/powerful-extractors/static/form.html @@ -0,0 +1,43 @@ + + + + + Forms + + + +

Submit Json

+ + + + + + diff --git a/examples/request-handlers/Cargo.toml b/examples/request-handlers/Cargo.toml new file mode 100644 index 0000000..325b659 --- /dev/null +++ b/examples/request-handlers/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "request-handlers" +version = "1.0.0" +edition = "2018" + +[dependencies] +actix-web = "1.0" diff --git a/examples/request-handlers/src/handlers_arc.rs b/examples/request-handlers/src/handlers_arc.rs new file mode 100644 index 0000000..cd9acbe --- /dev/null +++ b/examples/request-handlers/src/handlers_arc.rs @@ -0,0 +1,39 @@ +// +use actix_web::{web, Responder}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; + +#[derive(Clone)] +struct AppState { + count: Arc, +} + +fn show_count(data: web::Data) -> impl Responder { + format!("count: {}", data.count.load(Ordering::Relaxed)) +} + +fn add_one(data: web::Data) -> impl Responder { + data.count.fetch_add(1, Ordering::Relaxed); + + format!("count: {}", data.count.load(Ordering::Relaxed)) +} + +pub fn main() { + use actix_web::{App, HttpServer}; + + let data = AppState { + count: Arc::new(AtomicUsize::new(0)), + }; + + HttpServer::new(move || { + App::new() + .data(data.clone()) + .route("/", web::to(show_count)) + .route("/add", web::to(add_one)) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// diff --git a/examples/request-handlers/src/main.rs b/examples/request-handlers/src/main.rs new file mode 100644 index 0000000..9fb1734 --- /dev/null +++ b/examples/request-handlers/src/main.rs @@ -0,0 +1,40 @@ +pub mod handlers_arc; +// +use actix_web::{web, Responder}; +use std::cell::Cell; + +#[derive(Clone)] +struct AppState { + count: Cell, +} + +fn show_count(data: web::Data) -> impl Responder { + format!("count: {}", data.count.get()) +} + +fn add_one(data: web::Data) -> impl Responder { + let count = data.count.get(); + data.count.set(count + 1); + + format!("count: {}", data.count.get()) +} + +fn main() { + use actix_web::{App, HttpServer}; + + let data = AppState { + count: Cell::new(0), + }; + + HttpServer::new(move || { + App::new() + .data(data.clone()) + .route("/", web::to(show_count)) + .route("/add", web::to(add_one)) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// diff --git a/examples/request-routing/Cargo.toml b/examples/request-routing/Cargo.toml new file mode 100644 index 0000000..cdef3e6 --- /dev/null +++ b/examples/request-routing/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "request-routing" +version = "1.0.0" +edition = "2018" + +[dependencies] +actix-web = "1.0" diff --git a/examples/request-routing/src/main.rs b/examples/request-routing/src/main.rs new file mode 100644 index 0000000..8a20019 --- /dev/null +++ b/examples/request-routing/src/main.rs @@ -0,0 +1,23 @@ +// +use actix_web::{web, App, HttpRequest, HttpServer, Responder}; + +fn index(_req: HttpRequest) -> impl Responder { + "Hello from the index page." +} + +fn hello(path: web::Path) -> impl Responder { + format!("Hello {}!", &path) +} + +fn main() { + HttpServer::new(|| { + App::new() + .service(web::resource("/").to(index)) + .service(web::resource("/{name}").to(hello)) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// diff --git a/examples/requests/Cargo.toml b/examples/requests/Cargo.toml new file mode 100644 index 0000000..4d26310 --- /dev/null +++ b/examples/requests/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "requests" +version = "1.0.0" +edition = "2018" + +[dependencies] +serde = "1.0" +serde_json = "1.0" +actix-web = "1.0" +futures = "0.1" +bytes = "0.4" +actix-multipart = "0.1" diff --git a/examples/requests/src/json_two.rs b/examples/requests/src/json_two.rs new file mode 100644 index 0000000..5c0bfa8 --- /dev/null +++ b/examples/requests/src/json_two.rs @@ -0,0 +1,21 @@ +// // +// use actix_web::{error::Error, HttpRequest, HttpResponse}; +// use futures::Future; +// use serde::{Deserialize, Serialize}; + +// #[derive(Debug, Serialize, Deserialize)] +// struct MyObj { +// name: String, +// number: i32, +// } + +// pub fn index(req: HttpRequest) -> Box> { +// req.json() +// .from_err() +// .and_then(|val: MyObj| { +// println!("model: {:?}", val); +// Ok(HttpResponse::Ok().json(val)) // <- send response +// }) +// .responder() +// } +// // diff --git a/examples/requests/src/main.rs b/examples/requests/src/main.rs new file mode 100644 index 0000000..7d822e2 --- /dev/null +++ b/examples/requests/src/main.rs @@ -0,0 +1,28 @@ +pub mod json_two; +pub mod manual; +pub mod multipart; +pub mod streaming; +pub mod urlencoded; + +// +use actix_web::{web, App, HttpServer, Result}; +use serde::Deserialize; + +#[derive(Deserialize)] +struct Info { + username: String, +} + +/// extract `Info` using serde +fn index(info: web::Json) -> Result { + Ok(format!("Welcome {}!", info.username)) +} + +fn main() { + HttpServer::new(|| App::new().route("/", web::post().to(index))) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// diff --git a/examples/requests/src/manual.rs b/examples/requests/src/manual.rs new file mode 100644 index 0000000..44eeefc --- /dev/null +++ b/examples/requests/src/manual.rs @@ -0,0 +1,53 @@ +// +use actix_web::{error, web, App, Error, HttpResponse}; +use bytes::BytesMut; +use futures::{Future, Stream}; +use serde::{Deserialize, Serialize}; +use serde_json; + +#[derive(Serialize, Deserialize)] +struct MyObj { + name: String, + number: i32, +} + +const MAX_SIZE: usize = 262_144; // max payload size is 256k + +pub fn index_manual( + payload: web::Payload, +) -> impl Future { + // payload is a stream of Bytes objects + payload + // `Future::from_err` acts like `?` in that it coerces the error type from + // the future into the final error type + .from_err() + // `fold` will asynchronously read each chunk of the request body and + // call supplied closure, then it resolves to result of closure + .fold(BytesMut::new(), move |mut body, chunk| { + // limit max size of in-memory payload + if (body.len() + chunk.len()) > MAX_SIZE { + Err(error::ErrorBadRequest("overflow")) + } else { + body.extend_from_slice(&chunk); + Ok(body) + } + }) + // `Future::and_then` can be used to merge an asynchronous workflow with a + // synchronous workflow + .and_then(|body| { + // body is loaded, now we can deserialize serde-json + let obj = serde_json::from_slice::(&body)?; + Ok(HttpResponse::Ok().json(obj)) // <- send response + }) +} +// + +pub fn main() { + use actix_web::HttpServer; + + HttpServer::new(|| App::new().route("/", web::post().to_async(index_manual))) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} diff --git a/examples/requests/src/multipart.rs b/examples/requests/src/multipart.rs new file mode 100644 index 0000000..94199a1 --- /dev/null +++ b/examples/requests/src/multipart.rs @@ -0,0 +1,25 @@ +// +// use actix_web::{error, Error, HttpRequest, HttpResponse}; +// use futures::Future; + +// pub fn index(req: HttpRequest) -> Box> { +// // get multipart and iterate over multipart items +// req.multipart().and_then(|item| match item { +// multipart::MultipartItem::Field(field) => { +// println!( +// "==== FIELD ==== {:?} {:?}", +// field.headers(), +// field.content_type() +// ); +// Either::A( +// field +// .map(|chunk| { +// println!("-- CHUNK: \n{}", std::str::from_utf8(&chunk).unwrap()); +// }) +// .fold((), |_, _| result(Ok(()))), +// ) +// } +// multipart::MultipartItem::Nested(mp) => Either::B(result(Ok(()))), +// }) +// } +// diff --git a/examples/requests/src/streaming.rs b/examples/requests/src/streaming.rs new file mode 100644 index 0000000..a219d4a --- /dev/null +++ b/examples/requests/src/streaming.rs @@ -0,0 +1,26 @@ +// +use actix_web::{error, web, Error, HttpResponse}; +use futures::{future::result, Future, Stream}; + +pub fn index(payload: web::Payload) -> Box> { + Box::new( + payload + .from_err() + .fold((), |_, chunk| { + println!("Chunk: {:?}", chunk); + result::<_, error::PayloadError>(Ok(())) + }) + .map(|_| HttpResponse::Ok().into()), + ) +} +// + +pub fn main() { + use actix_web::{App, HttpServer}; + + HttpServer::new(|| App::new().route("/", web::post().to_async(index))) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} diff --git a/examples/requests/src/urlencoded.rs b/examples/requests/src/urlencoded.rs new file mode 100644 index 0000000..211c5a6 --- /dev/null +++ b/examples/requests/src/urlencoded.rs @@ -0,0 +1,23 @@ +// +use actix_web::{web, HttpResponse}; +use serde::Deserialize; + +#[derive(Deserialize)] +struct FormData { + username: String, +} + +fn index(form: web::Form) -> HttpResponse { + HttpResponse::Ok().body(format!("username: {}", form.username)) +} +// + +pub fn main() { + use actix_web::{App, HttpServer}; + + HttpServer::new(|| App::new().route("/", web::post().to(index))) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} diff --git a/examples/responder-trait/Cargo.toml b/examples/responder-trait/Cargo.toml new file mode 100644 index 0000000..565cd46 --- /dev/null +++ b/examples/responder-trait/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "responder-trait" +version = "1.0.0" +edition = "2018" + +[dependencies] +actix-web = "1.0" +serde = "1.0" +serde_json = "1.0" diff --git a/examples/responder-trait/src/main.rs b/examples/responder-trait/src/main.rs new file mode 100644 index 0000000..5b199b3 --- /dev/null +++ b/examples/responder-trait/src/main.rs @@ -0,0 +1,38 @@ +// +use actix_web::{Error, HttpRequest, HttpResponse, Responder}; +use serde::Serialize; + +#[derive(Serialize)] +struct MyObj { + name: &'static str, +} + +// Responder +impl Responder for MyObj { + type Error = Error; + type Future = Result; + + fn respond_to(self, _req: &HttpRequest) -> Self::Future { + let body = serde_json::to_string(&self)?; + + // Create response and set content type + Ok(HttpResponse::Ok() + .content_type("application/json") + .body(body)) + } +} + +fn index() -> impl Responder { + MyObj { name: "user" } +} +// + +fn main() { + use actix_web::{web, App, HttpServer}; + + HttpServer::new(|| App::new().route("/", web::get().to(index))) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} diff --git a/examples/responses/Cargo.toml b/examples/responses/Cargo.toml new file mode 100644 index 0000000..8d5624f --- /dev/null +++ b/examples/responses/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "responses" +version = "1.0.0" +edition = "2018" + +[dependencies] +actix-web = "1.0" +serde = "1.0" +futures = "0.1" +bytes = "0.4" diff --git a/examples/responses/src/auto.rs b/examples/responses/src/auto.rs new file mode 100644 index 0000000..1af1078 --- /dev/null +++ b/examples/responses/src/auto.rs @@ -0,0 +1,21 @@ +// +use actix_web::{http::ContentEncoding, middleware, HttpResponse}; + +fn index() -> HttpResponse { + HttpResponse::Ok().body("data") +} + +pub fn main() { + use actix_web::{web, App, HttpServer}; + + HttpServer::new(|| { + App::new() + .wrap(middleware::Compress::new(ContentEncoding::Br)) + .route("/", web::get().to(index)) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// diff --git a/examples/responses/src/brotli.rs b/examples/responses/src/brotli.rs new file mode 100644 index 0000000..ebdf7e2 --- /dev/null +++ b/examples/responses/src/brotli.rs @@ -0,0 +1,23 @@ +// +use actix_web::{http::ContentEncoding, middleware::BodyEncoding, HttpResponse}; + +fn index_br() -> HttpResponse { + HttpResponse::Ok() + .encoding(ContentEncoding::Br) + .body("data") +} + +pub fn main() { + use actix_web::{middleware, web, App, HttpServer}; + + HttpServer::new(|| { + App::new() + .wrap(middleware::Compress::default()) + .route("/", web::get().to(index_br)) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// diff --git a/examples/responses/src/brotli_two.rs b/examples/responses/src/brotli_two.rs new file mode 100644 index 0000000..cdd79f4 --- /dev/null +++ b/examples/responses/src/brotli_two.rs @@ -0,0 +1,21 @@ +// +use actix_web::{http::ContentEncoding, middleware::BodyEncoding, HttpResponse}; + +fn index_br() -> HttpResponse { + HttpResponse::Ok().body("data") +} + +pub fn main() { + use actix_web::{middleware, web, App, HttpServer}; + + HttpServer::new(|| { + App::new() + .wrap(middleware::Compress::new(ContentEncoding::Br)) + .route("/", web::get().to(index_br)) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// diff --git a/examples/responses/src/chunked.rs b/examples/responses/src/chunked.rs new file mode 100644 index 0000000..0c80ed4 --- /dev/null +++ b/examples/responses/src/chunked.rs @@ -0,0 +1,23 @@ +// // +// use actix_web::{web, HttpRequest, HttpResponse}; +// use bytes::Bytes; +// use futures::stream::once; + +// fn index(req: HttpRequest) -> HttpResponse { +// HttpResponse::Ok() +// .chunked() +// .body(Body::Streaming(Box::new(once(Ok(Bytes::from_static( +// b"data", +// )))))) +// } +// // + +// pub fn main() { +// use actix_web::{web, App, HttpServer}; + +// HttpServer::new(|| App::new().route("/", web::get().to(index))) +// .bind("127.0.0.1:8088") +// .unwrap() +// .run() +// .unwrap(); +// } diff --git a/examples/responses/src/compress.rs b/examples/responses/src/compress.rs new file mode 100644 index 0000000..105b76b --- /dev/null +++ b/examples/responses/src/compress.rs @@ -0,0 +1,21 @@ +// +use actix_web::{middleware, HttpResponse}; + +fn index_br() -> HttpResponse { + HttpResponse::Ok().body("data") +} + +pub fn main() { + use actix_web::{web, App, HttpServer}; + + HttpServer::new(|| { + App::new() + .wrap(middleware::Compress::default()) + .route("/", web::get().to(index_br)) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// diff --git a/examples/responses/src/identity.rs b/examples/responses/src/identity.rs new file mode 100644 index 0000000..2d759d1 --- /dev/null +++ b/examples/responses/src/identity.rs @@ -0,0 +1,26 @@ +// +use actix_web::{ + http::ContentEncoding, middleware, middleware::BodyEncoding, HttpResponse, +}; + +fn index() -> HttpResponse { + HttpResponse::Ok() + // v- disable compression + .encoding(ContentEncoding::Identity) + .body("data") +} + +pub fn main() { + use actix_web::{web, App, HttpServer}; + + HttpServer::new(|| { + App::new() + .wrap(middleware::Compress::default()) + .route("/", web::get().to(index)) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// diff --git a/examples/responses/src/identity_two.rs b/examples/responses/src/identity_two.rs new file mode 100644 index 0000000..62839ff --- /dev/null +++ b/examples/responses/src/identity_two.rs @@ -0,0 +1,32 @@ +// +use actix_web::{ + http::ContentEncoding, middleware, middleware::BodyEncoding, HttpResponse, +}; + +static HELLO_WORLD: &[u8] = &[ + 0x1f, 0x8b, 0x08, 0x00, 0xa2, 0x30, 0x10, 0x5c, 0x00, 0x03, 0xcb, 0x48, 0xcd, 0xc9, + 0xc9, 0x57, 0x28, 0xcf, 0x2f, 0xca, 0x49, 0xe1, 0x02, 0x00, 0x2d, 0x3b, 0x08, 0xaf, + 0x0c, 0x00, 0x00, 0x00, +]; + +pub fn index() -> HttpResponse { + HttpResponse::Ok() + .encoding(ContentEncoding::Identity) + .header("content-encoding", "gzip") + .body(HELLO_WORLD) +} +// + +pub fn main() { + use actix_web::{web, App, HttpServer}; + + HttpServer::new(|| { + App::new() + .wrap(middleware::Compress::default()) + .route("/", web::get().to(index)) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} diff --git a/examples/responses/src/json_resp.rs b/examples/responses/src/json_resp.rs new file mode 100644 index 0000000..7fc329d --- /dev/null +++ b/examples/responses/src/json_resp.rs @@ -0,0 +1,25 @@ +// +use actix_web::{web, HttpResponse, Result}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +struct MyObj { + name: String, +} + +fn index(obj: web::Path) -> Result { + Ok(HttpResponse::Ok().json(MyObj { + name: obj.name.to_string(), + })) +} + +pub fn main() { + use actix_web::{App, HttpServer}; + + HttpServer::new(|| App::new().route(r"/a/{name}", web::get().to(index))) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// diff --git a/examples/responses/src/main.rs b/examples/responses/src/main.rs new file mode 100644 index 0000000..d41f2b7 --- /dev/null +++ b/examples/responses/src/main.rs @@ -0,0 +1,28 @@ +pub mod auto; +pub mod brotli; +pub mod chunked; +pub mod compress; +pub mod identity; +pub mod identity_two; +pub mod json_resp; + +// +use actix_web::HttpResponse; + +fn index() -> HttpResponse { + HttpResponse::Ok() + .content_type("plain/text") + .header("X-Hdr", "sample") + .body("data") +} +// + +pub fn main() { + use actix_web::{web, App, HttpServer}; + + HttpServer::new(|| App::new().route("/", web::get().to(index))) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} diff --git a/examples/sentry/Cargo.toml b/examples/sentry/Cargo.toml new file mode 100644 index 0000000..0c3503b --- /dev/null +++ b/examples/sentry/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "sentry" +version = "0.7.0" +edition = "2018" + +[dependencies] +actix-web = "0.7" +sentry-actix = "0.15" +sentry = "0.15" diff --git a/examples/sentry/src/main.rs b/examples/sentry/src/main.rs new file mode 100644 index 0000000..a4de156 --- /dev/null +++ b/examples/sentry/src/main.rs @@ -0,0 +1,36 @@ +// +// use actix_web::{web, App, HttpResponse}; +// use sentry; +// use sentry_actix::SentryMiddleware; + +// use std::env; + +// fn main() { +// sentry::init("SENTRY_DSN_GOES_HERE"); +// env::set_var("RUST_BACKTRACE", "1"); +// sentry::integrations::panic::register_panic_handler(); + +// let mut app = App::new() +// // .data(state) +// .wrap(SentryMiddleware::new()) +// .route("/", web::get().to(|| HttpResponse::Ok())); +// } +// + +// +// use sentry::{Hub, Level}; +// use sentry_actix::ActixWebHubExt; + +// let hub = Hub::from_request(req); +// hub.capture_message("Something is not well", Level::Warning); +// + +// +// use sentry::{Hub, Level}; +// use sentry_actix::ActixWebHubExt; + +// let hub = Hub::from_request(req); +// Hub::run(hub, || { +// sentry::capture_message("Something is not well", Level::Warning); +// }); +// diff --git a/examples/server/Cargo.toml b/examples/server/Cargo.toml index 94e8f33..d3bfa22 100644 --- a/examples/server/Cargo.toml +++ b/examples/server/Cargo.toml @@ -1,10 +1,12 @@ [package] name = "server" -version = "0.7.0" +version = "1.0.0" workspace = "../" +edition = "2018" [dependencies] -actix = "0.7" -actix-web = { version="0.7", features=["alpn"] } +actix-rt = "0.2" +actix-web = { version = "1.0", features = ["ssl"] } futures = "0.1" openssl = "0.10" +actix-http = "0.2" diff --git a/examples/server/rustfmt.toml b/examples/server/rustfmt.toml new file mode 100644 index 0000000..df99c69 --- /dev/null +++ b/examples/server/rustfmt.toml @@ -0,0 +1 @@ +max_width = 80 diff --git a/examples/server/src/ka.rs b/examples/server/src/ka.rs deleted file mode 100644 index 675110c..0000000 --- a/examples/server/src/ka.rs +++ /dev/null @@ -1,14 +0,0 @@ -// -use actix_web::{server, App, HttpResponse}; - -fn main() { - server::new(|| App::new().resource("/", |r| r.f(|_| HttpResponse::Ok()))) - .keep_alive(75); // <- Set keep-alive to 75 seconds - - server::new(|| App::new().resource("/", |r| r.f(|_| HttpResponse::Ok()))) - .keep_alive(server::KeepAlive::Tcp(75)); // <- Use `SO_KEEPALIVE` socket option. - - server::new(|| App::new().resource("/", |r| r.f(|_| HttpResponse::Ok()))) - .keep_alive(None); // <- Disable keep-alive -} -// diff --git a/examples/server/src/keep_alive.rs b/examples/server/src/keep_alive.rs new file mode 100644 index 0000000..d997286 --- /dev/null +++ b/examples/server/src/keep_alive.rs @@ -0,0 +1,22 @@ +// +use actix_web::{web, App, HttpResponse, HttpServer}; + +pub fn main() { + let one = HttpServer::new(|| { + App::new().route("/", web::get().to(|| HttpResponse::Ok())) + }) + .keep_alive(75); // <- Set keep-alive to 75 seconds + + // let _two = HttpServer::new(|| { + // App::new().route("/", web::get().to(|| HttpResponse::Ok())) + // }) + // .keep_alive(); // <- Use `SO_KEEPALIVE` socket option. + + let _three = HttpServer::new(|| { + App::new().route("/", web::get().to(|| HttpResponse::Ok())) + }) + .keep_alive(None); // <- Disable keep-alive + + one.bind("127.0.0.1:8088").unwrap().run().unwrap(); +} +// diff --git a/examples/server/src/ka_tp.rs b/examples/server/src/keep_alive_tp.rs similarity index 52% rename from examples/server/src/ka_tp.rs rename to examples/server/src/keep_alive_tp.rs index bb3c0a8..19287ee 100644 --- a/examples/server/src/ka_tp.rs +++ b/examples/server/src/keep_alive_tp.rs @@ -1,10 +1,13 @@ // use actix_web::{http, HttpRequest, HttpResponse}; -fn index(req: HttpRequest) -> HttpResponse { +pub fn index(req: HttpRequest) -> HttpResponse { HttpResponse::Ok() .connection_type(http::ConnectionType::Close) // <- Close connection - .force_close() // <- Alternative method + .force_close() // <- Alternative method .finish() } // +// ConnectionType::Close +// ConnectionType::KeepAlive +// ConnectionType::Upgrade diff --git a/examples/server/src/main.rs b/examples/server/src/main.rs index 3006fe2..6d43ee1 100644 --- a/examples/server/src/main.rs +++ b/examples/server/src/main.rs @@ -1,24 +1,21 @@ -extern crate actix; -extern crate actix_web; -extern crate futures; -extern crate openssl; - -mod ka; -mod ka_tp; -mod signals; -mod ssl; -mod workers; +pub mod keep_alive; +// pub mod keep_alive_tp; +pub mod signals; +pub mod ssl; +pub mod workers; //
-use actix_web::{server::HttpServer, App, HttpResponse}; +use actix_web::{web, App, HttpResponse, HttpServer}; fn main() { - let sys = actix::System::new("guide"); + let sys = actix_rt::System::new("example"); - HttpServer::new(|| App::new().resource("/", |r| r.f(|_| HttpResponse::Ok()))) - .bind("127.0.0.1:59080") - .unwrap() - .start(); + HttpServer::new(|| { + App::new().route("/", web::get().to(|| HttpResponse::Ok())) + }) + .bind("127.0.0.1:8088") + .unwrap() + .start(); let _ = sys.run(); } diff --git a/examples/server/src/signals.rs b/examples/server/src/signals.rs index bc0d935..f926622 100644 --- a/examples/server/src/signals.rs +++ b/examples/server/src/signals.rs @@ -1,28 +1,41 @@ -use actix; +use actix_rt; use futures::Future; // -use actix_web::{server, App, HttpResponse}; +use actix_web::{web, App, HttpResponse, HttpServer}; use std::sync::mpsc; use std::thread; -fn main() { +pub fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { - let sys = actix::System::new("http-server"); - let addr = server::new(|| { - App::new() - .resource("/", |r| r.f(|_| HttpResponse::Ok())) + let sys = actix_rt::System::new("http-server"); + + let addr = HttpServer::new(|| { + App::new().route("/", web::get().to(|| HttpResponse::Ok())) }) - .bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0") - .shutdown_timeout(60) // <- Set shutdown timeout to 60 seconds - .start(); + .bind("127.0.0.1:8088") + .unwrap() + .shutdown_timeout(60) // <- Set shutdown timeout to 60 seconds + .start(); + let _ = tx.send(addr); let _ = sys.run(); }); let addr = rx.recv().unwrap(); - let _ = addr.send(server::StopServer { graceful: true }).wait(); // <- Send `StopServer` message to server. + let _ = addr + .pause() + .wait() + .map(|_| println!("`actix_server::ServerCommand::Pause`")); + let _ = addr + .resume() + .wait() + .map(|_| println!("`actix_server::ServerCommand::Resume`")); + let _ = addr + .stop(true) + .wait() + .map(|_| println!("`actix_server::ServerCommand::Stop`")); } // diff --git a/examples/server/src/ssl.rs b/examples/server/src/ssl.rs index 2b58256..4efb8de 100644 --- a/examples/server/src/ssl.rs +++ b/examples/server/src/ssl.rs @@ -1,22 +1,28 @@ // -use actix_web::{server, App, HttpRequest, Responder}; +use actix_web::{web, App, HttpRequest, HttpServer, Responder}; use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; -fn index(req: &HttpRequest) -> impl Responder { +fn index(_req: HttpRequest) -> impl Responder { "Welcome!" } -fn main() { +pub fn main() { // load ssl keys - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); + // to create a self-signed temporary cert for testing: + // `openssl req -x509 -newkey rsa:4096 -nodes -keyout key.pem -out cert.pem -days 365 -subj '/CN=localhost'` + let mut builder = + SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); builder .set_private_key_file("key.pem", SslFiletype::PEM) .unwrap(); builder.set_certificate_chain_file("cert.pem").unwrap(); - server::new(|| App::new().resource("/index.html", |r| r.f(index))) - .bind_ssl("127.0.0.1:8080", builder) + HttpServer::new(|| App::new().route("/", web::get().to(index))) + .bind_ssl("127.0.0.1:8088", builder) .unwrap() - .run(); + .run() + .unwrap(); } // +// +// sssl rust-tls diff --git a/examples/server/src/workers.rs b/examples/server/src/workers.rs index 89cf498..f7eba99 100644 --- a/examples/server/src/workers.rs +++ b/examples/server/src/workers.rs @@ -1,8 +1,10 @@ // -use actix_web::{server::HttpServer, App, HttpResponse}; +use actix_web::{web, App, HttpResponse, HttpServer}; -fn main() { - HttpServer::new(|| App::new().resource("/", |r| r.f(|_| HttpResponse::Ok()))) - .workers(4); // <- Start 4 workers +pub fn main() { + HttpServer::new(|| { + App::new().route("/", web::get().to(|| HttpResponse::Ok())) + }) + .workers(4); // <- Start 4 workers } // diff --git a/examples/static-files/Cargo.toml b/examples/static-files/Cargo.toml new file mode 100644 index 0000000..8018328 --- /dev/null +++ b/examples/static-files/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "static-files" +version = "1.0.0" +edition = "2018" + +[dependencies] +actix-web = "1.0" +actix-files = "0.1" +mime = "*" diff --git a/examples/static-files/src/configuration.rs b/examples/static-files/src/configuration.rs new file mode 100644 index 0000000..54b5d2a --- /dev/null +++ b/examples/static-files/src/configuration.rs @@ -0,0 +1,24 @@ +// +use actix_files as fs; +use actix_web::http::header::{ContentDisposition, DispositionType}; +use actix_web::{web, App, Error, HttpRequest, HttpServer}; + +fn index(req: HttpRequest) -> Result { + let path: std::path::PathBuf = req.match_info().query("filename").parse().unwrap(); + let file = fs::NamedFile::open(path)?; + Ok(file + .use_last_modified(true) + .set_content_disposition(ContentDisposition { + disposition: DispositionType::Attachment, + parameters: vec![], + })) +} + +pub fn main() { + HttpServer::new(|| App::new().route("/{filename:.*}", web::get().to(index))) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// diff --git a/examples/static-files/src/configuration_two.rs b/examples/static-files/src/configuration_two.rs new file mode 100644 index 0000000..669e625 --- /dev/null +++ b/examples/static-files/src/configuration_two.rs @@ -0,0 +1,18 @@ +// +use actix_files as fs; +use actix_web::{App, HttpServer}; + +pub fn main() { + HttpServer::new(|| { + App::new().service( + fs::Files::new("/static", ".") + .show_files_listing() + .use_last_modified(true), + ) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// diff --git a/examples/static-files/src/directory.rs b/examples/static-files/src/directory.rs new file mode 100644 index 0000000..6da7ea4 --- /dev/null +++ b/examples/static-files/src/directory.rs @@ -0,0 +1,14 @@ +// +use actix_files as fs; +use actix_web::{App, HttpServer}; + +pub fn main() { + HttpServer::new(|| { + App::new().service(fs::Files::new("/static", ".").show_files_listing()) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// diff --git a/examples/static-files/src/main.rs b/examples/static-files/src/main.rs new file mode 100644 index 0000000..a30d5db --- /dev/null +++ b/examples/static-files/src/main.rs @@ -0,0 +1,24 @@ +pub mod configuration; +pub mod configuration_two; +pub mod directory; + +// +use actix_files::NamedFile; +use actix_web::{HttpRequest, Result}; +use std::path::PathBuf; + +fn index(req: HttpRequest) -> Result { + let path: PathBuf = req.match_info().query("filename").parse().unwrap(); + Ok(NamedFile::open(path)?) +} + +fn main() { + use actix_web::{web, App, HttpServer}; + + HttpServer::new(|| App::new().route("/{filename:.*}", web::get().to(index))) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// diff --git a/examples/testing/Cargo.toml b/examples/testing/Cargo.toml new file mode 100644 index 0000000..5eba424 --- /dev/null +++ b/examples/testing/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "testing" +version = "1.0.0" +edition = "2018" + +[dependencies] +actix-web = "1.0" +futures = "0.1" +bytes = "0.4" +serde = "1.0" +serde_json = "1.0" diff --git a/examples/testing/src/integration_one.rs b/examples/testing/src/integration_one.rs new file mode 100644 index 0000000..750cbc9 --- /dev/null +++ b/examples/testing/src/integration_one.rs @@ -0,0 +1,33 @@ +use actix_web::{HttpRequest, Responder}; + +#[allow(dead_code)] +fn index(_req: HttpRequest) -> impl Responder { + "Hello world!" +} + +// +#[cfg(test)] +mod tests { + use super::*; + use actix_web::dev::Service; + use actix_web::{test, web, App}; + + #[test] + fn test_index_get() { + let mut app = test::init_service(App::new().route("/", web::get().to(index))); + let req = test::TestRequest::get().uri("/").to_request(); + let resp = test::block_on(app.call(req)).unwrap(); + + assert!(resp.status().is_success()); + } + + #[test] + fn test_index_post() { + let mut app = test::init_service(App::new().route("/", web::get().to(index))); + let req = test::TestRequest::post().uri("/").to_request(); + let resp = test::block_on(app.call(req)).unwrap(); + + assert!(resp.status().is_client_error()); + } +} +// diff --git a/examples/testing/src/integration_two.rs b/examples/testing/src/integration_two.rs new file mode 100644 index 0000000..0c5e164 --- /dev/null +++ b/examples/testing/src/integration_two.rs @@ -0,0 +1,33 @@ +use actix_web::{web, HttpResponse, Responder}; +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, Serialize, Debug)] +struct AppState { + count: i32, +} + +#[allow(dead_code)] +fn index(data: web::Data) -> impl Responder { + HttpResponse::Ok().json(data.get_ref()) +} + +// +#[cfg(test)] +mod tests { + use super::*; + use actix_web::{test, web, App}; + + #[test] + fn test_index_get() { + let mut app = test::init_service( + App::new() + .data(AppState { count: 4 }) + .route("/", web::get().to(index)), + ); + let req = test::TestRequest::get().uri("/").to_request(); + let resp: AppState = test::read_response_json(&mut app, req); + + assert!(resp.count == 4); + } +} +// diff --git a/examples/testing/src/main.rs b/examples/testing/src/main.rs new file mode 100644 index 0000000..f1780fb --- /dev/null +++ b/examples/testing/src/main.rs @@ -0,0 +1,41 @@ +pub mod integration_one; +pub mod integration_two; +pub mod stream_response; +use actix_web::{http, web, App, HttpRequest, HttpResponse}; + +fn index(req: HttpRequest) -> HttpResponse { + if let Some(hdr) = req.headers().get(http::header::CONTENT_TYPE) { + if let Ok(_s) = hdr.to_str() { + return HttpResponse::Ok().into(); + } + } + HttpResponse::BadRequest().into() +} + +fn main() { + App::new().route("/", web::get().to(index)); +} + +// +#[cfg(test)] +mod tests { + use super::*; + use actix_web::test; + + #[test] + fn test_index_ok() { + let req = test::TestRequest::with_header("content-type", "text/plain") + .to_http_request(); + + let resp = test::block_on(index(req)).unwrap(); + assert_eq!(resp.status(), http::StatusCode::OK); + } + + #[test] + fn test_index_not_ok() { + let req = test::TestRequest::default().to_http_request(); + let resp = test::block_on(index(req)).unwrap(); + assert_eq!(resp.status(), http::StatusCode::BAD_REQUEST); + } +} +// diff --git a/examples/testing/src/stream_response.rs b/examples/testing/src/stream_response.rs new file mode 100644 index 0000000..f922e05 --- /dev/null +++ b/examples/testing/src/stream_response.rs @@ -0,0 +1,49 @@ +// +use bytes::Bytes; +use futures::stream::poll_fn; +use futures::{Async, Poll}; + +use actix_web::http::{ContentEncoding, StatusCode}; +use actix_web::{middleware::BodyEncoding, web, App, Error, HttpRequest, HttpResponse}; + +fn sse(_req: HttpRequest) -> HttpResponse { + let mut counter: usize = 5; + + // yields `data: N` where N in [5; 1] + let server_events = poll_fn(move || -> Poll, Error> { + if counter == 0 { + return Ok(Async::Ready(None)); + } + let payload = format!("data: {}\n\n", counter); + counter -= 1; + Ok(Async::Ready(Some(Bytes::from(payload)))) + }); + + HttpResponse::build(StatusCode::OK) + .encoding(ContentEncoding::Identity) + .content_type("text/event-stream") + .streaming(server_events) +} + +pub fn main() { + App::new().route("/", web::get().to(sse)); +} + +#[cfg(test)] +mod tests { + use super::*; + use actix_web::{test, web, App}; + + #[test] + fn test_stream() { + let mut app = test::init_service(App::new().route("/", web::get().to(sse))); + let req = test::TestRequest::get().to_request(); + let resp = test::read_response(&mut app, req); + assert!( + resp == Bytes::from_static( + b"data: 5\n\ndata: 4\n\ndata: 3\n\ndata: 2\n\ndata: 1\n\n" + ) + ); + } +} +// diff --git a/examples/url-dispatch/Cargo.toml b/examples/url-dispatch/Cargo.toml index b6d081c..b7914ca 100644 --- a/examples/url-dispatch/Cargo.toml +++ b/examples/url-dispatch/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "url-dispatch" -version = "0.7.0" +version = "1.0.0" +edition = "2018" workspace = "../" [dependencies] -actix = "0.7" -actix-web = "0.7" +actix = "0.8" +actix-web = "1.0" futures = "0.1" openssl = "0.10" serde = "1.0" -serde_derive = "1.0" diff --git a/examples/url-dispatch/src/cfg.rs b/examples/url-dispatch/src/cfg.rs index 80f63d8..dfad2cd 100644 --- a/examples/url-dispatch/src/cfg.rs +++ b/examples/url-dispatch/src/cfg.rs @@ -1,15 +1,23 @@ -// -use actix_web::{pred, App, HttpResponse}; +use actix_web::{guard, web, App, HttpResponse}; -fn main() { - App::new() - .resource("/path", |resource| { - resource - .route() - .filter(pred::Get()) - .filter(pred::Header("content-type", "text/plain")) - .f(|req| HttpResponse::Ok()) - }) - .finish(); -} +#[rustfmt::skip] +pub fn main() { + use actix_web::HttpServer; + + HttpServer::new(|| { +// +App::new().service( + web::resource("/path").route( + web::route() + .guard(guard::Get()) + .guard(guard::Header("content-type", "text/plain")) + .to(|| HttpResponse::Ok()), + ), +) // + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} diff --git a/examples/url-dispatch/src/dhandler.rs b/examples/url-dispatch/src/dhandler.rs index 7678d9b..e67891a 100644 --- a/examples/url-dispatch/src/dhandler.rs +++ b/examples/url-dispatch/src/dhandler.rs @@ -1,14 +1,23 @@ -// -use actix_web::{http::Method, pred, App, HttpResponse}; +use actix_web::{guard, web, App, HttpRequest, HttpResponse, HttpServer, Responder}; -fn main() { - App::new() - .default_resource(|r| { - r.method(Method::GET).f(|req| HttpResponse::NotFound()); - r.route() - .filter(pred::Not(pred::Get())) - .f(|req| HttpResponse::MethodNotAllowed()); - }) - .finish(); +fn index(_req: HttpRequest) -> impl Responder { + "Welcome!" +} + +// +pub fn main() { + HttpServer::new(|| { + App::new() + .service(web::resource("/").route(web::get().to(index))) + .default_service( + web::route() + .guard(guard::Not(guard::Get())) + .to(|| HttpResponse::MethodNotAllowed()), + ) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); } // diff --git a/examples/url-dispatch/src/guard.rs b/examples/url-dispatch/src/guard.rs new file mode 100644 index 0000000..1ef5d6b --- /dev/null +++ b/examples/url-dispatch/src/guard.rs @@ -0,0 +1,28 @@ +// +use actix_web::{dev::RequestHead, guard::Guard, http, HttpResponse}; + +struct ContentTypeHeader; + +impl Guard for ContentTypeHeader { + fn check(&self, req: &RequestHead) -> bool { + req.headers().contains_key(http::header::CONTENT_TYPE) + } +} + +pub fn main() { + use actix_web::{web, App, HttpServer}; + + HttpServer::new(|| { + App::new().route( + "/", + web::route() + .guard(ContentTypeHeader) + .to(|| HttpResponse::Ok()), + ) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// diff --git a/examples/url-dispatch/src/guard2.rs b/examples/url-dispatch/src/guard2.rs new file mode 100644 index 0000000..2d3d30b --- /dev/null +++ b/examples/url-dispatch/src/guard2.rs @@ -0,0 +1,18 @@ +// +use actix_web::{guard, web, App, HttpResponse, HttpServer}; + +pub fn main() { + HttpServer::new(|| { + App::new().route( + "/", + web::route() + .guard(guard::Not(guard::Get())) + .to(|| HttpResponse::MethodNotAllowed()), + ) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// diff --git a/examples/url-dispatch/src/main.rs b/examples/url-dispatch/src/main.rs index ac19232..009b26a 100644 --- a/examples/url-dispatch/src/main.rs +++ b/examples/url-dispatch/src/main.rs @@ -1,38 +1,34 @@ -extern crate actix; -extern crate actix_web; -extern crate futures; -extern crate openssl; -#[macro_use] -extern crate serde_derive; -extern crate serde; - -mod cfg; -mod dhandler; -mod minfo; -mod norm; -mod norm2; -mod path; -mod path2; -mod pbuf; -mod pred; -mod pred2; -mod prefix; -mod resource; -mod scope; -mod url_ext; -mod urls; +pub mod cfg; +pub mod dhandler; +pub mod guard; +pub mod guard2; +pub mod minfo; +pub mod norm; +pub mod norm2; +pub mod path; +pub mod path2; +pub mod pbuf; +pub mod resource; +pub mod scope; +pub mod url_ext; +pub mod urls; //
-use actix_web::{http::Method, App, HttpRequest, HttpResponse}; +use actix_web::{web, App, HttpResponse, HttpServer}; -fn index(req: HttpRequest) -> HttpResponse { - unimplemented!() +fn index() -> HttpResponse { + HttpResponse::Ok().body("Hello") } fn main() { - App::new() - .route("/user/{name}", Method::GET, index) - .route("/user/{name}", Method::POST, index) - .finish(); + HttpServer::new(|| { + App::new() + .route("/", web::get().to(index)) + .route("/user", web::post().to(index)) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); } //
diff --git a/examples/url-dispatch/src/minfo.rs b/examples/url-dispatch/src/minfo.rs index 2c05a57..7c993d7 100644 --- a/examples/url-dispatch/src/minfo.rs +++ b/examples/url-dispatch/src/minfo.rs @@ -1,15 +1,24 @@ // -use actix_web::{App, HttpRequest, Result}; +use actix_web::{HttpRequest, HttpResponse, Result}; -fn index(req: &HttpRequest) -> Result { - let v1: u8 = req.match_info().query("v1")?; - let v2: u8 = req.match_info().query("v2")?; - Ok(format!("Values {} {}", v1, v2)) +fn index(req: HttpRequest) -> Result { + let v1: u8 = req.match_info().get("v1").unwrap().parse().unwrap(); + let v2: u8 = req.match_info().query("v2").parse().unwrap(); + let (v3, v4): (u8, u8) = req.match_info().load().unwrap(); + Ok(format!("Values {} {} {} {}", v1, v2, v3, v4)) } -fn main() { - App::new() - .resource(r"/a/{v1}/{v2}/", |r| r.f(index)) - .finish(); +pub fn main() { + use actix_web::{web, App, HttpServer}; + + HttpServer::new(|| { + App::new() + .route("/a/{v1}/{v2}/", web::get().to(index)) + .route("", web::get().to(|| HttpResponse::Ok())) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); } // diff --git a/examples/url-dispatch/src/norm.rs b/examples/url-dispatch/src/norm.rs index fcdbd50..4d1abf7 100644 --- a/examples/url-dispatch/src/norm.rs +++ b/examples/url-dispatch/src/norm.rs @@ -1,15 +1,21 @@ // -use actix_web::{http::NormalizePath, App}; +use actix_web::{middleware, HttpResponse}; -fn main() { - let app = App::new() - .resource("/resource/", |r| r.f(index)) - .default_resource(|r| r.h(NormalizePath::default())) - .finish(); +fn index() -> HttpResponse { + HttpResponse::Ok().body("Hello") +} + +pub fn main() { + use actix_web::{web, App, HttpServer}; + + HttpServer::new(|| { + App::new() + .wrap(middleware::NormalizePath) + .route("/resource/", web::to(index)) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); } // - -use actix_web::HttpRequest; -fn index(req: &HttpRequest) -> String { - unimplemented!() -} diff --git a/examples/url-dispatch/src/norm2.rs b/examples/url-dispatch/src/norm2.rs index 7a4fb91..a704fa7 100644 --- a/examples/url-dispatch/src/norm2.rs +++ b/examples/url-dispatch/src/norm2.rs @@ -1,16 +1,22 @@ -// -use actix_web::{http::Method, http::NormalizePath, App}; +use actix_web::HttpResponse; -fn main() { - let app = App::new() - .resource("/resource/", |r| r.f(index)) - .default_resource(|r| r.method(Method::GET).h(NormalizePath::default())) - .finish(); +fn index() -> HttpResponse { + HttpResponse::Ok().body("Hello") +} + +// +use actix_web::{http::Method, middleware, web, App, HttpServer}; + +pub fn main() { + HttpServer::new(|| { + App::new() + .wrap(middleware::NormalizePath) + .route("/resource/", web::get().to(index)) + .default_service(web::route().method(Method::GET)) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); } // - -use actix_web::HttpRequest; - -fn index(req: &HttpRequest) -> String { - unimplemented!() -} diff --git a/examples/url-dispatch/src/path.rs b/examples/url-dispatch/src/path.rs index 7724c1e..11e2a99 100644 --- a/examples/url-dispatch/src/path.rs +++ b/examples/url-dispatch/src/path.rs @@ -1,15 +1,22 @@ // -use actix_web::{http::Method, App, Path, Result}; +use actix_web::{web, Result}; -// extract path info using serde -fn index(info: Path<(String, u32)>) -> Result { +fn index(info: web::Path<(String, u32)>) -> Result { Ok(format!("Welcome {}! id: {}", info.0, info.1)) } -fn main() { - let app = App::new().resource( - "/{username}/{id}/index.html", // <- define path parameters - |r| r.method(Method::GET).with(index), - ); +pub fn main() { + use actix_web::{App, HttpServer}; + + HttpServer::new(|| { + App::new().route( + "/{username}/{id}/index.html", // <- define path parameters + web::get().to(index), + ) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); } // diff --git a/examples/url-dispatch/src/path2.rs b/examples/url-dispatch/src/path2.rs index 6f2aea6..799a412 100644 --- a/examples/url-dispatch/src/path2.rs +++ b/examples/url-dispatch/src/path2.rs @@ -1,6 +1,6 @@ // -extern crate serde_derive; -use actix_web::{http::Method, App, Path, Result}; +use actix_web::{web, Result}; +use serde::Deserialize; #[derive(Deserialize)] struct Info { @@ -8,14 +8,22 @@ struct Info { } // extract path info using serde -fn index(info: Path) -> Result { +fn index(info: web::Path) -> Result { Ok(format!("Welcome {}!", info.username)) } -fn main() { - let app = App::new().resource( - "/{username}/index.html", // <- define path parameters - |r| r.method(Method::GET).with(index), - ); +pub fn main() { + use actix_web::{App, HttpServer}; + + HttpServer::new(|| { + App::new().route( + "/{username}/index.html", // <- define path parameters + web::get().to(index), + ) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); } // diff --git a/examples/url-dispatch/src/pbuf.rs b/examples/url-dispatch/src/pbuf.rs index b18ae35..36b7e7c 100644 --- a/examples/url-dispatch/src/pbuf.rs +++ b/examples/url-dispatch/src/pbuf.rs @@ -1,15 +1,19 @@ // -use actix_web::{http::Method, App, HttpRequest, Result}; +use actix_web::{HttpRequest, Result}; use std::path::PathBuf; -fn index(req: &HttpRequest) -> Result { - let path: PathBuf = req.match_info().query("tail")?; +fn index(req: HttpRequest) -> Result { + let path: PathBuf = req.match_info().query("tail").parse().unwrap(); Ok(format!("Path {:?}", path)) } -fn main() { - App::new() - .resource(r"/a/{tail:.*}", |r| r.method(Method::GET).f(index)) - .finish(); +pub fn main() { + use actix_web::{web, App, HttpServer}; + + HttpServer::new(|| App::new().route(r"/a/{tail:.*}", web::get().to(index))) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); } // diff --git a/examples/url-dispatch/src/pred.rs b/examples/url-dispatch/src/pred.rs deleted file mode 100644 index 2fda5a5..0000000 --- a/examples/url-dispatch/src/pred.rs +++ /dev/null @@ -1,19 +0,0 @@ -// -use actix_web::{http, server::Request, pred::Predicate, App, HttpResponse}; - -struct ContentTypeHeader; - -impl Predicate for ContentTypeHeader { - fn check(&self, req: &Request, state: &S) -> bool { - req.headers().contains_key(http::header::CONTENT_TYPE) - } -} - -fn main() { - App::new().resource("/index.html", |r| { - r.route() - .filter(ContentTypeHeader) - .f(|_| HttpResponse::Ok()) - }); -} -// diff --git a/examples/url-dispatch/src/pred2.rs b/examples/url-dispatch/src/pred2.rs deleted file mode 100644 index 6a9449a..0000000 --- a/examples/url-dispatch/src/pred2.rs +++ /dev/null @@ -1,13 +0,0 @@ -// -use actix_web::{pred, App, HttpResponse}; - -fn main() { - App::new() - .resource("/index.html", |r| { - r.route() - .filter(pred::Not(pred::Get())) - .f(|req| HttpResponse::MethodNotAllowed()) - }) - .finish(); -} -// diff --git a/examples/url-dispatch/src/prefix.rs b/examples/url-dispatch/src/prefix.rs deleted file mode 100644 index 8b3985d..0000000 --- a/examples/url-dispatch/src/prefix.rs +++ /dev/null @@ -1,14 +0,0 @@ -use actix_web::{App, HttpRequest, HttpResponse}; - -// -fn show_users(req: &HttpRequest) -> HttpResponse { - unimplemented!() -} - -fn main() { - App::new() - .prefix("/users") - .resource("/show", |r| r.f(show_users)) - .finish(); -} -// diff --git a/examples/url-dispatch/src/resource.rs b/examples/url-dispatch/src/resource.rs index aebfebd..778a712 100644 --- a/examples/url-dispatch/src/resource.rs +++ b/examples/url-dispatch/src/resource.rs @@ -1,16 +1,19 @@ // -use actix_web::{http::Method, App, HttpRequest, HttpResponse}; +use actix_web::{guard, web, App, HttpResponse}; -fn index(req: &HttpRequest) -> HttpResponse { - unimplemented!() +fn index() -> HttpResponse { + HttpResponse::Ok().body("Hello") } -fn main() { +pub fn main() { App::new() - .resource("/prefix", |r| r.f(index)) - .resource("/user/{name}", |r| { - r.method(Method::GET).f(|req| HttpResponse::Ok()) - }) - .finish(); + .service(web::resource("/prefix").to(index)) + .service( + web::resource("/user/{name}") + .name("user_detail") + .guard(guard::Header("content-type", "application/json")) + .route(web::get().to(|| HttpResponse::Ok())) + .route(web::put().to(|| HttpResponse::Ok())), + ); } // diff --git a/examples/url-dispatch/src/scope.rs b/examples/url-dispatch/src/scope.rs index 8c8f5bd..11ac91e 100644 --- a/examples/url-dispatch/src/scope.rs +++ b/examples/url-dispatch/src/scope.rs @@ -1,54 +1,25 @@ -#![allow(dead_code)] -use actix_web::{http::Method, App, HttpRequest}; +use actix_web::{web, App, HttpResponse, HttpServer}; -fn get_projects(_: &HttpRequest) -> String { - unimplemented!() -} -fn create_project(_: &HttpRequest) -> String { - unimplemented!() -} -fn update_project(_: HttpRequest) -> String { - unimplemented!() -} -fn delete_project(_: &HttpRequest) -> String { - unimplemented!() -} -fn get_tasks(_: &HttpRequest) -> String { - unimplemented!() -} -fn create_task(_: &HttpRequest) -> String { - unimplemented!() -} -fn update_task(_: HttpRequest) -> String { - unimplemented!() -} -fn delete_task(_: HttpRequest) -> String { - unimplemented!() -} - -fn main() { // -App::new().scope("/project", |proj_scope| { - proj_scope - .resource("", |r| { - r.method(Method::GET).f(get_projects); - r.method(Method::POST).f(create_project) - }) - .resource("/{project_id}", |r| { - r.method(Method::PUT).with(update_project); - r.method(Method::DELETE).f(delete_project) - }) - .nested("/{project_id}/task", |task_scope| { - task_scope - .resource("", |r| { - r.method(Method::GET).f(get_tasks); - r.method(Method::POST).f(create_task) - }) - .resource("/{task_id}", |r| { - r.method(Method::PUT).with(update_task); - r.method(Method::DELETE).with(delete_task) - }) - }) -}); -// +fn show_users() -> HttpResponse { + HttpResponse::Ok().body("Show users") } + +fn user_detail(path: web::Path<(u32,)>) -> HttpResponse { + HttpResponse::Ok().body(format!("User detail: {}", path.0)) +} + +pub fn main() { + HttpServer::new(|| { + App::new().service( + web::scope("/users") + .route("/show", web::get().to(show_users)) + .route("/show/{id}", web::get().to(user_detail)), + ) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// diff --git a/examples/url-dispatch/src/url_ext.rs b/examples/url-dispatch/src/url_ext.rs index f02f14f..7de8e6e 100644 --- a/examples/url-dispatch/src/url_ext.rs +++ b/examples/url-dispatch/src/url_ext.rs @@ -1,16 +1,25 @@ // -use actix_web::{App, Error, HttpRequest, HttpResponse}; +use actix_web::{HttpRequest, Responder}; -fn index(req: &HttpRequest) -> Result { - let url = req.url_for("youtube", &["oHg5SJYRHA0"])?; +fn index(req: HttpRequest) -> impl Responder { + let url = req.url_for("youtube", &["oHg5SJYRHA0"]).unwrap(); assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0"); - Ok(HttpResponse::Ok().into()) + + url.into_string() } -fn main() { - let app = App::new() - .resource("/index.html", |r| r.f(index)) - .external_resource("youtube", "https://youtube.com/watch/{video_id}") - .finish(); +pub fn main() { + use actix_web::{web, App, HttpServer}; + + HttpServer::new(|| { + App::new() + .route("/", web::get().to(index)) + .external_resource("youtube", "https://youtube.com/watch/{video_id}") + .route("/", actix_web::web::get().to(index)) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); } // diff --git a/examples/url-dispatch/src/urls.rs b/examples/url-dispatch/src/urls.rs index 6e67a3a..8fc03fb 100644 --- a/examples/url-dispatch/src/urls.rs +++ b/examples/url-dispatch/src/urls.rs @@ -1,20 +1,30 @@ // -use actix_web::{http::header, http::Method, App, HttpRequest, HttpResponse, Result}; +use actix_web::{guard, http::header, HttpRequest, HttpResponse, Result}; fn index(req: HttpRequest) -> Result { let url = req.url_for("foo", &["1", "2", "3"])?; // <- generate url for "foo" resource + Ok(HttpResponse::Found() .header(header::LOCATION, url.as_str()) .finish()) } -fn main() { - let app = App::new() - .resource("/test/{a}/{b}/{c}", |r| { - r.name("foo"); // <- set resource name, then it could be used in `url_for` - r.method(Method::GET).f(|_| HttpResponse::Ok()); - }) - .route("/test/", Method::GET, index) - .finish(); +pub fn main() { + use actix_web::{web, App, HttpServer}; + + HttpServer::new(|| { + App::new() + .service( + web::resource("/test/{a}/{b}/{c}") + .name("foo") // <- set resource name, then it could be used in `url_for` + .guard(guard::Get()) + .to(|| HttpResponse::Ok()), + ) + .route("/test/", web::get().to(index)) + }) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); } // diff --git a/examples/websockets/Cargo.toml b/examples/websockets/Cargo.toml new file mode 100644 index 0000000..e8f5b50 --- /dev/null +++ b/examples/websockets/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "websockets" +version = "1.0.0" +edition = "2018" + +[dependencies] +actix = "0.8" +actix-web = "1.0" +actix-web-actors = "1.0" diff --git a/examples/websockets/src/main.rs b/examples/websockets/src/main.rs new file mode 100644 index 0000000..fd49c5c --- /dev/null +++ b/examples/websockets/src/main.rs @@ -0,0 +1,44 @@ +// +use actix::{Actor, StreamHandler}; +use actix_web::{web, App, Error, HttpRequest, HttpResponse, HttpServer}; +use actix_web_actors::ws; + +/// Define http actor +struct MyWs; + +impl Actor for MyWs { + type Context = ws::WebsocketContext; +} + +/// Handler for ws::Message message +impl StreamHandler for MyWs { + fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { + match msg { + ws::Message::Ping(msg) => ctx.pong(&msg), + ws::Message::Text(text) => ctx.text(text), + ws::Message::Binary(bin) => ctx.binary(bin), + _ => (), + } + } +} + +fn index(req: HttpRequest, stream: web::Payload) -> Result { + let resp = ws::start(MyWs {}, &req, stream); + println!("{:?}", resp); + resp +} + +fn main() { + HttpServer::new(|| App::new().route("/ws/", web::get().to(index))) + .bind("127.0.0.1:8088") + .unwrap() + .run() + .unwrap(); +} +// + +// testing requires specific headers: +// Upgrade: websocket +// Connection: Upgrade +// Sec-WebSocket-Key: SOME_KEY +// Sec-WebSocket-Version: 13 diff --git a/layouts/docs/baseof.html b/layouts/docs/baseof.html index fe301b3..626433b 100644 --- a/layouts/docs/baseof.html +++ b/layouts/docs/baseof.html @@ -79,7 +79,7 @@ diff --git a/layouts/index.html b/layouts/index.html index 9751979..d0e1648 100644 --- a/layouts/index.html +++ b/layouts/index.html @@ -22,7 +22,7 @@ Feature Rich -

Actix provides a lot of features out of box. WebSockets, HTTP/2, pipelining etc.

+

Actix provides a lot of features out of box. HTTP/2, logging, etc.

@@ -39,23 +39,23 @@
- {{ highlight `extern crate actix_web; -use actix_web::{server, App, HttpRequest, Responder}; + {{ highlight `use actix_web::{web, App, HttpRequest, HttpServer}; -fn greet(req: &HttpRequest) -> impl Responder { - let to = req.match_info().get("name").unwrap_or("World"); - format!("Hello {}!", to) +fn greet(req: HttpRequest) -> impl Responder { + let name = req.match_info().get("name").unwrap_or("World"); + format!("Hello {}!", &name) } fn main() { - server::new(|| { + HttpServer::new(|| { App::new() - .resource("/", |r| r.f(greet)) - .resource("/{name}", |r| r.f(greet)) + .route("/", web::get().to(greet)) + .route("/{name}", web::get().to(greet)) }) .bind("127.0.0.1:8000") .expect("Can not bind to port 8000") - .run(); + .run() + .unwrap(); }` "rust" "" }}
@@ -78,8 +78,8 @@ fn hello_world() -> impl Responder { "Hello World!" } -fn current_temperature(_req: HttpRequest) -> impl Responder { - Json(Measurement { temperature: 42.3 }) +fn current_temperature() -> impl Responder { + web::Json(Measurement { temperature: 42.3 }) }` "rust" "" }}
@@ -91,16 +91,17 @@ fn current_temperature(_req: HttpRequest) -> impl Responder { your view functions can be synchronous code and still benefit from asynchronous IO handling.

- {{ highlight `#[derive(Deserialize)] + {{ highlight `#[derive(Deserialize, Serialize)] struct Event { + id: Option, timestamp: f64, kind: String, tags: Vec, } -fn capture_event(evt: Json) -> impl Responder { - let id = store_event_in_db(evt.timestamp, evt.kind, evt.tags); - format!("got event {}", id) +fn capture_event(evt: web::Json) -> impl Responder { + let new_event = store_in_db(evt.timestamp, &evt.kind, &evt.tags); + format!("got event {}", new_event.id.unwrap()) }` "rust" "" }}
@@ -116,8 +117,8 @@ struct Register { country: String, } -fn register(data: Form) -> impl Responder { - format!("Hello {} from {}!", data.username, data.country) +fn register(form: web::Form) -> impl Responder { + format!("Hello {} from {}!", form.username, form.country) }` "rust" "" }}
@@ -127,19 +128,18 @@ fn register(data: Form) -> impl Responder { URLs and invoke individual handlers. For extra flexibility, scopes can be used.

- {{ highlight `fn index(req: HttpRequest) -> impl Responder { - "Hello from the index page" + {{ highlight `fn index(_req: HttpRequest) -> impl Responder { + "Hello from the index page!" } -fn hello(path: Path) -> impl Responder { - format!("Hello {}!", *path) +fn hello(path: web::Path) -> impl Responder { + format!("Hello {}!", &path) } fn main() { App::new() - .resource("/", |r| r.method(Method::GET).with(index)) - .resource("/hello/{name}", |r| r.method(Method::GET).with(hello)) - .finish(); + .route("/", web::get().to(index)) + .route("/{name}", web::get().to(hello)); }` "rust" "" }}
diff --git a/layouts/shortcodes/rust-version.html b/layouts/shortcodes/rust-version.html new file mode 100644 index 0000000..9f50a46 --- /dev/null +++ b/layouts/shortcodes/rust-version.html @@ -0,0 +1,5 @@ +{{- if eq (.Get 0) "actix" -}} + {{- .Page.Site.Params.actixMinRustVersion -}} +{{- else if eq (.Get 0) "actix-web" -}} + {{- .Page.Site.Params.actixWebMinRustVersion -}} +{{- end -}}