DEV Community

tengxgfyrz67s
tengxgfyrz67s

Posted on

Creating-and-Running-a-Server

Creating and Running a Server

Project Code:https://github.com/hyperlane-dev/hyperlane

Introduction

The server is the heart of any web application. In Hyperlane, creating and running a server is designed to be straightforward while providing the flexibility needed for complex deployment scenarios. This article covers all the ways to create a server, how to start and stop it, and how to run multiple servers concurrently.

Server Creation Methods

Hyperlane provides three distinct ways to create a server, each suited to different use cases.

Method 1: Default Server

The simplest way to create a server is using Server::default(). This creates a server with sensible default settings:

let mut server: Server = Server::default();
let server_control_hook: ServerControlHook = server.run().await.unwrap_or_default();
server_control_hook.wait().await;
Enter fullscreen mode Exit fullscreen mode

The default server is perfect for development, testing, and simple applications. It uses predefined settings for address, request limits, and connection handling, so you can focus on building your application logic rather than configuration.

Method 2: From ServerConfig

When you need to customize server-level settings like the bind address, nodelay, or TTL, create a server from a ServerConfig:

let server_config: ServerConfig = ServerConfig::default();
let mut server: Server = Server::from(server_config);
Enter fullscreen mode Exit fullscreen mode

This approach gives you full control over the server's behavior. You can configure the address, TCP options, and other settings before the server starts. The Server::from() trait implementation handles the conversion automatically.

Method 3: From RequestConfig

If your primary concern is request-level settings (buffer sizes, body size limits, timeouts), create a server from a RequestConfig:

let request_config: RequestConfig = RequestConfig::default();
let mut server: Server = Server::from(request_config);
Enter fullscreen mode Exit fullscreen mode

This is useful when you want to fine-tune how the server handles individual requests without changing server-level settings.

Starting a Server

Once you've created a server instance, you need to start it. The run() method is the key:

let server_control_hook: ServerControlHook = server.run().await.unwrap_or_default();
Enter fullscreen mode Exit fullscreen mode

The run() method is asynchronous and returns a Result<ServerControlHook, ...>. The ServerControlHook is a control handle that lets you manage the server after it starts.

The ServerControlHook

The ServerControlHook provides two essential operations:

  1. wait(): Keeps the server running until it's explicitly shut down.
  2. shutdown(): Gracefully stops the server.
// Start and keep running
let server_control_hook: ServerControlHook = server.run().await.unwrap_or_default();
server_control_hook.wait().await;
Enter fullscreen mode Exit fullscreen mode

Keeping the Server Alive

After calling run(), you need to keep the server process alive. The wait() method on ServerControlHook blocks the current task until the server is shut down:

server_control_hook.wait().await;
Enter fullscreen mode Exit fullscreen mode

This is typically the last statement in your main function. Without it, the program would exit immediately after run() returns.

Graceful Shutdown

Hyperlane supports graceful shutdown through the ServerControlHook. When you call shutdown(), the server stops accepting new connections and finishes processing existing ones before stopping:

let server_control_hook: ServerControlHook = server.run().await.unwrap_or_default();
server_control_hook.shutdown().await;
Enter fullscreen mode Exit fullscreen mode

This is important for production deployments where you need to restart or stop the server without dropping in-flight requests. The shutdown process ensures that:

  1. No new connections are accepted
  2. Existing connections are given time to complete
  3. Resources are properly cleaned up

Attribute Macro Style

Hyperlane's attribute macro system provides a more declarative way to set up your server:

use hyperlane::*;
use hyperlane_macros::*;

#[hyperlane(server: Server)]
#[hyperlane(server_config: ServerConfig)]
#[tokio::main]
async fn main() {
    server_config.set_nodelay(Some(false));
    server.server_config(server_config);
    let server_control_hook: ServerControlHook = server.run().await.unwrap_or_default();
    server_control_hook.wait().await;
}
Enter fullscreen mode Exit fullscreen mode

