Building a Thread-Based Web Server without using any third-party libraries or frameworks with Ruby
Tags: ruby, webdev, programming, tutorial
Published: 2023-06-03 00:03:59

Hi There!

Today, I'd like to showcase something I've been working on: a web server built entirely from scratch using Ruby. The goal isn't to present the project per se, but rather to share some insights and knowledge I've gained through this journey. I'll be explaining the architecture, its pros and cons, and an overall view of how it works.

This server is part of [MacawFramework](https://github.com/ariasdiniz/macaw_framework), an open-source Ruby framework designed to help developers create web applications with ease.

## **Server Architecture**

The server is built around Ruby's TCPServer class and incorporates a thread-based architecture. The ThreadServer class includes a base module, ServerBase, which is primarily responsible for handling the HTTP protocol. Together, these components provide a default implementation for our web server.

The code demonstrates usage of key Ruby features, such as the `Queue` class for thread-safe data handling and the `Mutex` class for synchronization. It also takes advantage of Ruby's OpenSSL library to integrate SSL security into the server.

```ruby
class ThreadServer
  include ServerBase

  def run
    @server = TCPServer.new(@bind, @port)
    @server = OpenSSL::SSL::SSLServer.new(@server, @context) if @context
    ...
  end
  ...
end
```

## **Thread-based Model**

The thread-based model is simple yet effective. Upon starting the server, it spins up a specified number of worker threads that handle client connections. Incoming connections are queued in a work queue (`@work_queue`), where they are then processed by the worker threads, ensuring fair scheduling and load distribution.

```ruby
def run
  ...
  @num_threads.times do
    spawn_worker
  end
  ...
end

private

def spawn_worker
  @workers_mutex.synchronize do
    @workers << Thread.new do
      loop do
        client = @work_queue.pop
        break if client == :shutdown

        handle_client(client)
      end
    end
  end
end
```

## **Thread Pool Management**

The server uses a Mutex (`@workers_mutex`) to safely manage the worker threads pool. A maintenance routine runs in a separate thread, checking the health of the worker threads every 10 seconds. It will respawn any threads that have died, ensuring consistent server performance.

```ruby
def run
  ...
  Thread.new do
    loop do
      sleep 10
      maintain_worker_pool
    end
  end
  ...
end

private

def maintain_worker_pool
  @workers_mutex.synchronize do
    @workers.each_with_index do |worker, index|
      unless worker.alive?
        if @is_shutting_down
          @macaw_log&.info("Worker thread #{index} finished, not respawning due to server shutdown.")
        else
          @macaw_log&.error("Worker thread #{index} died, respawning...")
          @workers[index] = spawn_worker
        end
      end
    end
  end
end
```

## **Graceful Shutdown**

The server is designed to gracefully shut down when required, making sure all pending connections in the work queue are processed before closing the worker threads and the server itself.

```ruby
def close
  shutdown
end

private

def shutdown
  @is_shutting_down = true
  ...
  @workers.each(&:join)
  @server.close
end
```

## **Limitations**

While the threading model is effective, it has some limitations due to Ruby's Global Interpreter Lock (GIL). The number of concurrent connections it can handle is limited by the number of worker threads. Additionally, slow clients could potentially tie up a worker thread, reducing the server's capacity. However, for JRuby and TruffleRuby users, this threading model can leverage true system-level threading due to the lack of a GIL, potentially providing better performance on multi-core systems.

Despite these trade-offs, the built-in web server in MacawFramework offers a good balance for most web applications, particularly for small to medium scale deployments. For larger-scale applications with high concurrency demands, consider supplementing the built-in server with an event-driven architecture or utilizing a third-party server solution better suited for such scenarios.

## **Conclusion**

In summary, the thread-based web server included in MacawFramework provides a straightforward, efficient, and secure solution for running your web applications, requiring minimal configuration and making deployment a breeze. It's a demonstration of how core Ruby features can be leveraged to build a simple yet robust web server.

I hope this journey through the creation of a thread-based web server was insightful. There's always more to learn and improve, so let's keep sharing and growing. Happy coding!