Geek & Roll - Blog Archive » Haciendo peticiones asíncronas entre distintos dominios (cross-domain) utilizando GWT

Haciendo peticiones asíncronas entre distintos dominios (cross-domain) utilizando GWT

Cesar February 27th, 2007 javascript, programacion 3 comentarios

(o como hacer que el modo hosted se lleve bien con php)

Nota
Este tutorial es centrado en Linux, pero no veo porque no pueda ser aplicado a GWT bajo Windows o OSX. Después de todo, ¡es Java! También es importante notar que esta solución es para hacer que el modo hosted trabaje con una instalación local de php. Si lo que quieres es utilizar APIs populares como Google Maps o Yahoo! Search entonces esto no es para ti. De hecho se puede hacer que funcione pero solamente serviría para modo hosted ya que cuando la aplicación es emplazada, se pierde el contenedor tomcat (donde sucede la magia).

Introducción
GWT incluye en sus propias clases la clase HTTPRequest. Esta clase se utiliza para hacer peticiones HTTP asíncronas al servidor de origen. La clase HTTPRequest tiene 4 métodos estáticos que manejan tanto peticiones GET y POST y todos los 4 métodos regresan false si la petición falla.

El escenario típico del uso de HTTPRequest es comunicarse con una API remota (como Google Maps o Yahoo! Search) o con tus propios scripts del lado del servidor (php, asp, jsp, etc). En cualquier caso, el usar el modo hosted de GWT para hacer llamadas asíncronas NO será algo facil a menos que tu petición sea dirigida al mismo dominio (otro puerto cuenta como un dominio distinto y por lo tanto localhost y localhost:8888 se encuentran en dominios distintos). Para obscurecer el horizonte aún más, el modo hosted de GWT corre un servidor tomcat embebido y por lo tanto si tienes algunos scripts php locales y quieres llamarlos utilizando HTTPRequest va a fallar, ya que el servidor de origen (el servidor que tiene el script que hace la petición) es tomcat corriendo en un puerto distinto de tu servidor php (típicamente apache en el puerto 80).

Yo creo que la solución presentada aquí es la mejor opción de entre otras alternativas, dado que solo modifica el servidor tomcat embebido en GWT y no el servidor que mantiene los scripts o el servidor de emplazamiento. Además, también es posible hacerlo sin la necesidad de modificar la URL pedida. Pero suficiente teoría, pasemos al código.

La solución
El truco aquí es utilizar un proxy, es decir algo entre nuestra petición y el servidor destino. ¿Como funciona? o mejor, ¿Porque funciona? Todo el problema se debe a la restricción impuesta por el navegador para hacer peticiones cross-domain, una restricción que no existe en el lado del servidor (obviamente). Entonces, lo que haremos es hacer que tomcat intercepte nuestra petición y la redirecte a un proxy corriendo dentro de tomcat. El proxy (en el lado del servidor) hará la petición real al servidor remoto, y nos dará la salida. Todos felices ya que HTTPRequest hace una petición en el mismo dominio, pero aún obtenemos la salida del servidor de otro dominio (gracias a nuestro proxy en el lado del servidor).

El proxy es realmente un filtro de tomcat. Un filtro escrito en Java debe implementar la interfaz javax.servlet.Filter como veremos en el siguiente código. La interfaz contiene tres métodos:


doFilter(ServletRequest, ServletResponse, FilterChain): the meat and potatoes of the filter happens here. Whenever tomcat matches a URL pattern to a requested URL it triggers this method.

init(FilterConfig): This is executed before doFilter() and it's used to obtain the configuration parameters (more on this later).

destroy(): Called after doFilter() is executed.

El código para el filtro se muestra a continuación, sin embargo, yo no soy el autor de este código. Alguien en grupo GWT de Google Groups lo propuso para resolver este problema con HTTPRequest (bajar el código aquí, o un archivo jar precompilado):


package org.geekandroll.httpproxy;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethodBase;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;

/*
* @author jon
*/
public class PHPProxy implements Filter {
private static final HttpClient httpClient = new HttpClient();
/**
* @see javax.servlet.Filter#destroy()
*/
public void destroy() {
}

/**
* @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
*/
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
forward((HttpServletRequest) request, response);
}

/**
* @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
*/
public void init(FilterConfig arg0) throws ServletException {
}
private synchronized static void forward(HttpServletRequest request, ServletResponse response) throws IOException {
final String url = "http://localhost:80" + request.getRequestURI();
HttpMethodBase method = null;
if (request.getMethod().equalsIgnoreCase("POST")) {
method = new PostMethod(url);
final BufferedReader br = request.getReader();
char c;
boolean atend = false;
StringBuffer sb = new StringBuffer();
String key = null;

for (int i = 0;; i++) {
c = (char) br.read();
atend = ((i + 1) == request.getContentLength());
if (key == null && c == '=') {
key = sb.toString();
sb.setLength(0);
}
else if (key != null && (c == '&' || atend)) {
((PostMethod) method).addParameter(key, sb.toString());
key = null;
sb.setLength(0);
}
else
sb.append(c);

if (atend)
break;
}
} else {
method = new GetMethod(url + "?" + request.getQueryString());
}

httpClient.executeMethod(method);

final InputStream is = method.getResponseBodyAsStream();
int x;

while ((x = is.read()) != -1)
response.getOutputStream().print((char)x);

method.releaseConnection();
}
}

