Skip to main content
Lesson 5 of 12Beginner⏱ 10 mins

HTTP Requests & Responses

Understand the lifecycle of requests and how to craft responses.

33% of path

Learning Objectives

  • Read request data
  • Build HTTP responses

##The request and the response

Every interaction with your server is a pair: a request from the client and a response from you. Understanding both parts makes the rest of Actix Web much easier.

#Anatomy of a request

A request has four parts:

  • Method: what the client wants to do (GET, POST, PUT, DELETE).
  • Path and query: which resource (/users/42?active=true).
  • Headers: metadata such as Content-Type, Authorization and User-Agent.
  • Body: optional data, usually JSON for POST and PUT.

You can inspect the raw request by taking HttpRequest as a parameter:

code_blocksrust
use actix_web::{get, HttpRequest, HttpResponse};

#[get("/inspect")]
async fn inspect(req: HttpRequest) -> HttpResponse {
    let ua = req
        .headers()
        .get("user-agent")
        .and_then(|v| v.to_str().ok())
        .unwrap_or("unknown");
    HttpResponse::Ok().body(format!("{} {} from {}", req.method(), req.path(), ua))
}

#Anatomy of a response

A response has a status code, headers and an optional body. HttpResponse is the builder:

code_blocksrust
use actix_web::{http::header, HttpResponse};

fn example() -> HttpResponse {
    HttpResponse::Ok()
        .insert_header((header::CACHE_CONTROL, "no-store"))
        .content_type("text/plain; charset=utf-8")
        .body("hello")
}

#Status codes you will use constantly

  • 200 OK: success with a body.
  • 201 Created: a new resource was made.
  • 204 No Content: success with nothing to return.
  • 400 Bad Request: the client sent something invalid.
  • 401 / 403: not logged in / not allowed.
  • 404 Not Found: the resource does not exist.
  • 500 Internal Server Error: your server failed.

Actix has a shortcut for each: HttpResponse::Created(), HttpResponse::NotFound(), HttpResponse::BadRequest() and so on.

#Anything can be a response

Many types implement Responder, so for simple cases you can skip HttpResponse entirely: a &str or String becomes a 200 text response, web::Json<T> becomes JSON, and a tuple like (body, StatusCode::CREATED) sets the status.

Choose HttpResponse when you need to control headers or the status, and a plain value when you do not.

Interactive Code Inspector

Select any highlighted line of code below to deeply inspect its mechanics, understand why it exists, and what happens if you change it.

src/main.rs
1use actix_web::{HttpRequest, HttpResponse, Responder};
2
3async fn custom_handler(req: HttpRequest) -> impl Responder {
4 let user_agent = req.headers().get("User-Agent").unwrap();
5
6 HttpResponse::Ok()
7 .insert_header(("X-Custom-Header", "Actix"))
8 .body(format!("Hello, you are using: {:?}", user_agent))
9}
Line 3
req: HttpRequest

What it does

Extracts the raw HTTP request.

Why use it?

To access headers, method, or URI.

How it works underneath

Actix injects the request object automatically.

If you remove it

You wouldn't be able to inspect headers.

"What If?" Experiment

Learning by doing. Modify the code to see what happens.

Goal

Add a custom header to a response.

Starting Code
HttpResponse::Ok().body("hello")
Suggested Modification

Add .insert_header(("X-App", "ActixWeb")) before .body.

Expected Result

The response now includes the header X-App: ActixWeb.

Explanation: HttpResponse is a builder, so each call adds to the response before you finish it with body, json or finish.

Common Mistakes

Anticipate and avoid these frequent pitfalls when working with this concept.

!

Returning 200 for errors

It feels simpler to always return Ok, but clients rely on the status code.

Incorrect Pattern
HttpResponse::Ok().body("user not found")
Correct Pattern
HttpResponse::NotFound().body("user not found")

Explanation: Browsers, caches and API clients use the status code to decide what happened. Use the code that matches the outcome.

!

Forgetting the Content-Type for JSON

Writing JSON text into body() does not set the header.

Incorrect Pattern
HttpResponse::Ok().body("{\"ok\":true}")
Correct Pattern
HttpResponse::Ok().json(serde_json::json!({ "ok": true }))

Explanation: .json() serializes the value and sets Content-Type to application/json, so clients parse it correctly.

Practical Exercise

Return the right status codes

Task

Write a handler for DELETE /items/{id} that returns 204 when id is 1 and 404 otherwise.

Requirements

  • Read id from the path as a u32
  • Return HttpResponse::NoContent() for id 1
  • Return HttpResponse::NotFound() for anything else

Need a hint?

Try solving the exercise in your local environment first. Once you have a solution, or if you get completely stuck, reveal the answer.

Real-World Application

Public APIs are judged by their status codes and headers: caching headers control CDN behaviour, 201 with a Location header signals creation, and consistent 4xx responses make clients easy to write.

Key Takeaways

  • A request is method + path + headers + optional body; a response is status + headers + optional body.
  • HttpRequest gives you raw access; extractors are usually cleaner.
  • HttpResponse::Ok(), Created(), NotFound() and friends set the status code.
  • Strings, web::Json and tuples already implement Responder.

Finished reading?

Mark this lesson as done to track your progress and earn XP.