Vert.x Requests and Responses

At the core of Vert.x lies a high-performance, asynchronous networking layer built on top of Netty. Its HTTP module adopts an event-driven, non-blocking paradigm for handling incoming requests and crafting outgoing responses. A solid grasp of the HttpServerRequest and HttpServerResponse objects serves as the foundation for any robust Vert.x web application.

Whenever a client establishes a connection and sends an HTTP request, the HttpServer triggers a registered requestHandler callback, passing along an HttpServerRequest instance. This object encapsulates metadata such as the HTTP method, request URI, and headers. Additionally, it provides direct access to the paired HttpServerResponse via the response() method, allowing developers to stream data back to the client seamlessly.

Two crucial principles govern this workflow:

  • One-to-One Mapping: Each HttpServerRequest is strictly tied to a single, unique HttpServerResponse instance.
  • Non-Blocking Execution: All operations are asynchronous. Developers must rely on callbacks to handle results and must never execute long-running, blocking tasks directly on the event loop threads.
    Creating a basic HTTP server involves instantiating Vert.x, configuring the server, and defining the request handler:
Vertx vertx = Vertx.vertx();
HttpServer server = vertx.createHttpServer();

server.requestHandler(request -> {
    request.response()
           .putHeader("Content-Type", "text/plain; charset=utf-8")
           .end("Hello, Vert.x!");
});

server.listen(8080, res -> {
    if (res.succeeded()) {
        System.out.println("Server started on port 8080");
    } else {
        System.out.println("Failed to start server: " + res.cause());
    }
});

The end() method signals the completion of the response lifecycle, accepting an optional string or Buffer. Once end() is invoked, no further data can be written to that particular response.

Reading Incoming Requests

Query Parameters and Headers

Extracting parameters and headers from a request is straightforward using built-in helper methods:

server.requestHandler(request -> {
    // Read query parameters, e.g., /api?name=tom
    String name = request.getParam("name");

    // Read request headers
    String token = request.getHeader("Authorization");

    // Read request path and HTTP method
    String path = request.path();
    HttpMethod method = request.method();

    request.response().end("name=" + name + ", token=" + token);
});

Frequently used methods include:

  • getParam(String): Retrieves a query parameter by name.
  • getHeader(String): Retrieves a specific request header.
  • path(), uri(), and method(): Provide insights into the routing path, full URI, and HTTP verb.

Handling Request Bodies

Because request bodies arrive as a data stream over the network, developers typically register a bodyHandler that fires once the entire payload has been fully buffered:

server.requestHandler(request -> {
    request.bodyHandler(buffer -> {
        System.out.println("Received request body: " + buffer.toString());
        request.response().end("ok");
    });
});

When building applications with vertx-web, developers can mount a BodyHandler middleware in advance. This allows them to access the complete payload immediately through request.body(), eliminating the need to manually manage stream chunks.

Constructing Responses

Setting Status Codes and Headers

Customizing the HTTP response requires interacting directly with the HttpServerResponse object:

HttpServerResponse response = request.response();
response.setStatusCode(201)
        .putHeader("Content-Type", "application/json; charset=utf-8")
        .end("{\"status\":\"created\"}");

Chunked Writing and Streaming Responses

For large files or dynamically generated content, developers can stream data in chunks by repeatedly calling write() before finalizing the transmission with end():

response.setChunked(true);
response.write("First chunk of data\n");
response.write("Second chunk of data\n");
response.end();

Enabling chunked transfer encoding via setChunked(true) is ideal for large datasets or scenarios where the total content length is initially unknown. Furthermore, response.sendFile("index.html") allows developers to serve static files efficiently using internal non-blocking file I/O operations.

Simplifying Workflows with Routers

Real-world applications typically leverage the Router component provided by vertx-web to dispatch requests cleanly based on HTTP methods and URL paths:

Router router = Router.router(vertx);

router.route(HttpMethod.GET, "/hello")
      .handler(ctx -> {
          ctx.response()
             .putHeader("Content-Type", "text/plain; charset=utf-8")
             .end("Hello Router!");
      });

vertx.createHttpServer()
     .requestHandler(router)
     .listen(8080);

The RoutingContext encapsulates both the request and response objects. Combined with modular middleware like body parsers and timeout handlers, it simplifies parameter extraction and exception management, resulting in clean and maintainable code architectures.

Conclusion

Vert.x handles requests and responses through a disciplined model built on a single-request, single-response, and fully asynchronous lifecycle:

  1. Ingesting incoming data streams via HttpServerRequest inside the requestHandler.
  2. Inspecting parameters, headers, and bodies using methods like getParam, getHeader, and bodyHandler.
  3. Configuring status codes and headers on the HttpServerResponse, and concluding transmissions with write or end.
  4. Scaling application complexity cleanly by adopting Router and middleware components from vertx-web.

Mastering these non-blocking, callback-driven APIs lays a solid foundation for exploring advanced features such as file uploads, WebSockets, and reactive stream processing.