Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

17 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
miércoles, 11 de septiembre de 2013
Sin duda, si hay un término que está ganando popularidad en los últimos tiempos, éste es OWIN. Y mejor que nos vayamos acostumbrando a él, porque viene pisando fuerte y probablemente nos acompañe en bastantes de nuestros desarrollos del lado servidor a partir de la próxima oleada de versiones de plataformas que está a punto de aterrizar.

OWIN (Open Web Interface for .NET) es una especificación abierta iniciada por dos miembros del equipo de ASP.NET en Microsoft, Louis Dejardin y Benjamin Vanderveen, que define un interfaz estándar para comunicar servidores con aplicaciones .NET, inspirada en iniciativas similares procedentes de tecnologías como Ruby, Python o incluso Node. La versión 1.0 de la especificación fue publicada en octubre de 2012 y está disponible en el sitio web del proyecto.


Básicamente, el objetivo de OWIN es definir una fórmula para independizar las aplicaciones .NET de los servidores sobre los que se ejecutan, actuando como una capa de abstracción entre estos dos elementos que históricamente han estado tan acoplados. Y esto creo que se entiende mejor si vemos la arquitectura de casi todas las aplicaciones ASP.NET que pululan por el mundo, que viene a ser algo como:Arquitectura monolítica de aplicaciones ASP.NET
  • Tenemos un servidor, normalmente IIS…
  • … sobre el que corre ASP.NET…
  • … con el que está construido el framework (MVC, Webforms…)…
  • … en el que desarrollamos nuestras aplicaciones.
¿Y qué problema tiene esto? Pues el acoplamiento entre todos estos componentes: hasta hace bien poco, los frameworks han estado siempre muy atados a ASP.NET, e incluso algunos continúan así. Las aplicaciones formaban parte de un ente monolítico difícil de desmembrar y utilizar cuando faltaba una de las piezas del gran bloque en el que estaban integradas, y esto siempre ha generado muchos problemas.

Un ejemplo: sabéis que podemos ejecutar una aplicación MVC sobre Linux gracias al proyecto Mono, ¿verdad? Pues esto es posible porque existe un servidor como Apache, sobre el que el equipo de dicho proyecto ha creado un módulo capaz de ejecutar su implementación propia del framework ASP.NET, y ya sobre éste se puede correr ASP.NET MVC. Observad que para que sea posible han tenido que crear toda la infraestructura completa, porque MVC necesita tener por debajo ASP.NET, y éste necesita tener por debajo un servidor web capaz de ejecutarlo; o en otras palabras, han tenido que reproducir el monolito completo. Un auténtico trabajazo que es difícil mantener actualizado por la velocidad de versionado y evolución de estas tecnologías.

Otro ejemplo: imaginad que tenéis un servicio de sistema operativo corriendo sobre Windows y queréis que su funcionamiento sea configurable por los usuarios utilizando un browser. Por simplificar despliegues, el ideal sería que el propio servicio, sin necesidad de IIS, fuera capaz de procesar peticiones del navegador, servir páginas y recoger información enviada por el usuario. Y más aún, el ideal sería que el propio servicio Windows fuera capaz de alojar una aplicación, por ejemplo implementada en ASP.NET MVC o WebForms. Pues bien, a menos que seáis expertos en magia negra, no sería fácil conseguir arrancar estos frameworks sobre algo que no fuera IIS debido al fuerte acoplamiento entre los frameworks, ASP.NET e IIS.

Y un último ejemplo, para que lo veáis aún más claro: ¿os habéis fijado en cada cuánto tiempo aparecen nuevas versiones de Webforms? Pues cada vez que hay una revisión de ASP.NET. Webforms y ASP.NET son productos indivisibles cuyos ciclos de actualización van irremediablemente vinculados, y esto no es nada bueno porque dificulta su mantenimiento e impide su evolución al ritmo que cambian los paradigmas de la programación web, lo que hace al producto menos competitivo.

