Introducción

Los teléfonos Android suelen disponer de conexión a Internet. Esto nos permite no solo almacenar los datos en nuestro dispositivo, si no compartirlos con otros usuarios. En el primer punto del capítulo trataremos de resolver el problema de comunicar dos aplicaciones en Internet mediante la herramienta básica: los sockets. Existen otras alternativas de más alto nivel, como el uso del protocolo HTTP, que será estudiada en el segundo punto del capítulo. Para terminar se tratará una tercera alternativa, todavía de más alto nivel, los servicios web.

En este capítulo implementáramos el mismo ejemplo que en el capítulo anterior, es decir, almacenaremos las puntuaciones obtenidas en Asteroides, pero ahora en un servidor de Internet. Utilizaremos las tres alternativas descritas en el párrafo anterior. No obstante, has de tener claro que estos mecanismos están relacionados entre sí. Por ejemplo, si utilizas servicios web, internamente se utilizará el protocolo HTTP y además este protocolo utiliza sockets para establecer la comunicación.

Objetivos:

  • Repasar las alternativas principales para intercambiar datos por Internet.
  • Describir el uso de socket como herramienta básica de una aplicación para comunicarse con otras aplicaciones por Internet.
  • Mostrar cómo se programaría un protocolo basado en sockets sobre TCP.
  • Describir el funcionamiento del protocolo HTTP
  • Mostrar cómo se pueden programa peticiones HTTP desde Android.
  • Definir el concepto de servicio Web y comparar las alternativas más importantes.
  • Mostrar el acceso a servicios web de terceros desde un cliente Android.
  • Aprender a crear nuestro propio servidor de servicios web con PHP, Apache y MySQL.

Comunicaciones en Internet mediante sockets

Antes de definir que es un socket conviene aclarar los roles o configuraciones que pueden tomar las aplicaciones en un proceso de comunicación. Las dos configuraciones más importantes que pueden tomar son: las llamadas “arquitectura igual a igual” y la “arquitectura cliente/servidor”. Esta segunda es la que utilizaremos en los ejemplos de este capítulo, por tanto, conviene aclarar este aspecto.

La arquitectura cliente/servidor

Las aplicaciones en Internet suelen seguir la arquitectura cliente/servidor. Esta arquitectura se caracteriza por descomponer el trabajo en dos partes (es decir dos programas): el servidor, que centraliza el servicio, y el cliente, que controla la interacción con el usuario. El servidor ha de ofrecer sus servicios a través de una dirección conocida. Algunos ejemplos de aplicaciones basadas en la arquitectura cliente/servidor son WWW o el correo electrónico. Se suelen seguir las siguientes pautas de comportamiento en esta arquitectura:

- ¿Qué es un socket?

Cada una de las diferentes aplicaciones en Internet (web, correo electrónico,…) ha de poder intercambiar información entre programas situados en diferentes ordenadores o dispositivos. Con este propósito, se va ha hacer uso del nivel de transporte de la pila de protocolos TCP/IP, cuyo objetivo final es permitir el intercambio de información a través de la red de forma fiable y transparente.

video[Tutorial] El interfaz socket

La interfaz socket define las reglas que un programa ha de seguir para utilizar los servicios del nivel de transporte en una red TCP/IP. Esta interfaz se basa en el concepto de socket. Un socket es el punto final de una comunicación bidireccional entre dos programas que intercambian información a través de Internet (socket se traduce literalmente como “enchufe”).

Dado que en un mismo dispositivo/ordenador podemos estar ejecutando de forma simultánea diferentes aplicaciones que utilizan Internet para comunicarse, resulta imprescindible identificar cada socket con una dirección diferente. Un socket se va a identificar por la dirección IP del dispositivo, más un número de puerto (de 16 bits). En Internet se suele asociar a cada aplicación un número de puerto concreto (por ejemplo: 80 para la web, 25 para el correo electrónico, 7 para ECHO o 4661 para eDonkey).

Una conexión está determinada por un par de sockets, uno en cada extremo de la conexión. Existen dos tipos de socket: socket stream y socket datagram. Veamos en qué se diferencian:

- Sockets stream (TCP)-datagram (UDP)

Sockets stream (TCP)

Los sockets stream ofrecen un servicio orientado a conexión, donde los datos se transfieren como un flujo continuo, sin encuadrarlos en registros o bloques. Este tipo de socket se basa en el protocolo TCP, que es un protocolo orientado a conexión. Esto implica que antes de transmitir información hay que establecer una conexión entre los dos sockets. Mientras uno de los socketsatiende peticiones de conexión (servidor), el otro solicita la conexión (cliente). Una vez que los dossockets están conectados, ya se puede transmitir datos en ambas direcciones. El protocolo incorpora de forma transparente al programador la corrección de errores. Es decir, si detecta que parte de la información no llegó a su destino correctamente, esta volverá a ser trasmitida. Además, no limita el tamaño máximo de información a transmitir.

Sockets datagram (UDP)

