lunes, 15 de noviembre de 2010

REST(full) web services

Colaboro con islamanzana y este es un artículo para la web. Ya que lo he escrito lo cuelgo también por aquí.

Los web services son servicios que se ofrecen de forma independiente a la tecnología en la que se han desarrollado. Son realmente una forma de intercambiar información, de pedir algo sin tener en cuenta el lenguaje de programación, para dar un servicio de forma independiente. Solo hace falta saber cómo invocar al servicio.

Dentro de las novedades de los web services, he descubierto hace poco los servicios REST(ful). Se basan en HTTP para intercambiar información y no necesita encapsulado extra para ello. Es más ligero, menos engorroso, pero al mismo tiempo tiene más limitaciones. Aquí podeis ver algo de información al respecto. En resumen, en vez de hacer peticiones encapsuladas en un sobre SOAP para solicitar un servicio para lo que es necesario el wsdl, en los REST las peticiones se hacen mediante el protocolo http, con GET, POST... sin necesidad de encapsularlo.

Recientemente he tenido que investigar si utilizar AXIS2 o Jersey para implementar un servicio web del tipo RESTFUL. Para ello he hecho lo siguiente:

Axis2:
Normalmente se utiliza en forma de aplicación independiente, un war que se despliega completamente dentro de nuestro servidor de aplicaciones, pero para agilizarlo, lo que he hecho yo ha sido descomprimir el war y copiarme solo las librerias necesarias que son las siguiente para un servicio REST.
                    |       axiom-api-1.2.9.jar
                    |       axiom-impl-1.2.9.jar
                    |       axis2-adb-1.5.2.jar
                    |       axis2-clustering-1.5.2.jar
                    |       axis2-jaxws-1.5.2.jar
                    |       axis2-kernel-1.5.2.jar
                    |       axis2-transport-http-1.5.2.jar
                    |       geronimo-stax-api_1.0_spec-1.0.1.jar
                    |       geronimo-ws-metadata_2.0_spec-1.1.2.jar
                    |       httpcore-4.0.jar
                    |       neethi-2.0.4.jar
                    |       woden-api-1.0M8.jar
                    |       wstx-asl-3.2.9.jar
                    |       XmlSchema-1.4.3.jar
Las librerías como no, van en WEB-INF/lib
Después, he modificado el web.xml de mi aplicación para que me coja el servlet de Axis que me va a redirigir a los servicios web que implemente con el siguiente código:




En mi caso, el parámetro load-on-startup tiene su sentido, y también tiene su sentido que tenga el valor tres, pero es opcional.
Una vez descrito el servlet de Axis2, me he generado un servicio mediante una clase java que por ejemplo puede tener esta pinta:
public class SimpleService {

    public String hola(String value) {
         return "hola" + value;
    }
}

También tengo el archivo axis.xml con la configuración por defecto pero añadiendo que puede servir servicios rest que por defecto creo vienen desactivados. Se puede ver la configuración en la web de axis2. Aquí mi linea para configurar los servicios REST en axis2 (primero el parametro de desactivar REST comentado y a continuación el parametro de activar REST).

y por último el descriptor del servicio, el service.xml donde le digo realmente donde y como se ejecutan los servicios:
Bueno, en este punto, me falta por describir donde va cada cosa:web.xml donde siempre, dentro del directorio WEB-INF
axis2.xml donde lo hayas configurado dentro del web.xml mediante una ruta absoluta o bien por defecto en WEB-INF/conf
services.xml dentro de WEB-INF/services
clase java con el servicio, en el paquete que se le haya descrito.

Y ya está! No hace falta wsdl porque en este caso, aunque axis2 necesita del descriptor, lo genera automáticamente. Si se quiere definir el método de entrada como POST o GET hay que hacerlo en el wsdl, aunque por defecto te lo genera como POST (y al mismo tiempo, si no hay get, también funciona como get)
Llamada GET de ejemplo en un navegador:
http://localhost:9082/MyWebApp/services/SimpleService/hola?value=Pepe
En la pantalla aparecerá Hola Pepe
wsdl, por curiosear
http://localhost:9082/MyWebApp/services/SimpleService/hola?wsdl