Podéis leer una magnífica retrospectiva de Eduard Tomás donde comenta en orden cronológico cómo las tecnologías de desarrollo para la web de Microsoft han ido evolucionando desde el principio de los tiempos (año arriba, año abajo ;-)), desde ASP.NET clásico hasta llegar a OWIN, pasando por las importantes novedades que aportaron MVC, Nuget y WebAPI.

Arquitectura OWIN

La especificación OWIN propone una abstracción que permite separar los servidores y hosts donde se ejecutan las aplicaciones .NET, de las aplicaciones en sí mismas. La arquitectura conceptual de las aplicaciones basadas en este estándar se refleja en el siguiente diagrama, que incluye los principales actores definidos en el mismo:
OWIN architecture
  • Host, que es el proceso que aloja el sistema, como podría ser IIS o una aplicación de consola.
  • Server, que se ejecuta en el interior de un Host y que actúa como servidor HTTP. Procesa las peticiones usando la sintaxis y reglas definidas por OWIN.
  • Middleware, que son componentes ejecutados durante la gestión de las peticiones, que tienen la capacidad de capturar, procesar, e incluso alterar el contenido de las solicitudes y las respuestas. Normalmente en una aplicación usaremos varios middlewares, que componen una cadena llamada pipeline, que es “la tubería” por donde pasarán todas las peticiones recibidas. En un pipeline podría existir, por ejemplo, un módulo middleware que registrara en un log todas las peticiones, otro que comprobara si la petición procede de un usuario autenticado, y, al final, un módulo que retorne al usuario un contenido determinado. Pero siempre la información viajaría de la forma en que se describe en la especificación OWIN.
  • Framework, que son los marcos de trabajo sobre los que construiremos nuestras aplicaciones, como podrían ser MVC, Web API o SignalR. Están totalmente desacoplados del Server, pues toda la información que necesita le llegará usando las abstracciones propuestas por OWIN.
  • Application, nuestra aplicación, construida sobre uno de estos frameworks compatibles con OWIN.
Entre estos elementos conceptuales existe una línea divisoria a veces muy fina: por ejemplo, los frameworks pueden ser considerados módulos middleware complejos, o hay hosts que a su vez podrían actuar como servers. Esto no tiene mucha importancia en la práctica: lo realmente importante es que desde Server hacia arriba sólo se habla OWIN.

La arquitectura de sistemas OWIN permite, por ejemplo, que una aplicación de servidor escrita con SignalR 2.0 (ya compatible con OWIN) pueda ser alojada con total normalidad con IIS/ASP.NET, en el interior de un servicio Windows (el host sería el servicio, y el server podría estar implementado usando listeners HTTP), o en una plataforma basada en Mono/Apache, siempre que existieran los oportunos adaptadores OWIN. Nuestra aplicación sería la misma, independientemente del entorno en el que se ejecute.

Asimismo, gracias a ella es posible independizar los ciclos de release de los distintos productos al no existir dependencias rígidas y los frameworks podrán ser actualizados más frecuentemente sin esperar la llegada de nuevas versiones de las plataformas sobre las que corren. A la velocidad que va la gente de Microsoft, esto no sé si me gusta o me da miedo, la verdad ;-D

Y, por último, un detalle adicional: los middlewares ofrecen una oportunidad fantástica para comenzar a trasladar a ese punto funcionalidades trasversales tradicionalmente incluidos en el mismísimo core de ASP.NET, pero con la ventaja de que pueden ser componentes pluggables de forma opcional e independiente, y compartidos por todos los frameworks y aplicaciones. Quién sabe, quizás sea el principio del proceso de liposucción que ASP.NET necesitaba desde hace tiempo ;-)

Aquí se habla OWIN

La forma que OWIN propone para que servidores y frameworks se comuniquen evitando totalmente el acoplamiento es realmente sencilla: un diccionario de datos con claves estandarizadas. Es decir, toda la interacción entre esos dos componentes se realiza mediante un IDictionary<string, object> en el que ambas partes usan claves previamente acordadas para guardar y extraer información.

