Mostrando entradas con la etiqueta IIS. Mostrar todas las entradas
Mostrando entradas con la etiqueta IIS. Mostrar todas las entradas

martes, 19 de febrero de 2013

Compresión HTTP

Habilitar la compresión de ficheros en el servidor antes de ser enviados al explorador, puede suponer una notable mejora del ancho de banda utilizado por tu aplicación. La idea es sencilla, si el explorador es capaz de entender un fichero comprimido, el servidor lo comprimirá antes de enviarlo por la red y después el explorador lo descomprimirá cuando lo reciba.

Un navegador indica al servidor que soporta la compresión a través de la cabecera Accept-Encoding. En esta cabecera se listan los modos de compresión que acepta el navegador. Normalmente tendrá un valor similar a gzip,deflate. Es decir, el navegador aceptará contenido comprimido desde el servidor con gzip o con deflate.

En cuanto al servidor, deberá tener activada la compresión y cuando un cliente envíe la cabecera Accept-Encoding con un valor válido, comprimirá el fichero antes de servírselo al cliente.

El servidor (hablando siempre en términos de IIS) diferencia entre contenido estático y dinámico. Comprimir el contenido estático no incurre (o casi no incurre) en ningún gasto de procesador para el servidor, puesto que lo comprimirá la primera vez que algún cliente lo solicite y después lo servirá ya comprimido a siguientes clientes desde una cache temporal. Por otro lado, si hablamos de contenido dinámico, aquí sí se advierte que comprimirlo supondrá un esfuerzo extra para el servidor puesto que no podrá cachear el recurso y siempre que un cliente lo solicite tendrá que comprimirse de nuevo antes de ser enviado (fijarse que un recurso típico dinámico podría ser una página .aspx que para cada solicitud podría devolver resultados distintos, es por ello que no puede ser cacheada).

Para habilitar la compresión en IIS 6.0, podremos realizar parte del trabajo desde la interfaz gráfica (inetmgr) pero para otra parte tendremos que utilizar el comando adsutil.vbs disponible en la carpeta AdminScripts.

Desde la interfaz gráfica podremos habilitar o deshabilitar la compresión estática y dinámica, así como establecer el directorio donde se cacheará los ficheros estáticos y, opcionalmente, poner un límite de tamaño a este directorio. Para ello hay que abrir la opción “Properties” en el nodo “Web Sites”. Todo esto está muy bien explicado en el siguiente enlace: Enabling HTTP Compression (IIS 6.0).

Services

El momento en el que tendremos que utilizar adsutil.vbs es a la hora de configurar que extensiones de ficheros son considerados estáticos y cuales dinámicos. Customizing the File Types IIS Compresses (IIS 6.0). Aunque en el anterior enlace está todo muy bien explicado, personalmente he encontrado problemas con la sintaxis propuesta para agregar las extensiones. La clave está en que el separador de extensiones (diga lo diga el anterior enlace) es CR/LF y no un blanco como dice la MSDN. A continuación te dejo un par de enlaces que aportan mucha luz a posibles problemas que podrían surgir al habilitar la compresión:

Troubleshooting HTTP Compression in IIS 6.0

HTTP Compression and IIS 6.0

Por otro lado, si eres un valiente (y quizás también un temerario), podrías editar directamente la metabase de IIS 6.0 para realizar los cambios oportunos. La metabase está en C:\WINDOWS\system32\inetsrv\MetaBase.xml. Por ejemplo la sección dedicada a la compresión podría ser como la siguiente:

<IIsCompressionScheme    Location ="/LM/W3SVC/Filters/Compression/deflate"
        HcCompressionDll="%windir%\system32\inetsrv\gzip.dll"
        HcCreateFlags="0"
        HcDoDynamicCompression="TRUE"
        HcDoOnDemandCompression="TRUE"
        HcDoStaticCompression="TRUE"
        HcDynamicCompressionLevel="0"
        HcFileExtensions="png
            jpg
            htm
            html
            css
            js
            txt"
        HcOnDemandCompLevel="10"
        HcPriority="1"
        HcScriptFileExtensions="asp
            dll
            exe
            aspx
            png
            jpg
            js"
    >