Como pueden ver, este código usa algunos paquetes de jakarta commons. Específicamente necesitarán descargar commons-httpclient y commons-codec. Una vez que se descargan estos paquetes, se debe poner el archivo .jar en cualquier directorio (yo los puse dentro del directorio de la distribución de GWT pero no es requerido) y ahora necesitamos decirle al servidor tomcat embebido donde están estos archivos para que pueda cargarlos. Esto es llamado modificar el classpath.

Para modificar el classpath, se abre el archivo utilizado para lanzar el modo hosted (nombredelproyecto-shell) con un editor de texto. Ahí veran algo como: java -cp “$APPDIR/src:$APPDIR/bin:/opt/gwt/gwt-user.jar:/opt/gwt/gwt-dev-linux.jar”. Solo hay que agregar una coma después del último nombre de paquete y agregar la ruta completa a los paquetes common descargados. En mi computadora mi classpath esta modificado de la siguiente manera (dividido en varias lineas por claridad solamente):

java -cp “$APPDIR/src:$APPDIR/bin:/opt/gwt/gwt-user.jar:
/opt/gwt/gwt-dev-linux.jar:
/opt/gwt/commons-httpclient-3.0.1.jar:
/opt/gwt/commons-codec-1.3.jar”

Ahora hay que compilar el código para el filtro, agregarlo a un archivo .jar y agregarlo al classpath de tomcat (como se explicó anteriormente). O simplemente descargar el jar precompilado y ponerlo en el classpath de tomcat.

Ya casi, ahora es solamente cuestión de configurar tomcat y decirle que patrón URL debe redirigir por medio del filtro. Esto se hace en el archivo web.xml dentro del directoriodelproyecto/tomcat/webapps/ROOT/WEB-INF. Hay que abrir el archivo web.xml y dentro del tag agregar algo como:



all
org.geekandroll.httpproxy.PHPProxy


all
*.php

El argumento filter-class es el nombre completamente calificado de nuestro filtro. Si compilaron el filtro por su cuenta probablemente el nombre del paquete cambie, así que hay que editarlo de manera acorde. Otra cosa importante es url-pattern. Este argumento es lo que tomcat pasará por el filtro. En este caso particular cualquier petición hecha a un URL que termine con .php será redirigida. Ahora hay que hacer la petición y ver todo volar. Yo utilizo el siguiente código para hacer la petición:


if(!HTTPRequest.asyncPost("/jolette/login.php","user="+
userText.getText()+
"&pass="+passText.getText(),new ResponseTextHandler(){
public void onCompletion(String responseText){
Window.alert(responseText);
}
}))Window.alert("Error");

Entonces, lo que está pásando es que como el URL termina con .php, tomcat utiliza el filtro. El filtro tiene la siguiente linea:


final String url = "http://localhost:80" + request.getRequestURI();

Y lo que resulta es que el URL final utilizado es http://localhost:80/jolette/login.php y ahí es donde nuestro script php están con apache.

Conclusión
Para recapitular… las cosas que debes hacer solo una vez:

  • Descargar commons-httpclient y commons-codec
  • Compilar la clase filtro, o descargarla de aquí

Cosas que necesitas hacer cada vez que inicias un proyecto nuevo:

  • Agregar los jars descargados al classpath del proyecto (nombreproyecto-shell)
  • Configurar el filtro en el archivo web.xml de tomcat

¡Y es todo! una explicación muy larga para una solución bastante simple. Pero es mejor entender porque estamos haciendo las cosas y no hacerlas a ciegas.

3 Comentarios

Cesar Olea Punto Com » Making asynchronous cross-domain requests using GWT

February 27th, 2007

[...] para la versión en español pueden visitar el mismo artículo en Geek&Roll. – Spanish mode off [...]

Cesar Olea Punto Com » Peticiones interdominio con GWT

February 27th, 2007

[...] artículo en español lo encuentran en Geek&Roll y en inglés aquí mismo. [...]

Geek & Roll » Archivo » Programando con Google Web Toolkit (Parte #3)

March 2nd, 2007

[...] a partir de este punto en adelante, estoy suponiendo que ya leyeron como hacer llamadas remotas interdominio con GWT. Si no lo han hecho aún, vayan, aquí los [...]

Haz un comentario:

Es necesario que dejes tu nombre y correo electrónico (no se publicarán).

Si dejas un comentario anónimo, con insultos o ajeno al tema, iremos hasta tu casa y le diremos a tu mamá la cantidad de porno que hay en tu computadora. Si, lo sabemos.