In this style:

  • #[hyperlane(server: Server)] injects a server variable into the function scope
  • #[hyperlane(server_config: ServerConfig)] injects a server_config variable
  • You can configure the server directly before calling run()

This approach is particularly useful when combined with other attribute macros for routing, middleware, and request handling.

Running Multiple Servers

Hyperlane makes it easy to run multiple server instances concurrently using Tokio's spawn and join! macros. This is useful for serving different applications on different ports:

let app1 = tokio::spawn(async move {
    let mut server_config: ServerConfig = ServerConfig::default();
    server_config.set_address(Server::format_bind_address(DEFAULT_HOST, 80));
    let mut server: Server = Server::default();
    server.server_config(server_config);
    let server_control_hook: ServerControlHook = server.run().await.unwrap_or_default();
    server_control_hook.wait().await;
});

let app2 = tokio::spawn(async move {
    let mut server_config: ServerConfig = ServerConfig::default();
    server_config.set_address(Server::format_bind_address(DEFAULT_HOST, 81));
    let mut server: Server = Server::default();
    server.server_config(server_config);
    let server_control_hook: ServerControlHook = server.run().await.unwrap_or_default();
    server_control_hook.wait().await;
});

let _ = tokio::join!(app1, app2);
Enter fullscreen mode Exit fullscreen mode

In this example:

  • app1 runs on port 80
  • app2 runs on port 81
  • Both servers run concurrently as separate Tokio tasks
  • tokio::join! waits for both tasks to complete

The Server::format_bind_address(DEFAULT_HOST, port) helper method makes it easy to construct bind addresses programmatically.

Use Cases for Multi-Server

Running multiple servers is useful in several scenarios:

  1. HTTP and HTTPS: Run an HTTP server on port 80 and an HTTPS server on port 443.
  2. API versioning: Serve different API versions on different ports.
  3. Microservices: Run multiple small services within the same process.
  4. Health checks: Run your main application on one port and a health check endpoint on another.

Complete Example

Here's a complete example that demonstrates the full lifecycle of creating, configuring, running, and shutting down a server:

use hyperlane::*;

#[tokio::main]
async fn main() {
    // Create server with custom configuration
    let mut config: ServerConfig = ServerConfig::default();
    config.set_address("0.0.0.0:8080");
    config.set_nodelay(Some(true));
    config.set_ttl(Some(128));

    let mut server: Server = Server::from(config);

    // Start the server
    let server_control_hook: ServerControlHook = server.run().await.unwrap_or_default();

    // Server is now running and accepting connections
    // In a real application, you might register routes and middleware here

    // Keep the server alive
    server_control_hook.wait().await;
}
Enter fullscreen mode Exit fullscreen mode

Error Handling

When starting a server, the run() method returns a Result. The unwrap_or_default() call provides a default ServerControlHook if the server fails to start:

let server_control_hook: ServerControlHook = server.run().await.unwrap_or_default();
Enter fullscreen mode Exit fullscreen mode

In production code, you should handle the error explicitly:

match server.run().await {
    Ok(hook) => {
        println!("Server started successfully");
        hook.wait().await;
    }
    Err(e) => {
        eprintln!("Failed to start server: {:?}", e);
    }
}
Enter fullscreen mode Exit fullscreen mode

Common reasons for server startup failures include:

  • Port already in use
  • Insufficient permissions to bind to the address
  • Invalid configuration values

Conclusion

Creating and running a server in Hyperlane is designed to be simple yet flexible. Whether you need a quick default server for development or a carefully configured multi-server setup for production, Hyperlane provides the tools you need. The ServerControlHook gives you clean control over the server's lifecycle, and the attribute macro system offers a declarative alternative for those who prefer it.

In the next article, we'll explore Hyperlane's middleware system, which is essential for implementing cross-cutting concerns like authentication, logging, and CORS.


Project Code:https://github.com/hyperlane-dev/hyperlane

Top comments (0)