</IIsCompressionScheme>
<IIsCompressionScheme    Location ="/LM/W3SVC/Filters/Compression/gzip"
        HcCompressionDll="%windir%\system32\inetsrv\gzip.dll"
        HcCreateFlags="1"
        HcDoDynamicCompression="TRUE"
        HcDoOnDemandCompression="TRUE"
        HcDoStaticCompression="TRUE"
        HcDynamicCompressionLevel="0"
        HcFileExtensions="png
            jpg
            htm
            html
            css
            js
            txt"
        HcOnDemandCompLevel="10"
        HcPriority="1"
        HcScriptFileExtensions="asp
            dll
            exe
            aspx
            png
            jpg
            js"
    >
</IIsCompressionScheme>



Fíjate que las extensiones están separadas por un retorno de carro y que se configuran gzip y deflate de forma separada. Además puedes observar que algunas extensiones como jpg y png están incluidas tanto como contenido estático como dinámico. Esto es porque podría pasar que ASP.NET procesara este tipo de ficheros en algún momento y entonces pasarían a ser considerados dinámicos en vez de estáticos.


Si damos el salto a IIS 7.x o superior, lo cierto es que todo resultará ser más sencillo y, sobre todo más configurable. La principal diferencia es que en IIS 6.0 hablábamos de extensiones de ficheros y en IIS 7.x o superior, hablamos de tipos mime. Además, ahora podremos configurar la compresión directamente desde el fichero web.config de nuestra aplicación, desde la sección system.WebServer:


<urlCompression doDynamicCompression="true" doStaticCompression="true" />


Si queremos ampliar información sobre este elemento o sobre que tipos mime serán comprimidos, podemos navegar a los siguientes enlaces:


URL Compression


HTTP Compression


Lo cierto es que a medida que dispongamos de nuevos tipos mime, tendremos que ir actualizando la lista para tenerlos en cuenta, un ejemplo en Enabling dynamic compression (gzip, deflate) for WCF Data Feeds, OData and other custom services in IIS7


Un saludo!

lunes, 19 de noviembre de 2012

Una y no más, Santo Crystal Reports

Tristemente, en mi empresa tenemos alguna aplicación que utiliza Crystal Reports. Más tristemente, la versión de Crystal Reports es 2008. Digo esto último porque quizás futuras versiones faciliten más la vida al desarrollador, porque desde luego la versión 2008 no es nada agradecida.

A modo de diario y previendo futuras publicaciones de esta misma aplicación, dejo a continuación los pasos que hemos tenido que llevar a cabo para hacer que los informes de Crystal funcionen en ASP.NET.

El sistema operativo de despliegue es un Windows Web Server 2008 R2. La versión del framework de la aplicación de ASP.NET es 3.5… y lo dicho, la versión de Crystal es 2008.

Lo primero es descargar el runtime de CR. Podría parecer sencillo, pero no lo es. Los señores de SAP han decidido que sea todo un galimatías encontrar algo en su página, así que si no tiene el enlace directo podrías vértelas canutas para descargar el runtime, pero bueno…

La página de descarga de diversas utilidades (entre ellas el runtime) es https://websmp230.sap-ag.de/sap%28bD1lcyZjPTAwMQ==%29/bc/bsp/spn/bobj_download/main.htm Aquí tienes que localizar “Crystal Reports 2008 Service Pack 4 – Redist Install”. https://smpdl.sap-ag.de/~sapidp/012002523100008782532011E/cr2008sp4_redist.zip Me hubiera gustado bajarme el SP5 que se anuncia en https://wiki.sdn.sap.com/wiki/pages/viewpage.action?pageId=56787567 pero me ha sido imposible registrarme y la descarga pide nombre de usuario y contraseña.

