[~/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
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 endsi 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"]
[~/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
[~/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 la documentación de las siguientes clases:
Instale la gema debugger
.
Llame al método debugger
en el punto en el que quiere detener la ejecución para inspeccionar el estado del programa.
Arranque el servidor y en el navegador visite la página.
Use
Haml
para crear un template index.haml
en un directorio views
.
[~/local/src/ruby/sinatra/rack/rack-rock-paper-scissors(template)]$ tree . |--- README |--- Rakefile |--- rps.rb `--- views `--- index.hamlEl template puede ser usado así:
require 'rack/request' require 'rack/response' require 'haml' module RockPaperScissors class App ... def call(env) ... engine = Haml::Engine.new File.open("views/index.haml").read res = Rack::Response.new res.write engine.render({}, :answer => answer, :choose => @choose, :throws => @throws) res.finish end # call end # App end # RockPaperScissors
Véase:
La sintáxis del método render
es:
(String) render(scope = Object.new, locals = {})También se puede usar como
to_html
.
Procesa el template y retorna el resultado como una cadena.
El parámetro scope
es el contexto en el cual se evalúa el template.
Si es un objeto Binding haml lo usa como segundo
argumento de
Kernel#eval
(Véase la sección Bindings (encarpetados) y eval en
)
en otro caso, haml utiliza
#instance_eval
.
Nótese que Haml modifica el contexto de la evaluación (bien el objeto ámbito
o el objeto self
del ámbito del binding).
Se extiende Haml::Helpers y se establecen diversas variables de instancia
(todas ellas prefijadas con haml_
).
Por ejemplo:
s = "foobar" Haml::Engine.new("%p= upcase").render(s)produce:
"<p>FOOBAR</p>"
Ahora s
extiende Haml::Helpers :
s.respond_to?(:html_attrs) #=> true
Haml::Helpers contiene un conjunto de métodos/utilidades para facilitar distintas tareas. La idea de que estén disponibles en el contexto es para ayudarnos dentro del template. Por ejemplo el método
- (String) escape_once(text)Escapa las entidades HTML en el texto.
locals
es un hash de variables locales que se deja disponible dentro
del template. Por ejemplo:
Haml::Engine.new("%p= foo").render(Object.new, :foo => "Hello, world!")producirá:
"<p>Hello, world!</p>"
Si se pasa un bloque a render
el bloque será ejecutado
en aquellos puntos en los que se llama a yield
desde el
template.
Debido a algunas peculiaridades de Ruby, si el ámbito es un Binding y se proporciona también un bloque, el contexto de la evaluación puede no ser el que el usuario espera.
Parametros:
scope (Binding, Proc, Object)
(por defecto: Object.new
).
El contexto en el que se evalúa el template
locals ({Symbol => Object})
(por defecto: {}
).
Variables locales que se dejan disponibles en el template
block (#to_proc)
Un bloque que será llamado desde el template.
Añada hojas de estilo a la práctica anterior (sección 43.17.2).
[~/local/src/ruby/sinatra/rack/rack-rock-paper-scissors(bootstrap)]$ tree . |--- Gemfile |--- Gemfile.lock |--- README |--- Rakefile |--- TODO |--- config.ru |--- lib | `--- rps.rb |--- public | |--- css | | |--- bootstrap-responsive.css | | |--- bootstrap-responsive.min.css | | |--- bootstrap.css | | `-- bootstrap.min.css | |--- img | | |--- glyphicons-halflings-white.png | | |--- glyphicons-halflings.png | | `-- programming-languages.jpg | `-- js | |--- bootstrap.js | `--- bootstrap.min.js |--- rps.rb `-- views `--- index.haml 6 directories, 18 files
use Rack::Static, :urls => ["/public"]Servirá todas las peticiones que comiencen por
/public
desde la carpeta public
localizada en el directorio actual (esto
es public/*
).
En nuestro jerarquía pondremos en el programa rps.rb
:
builder = Rack::Builder.new do use Rack::Static, :urls => ['/public'] use Rack::ShowExceptions use Rack::Lint run RockPaperScissors::App.new end Rack::Handler::Thin.run builder
y dentro del template haml nos referiremos por ejemplo al fichero javascript como
%script{:src => "/public/js/bootstrap.js"}Otro ejemplo:
use Rack::Static, :urls => ["/css", "/images"], :root => "public"servirá las peticiones comenzando con
/css
o /images
desde la carpeta public
en el directorio actual
(esto es
public/css/*
y public/images/*
)
views/index.haml
deberá enlazar a las hojas de estilo:
!!! %html{:lang => "en"} %head %meta{:charset => "utf-8"}/ %title RPS %link{:href => "/public/css/bootstrap.css", :rel => "stylesheet"} %link{:href => "/public/css/bootstrap.css", :rel => "stylesheet"}y las imágenes como:
%img(src="/public/img/programming-languages.jpg" width="40%")
path info
de la petición Rack.
por ejemplo, cuando se usa
Rack::File.new("/etc")
podremos acceder al fichero passwd
como
localhost:9292/passwd
.
Casiano Rodríguez León