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!
Putz, nunca imaginei que ia me dar tão bem com middlewares. Estou gostando tanto disso que começei a discutir com meu professor na faculdade sobre o Rack, e para variar ele não sabia nada sobre ele. Esses artigos são muuuito interessantes e estou ansioso pelo próximo. Parabéns Thiago.