Una vez descargado, se instala y listo. Crea una aplicación web llamada “crystalreportviewers12” en el sitio web por defecto (tampoco pregunta donde quieres crearla no vaya a ser que sea de utilidad, que aquí estamos para complicar los cosas… habrá pensado quién hizo el paquete de instalación). Como nuestra aplicación no está en el sitio web por defecto (está en otro sitio web), pues toca crear un directorio virtual en nuestro sitio web de la aplicación, apuntando a C:\Program Files (x86)\Business Objects\Common\4.0\crystalreportviewers12 con el nombre “crystalreportviewers12”. Además, también he tenido que editar el fichero web.config de la anterior ruta y quitar la siguiente sección

<system.web>

<!-- request length is 20MB -->

<httpRuntime maxRequestLength="20000"/>

<sessionState timeout="20" />

</system.web>

Como parte de la instalación también ha agregado al sitio web por defecto, una carpeta aspnet_client. No preguntes y cópiala también en la raíz de tu sitio web.

Otra cosa un tanto hiriente es que tendrás que compilar tu aplicación en x86. En el siguiente foro lo explican bien http://scn.sap.com/thread/1519581, pero básicamente “Only the .NET 2005 (CR 10.2) and .NET 2008 (CR 10.5) bundles are 64 bit. You must compile the app as 32 bit and use the 32 bit CR runtime.”

Además, también tendrás que habilitar en el grupo de aplicaciones en que esté asignada tu aplicación “Habilitar aplicaciones de 32 bits”.

clip_image001

Aunque en esta publicación no me ha pasado, recuerdo también haber encontrado problemas con la típica imagen de logo del cliente en un informe. En su día me ayudaron los siguientes enlaces (problemas con permisos, problemas con CrystalImageHandler.aspx, etc.) http://www.sdn.sap.com/irj/boc/go/portal/prtroot/docs/library/uuid/a0437ea8-97d2-2b10-2795-c202a76a5e80?QuickLink=index&overridelayout=true

También es posible que tengas que mapear CrystalImageHandler.aspx en el IIS. Visita este enlace para más información http://crmtogether.com/mwCrystal/index.php?title=Troubleshoot

La moraleja de todo esto es que “una no más, santo tomás”. No volveré a usar Crystal Reports para ninguna tarea de reporting. Probaré con otros soluciones de terceros o incluso me planteo hacerme los informes a mano. De hecho, creo que los informes, cada vez más, son un recurso en desuso que solo sirven a un pocos y durante un tiempo limitado (después los requerimientos cambian y nos quedamos con una ristra de informes que no valen para nada).

Un saludo!

martes, 19 de junio de 2012

Reciclaje de grupos de aplicaciones en IIS

Lo cierto es que, aunque conocía la característica de reciclaje de un grupo de aplicaciones de IIS, hasta hoy no había tenido la necesidad de meterme con ella en serio.

Normalmente, cuando subo una aplicación a IIS los únicos parámetros que configuro del grupo de aplicaciones es seleccionar la versión del .NET Framework correcta. En la mayoría de los casos no hay mucho más que hacer al respecto pero, como te mostraré a continuación, en según qué escenarios podría no ser suficiente.

Imagina una aplicación que realiza ciertas tareas pesadas o críticas (lo dejo a tu elección) al arranque de la aplicación (por ejemplo en el evento Application_OnStart del fichero global.asax).

Con este escenario ¿Sabes con certeza en que momentos será ejecutado tu código de inicialización? Está claro que se ejecutará al levantar el grupo de aplicaciones asociado a tu aplicación pero ¿Cuándo y porqué se vuelve a levantar tu grupo de aplicaciones?

Las respuestas más obvias es que un grupo de aplicaciones se iniciará en los siguientes casos:

  • Reinicio del servicio W3C
    • Bien sea de forma manual, bien porque se ha reiniciado el servidor, etc.
  • Reinicio o reciclaje manual del grupo de aplicaciones.
    • Acto totalmente voluntario y, por ende, controlable.
  • Cambios en la configuración de la aplicación (web.config, global.asax, etc.).
    • De nuevo un acto voluntario y programable.

