Interfaces Web com Java HttpServer

Cada vez surgem mais aplicações de desktop com interfaces web, que permitem o controlo da aplicação por um simples browser usando o protocolo HTTP. Bom exemplo disso são as aplicações P2P, como o conhecido cliente de bit torrent Azureus, todas as quais actualmente com esse suporte.

Com o Java 6 esta tarefa foi bastante simplificada, pois esta versão vem com a API HttpServer, que fornece os mecanismos básicos de um servidor HTTP. Iremos ver neste artigo como usar algumas dessas funcionalidades. Vamos para isso construir um leitor de áudio simples, apenas com os comandos reprodução e parar, e criaremos para isso um interface web, usando a API fornecida pelo Java para esse efeito.

Para começar vamos fazer o nosso pequeno player. Para tal vamos implementar a classe Player como podemos ver:

import java.applet.Applet;
import java.applet.AudioClip;
import java.io.File;
public class Player{
 
   private AudioClip som;
   protected boolean loading;
 
   public Player (String src) throws Exception{
       this.som = Applet.newAudioClip (new File (src).toURI ().toURL ());
       this.loading = false;
   }
 
   public void play (){
       this.som.play ();
       this.loading = true;
   }
 
   public void stop (){
       this.som.stop ();
       this.loading = false;
   }
 
   public String estado (){
       if(loading){
           return "Ligado";
       }
       return "Desligado";
   }
}

Como podemos ver, trata-se de uma classe bastante simples. Na linha 6 temos a variável com o clip de som, e na linha 7 uma variável booleana que nos indica se a musica está a tocar ou não. Em seguida, nas linhas 9 a 12, temos o construtor da nossa classe, que recebe uma String com o caminho para o ficheiro, inicializa o objecto AudioClip e atribui o valor false à variável loading.

Depois criamos os métodos reproduzir(play) (linhas 14 a 17), parar(stop) (linhas 19 a 22) e estado (linhas 24 a 19), que nos vão permitir iniciar a música, pará-la e saber se esta está a tocar ou não, e assim temos o mini player que usaremos no nosso exemplo.

import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
 
public class ClienteHttp{
 
  protected static Player player;
 
   public static void main (String [] args) {
       try{
           player = new Player ("/home/magician/Sample.wav");
 
           InetSocketAddress addr = new InetSocketAddress (8080);
           HttpServer server = HttpServer.create (addr, 0);
           server.createContext ("/", new PlayerHandler ());
           server.setExecutor (Executors.newCachedThreadPool ());
           server.start ();
           System.out.println ("Servidor na porta 8080");
 
       } catch(Exception e){
           e.printStackTrace ();
       }
   }
}

Agora temos a nossa classe principal, ou seja, aquela que irá fazer a ligação entre o player e o nosso mecanismo de interface web. Esta classe basicamente será a classe que irá conter o método main, e que vai inicializar o nosso Player (linha 12). Atenção que este player apenas irá abrir ficheiros wav, pois trata-se de um sistema simples e o nosso objectivo também não é o de criar um derradeiro player, mas sim o de criar uma interface web para essa aplicação. As linhas seguintes servem para inicializar o nosso HttpServer. Na linha 14 inicializamos um objecto InetSocketAddress para a porta 8080 (aqui poderíamos usar qualquer outra porta livre no sistema). Agora vamos criar um objecto HttpServer, que irá ser o nosso servidor de HTTP propriamente dito, e para isso vamos usar o InetSocketAddress criado anteriormente (linha 15). Na linha 16 vamos dizer ao nosso servidor que irá trabalhar no contexto /, assim para aceder às nossas páginas iremos usar por exemplo http://localhost/pagina, caso o nosso contexto fosse /player então teríamos de usar o endereço http://localhost/player/pagina. Para além do caminho do contexto, iremos definir o que irá ser executado quando esse caminho for acedido. Neste caso irá ser chamada a classe PlayerHandler, que iremos ver mais para a frente. É possível criar vários contextos para um servidor, mas neste caso usaremos apenas um. Na linha seguinte (linha 17) definimos o Executer para o nosso servidor. Iremos usar o Executer CachedThreadPool que executa cada um dos pedidos numa thread separada, este método deve sempre ser definido antes do método start (linha 18), para que o servidor saiba como processar os pedidos.

Por fim vamos ver a nossa classe PlayerHandler, que irá ser usada para processar os pedido feitos ao servidor e retornar a resposta correspondente sob a forma de HTML.

import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
 
public class PlayerHandler implements HttpHandler{
 
