Continuando nossa saga iniciada no post anterior, voltaremos ao exemplo do HelloWorld para entendermos mais profundamente a estrutura de um objeto Rack. Como mencionado anteriormente, um este objeto deve possuir (na verdade responder a) um método call(env), retornando em sua resposta um array com 3 elementos. Veremos mais profundamente cada um desses elementos.

class HelloWorld
  def call(env)
    [200, {"Content-Type" => "text/html"}, "Hello World"]
  end
end

O método def call(env)

Env

O parâmetro recebido pelo método call é uma hash com as propriedades do ambiente do visitante da aplicação. Se alterarmos o nosso HelloWorld para imprimir o objeto env ao invés da string, teremos algo parecido com isso:

{
"REQUEST_METHOD"=>"GET",
"REQUEST_PATH"=>"/",
"REQUEST_URI"=>"/",
"HTTP_VERSION"=>"HTTP/1.1",
"HTTP_HOST"=>"localhost:9292",
"HTTP_USER_AGENT"=>"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_7; en-us) AppleWebKit/530.18 (KHTML, like Gecko) Version/4.0.1 Safari/530.18",
"HTTP_ACCEPT"=>"application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5", "HTTP_ACCEPT_LANGUAGE"=>"en-us", "HTTP_ACCEPT_ENCODING"=>"gzip,deflate",
"HTTP_CONNECTION"=>"keep-alive", "GATEWAY_INTERFACE"=>"CGI/1.2",
"SERVER_NAME"=>"localhost",
"SERVER_PORT"=>"9292",
"SERVER_PROTOCOL"=>"HTTP/1.1",
"SERVER_SOFTWARE"=>"Mongrel 1.1.5",
 "PATH_INFO"=>"/",
 "SCRIPT_NAME"=>"",
 "REMOTE_ADDR"=>"127.0.0.1", "rack.version"=>[0, 1], "rack.input"=>#, "rack.errors"=>#>, "rack.multithread"=>true, "rack.multiprocess"=>false, "rack.run_once"=>false, "rack.url_scheme"=>"http",
 "QUERY_STRING"=>""}
 

Dentre as informações impressas, algumas são bem importantes, como:

  • REQUEST_METHOD: Informa qual o verbo HTTP (GET, PUT, DELETE…) usado pelo usuário ao acessar a página.
  • HTTP_USER_AGENT: Informa o browser do usuário, facilitando o tratamento para browsers diferente em sua aplicação.
  • QUERY_STRING: Informa os argumentos passados após o “?” da url, útil para fazer parsing dos parâmetros.

Status

O primeiro objeto retornado no array de resposta é um inteiro, representando o status da resposta que sua aplicação retornará no método. O 200 do nosso exemplo, representa “OK” segundo a especificação do HTTP. É aqui seu controle de exceção jogará aquele lindo “Erro 404″ quando o usuário digitar incorretamente um sub-caminho de sua aplicação. ;-)

HTTP Headers

Cabeçalhos HTTP informam o tipo de retorno do pacote. O segundo objeto do nosso retorno deve OBRIGATORIAMENTE responder a um método “each” e devolver chaves e valores de seus elementos, tipicamente um Hash, como no nosso exemplo. A chave Content-Type, por exemplo, deve retornar o tipo de retorno a ser esperado, podendo ser HTML(“Content-Type” => “text/html”), XML, MP3 (‘Content-Type’ => ‘audio/mp3′), entre outros.

Response Body

O retorno “Body” deve responder ao método each retornando um array de strings. Este será o conteúdo que será apresentado ao usuário no navegador.

ATENÇÃO: Segundo a documentação atual do Rack, o Body não deve mais ser uma simples string(como nos exemplos do post anterior). Aparentemente isto quebrará no Ruby 1.9.

Rack:Builder

É uma ferramenta que nos permite construir aplicações Rack utilizando uma DSL para “costurar” diferentes middlewares e aplicações. Os Builders são os parafusos das estantes, ou a cola que une as estruturas separadas criando uma inteiramente nova. Na verdade, o Builder é o cara que torna o a idéia do Rack tão genial. A facilidade que ele proporciona na criação de Middlewares ficará bem claro no próximo post.