Los sockets datagram se basan en el protocolo UDP y ofrecen un servicio de transporte sin conexión. Es decir, podemos mandar información a un destino sin necesidad de realizar una conexión previa. El protocolo UDP es más eficiente que el TCP, pero tiene el inconveniente de que no se garantiza la fiabilidad. Además, los datos se envían y reciben en datagramas (paquetes de información) de tamaño limitado. La entrega de un datagrama no está garantizada: estos pueden duplicarse, perderse o llegar en un orden diferente del que se envió.

La gran ventaja de este tipo de sockets es que apenas introducen sobrecarga sobre la información transmitida. Además, los retrasos introducidos son mínimos, lo cual los hace especialmente interesantes para aplicaciones en tiempo real, como la transmisión de audio y vídeo sobre Internet. Sin embargo, presentan muchos inconvenientes para el programador: cuando transmitimos un datagrama no tenemos la certeza de que este llegue a su destino, por lo que, si fuera necesario, tendríamos que implementar nuestro propio mecanismo de control de errores. Otro inconveniente es el hecho de que existe un tamaño máximo de datagrama: unos 1500 bytes dependiendo de la implementación. Si la información a enviar es mayor, tendremos que fraccionarla y enviar varios datagramas independientes. En el destino tendremos que concatenarlos en el orden correcto.

En conclusión, si deseas una comunicación libre de errores y sin preocupaciones para el programador, es más conveniente que utilices sockets stream. Es el tipo de sockets que utilizaremos en los siguientes ejemplos.

 

- Un ejemplo de un cliente / servidor de ECHO

 

El servicio ECHO suele estar instalado en el puerto 7 de máquinas Unix y permite comprobar que la máquina está operativa y que se puede establecer una conexión con dicha máquina.

El funcionamiento de un servidor ECHO es muy sencillo, cuando alguien se conecta espera que le envíe algo y le responde exactamente con la misma información recibida. El cliente actúa de forma contraria; envía datos al servidor y luego comprueba que los datos recibidos son idénticos a los transmitidos.

Ejercicio: Estudio del protocolo ECHO con el comando telnet.

El siguiente ejercicio nos muestra un truco para testear si un servidor que utiliza TCP funciona correctamente. Te recomendamos que lo utilices antes de realizar la programación del cliente. Por una parte, te permitirá asegurarte que tanto la conexión, como el servidor funcionan correctamente. Por otra parte, te asegurarás que has entendido correctamente el protocolo a implementar.

NOTA: Para que el ejemplo funcione en la dirección IP 158.42.146.127 ha de haber un servidor de ECHO en funcionamiento. En caso de no ser así, puedes reemplazar la IP por la de un servidor de ECHO en funcionamiento. También puedes implementar tu propio servidor de ECHO,  tal y como se muestra en uno de los siguientes ejercicios.

1.     Para verificar si el servidor de ECHO está en marcha,  desde un intérprete de comandos (símbolo del sistema/shell) escribe:

  telnet 158.42.146.127 7

Este comando permite establecer una conexión TCP con el puerto 7 del servidor. A partir de ahora todo lo que escribas se enviará al servidor (aunque en muchos clientes Telnet no se muestra en pantalla lo que escribes) y todo lo que el servidor envíe se imprimirá en pantalla.

 

NOTA: Windows 7 tiene desactivado por defecto el comando Telnet. Para habilitarlo, haz clic en el menú Inicio > Panel de control > Programas > Activar o desactiva las características de Windows y marca Cliente Telnet.

 

2.     Espera a que se establezca la conexión. De no ser posible, vuelve a intentarlo o lee la nota del principio del ejercicio.

3.     Escribe una frase cualquiera y pulsa <Intro>. Por ejemplo:

Hola hola

Si te equivocas no uses la tecla de borrar. En tal caso, repite el ejercicio desde el punto 3. Equivocarse en este protocolo no tiene ninguna repercusión, pero en el resto que vamos a estudiar hará que el servidor no nos entienda.

4.     Observa que la respuesta obtenida coincide con la frase que has introducido. Además el servidor cerrará inmediatamente la conexión y no te permitirá mandar más frases.

Ejercicio: Un cliente de ECHO

El siguiente ejemplo muestra cómo podrías desarrollar un cliente de ECHO que utiliza unsocket stream desde Android.

NOTA: Para que el ejemplo funcione en la dirección IP 158.42.146.127 ha de haber un servidor de ECHO en funcionamiento. En caso de no ser así, puedes reemplazar la IP por la de un servidor de ECHO en funcionamiento o realizar el siguiente ejercicio.

1.     Crea una nueva aplicación con los siguientes datos:

Application Name: Cliente ECHO

     Package Name: org.example.clienteecho

☑Phone and Tablet

Minimum SDK: API 9 Android 2.3 (Gingerbread)

Add an activity: Empty Activity

 

NOTA: Utilizamos como versión mínima la 2.3 para poder configurar StrictMode (se explica a continuación).

2.     Añade la etiqueta android:id="@+id/TextView01" en el TextView del layout de la actividad.

3.     Reemplaza el código de la actividad por:

public class MainActivity extends Activity {
   private TextView output;