A partir de aquí es cuando me he encontrado que, muy diversos factores también podrían forzar el reciclaje del grupo de aplicaciones y, por consiguiente, que mi código pesado de inicialización volviera a ejecutarse cuando yo no lo tenía previsto.

Si accedemos a la “configuración avanzada” de un grupo de aplicaciones desde la consola de administración de IIS, las secciones que podrían interesarnos son: Modelo de proceso y Reciclaje.

clip_image001[4]

Sin meternos en configurar límites de memoria o CPU, que al ser alcanzados provocarían el reciclaje de la aplicación, hay otros valores que “de serie” pueden hacer que nuestro grupo de aplicaciones se recicle sin nosotros haberlo previsto. Principalmente estoy hablando de “Tiempo de inactividad” e “Intervalo de tiempo regular”.

Tiempo de inactividad” ha sido el mayor de mis problemas durante algún tiempo y, de hecho, es el valor que ha originado la redacción de este post. Este valor indica un cantidad de minutos en las que, sino hay actividad en el servidor, el grupo de aplicaciones será reiniciado.

En IIS 7.5 tiene por defecto el valor 5, lo que significa que si en 5 minutos nadie hace una petición a tu aplicación… el grupo de aplicaciones se reciclará.

Por otro lado, “Intervalo de tiempo regular” es un valor, también expresado en minutos, después de los cuales se reciclará el grupo de aplicaciones, con independencia de si están o activas las aplicaciones del grupo.

Por defecto son 1740 minutos (29 horas).

En esta situación resulta que tengo una aplicación que se recicla después de 5 minutos de inactividad y además cada 29 horas… luego se recicla cuando quiere y no tengo nada controlado el tema!

Para solucionar esto he optado por utilizar el valor “Horas específicas”, que permite especificar una o más horas en las cuales será reciclado el grupo de aplicaciones. En mi caso he pasado de un “reciclado aleatorio” a un “reciclado determinista”. Además, al especificar “Horas específicas” ya no se tiene en cuenta el valor de “Intervalo de tiempo regular”.

Por otro lado (y como no quiero ser un killer server), tampoco voy a poner 0 en “Tiempo de inactividad”… pero tampoco voy a dejar 5 (es más que probable que un usuario esté en mi página sin generar tráfico durante 5 minutos)… así que pondré un valor más alto (por ejemplo 20 para coincidir con el Session.Timeout).

Llegados a este punto creo tener algo más controlado cuando se reciclará mi grupo de aplicaciones, pero ¿Cómo puedo confirmar que mi nueva política de reciclaje se está llevando a cabo? Pues registrando los reciclados en el visor de eventos de Windows.

clip_image002[4]

Ahora cada vez que se recicle el grupo de aplicaciones podré consultar el visor de eventos y ver algo parecido a esto:

clip_image004[4]

clip_image006[4]

Está claro que la administración de un servidor IIS probablemente no sea tarea de un programador pero, al final, siempre acaba siendo tu tarea controlar el entorno donde se ejecuta tu aplicación y supervisar el rendimiento de la misma.

Aunque no es el propósito de este post, parece ser (no lo he comprobado) que la sección “Modelo de proceso” se puede especificar a través del fichero de configuración de nuestra aplicación (web.config) http://technet.microsoft.com/es-es/library/cc779563(v=ws.10).aspx. Sin embargo, la sección “Reciclaje” parece que no http://stackoverflow.com/questions/9697119/disable-iis7-pool-recycling-from-asp-net-config 

Por último, si quieres una referencia exacta de estas 2 secciones, puedes visitar los siguientes enlaces:

Process Model Settings for an Application Pool <processModel>

http://www.iis.net/ConfigReference/system.applicationHost/applicationPools/add/processModel

Recycling Settings for an Application Pool <recycling>

http://www.iis.net/ConfigReference/system.applicationHost/applicationPools/add/recycling

Un saludo!

miércoles, 30 de noviembre de 2011

