Vamos a implementar un servidor de HTTP básico, sólo
le permitiremos admitir operaciones GET y un rango limitado de
tipos MIME codificados. Los tipos MIME son los descriptores
de tipo para contenido multimedia. Esperamos que este ejemplo
sirva como base para un entretenido ejercicio de ampliación
y exploración porque, desde luego, lo que no pretendemos
es inquietar a los Consejos de Dirección de Microsoft o
Netscape.
La aplicación va a crear un ServerSocket conectado
al puerto 80, que en caso de no tener privilegios para su uso,
podemos cambiar, por ejemplo al 8080; y después entra en
un bucle infinito. Dentro del bucle, espera dentro del método
accept() del ServerSocket hasta que se establece una conexión
cliente. Después asigna un flujo de entrada y salida al
socket. A continuación lee la solicitud del cliente utilizando
el método getRawRequest(), que devolverá
un null si hay un error de entrada/salida o el cliente
corta la conexión. Luego se identifica el tipo de solicitud
y se gestiona mediante el método handlget() o handleUnsup().
Finalmente se cierran los sockets y se comienza de nuevo.
Cuando se ejecuta el programa completo, se escribe en pantalla
lo que el navegador cliente envía al servidor. Aunque se
capturan varias condiciones de error, en la práctica no
aparecen. El ampliar este servidor para que soporte una carga
de millones de visitas al día requiere bastante trabajo;
no obstante, en el ordenador en que estoy escribiendo esto, no
se enlenteció demasiado con una carga de hasta diez entradas
por segundo, lo que permitiría alrededor de un millón
de visitas al día. Se podría mejorar mediante el
uso de threads y control de la memoria caché para gestionar
esas visitas, pero eso ya forma parte del ejercicio sobre el que
se puede trabajar.
El código fuente de nuestro mini servidor de HTTP se encuentra
en el fichero TutHttp.java, que reproducimos a continuación:
import java.net.*; import java.io.*; import java.util.*; // Clase de utilidades donde declaramos los tipos MIME y algunos gestores // de los errores que se pueden generar en HTML class HttpUtilidades { final static String version = "1.0"; final static String mime_text_plain = "text/plain"; final static String mime_text_html = "text/html"; final static String mime_image_gif = "image/gif"; final static String mime_image_jpg = "image/jpg"; final static String mime_app_os = "application/octet-stream"; final static String CRLF = "\r\n"; // Método que convierte un objeto String en una matriz de bytes. // Java gestiona las cadenas como objetos, por lo que es necesario // convertir las matrices de bytes que se obtienen a Strings y // viceversa public static byte aBytes( String s )[] { byte b[] = new byte[ s.length() ]; s.getBytes( 0,b.length,b,0 ); return( b ); } // Este método concatena dos matrices de bytes. El método // arraycopy() asombra por su rapidez public static byte concatenarBytes( byte a[],byte b[] )[] { byte ret[] = new byte[ a.length+b.length ]; System.arraycopy( a,0,ret,0,a.length ); System.arraycopy( b,0,ret,a.length,b.length ); return( ret ); } // Este método toma un tipo de contenido y una longitud, para // devolver la matriz de bytes que contiene el mensaje de cabecera // MIME con formato public static byte cabMime( String ct,int tam )[] { return( cabMime( 200,"OK",ct,tam ) ); } // Es el mismo método anterior, pero permite un ajuste más fino // del código que se devuelve y el mensaje de error de HTTP public static byte cabMime(int codigo,String mensaje,String ct, int tam )[] { Date d = new Date(); return( aBytes( "HTTP/1.0 "+codigo+" "+mensaje+CRLF+ "Date: "+d.toGMTString()+CRLF+ "Server: Java/"+version +CRLF+ "Content-type: "+ct+CRLF+ ( tam > 0 ? "Content-length: "+tam+CRLF : "" )+CRLF ) ); } // Este método construye un mensaje HTML con un formato decente // para presentar una condición de error y lo devuelve como // matriz de bytes public static byte error( int codigo,String msg,String fname)[] { String ret = "<BODY>"+CRLF+"<H1>"+codigo+" "+msg+"</H1>"+CRLF; if( fname != null ) ret += "Error al buscar el URL: "+fname+CRLF; ret += "</BODY>"+CRLF; byte tmp[] = cabMime( codigo,msg,mime_text_html,0 ); return( concatenarBytes( tmp,aBytes( ret ) ) ); } // Devuelve el tipo MIME que corresponde a un nombre de archivo dado public static String mimeTypeString( String fichero ) { String tipo; if( fichero.endsWith( ".html" ) || fichero.endsWith( ".htm" ) ) tipo = mime_text_html; else if( fichero.endsWith( ".class" ) ) tipo = mime_app_os; else if( fichero.endsWith( ".gif" ) ) tipo = mime_image_gif; else if( fichero.endsWith( ".jpg" ) ) tipo = mime_image_jpg; else tipo = mime_text_plain; return( tipo ); } } // Esta clase sirve para que nos enteremos de lo que está haciendo // nuestro servidor. En una implementación real, todos estos mensajes // deberían registrarse en algún fichero class HTTPlog { public static void error( String entrada ) { System.out.println( "Error: "+entrada ); } public static void peticion( String peticion ) { System.out.println( peticion ); } } // Esta es la clase principal de nuestro servidor Http class TutHttp { public static final int puerto = 80; final static String docRaiz = "/html"; final static String fichIndice = "index.html"; final static int buffer = 2048; public static final int RT_GET=1; public static final int RT_UNSUP=2; public static final int RT_END=4; // Indica que la petición no está soportada, por ejemplo POST y HEAD private static void ctrlNoSop(String peticion,OutputStream sout) { HTTPlog.error( "Peticion no soportada: "+peticion ); } // Este método analiza gramaticalmente la solicitud enviada con el // GET y la descompone en sus partes para extraer el nombre del // archivo que se está solicitando. Entonces lee el fichero que // se pide private static void ctrlGet( String peticion,OutputStream sout ) { int fsp = peticion.indexOf( ' ' ); int nsp = peticion.indexOf( ' ',fsp+1 ); String fich = peticion.substring( fsp+1,nsp ); fich = docRaiz+fich+( fich.endsWith("/") ? fichIndice : "" ); try { File f = new File( fich ); if( !f.exists() ) { sout.write( HttpUtilidades.error( 404, "No Encontrado",fich ) ); return; } if( !f.canRead() ) { sout.write( HttpUtilidades.error( 404, "Permiso Denegado",fich ) ); return; } // Ahora lee el fichero que se ha solicitado InputStream sin = new FileInputStream( f ); String cabmime = HttpUtilidades.mimeTypeString( fich ); int n = sin.available(); sout.write( HttpUtilidades.cabMime( cabmime,n ) ); byte buf[] = new byte[buffer]; while( ( n = sin.read( buf ) ) >= 0 ) sout.write( buf,0,n ); sin.close(); } catch( IOException e ) { HTTPlog.error( "Excepcion: "+e ); } } // Devuelve la cabecera de la solicitud completa del cliente al // método main de nuestro servidor private static String getPeticion( InputStream sin ) { try { byte buf[] = new byte[buffer]; boolean esCR = false; int pos = 0; int c; while( ( c = sin.read() ) != -1 ) { switch( c ) { case '\r': break; case '\n': if( esCR ) return( new String( buf,0,0,pos ) ); esCR = true; // Continúa, se ha puesto el primer \n en la cadena default: if( c != '\n' ) esCR = false; buf[pos++] = (byte)c; } } } catch( IOException e ) { HTTPlog.error( "Error de Recepcion" ); } return( null ); } private static int tipoPeticion( String peticion ) { return( peticion.regionMatches( true,0,"get ",0,4 ) ? RT_GET : RT_UNSUP ); } // Función principal de nuestro servidor, que se conecta al socket // y se embucla indefinidamente public static void main( String args[] ) throws Exception { ServerSocket ss = new ServerSocket( puerto ); while( true ) { String peticion; Socket s = ss.accept(); OutputStream sOut = s.getOutputStream(); InputStream sIn = s.getInputStream(); if( ( peticion = getPeticion( sIn ) ) != null ) { switch( tipoPeticion( peticion ) ) { case RT_GET: ctrlGet( peticion,sOut ); break; case RT_UNSUP: default: ctrlNoSop( peticion,sOut ); break; } HTTPlog.peticion( peticion ); } sIn.close(); sOut.close(); s.close(); } } }
|
[Anterior] [Indice] [Siguiente] |