   @Override
   public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      output = (TextView) findViewById(R.id.TextView01);
      StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
            .permitNetwork().build());
      ejecutaCliente();
   }

   private void ejecutaCliente() {
      String ip = "158.42.146.127";
      int puerto = 7;
      log(" socket " + ip + " " + puerto);
      try {
         Socket sk = new Socket(ip, puerto);
         BufferedReader entrada = new BufferedReader(
                  new InputStreamReader(sk.getInputStream()));
         PrintWriter salida = new PrintWriter(
                  new OutputStreamWriter(sk.getOutputStream()), true);
         log("enviando... Hola Mundo ");
         salida.println("Hola Mundo");
         log("recibiendo ... " + entrada.readLine());
         sk.close();
      } catch (Exception e) {
         log("error: " + e.toString());
      }
   }

   private void log(String string) {
      output.append(string + "\n");
   }
}


Las tres primeras líneas del método onCreate() han de resultarte familiares. En la cuarta se configura StrictMode[1]. Consiste en una herramienta de desarrollo que detecta cosas que podrías estar haciendo mal y te llama la atención para que las corrijas. Esta herramienta aparece en el nivel de API 9. Una de las verificaciones que realiza es que no se acceda a la red desde el hilo principal. Este problema se podría resolver lanzando un nuevo hilo para realizar el acceso al servidor[2]. Más adelante se muestra como resolverlo usando AsyncTask. En este apartado queremos centrarnos en el uso de sockets y no en el manejo de hilos, por lo que vamos a desactivar esta comprobación. En la línea en cuestión se indica a StrictMode que en su política dethreads no tenga en cuenta los accesos a la red.

La parte interesante se encuentra en el método ejecutarCliente(). En primer lugar, todo cliente ha de conocer la dirección del socket del servidor; en este caso los valores se indican en el par de variables ip y puerto. Nunca tenemos la certeza de que el servidor admita la conexión, por lo que es obligatorio utilizar una sección try / catch. La conexión propiamente dicha se realiza con el constructor de la clase Socket. Siempre hay que tener previsto que ocurra algún problema, en tal caso, se creará la excepción y se pasará a la sección catch. En caso contrario, continuaremos obteniendo el InputStream y el OutputStream asociado alsocket. Lo cual nos permitirá obtener las variables, entrada y salida mediante las que podremos recibir y transmitir información. El programa transmite la cadena "Hola Mundo", tras lo que visualiza la información recibida. Si todo es correcto, ha de coincidir con lo transmitido.

 

4.     Solicita en la aplicación el permiso INTERNET en AndroidManifest.

<uses-permission android:name="android.permission.INTERNET"/>

5.     Ejecuta la aplicación y verifica si el servidor responde. Recuerda que es posible que no se haya arrancado este servicio en el servidor.

6.   Comenta la línea de onCreate() donde se se configura StrictMode y ejecuta de nuevo la aplicación. El resultado no será satisfactorio. En este caso ocurrirá una excepción NetworkOnMainThreadException.

 

Ejercicio: Un servidor de ECHO

Vamos a implementar un servidor de ECHO en tu ordenador personal. Has de tener claro que no va a ser una aplicación Android, si no un programa 100% Java. 

 

1.     Crea un nuevo proyecto Java y llámalo ServidorECHO.

Crea un nuevo proyecto (File > New Project..). Utiliza la opción Add No Activity, así no creamos una actividad que nunca será usada. Pulsa en File > New Module. Selecciona Java Library y pulsa Next. Introduce en Library name: servidor, como Java pakage name: com.example.servidorecho y en Java class name: ServidorECHO. Pulsa el botón Finish. Se creará un nuevo módulo Java dentro de tu proyecto Android. Pulsa en el botón desplegable a la derecha del botón Run . Selecciona Edit Configurations... En la nueva ventana, haz clic en el signo + de la esquina superior izquierda y selecciona Application. Aparecerá una nueva configuración de aplicación. Selecciona en Name: servidor, en Main class: com.example.servidorecho.ServidorECHO y en Use classpath of module: servidor. Pulsa en OK.

2.     Reemplaza el código de ServidorECHO con el siguiente código:

public class ServidorECHO {
   public static void main(String args[]) {
      try {
         System.out.println("Servidor en marcha...");
         ServerSocket sk = new ServerSocket(7);
         while (true) {
            Socket cliente = sk.accept();
            BufferedReader entrada = new BufferedReader(
                  new InputStreamReader(cliente.getInputStream()));
            PrintWriter salida = new PrintWriter(new OutputStreamWriter(
                  cliente.getOutputStream()), true);
            String datos = entrada.readLine();
            salida.println(datos);
            cliente.close();
            System.out.println(datos);
         }
      } catch (IOException e) {
         System.out.println(e);
      }
   }
}

En este caso utilizaremos la clase ServerSocket asociado al puerto 7 para crear un socket que acepta conexiones. Luego, se introduce un bucle infinito para que el servidor esté perpetuamente en servicio. El método accept() bloquea al servidor hasta que un cliente se conecte. Cuando ocurra esto, todo el intercambio de información se realizará a través de un nuevo socket creado con este propósito, el socket cliente. El resto del código es similar al cliente, aunque en este caso primero se recibe y luego se transmite lo mismo que ha recibido.

 

