Skip to content

Chapter 13 · Non-HTTP Protocol Testing ​

Chapter goals: Break through HTTP limits by testing TCP Socket, gRPC, XML-RPC and other protocols with custom User classes, and understand the gevent monkey-patching constraints.

13.1 Extension Principle: The request Event ​

Locust natively supports only HTTP/HTTPS, but the architecture leaves an extension point. The core idea:

  1. Inherit from User (not HttpUser) to create a custom client;
  2. After each protocol call, manually fire the request event so Locust records statistics.
python
from locust import User, task, between, events
from locust.exception import LocustError
import socket
import time

class TcpClient:
    def __init__(self, host, port):
        self.host = host
        self.port = port
        self.socket = None

    def connect(self):
        self.socket = socket.create_connection((self.host, self.port))

    def send_and_receive(self, payload: bytes) -> bytes:
        start_time = time.time()
        start_perf = time.perf_counter()
        try:
            self.socket.sendall(payload)
            response = self.socket.recv(4096)
            elapsed = time.perf_counter() - start_perf
            # Fire the request event so Locust records stats
            events.request.fire(
                request_type="TCP",
                name="send_recv",
                response_time=elapsed * 1000,
                response_length=len(response),
                exception=None,
                context={},
            )
            return response
        except Exception as e:
            elapsed = time.perf_counter() - start_perf
            events.request.fire(
                request_type="TCP", name="send_recv",
                response_time=elapsed * 1000,
                response_length=0, exception=e, context={},
            )
            raise

    def close(self):
        if self.socket:
            self.socket.close()

13.2 gevent Monkey-Patching Constraints ​

Key limitation

Locust's core uses gevent cooperative concurrency. All libraries under test must be patchable by gevent — pure Python libraries (using the standard socket module) work out of the box; C-extension libraries (like psycopg2) cannot be patched and will block the entire process.

LibraryPatchable?Workaround
socket / ssl✅ Works out of the box—
requests / urllib3✅ Built-in—
pymysql / asyncpg✅ Pure Python—
psycopg2 (PostgreSQL)❌ C extensionUse psycogreen patch
redis-py (hiredis)⚠️ PartialDisable hiredis parser

13.3 XML-RPC Load Testing Example ​

The official docs provide an XML-RPC client wrapper example. Start a test server first:

python
# server.py
from xmlrpc.server import SimpleXMLRPCServer
import time, random

def add(x, y):
    time.sleep(random.uniform(0.01, 0.05))
    return x + y

def query_database(query):
    time.sleep(random.uniform(0.05, 0.2))
    return [{"id": 1, "result": 42}]

server = SimpleXMLRPCServer(("localhost", 8000))
server.register_function(add, "add")
server.register_function(query_database, "db.query")
server.serve_forever()

Then the Locust test:

python
import xmlrpc.client
from locust import User, task, events

class XmlRpcUser(User):
    wait_time = between(0.5, 2)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.client = xmlrpc.client.ServerProxy("http://localhost:8000")

    @task
    def add_numbers(self):
        start = time.perf_counter()
        try:
            result = self.client.add(10, 20)
            elapsed = (time.perf_counter() - start) * 1000
            events.request.fire(
                request_type="XML-RPC", name="add",
                response_time=elapsed, response_length=len(str(result)),
                exception=None,
            )
        except Exception as e:
            elapsed = (time.perf_counter() - start) * 1000
            events.request.fire(
                request_type="XML-RPC", name="add",
                response_time=elapsed, response_length=0,
                exception=e,
            )

13.4 Chapter Summary ​

  • Inherit from User (not HttpUser) for non-HTTP protocols;
  • Manually fire events.request.fire() after each call to record stats;
  • gevent monkey-patching is required — C-extension libraries may block;
  • Custom TCP, XML-RPC, and gRPC clients all follow the same pattern.

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. To test a non-HTTP protocol in Locust, which class should you inherit?

2. Why can C-extension libraries like psycopg2 block Locust?

3. What is the minimum code needed to record a custom-protocol request in Locust stats?

4. Which library below CANNOT be used directly with Locust without a workaround?

🛠️ Hands-On Exercises ​

  1. Write a custom TCP User that connects to a local echo server, sends messages, and fires events.request.fire() for each round-trip.
  2. Replace the TCP client with an XML-RPC client; verify the request appears in the Web UI under the "XML-RPC" request type.
  3. Try using psycopg2 directly in a Locust task; observe the blocking behavior and fix it with psycogreen.

Proceed to Chapter 14: asyncio Support.