Ruby Programming Language Enables Concise Network Programming

ometimes runtime performance requirements determine which programming language I use. For flat out runtime performance, I have always found that natively compiled Common Lisp or C++ really do the trick. For large-scale Web applications, I usually use Java because of its wealth of infrastructure software.

For other projects, I find myself using the Ruby programming language more and more. Not only is Ruby a high-performance programming language that minimizes software development and maintenance costs, but writing software in it is just fun! Ruby is a very concise programming notation, so you can write programs quickly. The readability of Ruby code also makes it easy to make changes later.

I currently use Ruby for small text-based utility programs and network programming, and I use Ruby on Rails for some Web application development. This article concentrates on using Ruby for network programming and provides examples for Web service implementations with Ruby. Don’t worry if you’re new to Ruby. You really don’t need to know much about it in order to get something from this tutorial. The Related Resources section in the left column offers links to other Ruby tutorials and books for further reading.

Simple Server-Side Ruby: a Web Server

The Ruby language is supported by a rich built-in library. This first example uses the TCPServer class to open a server socket on port 9090 and waits for incoming connections. The goal is to be able to handle HTML GET requests like http://127.0.0.1:9090/foo?bar=123.

The following Ruby code opens a server TCP socket, accepts incoming session requests, and writes each incoming request back to the user’s browser:

Listing 1
require 'socket'server = TCPServer.new('127.0.0.1', 9090)while (session = server.accept) request = session.gets puts request session.print "HTTP/1.1 200/OK Content-type: text/html " session.print "Response from Ruby Web server " session.print "request was:" session.print request session.print "" session.closeend

The line “puts request” prints out incoming browser requests:

GET /foo?bar=123 HTTP/1.1GET /favicon.ico HTTP/1.1

The second line printed because my browser (Safari on OS X) requested a “favorite icon” for this Web site. Fetching this response also would be simple without using a Web browser but instead using some simple Ruby TCP client code (here I use the “open-uri” library, which allows a remote resource identified by a URI to be accessed like a local file):

Listing 2
require 'open-uri' # allows the use of a file like API for URLsopen("http://127.0.0.1:9090/foo?bar=123") { |file| lines = file.read puts lines}

The output would be:

Response from a very simple Ruby Web serverrequest was:GET /foo?bar=123 HTTP/1.1

While extending this simple Web server to return local HTML files, etc. would be trivial, it wouldn’t make much sense: the standard Ruby library contains the flexible WEBrick Web server toolkit. If you want to set up a full-featured Web site using pure Ruby, use WEBrick! Using the standard library, you can create a full service Web server with just a few lines of code (from the WEBrick documentation):

Listing 3
require 'webrick'include WEBricks = HTTPServer.new(:Port => 9090, :DocumentRoot => Dir::pwd + "/htdocs")trap("INT"){ s.shutdown }s.start

Sometimes writing a simple Web server from scratch in Ruby is useful. Instead of returning HTML, a simple TCP socket server can return XML data. The next section illustrates this idea with the implementation of a REST-based Web service.

Implement a REST-Based Web Service

Representational State Transfer (REST) is a simple means for implementing Web services. While REST also supports HTTP authentication and PUT requests for sending data to a Web service, the examples in this section assume only the use of HTTP GET requests for Web services. The first simple example is a REST-based Web service that adds two numbers. The goal is to handle GET requests that look like this:

GET /add?x=123&y=9 HTTP/1.1

Since Ruby has great built-in support for regular expressions, you can start up the Ruby interactive interpreter IRB and experiment with a regular expression matcher until you get one that works:

Listing 4
irbirb(main):001:0> s="GET /add?x=123&y=9 HTTP/1.1"=> "GET /add?x=123&y=9 HTTP/1.1"irb(main):002:0> re=/x=(d+)&y=(d+)/=> /x=(d+)&y=(d+)/irb(main):003:0> x = re.match(s)=> #irb(main):004:0> x[1,4]=> ["123", "9"]irb(main):005:0> x[1]=> "123"irb(main):006:0> x[2]=> "9"irb(main):007:0>

Now you are ready for a server that is similar to the first example, but this example returns an XML payload instead of HTML:

Listing 5
require 'socket'server = TCPServer.new('127.0.0.1', 9090)re=/x=(d+)&y=(d+)/while (session = server.accept) request = session.gets numbers = re.match(request) answer = numbers[1].to_i + numbers[2].to_i session.print "HTTP/1.1 200/OK Content-type: text/xml " session.puts " " session.closeend

You could use the test client in Listing 2, but it is not a practical example. So you should change it slightly to accept a text string as an argument. You will need to URL encode the text string using code like this:

require 'CGI'CGI.escape("1 2 3://:$")

This produces: “1+2+3%3A%2F%2F%3A%24“.

The following example looks for a GET request like this:

GET /query=Why+did+the+dog+bite+the+cat%3F HTTP/1.1

