Realizando pruebas de carga de una aplicación Cookieless

Creado el día 09/01/2008 09:31 por zorry

La aplicación web en la que estoy trabajando, trabaja en modo cookieless. Necesito probarla y replicar el funcionamiento del navegador. Una aplicación funcionando en modo cookieless almacena el ID de sesión en la URL, de manera que tras la primera petición del navegador, la aplicación redirige a otra Url, con el ID de sesión embebido, y las consecuentes peticiones contienen este Id de Sesión.

Cuando realizamos una captura de sesión mediante Fiddler (ver artículo anterior), al capturar la sesión, capturamos el SessionID, quedando este en hardcode en las peticiones. Si necesitamos que el SessionID varíe para cada prueba, necesitamos que capturar el SessionID de la primera petición y propagarlo a las siguientes.

Para poder hacerlo, vamos a crearnos un CustomExtractionRule, para ello, nos creamos una clase que herede de ExtractionRule:

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Text;
   4: using Microsoft.VisualStudio.TestTools.WebTesting;
   5:  
   6: namespace WebTest.Funcional
   7: {
   8:     public class ExtractCookielessSessionId : ExtractionRule
   9:     {
  10:         public override void Extract(object sender, ExtractionEventArgs e)
  11:         {
  12:             if (!e.Response.IsHtml)
  13:             {
  14:                 e.Success = false;
  15:                 e.Message = "The response did not contain HTML";
  16:             }
  17:  
  18:             //Obtiene el SessionID para cookieless
  19:             string beginSessionId = "(S(";
  20:             string endSessionId = "))";
  21:             string url = e.Response.ResponseUri.AbsoluteUri;
  22:             int inicioSID = url.IndexOf(beginSessionId);
  23:             int finSID = url.IndexOf(endSessionId);
  24:  
  25:             if (inicioSID >= 0 && finSID > inicioSID)
  26:             {
  27:                 string sessionId = url.Substring(inicioSID, finSID - inicioSID + endSessionId.Length);
  28:                 e.WebTest.Context.Add(this.ContextParameterName, sessionId);
  29:             }
  30:             
  31:         }
  32:  
  33:         public override string RuleName
  34:         {
  35:             get { return "ExtractCookielessSessionId"; }
  36:         }
  37:  
  38:         public override string RuleDescription
  39:         {
  40:             get { return "Extracts Cookieless Session Id"; }
  41:         }
  42:     }
  43: }

 

Una vez creada esta clase, compilamos el proyecto de pruebas, y agregamos la nueva CustomExtractionRule a la primera petición. Además, a la regla, le ponemos el nombre de parámetro de contexto SESSION (de esta manera, la regla de extracción sacará el SessionID a esta variable de contexto).

addextractionrule

Posteriormente, sólo nos quedará modificar el resto de las Url, sustituyendo el SessionID por el valor {{SESSION}}:

steps

Con esto, al ejecutar el webtest podremos ver cómo se emplea cada vez un SessionID diferente, con lo que conseguimos independizar cada prueba.


Cómo hacer funcionar correctamente el AjaxControlToolkit en una aplicación cookieless (revisado)

Creado el día 29/12/2007 08:12 por zorry

Revisando el fix que hice ayer, y debido sobre todo a que no me gusta hacer modificaciones en librerías que no dependen de mí (sobre todo, para evitar que en una nueva release del AjaxControlToolkit me machaquen los cambios), me he fijado en una propiedad del ToolkitScriptManager denominada CombineScriptsHandlerUrl. Esta propiedad permite especificar un handler específico para manejar la combinación de todos los script del AjaxControlToolkit. De modo que me dispongo a deshacer los cambios que hice ayer, y hago varios cambios en mi aplicación web:

  • En la definición del ToolkitScriptManager, he incluído el siguiente atributo: CombineScriptsHandlerUrl="~/CombineScriptsHandler.ashx"
  • He creado un nuevo handler en la aplicación con el nombre definido en el nombre anterior. En el archivo ashx he introducido el siguiente código:

 

   1: <%@ WebHandler Language="C#" Class="CombineScriptsHandler" %>
   2: 
   3: using System;
   4: using System.Web;
   5: using AjaxControlToolkit;
   6: 
   7: public class CombineScriptsHandler : IHttpHandler
   8: {
   9:     /// <summary>
  10:     /// ProcessRequest implementation outputs the combined script file
  11:     /// </summary>
  12:     /// <param name="context"></param>
  13:     public void ProcessRequest(HttpContext context)
  14:     {
  15:         if (!ToolkitScriptManager.OutputCombinedScriptFile(context))
  16:         {
  17:             throw new InvalidOperationException("Combined script file output failed unexpectedly.");
  18:         }
  19:     }
  20: 
  21:     /// <summary>
  22:     /// IsReusable implementation returns true since this class is stateless
  23:     /// </summary>
  24:     public bool IsReusable
  25:     {
  26:         get { return true; }
  27:     }
  28: }
  29: 

