Locust’s FastHTTP client is surprisingly capable of outperforming Python’s built-in requests library for load testing, even with simple use cases.
Here’s a quick look at a Locust file using FastHTTP:
from locust import HttpUser, task, between
from locust_plugins.users.fastapi import FastHTTPUser # Or from locust_plugins.users.fastapi import FastHTTPUser for FastAPI
class WebsiteUser(HttpUser):
wait_time = between(1, 2)
host = "http://localhost:8000"
@task
def index(self):
self.client.get("/")
@task
def users(self):
self.client.get("/users")
@task
def create_user(self):
self.client.post("/users", json={"name": "testuser", "email": "test@example.com"})
When you run this, you’ll see requests being fired off at an impressive rate. The key difference lies in how FastHTTP handles the underlying network operations. Instead of relying on Python’s standard socket module and blocking I/O, it leverages uvloop and httpx (or fastapi.testclient if you’re testing a FastAPI app directly) to achieve asynchronous, non-blocking I/O. This means a single Locust worker can manage thousands of concurrent connections without getting bogged down waiting for responses.
The problem this solves is the inherent limitation of traditional synchronous HTTP clients in generating high volumes of traffic. When you’re load testing, you want your testing tool to be the bottleneck, not the client library itself. requests is fantastic for general web interactions, but for raw throughput, its synchronous nature becomes a limiting factor. FastHTTP’s asynchronous architecture allows it to keep many connections "in flight" simultaneously, dramatically increasing the number of requests a single process can send per second.
Let’s break down the core components:
HttpUser: This is the base class for users that interact with an HTTP(S) service.wait_time: Defines the pause between tasks for a user.between(1, 2)means each user will wait 1 to 2 seconds randomly before executing their next task.host: The base URL of the service you are testing.@task: Decorator to define a specific user behavior (an HTTP request in this case).self.client: An instance of the FastHTTP client. Its methods (get,post,put,delete, etc.) mirror therequestslibrary but are executed asynchronously.locust_plugins.users.fastapi.FastHTTPUser: If you’re testing a FastAPI application, this specific user class is optimized for it, often leveragingfastapi.testclientfor even tighter integration and faster testing of your API endpoints without needing to run a separate server process.
The real power comes from the underlying libraries. uvloop is a drop-in replacement for Python’s default event loop, offering significantly better performance. httpx is a modern, async-capable HTTP client that locust-plugins uses under the hood. Together, they allow Locust to manage a massive number of concurrent HTTP requests efficiently.
When you’re configuring Locust, you’re essentially telling it how many users to simulate and what actions those users should perform. With FastHTTP, the bottleneck shifts from your client’s ability to manage connections to the network bandwidth and the server’s capacity.
Most people understand that asynchronous I/O is faster, but they often miss how deeply it affects the number of concurrent operations a single thread can handle. It’s not just about not blocking; it’s about the event loop being able to rapidly poll many sockets, process incoming data, and send outgoing data without context switching overhead or thread contention. This allows a single Python process, running on a single CPU core, to saturate a gigabit network connection with HTTP requests if the server can keep up.
The next logical step is to explore how to integrate custom headers and authentication mechanisms with the FastHTTP client.