ruby on rails - ¿Rails no comparte nada o las solicitudes separadas pueden acceder a las mismas variables de tiempo de ejecución?
ruby-on-rails security (4)
En su implementación promedio usando Passenger, probablemente tenga múltiples procesos de aplicaciones que no comparten nada entre ellos, sino clases dentro de cada proceso que mantienen su estado (estático) desde la solicitud hasta la solicitud. Cada solicitud, sin embargo, crea una nueva instancia de sus controladores.
Puede llamar a esto un grupo de entornos de estado compartido distintos.
Para utilizar su analogía de Java, puede hacer el almacenamiento en caché y hacer que funcione de una solicitud a otra, simplemente no puede asumir que estará disponible en cada solicitud.
PHP se ejecuta en un entorno de nada compartido, lo que en este contexto significa que cada solicitud web se ejecuta en un entorno limpio. No puede acceder a los datos de otra solicitud, excepto a través de una capa de persistencia separada (sistema de archivos, base de datos, etc.).
¿Qué pasa con Ruby on Rails? Acabo de leer una publicación de blog que indica que las solicitudes separadas pueden acceder a la misma variable de clase.
Se me ha ocurrido que esto probablemente depende del servidor web. Las Preguntas frecuentes de Mongrel indican que Mongrel usa un hilo por solicitud, lo que sugiere un entorno de nada compartido. Las preguntas frecuentes continúan diciendo que RoR no es seguro para subprocesos, lo que sugiere que RoR no existiría en un entorno compartido a menos que una nueva solicitud reutilice los objetos en memoria creados a partir de la solicitud anterior.
Obviamente esto tiene enormes ramificaciones de seguridad. Así que tengo dos preguntas:
- ¿Es el entorno RoR compartido-nada?
- Si RoR se ejecuta en (o podría ejecutarse en algunas circunstancias) un entorno compartido, ¿sobre qué variables y otros datos de almacenamiento debo estar paranoico?
Actualización: voy a aclarar más. En un contenedor de servlet de Java puede tener objetos que persisten en múltiples solicitudes. Esto se hace normalmente para almacenar en caché los datos a los que tendrían acceso múltiples usuarios, conexiones de base de datos, etc. En PHP, esto no se puede hacer en la capa de la aplicación, debe hacerse en una capa de persistencia separada como Memcached. Entonces, la pregunta doble es: ¿qué escenario es RoR (PHP o Java) y si es como Java, qué tipos de datos persisten en varias solicitudes?
Este es un ejemplo relativamente simple que ilustra lo que puede suceder si no tiene cuidado al modificar objetos compartidos.
Crear un nuevo proyecto Rails:
rails test
Crea un nuevo archivo
lib/misc.rb
y ponlo en esto:class Misc @xxx = ''Hello'' def Misc.contents() return @xxx end end
- Crear un nuevo controlador:
ruby script/generate controller Posts index
Cambie
app/views/posts/index.html.erb
para que contenga este código:<% require ''misc''; y = Misc.contents() ; y << '' (goodbye) '' %> <pre><%= y %></pre>
(Aquí es donde modificamos el objeto compartido implícitamente).
- Agregue rutas RESTful a
config/routes.rb
. - Inicie el
ruby script/server
server deruby script/server
y cargue la página/posts
varias veces. Verá que la cantidad de cadenas( goodbye)
aumenta en una en cada recarga de página sucesiva.
La nada compartida es a veces una buena idea. Pero no cuando tiene que cargar un marco de aplicación grande y un modelo de dominio grande y una gran cantidad de configuración en cada solicitud.
Para mayor eficiencia, Rails mantiene algunos datos disponibles en la memoria para compartirlos entre todas las solicitudes durante la vida útil de una aplicación. La mayoría de estos datos son de solo lectura, por lo que no debe preocuparse.
Cuando escriba su aplicación, evite escribir en objetos compartidos (excluyendo la base de datos, por ejemplo, que viene de la caja con un buen control de concurrencia) y debería estar bien.
En breve:
- No, Rails nunca se ejecuta en un entorno de nada compartido.
- Sea paranoico acerca de las variables de clase y las variables de instancia de clase .
La versión más larga:
Los procesos de Rails comienzan su ciclo de vida cargando el marco y la aplicación. Por lo general, solo ejecutarán un único hilo, que procesará muchas solicitudes durante su vida útil. Por lo tanto, las solicitudes se enviarán de forma estrictamente secuencial .
Sin embargo, todas las clases persisten a través de las solicitudes. Esto significa que cualquier objeto al que se haga referencia desde sus clases y metaclases (como las variables de clase y las variables de instancia de clase) se compartirá entre solicitudes. Esto puede morderlo , por ejemplo, si intenta memorizar métodos ( @var ||= expensive_calculation
) en sus métodos de clase , esperar que solo se mantenga durante la solicitud actual. En realidad, el cálculo solo se realizará en la primera solicitud.
En la superficie, puede parecer agradable implementar el almacenamiento en caché u otro comportamiento que dependa de la persistencia entre las solicitudes. Típicamente, no lo es. Esto se debe a que la mayoría de las estrategias de implementación usarán varios procesos de Rails para contrarrestar su propia naturaleza de un solo hilo. Simplemente no es bueno bloquear todas las solicitudes mientras se espera una consulta lenta de la base de datos, por lo que la salida más fácil es generar más procesos. Naturalmente, estos procesos no comparten nada (excepto quizás algo de memoria, que no notará). Esto puede morderle si guarda cosas en sus variables de clase o variables de instancia de clase durante las solicitudes . Entonces, de alguna manera, a veces las cosas parecen estar presentes, y otras veces parecen haberse ido. (En realidad, por supuesto, los datos pueden o no estar presentes en algún proceso y ausentes en otros).
De hecho, algunas configuraciones de implementación (especialmente JRuby + Glassfish) son multiproceso. Los rieles son seguros para la rosca, por lo que puede tratar con ellos Pero su aplicación puede no ser segura para subprocesos. Todas las instancias de controlador se desechan después de cada solicitud, pero como sabemos, las clases se comparten. Esto puede morderlo si pasa información en variables de clase o en variables de instancia de clase. Si no usa los métodos de sincronización correctamente, es muy probable que termine en el infierno de la condición de carrera.
Como nota al margen: Rails generalmente se ejecuta en procesos de un solo hilo porque la implementación del hilo de Ruby es una mierda. Afortunadamente, las cosas están un poco mejor en Ruby 1.9. Y mucho mejor en JRuby.
Con estas dos implementaciones de Ruby ganando popularidad, parece probable que las estrategias de implementación de Rails multiproceso también ganen popularidad y número. Es una buena idea escribir su aplicación teniendo en cuenta el envío de solicitudes multiproceso.