Un Ejemplo Simple: Piedra, Papel, tijeras

[~/rack/rack-rock-paper-scissors(simple)]$ cat -n rps.rb
 1  require 'rack/request'
 2  require 'rack/response'
 3  
 4  module RockPaperScissors
 5    class App 
 6  
 7      def initialize(app = nil)
 8        @app = app
 9        @content_type = :html
10        @defeat = {'rock' => 'scissors', 'paper' => 'rock', 'scissors' => 'paper'}
11        @throws = @defeat.keys
12        @choose = @throws.map { |x| 
13           %Q{ <li><a href="/?choice=#{x}">#{x}</a></li> }
14        }.join("\n")
15        @choose = "<p>\n<ul>\n#{@choose}\n</ul>"
16      end
17  
18      def call(env)
19        req = Rack::Request.new(env)
20  
21        req.env.keys.sort.each { |x| puts "#{x} => #{req.env[x]}" }
22  
23        computer_throw = @throws.sample
24        player_throw = req.GET["choice"]
25        anwser = if !@throws.include?(player_throw)
26            "Choose one of the following:"
27          elsif player_throw == computer_throw
28            "You tied with the computer"
29          elsif computer_throw == @defeat[player_throw]
30            "Nicely done; #{player_throw} beats #{computer_throw}"
31          else
32            "Ouch; #{computer_throw} beats #{player_throw}. Better luck next time!"
33          end
34  
35        res = Rack::Response.new
36        res.write <<-"EOS"
37        <html>
38          <title>rps</title>
39          <body>
40            <h1>
41               #{anwser}
42               #{@choose}
43            </h1>
44          </body>
45        </html>
46        EOS
47        res.finish
48      end # call
49    end   # App
50  end     # RockPaperScissors
51  
52  if $0 == __FILE__
53    require 'rack'
54    require 'rack/showexceptions'
55    Rack::Server.start(
56      :app => Rack::ShowExceptions.new(
57                Rack::Lint.new(
58                  RockPaperScissors::App.new)), 
59      :Port => 9292,
60      :server => 'thin'
61    )
62  end

El Objeto req

El objeto req pertenece a la clase Rack::Request. Tiene un único atributo env:

(rdb:1)  req
#<Rack::Request:0x007f8d735b1410 
@env={
"SERVER_SOFTWARE"=>"thin 1.5.1 codename Straight Razor", 
"SERVER_NAME"=>"0.0.0.0", 
"rack.input"=>#<Rack::Lint::InputWrapper:0x007f8d735776c0 
                @input=#<StringIO:0x007f8d735426a0>>, "rack.version"=>[1, 0], 
                "rack.errors"=>#<Rack::Lint::ErrorWrapper:0x007f8d73577620 @error=#<IO:<STDERR>>
              >, 
"rack.multithread"=>false, 
"rack.multiprocess"=>false, 
"rack.run_once"=>false, 
"REQUEST_METHOD"=>"GET", 
"REQUEST_PATH"=>"/", 
"PATH_INFO"=>"/", 
"REQUEST_URI"=>"/", 
"HTTP_VERSION"=>"HTTP/1.1", 
"HTTP_HOST"=>"0.0.0.0:9292", 
"HTTP_CONNECTION"=>"keep-alive", 
"HTTP_ACCEPT"=>"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 
"HTTP_USER_AGENT"=>"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.76 Safari/537.36", 
"HTTP_ACCEPT_ENCODING"=>"gzip,deflate,sdch", 
"HTTP_ACCEPT_LANGUAGE"=>"es-ES,es;q=0.8", 
"GATEWAY_INTERFACE"=>"CGI/1.2", 
"SERVER_PORT"=>"9292", 
"QUERY_STRING"=>"", 
"SERVER_PROTOCOL"=>"HTTP/1.1", 
"rack.url_scheme"=>"http", 
"SCRIPT_NAME"=>"", 
"REMOTE_ADDR"=>"127.0.0.1", 
"async.callback"=>#<Method: Thin::Connection#post_process>, 
"async.close"=>#<EventMachine::DefaultDeferrable:0x007f8d735603f8>}>
Cuando llamamos a GET para obtener el valor del parámetro choice:
player_throw = req.GET["choice"]
Si visitamos la página http://0.0.0.0:9292/ el entorno contiene algo como esto:
rdb:1) p @env
{"SERVER_SOFTWARE"=>"thin 1.5.1 codename Straight Razor",
 ...
 "QUERY_STRING"=>"",
 "REQUEST_URI"=>"/"
 ...
}
el código de GET nos da los datos almacenados en QUERY_STRING:
   def GET 
      if @env["rack.request.query_string"] == query_string
        @env["rack.request.query_hash"]
      else
        @env["rack.request.query_string"] = query_string
        @env["rack.request.query_hash"]   = parse_query(query_string)
      end 
    end 

   def query_string;    @env["QUERY_STRING"].to_s                end
si es la primera vez, @env["rack.request.query_string"] está a nil y se ejecuta el else inicializando @env["rack.request.query_string"] y @env["rack.request.query_hash"]

Si por ejemplo visitamos la URL: http://localhost:9292?choice=rock entonces env contendrá:

rdb:1) p env
{ ... 
  "QUERY_STRING"=>"choice=rock", 
  "REQUEST_URI"=>"/?choice=rock", 
  ...
}
Familiaricemonos con algunos de los métodos de Rack::Request:
(rdb:1) req.GET
{"choice"=>"paper"}
(rdb:1)  req.GET["choice"]
"paper"
(rdb:1) req.POST
{}
(rdb:1) req.params
{"choice"=>"paper"}
(rdb:1) req["choice"]
"paper"
(rdb:1) req[:choice]
"paper"
(rdb:1) req.cookies()
{}
(rdb:1) req.get?
true
(rdb:1) req.post?
false
(rdb:1) req.fullpath
"/?choice=paper"
(rdb:1) req.host
"0.0.0.0"
(rdb:1) req.host_with_port
"0.0.0.0:9292"
(rdb:1) req.body
#<Rack::Lint::InputWrapper:0x007f8d7369b5d8 @input=#<StringIO:0x007f8d73690318>>
(rdb:1) req.cookies()
{}
(rdb:1) req.get?
true
(rdb:1) req.post?
false
(rdb:1) req.fullpath
"/?choice=paper"
(rdb:1) req.host
"0.0.0.0"
(rdb:1) req.host_with_port
"0.0.0.0:9292"
(rdb:1) req.ip
"127.0.0.1"
(rdb:1) req.params
{"choice"=>"paper"}
(rdb:1) req.path
"/"
(rdb:1) req.path_info
"/"
(rdb:1) req.port
9292
(rdb:1) req.request_method
"GET"
(rdb:1) req.scheme
"http"
(rdb:1) req.url
"http://0.0.0.0:9292/?choice=paper"
(rdb:1) req.user_agent
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.36 (KHTML, like Gecko) 
Chrome/29.0.1547.76 Safari/537.36"
(rdb:1) req.values_at("choice")
["paper"]

