Compartir a través de


Este artículo proviene de un motor de traducción automática.

Pronóstico: nublado

Mezcla Node.js en su Windows Azure solución

Joseph Fultz

 

Joseph FultzNode.js ha estado recibiendo mucha prensa últimamente, y es altamente promocionado por su modelo asincrónico de I/O, que libera el subproceso principal para hacer otro trabajo mientras esperan respuestas de I/O. La regla general de Node.js es que I/O es caro, y que intenta mitigar el gasto forzando un modelo asincrónico de I/O. He estado pensando acerca de cómo se podría integrar en un marco ya existente. Si usted está empezando desde cero, es relativamente fácil de diseñar las opciones de tecnología y tomar una decisión, incluso si la decisión se basa bastante cool factor por sí solo. Sin embargo, si el objetivo es realizar una actualización de la tecnología en una parte de una solución, el truco es escoger algo que es actual, tiene un futuro, no viene con un gran costo adicional y encaja perfectamente con el paisaje existente de la solución.

Eso es exactamente lo que voy a demostrar en esta columna. Me quedo con una solución existente que permite la visualización de documentos en almacenamiento de información, pero requiere una firma de acceso compartido a descargarlos. A esa solución agregaré una sencilla interfaz de usuario utilizando Node.js. Para ayudar a que la aplicación, me quedo con ventaja de algunos marcos utilizados para Node.js. La solución, por tanto, incluirá:

  • Motor principal de node.js—the
  • Express — un Model-View-Controller (MVC)-marco de estilo
  • Jade: un motor de renderizado y plantillas

Juntos estas tres herramientas proporcionará un marco Rico desde el cual construir la interfaz de usuario, como usando ASP.NET MVC 3 y Razor.

Para comenzar

Si eres nuevo en Node.js, es probablemente lo mejor es empezar con los tutoriales disponibles de Microsoft en windowsazure.com/develop/nodejs. También deberás instalar el SDK de Node.js para Windows Azure. Además, usted probablemente querrá pasar un poco de tiempo metiese alrededor Express (expressjs.com) y Jade (jade-lang.com). Si eres nuevo en estas herramientas, encontrará algunos conceptos familiares y una mezcla de sintaxis familiar y desconocida.

En este caso, mis servicios existentes hará el trabajo en el lado de Windows Azure, y el sitio alojado en Windows Azure basada en Node.js llamará esos servicios para representar una lista de documentos para el acceso. Generalmente, es una práctica útil para poner una capa de direccionamiento indirecto entre el cliente y los servicios de back-end. Esto aísla los servicios de cambios de interfaz, pero el valor real es a menudo en la flexibilidad extra funcional y la forma en que puede incluir y excluir a los proveedores de servicios de back-end.

En la solución existente, como representado en figura 1, el objetivo era dar un acceso de usuario sólo si él fue autenticado, lo que genera una firma de acceso compartido (SAS). La idea era que conceda acceso de lectura a quienes autenticado y luego posteriormente otorgar plena creación, lectura, actualización, acceso de eliminar (CRUD) a un documento particular basado en papel y pertenencia a nivel. Aquí me centraré exclusivamente en los permisos de lectura.

The Request Sequence
Figura 1 la secuencia de solicitud

Creación de los servicios

Voy a la burlarse de un servicio de autenticación para devolver un ID. La llamada de servicio posterior recupera una lista de archivos. El recipiente de almacenamiento de Windows Azure que estoy usando ("documentos") tiene los permisos públicos restringidos. Quiero ofrecer una lista de documentos, incluso si el usuario no está autenticado, pero no desea que un usuario no autenticado para poder abrir los archivos. Mis dos firmas llamadas de la API que he creado son:

http://[host]/admin/GetAccess?User=[user] & pwd = [contraseña]

http://[host]/admin/Files?accessId=[authId]

Por supuesto, querrá un servicio auth más realista que no utilice el querystring y SSL; Yo no cubre ese pedazo de la solución aquí.

En primer lugar, necesito un método para crear una SAS (ver figura 2). Necesitaré este método poco cuando se crea el método que se basa la lista de documentos.

Método de la figura 2 para obtener la firma de acceso compartido

public string GetSharedAccessSignature()
{
  string sas = "";          
  sas = (string) System.Web.HttpContext.Current.Cache.Get("sas");
  // If no SAS then get one
  if (sas == null)
  {
    // TODO: hardcoded container, move to config
    CloudBlobContainer container = 
      blobClient.GetContainerReference("documents");
    // Ask the container for SAS passing in the a newly initialized policy
    sas = container.GetSharedAccessSignature(new SharedAccessPolicy()
    {
      SharedAccessStartTime = DateTime.Now,
      SharedAccessExpiryTime = DateTime.Now.AddMinutes(MaxMinutes),
      Permissions = 
        SharedAccessPermissions.Read | SharedAccessPermissions.List
    });
    // Add to cache for reuse because this isn't a per-user SAS
    System.Web.HttpContext.Current.Cache.Add("sas", sas, null,
      DateTime.Now.AddMinutes(MaxMinutes), new TimeSpan(0,0,5,0,0),
      CacheItemPriority.High, null);
  }
  return sas
}

En su mayor parte, esto es bastante típico código de Windows Azure Storage hasta la llamada a GetSharedAccessSignature. No hay una política de acceso compartido aquí, así que necesito para pasar información acerca de cuándo iniciar o detener permitiendo el acceso y el tipo de permisos. Todo lo que quiero ofrecer a través de la SAS es la capacidad para leer y mostrar archivos. También, porque el SAS se utilizará teóricamente por quien se autentica, agregarlo a la caché para su reutilización para evitar colisiones y reducir la cantidad de tasas de deserción en la generación de claves de acceso.

La interfaz del servicio se configurará como un WebMethod:

[OperationContract]
[WebGet(UriTemplate = "Files?accessId={accessId}")]
List<BlobInfo> GetFiles(string accessId);

Observe el uso de la clase personalizada BlobInfo — una vez más, estoy usando direccionamiento indirecto. Tengo campos específicos que quiero volver y IListBlobItem no representan necesariamente. Así, te Mariscal la información devuelta desde IListBlobItems en una lista de mi tipo, tal como se muestra en la figura 3.

Figura 3 GetFiles aplicación

public List<BlobInfo> GetFiles(string accessId)
{
  List<BlobInfo> blobs = new List<BlobInfo>();
  CloudBlobClient sasBlobClient = default(CloudBlobClient);
  CloudStorageAccount storageAccount =
    CloudStorageAccount.FromConfigurationSetting(
    "StorageAccountConnectionString");
  string sas = default(string);
  if(accessId.Length > 0)
  {
    // For the mock just make a simple check
    if(VerifyId(accessId))
    {
      sas = GetSharedAccessSignature();
      // Create the blob client directly, using the SAS
      sasBlobClient = new CloudBlobClient(storageAccount.BlobEndpoint,
        new StorageCredentialsSharedAccessSignature(sas));
    }
  }
  else
  {
    sasBlobClient = storageAccount.CreateCloudBlobClient();
  }
  CloudBlobContainer blobContainer =
    sasBlobClient.GetContainerReference("documents");
  foreach (IListBlobItem blob in blobContainer.ListBlobs())
  {
    BlobInfo info = new BlobInfo();
    info.Name = blob.Uri.LocalPath;
    info.Uri = blob.Uri.AbsoluteUri;
    info.Sas = sas;
    info.CombinedUri = blob.Uri.AbsoluteUri + sas;
    blobs.Add(info);
  }
  return blobs;
}

Es importante tener en cuenta en figura 3 que estoy usando el SAS si se autentica el usuario con el fin de devolver una lista que respeta la política de acceso en el contenedor.

Con el servicio de transferencia de estado representacional (REST) en su lugar puedo ejecutar una prueba rápida a través de una ventana del explorador. Al configurar la interfaz de servicio de esta manera, se hace más fácil para mí para burlarse de autenticación utilizando un valor conocido hasta que tengo el bucle generando la lista y el SAS funcionando correctamente. Una vez hecho esto, el VerifyId(string) simplemente comprueba si tengo una credencial en caché con una clave igual a la accessId. Figura 4 muestra una lista devuelta sin ser autenticado. Porque no era autentica la lista devuelta por el servicio, el valor SAS se establece a cero. Así, puedo usar los datos para hacer la lista, pero no puedo dar un vínculo de trabajo para el usuario, porque no hay ningún SAS.

An Unauthenticated List
4 Figura una lista no autenticada

Figura 5 muestra la lista autenticada, que incluyen el SAS.

An Authenticated List with the SAS
Figura 5: una lista autenticada con el SAS

Será el trabajo del cliente Node.js para ordenar a través de lo que devuelve el servicio desde una llamada autenticada y representar los hipervínculos con el SAS fijarse al URI. Para ayudar con eso, que he proporcionado un elemento CombinedUri para que el cliente deba acceder sólo que un elemento. Por último, mientras que el XML es genial, porque estoy trabajando en Node.js, tiene sentido cambiar la atribución de la interfaz que devolver JSON para que la respuesta del servicio puede consumirse directamente como un objeto:

[WebGet(UriTemplate = "Files?accessId={accessId}",
  ResponseFormat=WebMessageFormat.Json)]

Aquí está lo que la salida JSON como luce:

