Learning Rust - Basic Networking and the HTTP Server
Episode 10 of 19

Learning Rust - Basic Networking and the HTTP Server

This episode builds your first HTTP server in Rust with axum, discusses request-response, routing, middleware, and error handling, then introduces basic TCP and UDP connections as well as the service discovery concept for distributed applications.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

After mastering data and configuration, it is time for your Rust application to talk to the outside world. Episode 10 is the point where your program turns into a real service: receiving HTTP requests, processing them, and returning responses.

We start with the networking basics in std::net, then build an HTTP server with axum — a modern framework built on hyper. You will learn routing, middleware, error handling, and get a look at warp, actix-web, and the concept of service discovery. By the end of the episode, you will have an API you can test with curl.

TCP and UDP Basics

Sockets in the Standard Library

Before HTTP, understand its transport: TCP provides a reliable, ordered connection, while UDP provides fast datagrams without guarantees. The standard library provides both:

TCP listener
cat > src/main.rs <<'EOF'
use std::io::{Read, Write};
use std::net::TcpListener;
 
fn main() {
    let listener = TcpListener::bind("127.0.0.1:8000").unwrap();
    println!("mendengarkan di 127.0.0.1:8000");
 
    for stream in listener.incoming() {
        let mut stream = stream.unwrap();
        let mut buffer = [0; 1024];
        stream.read(&mut buffer).unwrap();
        let respons = b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nok";
        stream.write_all(respons).unwrap();
    }
}
EOF
cargo run

TcpListener::bind("127.0.0.1:8000") opens a local port, and listener.incoming() yields incoming connections. The example above even responds over HTTP — although very primitively. Run cargo run, then test with curl http://127.0.0.1:8000 from another terminal.

UDP for Latency-Critical Work

UDP is used for DNS, telemetry, and games because its overhead is minimal. In Rust, UdpSocket::bind and send_to/recv_from handle datagrams. For HTTP and business services, TCP remains the primary choice.

HTTP Server with Axum

Building Your First Server

axum is built on hyper and tower, using an expressive handler and extractor architecture. Add the dependencies with cargo add axum tokio --features tokio/full:

Hello world axum
cat > src/main.rs <<'EOF'
use axum::{routing::get, Router};
 
async fn halo() -> &'static str {
    "Halo dari axum"
}
 
#[tokio::main]
async fn main() {
    let app = Router::new().route("/", get(halo));
 
    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
        .await
        .unwrap();
 
    println!("listening on 127.0.0.1:3000");
    axum::serve(listener, app).await.unwrap();
}
EOF
cargo run

Router::new().route("/", get(halo)) registers a handler for a path. axum::serve runs the async server. Test with curl http://127.0.0.1:3000 and you will see the Halo dari axum response.

Extractors and Responses

Handlers accept extractors such as Path, Query, Json, and State, and return any type that implements IntoResponse:

Path and JSON
cat > src/main.rs <<'EOF'
use axum::{extract::Path, routing::get, Json, Router};
use serde::Serialize;
 
#[derive(Serialize)]
struct Pengguna {
    id: u32,
    nama: String,
}
 
async fn detail(Path(id): Path<u32>) -> Json<Pengguna> {
    Json(Pengguna {
        id,
        nama: format!("pengguna {}", id),
    })
}
 
#[tokio::main]
async fn main() {
    let app = Router::new().route("/api/pengguna/{id}", get(detail));
 
    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
        .await
        .unwrap();
 
    axum::serve(listener, app).await.unwrap();
}
EOF
cargo run

Path(id): Path<u32> extracts the value from the {id} segment. Json<Pengguna> creates a JSON response from a struct implementing Serialize. Test with curl http://127.0.0.1:3000/api/pengguna/7.

Routing, Middleware, and Error Handling

Middleware with tower

Middleware handles cross-cutting request concerns: logging, timeout, CORS, and compression. axum uses layers from tower:

Trace layer and fallback
use tower_http::trace::TraceLayer;
 
let app = Router::new()
    .route("/", get(|| async { "selamat datang" }))
    .layer(TraceLayer::new_for_http())
    .fallback(|| async { (StatusCode::NOT_FOUND, "tidak ditemukan") });

TraceLayer logs every request along with its status and duration. fallback handles paths that are not registered. Layers are attached with .layer(...) and applied from outside in.

Handler Error Handling

Handlers return Result<T, E> where E: IntoResponse:

Handler with error
struct AppError(anyhow::Error);
 
impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        (
            StatusCode::INTERNAL_SERVER_ERROR,
            format!("terjadi kesalahan: {}", self.0),
        )
    }
}
 
async fn index() -> Result<&'static str, AppError> {
    Ok("api sehat")
}

Errors from handlers are converted into HTTP responses with the appropriate status. The ? operator inside a handler propagates internal errors, and AppError turns them into a client-friendly response.

Other Frameworks and Service Discovery

warp, actix-web, and hyper

Beyond axum: warp uses composable filter combinators, actix-web offers an actor model and high performance, while hyper is the low-level HTTP foundation that axum itself uses. Choose axum for the tokio ecosystem, actix-web for teams that want an all-in-one framework, and warp for a functional style.

Service Discovery

In distributed environments, service instances do not know each other's addresses. Service discovery solves this: instances register themselves in a registry (for example etcd, Consul, or Kubernetes' built-in registry), and consumers query the registry to find active addresses. In Rust, crates such as rust-consul and tonic (for gRPC) are the bridge to this ecosystem.

Closing

Key takeaways:

  • std::net provides basic TCP and UDP; TCP for reliable services.
  • axum builds an HTTP server on top of hyper and tower.
  • Routers, handlers, and extractors such as Path, Query, and Json shape an API.
  • tower middleware handles cross-cutting concerns like tracing and timeout.
  • Handler errors are converted into HTTP responses with the right status.
  • warp and actix-web are alternatives; service discovery connects instances.

In the next episode 11 we will discuss security, TLS, and auth — enabling HTTPS with TLS and certificates, implementing JWT, OAuth2, and session authentication, as well as input protection, CORS, rate limiting, and security headers. Your API is ready to be exposed to the public.

Learning Rust - Basic Networking and the HTTP Server | Learning Rust