Subsecciones


Gestión de Sesiones

Introducción

  1. Hypertext Transfer Protocol (HTTP) is stateless: a client computer running a web browser must establish a new Transmission Control Protocol (TCP) network connection to the web server with each new HTTP GET or POST request.

  2. The web server, therefore, cannot rely on an established TCP network connection for longer than a single HTTP GET or POST operation.

  3. Session management is the technique used by the web developer to make the stateless HTTP protocol support session state.

  4. For example, once a user has been authenticated to the web server, the user's next HTTP request (GET or POST) should not cause the web server to ask for the user's account and password again.

  5. The session information is stored on the web server using the session identifier generated as a result of the first (sometimes the first authenticated) request from the end user running a web browser.

  6. The "storage" of Session IDs and the associated session data (user name, account number, etc.) on the web server is accomplished using a variety of techniques including, but not limited to, local memory, flat files, and databases.

  7. A session token is a unique identifier that is generated and sent from a server to a client to identify the current interaction session.

  8. The client usually stores and sends the token as an HTTP cookie and/or sends it as a parameter in GET or POST queries. The reason to use session tokens is that the client only has to handle the identifier—all session data is stored on the server (usually in a database, to which the client does not have direct access) linked to that identifier.

Uso de Cookies para el manejo de sesiones

  1. Allowing users to log into a website is a frequent use of cookies.
  2. A web server typically sends a cookie containing a unique session identifier. The web browser will send back that session identifier with each subsequent request and related items are stored associated with this unique session identifier.
  3. Typically the web server will first send a cookie containing a unique session identifier. Users then submit their credentials and the web application authenticates the session and allows the user access to services.
  4. Applications today usually store the gathered information in a database on the server side, rather than storing them in cookies

Ejemplo

Rack::Session::Cookie proporciona un sencillo sistema para gestionar sesiones basado en cookies.

  1. La sesión es un cookie que contiene un hash almacenado mediante marshalling codificado en base64.
  2. Por defecto el nombre del cookie es rack.session pero puede ser modificado mediante el atributo :key.
  3. Dándole un valor a secret_key se garantiza que es comprobada la integridad de los datos de la cookie
  4. Para acceder dentro de nuestro programa a la sesión accedemos al hash env["rack.session"] o bien env["key-value"] si hemos especificado el atributo :key

Sigue un ejemplo:

[~/local/src/ruby/sinatra/rack/rack-session-cookie(master)]$ cat configapp.ru 
require 'pp'
require './myapp'

use Rack::Session::Cookie, 
      :key => 'rack.session', 
      :domain => 'example.com',
      :secret => 'some_secret'

run MyApp.new

[~/local/src/ruby/sinatra/rack/rack-session-cookie(master)]$ cat myapp.rb 
class MyApp

  def set_env(env)
    @env = env
    @session = env['rack.session']
  end

  def some_key 
    return @session['some_key'].to_i if @session['some_key']
    @session['some_key'] = 0
  end

  def some_key=(value)
    @session['some_key'] = value
  end

  def call(env)
    set_env(env)
    res = Rack::Response.new
    req = Rack::Request.new env

    self.some_key = self.some_key + 1 if req.path == '/'

    res.write("some_key = #{@session['some_key']}\n")

    res.finish
  end

end

Hagamos la prueba conectándonos a www.example.com. Para ello edtiamos /etc/hosts para que localhost apunte a www.example.com:

[~/local/src/ruby/sinatra/rack/rack-session-cookie(master)]$ cat /etc/hosts
##
# Host Database
#
# localhost is used to configure the loopback interface
# when the system is booting.  Do not change this entry.
##
127.0.0.1 localhost www.example.com
...

Arrancamos el servidor:

[~/local/src/ruby/sinatra/rack/rack-session-cookie(master)]$ rackup  configapp.ru 
>> 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

Y visitamos www.example.com con nuestro navegador:

Ejercicio

Supongamos el siguiente programa rack en el que se incrementa la variable @some_key:

[~/local/src/ruby/sinatra/rack/rack-appvswebserver(icon)]$ cat configapp.ru 
class Persistence

  def call(env)
    
    res = Rack::Response.new
    req = Rack::Request.new env

    @some_key ||= 0
    @some_key = @some_key + 1 

    res.write("@some_key = #{@some_key}\n")

    res.finish
  end

end

run Persistence.new

Supongamos que arranco el servidor:

[~/local/src/ruby/sinatra/rack/rack-appvswebserver(master)]$ rackup configapp.ru >> 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

Nótese que con thin arrancado desde rack se tienen los valores de env para las claves:

rack.multithread => false
rack.multiprocess => false
lo que indica que el servidor no está soportando multithreading ni multiproceso.

Responda a estas preguntas:

  1. ¿Que valores de @some_key serán mostrados cuando me conecto a localhost:9292?
  2. ¿Y si recargo la página varias veces?
  3. ¿Y si abro un nuevo navegador o ventana de incógnito en la misma URL?
  4. ¿Y si re-arranco el servidor?
  5. ¿Como afectaría a la conducta que el servidor fuera multithreading?
    [~/local/src/ruby/sinatra/rack/rack-appvswebserver(icon)]$ rvm use jruby-1.7.3
    Using /Users/casiano/.rvm/gems/jruby-1.7.3
    [~/local/src/ruby/sinatra/rack/rack-appvswebserver(icon)]$ rackup configapp.ru 
    Puma 2.6.0 starting...
    * Min threads: 0, max threads: 16
    * Environment: development
    * Listening on tcp://0.0.0.0:9292
    rack.multithread => true
    rack.multiprocess => false
    
    [~/local/src/ruby/sinatra/rack/rack-appvswebserver(icon)]$ cat Rakefile 
    desc "run the server"
    task :default do
      sh <<-"EOS"
      #rvm use jruby-1.7.3 &&
      #ruby -v &&
      rackup -s puma configapp.ru 
      EOS
    end
    
    desc "run the client"
    task :client do
      pids = []
      (0...100).each do
        pids << fork do
          sh %q{curl -v 'http://localhost:9292' >> salida 2>> logs}
        end
      end
      puts pids
    end
    
    desc "remove output and logs"
    task :clean do
      sh "rm -f salida logs"
    end
    

De acuerdo a una respuesta en StackOverflow a la pregunta: Is Sinatra multi-threaded? I read else where that "sinatra is multi-threaded by default", what does that imply?

The choice is mainly made by the server and middleware you use:

  1. Multi-Process, non-preforking: Mongrel, Thin, WEBrick, Zbatery
  2. Multi-Process, preforking: Unicorn, Rainbows, Passenger
  3. Evented (suited for sinatra-synchrony): Thin, Rainbows, Zbatery
  4. Threaded: Net::HTTP::Server, Threaded Mongrel, Puma, Rainbows, Zbatery, Phusion Passenger Enterprise >= 4
  5. Since Sinatra 1.3.0, Thin will be started in threaded mode, if it is started by Sinatra (i.e. with ruby app.rb, but not with the thin command, nor with rackup).

Casiano Rodríguez León
2015-01-25