Com preparar un entorn de desenvolupament per a Python for Android.

En un post anterior vaig presentar el Python for Android (Py4A), que és un python per a dispositius Android que funciona sobre l’Scripting Layer For Android (SL4A).

En aquell post presentava diversos scripts que es podien executar sobre el dispositiu Android. O sobre l’emulador d’Android que es troba disponible a l’Android SDK.

Tanmateix, en eaquell post no parlava dels entorns disponibles per a desenvolupar els scripts. Com puc desenvolupar amb Py4A de forma còmoda?

Prerequisits

Evidentment, és prerequisit tenir instal·lat a la tablet l’SL4A i el Py4A.
A l’ordinador que farem servir per desenvolupar cal tenir el JDK 1.6 de Java, com a mínim. El JDK permetrà instal·lar, a continuació l’Android SDK, el qual ens proporciona les eines que ens interessen: l’adb i el ddms. El DDMS és, en realitat, una aplicació Java.

L’Android SDK proporciona un parell d’eines que seran útils per a desenvolupar scripts Python: l’Android Debug Bridge (adb) i el Dalvik Debug Monitor Server (ddms).

Per a poder aprofitar les dues eines esmentades caldrà posar l’SL4A en “mode servidor”. Amb la combinació del “mode servidor”, l’adb, el ddms, i un cable usb serem capaços de desenvolupar scripts python al nostre ordinador i executar-los de forma immediata al dispositiu Android o a l’emulador de l’Android SDK.

Connectar el dispositiu per USB.

El que ve a continuació està basat en https://help.ubuntu.com/community/AndroidSDK

El primer pas ha estat aconseguir que l’adb i el ddms reconeixessin la meva tablet. Es tracta d’un model barat de fabricació xinesa: una tab-playtabpro de Point-of-View, que munta un sistema operatiu Android 4.0.3.

Activar depuració USB
Per a fer-ho, he connectat la tablet a l’ordinador amb el cable USB. NO he activat l’emmagatzematge USB. Quan s’activa l’emmagatzematge USB, per seguretat, hi han tot un seguit d’opcions que es desactiven a la tablet. En particular, es perd la visibilitat de sdcard. Repeteixo: NO he activat l’emmagatzematge USB. Aleshores he posat la tablet en mode de “depuració USB”. Per a fer això he anat a la configuració de la tablet, a les “opcions de desenvolupador” dins del submenú “Sistema” i allà he marcat “Depuració d’USB”, com es veu a la imatge:

ID Vendor
(de http://developer.android.com/tools/device.html)
En un terminal he executat l’ordre lsusb (estic fent les meves proves amb un Linux Lubuntu 12.04).

albert@athena:~$ lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 003 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 004 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 005 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 006 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 007 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 008 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 001 Device 003: ID 058f:6254 Alcor Micro Corp. USB Hub
Bus 003 Device 002: ID 1631:5400 Good Way Technology 
Bus 003 Device 003: ID 1631:5002 Good Way Technology 
Bus 003 Device 004: ID 045e:00f6 Microsoft Corp. Comfort Optical Mouse 1000
Bus 001 Device 008: ID 0781:5535 SanDisk Corp. 
Bus 002 Device 006: ID 18d1:0003 Google Inc. 

Observo el dispositiu Bus 002 Device 006: ID 18d1:0003 Google Inc. És la meva tablet. M’interessa l’ID 18dl. Aquest paràmetre el faré servir per a crear un fitxer de filtre amb

sudo leafpad /etc/udev/rules.d/51-android.rules

Si el fitxer no existeix, aleshores el crea. Cal afegir la línia següent. En negreta, el ID Vendor que he trobat amb lsusb

SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", MODE="0666", GROUP="plugdev"

i li donem els permisos adequats amb

sudo chmod a+r /etc/udev/rules.d/51-android.rules

Finalment, el fitxer que he creat:

albert@athena:~$ ls -al /etc/udev/rules.d/51-android.rules
-rw-r--r-- 1 root root 70 ago 17 19:25 /etc/udev/rules.d/51-android.rules
albert@athena:~$ 

Amb l’anterior ja hauria de tenir prou com per a poder activar el DDMS i l’ADB

Si tot ha anat bé, Engegant el DDMS (l’script ddms el trobaré a HOME_ANDROID_SDK/platform/tools/) el dispositiu apareixerà “online”, com a la imatge de sota.

En cas de no haver-se pogut establir la connexió, apareixeran ?????????.???. Caldrà revisar què és el que no hem fet bé.

En aquest moment, amb el DDMS ja podem fer coses com:
– còpies de la pantalla del dispositiu Android.
– enviar i rebre fitxers a i del dispositiu.
– analitzar el consum de memòria de les aplicacions del dispositiu
– analitzar el tràfic de xarxa de les aplicacions.
– emular l’entrada i sortida de trucades telefòniques, SMS, simular ubicació per a testejar el posicionament.
– …

Python en Server Mode

A continuació he posat l’SL4A en Server Mode. Primer de tot, obro SL4A

Per comoditat, preestableixo el port del Server Mode a 50000 (ha de ser un port lliure, i els port “alts”, per sobre de 32768 acostumen a estar-ho). Com alternativa, si poso 0 al valor del port, aleshores el SL4A és el que tria el port. En aquest últim cas, però, com em caldrà després, hauré de revisar quin port ha establert.

Per a fer-ho, primer de tot, al SL4A obro el menú de preferències:

Trio l’opció Server Port:

I l’estableixo a 50000.

A continuació, obro la vista d'”Interpreters”

Activo el mode Servidor (Start Server)

I l’inicio en mode “Privat”

En aquest moment, el SL4A – Py4A ja està iniciat en mode servidor, com podem veure a la notificació:

Vull confirmar que el port del servidor és el 50000. Si faig click a l’icona de l’SL4A s’obre la notificació que em mostra els scripts de SL4A que estan en funcionament:

I obtinc el port. Efectivament és el 50000.

Ja gairebé ho tinc tot a punt.

android.py

Finalment, he d’obtenir de la carpeta d’android el fitxer android.py i l’he de copiar a l’ordinador. Aquest android.py s’ha d’importar en els scripts de Py4A i, per tant, haurà de ser “visible” des de l’interpret de python de l’ordinador. Android.py actua com a proxy entre l’interpret de Python i l’SL4A. Aquesta comunicació quan el SL4A està en server mode es fa per RPC, de forma que realment no cal que l’interpret i el SL4A resideixin a la mateixa màquina. Aquest fet és el que permet desenvolupar a l’ordinador i executar a la tablet.

Per a obtenir el fitxer android.py de la tableta faré servir el File Explorer del ddms.

El fitxer es troba a /sdcard/com.googlecode.pythonforandroid/extras/python

Aleshores fent servir el botó “Pull File From Device”, El botó amb l’icona del disquet del File Explorer, puc copiar android.py a la carpeta de fonts python amb la que treballaré. En el meu cas l’he copiat a /home/albert/workspace/wk-python/prova-py4a

adb forwardind
Finalment, obro un terminal i em poso a la carpeta de fonts, i faig el següent:

albert@athena:~/workspace/wk-python/prova-py4a$ /home/albert/android-sdk-linux/platform-tools/adb start-server
albert@athena:~/workspace/wk-python/prova-py4a$ /home/albert/android-sdk-linux/platform-tools/adb forward tcp:9999 tcp:50000
albert@athena:~/workspace/wk-python/prova-py4a$ export AP_PORT=9999

El port 9999 és troba a la major part de la literatura sobre SL4A, però és per convenció.

Fixem-nos que les quatre línies anteriors sempre seran les mateixes, o sigui que les puc posar en un script.

Dit i fet, el meu script per engegar l’entorn d’sl4a és aquest.

#!/bin/bash

/home/albert/android-sdk-linux/tools/ddms &
/home/albert/android-sdk-linux/platform-tools/adb start-server
/home/albert/android-sdk-linux/platform-tools/adb forward tcp:9999 tcp:50000
export AP_PORT=9999
idle &

Li en dono els permisos d’execució (chmod a+x sl4a)

A L’script primer engego el ddms per si em cal passar scripts de l’ordinador a la tablet, o per si cal controlar l’us de memòria, comunicacions, o per si vull fer còpies de pantalla… Després engeo l’adb i li passo els ports 9999, i 50000 (el que he preestablert per al SL4A Server Mode). Finalment, exporto la variable AP_PORT i, finalment, engego IDLE, l’IDE de pyhton construit amb TkInter.

En cas que no el tingueu instal·lat, IDLE es pot instal·lar amb Synaptic, o des del Centre de Programari de l’Ubuntu

Finalment, des de l’IDLE faig la prova canònica:

I, com no podria ser d’una altre forma, funciona 😉

Hem construït un entorn de desenvolupament per a SL4A i Py4A amb:
Un cable USB
Un ordinador amb
Lubuntu 12.01
JDK1.6
Android SDK
IDE de Python IDLE
Una tablet barata.

Albert Baranguer – 26.08.2012

HTML5. WebSockets amb Jetty

Continuant amb l’exploració de l’HTML5, avui parlo dels WebSockets. I per a il·lustrar-lo, faré un petit exercici amb websockets fent servir el servidor java Jetty.

La idea de l’exemple l’he tret del projecte http://code.google.com/p/phpwebsocket/. El client html és pràcticament idèntic. La part del servidor, en el meu exemple, l’he implementada amb Java; estant en PHP en l’original.

Els WebSockets permeten realitzar la comunicació bidireccional i full-duplex sobre un canal TCP, entre la pàgina web i el servidor de forma molt més eficient que amb l’esquema de petició-resposta de l’HTML, o que la comunicació amb AJAX o, més explí­citament, mitjançant Comet.

El protocol WebSocket es descriu a la RFC6455.

A dia d’avui, aquest protocol es troba implementat per les darreres versions dels principals navegadors:  Chrome, Internet Explorer, Firefox i Safari.

A més, el servidor ha de soportar també els WebSockets de servidor.

En si mateix el protocol recorda una crida HTTP, però no ho és:

Exemple de petició des del client:

GET /mychat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat
Sec-WebSocket-Version: 13
Origin: http://example.com

Exemple de resposta del servidor:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat

Des del punt de vista del desenvolupament parlem, doncs, de clients i servidors de WebSockets.
HTML5 ofereix una API (normalitzada pel W3C) per a desenvolupar clients d’aplicacions basades en WebSocket.

Per la banda del servidor, els llenguatges més utlitzats, com Java o php aporten classes, mètodes i funcions per a crear i utilitzar websockets de servidor.

Sobre servidors Java: jWebSocket, Apache Tomcat i Jetty també tenen implementacions de sockets de servidor.
Sobre servidors PHP: phpWebSockets

Podem trobar implementacions de servidor WebSocket amb altres llenguatges. Per exemple amb Python una cerca ens proporciona: pywebsocket, o WebSocket for Python (ws4py).

Per a fer un experiment senzill utilitzaré el servidor de java servlets Jetty (versió 8) que proporciona una implementació java de WebSockets de servidor. Podeu revisar aquest article: Jetty WebSocket Server, i aquest altre: Jetty/Feature/WebSockets.
Per descomptat, el javadoc: http://download.eclipse.org/jetty/stable-8/apidocs/

Per a provar els WebSockets he creat un petit projecte amb Eclipse, amb la següent estructura de carpetes:

A la carpeta src hi creo el package java amb la següent classe: com.stsoftlliure.proves.html5.websockets.ProvaWebSocket

Fitxer ProvaWebSocket.java:

package com.stsoftlliure.proves.html5.websockets;

import java.io.IOException;
import java.util.Date;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.websocket.WebSocket;
import org.eclipse.jetty.websocket.WebSocketServlet;
import org.eclipse.jetty.util.log.Log;
public class ProvaWebSocket extends WebSocketServlet  {

   private static final long serialVersionUID = 8442112605629206192L;


    protected void doGet(HttpServletRequest request,
                         HttpServletResponse response)
    throws ServletException ,IOException  {
      response.getOutputStream().println("aquesta és la resposta");
      Log.info("[doGet] No fa res");
    }

    public WebSocket doWebSocketConnect(HttpServletRequest request,
                                        String protocol) {
      Log.info("[doWebSocketConnect] entra a doWebSocketConnect");
      return new ProvaServerWebSocket();
    }

    // inner class que implementa la interface WebSocket
    class ProvaServerWebSocket implements WebSocket.OnTextMessage {
      // la connexió
      Connection wsConn;
      Date dataInici;

      public ProvaServerWebSocket() {
         dataInici = new Date();
      }

      @Override
      public void onClose(int arg0, String arg1) {
         // TODO Auto-generated method stub
         Log.info("[onClose] arg0:" + arg0 + "; arg1: " + arg1);
         wsConn.close(arg0, arg1);
      }

      @Override
      public void onOpen(Connection wsConn) {
         this.wsConn = wsConn;
         try {
            Log.info("[onOpen] wsConn:" + wsConn);
            wsConn.sendMessage("Estic connectat!");
         } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            Log.debug("Error: " + e.toString());
         }
      }

      @Override
      public void onMessage(String sMessage) {
         // TODO Auto-generated method stub
         Log.info("[onMessage] Missatge rebut: " + sMessage);
            // fa el tractament:
         // to do
         // help, hola, nom, edat, data, adeu
 
         try {
            if (sMessage.trim().equalsIgnoreCase("help")) {
               wsConn.sendMessage("ordres acceptades: help, hola, nom, edat, data, adeu");
            }

            if (sMessage.trim().equalsIgnoreCase("hola")) {
               wsConn.sendMessage("Hola! benvingut al servidor WebSocket!");
            }

            if (sMessage.trim().equalsIgnoreCase("nom")) {
               wsConn.sendMessage("Jo em dic ProvaWebSocket.class");
            }

            if (sMessage.trim().equalsIgnoreCase("edat")) {
               wsConn.sendMessage("En funcionament des de: " + dataInici);
            }

            if (sMessage.trim().equalsIgnoreCase("data")) {
               wsConn.sendMessage("ara són les: " + (new Date()));
            }

            if (sMessage.trim().equalsIgnoreCase("adeu")) {
               wsConn.sendMessage("Adéu siau!");
               wsConn.close();
            }
         } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            Log.debug("Error: " + e.toString());
         }
      }
   }
}

Algunes remarques a la classe anterior:

– La classe és un servlet del tipus nou WebSocketServlet.
– Els WebSocketServlet implementen el doGet. El puc invocar directament i obtinc una resposta HTML. Però la funcionalitat apareix amb el nou mètode doWebSocketConnect. A doWebSocketConnect és on treballa el socket de servidor.
-Quan es rep una nova connexió, el mètode doWebSocketConnect el que fa és instanciar la inner class ProvaServerWebSocket, que implementa la interface WebSocket.OnTextMessage.
– La interface WebSocket té altres subinterfaces, a més de la OnTextMessage, per a tractar altres tipus de connexions. Les subinterfaces de WebSocket són: WebSocket.OnBinaryMessage, WebSocket.OnControl, WebSocket.OnFrame, WebSocket.OnTextMessage.
– en la connexió onOpen, obtinc l’objecte Connection wsConn. Que serà propiament el canal full duplex entre servidor i client.
– El tractament d’un missatge rebut el faig en implementar el mètode public void onMessage(String sMessage). sMessage és el missatge rebut des del client. Les respostes al client s’envien mitjançant l’objecte Connection wsConn obtingut en l’event onOpen. En aquest exemple, simplement analitzo quin missatge rebo i, depenent del missatge, envio una o altre resposta al client.
– A la sortida onClose, tanco la connexió

Senzill i net, oi?

L’altre part és la pàgina del client. Una pàgina HTML5 que fa servir websockets ha d’implementar els mètodes que responen als esdeveniments de connexió, alliberament, recepció i enviament de missatges al servidor. Vet aquí la pàgina HTML que dialoga amb el servidor:

Fitxer prova.html

<html>
<head>
<title>WebSocket</title>

<style>
 html,body{font:normal 0.9em arial,helvetica;}
 #log {width:440px; height:200px; border:1px solid #7F9DB9; overflow:auto;}
 #msg {width:330px;}
</style>

<script>
var socket;

function init(){
  var host = "ws://localhost:8080/websockets2/servlet/WebSocket";
  try{
    socket = new WebSocket(host);
    log('WebSocket - status '+socket.readyState);
    socket.onopen    = function(msg){ log("Welcome - status "+this.readyState); };
    socket.onmessage = function(msg){ log("Received: "+msg.data); };
    socket.onclose   = function(msg){ log("Disconnected - status "+this.readyState); };
  }
  catch(ex){ log(ex); }
  $("msg").focus();
}

function send(){
  var txt,msg;
  txt = $("msg");
  msg = txt.value;
  if(!msg){ alert("El missatge no pot ser nul"); return; }
  txt.value="";
  txt.focus();
  try{ socket.send(msg); log('Enviat: '+msg); } catch(ex){ log(ex); }
}

function quit(){
  log("Adéu!");
  socket.close();
  socket=null;
}

// Utilities
function $(id){ return document.getElementById(id); }
function log(msg){ $("log").innerHTML+="<br>"+msg; }
function onkey(event){ if(event.keyCode==13){ send(); } }
</script>

</head>
<body onload="init()">
 <h3>WebSocket</h3>
 <div id="log"></div>
 <input id="msg" type="textbox" onkeypress="onkey(event)"/>
 <button onclick="send()">Send</button>
 <button onclick="quit()">Quit</button>
 <div>Commands: help, hola, nom, edat, data, adeu</div>
</body>
</html>

Alguns comentaris: com es veu, la pàgina és senzilla, la clau del funcionament està al mètode init

function init(){
  var host = "ws://localhost:8080/websockets2/servlet/WebSocket";
  try{
    socket = new WebSocket(host);
    log('WebSocket - status '+socket.readyState);
    socket.onopen    = function(msg){ log("Welcome - status "+this.readyState); };
    socket.onmessage = function(msg){ log("Received: "+msg.data); };
    socket.onclose   = function(msg){ log("Disconnected - status "+this.readyState); };
  }
  catch(ex){ log(ex); }
  $("msg").focus();
}

El qual crea l’objecte socket, el connecta al host, adonem-nos del protocol “ws”; i enllaça els esdeveniments del socket: onopen, onmessage, onclose amb les funcions que els tracten.

L’enviament de missatges al servidor es realitza amb la funció send, que invoca al mètode send de l’objecte socket.

function send(){
  var txt,msg;
  txt = $("msg");
  msg = txt.value;
  if(!msg){ alert("El missatge no pot ser nul"); return; }
  txt.value="";
  txt.focus();
  try{ socket.send(msg); log('Enviat: '+msg); } catch(ex){ log(ex); }
}

Amb l’anterior ja tinc tot el codi. Poso la pàgina prova.html a la carpeta webcontent del projecte Eclipse. A més, en la carpeta webcontent també hi posaré la carpeta WEB-INF, amb una subcarpeta classes buida, la carpeta lib amb les llibreries que em calen i el web.xml.

En resum, a webcontent hi trobo:

webcontent/
    prova.html
    WEB-INF/
        classes/
        lib/
            jetty-util-8.1.2.v20120308.jar
            jetty-websocket-8.1.2.v20120308.jar
        web.xml

Les llibreries a la carpeta lib corresponen a la versió 8 del Jetty i es poden trobar a la seva carpeta lib. He vist que cal incloure-les al war. Em pensava que es referenciarien automàticament al fer deploy al Jetty, però no ha estat així.

El web.xml és força estàndar:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
	http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
	<display-name>tailor</display-name>
	<servlet>
		<servlet-name>WebSocket</servlet-name>
		<servlet-class>com.stsoftlliure.proves.html5.websockets.ProvaWebSocket</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>WebSocket</servlet-name>
		<url-pattern>/servlet/*</url-pattern>
	</servlet-mapping>

	<welcome-file-list>
		<welcome-file>prova.html</welcome-file>
	</welcome-file-list>
</web-app>

Si de cas, remarcar el mapeig de /servlet/* a WebSocket. Aquesta és l’explicació de
var host = “ws://localhost:8080/websockets2/servlet/WebSocket”;
a la funció init.

Amb Jetty, a més, m’ha calgut establir el “context.” Al projecte, el fitxer de context l’he guardat a la carpeta homònima. És el següent fitxer.

websockets2.xml:

<?xml version="1.0"  encoding="ISO-8859-1"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">

<Configure class="org.eclipse.jetty.webapp.WebAppContext">
  <Set name="contextPath">/websockets2</Set>
  <Set name="war"><SystemProperty name="jetty.home" default="."/>/webapps/websockets2.war</Set>
</Configure>

Compte amb els salts de línia. El fitxer XML de context potser no queda “bonic” tal com està, però us podeu trobar amb problemes estranys si afegiu salts de línia.

Amb el fitxer anterior li estem dient quin és el war que ha de desplegar i quin nom tindrà el context. Amb aquest fitxer de context acabem de completar l’explicació del

var host = “ws://localhost:8080/websockets2/servlet/WebSocket”;

Ara és evident:

ws:// – Protocol websocket. Amb websockets segurs, serà wss://
localhost:8080 – Host i port del servidor websocket
websockets2 – El context, o l’aplicació web.
servlet/WebSocket – el Servlet servidor de WebSocket

Finalment, per automatitzar el muntatge i ddesplegament al servidor jetty local de l’apliació, faig servir el següent build.xml d’ant:

<project name="websocket" default="deploy" basedir=".">
  <description>
  proves amb websockets 2
  </description>

  <!-- estableix propietats globals -->
  <property name="src" location="src"/>
  <property name="bin" location="bin"/>
  <property name="war" location="war"/>
  <property name="webcontent" location="webcontent" />
  <property name="context" location="context" />
  <property name="build" location="build"/>
  <property name="lib" location="/home/albert/jetty/lib" />
  <property name="deploy" location="/home/albert/jetty/webapps" />
  <property name="deploy-context" location="/home/albert/jetty/contexts" />


  <target name="init">
    <!-- time stap -->
    <tstamp/>
    <!-- Crea el directory bin utilitzat en la compilació -->
    <mkdir dir="${bin}"/>
  </target>


  <target name="compile" depends="init" description="compila els fitxers font" >
    <!-- Compila les fonts java de ${src} en ${build} -->
    <javac srcdir="${src}" destdir="${bin}">
      <classpath>
       <pathelement location="${lib}/jetty-websocket-8.1.2.v20120308.jar"/>
       <pathelement location="${lib}/servlet-api-3.0.jar"/>
       <pathelement location="${lib}/jetty-util-8.1.2.v20120308.jar"/>
      </classpath>
   </javac>
  </target>

  <target name="build" depends="compile" description="genera el build">
    <!-- Crea el directori de distribució -->
    <delete dir="${build}" />
    <mkdir dir="${build}"/>

    <!--  copia webcontent a build -->
    <copy todir="${build}" >
        <fileset dir="${webcontent}" />
    </copy>
    
    <!--  copia bin a classes -->
    <copy todir="${build}/WEB-INF/classes">
        <fileset dir="${bin}" />
    </copy>
  </target>
     
  <target name="war" depends="build" description="genera el war">   
    <delete file="${war}/websockets2.war" />
    <jar jarfile="${war}/websockets2.war" basedir="${build}"> 
      <include name="**/*"/>
    </jar>
  </target>
  	
  <target name="deploy" depends="war" description="deploy del war i del context">
    <delete file="${deploy}/websockets2.war" />
    <delete file="${deploy-context}/websockets2.xml" />
  	<copy file="${war}/websockets2.war" todir="${deploy}" />
  	<copy file="${context}/websockets2.xml" todir="${deploy-context}" /> 
  </target>

</project>

Aleshores, ses d’Eclipse puc executar el build.xml que compila la classe, construeix el war i el copia a la carpeta webapps del meu Jetty local.

Per provar l’aplicació només en cal engegar el Jetty i obrir el navegador amb suport de websockets (en el meu cas, el Chrome) i apuntar-lo a l’adreça http://localhost:8080/websockets2

I el resultat és el següent.

I, efectivament, puc mantindre el diàleg amb el servidor: