Skip to main content

The HTTP Server

The HttpServer 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.

To start the web server it must first be bound to a network socket. Use HttpServer::bind() with a socket address tuple or string such as ("127.0.0.1", 8080) or "0.0.0.0:8080". This will fail if the socket is being used by another application.

After the bind is successful, use HttpServer::run() to return a Server instance. The Server must be awaited or spawned to start processing requests and will run until it receives a shutdown signal (such as, by default, a ctrl-c; read more here).

use actix_web::{web, App, HttpResponse, HttpServer};

#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().route("/", web::get().to(HttpResponse::Ok)))
.bind(("127.0.0.1", 8080))?
.run()
.await
}

Multi-Threading​

HttpServer automatically starts a number of HTTP workers, by default this number is equal to the number of physical CPUs in the system. This number can be overridden with the HttpServer::workers() method.

use actix_web::{web, App, HttpResponse, HttpServer};

#[actix_web::main]
async fn main() {
let _ = HttpServer::new(|| App::new().route("/", web::get().to(HttpResponse::Ok))).workers(4);
// <- Start 4 workers
}

Once the workers are created, they each receive a separate application instance to handle requests. Application state is not shared between the threads, and handlers are free to manipulate their copy of the state with no concurrency concerns.

Application state does not need to be Send or Sync, but application factories must be Send + Sync.

To share state between worker threads, use an Arc/Data. Special care should be taken once sharing and synchronization are introduced. In many cases, performance costs are inadvertently introduced as a result of locking the shared state for modifications.

In some cases these costs can be alleviated using more efficient locking strategies, for example using read/write locks instead of mutexes to achieve non-exclusive locking, but the most performant implementations often tend to be ones in which no locking occurs at all.

Since each worker thread processes its requests sequentially, handlers which block the current thread will cause the current worker to stop processing new requests:

fn my_handler() -> impl Responder {
std::thread::sleep(Duration::from_secs(5)); // <-- Bad practice! Will cause the current worker thread to hang!
"response"
}

For this reason, any long, non-cpu-bound operation (e.g. I/O, database operations, etc.) should be expressed as futures or asynchronous functions. Async handlers get executed concurrently by worker threads and thus don't block execution:

async fn my_handler() -> impl Responder {
tokio::time::sleep(Duration::from_secs(5)).await; // <-- Ok. Worker thread will handle other requests here
"response"
}

The same limitation applies to extractors as well. When a handler function receives an argument which implements FromRequest, and that implementation blocks the current thread, the worker thread will block when running the handler. Special attention must be given when implementing extractors for this very reason, and they should also be implemented asynchronously where needed.

TLS / HTTPS​

Actix Web supports TLS / HTTPS through rustls and openssl. Use rustls for new applications. OpenSSL is available as an alternative for applications that need it.

Enable the rustls-0_23 feature and add rustls and rustls-pemfile to load certificates and private keys from PEM files:

[dependencies]
actix-web = { version = "4", features = ["rustls-0_23"] }
rustls = "0.23"
rustls-pemfile = "2"

Load the certificate chain and private key, then pass the TLS configuration to HttpServer::bind_rustls_0_23():

use std::{fs::File, io::BufReader};

use actix_web::{App, HttpRequest, HttpServer, Responder, web};

async fn index(_req: HttpRequest) -> impl Responder {
"Hello TLS World!"
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
rustls::crypto::aws_lc_rs::default_provider()
.install_default()
.unwrap();

let mut certs_file = BufReader::new(File::open("cert.pem").unwrap());
let mut key_file = BufReader::new(File::open("key.pem").unwrap());

// load TLS certs and key
// 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 tls_certs = rustls_pemfile::certs(&mut certs_file)
.collect::<Result<Vec<_>, _>>()
.unwrap();
let tls_key = rustls_pemfile::pkcs8_private_keys(&mut key_file)
.next()
.unwrap()
.unwrap();

// set up TLS config options
let tls_config = rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(tls_certs, rustls::pki_types::PrivateKeyDer::Pkcs8(tls_key))
.unwrap();

HttpServer::new(|| App::new().route("/", web::get().to(index)))
.bind_rustls_0_23(("127.0.0.1", 8443), tls_config)?
.run()
.await
}

This example reads cert.pem and key.pem from the working directory and serves HTTPS at https://localhost:8443. The private key must be in unencrypted PKCS#8 PEM format.

Certificates for local development​

Install mkcert, then create and install a local certificate authority and generate a certificate for localhost:

$ mkcert -install
$ mkcert -cert-file cert.pem -key-file key.pem localhost 127.0.0.1 ::1

mkcert installs its local certificate authority in the system trust store, so supported browsers trust these development certificates. Use mkcert only for local development, and do not share its rootCA-key.pem file.

OpenSSL alternative​

To use OpenSSL, enable the openssl feature and add the openssl crate:

[dependencies]
actix-web = { version = "4", features = ["openssl"] }
openssl = { version = "0.10" }

Load the certificate and private key with OpenSSL, then use HttpServer::bind_openssl():

use actix_web::{get, App, HttpRequest, HttpServer, Responder};
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};

#[get("/")]
async fn index(_req: HttpRequest) -> impl Responder {
"Welcome!"
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
// load TLS 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().service(index))
.bind_openssl("127.0.0.1:8080", builder)?
.run()
.await
}

Keep-Alive​

Actix Web keeps connections open to wait for subsequent requests.

keep alive connection behavior is defined by server settings.

  • Duration::from_secs(75) or KeepAlive::Timeout(75): enables 75 second keep-alive timer.
  • KeepAlive::Os: uses OS keep-alive.
  • None or KeepAlive::Disabled: disables keep-alive.
use actix_web::{http::KeepAlive, HttpServer};
use std::time::Duration;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
// Set keep-alive to 75 seconds
let _one = HttpServer::new(app).keep_alive(Duration::from_secs(75));

// Use OS's keep-alive (usually quite long)
let _two = HttpServer::new(app).keep_alive(KeepAlive::Os);

// Disable keep-alive
let _three = HttpServer::new(app).keep_alive(None);

Ok(())
}

If the first option above is selected, then keep-alive is enabled for HTTP/1.1 requests if the response does not explicitly disallow it by, for example, setting the connection type to Close or Upgrade. Force closing a connection can be done with the force_close() method on HttpResponseBuilder

Keep-alive is off for HTTP/1.0 and is on for HTTP/1.1 and HTTP/2.0.

use actix_web::{http, HttpRequest, HttpResponse};

async fn index(_req: HttpRequest) -> HttpResponse {
let mut resp = HttpResponse::Ok()
.force_close() // <- Close connection on HttpResponseBuilder
.finish();

// Alternatively close connection on the HttpResponse struct
resp.head_mut().set_connection_type(http::ConnectionType::Close);

resp
}

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() method.

HttpServer handles several OS signals. CTRL-C is available on all OSes, 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() method.