Con estos cambios por fin he conseguido que la aplicación funcione correctamente en modo cookieless, y sin los problemas de javascript que me estaba encontrando anteriormente.


Cómo hacer funcionar correctamente el AjaxControlToolkit en una aplicación Cookieless

Creado el día 28/12/2007 15:12 por zorry

Ok, por fin tengo mi aplicación funcionando en modo cookieless (para que funcione ciertas cosas de la aplicación que la consume). Instalo la última version del AjaxControlToolkit, configuro el tag ToolkitScriptManager correctamente, y pongo el tag CombineScripts a true, para que me combine todos los scripts generados por el toolkit en uno sólo.

Y al cargar la página, me pierde la sesión, en las trazas de IIS, veo que se está llamando a mi página inicial con otro SessionID, con lo que me da un error al cargar los scripts y la página no funciona...

En concreto el problema es que trata de cargar el script sin poner delante el SessionID:

/WebApp/Resumen.aspx?_TSM_HiddenField_=ctl00_scriptManager_HiddenField&amp;_TSM_CombinedScripts_=%3b%3bAjaxControlToolkit%2c+Version%3d1.0.11119.32029%2c+Culture%3dneutral%2c+PublicKeyToken%3d28f01b0e84b6d53e%3aes-ES%3a2d550902-56f7-46bd-9795-b930029c9f3f%3ae2e86ef9%3a9ea3f0e2%3a9e8e87e9%3a1df13a87%3a80f47b59%3ad7738de7

Tenemos que lograr que el AjaxControlToolkit renderice la siguiente llamada para asegurarnos que no se pierde la sesión:

/WebApp/(S(w4fcmx45goyog355ydy4rw3w))/Resumen.aspx?_TSM_HiddenField_=ctl00_scriptManager_HiddenField&amp;_TSM_CombinedScripts_=%3b%3bAjaxControlToolkit%2c+Version%3d1.0.11119.33116%2c+Culture%3dneutral%2c+PublicKeyToken%3d28f01b0e84b6d53e%3aes-ES%3af5528113-d4f3-4bcc-99aa-dc1a40d76a47%3ae2e86ef9%3a9ea3f0e2%3a9e8e87e9%3a1df13a87%3a80f47b59%3ad7738de7

Para ello, abrimos el código del AjaxControlToolkit... En concreto tocamos en la clase ToolkitScriptManager.cs... (Tengamos en cuenta que estoy trabajando con la release del 19 de Noviembre de 2007). La línea a modificar es la 144, y la reemplazaremos dejando el siguiente código:

 

   144:  _combinedScriptUrl = String.Format(CultureInfo.InvariantCulture, 
   145:      "{0}?{1}={2}&{3}={4}", ((null != _combineScriptsHandlerUrl) ? _combineScriptsHandlerUrl.ToString()
   146:         : (new Uri(Page.Request.Url, Page.Request.RawUrl)).AbsolutePath)
   147:      , HiddenFieldParamName, HiddenFieldName, CombinedScriptsParamName, 
   148:      HttpUtility.UrlEncode(SerializeScriptEntries(_scriptEntries, false)));

 

El código modificado se encuentra en la línea 146. De esta manera, se parsea la Url de la petición haciendo que se solicite la Url del script con el id de sesión, funcionando así correctamente la llamada Cookieless.


Reescribir la URL para soportar una aplicación Cookieless

Creado el día 05/12/2007 08:12 por zorry

Al hilo del post anterior, la aplicación Windows que trabaja con nuestra aplicación web, es una aplicación con un Internet Explorer embebido. Como el manejo de las Url lo realiza por debajo, no soporta llamar a una Url con un SessionId específico de la siguiente forma:

http://servidor/Aplicacion/(S(i1hxwon1me1aazix1w1jnd55))/default.aspx

