Code Project: Create a web server in Ruby
Most of us are aware of Ruby through its modern-day Ruby on Rails incarnation, which is a framework for developing large-scale websites. This has been used on talked-about websites such as the Basecamp, Jobster and 43 Things sites and is shipped with the latest version of OS X, Leopard.
Given Ruby's very recent step into the coding limelight, it's surprising how long the language has been in development: since 1993! Created by Yukihiro Matsumoto, Ruby was first released to the world in 1995, and was designed to reduce the menial work that programmers typically have to put in. Why should we have to battle with the syntax of a language when what we want to achieve is really very simple?
With this in mind, Matsumoto made sure that Ruby followed the principle of least surprise; it should behave as the programmer expects, without strange rules or corner cases. As a C++ programmer, Matsumoto found that he was still being surprised with the language after two years of learning, and decreed that: "We need to focus on humans, on how humans care about doing programming or operating the application of the machines. We are the masters. They are the slaves."
Enough history - let's get coding. Ruby is described as a 'multi-paradigm' language, which sounds hideous but actually has some meaning: in Ruby, you can program with object orientation in mind, or fall back to functional and procedural programming. It's an interpreted language, with a syntax similar to Perl and Python, making it pretty easy to grasp.
You can even create Gnome apps in Ruby with the Ruby-Gnome2 bindings.
First off, you'll need to install the Ruby interpreter, which should be in your package manager's repositories. You'll also want to install Irb, the interactive Ruby interpreter, which is supplied in a separate package in some distros.
Once these things have been installed, fire up an interactive Ruby session by entering irb at a shell prompt. This will pop up a prompt for you to start entering Ruby statements. It's almost law that every coding guide starts with a Hello World instruction, but we'll bend the rules a little here. Run
puts "Goodbye moon!"
That's pretty self-explanatory - puts outputs the supplied string to the screen. However, one of the most crucial features of Ruby to note is that everything is an object. Yes, even literal strings. It's pretty weird, but it enables you to do things like
Enter this, and you'll see 10 printed as the result. Or you could try:
Which returns 5, the position in the string that the letter 'd' appears. (Note that Ruby uses zero as the first item in a list, not 1.)
So we've seen that Ruby is an object utopia that can massively simplify programming. It's also a dynamically-typed language, so you don't need to declare variables before use - take the following example of getting input. As before, just enter these lines of code into the irb prompt:
puts "Do you love Ruby yet?" answer = gets puts "You answered " + answer
The first line prints out the question text, then the second line declares a variable called answer, and assigns it to the result of gets. As you'd expect, gets is a command to grab a string from the keyboard, so when you enter the second line of this code it'll prompt you for some text entry. The third line then prints a string of text followed by your result.
Line three of this code also illustrates variable handling in Ruby. You can join up strings with a simple + command, and even use multiplication, like this:
mystring = "Some" + "text" puts mystring anotherstring = "Wowzers" * 10 puts anotherstring
Here, the first two lines demonstrate joining together strings, while the last two lines show how easy it is to manipulate text in Ruby. anotherstring ends up being ten copies of Wowzers in a row. As you can imagine, this easy handling of text is one of the reasons why Ruby is gaining ground in the website programming world.
It's worth knowing about chomp, which you may have seen before in Perl. Basically, if you make your Ruby program input a string with gets, it'll store the carriage return character when the user presses Enter (denoted as \n). You can remove this by using string.chomp, where string is the variable containing the entered text. This is also useful when we're dealing with numeric variables; because Ruby is object-tastic, we can say
x = gets.chomp.to_i
Here we want a raw number - no strings, no newlines, nothing else. So we create the x variable, call gets on it, but qualify that with a chomp to strip the newline character, and finally use to_i to convert it into an integer value. This means x contains just a number and nothing else. Then we can test the contents of the variable with
x = gets.chomp.to_i x.is_a?(Integer) x.is_a?(String)
This shows that it's an integer, but not a string. Of course, because Ruby is dynamically typed, we can immediately reuse x as a string variable if need be.
Conditionals and flow
So, that's variables and input/output sorted out; let's move on to program control. For the rest of the code in this feature, you'll need to enter it into a text file and run it with ruby <filename> - we won't be using the irb tool any more. For an example of this, let's look at if statements and code blocks.
First, enter the following into a file called test.rb, then run it with ruby test.rb at the shell prompt: puts "How many kittens do you have?"
x = gets.chomp.to_i if x > 0 # More than zero kittens? puts "You have " + x.to_s + " kittens" else puts "Aww, you have no kittens!" end
In the first two lines of this program, we ask the user for some input, then convert the entered string into an integer using the to_i method described above. Then we move on to the if block: this is made up of an if, which tests to see if the value entered by the user is greater than zero and prints out how many kittens the lucky user has if that condition is true. Note the use of to_s in the puts line - this time, we're converting the numerical variable into a string, so that we can join it easily with the other words.
If the user has entered a value of 0, the else section kicks in, and we print out a different message. Then we terminate the code block with end. As you can see, it's very simple to grasp and works just as you'll have seen in other languages. One point of note: this is the first time we've used indentation. In Ruby, as with most languages, it's common practice to use tabs to indent your code for readability, although you can switch to double spaces or similar if you prefer.
You'll also notice that on the if line we have a hash (#) character, which denotes a comment. Making loops is a doddle - you should have no problem working out what the following code does:
10.times do puts "Ruby rocks" end
Yep, it prints the string ten times. while loops are just as simple:
x = 0 while x != 15 puts "Please enter 15" x = gets.chomp.to_i end
This code prints out a string and gets user input until the user enters the number 15. It then breaks out of the while/end loop and continues to execute (although in our case, we don't have any other code!).
Modularity is the keystone of all good high-level programming, so let's look at creating procedures. As mentioned earlier, Ruby is an object-oriented (OO) language; if you're familiar with C++, C# or Python you'll feel right at home here. But even if you're from a regular C background, you don't have to worry about the complexities of OO - well, at least not for simple programs.
Here's an example of creating a function (as before, save this into a text file and run it with ruby <filename>):
def doubler(value) value = value * 2 return value end puts "Enter a number" x = gets.chomp.to_i y = doubler(x) puts y.to_s
The first four lines of this program are a function, so they're not executed when the program begins. (Note that functions have to be declared before they're called in the program, otherwise Ruby will throw up errors.) Here we create a function called doubler, which takes a number as its input, multiplies it by two, and then returns the result of the multiplication. The program is trivial in purpose, but illustrates the use of functions with call and return values.
Ruby actually starts to execute this program at the puts line. We grab a number from the user and place it into the x variable, then pass x to the doubler function we wrote at the top. doubler returns a new number, which we store in y, and lastly we print y to the screen. Creating objects in Ruby is beyond the scope of this guide, but if you want to find out more, see the URLs in our Resources box, below.
Do it clean
Exception handling under Ruby is blissfully easy. You can trap errors with a few lines of code, as demonstrated in this program:
puts "Divide 5 by what?" x = gets.chomp.to_i begin y = 10 / x rescue ZeroDivisionError puts "Eeep, divide by zero!" end puts y.to_s
Here we get the user to enter a number, and then start a new begin/end block with a rescue section inside it. So, say the user has inputted 5 at the prompt: we tell the y variable to store the result of ten divided by the user-inputted 5, and after the begin/end block, print out y. In this case, ten divided by five is two, as printed when you run the program.
But what happens if you enter 0 (zero) when you run this program? The y = 10 / x line would quickly send the program to errorland - you can't divide a number by zero. Normally this would just land you back at the shell prompt with a lovely error message to decipher.
However, we avoid all this hassle by using the rescue line. Basically, this takes an exception or error condition, and runs the code beneath it if such an error occurs in the current code block. So, in our case, the rescue kicks in if a divide-by-zero error occurs in the begin/end block, and if so, it prints out our own 'Eeep' error message.
If you're not familiar with exception handling, you'll find it supremely helpful and time-saving. You don't need to leap around your code like a gibbon, adding checks all over the place to make sure the program won't crash. Instead, you can neatly organise error-trapping code in a compact and highly readable fashion. You can use this method to catch many kinds of runtime errors, check if files have opened correctly, and so forth.
Writing a web server
Right, let's combine all the things we've learned and write our own web server! Sounds a bit extreme, doesn't it? But it's actually quite simple in Ruby, thanks to the socket library, which simplifies matters enormously. Of course, feature-wise our web server won't pose any challenge to the mighty Apache right now, but it demonstrates many of Ruby's features and is more than capable of chucking out regular HTML files.
If you just need to pass some web pages over your home network, why install and configure a full web server? Our new-found Ruby skills will solve the problem faster than you can say "/etc/apache/httpd.conf"...
Enter the following code into a file called webserver.rb and save it in your home directory. Alternatively, if you're feeling lazy or don't learn by typing, you can just download the code here. Also, create two HTML files in your home directory: index.html and test.html, which we'll use to confirm that our server is working correctly. Just put anything you like into these HTML files - anything that will display in a web browser.
require 'socket' webserver = TCPServer.new('127.0.0.1', 7125) while (session = webserver.accept) session.print "HTTP/1.1 200/OK\r\nContent-type:text/html\r\n\r\n" request = session.gets trimmedrequest = request.gsub(/GET\ \//, '').gsub(/\ HTTP.*/, '') filename = trimmedrequest.chomp if filename == "" filename = "index.html" end begin displayfile = File.open(filename, 'r') content = displayfile.read() session.print content rescue Errno::ENOENT session.print "File not found" end session.close end
Once you've got this code, enter ruby webserver.rb in your home directory and open up your web browser. Now go to http://127.0.0.1:7125 and see the results. If all is well, you'll see the index.html file you created earlier on the screen, just like it was being sent out by any other web server!
Or, if you use the URL http://127.0.0.1:7125/test.html you'll see the contents of test.html. Most of the code is self-explanatory, but we'll go through each part to fully clarify it.
We start with a require, which tells Ruby that we need to make use of an external library for additional functionality. In this case, we're making use of the socket library - a bunch of routines to make network programming easier. See the What A Gem box, below, for more information.
What a Gem
Ruby is supplied with a range of add-on modules to extend the versatility of the language, in a similar fashion to C libraries or Python modules. Where they are located will depend on your distro and the version of Ruby you have installed, but in Ubuntu, for example, you can see the included modules somewhere under /usr/lib/ruby/.
If you look in that directory, you'll see that modules are supplied for such tasks as network programming, using zlib compression, writing Ncurses-based console apps, and more. You can bring the functionality of these libraries into your programs with the require line, as demonstrated in the web server project towards the end of this article.
You can get more modules for your Ruby installation via the RubyGems system, which is analogous to Perl's CPAN. There's a wealth of extra Ruby goodness out on the net - MySQL database interfacing, Gnome and KDE bindings, ImageMagick support, and much more. It's getting to the stage now where nigh-on any major desktop app could be implemented in Ruby, given some development time. For more information, check out the RubyForge link in our Resources at the end.
RubyForge: the ultimate website for Ruby applications and libraries.
In the second line we declare a new server object for the local machine (IP address 127.0.0.1), operating on port number 7125. The port number is arbitrary; you can change it to anything you like, although you'll have to run webserver.rb as root if you want to run it on a port under 1000 (such as 80, the normal web server port).
So now we've set up a local network server, but we have to turn it into a server that web browsers will understand. On the third line of code we set up a while loop, which handles server requests until Ctrl+C is pressed in the terminal window.
In this while block, our program sends out the mandatory data that all web browsers expect - a two-line response to the browser's request, saying that we have received the request correctly and will now send back some HTML. The \r and \n bits mean 'carriage return' and 'new line' characters respectively.
The following three-line code chunk converts the web browser's request into something we can use. A normal web browser will send something like this to the server: GET /filename.html HTTP/1.1, but we just want the actual filename and nothing else. So we store the web browser's request (session.gets) into our request variable, then perform a couple of gsub operations, which use regular expressions to strip out the GET and HTTP/1.1 parts. Think of gsub a bit like the command line utility sed - a quick way to replace or strip out specified parts of string variables.
This still leaves a trailing carriage return character, though, so we get rid of that with a chomp and store it in the filename variable. Most web servers default to index.html if no filename is specified, and the if block does just that: if the filename string is empty, fill it with index.html. You could, of course, change this to a different filename if you want an alternative default page.
On to the begin block of our code. Here, we attempt to open the filename specified in read-only mode, and then, using the read() method, grab the contents of that file into our originally-named content variable. Next we send the file's contents out to the web browser with the session.print line. However, we can't be guaranteed that this worked - what if the file name wasn't found? We check for this possible error with the rescue line, which sends an error message to the browser if we can't read the specified file.
Lastly we close the code block. Surprisingly simple, wasn't it? Naturally, there are limitations to our web server - eg it only handles HTML files - but it's a good structure to flesh out. Most importantly, it demonstrates Ruby in a real-world application, and how wonderfully readable and simple the code is.
The end result of our project: a simple but fully functioning web server!
There's so much more to explore in Ruby, and as Ruby on Rails continues to soar in popularity, no doubt we'll be hearing a lot more about the language in the near future. It's unquestionably one of the best languages to learn if you've got a background in C or Basic, and want to tackle object orientation with a clear and meaningful syntax.
In the meantime, check out our Resources box, below, for links to Ruby tutorials and further information - and if you write a cool little app in the language, make sure you pass the word around and share it with other people!
First published in Linux Format magazine