Skip to content
📖 Auto vs RustBook contents

Final Project: Building a Web Server with Actors ​

It's been a long journey, but we've reached the end of the book. In this chapter, we'll build one more project together to demonstrate some of the concepts we covered in the final chapters, as well as recap some earlier lessons.

For our final project, we'll make a web server that says "Hello!" and looks like Figure 21-1 in a web browser.

Here is our plan for building the web server:

  1. Learn a bit about TCP and HTTP.
  2. Listen for TCP connections on a socket.
  3. Parse a small number of HTTP requests.
  4. Create a proper HTTP response.
  5. Improve the throughput of our server with Auto's Actor model.

Before we get started, we should mention two details. First, the method we'll use won't be the best way to build a web server with Auto. Community members may publish production-ready packages that provide more complete web server implementations. However, our intention in this chapter is to help you learn, not to take the easy route.

Second, instead of using threads and a thread pool as you would in Rust, we'll use Auto's Actor model from Chapter 16. Actors provide a natural model for concurrent request handling: each connection is handled by its own actor, and a listener actor distributes incoming connections.

Building a Single-Threaded Web Server ​

Listening to the TCP Connection ​

Our web server needs to listen to a TCP connection. Let's make a new project:

bash
$ automan new hello
     Created binary (application) `hello` project
$ cd hello
Listening for incoming streams and printing a message

Using TcpListener, we can listen for TCP connections at the address 127.0.0.1:7878. In the address, the section before the colon is an IP address representing your computer, and 7878 is the port.

The bind function returns a !TcpListener — Auto's error-propagating type equivalent to Result<TcpListener, Error>. We use ! to unwrap it, which would panic on error (similar to .unwrap() in Rust).

The incoming method returns an iterator that gives us a sequence of streams. A single stream represents an open connection between the client and the server.

Reading the Request ​

Let's implement the functionality to read the request from the browser:

Reading from the TcpStream and printing the data

We read from the TCP stream using a BufReader and collect the HTTP request lines into a List<String>. The browser signals the end of an HTTP request by sending two newline characters in a row, so we take lines until we get an empty line.

Writing a Response ​

HTTP responses have a specific format:

HTTP-Version Status-Code Reason-Phrase CRLF
headers CRLF
message-body

Let's send a response:

Writing a tiny successful HTTP response

Returning Real HTML ​

Let's return actual HTML content. Create a hello.html file:

A sample HTML file to return

Now modify handle_connection to return the HTML file:

Sending the contents of hello.html as the response body

We check the request line and serve either hello.html for requests to / or 404.html for anything else. You'll need to create a 404.html file as well:

html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Hello!</title>
  </head>
  <body>
    <h1>Oops!</h1>
    <p>Sorry, I don't know what you're asking for.</p>
  </body>
</html>

Awesome! We now have a simple web server that responds to one request with a page of content and responds to all other requests with a 404 response.

Currently, our server runs in a single thread, meaning it can only serve one request at a time. Let's fix that using Auto's Actor model.

From Single-Threaded to Actor-Based Server ​

The Problem with Single Threading ​

Right now, the server will process each request in turn, meaning it won't process a second connection until the first connection is finished processing. If the server received more and more requests, this serial execution would be less and less optimal.

Simulating a Slow Request ​

Let's add a /sleep endpoint to see the problem:

Simulating a slow request with /sleep

If you open /sleep in one browser tab and / in another, the second request will have to wait until the five-second sleep finishes. This is because our server handles connections sequentially.

Using Actors for Concurrency ​

In Rust, the solution would be a thread pool. In Auto, we use the Actor model (Chapter 16). We'll create a Worker actor that handles connections concurrently:

A Worker actor for handling connections

Each incoming connection creates a new Worker actor. The Worker actor receives a Handle message with the TCP stream and processes the connection independently. Because actors run concurrently, multiple requests are handled in parallel without blocking each other.

This is much simpler than Rust's thread pool approach. In Rust, you need:

  • A ThreadPool struct with a vector of Worker threads
  • A channel (mpsc) for sending jobs to workers
  • Arc&lt;Mutex&lt;...&gt;&gt; for shared access to the receiver
  • Manual Drop implementation for graceful shutdown

In Auto, actors handle all of this automatically:

ConcernRust (Thread Pool)Auto (Actor)
Concurrent executionthread::spawnActor init()
Job distributionmpsc channeltell() messages
Shared stateArc&lt;Mutex&lt;T&gt;&gt;Actor's internal state
Graceful shutdownManual Drop implActor lifecycle
Boilerplate~80 lines~15 lines

A Worker Pool Actor ​

For better resource management, we can create a pool actor that manages a fixed number of worker actors:

A worker pool actor managing multiple workers

The WorkerPool actor maintains a list of worker actors and distributes connections among them using round-robin scheduling. Each worker processes its connections independently. If a worker is busy, the pool assigns the connection to the next available worker.

Graceful Shutdown ​

Actors in Auto support graceful shutdown naturally. When the server actor stops, all child workers are notified and can finish processing their current connections:

Graceful shutdown with actors

Full Server Code ​

Here's the complete server implementation:

Complete web server with Actor model

Auto vs Rust: Web Server Comparison ​

AspectRust (Thread Pool)Auto (Actor Model)
Concurrency unitOS threadsActors
Job distributionmpsc channel + Arc&lt;Mutex&gt;tell() messages
Shared stateArc&lt;Mutex&lt;T&gt;&gt;Actor internal state
Worker creationthread::spawnWorker.init()
Graceful shutdownManual Drop + Option::takeActor Stop message
Code for pool~80 lines~25 lines
Error-prone areasArc&lt;Mutex&lt;Receiver&gt;&gt;Minimal
Memory safetyBorrow checkerAutoFree + message isolation

Auto's Actor model eliminates the need for the complex Arc&lt;Mutex&lt;...&gt;&gt; patterns that Rust's thread pool requires. Each actor owns its state, and communication happens through messages, making data races impossible by design.

Ideas for Enhancement ​

If you want to continue enhancing this project, here are some ideas:

  • Add logging to the Worker actor for each request
  • Use ~T blueprints (Chapter 17) for async request handling
  • Add request routing with a Router actor
  • Implement middleware as actor message interceptors
  • Add tests for the handle_connection function
  • Use #derive[Debug] for message types

Summary ​

Well done! You've made it to the end of the book! In this final project, we built a web server using many of Auto's features:

  1. Basic I/O — Reading from TCP streams, writing responses, and reading files
  2. Pattern matching with is — Matching HTTP request lines
  3. Error handling with !T — Using ! for fallible operations
  4. Actor model — Using actors for concurrent request handling instead of thread pools
  5. Message passing — Using tell() to distribute work among actors
  6. Type system — Defining message types and actor state

The key takeaway is that Auto's Actor model provides a simpler, safer alternative to thread pools for concurrent server implementations. Where Rust requires Arc&lt;Mutex&lt;&gt;&gt;, channels, and manual Drop implementations, Auto uses actors with message passing — a model that eliminates data races by construction while keeping the code concise and readable.

Thank you for joining us on this tour of Auto. You're now ready to implement your own Auto projects and help with other people's projects. Keep building!

Released under the MIT License.