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, 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.
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.
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.
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.
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!
Top comments (2)
Great post
Thanks!