Así, cuando llega una petición, el servidor creará un diccionario de este tipo, que será considerado el contexto de la petición (algo así como el equivalente al HttpContext.Current de ASP.NET) y lo poblará con información de la solicitud y algunos otros regalitos para facilitar el proceso. Este diccionario es el que recorrerá el pipeline representando a la petición hasta encontrar un middleware que la gestione y vierta el resultado sobre el mismo diccionario.

Por ejemplo, desde el punto de vista de un framework o middleware basado en OWIN, para obtener el verbo HTTP utilizado por una petición ya se no podría utilizar el tradicional Request.HttpMethod, puesto que esto implicaría una referencia rígida a System.Web (ASP.NET). Con OWIN, el framework acudiría al diccionario asociado a la petición y obtendría el valor de la clave predefinida “owin.RequestMethod”.

Las entradas consideradas obligatorias en este diccionario son las siguientes:

Clave Valor
Datos de la solicitud
owin.RequestBody Objeto de tipo Stream que proporciona acceso al cuerpo de la petición.
owin.RequestHeaders Objeto IDictionary<string, string[]> con los valores de los encabezados de la petición.
owin.RequestMethod string que contiene el verbo de la petición.
owin.RequestPath string que contiene la ruta del recurso solicitado, relativa al raíz de la aplicación.
owin.RequestPathBase string que especifica la ruta raíz de la aplicación.
owin.RequestProtocol string que indica el protocolo y versión de la petición.
owin.RequestQueryString string conteniendo la componente querystring de la URL solicitada.
owin.RequestScheme string que contiene el esquema (http/https) usado en la petición.
Datos de la respuesta
owin.ResponseBody Objeto Stream utilizado para enviar la respuesta al cliente.
owin.ResponseHeaders Objecto IDictionary<string, string[]> que contiene los encabezados de la respuesta.
Otros datos
owin.CallCancelled CancellationToken que indica si la solicitud ha sido cancelada o abortada. OWIN está muy enfocado hacia la asincronía para obtener el mejor rendimiento posible.
owin.Version Versión de OWIN, “1.0” en estos momentos.

Aunque estas son sólo las requeridas, existen muchas más entradas que pueden venir en el diccionario asociado a la petición. En el sitio web del proyecto se pueden ver otras de carácter opcional, algunas extensiones, o incluso claves frecuentes y reglas a seguir a la hora de añadir entradas personalizadas.

Ciclo de vida de aplicaciones OWIN

La especificación OWIN define claramente cómo debe ser el ciclo de vida de las aplicaciones que se basen en este estándar. Comencemos desde el principio.

Al arrancar el sistema, el Host crea un diccionario inicial de tipo IDictionary<string, object>, que puebla con información de inicialización y datos del entorno, como la descripción de las capacidades del servidor.

A continuación, el Host selecciona el Server a utilizar y le suministra el diccionario de entorno con objeto de que él también pueda añadir información de contexto a dicho diccionario.

Seguidamente, el Host localiza el código de arranque de la aplicación y lo invoca, suministrándole el diccionario anterior. De esta forma, la aplicación tiene la oportunidad de leer o modificar el contenido del diccionario de entorno, y configurar su propio pipeline de ejecución, es decir, decidir qué componentes middleware (sean módulos o frameworks) utilizará para procesar las peticiones.

Una de las cosas más interesantes que tiene OWIN es su modularidad: nuestras aplicaciones sólo incluirán los módulos que realmente vayan a necesitar. Esto es una diferencia muy importante respecto a ASP.NET, donde su estructura monolítica hacía que cargáramos con todo su peso aunque no fuéramos a utilizar la mayoría de sus características; con OWIN sólo se incluirán los componentes que necesitemos. ¿Vamos a necesitar WebAPI? Pues añadimos WebAPI. ¿Nuestra aplicación también usa SignalR? Pues añadimos SignalR. ¿Y necesitamos un sistema de seguridad basado en cookies? Pues añadimos el correspondiente módulo de autenticación.

