The Big Project
So, assuming you’ve read the previous parts of this series, you should have a somewhat working knowledge of the various parts of Celluloid.
But, so far, we haven’t really covered how all of these parts come together to form a complete application. We’re going to do that here!
So, let’s dive in!
What is it?
What will be our objective? Its actually quite simple: we’ll build an HTTP server.
More precisely, we’ll build a very incomplete, yet easily extendable, HTTP server written in Ruby with the Celluoid library.
Don’t expect it to be something like Apache or Thin – but, we’ll learn a ton in the process about Celluloid and HTTP, which (arguably) is the important networking protocol for developers to understand.
A Little About HTTP
Of course, if we want to build an HTTP server, we need to be in the know about what HTTP is and how it functions.
In case you don’t know, HTTP is a protocol that is used to transfer HTML from server to client (this isn’t always the case, but it is the most common), and the most common, for the client to tell the server to do certain things.
The protocol is based on requests. So, the client can issue a “GET” request to the server in order to get the HTML for a certain page. Also, the client can issue a “POST” request to provide some data to the server.
An important point about HTTP is that the protocol itself is stateless. This means that each request has no knowledge of any previouse requests.
We will only implement the GET request; this is for two reasons. Firstly, it is a core function of the HTTP server. Second, it is very easy to implement.
But, we will write our code so that it is modular enough for other methods to be added quite easily.
First of all, let’s build a quick and dirty prototype.
Ruby has built in support for socket communication, which is all attained with the simple “require ‘socket'” statement. Using that, and all the magic from Celluloid, here is our prototype:
We have an
HTTPServer class, which revolves under its “start” method. This method simply starts a server, using the
TCPServer class from the socket module).
Then, we do
@server.accept. This is very important, because this is a blocking call. Meaning that work won’t move any further until a client has come in to be served.
Then, we simply write the HTTP headers and HTML (regardless of what type of request we’ve received.)
Of course, there’s an aparent problem. This isn’t concurrent. Since all of the calls we’re using block, we’re just doing each client synchronously. That’s no good!
Taking it Async
The solution to this dilemma comes in the form of writing another actor.
Here’s the code:
That’s pretty big to process at once, but we’ll simply take it step by step.
AnswerActor is just another actor – it creates a new thread when an instance of it is created. The real work is happening in the
Here, we get a hold of the client socket (with which we can talk to the client) and write the header and HTML to it and then just wait around.
HTTPServer class (which is also an actor!), we call
aa.start!, which means that
start is called asynchronously, so, we can move right along to the next client.
To test if this really is working, open up two telnet (or netcat, which is my favorite) sessions to our homebrew HTTP server, and you should see that you get a response on both (which is also the reason why we have the loop at the end of the start method in the AnswerActor class – you have to be able to see the parallel connections to believe them!)
But, we’re not being too efficient with this. Our actors are kind of just sitting around once they’re created, eating up resources. What can Celluloid do for us?
Going for a Swim
We can use pools! Get it, that’s why I called this section “going for a swim”! No? Well, on to the code:
If you look at the modified code in
HTTPServer.start, it is quite clear. We simply create a pool of 50 actors (and, therefore, 50 threads) which we can then assign work. All of that with one line of code. That’s awesome!
Why 50? Well, just a random number I picked. There’s been some good work behind selecting optimal sizes of thread pools, if you’re interested.
In case an exception occurs for an actor within the pool, it will be restarted automatically and ready to use the next time it is needed!
So far, we’ve only spit back just a pair of html tags – we haven’t actually listened to what the user is requesting. Let’s work that in.
Again, there are a number of changes. We’ve added the
Query class, which has a type, url and other attributes to it, representing an HTTP request.
As an example, a GET request looks like “GET /index.html HTTP/1.1”, where /index.html is the URL being requested and HTTP/1.1 is the protocol being used (as opposed to HTTP/1.0).
AnswerWorker, we call a
process_get method if the query type is GET.
But, we haven’t defined what exactly
process_get should do – let’s make a simple way to view files inside a certain folder.
Answering the GETs
Beware, this is a horrible implementation of
process_get, but, it is meant to be very simple and straightforward since it really has nothing to do with the scope of this article, which is to learn more about Celluloid.
Here it is:
It simply takes the “server” folder (in the same folder as the HTTP server’s source code itself), and returns the contents of a given file.
And, we’ve also sorted out the way we handle what type of request has been sent by the client, to make the server easy to extend.
Note that, because of the simple way this has been done, directly plugging in 127.0.0.1:3000 into your browser will not work – you will have to use 127.0.0.1:3000/index.html or some other filename!
Wrapping it Up
We’ve built a (very) rudimentary HTTP server using Celluloid.
In doing so, we’ve seen how actors can work together in simple situations.
Of course, you will write applications larger than this one using Celluloid (I sure hope so!) and you probably will use more advanced features. Remember, however, that the basics are very important. I hope seeing some of the concepts come together made the learning easier.
If you liked the article, do Tweet it out to your followers. Thanks for reading!