Mastodon

Ruby Redo’s: Sinatra

Sinatra is described as “a DSL for quickly creating web applications” and since I have been playing with DSLs lately I thought I would try my hand at making something like it. You can see in Sinatra’s famous three line “Hello World” that they are calling get directly on the top-level object called main:

require 'sinatra'

get '/hi' do
  "Hello World!"
end

So first thing we know is that we need a Rack compatible class. Why do we know that? Because Rack deals with the webserver/HTTP stuff as long as we follow their interface spec which saves us a lot of fussing with servers. The spec is simple:

A Rack application is a Ruby object (not a class) that responds to call. It takes exactly one argument, the environment and returns an Array of exactly three values: The status, the headers, and the body.

So that actually tells us a fair bit about the class we need to write. As the saying goes, “There are only two hard things in Computer Science: cache invalidation and naming things”, I think I will name this thing HTTP. So here is the minimum to conform with the Rack spec:

class HTTP
  def call env
    [200, {}, ""]
  end
end

If you are curious about what kind of stuff is going to end up in that env variable here is an example:

{
 "SERVER_SOFTWARE"=>"thin 1.5.0 codename Knife",
 "SERVER_NAME"=>"localhost",
 "rack.input"=> StringIO.new(),
 "rack.version"=>[1, 0],
 "errors"=>STDERR,
 "rack.multithread"=>false,
 "rack.multiprocess"=>false,
 "rack.run_once"=>false,
 "REQUEST_METHOD"=>"GET",
 "REQUEST_PATH"=>"/test",
 "PATH_INFO" => "/test",
 "REQUEST_URI"=>"/test",
 "HTTP_VERSION"=>"HTTP/1.1",
 "HTTP_USER_AGENT"=>"curl/7.27.0",
 "HTTP_HOST"=>"localhost:8080",
 "HTTP_ACCEPT"=>"*/*",
 "GATEWTERFACE"=>"CGI/1.2",
 "SERVER_PORT"=>"8080",
 "QUERY_STRING"=>"",
 "SERVER_PROTOCOL"=>"HTTP/1.1",
 "rack.url_scheme"=>"http",
 "SCRIPT_NAME"=>"",
 "REMOTE_ADDR" => "127.0.0.1"
}

Rack actually has a bunch of helpful classes that we are going to use to get thing working, lets add them in:

require 'rack'

class HTTP

  def call env
    request = Rack::Request.new env
    response = Rack::Response.new
    response.write "Hello Whirled"
    response.finish
  end

end

at_exit do
  Rack::Handler.default.run HTTP.new
end

You can see that we now have request (that wraps the environment hash that you can see us passing in) and a response object. Notice that response.finish is the last line of the call method now. This is because it returns the array of status, headers and body that Rack needs. The other thing to notice is that we are using Kernel#at_exit to run whatever Rack decides is the default webserver when the script exits. This actually gives us a working application that you can run like this:

mike@sleepycat:~/play/http$ ruby http.rb
>> Thin web server (v1.5.0 codename Knife)
>> Maximum connections set to 1024
>> Listening on 0.0.0.0:8080, CTRL+C to stop

Lets take another step and get that response.write out of there and put it in a Proc. Next, we use instance_eval to evaluate the Proc (which is being turned from a proc object back into a block with the & operator) in the context of the current object. Since the block is being evaluated in the context of the current object and not the context of the current method we need to make request and response instance variables and add some attr_accessor methods. You can see also that we are now storing the routes in a hash of hashes that is held in a class variable so that when we call new, our instance can still see the routes.

require 'rack'

class HTTP

  attr_accessor :request, :response

  @@routes = { get: {"/" => Proc.new {response.write "Hello Whirled"}} }

  def call env
    @request = Rack::Request.new env
    @response = Rack::Response.new
    the_proc = @@routes[:get]["/"]
    instance_eval &the_proc
    response.finish
  end

end

at_exit do
  Rack::Handler.default.run HTTP.new
end

Our next step is creating some way to add methods to that @@routes hash. For that we will create a class method called save_route. We’ll also add some other methods to the hash as well and make sure that if there is no block found for a url that we send back a 404:

require 'rack'

