Ruby Simplicity

Now, I am not an expert at Ruby. I know enough of the language to make simple scripts work, and I keep my trusty pickaxe handy in case of (frequent) emergencies. What I needed was a http server that posted back a series of XML files (based on a timestamp in the XML) to the requester to simplify testing of the system. A really simple app is all that is needed. Here it is:

require ‘webrick’ include WEBrick # get directory to get files from dir = (ARGV[0] || “.”) # change directory, read in files Dir.chdir(dir) puts “Processing files from directory [#{Dir.getwd}]” filenames = Dir.glob(”*.{xml,XML}”).sort files = Array.new for x in 0…filenames.size puts “Adding file: #{filenames[x]}” files[x] = File.read(filenames[x]) end # filecount is used for handling the rotation of files filecount = 0 # Create the server, add the servlet s = HTTPServer.new( :Port => 8080 ) s.mount_proc(”/FileServ”){|req, res| res['Content-Type'] = “application/xml” res.body = files[filecount % files.size] filecount += 1 } # start the server trap(”INT”){ s.shutdown } s.start

Thats it. 31 lines including whitespace and comments (20 actual source lines). Does exactly what I needed, with an optimization of making the XML filenames include the date for easy sorting (used same technique in the Java example as well). It should also be noted that this is a standalone script… I can just double-click on it in windows and its running.

Now lets look at the Java code:

package net.secosoft.fileserv; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FilenameFilter; import java.io.IOException; import java.io.OutputStream; import java.util.Arrays; import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class FileServlet extends HttpServlet implements Servlet { static int fileCount = 0; static byte[][] files = null; public FileServlet() { super(); } public void init(ServletConfig config) throws ServletException { super.init(config); File dir = new File(this.getInitParameter(”filesPath”)); File[] fileList = dir.listFiles( new FilenameFilter() { public boolean accept(File dir, String name) { return name.endsWith(”.xml”); } }); Arrays.sort(fileList); files = new byte[fileList.length][]; for(int f = 0; f < fileList.length; f++ ) { try { files[f] = load(fileList[f]); System.out.println("File [" + fileList[f].toString() + "] loaded."); } catch(IOException ioe) { System.out.println("Error loading file [" + fileList[f].toString() + "]" ); } } } protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int currentFile = fileCount % files.length; response.setContentType("application/xml"); response.setContentLength(files[currentFile].length); OutputStream out = response.getOutputStream(); out.write(files[currentFile]); out.flush(); fileCount++; } private byte[] load(File file) throws IOException { BufferedInputStream in = new BufferedInputStream( new FileInputStream(file)); byte[] buffer = new byte[4096]; ByteArrayOutputStream baos = new ByteArrayOutputStream((int)file.length()); while(true) { int len = in.read(buffer); if ( len == -1 ) break; baos.write(buffer,0,len); } return baos.toByteArray(); } }

84 lines with comments and whitespace, 49 without (comments, whitespace or import statements). But wait, theres more! This is a servlet… we cannot forget about our trusty web.xml file! Add an extra 17 for that… and the fact that I had to deploy this to a server. Thank goodness Eclipse has a export War tool that can put the war file directly in Tomcat’s webapps directory, otherwise I would have had an Ant script as well.

Before you all go crazy trying to count the lines of code, understand that I only removed the whitespace and commented lines from the above blocks and then counted what remained. I coded these as I always code, so while there are obvious ways of shrinking the number of lines for each of them, I think the loc : expressiveness ratio would decrease. Again, thats subjective, and this is my code the way I write code, so just take this as an example, not a rigorously controlled test scenario.

Given that, both code blocks are pretty straightforward. Ruby, with over 50% less lines (21 vs 49), has a major advantage above just LoC: the fact that I do not need to set up a servlet container (not that that is hard), nor do I need to go through the extra step of deploying a WAR file to said container. The point is: there is simplicity there… And for what I needed: perfect.

It took me only about 30 minutes to make the java version (already had servlet container set up, etc). The Ruby one took quite a bit longer, though the reasons for that are solely my inexperience with the language and its APIs. I think the code was mostly done in around 10 minutes, but it took a lot of time to find simple little things that I already learned how to avoid a long long time ago in Java.

Also, I’m sure that I’ve missed a couple of usefull shortcuts in both the Ruby and the Java code… if you have pointers for me I always appreciate learning something new.