A Simple Web Server in Ruby

Few days back I had a job interview with a San Francisco based start up. The interviewer called me over Skype and asked me to write a simple web server in Ruby. The milestones were:

1) Web server returns “Hello World”.
2) Web server returns the list of files in the base directory.
3) Web server allows to navigate the directory structure.
4) If a user click on a file the browser should display it.

The only constraint he gave me was time: I had 60 minutes. After I heard the question I thought “Easy! 60 minutes seemed a long time and Ruby makes coding so much faster”. Well I was wrong I could not even complete milestone 3. I pretty much got lost for 35 minutes on the File and Directory APIs.
So I decided to finish the exercise in my spare time. It took me an other 45 minutes to complete the exercise. The hardest part was to remove 3 bugs (28 minutes!) all related to milestone 3.

See the end of the blog for the 5 seconds solution.

This is a great little exercise to learn Ruby and to exercise your coding skills so my suggestion is stop reading an get to work.

Here is how I did it:

1) Web server returns “Hello World”.

require "socket"

webserver = TCPServer.new('localhost', 2000)
while (session = webserver.accept)
  session.write(Time.now)
  session.print("Hello World!")
  session.close
end

2) Web server returns the list of files in the base directory.

require "socket"

webserver = TCPServer.new('localhost', 2000)
base_dir = Dir.new(".")
while (session = webserver.accept)
  session.print "HTTP/1.1 200/OK\r\nContent-type:text/html\r\n\r\n"
  base_dir.entries.each do |f|
    if File.directory? f
      session.print("
#{f}/

")
    else
      session.print("
#{f}

")
    end
  end
  session.close
end

3) Web server allows to navigate the directory structure.

First the solution I had after the 60 minutes interview.
(pretty bad!)

require "socket"

webserver = TCPServer.new('localhost', 2000)
base_dir = Dir.new(".")
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.*/, '')
  if trimmedrequest.chomp != ""
    base_dir = Dir.new("./#{trimmedrequest}".chomp)
  end
  session.print "
#{trimmedrequest}

"

  session.print("#{base_dir}")
  if Dir.exists? base_dir
     base_dir.entries.each do |f|
       if File.directory? f
         session.print("<a href="#{f}"> #{f}</a>")
       else
        session.print("
#{f}

")
       end
     end
  else
    session.print("Directory does not exists!")
  end
  session.close
end

This is what I came up with in 10 minutes of coding and 28 minutes debugging. The bugs were related to my ignorance of the Ruby API in particular the File and Directory API, and a typos in the construction of the links: line 40 and 42 I forgot to add the “/” so the links were wrong but only I navigated down 2 levels in the directory hierarchies.

require "socket"

webserver = TCPServer.new('localhost', 2000)
base_dir = Dir.new(".")
while (session = webserver.accept)
  request = session.gets
  puts request
  trimmedrequest = request.gsub(/GET\ \//, '').gsub(/\ HTTP.*/, '').chomp
  resource =  trimmedrequest

  if resource == ""
    resource = "."
  end
  print resource

  if !File.exists?(resource)
    session.print "HTTP/1.1 404/Object Not Found\r\nServer Matteo\r\n\r\n"
    session.close
    next
  end

  if File.directory?(resource)
    session.print "HTTP/1.1 200/OK\r\nContent-type:text/html\r\n\r\n"
    if resource == ""
      base_dir = Dir.new(".")
    else
      base_dir = Dir.new("./#{trimmedrequest}")
    end
    base_dir.entries.each do |f|
      dir_sign = ""
      base_path = resource + "/"
      base_path = "" if resource == ""
      resource_path = base_path + f
      if File.directory?(resource_path)
        dir_sign = "/"
      end
      if f == ".."
        upper_dir = base_path.split("/")[0..-2].join("/")
        session.print("<a href="\&quot;/#{upper_dir}\&quot;">#{f}/</a>")
      else
        session.print("<a href="\&quot;/#{resource_path}\&quot;">#{f}#{dir_sign}</a>")
      end
    end
  else
     ## return file
  end
  session.close
end

4) If a user click on a file the browser should display it

This was pretty straight forward and it only took me 5 minutes since I copied the get_content_type from this blog post.

require "socket"

def get_content_type(path)
    ext = File.extname(path)
    return "text/html"  if ext == ".html" or ext == ".htm"
    return "text/plain" if ext == ".txt"
    return "text/css"   if ext == ".css"
    return "image/jpeg" if ext == ".jpeg" or ext == ".jpg"
    return "image/gif"  if ext == ".gif"
    return "image/bmp"  if ext == ".bmp"
    return "text/plain" if ext == ".rb"
    return "text/xml"   if ext == ".xml"
    return "text/xml"   if ext == ".xsl"
    return "text/html"
end

webserver = TCPServer.new('localhost', 2000)
base_dir = Dir.new(".")
while (session = webserver.accept)
  request = session.gets
  puts request
  trimmedrequest = request.gsub(/GET\ \//, '').gsub(/\ HTTP.*/, '').chomp
  resource =  trimmedrequest
  if resource == ""
    resource = "."
  end
  print resource

  if !File.exists?(resource)
    session.print "HTTP/1.1 404/Object Not Found\r\nServer Matteo\r\n\r\n"
    session.print "404 - Resource cannot be found."
    session.close
    next
  end

  if File.directory?(resource)
    session.print "HTTP/1.1 200/OK\r\nContent-type:text/html\r\n\r\n"
    if resource == ""
      base_dir = Dir.new(".")
    else
      base_dir = Dir.new("./#{trimmedrequest}")
    end
    base_dir.entries.each do |f|
      dir_sign = ""
      base_path = resource + "/"
      base_path = "" if resource == ""
      resource_path = base_path + f
      if File.directory?(resource_path)
        dir_sign = "/"
      end
      if f == ".."
        upper_dir = base_path.split("/")[0..-2].join("/")
        session.print("<a href="\&quot;/#{upper_dir}\&quot;">#{f}/</a>")
      else
        session.print("<a href="\&quot;/#{resource_path}\&quot;">#{f}#{dir_sign}</a>")
      end
    end
  else
    contentType = get_content_type(resource)
    session.print "HTTP/1.1 200/OK\r\nServer: Matteo\r\nContent-type: #{contentType}\r\n\r\n"
    File.open(resource, "rb") do |f|
      while (!f.eof?) do
        buffer = f.read(256)
        session.write(buffer)
      end
    end
  end
  session.close
end

The 5 Seconds Solution

require 'webrick'

include WEBrick

puts "Starting server: http://#{Socket.gethostname}:#{port}"
server = HTTPServer.new(:Port=>2000,:DocumentRoot=>Dir::pwd )
trap("INT"){ server.shutdown }
server.start

A Simple Token Authentication Service for Mobile Devices Using Rails 3 and Devise.

This post will show you how to implement a simple token based authentication schema for a Rails 3 application. The implementation uses Devise(https://github.com/plataformatec/devise) with the token_authenticable module enabled.

Show me the code: https://github.com/matteomelani/Auth-Token-Service-Prototype

The Problem

Let’s say you have a nice Rails 3 web application that uses Devise for authenticating users and you now want to add a native mobile application to the picture. How do you go about authentication?

The Solution

The solution is to use a simple authentication token protocol.

  1. Client: HTTP GET /api/v1/photos.json
  2. Server: status=401, body={}
  3. Client: display sign in screen to user
  4. User: enter username (email address) and password
  5. Client:  HTTPS POST /api/v1/tokens, body: email=user@emailservice.com&password=userpassword
  6. Server: status =200, {“token”:”ASDERfsfgewrvsa@#$%5″}
  7. Client: HTTP GET /api/v1/photos.json?auth_token=ASDERfsfgewrvsa@#$%5
  8. Server: status=200, list of user’s photo in JSON format

Helpful Tools

  1. Firefox plugin RESTClient (https://addons.mozilla.org/en-US/firefox/addon/restclient/). This is a simple tools that allows you to manually test your REST services. If Windows is your platform Fiddler2 (http://www.fiddler2.com/) is your choice.
  2. Cucumber and Capybara for acceptance testing.

Implementation

Assumptions

  • You have a simple Rails app that allows you to see a list of college courses. You can also add courses.
  • You have set up Devise for the user model so that to view the list of courses you need to be logged in.
  • So you are not logged in

Step 1: Enable Devise token-authenticable module
Change the user table with the following migration

class AddDeviseColumnsToUser < ActiveRecord::Migration
  def self.up
    change_table :users do |t|
      t.token_authenticatable
    end
  end
  def self.down
    t.remove :authentication_token
  end
end

In user.rb add :token_authenticatable to the list of devise modules, it should look something like this depending on what devise module you have already enabled

devise :database_authenticatable,
       :token_authenticatable,
       :recoverable,
       :rememberable
      ,:omniauthable,
       :validatable,
       :trackable,
       :timeoutable,
       :lockable,
       :confirmable


Step 2: Create the routes for the Token service.

namespace :api do
  namespace :v1  do
    resources :tokens,:only => [:create, :destroy]
  end
end

All the API controllers are in a separate directory: …/myapp/app/controllers/api/v1. Having separated set of controllers to serve two different application interface (JSON and HTML) helps to keep maintenance and upgrades simple.

Step 3: Create tokens controller.
Create the tokens controller in /myapp/app/controllers/api/v1/tokens_controller.rb

class Api::V1::TokensController  < ApplicationController
    skip_before_filter :verify_authenticity_token
    respond_to :json
    def create
      email = params[:email]
      password = params[:password]
      if request.format != :json
        render :status=>406, :json=>{:message=>"The request must be json"}
        return
       end

    if email.nil? or password.nil?
       render :status=>400,
              :json=>{:message=>"The request must contain the user email and password."}
       return
    end

    @user=User.find_by_email(email.downcase)

    if @user.nil?
      logger.info("User #{email} failed signin, user cannot be found.")
      render :status=>401, :json=>{:message=>"Invalid email or passoword."}
      return
    end

    # http://rdoc.info/github/plataformatec/devise/master/Devise/Models/TokenAuthenticatable
    @user.ensure_authentication_token!

    if not @user.valid_password?(password)
      logger.info("User #{email} failed signin, password \"#{password}\" is invalid")
      render :status=>401, :json=>{:message=>"Invalid email or password."}
    else
      render :status=>200, :json=>{:token=>@user.authentication_token}
    end
  end

  def destroy
    @user=User.find_by_authentication_token(params[:id])
    if @user.nil?
      logger.info(“Token not found.”)
      render :status=>404, :json=>{:message=>”Invalid token.”}
    else
      @user.reset_authentication_token!
      render :status=>200, :json=>{:token=>params[:id]}
    end
  end

end

Step 4: Manual Testing
You can now use RESTClient to make sure everything works fine. Fire it up and send the first POST request to http://localhost:3000/api/v1/tokens.json. Do not forget to add the email and password to the body.

Note: to have the server see the post params make sure the RESTClient request header contains a parameter with name “Content-Type” and value “application/x-www-form-urlencoded”.

If you now click on RESTClient Response Body tab you can see the returned authentication token.

If you now send a HTTP GET to http://localhost:3000/courses.json?auth_token=<put your token here> you should see the list of courses.