martes, marzo 24, 2009

Como consumuir Webservice REST JSON en Javascript

este post es continuación de este: http://marianoguerra.blogspot.com/2009/03/serializardesserializar-objetos-java.html

ahora vamos a hacer un modulo en javascript para usar la API REST JSON desde una pagina web (lo que normalmente se llama Web 2.0 o AJAX solo que la X de XML la cambiamos por la J de JSON asi que queda AJAJ).

el primer problema que debemos superar es la regla de "same origin" de javascript por la cual solo podemos hacer requests asincronicos con HttpRequest solo a la misma URL de la cual se descargo el script, y como nuestra aplicación se va a distribuir en un jar, nuestro js y html tiene que estar en ese jar y lo tiene que servir la misma aplicación, lo que significa que si le pido 0.0.0.0:9999/pagina.html la aplicación tiene que buscar en el jar el archivo en algun lugar y devolverlo. Si el archivo viene de otro lado nuestro request va a fallar.

Para solucionar esto la unica forma que encontre fue escribir un handler a mano para devolver archivos estaticos.

/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/

package addressbook;

import java.io.IOException;
import java.net.MalformedURLException;
import org.mortbay.jetty.handler.ResourceHandler;
import org.mortbay.resource.Resource;

/**
*
* @author mariano
*/
public class ContentHandler extends ResourceHandler{
@Override
public Resource getResource(String path) throws MalformedURLException
{
System.out.println(path);
try
{
System.out.println(getResourceBase().replaceAll("file:", "") + path);
return Resource.newResource(this.getClass().getResource(getResourceBase().replaceAll("file:", "") + path));
}
catch(IOException ex)
{
return null;
}
}
}


el archivo Main.java se modifica para agregar este handler a la lista de handlers, quedaria asi:

/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/

package addressbook;

import com.sun.jersey.spi.container.servlet.ServletContainer;
import org.mortbay.jetty.Handler;
import org.mortbay.jetty.handler.DefaultHandler;
import org.mortbay.jetty.handler.HandlerList;
import org.mortbay.jetty.servlet.Context;
import org.mortbay.jetty.servlet.ServletHolder;

/**
*
* @author mariano
*/
public class Main {

/**
* @param args the command line arguments
*/
public static void main(String[] args) throws Exception{
org.mortbay.jetty.Server server;
ServletHolder holder;
Context contextWS;

holder = new ServletHolder(ServletContainer.class);

holder.setInitParameter("com.sun.jersey.config.property.packages",
"addressbook.ws");

server = new org.mortbay.jetty.Server(9999);

contextWS = new Context(server, "/ws", Context.SESSIONS);
contextWS.addServlet(holder, "/*");
ContentHandler resource_handler = new ContentHandler();
resource_handler.setResourceBase("/resources");
HandlerList handlers = new HandlerList();
handlers.setHandlers(new Handler[] { resource_handler, contextWS, new DefaultHandler()});
server.setHandler(handlers);
addressbook.ws.AddressBook.fill();
server.start();
}

}


ahora creamos un paquete llamado resources en la base del proyecto en donde vamos a poner nuestros archivos estaticos.

para poder hacer nuestra pagina über AJAX vamos a necesitar dos librerias, jquery y JSON.js, la primera nos ayuda en muchas cosas, siendo una de ellas la de hacer request HTTP que funcione en todos los browsers, la segunda nos sirve para serializar/deserializar objetos javascript.

las urls son las siguientes:

http://jquery.com/

http://www.json.org/json2.js


ponemos la libreria jquery-version.js y json2.js que descargamos en el paquete resources.js, creamos un archivo addressbook.js en el mismo paquete.

tambien creamos un archivo addressbook.html en el paquete resources, osea que la estructura quedaria asi:


/resources
/resources/addressbook.html
/resources/js
/resources/js/addressbook.js
/resources/js/jquery-1.3.1.min.js
/resources/js/json2.js

en addressbook.js vamos a crear los metodos de javascript que nos permitan interactuar con el servidor, asi como las clases que debemos enviar serializadas.

ahora pongo el contenido de los archivos.

resources/addressbook.html


<html>
<head>
<title>Address Book</title>
<link rel="stylesheet" type="text/css" href="css/style.css" />