3.     Sustituye la dirección IP en ClienteECHO por la IP de tu ordenador. El comando ipconfig (Windows) o ifconfig (Linux/Mac) te permite averiguar la dirección IP de tu ordenador. No utilices como IP 127.0.0.1 (localhost) dado que, aunque se ejecuten en la misma máquina, la IP del emulador es diferente a la del PC.

4.     Ejecuta primero el servidor y luego el cliente para verificar que funciona.

 

NOTA: Si la IP de tu ordenador es privada, no podrás crear un servidor accesible desde cualquier parte de Internet. En este caso utiliza para el cliente un emulador o un dispositivo Android real que se conecte por Wi-Fia la misma red de tu ordenador. De lo contrario, el cliente no encontrará el servidor.

 

[1] http://developer.android.com/reference/android/os/StrictMode.html

[2]En el capítulo 5 se describe este problema con más detalle

 

- Un servidor por sockets para las puntuaciones

 

Siguiendo la estructura básica de un cliente y un servidor TCP que acabamos de ver, va a resultar muy sencillo implementar un protocolo que permita a varios clientes conectarse a un servidor para consultar la lista de puntuaciones o mandar nuevas puntuaciones.El primer lugar, tenemos que diseñar un protocolo que permita realizar las dos operaciones.

Para consultar puntuaciones el cliente se conectará al servidor y le mandará los caracteres PUNTUACIONES (solo se permite en mayúsculas) seguido de un salto de línea. El servidor mandará todo el listado de puntuaciones, separadas por caracteres de salto de línea. A continuación, se cerrará la conexión.

Cliente:                     PUNTUACIONES

Servidor:                   19000 Pedro Perez

17500 María Suarez

13000 Juan García

 Para almacenar una nueva puntuación el cliente se conectará al servidor y mandará un texto con la puntuación obtenida, seguido de un salto de línea. El servidor lo reconocerá como una nueva puntuación siempre que este texto no sea PUNTUACIONES. En tal caso almacenará la puntuación y mandará los caracteres OK seguidos de un salto de línea. A continuación se cerrará la conexión.

Cliente:        32000 Eva Gutierrez

Servidor:       OK

En segundo lugar, hay que elegir un número de puerto para realizar la comunicación; por ejemplo el 1234.

Ejercicio paso a paso: Estudio del protocolo PUNTUACIONES utilizando el comando telnet.

El siguiente ejercicio nos muestra un truco para testear si un servidor que utiliza TCP funciona correctamente. Te recomendamos que lo utilices antes de realizar la programación del cliente. Por una parte, te permitirá asegurarte que tanto la conexión, como el servidor funcionan correctamente. Por otra parte, te asegurarás que has entendido correctamente el protocolo a implementar.

NOTA: Para que el ejemplo funcione en la dirección IP 158.42.146.127 ha de haber un servidor de PUNTUACIONES en funcionamiento. En caso de no ser así, implementar tu propio servidor de PUNTUACIONES,  tal y como se muestra en uno de los siguientes ejercicios.

1.     Para conectarse al servidor desde un intérprete de comandos (símbolo del sistema/shell) escribe:

  telnet 158.42.146.127 1234

2.     Si se establece la conexión, escribe un número seguido de tu nombre y pulsa <Intro>. Por ejemplo:

 14000 Juan García

3.     La respuesta obtenida ha de ser OK y luego se cerrará la conexión.

4.     Repite el punto 2 y tras la conexión escribe:

  PUNTUACIONES

5.     La respuesta obtenida ha de ser la lista de puntuaciones donde ha de estar la que acabas de introducir.

Ejercicio paso a paso: Almacenando las puntuaciones mediante un protocolo basado en sockets

1.     Crea un nuevo proyecto Java  (Java SE, no para Android) y llámalo ServidorPuntuacionesSocket. Este proyecto ha de contener la clase ServidorPuntuaciones. Instrucciones detalladas se muestran en el ejercicio Un servidor de ECHO.

2.     Reemplaza en código por el que se muestra a continuación:

public class ServidorPuntuaciones {

    public static void main(String args[]) {
      Vector<String> puntuaciones = new Vector<String>();

      try {
         ServerSocket s = new ServerSocket(1234);
         System.out.println("Esperando conexiones...");

         while (true) {
            Socket cliente = s.accept();
            BufferedReader entrada = new BufferedReader(
                  new InputStreamReader(cliente.getInputStream()));
            PrintWriter salida = new PrintWriter(new OutputStreamWriter(
                  cliente.getOutputStream()), true);
            String datos = entrada.readLine();
            if (datos.equals("PUNTUACIONES")) {
               for (int n = 0; n < puntuaciones.size(); n++) {
                  salida.println(puntuaciones.get(n));
               }
            } else {
               puntuaciones.add(0, datos);
               salida.println("OK");
            }
            cliente.close();
         }
      } catch (IOException e) {
         System.out.println(e);
      }
   }
}

 

3.     Ejecuta el proyecto

4.     Verifica que en la vista Run aparece: "Esperando conexiones..."

5.     Desde la vista Run podrás detener la aplicación pulsando en el cuadro rojo.