[{"CombinedUri":"https:\/\/footlocker.blob.core.windows.
net\/documents\/AzureKitchen-onesheet.docx?st=2012-03-05T05%3A22%3A22Z&se=2012-03-05T05%3A27%3A22Z&sr=c&sp=rl&sig=Fh41ZuV2y2z5ZPHi9OIyGMfFK%2F4zudLU0x5bg25iJas%3D","Name":"\/documents\/AzureKitchen-onesheet.docx","Sas":"?st=2012-03-05T05%3A22%3A22Z&se=2012-03-05T05%3A27%3A22Z&sr=c&sp=rl&sig=Fh41ZuV2y2z5ZPHi9OIyGMfFK%2F4zudLU0x5bg25iJas%3D","Uri":"https:\/\/footlocker.blob.core.windows.
net\/documents\/AzureKitchen-onesheet.docx"}]

Como se señaló, JSON es lo que en última instancia queremos aquí, ya que es directamente consumible en Express y Jade.

Interfaz de usuario de node.js

Ya he configurado Node.js, Express y Jade, así que estoy listo para crear la interfaz de usuario. Me he pasado por el proceso de implementación de roles Node.js obtener y ejecutar en Visual Studio, pero es un proceso bastante detallado y completo manual. Así, porque no hay ninguna integración de herramientas para la parte de Node.js de esta, usaré Sublime 2 texto cromado para depurar y hacer mis tareas de edición (como se describe en el blog de Tomasz Janczuk en bit.ly/uvufEM).

Debo mencionar algunos artículos de limpieza. Para los no iniciados, los marcos que estoy empleando proporcionan algunos contenedores easy-to-use cierta funcionalidad, MVC y un motor de representación de la plantilla:

  • Restler para llamadas de resto fáciles (que simplificado WebClient)
  • Express para el marco general de aplicación MVC-estilo
  • Jade para procesamiento de plantilla, relacionado con el motor de Razor en ASP.NET MVC

Estos son todos los módulos considerados Node.js (como archivos DLL en. net) y generalmente se instalan desde GitHub via nodo paquete Manager (MNP). Por ejemplo, para instalar Restler, utilice el comando "restler Instale el MNP" desde dentro de la carpeta del proyecto. Esto toma el lugar de instalación de un módulo y agregar una referencia a él en el proyecto manualmente.

Un último bit de información para el desconocido. Usted notará que muchas funciones anónimas anidado en otras funciones. Mi mejor Consejo es simplemente volver a formatear el código suficiente para ver la anidación mientras trabaja con ella hasta que se lo analizar naturalmente sin tener que formatearlo. Voy intentar anidar mis muestras para mejorar la legibilidad, así como utilizar imágenes de Sublime, que son de color bien y ayudar con la legibilidad.

Usé los comandos New-AzureService y AzureWeb de nueva­papel para crear una aplicación denominada AzureNodeExpress. También he hecho un par de otras modificaciones. En server.js se agregó rutas para llegar a la página de índice; el ASP.NET analógico es el método de MapRoutes utilizado en proyectos MVC.

Server.js modificaciones

Mucho como instrucciones using en C#, necesito decirle Node.js qué bibliotecas utilizará. En Node.js, configuran estas referencias asignando a una variable el valor de la devolución de la requieren ('[lib]') función nombre. Una vez establecidas las referencias, hago un poco de configuración para establecer algunas de las variables del motor (por ejemplo, establecer "ver el motor" "jade"). De particular interés son el "motor de vista", router, bodyParser, cookieParser y sesión.

Voy a saltar algunos de los elementos más mundanos, pero quiero configurar mi ruta. Para el verbo Get en mi página de índice voy simplemente representar la vista directamente:

app.get('/index',
  function(req, res){
    res.render('index.jade', {title: 'index'});
  }
);

Sin embargo, para el verbo Post quiero pasar el manejo sobre el modelo de índice. Para lograrlo tengo que "obligar" a un método definido del modelo:

app.post('/index', index.GetAccessKey.bind(index));

Con el enrutamiento en lugar que necesito configurar la vista y el modelo.

La View—Index.jade

En cierto sentido, estoy saltando desde el principio hasta el final yendo desde el controlador a la vista, pero cuando se trabaja en un estilo MVC quiero crear una vista simplificada para trabajar contra el primero. Sintaxis de Jade es básicamente HTML sin la decoración de los soportes. Mi plantilla de Jade todo se muestra en la figura 6.

Figura 6 plantilla de Jade

    html
    head
      title Default
    body
      h1 File List Home Page
      br
      label Welcome #{username}
      form(method='post', action='/index')
        label Username:
          input(name='username', type='text')
        br
        label Password:
          input(name='password', type='password')
        br
        button(type='submit') Login
      h2 Files   
      form
        table(border="1")
          tr
            td Name
            td Uri
            each doc in docList
              tr
                td #{doc.Name}
                td
                  a(href=#{doc.CombinedUri}) #{doc.Name}

Aquí destacan el uso de # {[var]} para hacer referencia a variables y la plantilla de la tabla con el lazo, que es una especie de foreach abreviado. Yo he nombrado arbitrariamente la lista de artículos que quiero iterar sobre docList. Esto es importante porque en la página de index.js donde pido Jade para representar esta vista, que necesito pasar el valor para docList. Las cosas son muy básicas aquí, porque solo estoy creando un desarrollador IU — simple y llano.

La Model—Index.js

Habiendo establecido la infraestructura de tiempo de ejecución en server.js y la plantilla de vista final en index.jade, me quedo con la carne de la ejecución, lo que sucede en index.js. Recuerde que configurar un enlace para app.Publicar en la página de índice. Que enlace se cargue y ejecute el prototipo que he creado en index.js. Para ello, agregaré funciones con el prototipo del índice, como se muestra en la figura 7. En esencia, estoy creando una función con nombre (por ejemplo, GetAccessKey) y definir una función anónima como su órgano de ejecución. En cada una de estas funciones usaré el módulo Restler para simplificar las llamadas resto que necesito hacer.

Figura 7 Index.js funciones

La primera llamada una vez que el Post pasa de bind es GetAccessKey, que simplemente toma el usuario y contraseña que he presentado a través del post de formulario, les anexa a la URI como parte de la cadena de consulta y usa Restler para hacer un Get. Recuerde, en Node.js toda la comunicación pasa de forma asincrónica, que es una de las razones que se verá la proliferación de funciones anónimas altamente anidadas. Mantenerse fiel a ese patrón en la llamada a rest.get, definir una función anónima que se ejecuta una vez finalizada la solicitud. Sin el código de control de errores, la línea se simplifica a:

rest.get (uri).on('complete',
  function(result){
    accesskey = result;
this.ShowDocs (req, res);}
  )

Ojala, que volver a formatear ayudarán a darle un sentido de lo que está pasando. Una vez que me he metido la clave de mi servicio, voy postfix al URI en el método para obtener la lista de documentos. Ahora las cosas se ponen un poco diferentes a la habitual. La función anónima manejo el retorno de la llamada de resto para obtener la lista de documentos, pido a Jade para procesar los resultados para mí:

res.render ('index', {title: "Doc List",
    layout: false,
    docList: result,
    username: req.BODY.username});

Señaló que en la plantilla que he creado el nombre de la variable "docList." Ahora necesito para asegurarse de que estoy usando ese nombre correcto. La llamada a res.render dice el marco Express para procesar el recurso de "índice" y luego pasa parámetros a través de una lista de pares de nombre: valor de dos puntos y comas.

Runtime

Si intento buscar a uno de los archivos para descargar, me presenté con nada. No se encuentra la página Web. Se podría esperar un error no autorizado de Windows Azure Storage, pero si se intenta acceder a algo que es marcados como privados, lo que vuelve es que no existe el recurso. Esto es así por diseño, y es deseable porque algo que es "privado" no debería existir para el público, incluso en el concepto. Si en cambio se devuelve un error 401, indicaría que algo está realmente allí, violando la política de seguridad representada por privado.

Porque he fijado la ubicación de almacenamiento, no se permite ningún acceso directo. Sin embargo, una vez que ejecuto el código de ejemplo, la historia es un poco diferente. I publicar la aplicación utilizando el comando de Windows PowerShell publicar-AzureService, vaya a la página y escriba mis credenciales; Entonces estoy presenta una lista de vínculos a los archivos (véase figura 8).

Links to the Files
Figura 8 vínculos a los archivos

Porque mi servicio es intermediación las llamadas para almacenamiento, fui capaz de listar los archivos, aunque no puedo enumerarlas directamente. También, porque cada vínculo es fijarse con el SAS, cuando hago clic en él yo soy pedirá abrir o guardar el documento de destino.

En resumen

Si estás interesado en las tecnologías nuevas o tendencias como una manera de evolucionar su aplicación de Windows Azure, y eres un verdadero creyente de lo que está sucediendo en el dominio de Node.js, Windows Azure le ha cubierto — no sólo con la solución de alojamiento, pero también en el lado de desarrollo con opciones tales como una biblioteca de cliente para Node.js, golpeando la API REST directamente o a través de direccionamiento indirecto como he demostrado aquí. Desarrollo sin duda sería mucho mejor y más fácil si Node.js contó con apoyo de herramientas adecuadas, y estoy seguro de que finalmente veremos a algún tipo de integración con Visual Studio si la popularidad de Node.js sigue creciendo.

Joseph Fultz es un arquitecto de software en Hewlett-Packard Co., que trabaja como parte del grupo HP.com Global IT. Anteriormente fue un arquitecto de software de Microsoft, trabaja con su empresa de primer nivel y clientes de ISV para definir soluciones de arquitectura y diseño.

Gracias a los siguientes expertos técnicos por su ayuda en la revisión de este artículo: Bruno Terkaly