Mostrando entradas con la etiqueta Programación Avanzada. Mostrar todas las entradas
Mostrando entradas con la etiqueta Programación Avanzada. Mostrar todas las entradas

AndroidWebServer

La aplicación

Para construir un servidor web es necesario contar con un programa que haga las veces de “servidor”, es decir, que esté escuchando siempre en uno o varios sockets a la espera de algún cliente para servirle la información solicitada.
La interfaz gráfica será muy sencilla, una etiqueta para mostrar la IP del servidor, un botón para encender o apagar el servidor web y una consola para ver que es lo que está sucediendo, es decir llevar un registro de eventos.

Antes de continuar: Este trabajo está también disponible en versión PDF, lo cual considero, es más cómodo de leer. Lo puedes descargar desde aquí.

Interfaz gráfica

Como se menciona anteriormente, la interfaz es bastante simple, pensando en que de inicio el servidor no es una aplicación que tenga que ser manipulada constantemente por el usuario final.
La interfaz queda abierta a ampliaciones, según se vaya dotando de funcionalidad a esta aplicación.

ServerSocket

Sobre la plataforma Android, el manejo de los sockets es similar al que se realiza en cualquier otra aplicación de escritorio.
Así que simplemente abrimos un ServerSocket
//Fragmento de código del archivo WebServerActivity.java
ServerSocket ss;
try {
    ss = new ServerSocket(puerto);
    while (!isCancelled()) {
        Socket entrante = ss.accept();
//...

Permisos

Para hacer uso de los sockets es necesario otorgar permiso de INTERNET en el manifiesto de la aplicación, lo cual se consigue añadiendo la línea
<uses-permission android:name="android.permission.INTERNET" />
Ya que de otro modo  obtendremos una excepción de IO.

Complicaciones con el manejo de los sockets

En Android no es posible trabajar con sockets en el hilo principal, por tanto no es posible utilizar el código anteriormente mostrado dentro de cualquier método. Por ejemplo, no podemos colocarlo en el método onCreate, ni colocarlo directamente dentro de algún manejador de eventos ya que al tratar de ejecutarlo nos lanzará la excepción:
android.os.NetworkOnMainThreadException
Por tanto es necesario utilizar una clase interna que extienda de AsyncTask para trabajar con el ServerSocket
//Fragmento de código del archivo WebServerActivity.java
public class ServidorWeb
    extends AsyncTask<WebServerActivity, String, Void> {

En cuyo método sobreescrito doInBackground, añadiremos el código necesario para inicializar el socket:
//Fragmento de código del archivo WebServerActivity.java
protected Void doInBackground(WebServerActivity... arg0) {
    try {
        ss = new ServerSocket(puerto);
        while (!isCancelled()) {
            Socket entrante = ss.accept();

Para comenzar la ejecución basta con crear un objeto de nuestra clase nueva, ServidorWeb y llamar al método execute, por la manera en la que se trabajó, al método es necesario pasarle la referencia a la clase de nuestra actividad.
//Fragmento de código del archivo WebServerActivity.java
ServidorWeb servidorWeb;
servidorWeb = new ServidorWeb();
servidorWeb.execute(this);

Registro de eventos

En la interfaz gráfica incluimos un control (View) para llevar el registro de eventos, un símil de consola, pero sin serlo necesariamente, ya que no admite parámetros de entrada ni escritura sobre ella.
Para añadir texto bastaría con llamar al método setText, sin embargo es aquí en donde existe otra limitante de Android, y es que los controles solo pueden ser manipulados directamente por el hilo que los creó, todos los controles son creados por el hilo de UI, y no se pueden modificar por otro sino ese. Recordaremos que los eventos son lanzados desde el servidor, que está en otra clase, y peor aún, corre en otro hilo.
Hacer una llamada como: ServerConsole.append(Html.fromHtml("Buen día señor sol")); desde cualquier otra clase que no sea la de la actividad en cuestión resultará en una excepción:
android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

Para nuestra suerte, existe una solución y es haciendo uso del método onProgressUpdate,con el cual podemos modificar cosas que estén corriendo en el hilo de UI
//Fragmento de código del archivo WebServerActivity.java
@Override
protected void onProgressUpdate(String... values) {
      /* logView es un método implementado en la clase WebServerActivity, en donde fueron creados los controles de la interfas. Se encarga de añadir el texto al control ServerConsole */
      logView((values[0]), level);
}
Contrario a lo que pareciera uno no debe llamar directamente a este método. Si no hacer uso del método (también proporcionado por la clase AsyncTask) publishProgress el cual, a cada llamada, provocará que el método onProgressUpdate se ejecute.
Esa es la manera en la que estaremos modificando la interfaz desde el hilo en el que correrá nuestro servidor.

Apagando y encendiendo el servidor

Para terminar la ejecución del código existente dentro de una AsyncTask tenemos a nuestra disposición el método cancel, del cual nos vamos a valer para apagar el servidor si es que no queremos que siga procesando solicitudes, dada la arquitectura de nuestro servidor, también es necesario cerrar el socket, ya que al estar esperando un cliente, no se apaga de inmediato.
//Fragmento de código del archivo WebServerActivity.java
servidorWeb.cancel(true);
servidorWeb.closeSocket();

Aceptando, leyendo y respondiendo la petición

Una vez que todo el esqueleto esté armado, es necesario adentrarnos a un nivel inferior, al núcleo del servidor, la forma en la que se hace una petición web es aceptando la conexión con un socket creado internamente, a través del cual podemos leer y escribir información al cliente que lo solicite.
Para realizar esto tenemos el siguiente código dentro de doInBackground:
//Fragmento de código del archivo WebServerActivity.java
///Mientras la tarea no haya sido cancelada
while (!isCancelled()) {
      ///Quedamos a la espera de un cliente nuevo
      Socket entrante = ss.accept();
///Actualizamos la “consola” con la información del cliente
///aceptado
      publishProgress("Cliente aceptado: <b>"                                     + entrante.getInetAddress().getHostName() + "</b>",
            "1");
///Usaremos la clase auxiliar PeticionWeb para procesar
      PeticionWeb pw = new PeticionWeb(entrante, this);
///Dado que PeticionWeb extiende de Thread, lo iniciamos
      pw.start();
}

La clase PeticionWeb


//Fragmento de código del archivo PeticionWeb.java
public class PeticionWeb extends Thread {
      private ServidorWeb web;
      privateSocket scliente = null;
Esta clase será el auxiliar encargado de procesar la petición, extiende de Threadpara que corra de manera que no bloquee a las demás peticiones entrantes. En ella se obtienen los flujos de entrada y de salida, que son los canales de comunicación con el cliente que está interesado en consumir de nuestro servidor.
El siguiente código es la implementación sobreescrita de run adaptada a nuestras necesidades.

//Fragmento de código del archivo PeticionWeb.java
@Override
public void run() {
      try {
            /* Obtenemos los flujos de entrada y salida, así como un objeto instancia de PrintWriter para escribir en el cliente */
            BufferedReader in = new BufferedReader(newInputStreamReader(
                        scliente.getInputStream()));
            OutputStream outputStream = scliente.getOutputStream();
            PrintWriter out = new PrintWriter(newOutputStreamWriter(
                        outputStream), true);
            out.flush();

            String cadena = "";
            /* ProcesaPeticion es la clase que usaremos para recuperar el contenido de los archivos almacenados en las carpetas del servidor */
            ProcesaPeticion pp = null;
            /* Leemos del flujo de entrada, mientras la lectura no sea nula continuaremos */
            while ((cadena = in.readLine()) != null) {

                  if (cadena != null ///es
&& !"".equals(cadena)) {
/* La lectura de una línea en blanco marca el final de la petición HTTP */
                        if (cadena.startsWith("GET")) {
                        /* Revisamos el contenido de la cadena que comience con GET, ya que es ahí en donde viene la información del archivo que se desea consultar */
                             cadena = cadena.substring(4);
                  cadena = cadena.substring(0,  
cadena.indexOf(" "));
                             web.log("Petición: " + cadena);
/* Dejamos que la clase auxiliar trate de buscar el archivo y devolver una respuesta */
                             pp = newProcesaPeticion(cadena);
                        }
                  } else
                        break;
            }
/* Escribimos las cabeceras de la respuesta y el contenido solicitado, el cual nos lo devolverá el método getResponse */
            out.println("HTTP/1.0 200 OK");
            out.println("Server: " + web.getIpAddr());
            out.println("Date: " + new Date());
            out.println("Content-Type: text/html");
            out.println("");
            if (pp != null) {
                  out.println(pp.getResponse());
            }
            out.flush();
            out.close();
      } catch (Exception e) {
            web.log("Error en servidor: " + e.toString(), "2");
      }
}

La clase ProcesaPeticion

Esta clase es el puente entre los archivos físicos en el servidor y la respuesta que el cliente espera recibir del servidor, no olvidar que, como estamos guardando los archivos del servidor en la memoria externa del dispositivo necesitamos añadir otro permiso al manifiesto de la app.
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
El código:
public class ProcesaPeticion {
      String path;
      String response;

      public ProcesaPeticion(String path) throwsException {
            this.path = path;
            // Checamos si está montada la SD:
            String state = Environment.getExternalStorageState();
            if (Environment.MEDIA_MOUNTED.equals(state)) {
                  // Variables File que nos ayudarán más adelante
                  File sdCard = Environment.getExternalStorageDirectory()
,peticion
,contenedor;
                  // Los documentos deberán existir en la carpeta
                  // "AndroidWebServer/wwwroot"
                  contenedor = new File(sdCard.getAbsolutePath()
                             + "/AndroidWebServer/wwwroot");
                  contenedor.mkdirs();
                  File request = new File(contenedor.getAbsolutePath() + path);
                  StringBuilder strLine = new StringBuilder();
                  if(request.exists() && request.isFile()) {
                        try {
                             // Lectura del archivo
                        } catch (Exception e) {
                        }
                  } else if(request.exists() && request.isDirectory()){
                        // Aquí manejaremos cuando la solicitud sea a un // directorio
                  } else {
                        // La ruta que solicitó no existe
                  }
                  response = strLine.toString();
            } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
            } else {
                  throw new Exception("No hay una memoria SD en el dispositivo");
            }
      }
     
}



Referencias

Wikipedia
Documentación de Android Developers



Contacto

Si tienes alguna duda con respecto a la aplicación puedes dejar un comentario en la siguiente liga
Enviarme un correo a antonio.feregrino@gmail.com
Mandarme un tweet a @fferegrino o @IO_Exception
No olvides que la aplicación está disponible en Google Play, la tienda de aplicaciones para Android en la siguiente liga:


¡Saludos!
@fferegrino :)

ediThor se muda a GitHub


Tiene rato ya que les había contado de ediThor, el editor de texto concurrente que en equipo programamos unos compañeros y yo para una asignatura en la UPIITA.

Pues resulta que en aquellos tiempos era un novato (más) en esto de compartir código en línea, ahora que ya me "enseñaron" a usar GitHub, he decidido mudar el proyecto a un repositorio en la plataforma. Cabe destacar que no es el ediThor (beta) del que se habla en la entrada anterior porque decidí reescribirlo todo (porque de igual manera el nivel de mi nuestra programación estaba bastante decadente en esos tiempos), eso sí, lo de beta aún no se le quita, "está chavo todavía" diría yo.

Pues así las cosas, si algún javamaniaco quisiera unirse al desarrollo del proyecto ya sabe en donde encontrarme. Con gusto colaboramos.

El repositorio es:



¡Saludos!
@fferegrino :)

¡Chuirer está al aire!



 Después de mucho trabajo (por eso tenía tan abandonado el blog) la aplicación web de la que les hablé hace ya unos cuantas entradas al fin está viva y en un grado estable.

Al parecer nos va a dar para un 10 en la asignatura y nos valió (dicen) felicitación hasta del subdirector académico de la ESCOM :P.


A recordar y para hacer un pequeño resumen de lo que es y qué hace:

Chuirer es una red social nueva, usando(copiando) el concepto de Twitter por completo. Tienes la ocpión de registrarte, iniciar sesión, seguir gente, unirte a grupos, publicar mensajes, calificar los mensajes... aún faltan cosas por desarrollar pero ya no cubrían los requerimientos que se nos hicieron para obtener una calificación (muy) buena. Sin embargo no se queda para la historia, al menos yo seguiré desarrollando Chuirer.

Ahora bien, para realizarla utilizamos varias tecnologías:
  • Se utlilizó Java como lenguaje de servidor. Con ello se usaron servlets y para las pantallas se utilizó JSP y HTML
  • También se hace uso de AJAX para las comunicaciones asíncronas y dotar de mayor dinamismo a la aplicación.
  •  Cuando se hace una petición con AJAX, el servidor responde en formato JSON(JavaScript Notation Object), para lo cual se utilizaron las librerías JSONObject y JSONArray
  • Para la implementación de AJAX así como de muchos otras acciones del lado del cliente se utilizó el framework de Js, JQuery.
  • Se utilizó el framework Struts para la implementación del patron MVC.
  • Se utlilizó JDOM, para realizar la persistencia de la información en formato XML(No olvides que para esta versión de Chuirer no se implementó el uso de un SGBD conocido ¡toda la información se guarda en archivos XML brutos!) 
  • Se emplea CSS3 para mejorar el diseño de la aplicación en las pantallas finales, las que ve el usuario.
Lo presentamos en la EXPO ESCOM y pues de ahí surgieron ideas, lo que viene para Chuirer es mover la aplicación para que funcione con una base de datos como MySQL, Implementación de busqueda semántica y ahora que estoy tomando, un curso de programación para dispositivos con Android, realizar una app para Chuirer.

El código es libre, lo puedes encontrar en GitHub: Chuirer on GitHub
Así mismo puedes verlo en funcionamiento: Chuirer en línea

Recuerda que estoy a tu disposición para cualquier duda, aclaración y ayuda que puedas llegar a necesitar. ¡Coménta!


¡Saludos!
@fferegrino :)

ediThor (Beta)

Con la novedad de que acabaron las clases de programación avanzada en UPIITA ( :( tiene como 2 semanas ) y entregamos el proyecto final para la asignatura al cual nombramos ediThor.

Empiezo por describir brevemente lo que tratamos de hacer en este proyecto:


Nuestro proyecto, es un editor cooperativo y “en vivo” de archivos de texto plano, esto es decir que permite que varios usuarios, en distintas computadoras, editen el mismo archivo a la vez y los cambios realizados en uno se muestren en todas las terminales cliente que tengan abierto el archivo.

Incluirá funciones básicas de un editor como abrir, guardar y cerrar archivo, además de cortar, copiar, remplazar. Inicialmente, solamente se puede editar un archivo a la vez con la posibilidad de hacerlo escalable hasta poder editar varios archivos.

El cliente incluye una aplicación de chat, el cual pueden usar los que editan el archivo sobre un mismo servidor, el chat por igual es básico, clásico, mensajes generales y privados.

Hasta ahí la descripción.

Lamentablemente el desarrollo del programa no quedó al 100% en este intento, se quedó en lo que yo llamaría una versión "beta".

Programamos y programamos, baja el producto de nuestro trabajo y ojalá te sirva:

ediThor(beta).

Gracias a los papahws:


Benavides Moreno Rodrigo. @frescasandia
Dávila Hernández Luis Javier.
Feregrino Bolaños Antonio. @fferegrino.


Saludos! @fferegrino :)