<script src="js/jquery-1.3.1.min.js" type="text/javascript"></script>
<script src="js/json2.js" type="text/javascript"></script>
<script src="js/addressbook.js" type="text/javascript"></script>
<script type="text/javascript">

function isArray(obj)
{
if(typeof obj == 'object')
{
var criterion = obj.constructor.toString().match(/array/i);
return (criterion != null);
}

return false;
}

function search()
{
var query = $('#search').val();
AddressBook.search("all", query, onSearchSucceed, onSearchFailed);
}

function onSearchSucceed(response)
{
showResults(response);
}

function showResults(response)
{
var results = $('.results');
results.html("");
entries = JSON.parse(response);

if(entries == null)
{
return;
}

if(!isArray(entries["entry"]))
{
var item = entries["entry"];
results.append(item.firstName + " " + item.lastName + '<br/>');
return;
}

for(var i in entries["entry"])
{
var item = entries["entry"][i];
results.append(item.firstName + " " + item.lastName + '<br/>');
}
}

function onSearchFailed(response)
{
alert("La busqueda fallo");
}

function getAll()
{
AddressBook.getAll(onGetAllSucceeded, onGetAllFailed);
}

function onGetAllSucceeded(response)
{
showResults(response);
}

function onGetAllFailed(response)
{
alert("La busqueda fallo");
}

function create()
{
var firstName = $('#firstName').val();
var lastName = $('#lastName').val();
var address = $('#address').val();
var workPhone = $('#workPhone').val();
var homePhone = $('#homePhone').val();
var cellPhone = $('#cellPhone').val();

var entry = new AddressBook.AddressBookEntry(firstName, lastName, address, homePhone, workPhone, cellPhone);
AddressBook.create(entry, onCreateSucceeded, onCreateFailed);
}

function onCreateSucceeded(response)
{
alert("entrada añadida con exito");
}

function onCreateFailed(response)
{
alert("error añadiendo entrada");
}
</script>
</head>

<body>
<h1>Address Book</h1>

<table>
<tr><td>Nombre</td><td><input type="text" id="firstName"></td></tr>
<tr><td>Apellido</td><td><input type="text" id="lastName"></td></tr>
<tr><td>direccion</td><td><input type="text" id="address"></td></tr>
<tr><td>Telefono Trabajo</td><td><input type="text" id="workPhone"></td></tr>
<tr><td>Telefono Casa</td><td><input type="text" id="homePhone"></td></tr>
<tr><td>Telefono Celular</td><td><input type="text" id="cellPhone"></td></tr>
<tr><td></td><td><input type="button" value="Crear" onclick="create();"></td></tr>
</table>

<input type="text" id="search" />
<input type="button" value="Search" onclick="search();" />
<input type="button" value="Get All" onclick="getAll();" />
<div class="results">
</div>
</body>
</html>


resources/js/addressbook.js

// AddressBook namespace
var AddressBook = new Object();

AddressBook.AddressBookEntry = function(firstName, lastName, address, homePhone, workPhone, cellPhone) {
this.firstName = firstName;
this.lastName = lastName;
this.address = address;
this.homePhone = homePhone;
this.workPhone = workPhone;
this.cellPhone = cellPhone;
}

// serializa a JSON
AddressBook.AddressBookEntry.prototype.serialize = function()
{
return JSON.stringify(this);
}

AddressBook.AddressBook = function(entries)
{
this.entries = entries || [];
}

AddressBook.AddressBook.prototype.serialize = function()
{
return JSON.stringify(this);
}

// devuelve una funcion que hace un request REST a path, usando method, el
// metodo devuelto recibe como primer parametro una entidad que es enviada
// en el *cuerpo* del request, por ello este metodo es util para PUT y POST
AddressBook.buildCreateUpdate = function(method, path)
{
return function(entity, success_cb, error_cb)
{
$.ajax({url: path, type: method, dataType: "text", contentType: "application/json",
data: entity.serialize(),
success: success_cb, error: error_cb});
}
}

// devuelve una funcion que hace un request REST a path, usando method, el
// metodo devuelto recibe como primer un string que es agregado al path
// por ello este metodo es util para GET y DELETE
AddressBook.buildGetDelete = function(method, path)
{
return function(id, success_cb, error_cb)
{
$.ajax({url: path + id, type: method, dataType: "text",
success: success_cb, error: error_cb});
}
}

AddressBook.getAll = function(success_cb, error_cb)
{
var request = AddressBook.buildGetDelete("GET", "/ws/book/json/all");
request("", success_cb, error_cb);
}


AddressBook.search = function(type, query, success_cb, error_cb)
{
var request = AddressBook.buildGetDelete("GET", "/ws/book/json/search");
request("/" + type + "/" + query, success_cb, error_cb);
}

AddressBook.create = AddressBook.buildCreateUpdate("PUT", "/ws/book/");


tambien hice algunos cambios en addressbook.ws.AddressBook, aca esta la version actual

/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/

package addressbook.ws;

import addressbook.model.AddressBookEntry;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;

/**
*
* @author mariano
*/
@Path("book")
public class AddressBook {
private static addressbook.model.AddressBook addressBook;

public static addressbook.model.AddressBook search(String type, String query)
{
if(type.equals("firstName"))
{
return new addressbook.model.AddressBook(addressBook.searchByFirstName(query));
}
else if(type.equals("lastName"))
{
return new addressbook.model.AddressBook(addressBook.searchByLastName(query));
}
else
{
return new addressbook.model.AddressBook(addressBook.search(query));
}
}

public static addressbook.model.AddressBook getAddressBook()
{
if(addressBook == null)
addressBook = new addressbook.model.AddressBook();

return addressBook;
}

public static void fill()
{
getAddressBook();
addressBook.addEntry(new AddressBookEntry("Bob", "Esponja", "Fondo de Bikini 1", "123", "321", "5551"));
addressBook.addEntry(new AddressBookEntry("Gary", "Caracol", "Fondo de Bikini 2", "234", "432", "5552"));
addressBook.addEntry(new AddressBookEntry("Arenita", "Ardilla", "Fondo de Bikini 3", "345", "543", "5553"));
addressBook.addEntry(new AddressBookEntry("Patricio", "Estrella", "Fondo de Bikini 4", "456", "654", "5554"));
addressBook.addEntry(new AddressBookEntry("Calamardo", "Calamar", "Fondo de Bikini 5", "567", "765", "5555"));
}

@GET
@Path("xml/all")
@Produces("application/xml")
public static addressbook.model.AddressBook getAllXml()
{
return addressBook;
}

@GET
@Path("json/all")
@Produces("application/json")
public static addressbook.model.AddressBook getAllJson()
{
return addressBook;
}

@GET
@Path("xml/search/{type}/{query}")
@Produces("application/xml")
public static addressbook.model.AddressBook searchXml(@PathParam("type") String type, @PathParam("query") String query)
{
return search(type, query);
}

@GET
@Path("json/search/{type}/{query}")
@Produces("application/json")
public static addressbook.model.AddressBook searchJson(@PathParam("type") String type, @PathParam("query") String query)
{
return search(type, query);
}

@PUT
@Consumes({"application/json", "application/xml"})
public static void add(AddressBookEntry entry)
{
addressBook.addEntry(entry);
}
}

con este ejemplo tenemos una aplicación que expone una API REST y un cliente en javascript/html que lo consume y utiliza, expandiendo la cantidad de entidades en la API podemos escalar a una aplicacion completa.

Si ando con ganas en el proximo post voy a explicar como usar un ORM (object relational mapper) para almacenar el contenido de la entidad en una base de datos.

el codigo completo de este ejemplo lo voy a colgar en github en esta direccion http://github.com/marianoguerra/examples/tree/master, ahora a aprender a usar git :)

4 comentarios:

Anónimo dijo...

hola, soy nuevo en esto y quisiera saber si puedes explicar mejor eso del PUT.

muchas gracias.

luismarianoguerra dijo...

que parte en particular?

Anónimo dijo...

hola nuevamente, estoy probando tu ejemplo en un servidor tomcat, al hacer el PUT me arroja error 403.

saludos

Anónimo dijo...

ya lo solucione.


gracias.

Seguidores

Archivo del Blog