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.
Mark Derricutt | 15-Sep-05 at 3:10 am | Permalink
Lets just say you had jetty on the classpath, had a blanket ‘throws Exception’, and used FQN class references everywhere, how much smaller could you get the java code?
Part of me also wonders about the scenario where webrick ISN’T installed on the system, what would you need to do in order to get your ruby application web-enabled? Configuration for Apache? IIS?
(To be honest, I’m not sure if there ever would be a scenario where webrick isn’t available, I just apt-get’d ruby on my Unbuntu machine and it was there so maybe its an “official” library?)
m4rkusha | 15-Sep-05 at 3:27 am | Permalink
files = Array.new
for x in 0…filenames.size
puts “Adding file: #{filenames[x]}”
files[x] = File.read(filenames[x])
end
the above can be reduced to (not tested):
puts “Adding file: #{filename}”
File.read(filename)
end
Mark Derricutt | 15-Sep-05 at 4:42 am | Permalink
Matt, having a few issues getting the trackback working, but I posted a version using an embedded Jetty webserver over on my blog…
Jason Bell | 15-Sep-05 at 5:12 am | Permalink
It’s swings and roundabouts really. I don’t see how the comparison acutally works. Everyone uses libraries to some degree so the code amount and savings are going to be different anyway.
Matt Secoske | 15-Sep-05 at 8:41 am | Permalink
Mark: thanks for your posts… I didn’t think to mention that WEBrick is not an “official” package. It isn’t, though everywhere I’ve used Ruby it has been there, like you mentioned. My primary dev box is XP/Eclipse/Tomcat/MySQL, so I didn’t have any extra software to install, and frankly I doubt anyone who does this for a living is without a servlet container handy. And if I were doing Ruby constantly, I would probably have Apache + FastCGI setup and ready to go, so that evens itself out.
You do still have the extra configuration of the Web.xml file, though in my case that is usually handled by my IDE.
Matt Secoske | 15-Sep-05 at 8:46 am | Permalink
Jason: your absolutely right. In Mark’s code there is a call to IO.copy copying an IinputStream to an OutputStream, which greatly reduces the amount of code in my example too, could get rid of an entire method.
Thats one of the great things about blogging, we can all learnsome new things. I’m not experienced in embedding Jetty, but it is definetly something I will keep in mind if I ever need a quick server in Java again… I’m sure it will happen sooner than later.
Brian McCallister | 15-Sep-05 at 9:41 am | Permalink
Webrick is part of the standard library in 1.8.X ruby’s =)
Mark Derricutt | 15-Sep-05 at 3:18 pm | Permalink
Matt - I thought I’d just point out that the IO class in my code is also part of the Jetty package and not standard Java.