actix-web
provides various primitives to build web servers and applications with Rust. It provides
routing, middleware, pre-processing of requests, post-processing of responses, etc.
All actix-web
servers are built around the App
instance. It is used for registering
routes for resources and middleware. It also stores application state shared across all handlers
within the same scope.
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 scope
/app
, any request with the paths/app
,/app/
, or/app/test
would match; however, the path/application
would not match.
use actix_web::{web, App, HttpServer, Responder};
async fn index() -> impl Responder {
"Hello world!"
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new().service(
// prefixes all resources and routes attached to it...
web::scope("/app")
// ...so this handles requests for `GET /app/index.html`
.route("/index.html", web::get().to(index)),
)
})
.bind("127.0.0.1:8080")?
.run()
.await
}
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 section.
Application state is shared with all routes and resources within the same scope. State can be
accessed with the web::Data<T>
extractor where T
is the type of the state. State is also
accessible for middleware.
Let’s write a simple application and store the application name in the state:
use actix_web::{get, web, App, HttpServer};
// This struct represents state
struct AppState {
app_name: String,
}
#[get("/")]
async fn index(data: web::Data<AppState>) -> String {
let app_name = &data.app_name; // <- get app_name
format!("Hello {}!", app_name) // <- response with app_name
}
and pass in the state when initializing the App, and start the application:
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.data(AppState {
app_name: String::from("Actix-web"),
})
.service(index)
})
.bind("127.0.0.1:8080")?
.run()
.await
}
Any number of state types could be registered within the application.
HttpServer
accepts an application factory rather than an application instance. An HttpServer
constructs an application instance for each thread. Therefore, application data must be constructed
multiple times. If you want to share data between different threads, a shareable object should be
used, e.g. Send
+ Sync
.
Internally, web::Data
uses Arc
. Thus, in order to avoid creating two Arc
s, we should
create our Data before registering it using App::app_data()
.
In the following example, we will write an application with mutable, shared state. First, we define our state and create our handler:
use actix_web::{web, App, HttpServer};
use std::sync::Mutex;
struct AppStateWithCounter {
counter: Mutex<i32>, // <- Mutex is necessary to mutate safely across threads
}
async fn index(data: web::Data<AppStateWithCounter>) -> String {
let mut counter = data.counter.lock().unwrap(); // <- get counter's MutexGuard
*counter += 1; // <- access counter inside MutexGuard
format!("Request number: {}", counter) // <- response with count
}
and register the data in an App
:
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let counter = web::Data::new(AppStateWithCounter {
counter: Mutex::new(0),
});
HttpServer::new(move || {
// move counter into the closure
App::new()
// Note: using app_data instead of data
.app_data(counter.clone()) // <- register the created data
.route("/", web::get().to(index))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
The web::scope()
method allows setting a resource group 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
original author intended while still maintaining the same resource names.
For example:
#[actix_web::main]
async fn main() {
let scope = web::scope("/users").service(show_users);
App::new().service(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 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.
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
trait.
Actix-web provides several guards. You can check the functions section of the API
docs.
One of the provided guards is Header
. It can be used as a filter based on request
header information.
#[actix_web::main]
async fn main() -> std::io::Result<()> {
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:8080")?
.run()
.await
}
For simplicity and reusability both App
and web::Scope
provide
the configure
method. This function is useful for moving parts of the configuration to a different
module or even library. For example, some of the resource’s configuration could be moved to a
different module.
use actix_web::{web, App, HttpResponse, HttpServer};
// this function could be located in a 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 a 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())),
);
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
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:8080")?
.run()
.await
}
The result of the above example would be:
/ -> "/"
/app -> "app"
/api/test -> "test"
Each ServiceConfig
can have its own data
, routes
, and services
.