   public void handle (HttpExchange exchange) throws IOException{
 
       String request = exchange.getRequestMethod ();
 
       if ( request.equalsIgnoreCase ("GET") || request.equalsIgnoreCase ("POST") ){
 
           Headers responseHeaders = exchange.getResponseHeaders ();
           responseHeaders.set ("Content-Type", "text/html");
           exchange.sendResponseHeaders (200, 0);
 
           if( exchange.getRequestURI ().toString ().endsWith ("index.javax") ){
               String output = "<html>" +
                       "<head>" +
                       "<title>HTTP Player</title>" +
                       "</head>" +
                       "<body>" +
                       "<H1>HTTP Player Control</H1>" +
                       "<BR />" +
                       "<B>Estado</B> : " + ClienteHttp.player.estado () +
                       "<BR /> <BR />";
 
               if(ClienteHttp.player.loading){
                   output = output + "<form method="post" action="stop.javax">" +
                           "<input type="submit" value="Desligar"> " +
                           "</form>";
               }
               else{
                   output = output + "<form method="post" action="play.javax">" +
                           "<input type="submit" value="Ligar"> " +
                           "</form>";
               }
 
               output = output + "</body>" + "</html>";
 
               OutputStream response = exchange.getResponseBody ();
 
               response.write (output.getBytes ());
               response.close ();
           }
 
       else if( exchange.getRequestURI ().toString ().endsWith ("play.javax") ){
               String output = "<html>" +
                       "<head>" +
                       "<title>HTTP Player</title>" +
                      "<meta http-equiv="refresh" content="2; url=./index.javax" />" +
                      "</head>" +
                       "<body>" +
                       "A ligar ... <BR />" +
                       "A voltar em 2 segundos ...";
 
               output = output + "</body>" + "</html>";
 
               ClienteHttp.player.play ();
 
               OutputStream response = exchange.getResponseBody ();
 
               response.write (output.getBytes ());
               response.close ();
       }
 
            else if( exchange.getRequestURI ().toString ().endsWith ("stop.javax") ){
               String output = "<html>" +
                       "<head>" +
                       "<title>HTTP Player</title>" +
                       "<meta http-equiv="refresh" content="2; url=./index.javax" />" +
                       "</head>" +
                       "<body>" +
                       "A desligar ... <BR />" +
                       "A voltar em 2 segundos ...";
 
               output = output + "</body>" + "</html>";
 
               ClienteHttp.player.stop ();
 
               OutputStream response = exchange.getResponseBody ();
 
               response.write (output.getBytes ());
               response.close ();
           }
       }
   }
}

Esta classe irá implementar o interface HttpHandler (linha 8), que contém o método handle (linha 10). Este método será chamado para processar os pedidos HTTP feitos ao servidor, ou seja, será neste método que vamos colocar o que o nosso servidor deve fazer a cada pedido.

Comecemos por definir que tipos de pedido serão aceites pelo servidor: neste caso GET e POST (linha 14). Poderíamos separar os dois de forma a optimizar o processamento dos pedidos, mas neste caso, como apenas vamos usar GET o POST, é apenas uma mera formalidade. Em seguida iremos definir o tipo de resposta enviada, que neste caso será HTML (linha 17) e o código HTTP (linha 18).

O próximo passo é fazer a verificação da página pedida, no nosso caso iremos aceitar pedidos para as páginas index.javax (linha 20), play.javax (linha 50) e stop.javax (linha 70). Estes nomes podem ser quaisquer outros. Usamos .javax, mas podemos usar .php, .asp, .x, .txt ou qualquer outro nome que queiram dar, até mesmo apenas um nome como index sem o ponto.

Em cada um dos ifs iremos criar uma string dinamicamente com o código HTML que será retornado para o cliente. Em seguida convertemos a string para um array de bytes e enviamos ao cliente, e por fim fechamos a resposta (linhas 46, 47, 66, 67 e 86, 87).

Assim chegamos ao fim do nosso programa. Depois de o executar, basta ir a http://localhost:8080/index.javax e, se tudo correr correctamente, irá aparecer uma página HTML que irá permitir as operações básicas para o nosso player. Esta nova API do Java 6 permite ainda fazer muitas outras coisas como autenticação HTTP, protocolo HTTPS e filtragem de pedidos.

Podem ter acesso a toda API em http://java.sun.com/javase/6/docs/jre/api/net/httpserver/spec/com/sun/net/httpserver/package-summary.html. Esta API contém alguns exemplos sob forma de comentário, bem como uma boa explicação sobre como usar e como funciona o mecanismo de HttpServer do Java.

Publicado na edição 11 (PDF) da Revista PROGRAMAR.