Here, the query is URL encoded. The following example in Listing 6 simply finds the sub-strings “query=” and “http” and eats the text in between them as the input text:

Listing 6
require 'socket'require 'CGI'server = TCPServer.new('127.0.0.1', 9090)while (session = server.accept) request = session.gets puts request index = request.index("query=") + 6 index2 = request.index(" HTTP") query = request[index..index2] puts query answer = CGI.unescape("I don't know the answer to: " + query) session.print "HTTP/1.1 200/OK Content-type: text/xml " session.puts "" + answer + "" session.closeend

Implement an XML-RPC-Based Web Service

First, you need to write a simple XML-RPC client. You frequently will want to access other services that support an XML-RPC interface, so all you need to know is how to write clients. (However, you shortly will see how to also write XML-RPC services in Ruby.)

The Ruby built-in XML-RPC library automatically marshals values in Ruby variables to and from XML. This example uses string arguments, but arguments could also be numbers, arrays, etc. The following client accesses the services “upper_case” and “lower_case” from a server that supports XML-RPC and implements these services:

Listing 7
require 'xmlrpc/client'server = XMLRPC::Client.new("127.0.0.1", "/RPC2", 9090)puts server.call("upper_case", "The fat dog chased the cat on Elm Street.")puts server.call("lower_case", "The fat dog chased the cat on Elm Street.")

I have written XML-RPC services in Java, Common Lisp, Python, and Perl. I think that, using the WEBrick classes, Ruby’s XML-RPC library is the easiest to use. Here is server code that would process calls for the code in Listing 7:

Listing 8
require 'webrick'require 'xmlrpc/server.rb'# create a servlet to handle XML-RPC requests:servlet = XMLRPC::WEBrickServlet.newservlet.add_handler("upper_case") { |a_string| a_string.upcase }servlet.add_handler("lower_case") { |a_string| a_string.downcase }# create a WEBrick instance to host this servlet:server=WEBrick::HTTPServer.new(:Port => 9090)trap("INT"){ server.shutdown }server.mount("/RPC2", servlet)server.start

You can add any number of named handlers to a WEBrickServlet. These handlers can take zero or more arguments.

Implement a SOAP-Based Web Service

First, you write a simple SOAP client. You frequently will want to access other services that support a SOAP interface, so all you need to know is how to write clients. (However, you shortly will see how to also write SOAP services in Ruby.)

The Ruby built-in SOAP library automatically marshals values in Ruby variables to and from XML. The following client accesses the services “upper_case“, “lower_case“, and “times_string” from a server that supports SOAP and implements these services:

Listing 9
require 'soap/rpc/driver'stub = SOAP::RPC::Driver.new("http://127.0.0.1:9090", "http://markwatson.com/Demo")stub.add_method('upper_case', 'a_string')stub.add_method('lower_case', 'a_string')stub.add_method('times_string', 'a_string', 'a_number')puts stub.lower_case("Jack and Jill went up the hill.")puts stub.upper_case("Jack and Jill went up the hill.")puts stub.times_string("Jack and Jill went up the hill.", 2)

The code specifies that the SOAP server is at IP address 127.0.0.1 (localhost) and listens for connections on port 9090. It specifies “http://markwatson.com/Demo” as a name space (must match what the server uses).

The server requires a class that implements the exposed methods “upper_case“, “lower_case“, and “times_string“; so you first implement DemoClass, which has these three methods:

Listing 10
require 'soap/rpc/standaloneserver'# define a class that has three methods to call remotely:class DemoClass def upper_case(a_string) a_string.upcase end def lower_case(a_string) a_string.downcase end def times_string(a_string, a_number) a_string * a_number endend# create a SOAP enabled server for the methods in DemoClass:class MyServer < SOAP::RPC::StandaloneServer def on_init demo = DemoClass.new add_method(demo, "upper_case", "a_string") add_method(demo, "lower_case", "a_string") add_method(demo, "times_string", "a_string", "a_number") endendserver = MyServer.new("Demo", "http://markwatson.com/Demo", "0.0.0.0", 9090)trap('INT') { server.shutdown }server.start

In Listing 10, after the code defines DemoClass for implementing the Web services, it sub-classes the SOAP standalone server class; the class on_init method adds methods to the class.

If you have ever written SOAP clients and services in other programming languages, you will appreciate how easy it is to use Ruby's SOAP libraries.

The Wrap-Up

For most applications, I like to use XML-RPC because it is lighter weight than SOAP and has implementations for more programming languages. REST is great for some applications that map well to a function-type call (think of the query URL as a function followed by arguments) returning an XML payload.

This article contained a lot of code, but hopefully you both understood the examples and ran the example code snippets without any problems. Want to go further with network programming in Ruby? Review the Related Resources section in the left column for more detailed information on the topics this article lightly covered.

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

Related Posts