NOTA:Si ejecutas de nuevo la aplicación sin pararla primero, dará un error. Esto es debido a que la aplicación ya lanzada no es detenida y esta aplicación tiene asociado el puerto 1234. El sistema no permitirá que una nueva aplicación escuche este puerto.

 

6.     Abre el proyecto Asteroides y crea la siguiente clase:

public class AlmacenPuntuacionesSocket implements AlmacenPuntuaciones{

  public AlmacenPuntuacionesSocket() {
    StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.
            Builder().permitNetwork().build());
  }

  public void guardarPuntuacion(int puntos, String nombre, long fecha){
   try {
      Socket sk = new Socket("X.X.X.X", 1234);
      BufferedReader entrada = new BufferedReader(
                new InputStreamReader(sk.getInputStream()));
      PrintWriter salida = new PrintWriter(
                new OutputStreamWriter(sk.getOutputStream()),true);
      salida.println(puntos + " " + nombre);
      String respuesta = entrada.readLine();
      if (!respuesta.equals("OK")) {
         Log.e("Asteroides", "Error: respuesta de servidor incorrecta");
      }
      sk.close();
   } catch (Exception e) {
      Log.e("Asteroides", e.toString(), e);
   }
  }

  public Vector<String> listaPuntuaciones(int cantidad) {
   Vector<String> result = new Vector<String>();
   try {
      Socket sk = new Socket("X.X.X.X", 1234);
      BufferedReader entrada =    new BufferedReader(
             new InputStreamReader(sk.getInputStream()));
      PrintWriter salida = new PrintWriter(
                new OutputStreamWriter(sk.getOutputStream()),true);
      salida.println("PUNTUACIONES");
      int n = 0;
      String respuesta;
      do {
         respuesta = entrada.readLine();
         if (respuesta != null) {
            result.add(respuesta);
            n++;
         }
      } while (n < cantidad && respuesta != null);
      sk.close();
   } catch (Exception e) {
      Log.e("Asteroides", e.toString(), e);
   }
   return result;
  }
}

7.     Sustituiye las dos apariciones de "X.X.X.X" por la dirección IP donde esté ejecutándose el servidor. 

NOTA: El comando ipconfig (Windows) o ifconfig (Linux/Mac)te permite averiguar la dirección IP de tu ordenador. No utilices como IP 127.0.0.1 (localhost) dado que, aunque se ejecuten en la misma máquina, la IP del emulador es diferente a la del PC.

8.     Recuerda que ahora la aplicación Asteroides necesita el permiso INTERNET. Y tiene que ser compilada con una versión mínima 9 (para StrictMode).

9.  Ejecuta la aplicación y accede a visualizar la lista de puntuaciones. Luego inicia una partida nueva.

10.  Verifica que en la vista Consola aparecen las consultas al servidor.

11.  Para terminar, reemplaza la IP por la siguiente "158.42.146.127" para conectarte a un servidor compartido. 

NOTA: Es posible que este servicio no haya sido iniciado.

12.  Modifica el código correspondiente para que la nueva clase pueda ser seleccionada como almacén de las puntuaciones.    

 

13.  Comprueba si otros usuarios han accedido a este servidor y aparecen sus puntuaciones.

Preguntas de repaso: El interfaz socket

 

La web y el protocolo HTTP

Dentro del mundo de Internet destaca una aplicación que es, con mucho, la más utilizada: la World Wide Web (WWW), a la que nos referiremos coloquialmente como la web. Su gran éxito se debe a la facilidad de uso, dado que simplifica el acceso a todo tipo de información, y a que esta información es presentada de forma atractiva. Básicamente, la web nos ofrece un servicio de acceso a información distribuida en miles de servidores en todo Internet, que nos permite ir navegando por todo tipo de documentos multimedia gracias a un sencillo sistema de hipervínculos.

Para la comunicación entre los clientes y los servidores de esta aplicación, se emplea el protocolo HTTP (Hypertext Transfer Protocol), que será el objeto de estudio de este apartado.

- El protocolo HTTP

HTTP es un sencillo protocolo cliente-servidor que articula los intercambios de información entre los navegadores web y los servidores web. Fue propuesto por Tim Berners-Lee, atendiendo a las necesidades de un sistema global de distribución de información como la World Wide Web. En la web los servidores han de escuchar en el puerto 80, esperando la conexión de algún cliente web.

 

  video[Tutorial] El protocolo HTTP

A continuación describimos los pasos habituales que se siguen en una interacción del protocolo:

1)   El usuario quiere acceder a la página http://www.upv.es/dir/pag.html. Para lo cual pincha en un enlace de un documento HTML o introduciendo directamente en el campo Dirección del navegador Web.

2)   El navegador averigua la dirección IP de www.upv.es.

3)   El navegador establece una conexión con el puerto 80 de esta IP.

4)   El navegador envía por esta conexión (¿ carácter de salto de línea):

GET /dir/pag.html

5)   El servidor envía la página a través de la conexión:

    <HTML>

         <HEAD>

         <TITLE>Página de ... </TITLE>

         ...

         </HTML>

 

6)   El servidor cierra la conexión.