class HTTP

  attr_accessor :response, :request

  @@routes = { get: {}, put: {}, post: {}, delete: {}, patch: {}, head: {} }

  def self.save_route *args
    method, route, block = *args
    @@routes[method.downcase.to_sym][route] = block
  end

  def call env
    puts "Handling request for: #{env["PATH_INFO"]}"
    @request = Rack::Request.new env
    @response = Rack::Response.new
    block = @@routes[request.request_method.downcase.to_sym][request.path_info]
    # if no block is stored for that path; 404!
    block ? instance_eval(&block) : throw_404
    response.finish
  end

  private

  def throw_404
    @response.status = 404
    @response.write "<html>Page Not Found</html>"
  end

end

at_exit do
  Rack::Handler.default.run HTTP.new
end

Now that we can add routes to our class, we need to turn our attention to adding methods to the main object. The methods we want to add are the usual HTTP methods, get, put and whatnot.
We want to define an method for each of them like this:

def get *args, &block
  HTTP.save_route(:get, args.first, block)
end

But since that is kind of repetitive we are going to write some code that writes that code. Often you would use Module#define_method for this but the main object is weird and does not have that method. So I am going to use eval:

require 'rack'

class HTTP
  #... all the HTTP code ...
end

def http_methods
  %w(get put post delete head patch).each do |method_name|
     eval <<-EOMETH
     def #{method_name} *args, &block
       HTTP.save_route(:#{method_name}, args.first, block)
     end
     EOMETH
   end
end

at_exit do
  # ... rack stuff ...
end

Then we need to open the eigenclass of main and attach the methods to that. This is so the methods are available as instance methods of the main object. We can do that with Ruby’s somewhat bizarre syntax:

require 'rack'

class HTTP
  #... all the HTTP code ...
end

def http_methods
  #... all the HTTP code ...
end

class << self
  http_methods
end

at_exit do
  # ... rack stuff ...
end

With that we now have a working toy version of Sinatra. We can use it like this:

require_relative 'http'

get "/" do
  response.status = 200
  response["HTTP-Referrer"] = "Mike"
  response.write "<html>Hello Whirled</html>"
end

When you are working in Rails you often hear about Rack but its not usually something you interact directly with. Playing with this has really made me appreciate Sinatra for the intelligent, concise DSL that it is (which is easy to forget somehow). While playing with DSLs is fun, taking some time to get to know Rack has also really helped my understanding of Rails.

Running a Sinatra app on Glassfish

I’m using JRuby more and more these days to deploy Rails apps. Typically that means creating a war file and deploying it on Glassfish. Yesterday I had request that seemed would be best fulfilled with a Sinatra app rather than Rails. It was my first experience getting Sinatra running on Glassfish and I thought I would share it.

The goal state here is a war file in the Glassfish autodeploy directory and the tool of choice is Warbler.


gem install warbler

With that installed we need to get ourselves set up to warble something. Warbler expects a specific directory structure it turns out, which is pretty typical for those familiar with the Ruby/Rails world:


mike@sleepycat:~/projects/myapp☺ find .
./Gemfile
./config.ru
./lib
./lib/myapp.rb
./Gemfile.lock

Of course you can add a views directory and other stuff in there as well. If you start doing things that Warbler does not expect you might have to add a warble.rb file but since I am using bundler and doing things that fall within warblers expectations I didn’t need one. I had a small number of very simple views, so I chose not to make a views directory and opted for named templates in the main app. To make this a little easier to digest, I’ve stripped out my code and made a basic hello world style app below thats going to be my starting point for next time I do this sort of thing. The three pieces you need are the Gemfile, the rackup file and the app itself.

The Gemfile:


source 'http://rubygems.org'

gem ‘sinatra’

group :development do
gem “sinatra-reloader”
gem “ruby-debug”
end

The config.ru


require 'rubygems'
require 'bundler'
Bundler.require
require 'lib/myapp'

set :run, false #disable the built-in web server
set :environment, :production

run Sinatra::Application

And, sitting inside the lib directory, the application itself with a named template:


get '/' do
@greeting = “Bonjour”
status 200
erb :index
end

template :index do
“<%= @greeting %> from Sinatra”
end
end

If you want to try it out at this point you can launch the app using rackup:


rackup config.ru

With those files in place you are ready to create a war file by running the “warble” command in the project directory. What that does is create a war file with the following structure:


./META-INF
./META-INF/init.rb
./META-INF/MANIFEST.MF
./WEB-INF
./WEB-INF/web.xml
./WEB-INF/Gemfile
./WEB-INF/config.ru
./WEB-INF/lib
./WEB-INF/lib/jruby-rack-1.1.5.jar
./WEB-INF/lib/jruby-core-1.6.7.jar
./WEB-INF/lib/jruby-stdlib-1.6.7.jar
./WEB-INF/lib/myapp.rb
./WEB-INF/gems
./WEB-INF/gems/specifications
...all the gemspecs from the gem dependencies end up here...
./WEB-INF/gems/gems
... All the gems that your project depends on are copied in to this folder ...
./WEB-INF/Gemfile.lock

And now you can drop that war in the Glassfish autodeploy directory and off it goes. My app was simple enough that I didn’t even need to use a database so you might need to add to this a little but it looks pretty simple. In fact every time I end up using Sinatra I come away impressed by how clean and simple it is. Now that I know how to run this on Glassfish, the next time may come sooner than later.

Rendering templates in Rails Metal

One of the things that has caught my interest lately is Rack. After reading about it a bunch I was looking for a way to start integrating it into my projects somehow. Adam Wiggins of Heroku, had an interesting presentation that called Rails Metal the “a gateway to the world of Rack.” Its a good description, and one that gave me a starting place for using Rack in my project.

If you run the “script/generate metal nameofMetalApp” command, Rails will generate the code for a small Rack app and dump it in the app directory of your project in a folder called “metal”. Inside that app, there are no Rails helpers, no convenience methods, no redirects or renders. You really are in a new world.

The reason for that is that Rails won’t have been loaded when the a request hits this little app. Its only if the apps in the metal folder all return 404 that Rails gets loaded at all.
What is gained here is speed. If a couple lines of code is sufficient to handle the request, why load an entire framework? For simple actions that happen a lot, it can make a big difference. The first thing I wanted to know was how to render HAML or ERB pages in my Metal apps.

Since that is probably pretty common I thought I would post it up here. Its not complicated but it will save some searching. I decided I would create a Metal end point that would serve up a static “about” page. There are many useful things to do with Metal, and while an “about” page might not be one of them, its a good place to start:

# Allow the metal piece to run in isolation
require(File.dirname(__FILE__) + &quot;/../../config/environment&quot;) unless defined?(Rails)
#added HAML
require 'haml'
class AboutApp
  def self.call(env)
    if env[&quot;PATH_INFO&quot;] =~ /^\/about/
      @time = Time.now
      #Render a HAML template using the HAML engine
      #http://haml-lang.com/docs/yardoc/Haml/Engine.html
      template = File.read(File.join(&quot;app&quot;,&quot;views&quot;, &quot;static&quot;, &quot;about.html.haml&quot;))
      haml_engine = Haml::Engine.new(template)
      output = haml_engine.render(binding)
      #Alternately you can render an ERB template
      #http://ruby-doc.org/stdlib/libdoc/erb/rdoc/classes/ERB.html
      #template = File.read(File.join(&quot;app&quot;, &quot;views&quot;, &quot;static&quot;, &quot;about.html.erb&quot;))
      #output = ERB.new(template).result(binding)
      [200, {&quot;Content-Type&quot; =&gt; &quot;text/html&quot;}, [&quot;#{output}&quot;]]
    else
      [404, {&quot;Content-Type&quot; =&gt; &quot;text/html&quot;}, [&quot;Not Found&quot;]]
    end
  end
end

Notice that I am creating an instance variable @time which is available in the templates because I am passing in binding objects to the renderer for both HAML and ERB.
And a basic template so we can see that its working (excuse the lack of spacing, wordpress is messing it up):

!!!
%html
  %body
    %h1 The time is
    %p
      =@time

Then fire up your server and request “/about”. That’s it. The thing to remember is that these Metal apps are real Rack apps. While most of them will be pretty basic, they don’t have to be. They just have to conform to the Rack spec. I have been curious about Sinatra for a while now and this seemed like a good time to try it out and see if I could use it instead of Metal. I wrote a quick little Sinatra app and dropped it into the metal folder, requested “/test” and there it was. (Again, excuse the lack of spacing)

require 'rubygems'
require 'sinatra'
require 'haml'
class TestApp &lt; Sinatra::Application
  get '/test' do
    output =&lt;&lt;-EOHAML
    !!!
    %html
      %body
        %h1
          Hello from Sinatra!
    EOHAML
    haml output
  end
end

While the purpose of all this is performance, it also seems that this is a fairly nice way to add other apps to your existing one. Say, if I wanted to run a basic CMS alongside one of my Rails apps for marketing purposes. What’s extra cool is that using a Rack based authentication middleware like Warden would mean that both apps would share an authentication mechanism. There are a lot of possibilities to explore!