Os métodos fornecidos pela DSL do Buider são:

  • run
  • use
  • map

Rack::Builder#run

É o método que o servidor web procurará para a execução da aplicação Rack. No exemplo abaixo, nosso builder é informado para rodar o bloco chamado clock a cada resquisição à porta 9292. Nada demais, por enquanto. Mas na medida que a aplicação for crescendo, é possível criar estruturas extremamente complexas que veremos posteriormente.

clock = Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["#{Time.now}"]]}
builder = Rack::Builder.new do
  run clock
end
Rack::Handler::Mongrel.run builder, :Port => 9292

Rack::Builder#use

É o método utilizado para acomplar novas middlewares à sua aplicação. No exemplo abaixo, o middleware CommonLogger é adicionado à nossa aplicação com apenas uma linha de código:

clock = Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["#{Time.now}"]]}
builder = Rack::Builder.new do
  use Rack::CommonLogger
  run clock
end
Rack::Handler::Mongrel.run builder, :Port => 9292

CommonLogger é um Middleware que adiciona a funcionalidade de log ao seu servidor. Rodando este exemplo, você terá um log semelhante ao de Rails e Sinatra em seu console, como no exemplo abaixo:

127.0.0.1 – - [25/Sep/2009 17:17:03] “GET / HTTP/1.1″ 200 30 0.0002
127.0.0.1 – - [25/Sep/2009 17:17:14] “GET / HTTP/1.1″ 200 30 0.0002
127.0.0.1 – - [25/Sep/2009 17:17:15] “GET /2 HTTP/1.1″ 200 30 0.0002
127.0.0.1 – - [25/Sep/2009 17:17:28] “GET /testand_log HTTP/1.1″ 200 30 0.0002

Rack::Builder#map

Método utilizado para mapear (jura?) suas rotas HTTP. Não a toa, é semelhante ao routes.rb do Rails, afinal, ambos são implementações diretas das best practices de desenvolvimento web atual.

Para ilustrar, criaremos um exemplo de aplicação que além de informar as horas, oferece um caminho para que o usuário seja informado em que ano ele se encontra.

clock = Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["#{Time.now}"]]}
builder = Rack::Builder.new do
  use Rack::CommonLogger
  map '/' do
    run clock
  end

  map '/year' do
    run Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["#{Time.new.year}"]] }
  end
end
Rack::Handler::Mongrel.run builder, :Port => 9292

Com este exemplo, se após iniciarmos a nossa aplicação tentarmos acessar a url http://localhost:9292/year, receberemos o ano atual em nosso browser.

Ok, mas e rotas aninhadas? Extremamente simples também! Acompanhem o exemplo abaixo.

builder = Rack::Builder.new do
  use Rack::CommonLogger
  map '/' do
    run Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["Welcome to my page"]]}
  end

  map '/clock' do
    map '/' do
      run Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["#{Time.now}"]] }
    end

    map '/year' do
      run Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["#{Time.now.year}"]] }
    end

    map '/hour' do
      run Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["#{Time.now.hour}"]] }
    end    

  end
end
Rack::Handler::Mongrel.run builder, :Port => 9292

Ok, mas nós podemos seguir o conceito de DRY para evitar código desnecessário:

def create_proc(body_return)
  Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["#{body_return}"]]}
end

builder = Rack::Builder.new do
  use Rack::CommonLogger
  map '/' do
    run create_proc('Welcome to my page')
  end

  map '/clock' do
    map '/' do
      run create_proc(Time.now)
    end

    map '/year' do
      run create_proc(Time.now.year)
    end

    map '/hour' do
      run create_proc(Time.now.hour)
    end    

  end
end
Rack::Handler::Mongrel.run builder, :Port => 9292

Desta maneira, criamos uma aplicação com as seguintes rotas disponíveis:

  • http://localhost:9292/
  • http://localhost:9292/clock
  • http://localhost:9292/clock/year
  • http://localhost:9292//clock/hour

Cenas do Próximo Capítulo

Após aprendermos o conceito do Rack, sua estrutura e os 3 principais métodos oferecidos para criação de aplicações, nos focaremos na criação de Middlewares no próximo post. Entenderemos seus conceitos e investigaremos alguns exemplos bem sucedidos disponíveis na web. Fiquem ligados!