Este proceso se repite cada vez que el navegador necesita un fichero del servidor. Por ejemplo, si se ha bajado un documento HTML en cuyo interior están insertadas cuatro imágenes, el proceso anterior se repite un total de cinco veces, una para el documento HTML y cuatro para las imágenes.

Como ves, se trata de un protocolo sin estado. Cada petición contiene la información necesaria para ser atendida. Si deseamos mantener un estado tendrá que ser implementado usando algún mecanismo adicional (por ejemplo las cookies).

Ejercicio paso a paso: Estudio del protocolo HTTP/0.9 utilizando el comando Telnet.

1.     Abre un navegador web y accede a la página:

http://www.dcomg.upv.es/~jtomas/corta.html

En caso de que el servidor no responda, puedes realizar el ejercicio con cualquier página de otro servidor. El carácter ~ se obtiene pulsando simultáneamente <Alt Gr> y <4>.

2.     Visualiza el contenido HTML de la página (menú “Ver/Código fuente”, “Herramientas/ Ver código fuente”, o similar).

3.     Desde un intérprete de comandos (símbolo del sistema/shell) escribe:

telnet www.dcomg.upv.es 80

Este comando permite establecer una conexión TCP con el puerto 80 del servidor. A partir de ahora todo lo que escribas será enviado al servidor (aunque en mucho casos no lo veas en pantalla) y todo lo que el servidor envíe será impreso en pantalla..

NOTA: Si utilizas un servidor diferente, asegúrate de que soporta la versión HTTP/0.9.

 

4.     Cuando se establezca la conexión teclea exactamente:

GET /~jtomas/corta.html

Si te equivocas no uses la tecla de borrar. En tal caso, repite el ejercicio desde el punto 3.

5.   Observa que la respuesta obtenida coincide con el contenido HTML del paso 2.

 

- Versión 1.0 del protocolo HTTP

Con la popularización de la aplicación WWW, pronto se vio la necesidad de ampliar este sencillo protocolo para permitir nuevas funcionalidades. Se definió la versión 1.0 del protocolo que añadía nuevos métodos (PUT, POST,..) además de permitir el intercambio de cabeceras entre cliente y servidor.

  video[Tutorial] El protocolo HTTP v1.0

 

A continuación se muestra un ejemplo de interacción para la versión 1.0:

Cliente: GET /dir/pag.html HTTP/1.0

                  User-Agent: Internet Explorer v3.2

                  Host: www.upv.es

                  Accept: text/html, image/gif, image/jpeg

                                                 <línea en blanco>

Servidor:HTTP/1.1 200 OK

                  Server: Microsoft-IIS/5.0

         Last-Modified: Mon, 25 Feb 2002 15:49:22 GMT

         Content-Type: text/html

                                                 <línea en blanco>

                <HTML>

                  <HEAD>

                  <TITLE>Página de ... </TITLE>

                  ...

                  </HTML>

En esta nueva versión, el navegador se conecta al puerto 80 del servidor y normalmente le envía el comando “GET” seguido de la página que desea obtener. Ahora el navegador añadirá la palabra “HTTP/1.0” para indicar al servidor que quiere utilizar esta nueva versión del protocolo. A continuación, el navegador introducirá un salto de línea seguido, opcionalmente, de alguna cabecera (véase RFC 2616). Una cabecera consta de un identificador de cabecera, seguido de dos puntos y el valor de la cabecera, más un salto de línea (¿). Estas cabeceras permitirán que el navegador indique al servidor ciertos datos que pueden serle de utilidad. Por ejemplo, con el identificador de cabecera “User-Agent:” se puede indicar el tipo y la versión de navegador con el que se realiza la solicitud. “Host:” permite indicar el ordenador donde se ejecuta el servidor. O la cabecera “Accept:”, que permite indicar al servidor qué tipo de documentos es capaz de visualizar el navegador en formato MIME. Cuando el navegador ya no quiera insertar más cabeceras, introducirá una línea en blanco. Es decir, tras el salto de línea de la última cabecera, manda un nuevo salto de línea.

Cuando el servidor lea una línea en blanco, es decir, dos saltos de línea seguidos, sabrá que le ha llegado su turno y tiene que contestar. En esta versión del protocolo el servidor no transmitirá la página solicitada directamente. Antes contesta con una línea indicando: primero la versión más alta que soporta (normalmente “HTTP/1.1”), a continuación un espacio y un código de tres dígitos que informa de si se puede realizar la operación solicitada, finalizando con una frase explicativa sobre este código. Algunos códigos de respuesta posibles son: 200 OK, 401 no autorizado, 404 fichero no encontrado, etc. Tras esta primera línea el servidor podrá enviar alguna cabecera con información sobre el servidor o el documento que va a transmitir. Cuando ya no quiera insertar más cabeceras introducirá una línea en blanco seguida del documento.

Aunque el método GET es el más utilizado, en la versión 1.0 se añaden nuevos métodos. A continuación se incluye una descripción:

GET:                 Petición de lectura de un recurso.

POST:              Envío de información asociada a un recurso del servidor.

PUT:                 Creación de un nuevo recurso en el servidor.

DELETE:          Eliminación de un recurso.

