Building my next HTTP server, part 2
Tags: ruby, webdev, showdev
Published: 2020-02-18 20:14:49

_This is the second post of a series about my HTTP server_

On my first post of this series I explained how the plan for my HTTP server is for it to be asynchronous, so it makes the best use of I/O and CPU at the same time.

In order to be asynchronous, the problem I have to solve is that a call to [`TCPServer#accept`](https://ruby-doc.org/stdlib-2.5.0/libdoc/socket/rdoc/TCPServer.html#method-i-accept) or to [`TCPServer#read`](https://ruby-doc.org/core-2.5.0/IO.html#method-i-read) is a blocking call. This means that the method call will block the execution flow until there is something to be read from the other side.

If there are two clients connected to the server and one of the clients gets stuck, it may block the whole server. A single client must never be able to block a whole server!

There are a few technologies that can be used to solve this issue. But all of them have the same basic idea behind them. If we think about a blocking read operation, we can break it down into two pieces. Let's take those pieces to picture what could be the implementation of the `TCPServer#read` method:

```ruby
class TCPServer
  def read(length)
    wait_until_bytes_available(length)
    read_available_bytes(length)
  end
end
```

Of course those two methods being called are fictitious, only for the purpose of illustrating. The idea is that the `wait_until_bytes_available` method waits until `length` amount of bytes are available to be read from the wire. This is where the actual blocking occurs, as this method will only return when there is enough bytes to be read. If the client never sends more data, the method would not return. In reality, the method would return with an error state if the connection is broken.

After that waiting, the `read_available_bytes` method call then does the actual reading. It reads `length` amount of bytes from the wire without blocking and then returns those bytes.

Now, in order to have it working asynchronously, it's just a matter of removing the waiting part. That is done by removing the `wait_until_bytes_available` method call. Now we're only left with the actual reading method. And ruby, in fact, has a method just for that. It's the [`IO#read_nonblock`](https://ruby-doc.org/core-2.5.0/IO.html#method-i-read_nonblock)

The `#read_nonblock` method call takes one argument that is how many bytes should be read _at maximum_. That is, the method will never read more bytes than what was passed in the argument, but it can read less than it in case there just isn't enough bytes available to be read.

Now, the `read` method is not the only one that will block. We also have to solve the issue with the `accept` method. And that is very easy, because Ruby has the [`TCPServer#accept_nonblock`](https://ruby-doc.org/stdlib-2.5.0/libdoc/socket/rdoc/TCPServer.html#method-i-accept_nonblock) method! Also, which will be needed later, there is the non-blocking counterpart for the `write` method, which is the, you guessed it, [`IO#write_nonblock`](https://ruby-doc.org/core-2.5.0/IO.html#method-i-write_nonblock).

Those `*_nonblock` methods are the way to go from here. But they introduce a lot of other difficulties to be dealt with. For instance, what should be done if there is nothing to read from the wire right now? That doesn't mean there won't be anything in the near future. Also, since the `read_nonblock` method can read less bytes than specified in its argument, this would mean that the data can be received in small pieces. How to manage those pieces and process them together afterwards?

I intend to cover those issues in the next post of this series, so see you there!