De vuelta a lo básico, codificar html, javascript y url.

Aunque puede resultar algo obvio a muchos, hoy quiero hablar sobre algo que de vez en cuando me ha dado más de un problema. Estoy hablando de estos maravillosos códigos de producto (o de cliente o de lo que sea) que puede incluir caracteres no habituales como la comilla simple, comilla doble, etc.

Imaginemos que tenemos un producto con un identificador que incluye tanto una comilla simple como una comilla doble, por ejemplo, a partir de ahora nuestro código maldito será “sergio’s” (incluyendo las 2 comillas dobles y la comilla simple).

Aunque lo iremos viendo poco a poco, te adelanto que en este post intentaré resolver como trabajar con este código en HTML, Javascript y como QueryString en una Url.

Si no nos metemos en ningún jardín y utilizamos ASP.NET para renderizar nuestro código HTML, lo cierto es que no tendremos ningún problema. Veamos un ejemplo sencillo donde se vuelca nuestro  código en una caja de texto.

El código de servidor ASP.NET es el siguiente:

txtIdProducto.Text = """sergio's"""


Y el HTML generado es:

<input name="txtIdProducto" type="text" value="&quot;sergio&#39;s&quot;" id="txtIdProducto" />

Si nos centramos en el atributo value, podemos ver como ciertos caracteres han sido sustituidos por nombres de entidad HTML:

·         La comilla doble pasó a ser &quot;

·         La comilla simple pasó a ser &#39;

Esto ha sucedido porque cuando se ha renderizado nuestra caja de texto se ha llamado automáticamente al método Server.HtmlEncode para su atributo value.

Es decir, Server.HtmlEncode("""sergio's""") devuelve &quot;sergio&#39;s&quot;

Y por el contrario, Server.HtmlDecode("&quot;sergio&#39;s&quot;") devuelve "sergio's"

La referencia de las entidades HTML (ya sea con nombre o numérico) las puedes encontrar aquí, aunque una simple búsqueda en google te arrojará decenas de resultados.

Para que te hagas una idea y veas que cualquier carácter puede ser representado con una entidad HTML, podríamos escribir lo siguiente (que es totalmente ilegible pero es igualmente válido):

<input type="text" value="&quot;&#115;&#101;&#114;&#103;&#105;&#111;&#39;&#115;&quot;" />

En este ejemplo, además de las comillas dobles y la comilla simple, hemos sustituido el resto de caracteres por su entidad numérico HTML:

·         . &quot;

·         s. &#115;

·         e. &#101;

·         r. &#114;

·         g. &#103;

·         i. &#105;

·         o. &#111;

·         . &#39;

·         s. &#115;

·         . &quot;

Lógicamente, asumo que no te vas a volver loco y a partir de ahora te vas a poner a escribir todo tu código HTML a través de entidades, pero en cualquier caso, podrías.

La conclusión cuando estamos trabajando con código HTML, es que cualquier valor de cualquier atributo de cualquier elemento (¿demasiado cualquier quizás?) tiene que ir acompañado de Server.HtmlEncode. Es decir, imagina que estás construyendo una cadena en servidor con código HTML:

' Mal

' Devuelve <input type='text' value='"sergio's"' />

Dim html As String = String.Format("<input type='text' value='{0}' />", """sergio's""")

 

' Bien

' Devuelve <input type='text' value='&quot;sergio&#39;s&quot;' />

html = String.Format("<input type='text' value='{0}' />", Server.HtmlEncode("""sergio's"""))

 

Aunque sobra decirlo, si recuperamos el valor de nuestro input desde javascript, siempre obtendremos el valor original:

alert(document.getElementById("txtIdProducto").value);

 

clip_image001[4]

Si nos centramos ahora en Javascript, es necesario complicar un poco más nuestro código de producto para que los ejemplos sean más completos. De esta forma, ahora nuestro código de producto pasará a ser “ser\gio’s” (hemos incluido una barra inversa).