Rakefile

[~/rack/rack-rock-paper-scissors(simple)]$ cat Rakefile 
desc "run the server"
task :default do
  sh "ruby rps.rb"
end

desc "run the client with rock"
task :rock do
  sh %q{curl -v 'http://localhost:9292?choice=rock'}
end

desc "run the client with paper"
task :paper do
  sh %q{curl -v 'http://localhost:9292?choice=paper'}
end

desc "run the client with scissors"
task :scissors do
  sh %q{curl -v 'http://localhost:9292?choice=scissors'}
end

  1. curl

Ejecuciones

[~/rack/rack-rock-paper-scissors(simple)]$ rake
ruby rps.rb
>> Thin web server (v1.5.1 codename Straight Razor)
>> Maximum connections set to 1024
>> Listening on 0.0.0.0:9292, CTRL+C to stop

[~/rack/rack-rock-paper-scissors(simple)]$ rake rock
curl -v 'http://localhost:9292?choice=rock'
* About to connect() to localhost port 9292 (#0)
*   Trying ::1... Connection refused
*   Trying 127.0.0.1... connected
* Connected to localhost (127.0.0.1) port 9292 (#0)
> GET /?choice=rock HTTP/1.1
> User-Agent: curl/7.21.4 (universal-apple-darwin11.0) libcurl/7.21.4 OpenSSL/0.9.8x zlib/1.2.5
> Host: localhost:9292
> Accept: */*
> 
< HTTP/1.1 200 OK
< Content-Length: 332
< Connection: keep-alive
< Server: thin 1.5.1 codename Straight Razor
< 
      <html>
        <title>rps</title>
        <body>
          <h1>
             Nicely done; rock beats scissors
             <p>
<ul>
 <li><a href="/?choice=rock">rock</a></li> 
 <li><a href="/?choice=paper">paper</a></li> 
 <li><a href="/?choice=scissors">scissors</a></li> 
</ul>
          </h1>
        </body>
      </html>
* Connection #0 to host localhost left intact
* Closing connection #0

[~/rack/rack-rock-paper-scissors(simple)]$ rake
ruby rps.rb
>> Thin web server (v1.5.1 codename Straight Razor)
>> Maximum connections set to 1024
>> Listening on 0.0.0.0:9292, CTRL+C to stop
GATEWAY_INTERFACE => CGI/1.2
HTTP_ACCEPT => */*
HTTP_HOST => localhost:9292
HTTP_USER_AGENT => curl/7.21.4 (universal-apple-darwin11.0) libcurl/7.21.4 OpenSSL/0.9.8x zlib/1.2.5
HTTP_VERSION => HTTP/1.1
PATH_INFO => /
QUERY_STRING => choice=rock
REMOTE_ADDR => 127.0.0.1
REQUEST_METHOD => GET
REQUEST_PATH => /
REQUEST_URI => /?choice=rock
SCRIPT_NAME => 
SERVER_NAME => localhost
SERVER_PORT => 9292
SERVER_PROTOCOL => HTTP/1.1
SERVER_SOFTWARE => thin 1.5.1 codename Straight Razor
async.callback => #<Method: Thin::Connection#post_process>
async.close => #<EventMachine::DefaultDeferrable:0x007ff4e2bf8e78>
rack.errors => #<Rack::Lint::ErrorWrapper:0x007ff4e2c04b88>
rack.input => #<Rack::Lint::InputWrapper:0x007ff4e2c04c00>
rack.multiprocess => false
rack.multithread => false
rack.run_once => false
rack.url_scheme => http
rack.version => [1, 0]

Véase También

Véase la documentación de las siguientes clases:

  1. Rack::Request
  2. Rack::Response
  3. Rack::Server
  4. Rack::ShowExceptions
  5. Rack::Lint



Subsecciones
Casiano Rodriguez León 2015-01-07