Vert.x Core

repository·master·Indexed 12 days ago

https://github.com/eclipse-vertx/vert.x

A toolkit for building reactive applications on the JVM. Vert.x Core provides fundamental low-level networking (HTTP, TCP), file system, and I/O capabilities, including support for UDP via DatagramSocket, SSL/TLS configuration, and efficient data handling with Buffers.

Tokens
30.9K
Snippets
94
Records
163
Agent score
97%

What's inside Vert.x

  1. Overview of Vert.x Core

    master
    Vert.x Core provides low-level functionality essential for building reactive applications. It includes support for HTTP, TCP, file system access, and other fundamental networking and I/O features. Vert.x Core serves as the foundation for many other Vert.x components and can be used directly in your applications.
  2. Handle HTTP 100-Continue on Client and Server

    master

    The Expect: 100-Continue header allows a client to send request headers and wait for a 100 (Continue) response from the server before sending a large request body. This saves bandwidth if the server intends to reject the request.

    Client Side

    Use HttpClientRequest#continueHandler(Handler) to receive the signal from the server. This must be used in conjunction with writeHead().

    Server Side

    • Automatic: Set HttpServerConfig#setHandle100ContinueAutomatically(true) to automatically respond with 100 Continue when the header is detected.
    • Manual: Set the option to false (default) to inspect headers manually. Use HttpServerResponse#writeContinue() to allow the client to proceed, or send a failure status code to reject the request.
    // Client side example
    request.continueHandler(ctx -> {
      // logic to handle 100-Continue
    });
    request.writeHead(status, headers);
    
    // Server side manual example
    if (shouldContinue) {
      response.writeContinue();
    } else {
      response.setStatusCode(403).end();
    }
  3. Configure DNS resolver options

    master

    The Vert.x DNS resolver provides several configuration options via io.vertx.core.dns.AddressResolverOptions:

    • Failover: If a server does not respond in time, the resolver tries the next one. The search is limited by setMaxQueries(int) (default: 4). A query fails if no answer is received within getQueryTimeout() milliseconds (default: 5 seconds).
    • Server list rotation: By default, the first server is used and others are for failover. Set setRotateServers(true) to use round-robin selection to spread load.
    • Hosts mapping: Vert.x uses the OS hosts file by default, but you can provide an alternative.
    • Search domains: You can provide an explicit list of search domains. When a list is used, the dot threshold is 1 (or from /etc/resolv.conf on Linux), which can be adjusted using setNdots(int).

    You can also use the JVM system property -Dvertx.disableDnsResolver=true to use the JVM's built-in resolver instead of the Vert.x resolver.

  4. Implement Request-Response pattern

    master

    To perform a request-response exchange, use send with a reply handler. The recipient processes the message and then calls message.reply(replyBody) to send a response back to the original sender.

    Example Workflow:

    1. Sender: Calls eb.request(address, message, ar -> { ... }) or eb.request(address, message).onComplete(ar -> { ... }).
    2. Receiver: Receives the Message, processes it, and calls message.reply(result).
    3. Sender: Receives the reply in its handler.
    // Sender
    eb.request("service.address", "request data", reply -> {
      if (reply.succeeded()) {
        System.out.println("Received reply: " + reply.result().body());
      }
    });
    
    // Receiver
    eb.consumer("service.address", message -> {
      // Process logic...
      message.reply("response data");
    });
  5. Use Asynchronous Shared Maps for clustered data storage

    master

    An AsyncMap allows you to store and retrieve data locally or from any other node in a Vert.x cluster. This is ideal for session state in a farm of servers.

    Key Characteristics:

    • Asynchronous: Getting the map and performing operations returns a Future or uses a handler.
    • Clustered: In clustered mode, data is accessible across all cluster members. Note that latency may be higher than local maps due to network overhead.
    • Data Types: Keys and values must be immutable, implement io.vertx.core.shareddata.ClusterSerializable, or implement java.io.Serializable.

    Operations:

    • put(key, value): Asynchronously puts data into the map.
    • get(key): Asynchronously retrieves data from the map.
    • remove(key): Removes an entry.
    • clear(): Clears the map.
    • size(): Returns the number of entries.

    If you only need an asynchronous map that is restricted to the local node, use localAsyncMap instead.

    // Example of getting an asynchronous shared map
    // See examples.SharedDataExamples#asyncMap for implementation details
  6. Parse delimited or fixed-size records with RecordParser

    master

    The RecordParser is used to transform a stream of input buffers into a sequence of structured buffers. It supports two primary modes:

    1. Delimited Records: Parses records separated by a specific sequence of bytes (e.g., a newline \n).
    2. Fixed-size Records: Parses records based on a constant number of bytes.

    This is particularly useful for protocols where messages are separated by delimiters or have a known length, allowing you to handle fragmented data arriving over a network.

    // Example of delimited parsing logic (conceptual based on documentation description)
    // If input is: "HELLO\nHOW ARE Y", "OU?\nI AM", " DOING OK\n"
    // RecordParser produces: "HELLO", "HOW ARE YOU?", "I AM DOING OK"
  7. Handle QUIC connections and streams

    master

    QUIC multiplexes streams within a single connection. To interact with data, you must navigate the hierarchy: Server -> Connection -> Stream.

    1. Connections: Set a connectHandler on the QuicServer to be notified of new QuicConnection instances.
    2. Streams: On a QuicConnection, set a streamHandler to handle incoming QuicStream instances. Alternatively, you can set a stream handler directly on the server.
    3. Data: On a QuicStream, set a handler to receive data as io.vertx.core.buffer.Buffer instances.
  8. Implement a Hybrid HTTP Server (HTTP/1, 2, and 3)

    master

    A hybrid server can handle both TCP (HTTP/1.x and HTTP/2) and QUIC (HTTP/3) by binding to two ports:

    • A TCP port for HTTP/1.x and/or HTTP/2 traffic.
    • A QUIC port (UDP) for HTTP/3 traffic.

    Each port can be configured independently or can use the same port number (since TCP and UDP ports are distinct). To advertise HTTP/3 support to clients, it is recommended to emit an HTTP Alternative Services (Alt-Svc) notification using io.vertx.core.http.HttpServerResponse#writeAltSvc.

  9. Graceful shutdown and immediate close of TCP servers/clients

    master

    Vert.x provides two ways to stop a NetServer or NetClient:

    1. shutdown() (Graceful): Initiates a shutdown phase allowing protocol-level cleanup. It waits until all sockets are closed or a timeout occurs (default is 30 seconds). Each socket can be notified via a shutdown handler to perform a protocol-level close.
    2. close() (Immediate): Immediately closes all open connections and releases resources without a grace period. This is asynchronous; use the returned Future to know when the close is complete.

    If you create TCP servers/clients inside Verticles, they are automatically closed when the verticle is undeployed.

  10. The Golden Rule: Don't Block the Event Loop

    master

    Vert.x relies on non-blocking event loops to achieve high concurrency. If you execute blocking code inside an event loop handler, that loop cannot process any other events, potentially grinding your application to a halt.

    Examples of blocking behavior to avoid:

    • Thread.sleep()
    • Waiting on locks, mutexes, or monitors (e.g., synchronized blocks)
    • Long-lived database operations
    • Complex, CPU-intensive calculations
    • Spinning in a loop

    Vert.x automatically logs warnings and provides stack traces if it detects an event loop has been blocked for a significant amount of time. You can configure these warning settings via VertxOptions.

  11. Handle incoming TCP connections

    master

    To handle new connections on a NetServer, set a connectHandler. This handler is called with a NetSocket instance representing the connection.

    With the NetSocket, you can:

    • Read data: Set a handler on the socket. It will be called with a Buffer every time data is received.
    • Write data: Use write methods (operations are asynchronous).
    • Get addresses: Use localAddress() and remoteAddress().
    • Send files: Use sendFile to efficiently stream files or classpath resources directly via the OS kernel.
    • Handle lifecycle: Use closeHandler to be notified when a socket closes and exceptionHandler to catch errors on the socket.
    • Stream data: NetSocket implements ReadStream and WriteStream, allowing it to be used with Vert.x pipes.
    server.connectHandler(socket -> {
        socket.handler(buffer -> {
            System.out.println("Received: " + buffer.toString());
        });
        socket.write("Hello!");
    });
  12. Use AsyncFile for streaming and random access

    master

    The io.vertx.core.file.AsyncFile abstraction allows you to manipulate files asynchronously. Because it implements both ReadStream and WriteStream, you can use the pipe method to stream data between files and other stream objects like net sockets, HTTP requests/responses, or WebSockets.

    Random Access Writes

    Use AsyncFile#write(Buffer buffer, long position) to write data at a specific offset. If the position is greater than or equal to the current file size, the file will be enlarged to accommodate the offset.

    Random Access Reads

    Use AsyncFile#read(Buffer buffer, int offset, long position, int length, Handler<AsyncFile> handler) to read a specific number of bytes from a specific position into a buffer at a given offset.

    // Random access write
    asyncFile.write(buffer, position, handler);
    
    // Random access read
    asyncFile.read(buffer, bufferOffset, filePosition, length, handler);