Bien, una vez configurado el pipeline con los módulos middleware que nos interese, se retorna al host un objeto denominado delegado de aplicación, o application delegate. También llamado AppFunc, el application delegate es sólo eso, un delegado a un método que será el encargado de procesar las peticiones. Normalmente se define de la siguiente forma:
using AppFunc = Func<
    IDictionary<string, object>, // Environment
    Task>;                       // Done
Cuando la aplicación retorna este objeto, lo que indica al Server es el código que deberá ejecutar cuando llegue una petición, que recibirá como argumento el diccionario con los datos del contexto y retornará una tarea en segundo plano (objeto Task) que se encargará de procesarla.

Finalmente, el Host inicia el Server suministrándole el delegado retornado por la aplicación, y éste comienza a aceptar peticiones.

OWIN pipeline
Por cada petición recibida, el Server crea el diccionario, lo puebla, y ejecuta con él el application delegate. A partir de ahí, la petición, ya convertida en diccionario, viajará a través del pipeline creado por la aplicación, atravesando los módulos middleware que hayan sido configurados.

Cuando un componente middleware toma el control, éste puede realizar varias cosas, por ejemplo:
  • Ejecutar una tarea y después permitir que la petición continúe su periplo por el pipeline. Por ejemplo, un middleware puede guardar en un log algunos datos de la petición y pasar el control al siguiente módulo en el pipeline, de forma que el proceso siga adelante.
  • Procesar directamente la petición y retornar la respuesta, como ocurriría con un framework. La petición llegaría al mismo, éste ejecutaría nuestra aplicación, y el resultado sería enviado de vuelta a través del pipeline.
  • Cortocircuitar el proceso y enviar un retorno personalizado. Por ejemplo, podríamos tener un módulo de seguridad que comprobara la autenticación del usuario y, en caso negativo, directamente retornara una redirección sin dar la posibilidad de que se ejecuten otros módulos.
  • Dejar que el resto de módulos se ejecuten y tomar el control cuando pase la  respuesta a la petición, observándola, post-procesándola, o sustituyéndola por otra. Por ejemplo, podríamos tener un módulo OWIN introducido en el pipeline examinando los resultados retornados al cliente y realizando sustituciones de términos, o procesando códigos HTTP determinados (por ejemplo, mostrando páginas de error personalizadas).
Finalmente, el Server recuperaría el encabezado y cuerpo del resultado desde el diccionario, y lo volcaría en el canal que mantiene abierto con el cliente, lo que daría por finalizado el proceso de la petición.

Uuf, vaya tostón... ¡Muestra ya un poco de código OWIN, por favor!

Pues me temo que no va a poder ser ;-) OWIN es sólo una especificación, no hay ningún código que ver, ni nada que podamos implementar con los conceptos que hemos visto.

El código lo dejaremos para un artículo posterior en el que hablemos del proyecto Katana, que es la implementación de Microsoft del estándar OWIN, y que contiene los componentes que utilizaremos normalmente para desarrollar utilizando esta nueva filosofía.

Publicado en Variable not found.

7 Comentarios:

Carlos dijo...

sencillamente brutal

gracias

josé M. Aguilar dijo...

Gracias, Carlos! Habrás adivinado que esto es lo que estaba en el horno ;)

Saludos!

Unknown dijo...

Hola José excelente artículo, la verdad que OWIN tendrá que ser tenido en cuenta! un componente que busca este tipo de desacoplamiento es cosa de locos, además desarrollado por integrantes del equipo de Asp.Net al igual que SignalR me imagino que se convertirá en una gran herramienta.

saludos!

ericardezp dijo...

Muchas gracias por la explicación y saludos

Barto dijo...

Muy buena explicación, y muy clara! Gracias Carlos

Bitcubico Technology dijo...

Muchas gracias, excelente tu explicación.

Unknown dijo...

Muchas gracias por la explicación. Excelente.