Servir recursos estáticos desde un CDN en Apache Tapestry

Escrito por el , actualizado el .
software java programacion tapestry planeta-codigo
Enlace permanente Comentarios

Apache Tapestry

Un Content Delivery Network (CDN) no es más que un servidor, servidores o servicio dedicado a servir el contenido estático o actuar de cache para los clientes. Alguno de los motivos por los que podríamos querer usar un CDN en una aplicación son:

  • Algunos servicios CDN están repartidos geográficamente por el mundo de modo que el contenido sea servido de un lugar más cercano al usuario esto hace que el tiempo que tarda en cargar un página o servirse el contenido sea menor.
  • Descargar la tarea de servir al menos parte del contenido de la aplicación al CDN hará que no nos tengamos que preocupar de tener la capacidad para servirlo. Cuando se cargar una página se hacen varias peticiones al servidor para obtener el contenido como el html, imágenes, estilos, … haciendo que los contenidos estáticos sean servidos por el CDN hará que el servidor tenga menos carga, dependiendo del número de usuarios de la aplicación o los picos de tráfico notaremos una mejoría.
  • La alta fiabilidad de servicio que ofrecen.

Amazon ClodFront es una de las opciones que podemos usar como CDN. En este artículo voy a comentar como tener un CDN para servir el contenido estático en una aplicación que emplee el framework de desarrollo de aplicaciones web en la plataforma Java Apache Tapestry.

Arquitectura no CDN (izquierda) contra arquitectura CDN (derecha)

Para que el contenido estático se sirva del CDN debemos hacer que las URL de las imágenes y hojas de estilo se generen con la URL propia del CDN, al menos, deberemos cambiar el host de esas URL. No hay que hacer mucho más ya que CloudFront creo que se puede configurar para que cuando le lleguen las peticiones del contenido si no las tiene las delegue en la aplicación, una vez que las tiene cacheadas ya no necesita solicitarselas a la aplicación y las sirve él mismo.

Una de las cosas muy interesantes de Tapestry es que podemos modificar prácticamente cualquier comportamiento del mismo, esto es debido a que la mayor parte de sus funcionalidades son ofrecidas mediante servicios que podemos sobrescribir con los que nosotros proporcionemos, el contenedor de dependencias (IoC) de tapestry lo hace muy fácil. Para modificar las URL de los recursos estáticos que son generados en Tapestry deberemos implementar la clase AssetPathConverter. Una implementación podría ser la siguiente:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package es.com.blogspot.elblogdepicodev.plugintapestry.misc;

import java.util.Map;

import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.annotations.Symbol;
import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
import org.apache.tapestry5.services.AssetPathConverter;

import es.com.blogspot.elblogdepicodev.plugintapestry.services.AppModule;

public class CDNAssetPathConverterImpl implements AssetPathConverter {

	private String protocol;
	private String host;
	private String port;
	private String path;

	private Map<String, String> resources = CollectionFactory.newMap();

	public CDNAssetPathConverterImpl(@Inject @Symbol(AppModule.CDN_DOMAIN_PROTOCOL) String protocol,
			@Inject @Symbol(AppModule.CDN_DOMAIN_HOST) String host,
			@Inject @Symbol(AppModule.CDN_DOMAIN_PORT) String port,
			@Inject @Symbol(AppModule.CDN_DOMAIN_PATH) String path) {

		this.protocol = protocol;
		this.host = host;
		this.port = (port == null || port.equals("")) ? "" : ":" + port;
		this.path = (path == null || path.equals("")) ? "" : "/" + path;
	}

	@Override
	public String convertAssetPath(String assetPath) {
		if (resources.containsKey(assetPath)) {
			return resources.get(assetPath);
		}
		String result = String.format("%s://%s%s%s%s", protocol, host, port, path, assetPath);
		resources.put(assetPath, result);
		return result;
	}

	@Override
	public boolean isInvariant() {
		return true;
	}
}
CDNAssetPathConverterImpl.java

También deberemos añadir un poco de configuración al módulo de la aplicación para que se use esta nueva implementación. Esto se hace en el método serviceOverride de la clase AppModule.java, donde también en el método contributeApplicationDefaults configuramos los símbolos que se usarán al generar las URLs entre ellos el dominio del CDN.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package es.com.blogspot.elblogdepicodev.plugintapestry.services;

...

public class AppModule {

	private static final Logger logger = LoggerFactory.getLogger(AppModule.class);

	public static final String CDN_DOMAIN_PROTOCOL = "cdn.protocol";
	public static final String CDN_DOMAIN_HOST = "cdn.host";
	public static final String CDN_DOMAIN_PORT = "cdn.port";
	public static final String CDN_DOMAIN_PATH = "cdn.path";

	...

	public static void contributeServiceOverride(MappedConfiguration<Class, Object> configuration, @Local HibernateSessionSource hibernateSessionSource) {
		configuration.add(HibernateSessionSource.class, hibernateSessionSource);
		// Servicio para usar un CDN lazy, pe. con Amazon CloudFront
		configuration.addInstance(AssetPathConverter.class, CDNAssetPathConverterImpl.class);

		if (isServidorJBoss(ContextListener.SERVLET_CONTEXT)) {
			configuration.add(ClasspathURLConverter.class, new WildFlyClasspathURLConverter());
		}
	}

	public static void contributeApplicationDefaults(MappedConfiguration<String, Object> configuration) {
		...

		configuration.add(CDN_DOMAIN_PROTOCOL, "http");
		configuration.add(CDN_DOMAIN_HOST, "s3-eu-west-1.amazonaws.com");
		configuration.add(CDN_DOMAIN_PORT, null);
		configuration.add(CDN_DOMAIN_PATH, null);
	}

	...
}
AppModule.java

Estas serían las URLs por defecto:

1
2
3
4
5
6
/PlugInTapestry/assets/meta/zbb0257e4/tapestry5/bootstrap/css/bootstrap.css
/PlugInTapestry/assets/ctx/8a53c27b/images/tapestry.png
/PlugInTapestry/assets/meta/z87656c56/tapestry5/require.js
/PlugInTapestry/assets/meta/z58df451c/tapestry5/bootstrap/css/bootstrap.css
/PlugInTapestry/assets/ctx/8a53c27b/images/tapestry.png
/PlugInTapestry/assets/meta/z87656c56/tapestry5/require.js
urls-sin-cdn.txt

Y estas las nuevas nuevas URL haciendo uso de la implementación del AssetPathConverter que como se aprecia incorporan un dominio de Amazon y siendo uno del servicio CloudFront en caso de no tener cacheado el recurso lo pedirá a la aplicación y lo cacheará para posteriores peticiones:

1
2
3
4
5
6
http://s3-eu-west-1.amazonaws.com/PlugInTapestry/assets/meta/zbb0257e4/tapestry5/bootstrap/css/bootstrap.css
http://s3-eu-west-1.amazonaws.com/PlugInTapestry/assets/ctx/8a53c27b/images/tapestry.png
http://s3-eu-west-1.amazonaws.com/PlugInTapestry/assets/meta/z87656c56/tapestry5/require.js
http://s3-eu-west-1.amazonaws.com/PlugInTapestry/assets/meta/z58df451c/tapestry5/bootstrap/css/bootstrap.css
http://s3-eu-west-1.amazonaws.com/PlugInTapestry/assets/ctx/8a53c27b/images/tapestry.png
http://s3-eu-west-1.amazonaws.com/PlugInTapestry/assets/meta/z87656c56/tapestry5/require.js
urls-con-cdn.txt

Así de simple podemos cambiar el comportamiento de Tapestry y en este caso emplear un CDN, esta implementación es sencilla y suficiente pero perfectamente podríamos implementarla con cualquier otra necesidad que tuviésemos. El cambio está localizado en una clase, son poco más que 46 líneas de código pero lo mejor es que es transparente para el código del resto de la aplicación, ¿que más se puede pedir?

Portada libro: PlugIn Tapestry

Libro PlugIn Tapestry

Si te interesa Apache Tapestry descarga gratis el libro de más de 300 páginas que he escrito sobre este framework en el formato que prefieras, PlugIn Tapestry: Desarrollo de aplicaciones y páginas web con Apache Tapestry, y el código de ejemplo asociado. En el libro comento detalladamente muchos aspectos que son necesarios en una aplicación web como persistencia, pruebas unitarias y de integración, inicio rápido, seguridad, formularios, internacionalización (i18n) y localización (l10n), AJAX, ... y como abordarlos usando Apache Tapestry.



Comparte el artículo: