Ruby on Rails is a beautiful thing; simple, fast, and powerful. As they say, it takes the pain out of web development.
I’m fortunate enough to have been able to use Rails at work for a few months now, and the more I learn about the Rails framework and the Ruby language, the more enamored I become of the simplicity, beauty, and zen-like nature of these tools. But you didn’t come here to see me gush with adoration like a schoolgirl at an Elvis concert (I hope), you came to see:
How to Turn a Rails Application Into a Telnet Server in 30 Lines of Code!
First, some code:
#!/usr/bin/env ruby
require 'config/environment'
require 'gserver'
class TestServer < GServer
def serve( io )
begin
io.puts ">> WELCOME <<"
loop do
begin
telnet_input = io.gets
break if telnet_input =~ /\Aquit\b/
io.puts eval(telnet_input)
rescue Exception
io.puts "Oops - " + $!
end
log "Rec'd " + telnet_input
end
io.puts ">> GOODBYE <<"
io.close
rescue Exception
puts "OOPS - " + $!
raise
end
end
end
ts = TestServer.new 1234
ts.start
ts.audit = true
ts.join
This code should work, as is, on just about any Rails application (as far as I’ve been able to tell; it’s worked on the few I’ve tried).
Last night I was tooling around, playing with code. I’d had an idea for some server software I’d like to write, so I mocked up a quick throwaway system in Rails, and started playing around with some of the logic. I liked the results, and I’d already decided I wanted to write the server in Ruby, but the thought of taking all that functionality and porting everything to DBI calls or whatever was handy just made me tired, especially since Rails does all of the tedious database tasks for you.
Fortunately, I’m slowly learning that Ruby is written for lazy programmers, and after a little googling and some educated guesses, I was able to whack the entire Rails app into a script simply by including:
require 'config/environment'
…at the top of a file in the project root. This makes everything available (so far as I’ve been able to tell) to your application.
For a piece-by-piece analysis:
#!/usr/bin/env ruby
require 'config/environment'
require 'gserver'
class TestServer < GServer
def serve( io )
This sets the scripting environment, includes the Rails app, and makes the “GServer” class available. GServer is a freebie that comes with Ruby, so there’s no need for gem installs or other libraries. We’re then creating our own class which inherits from GServer, and overriding its “serve” method.
io.puts ">> WELCOME <<"
loop do
telnet_input = io.gets
break if telnet_input =~ /\Aquit\b/
io.puts eval(telnet_input)
log "Rec'd " + telnet_input
end
io.puts ">> GOODBYE <<"
io.close
This is the heart of the server (note that I’ve left out the exception handling for readability). Simply speaking, the serve() method is what handles user connections, and the “io” object lets us do gets and puts across the wire.
When a user connects, we spit out a welcome message, then go into an endless loop that responds to user requests. The following:
telnet_input = io.gets
…grabs whatever line the user types in. This line:
break if telnet_input =~ /\Aquit\b/
…kicks us out of the loop if the user types “quit”. Note that I’m using a regex instead of a static string because io.gets() returns the linefeed that the user types in, and this was an easy way to cheat out the functionality. The “\A” portion of the regex matches the beginning of the string, and the “\b” matches a “word boundary” character, in this case, the linefeed.
io.puts eval(telnet_input)
log "Rec'd " + telnet_input
The first line here does an eval() on whatever the user typed in, returning the value back across the wire. This should allow the user to run any command that Ruby (and your Rails app) will accept. The second line spits whatever the user typed in to the console where the server is running.
io.puts ">> GOODBYE <<"
io.close
Once the loop is broken, we burp out a fond farewell and cleanly close the connection.
ts = TestServer.new 1234
ts.start
ts.audit = true
ts.join
The rest of the code just sets a few variables and kicks on the service. The “1234” on the creation of the “ts” object sets the listening port. Turning on auditing allows us to see when someone logs on and off from the console (which can be redirected to a log file).
CAVEATS:
This is an extremely simple server. It has no security, no performance optimization, and probably has more holes than a cheesecloth factory. It is very naive, and while it technically allows multiple users to log on, they’ll all share the same class variables. Really, it’s only interesting at this point to show that it can be done.
That having been said, the ease with which it was accomplished is one of the many testaments to Ruby’s power and simplicity. I’ve been programming for many years, coded a number of servers, and this was the first time I’ve ever felt drunk with power after a coding session. “I can do anything!” I bellowed as I reeled around the room, knocking old monitors and dusty programming tomes off the desk. “No one can stop me!” Fortunately, my wife, who is a lot more level-headed than I am, pointed out that she could, indeed, stop me, since it had gone past two in the morning.