[~/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 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"]
[~/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.haml
El 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