Ahora imaginemos que desde ASP.NET queremos generar un saludo con nuestro código de producto al cargar la página. Un posible código sería el siguiente:

Dim idProducto As String = """ser\gio's"""

Dim js As String = String.Format("alert('{0}');", idProducto)

ClientScript.RegisterClientScriptBlock(Me.GetType(), "Page_Load", js)

 

Y la salida que obtenemos es… que no hay alert! Pero bueno, ¿Dónde está mi alert?

Si vemos el código HTML de la página, tiene el siguiente código:

<script type="text/javascript">

//<![CDATA[

alert('"ser\gio's"');//]]>

</script>

Está claro que el mensaje de nuestro alert no está bien construido, así que toca lidiar con ello.

Para esta situación, yo suele escribir mi propia función donde reemplazo ciertos caracteres de la cadena entrante por sus caracteres de escape Javascript:

    Public Shared Function JavascriptEncode(ByVal s As String) As String

        s = s.Replace("\", "\\")

        s = s.Replace("'", "\'")

        s = s.Replace("""", "\""")

        Return s

    End Function

 

·         \ se reemplaza por \\

·         ‘ se reemplaza por \’

·         “ se reemplaza por \”

Como es de esperar, esta función seguro que no contempla ni la mitad de los casos que te pueden ocurrir, pero al menos es un principio. De hecho, le da igual si el delimitador de cadena es la comilla simple o la comilla doble (yo personalmente utilizo indistintamente ambos delimitadores en mi código Javascript cuando trabajo con cadenas).

Si ahora llamamos a la función desde el código de servidor ASP.NET:

Dim idProducto As String = """ser\gio's"""

Dim js As String = String.Format("alert('{0}');", JavascriptEncode(idProducto))

ClientScript.RegisterClientScriptBlock(Me.GetType(), "Page_Load", js)


El HTML resultante ya sí es correcto:

<script type="text/javascript">

//<![CDATA[

alert('\"ser\\gio\'s\"');//]]>

</script>

clip_image002[4]

Por último, hablaremos sobre este tipo de situaciones cuando trabajamos con nuestro código de producto en una Url como parte de la QueryString.

De nuevo, vamos a complicar nuestro código de producto (esta es la última vez, lo juro). Ahora será “&ser\gio’s” (le hemos incluido un ampersand)

Aquí tenemos que contemplar dos escenarios (al menos son con los que yo trabajo habitualmente):

·         Generar la url desde Javascript

·         Generar la url desde ASP.NET, es decir, desde el servidor

Si creamos la url desde Javascript:

var id = document.getElementById("txtIdProducto").value;

var url = "VerProducto.aspx?IdProducto=" + id;

alert(url);

 

clip_image003[4]

 

Está claro que hay no funcionará porque, para empezar, el carácter & es el delimitador de campos en una QueryString y entonces ¿Qué tengo en la url?

clip_image004[4]

Pues resulta que ASP.NET dice tener 2 campos. El segundo campo no tiene nombre y el primer campo no tiene valor… vamos, que la url está muy mal construida.

Para solucionarlo, basta con utilizar la función nativa de Javascript, encodeURIComponent.

var id = document.getElementById("txtIdProducto").value;

var url = "VerProducto.aspx?IdProducto=" + encodeURIComponent(id);

 

clip_image005[4]

clip_image006[4]

Ahora todo funciona correctamente, porque se han sustituido en la url los caracteres reservados por sus equivalentes caracteres de escape.

·         “ por %22

·         & por %26

Ya por último, si nos centramos en la generación de la url desde código de servidor ASP.NET, habrá que utilizar la función Server.UrlEncode.

Dim url As String = "VerProducto.aspx?IdProducto={0}"

url = String.Format(url, Server.UrlEncode("""&sergio's"""))


Que devolverá la cadena:

VerProducto.aspx?IdProducto=%22%26sergio%27s%22

Lo cierto es que este post, da para mucho que escribir, pero al menos espero haber sentado ciertas bases para que los códigos indeseables de productos no te quiten el sueño.

Un saludo!