Esto es porque sólo soporta enviar parámetros mediante querystring, de la siguiente manera:

http://servidor/Aplicacion/default.aspx?SessionId=(S(i1hxwon1me1aazix1w1jnd55))/

Con lo que necesitamos realizar un reescrito de la Url para que el servidor coja correctamente el Id de sesión que queremos que emplee. Lo conseguimos mediante un modulo Http.

Lo primero es crearnos una clase dentro de nuestra aplicación web:

   1: namespace MiAplicacion.Web.Modulo
   2: {
   3:     public class RewriteModule : IHttpModule
   4:     {
   5:         public RewriteModule() { }
   6:  
   7:         public void Dispose() { }
   8:  
   9:         public void Init(HttpApplication application)
  10:         {
  11:             application.BeginRequest += new EventHandler(application_BeginRequest);
  12:         }
  13:  
  14:         void application_BeginRequest(object source, EventArgs e)
  15:         {
  16:             HttpApplication app = (HttpApplication)source;
  17:  
  18:             //Control de sesión cookieless
  19:             if (getIsCookielessSessionState())
  20:                 processCookielessSessionId(app);
  21:         }
  22:     }
  23: }

En este código nos creamos un manejador para el evento BeginRequest. En este manejador comprobamos que la aplicación tiene configurada la sesión cookieless (línea 19), y en el caso de que así sea, reescribir la url (línea 20).

Seguidamente, hay que modificar el web.config, para activar el módulo http:

   1: <httpModules>
   2:  <add name="AuthHTTPModule" type="MiAplicacon.Web.Modulo.RewriteModule"/>
   3: </httpModules>

Y por último, y no menos importante, el código para los métodos auxiliares:

   1: private bool getIsCookielessSessionState()
   2: {
   3:     object untypedSessionState = ConfigurationManager.GetSection("system.web/sessionState");
   4:     try
   5:     {
   6:         if (untypedSessionState != null)
   7:         {
   8:             System.Web.Configuration.SessionStateSection section =
   9:                 (System.Web.Configuration.SessionStateSection)untypedSessionState;
  10:  
  11:             return section.Cookieless == HttpCookieMode.UseUri;
  12:         }
  13:     }
  14:     catch
  15:     {
  16:         return false;
  17:     }
  18:  
  19:     return false;
  20: }
  21:  
  22: private void processCookielessSessionId(HttpApplication app)
  23: {
  24:     //Tratamiento para las sesiones cookieless
  25:     string parameterSessionId = "SessionId";
  26:     string url = app.Request.RawUrl;
  27:     int indexOfSessionId = url.IndexOf(parameterSessionId, StringComparison.CurrentCultureIgnoreCase);
  28:  
  29:     if (indexOfSessionId >= 0)
  30:     {
  31:         string sessionId = url.Substring(indexOfSessionId + parameterSessionId.Length + 1);
  32:         if (sessionId.IndexOf("&") >= 0)
  33:             sessionId = sessionId.Substring(0, sessionId.IndexOf("&"));
  34:  
  35:         if (sessionId.Length > 0)
  36:         {
  37:             url = url.Replace(app.Request.ApplicationPath, String.Empty);
  38:             url = url.Replace(parameterSessionId + "=" + sessionId, String.Empty);
  39:  
  40:             string newUrl = app.Request.ApplicationPath + "/" + sessionId + "/" + url;
  41:  
  42:             //Estandarizamos la url
  43:             newUrl = newUrl.Replace("?&", "?");
  44:             newUrl = newUrl.Replace("//", "/");
  45:  
  46:             if (newUrl[newUrl.Length - 1] == '?')
  47:                 newUrl = newUrl.Remove(newUrl.Length - 1);
  48:             if (newUrl[newUrl.Length - 1] == '&')
  49:                 newUrl = newUrl.Remove(newUrl.Length - 1);
  50:  
  51:             //Realizamos un redirect
  52:             app.Response.Redirect(newUrl);
  53:         }
  54:     }
  55: }

En el primer método leemos el web.config para obtener si está activada la sesión cookieless (en el evento BeginRequest, no se puede acceder a los objetos de HttpApplication relativos a la sesión).

En el segundo método, parseamos la querystring y en el caso de que llegue el valor SessionId, realiza un Response.Redirect a la url con el SessionId embebido correctamente.