HEAD:              El servidor solo transmitirá las cabeceras, no la página

Ejercicio paso a paso: Estudio del protocolo HTTP v1.0 utilizando el comando telnet.

1.     Desde un intérprete de comandos (símbolo del sistema/shell) escribe:

telnet www.dcomg.upv.es 80

2.     Cuando se establezca la conexión teclea:

 GET /~jtomas/corta.html HTTP/1.0

             

3.     Observa que la respuesta obtenida es similar al ejercicio anterior, pero ahora el servidor ha incluido cabeceras. ¿Qué información puedes sacar de estas cabeceras? ¿Por qué has tenido que introducir dos saltos de línea?

4.     Repite el ejercicio utilizando el comando HEAD.

Aunque la versión más reciente es HTTP/1.2 (y existe un borrador de HTTP/2.0), la versión más utilizada en la actualidad es HTTP/1.1. Incorpora algunas mejoras, como las conexiones persistentes, activadas por defecto o la gestión de la caché del cliente. También permite al cliente enviar múltiples peticiones a la vez.

 

- Utilizando HTTP desde Android

 

Tras el gran éxito de la web, el protocolo HTTP está siendo utilizado con finalidades diferentes de las que tenía en un principio: la descarga de páginas web. Por ejemplo, hoy en día es frecuente su uso para el intercambio de ficheros, la emisión de vídeo o la comunicación entre aplicaciones. A continuación describiremos las herramientas disponibles en Android para utilizar el protocolo HTTP. Para este propósito tenemos dos alternativas principales desde Android: el uso de las librerías java.net.* o org.apache.commons.httpclient.*. En el siguiente ejemplo utilizaremos las primeras.

En el ejemplo de este apartado vamos a extraer información de una de las páginas web más utilizadas en la actualidad: el servicio de búsqueda de Google. En concreto, nos interesa conocer el número de apariciones de una determinada secuencia de palabras en la web. En ciertas ocasiones esta información puede resultar muy interesante. Por ejemplo, tenemos dudas sobre el uso de una preposición en inglés: ¿se escribe travel in bus o travel by bus? Si buscamos ambas secuencias de palabras en Google, hay que ponerlas entre comillas para que busque la secuencia de forma literal. Obtenemos 240.000 apariciones para la primera y 1,1 millones para la segunda. Nuestra duda ha sido resuelta. Esta aplicación también puede utilizarse para averiguar quién es más popular en Internet, Antonio Banderas o Rafael Nadal.

 

Básicamente la aplicación que mostramos a continuación funciona de la siguiente forma:

1)   El usuario introduce una secuencia de palabras, por ejemplo “Antonio Banderas”.

2)   Accedemos al servidor mediante la siguiente URL:

http://www.google.es/search?hl=es&q=”Antonio+Banderas”

En este caso las peticiones se atienten mediante el método GET. En este método si queremos enviar información al servidor hemos de incluirla tras un carácter “?” seguido de un nombre de parámetro seguido del carácter “=” seguido del valor. Los diferentes parámetros se separan mediante el carácter “&”. Los espacios en blanco han de ser sustituidos por el carácter “+”. En este ejemplo el parámetro hl corresponde al idioma de búsqueda y q a las palabras a buscar.  

3)   Obtenemos la respuesta del servidor en una variable de tipo string.

4)   Buscamos la primera aparición de “Aproximadamente” en la respuesta.

5)   Tras esta palabra se encuentra la información que buscamos.

 Obviamente, el correcto funcionamiento de esta aplicación está sujeto a que no se produzcan cambios en la forma en que Google visualiza los resultados. En caso de que se realice algún cambio en esta página, va a ser necesaria una adaptación de nuestra aplicación. La técnica de sacar información directamente de una página web se conoce como web scraping. Hoy en día existe otra alternativa más fiable para obtener información. En el siguiente apartado mostraremos cómo utilizar un servicio web con este propósito.

Ejercicio paso a paso: Búsquedas en Google con HTTP 

1.     Crea un  nuevo proyecto con los siguientes datos:

Application name: HTTP

Package name: com.example.http

☑Phone and Tablet

Minimum SDK: API 9 Android 2.3 (Gingerbread)

Add an activity: Empty Activity

NOTA: Utilizamos la versión mínima la 2.3 para poder configurar StrictMode

2.     La aplicación ha de solicitar el permiso de acceso a Internet, añadiendo en AndroidManifest.xml la siguiente línea:

<uses-permission android:name="android.permission.INTERNET"/>

 

3.     Reemplaza el código del Layout activity_main.xml por:

<LinearLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:orientation="vertical"
   android:layout_width="match_parent"
   android:layout_height="match_parent">
   <EditText android:id="@+id/EditText01"
      android:layout_height="wrap_content"
      android:layout_width="match_parent"
      android:text="palabra a buscar"/>
   <Button android:id="@+id/Button01"
      android:layout_height="wrap_content"
      android:layout_width="wrap_content"
      android:onClick="buscar"
      android:text="buscar en Google"/>
   <TextView android:id="@+id/TextView01"
      android:layout_height="match_parent"
      android:layout_width="match_parent"
         android:textSize="8pt"/>