Ejemplo de un cliente en java:
public static void main(String[] args) {

    Client client = new Client();
    WebResource webResource = client.resource("http://localhost:9082/MyWebApp");
    String response = webResource.path("services").path("SimpleService").path("hola")
        .queryParam("value", "Pepe").get(String.class);
    System.out.println("Response: " + response);
}

Nota: MyWebApp es el nombre de la aplicación web donde he metido el Axis2 a funcionar.

En resumen: necesitmoas unos 6 megas en librerías para que funcione lo más light posible.
La información va empaquetada por debajo como SOAP aunque nosotros utilicemos una llamada web.
No hace distinción entre GET y POST, me explico, si pones un servicio descrito como POST pero no hay GET, y haces una llamada mediante http como un GET, al no encontrar el get, va a ir al POST
Los parámetros de entrada tienen que coincidir en nombre con los definidos en las funciones del web service.

Por otro lado tenemos JERSEY:
Está preparado únicamente para web-services del tipo REST, no es posible utilizar encapsulamiento SOAP, es más limitado en este aspecto pero como yo quiero utilizar REST, me vale me sobra.
Primera diferencia, todas las librerías necesarias pesan un único mega.
Segunda diferencia: cuidado con las versiones, a partir de una de ellas se han compilado utilizando java 6 y puede dar problemas. Yo he utilizado la 1.0.2 porque me vale y me sobra y necesito librerías compiladas en java5.
Una vez tengo las librerías, configuro el servlet de jersey para que me coja luego los servicios web mediante la siguiente configuración


Veis que es todo igual, solo cambia la clase del servlet.
Cuando tenemos las librerías y el servlet, solo nos queda la clase java que va a ser el servicio web y aquí es donde va toda la configuración ya que no necesita ni service.xml, archivo de configuración extra ni wsdl (ni siquiera lo genera automáticamente, no es necesario)
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;

@Path("/hola")
public class SimpleService {

@GET
@Produces("text/plain")
public String hola(@QueryParam("nombre") String palabra) {
return "Hola " + palabra;
}

}

Cosas importantes
1. @Path("/hola") – Es el path que hay que poner para invocar al servicio web. En nuestro caso, dada la configuración de Jersey sería algo así como
http://servidor:puerto/services/hola?nombre=Pepe
2. @GET – Identifica al método que será accesible mediante una petición http GET
3. @Produces("text/plain") – devolverá texto plano
4. @QueryParam("nombre") – se mapea mediante este parámetro el valor de los parametros de entrada al metodo del servicio web. En nuestro ejemplo se cogerá de la uri el paámetro con el nombre 'nombre', y su valor (es decir, 'Pepe') se le pasará como parámetro de entrada al método hola en el parámetro palabra. Es decir, los parámetros de la uri no tienen que llamarse igual que los parámetros de los métodos del web service, ya que se configura el mapeo en el mismo método:
Cosas importantes: cada clase de un servicio web, tiene definido solo un path, una ruta para llegar a el, que es a nivel de clase, no a nivel de método como podía pasar con axis. Por tanto, en cada clase solo se puede definir un método de entrada de tipo GET y un método de entrada de tipo POST (o un método que sea para ambos) pero nunca se podrá dentro de la misma clase configurar dos métodos de entrada diferentes para el mismo tipo de petición, ya sea post o get.
Se puede apreciar lo ligero que es, es mucho más sencillo.
Además no genera nunca un wsdl, no le es necesario.
No se encapsula la petición bajo SOAP, va directamente mediante http.
Url para solicitar el servicio:
http://localhost:9082/MyWebApp/services/hola?nombre=Pepe

Ejemplo de cliente para este web service de Jersey
public static void main(String[] args) {
Client client = new Client();
WebResource webResource = client.resource("http://localhost:9082/MyWebApp");
    String response =  webResource.path("services"). path("hola").queryParam("nombre", "Pepe").get(String.class);
            System.out.println("Response: " + response);
}

Nota: MyWebApp es el nombre de la aplicación web donde he puesto Jersey a funcionar.
Diferencias muy importantes:
- Axis2 necestia una configuración más completa y sirve para mas cosas que aplicacioes RESTFUL mientras que Jersey solo sirve para RESTFUL pero no hace falta casi configuración.
- Jersey pesa 1 mega, y Axis, capando las librerias para que solo utilice las necesarias para restful, 6megas
- Jersey no encapsula las peticiones HTTP en un envelope SOAP mientras que Axis si, en este aspecto, axis funciona en restful simulando que lo hace, pero por debajo funciona como SOAP aunque sea transparente para el usuario
- Axis necesita de wsdl aunque bien es verdad que lo autogenera si no lo tiene. Jersey no necestia wsdl
- Jersey no necesita service.xml, la configuración va toda en la clase java mientras que Axis si que necesita el service.xml
- Jersey solo permite definir un web service por clase, configurándolo directamente en la clase java. Solo se puede poner un punto de acceso para cada tipo de petición http. Axis permite configurar todo eso al gusto en los archivos de configuración pero no filtra peticiones por el tipo de petición http que sea. Es decir, una petición get, si no está del todo definido y no hay petición get configurada, puede entrar por el método post. Tal vez con una buena configuración y un wsdl escrito a mano correctamente no pase, pero podría pasar si no se hace bien.
- Jersey se come el nombre del metodo. En este aspecto no le da importancia y solo utiliza la configuración de la clase java mediante etiquetas predefinidas de forma que si un servicio web se define con el path "/hola", la limitación de acceso a los metodos que tiene dentro solo se podrá hacer por tipo de petición http, bien GET bien POST.
- Los nombres de los parámetros de los métodos del web service deben coincidir con los parámetros de la uri para axis (cosa normal para web services), aunque no es necesario para jersey ya que se pueden configurar. También se puede describir de donde se quiere recoger los datos en jersey, bien de la uri, del contenido de la petición post...
- Axis no me ha dado problemas con java 5, mientras que jersey tiene cada una de sus versiones compiloada solo en una versión de java siendo las últimas java6. Hay que tener cuidado con eso.
- Jersey tiene algún bug en alguna versión anterior, y como cada versión está compilada solo en una versión de java, hay que tener mucho cuidado con cual se utiliza.

Mi opinión, si te es suficiente Jersey (si solo necesitas web services REST), utiliza Jersey, sobre todo ahora que viene el invierno (tachan! chiste malo), es mas fácil de configurar, menos pesado, mas fácil para empezar con el... Axis es mucho más potente pero también necesita mucha más configuración y control sobre lo qué estás haciendo.

5 comentarios:

Joste dijo...

Perdonadme que haya puesto el xml como imagen pero si intento escribirlo como texto, me lo coge como html y se me desconfigura completamente la entrada

cybernekanekane dijo...

Ah, pues bueno, te perdonamos, te perdonamos. Creo que en todo lo que has escrito la única palabra que entiendo es Jersey y me parece que no te estás refiriendo a una prenda de vestir...Que no me he enterado de nada, vaya, que para mí ha sido chino, pero del mandarín mandarín.
Un besote.

Anónimo dijo...

hola

segui tu ejemplo pero al correr el servidor me aparece este el mensaje

Estado HTTP 404 - /MyWebApp/services/hola


descripción El recurso requerido (/MyWebApp/services/hola) no está disponible


por favor si pudieran ayudarme,gracias

Joste dijo...

Hola amigo anónimo.

Este problema tiene toda la pinta de ser culpa de la configuración del servlet.

Para ayudarte necesito ver tu archivo web.xml, comprueba que el nombre de la aplicación web esté igual en el archivo de configuración, que la ruta completa con el servidor y el puerto sea el correcto y que el servidor haya arrancado sin problemas.

Saludos

cybernekanekane dijo...

¿Para cuándo una entradita nueva? Queremos saber qué es de tu vida.
Un saludo.

Nekane