</LinearLayout>

 

La apariencia de este Layout se muestra a continuación:

 

4.     Reemplaza el código de MainActivity.java por:

public class MainActivity extends Activity {
   private EditText entrada;
   private TextView salida;

   @Override
   public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      entrada = (EditText) findViewById(R.id.EditText01);
      salida = (TextView) findViewById(R.id.TextView01);
      StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.
            Builder().permitNetwork().build());
   }

   public void buscar(View view){
      try {
         String palabras = entrada.getText().toString();
         String resultado = resultadosGoogle(palabras);
         salida.append(palabras + "--" + resultado + "\n");
      } catch (Exception e) {
         salida.append("Error al conectar\n");
         Log.e("HTTP", e.getMessage(), e);
      }      
   }
   
   String resultadosGoogle(String palabras) throws Exception {
      String pagina = "", devuelve = "";
      URL url = new URL("http://www.google.es/search?hl=es&q=\""
            + URLEncoder.encode(palabras, "UTF-8") + "\"");
      HttpURLConnection conexion = (HttpURLConnection)
            url.openConnection();
      conexion.setRequestProperty("User-Agent",
            "Mozilla/5.0 (Windows NT 6.1)");
      if (conexion.getResponseCode()==HttpURLConnection.HTTP_OK) {
         BufferedReader reader = new BufferedReader(new
            InputStreamReader(conexion.getInputStream()));
         String linea = reader.readLine();
         while (linea != null) {
            pagina += linea;
            linea = reader.readLine();
         }
         reader.close();
         devuelve = buscaAproximadamente(pagina);
      } else {
         devuelve = "ERROR: " + conexion.getResponseMessage();
      }
      conexion.disconnect();
      return devuelve;
   }

   String buscaAproximadamente(String pagina){
      int ini = pagina.indexOf("Aproximadamente");
      if (ini != -1) {
         int fin = pagina.indexOf(" ", ini + 16);
         return pagina.substring(ini + 16, fin);
      } else {
         return "no encontrado";
      }
   }

}

 

El comienzo del código ha de resultarte familiar hasta la última línea del método onCreate(). En esta línea se configura StrictMode[1]. para que permita  accesos a la red desde el hilo principal.

Pasemos a describir el método resultadosGoogle(). Este método toma como entrada una secuencia de palabras y devuelve el número de veces que Google las ha encontrado en alguna página web. Lo primero que llama la atención es el modificador throws Exception. Estamos obligados a incluirlo si utilizamos la clase HttpURLConnection. Este modificador obliga a utilizar el método dentro de una sección try … catch …. La razón de esto es que toda conexión HTTP es susceptible de no poder realizarse, por lo que tenemos la obligación de tomar las acciones pertinentes en caso de problemas.

Tras la declaración de variables, creamos la URL que utilizaremos para la conexión. El método URLEncoder.encode() se encargará de codificar las palabras en el formato esperado por el servidor. Entre otras cosas reemplaza espacios en blanco, caracteres no ASCII, etc. A continuación, preparamos la conexión por medio de la clase HttpURLConnection. Mediante el método setRequestProperty() podemos añadir cabeceras HTTP. En el punto 7 de este ejercicio se demuestra la necesidad de insertar la cabecera User-Agent.

En la siguiente línea se utiliza el método getResponseCode() para establecer la conexión. Si se establece sin problemas (HTTP_OK) leemos la respuesta línea a línea, concatenándola en el string pagina. El método buscaAproximadamente() busca en pagina la primera aparición de la palabra “Aproximadamente”. Si la encontramos, buscamos el primer espacio a continuación del número que ha de aparecer tras “Aproximadamente” y devolvemos los caracteres entre ambas posiciones. En caso de no encontrar “Aproximadamente”, puede que la secuencia buscada no se haya encontrado, aunque también es posible que Google haya cambiado la forma de devolver los resultados. En el caso de que la conexión no haya sido satisfactoria, devolvemos el mensaje de respuesta que nos dio el servidor getResponseMessage().

5.     Ejecuta la aplicación y verifica que funciona correctamente.

6.    En el código anterior comenta la línea:

conexion.setRequestProperty("User-Agent", ...);

7.     Ejecuta el programa. Ahora el resultado ha de ser:

ERROR: Forbidden

En este caso, al tratar de establecer la conexión el servidor, en lugar de devolvernos el código de respuesta 200: OK, nos ha devuelto el código 403: Forbidden. ¿Por qué? La cabecera User-Agent informa al servidor de qué tipo de cliente ha establecido la conexión. Según se demuestra en este ejercicio, el servicio de búsquedas de Google prohíbe la respuesta a aquellos clientes que no se identifican.

 

8.     Analiza el valor asignado a la cabecera “Mozilla/4.0 ...”. Puedes comprobar que la información que estamos dando al servidor es totalmente errónea.

9.     Modifica este valor por “Ejemplo de El gran libro de Android” y comprueba el resultado es 403: Forbidden ¿Por qué no quiere responder? La respuesta es que desde finales de 2011, el servidor de Google exige que el tipo de navegador que se conecte sea conocido.