Los servelts tienen la ventaja sobre otras tecnologías que de están compilados, tienen capacidad de threads interna, y proporcionan un entorno de programación seguro. Incluso las sites web que antes no proporcionaban soporte para servlets, pueden hacerlo ahora usando programas como JRun o el módulo Java para el servidor Web Apache.
La aplicación subastas basada en web usa un servelt para aceptar y procesar entradas del comprador y vendedor a través del navegador y devuelve dinámicamente información sobre el ítem de la subasta hacia el navegador. El programa AuctionServlet se creo extendiendo la clase HttpServlet. Esta clase proporciona un marco de trabajo para manejar peticiones y respuestas HTTP.
Esta sección examina el AuctionServlet e incluye información sobre cómo usar objetos Cookie y Session en un servlet.
public class AuctionServlet extends HttpServlet {
Un servlet puede cargarse cuando se arranca el servidor web o cuando se solicita una petición HTTP a una URL que especifica el servlet. El servlet normalmente es cargado mediante un cargador de clases separado en el sevidor web porque esto permite que el servlet sea recargado descargando el cargador de clases que cargo la clase servlet. Sin embargo, si el servlet depende de otras clases y una de estas clases cambia, necesitaremos actualiza el sello de la fecha del servlet para recargarlo.
Después de cargar un servlet, el primer estado en su ciclo de vida es la llamada a su método init por parte del servidor web. Una vez cargado e inicializado, el siguiente estado en el ciclo de vida del servlet es para servir peticiones. El servlet sirve peticiones a través de las implementaciones de su métodos service, doGet, o doPost.
El servlet puede opcionalmente implementar un método destroy para realizar operaciones de limpieza antes de que el servidor web descarge el servlet.
El objeto ServletConfig es usado para acceder a la información mantenida por el servidor web incluyendo valores del parámetro initArgs en el fichero de propiedades del servlet. El código del método init usa el objeto ServletConfig para recuperar los valores de initArgs llamando al método config.getInitParameter("parameter").
El método AuctionServlet.init también contacta con el servidor de JavaBeans Enterprise para crear un objeto contexto (ctx). Este objeto e susado en el método service para establecer una conexión con el servidor de JavaBeans Enterprise.
Context ctx=null;
private String detailsTemplate;
public void init(ServletConfig config)
throws ServletException{
super.init(config);
try {
ctx = getInitialContext();
}catch (Exception e){
System.err.println(
"failed to contact EJB server"+e);
}
try {
detailsTemplate=readFile(
config.getInitParameter("detailstemplate"));
} catch(IOException e) {
System.err.println(
"Error in AuctionServlet <init>"+e);
}
}
public void destroy() {
saveServletState();
}
public void service(HttpServletRequest request,
HttpServletResponse response)
throws IOException {
String cmd;
response.setContentType("text/html");
ServletOutputStream out = response.getOutputStream();
if (ctx == null ) {
try {
ctx = getInitialContext();
}catch (Exception e){
System.err.println(
"failed to contact EJB server"+e);
}
}
cmd=request.getParameter("action");
if(cmd !=null) {
if(cmd.equals("list")) {
listAllItems(out);
}else
if(cmd.equals("newlist")) {
listAllNewItems(out);
}else if(cmd.equals("search")) {
searchItems(out, request);
}else if(cmd.equals("close")) {
listClosingItems(out);
}else if(cmd.equals("insert")) {
insertItem(out, request);
}else if (cmd.equals("details")) {
itemDetails(out, request );
}else if (cmd.equals("bid")) {
itemBid(out, request) ;
}else if (cmd.equals("register")) {
registerUser(out, request);
}
}else{
// no command set
setTitle(out, "error");
}
setFooter(out);
out.flush();
}
Las peticiones DELETE son para eliminar páginas Web.
Las peticiones OPTIONS son para obtener información sobre las opciones de comunicación disponibles en la cadena petición/respuesta.
Las peticiones TRACE son para realizar pruebas de diagnóstico porque permite que el cliente vea lo que se está recibiendo al orto final de la cadena de petición.
Las cookies normalmente están asociadas con un servidor. Si configuramos el dominio a .java.sun.com, entonces la cookies está asociada con ese dominio. Si no se configura nignún dominio, la cookie sólo está asociada con el servidor que creó la cookie.
El método startSession mostrado aquí está en el programa LoginServlet. En este método, el nombre en la pareja nombre valor usado para crea el Cookie es JDCAUCTION, y un identificador único generado por el servidor es el valor.
protected Session startSession(String theuser,
String password,
HttpServletResponse response) {
Session session = null;
if ( verifyPassword(theuser, password) ) {
// Create a session
session = new Session (theuser);
session.setExpires (sessionTimeout + i
System.currentTimeMillis());
sessionCache.put (session);
// Create a client cookie
Cookie c = new Cookie("JDCAUCTION",
String.valueOf(session.getId()));
c.setPath ("/");
c.setMaxAge (-1);
c.setDomain (domain);
response.addCookie (c);
}
return session;
}
Versiones posteriores del API Servlet incluye un API Session, para crear una sesión usando el API Servelt en el ejemplo anterior podemos usar el método getSession.
El método startSession es llamado mediante peticiones de acción login desde un POST al LoginServlet de esta forma:HttpSession session = new Session (true);
La cookie es creada con un edad máxima de -1, lo que significa que el cookie es almacenado pero permanece vivo miesntras el navegador se esté ejecutando. El valor se selecciona en segunod, aunque cuando s eusan valores menores que unos pocos segundos necesitamos tener cuidado con que los tiempos de las máquinas pudieran estar ligeramente desincronizados.<FORM ACTION="/LoginServlet" METHOD="POST"> <TABLE> <INPUT TYPE="HIDDEN" NAME="action" VALUE="login"> <TR> <TD>Enter your user id:</TD> <TD><INPUT TYPE="TEXT" SIZE=20 NAME="theuser"></TD> </TR> <TR> <TD>Enter your password:<TD> <TD><INPUT TYPE="PASSWORD" SIZE=20 NAME="password"></TD> </TR> </TABLE> <INPUT TYPE="SUBMIT" VALUE="Login" NAME="Enter"> </FORM>
El valor de path puede ser usado para especificar que el cookie sólo se aplica a directorios y ficheros bajo el path seleccionado en esa máquina. En este ejemplo, el path raíz / significa que el cookie es aplicable a todos los directorios.
El valor del dominio en este ejemplo es leído desde los parámetros de inicialización del servlet. Si el dominio es null, el cookie es sólo aplicado a esa máquina de domino.
Posteriormente podemos recuperar la pareja de selecciones nombre y valor llamando al método Cookie.getName para recuperar el nombre y al método Cookie.getValue para recuperar el valor.Cookie c[] = request.getCookies();
LoginServlet tiene un método validateSession que chequea las cookies del usuario para encontrar un cookie JDCAUCTION que fué enviada en este dominio:
private Session validateSession
(HttpServletRequest request,
HttpServletResponse response) {
Cookie c[] = request.getCookies();
Session session = null;
if( c != null ) {
Hashtable sessionTable = new Hashtable();
for (int i=0; i < c.length &&
session == null; i++ ) {
if(c[i].getName().equals("JDCAUCTION")) {
String key = String.valueOf (c[i].getValue());
session=sessionCache.get(key);
}
}
}
return session;
}
Si usamos el API Servlet podemos usar el siguiente método, observamos que el parámetro es false para especificar que el valor de sesión es devuelto y que no se cree una nueva sesión:
HttpSession session = request.getSession(false);
La única forma de referenciar el nombre del usuario en el servidor es con este identificador de sesión, que está almacenado en un sencillo caché de memoria con los otros identificadores de sesión. Cuando un usuario termina una sesión, se llama a la acción LoginServlet de esta forma:
El caché de sesión implementado en el programa SessionCache.java incluye un thread para eliminar sesiones más viejas que el tiempo preseleccionado. Este tiempo podría medise en horas o días, dependiendo del tráfico de la web site.http://localhost:7001/LoginServlet?action=logout
private void setNoCache (HttpServletRequest request,
HttpServletResponse response) {
if(request.getProtocol().compareTo ("HTTP/1.0") == 0) {
response.setHeader ("Pragma", "no-cache");
} else if (request.getProtocol().compareTo
("HTTP/1.1") == 0) {
response.setHeader ("Cache-Control", "no-cache");
}
response.setDateHeader ("Expires", 0);
}
El programa LoginServlet chequea un directorio restringido en este método init. El método init mostrado abajo configura la variable protectedDir a true si la variable config pasada a él especifica un directorio protegido. El fichero de configuración del servidor Web proporciona las configuraciones pasadas a un servlet en la variable config.
public void init(ServletConfig config)
throws ServletException {
super.init(config);
domain = config.getInitParameter("domain");
restricted = config.getInitParameter("restricted");
if(restricted != null) {
protectedDir=true;
}
Más tarde en los métodos validateSession y service, la variable protectedDir es comprobada y se llama al método HttpResponse.sendRedirect para viar al usuario a la página correcta basándose en sus estados de login y sesión
if(protectedDir) {
response.sendRedirect (restricted+"/index.html");
}else{
response.sendRedirect (defaultPage);
}
El método init también recupera el contexto del servlet para el servlet FileServlet para que los métodos puedan ser llamados sobre FileServlet en el método validateSession. La ventaja de llamar a los métodos sobre el servlet FileServlet para servir los ficheros desde dentro del servlet LoginServlet, es que obtenemos todas las ventajas de la funcionalidades añadidas dentro del servlet FileServlet como el mepeo de memoria o el chaché de ficheros. La parte negativa es que el código podría no ser portable a otros servidores que no tengan un servlet FileServlet. Este código recupera el contexto FileServlet:
FileServlet fileServlet=(FileServlet)
config.getServletContext().getServlet("file");
El método validateSession evita que los usuarios sin login de sesión accedan a los directorios restringidos.
protected void service (HttpServletRequest request,
HttpServletResponse response)
throws ServletException {
response.sendError (500);
}
El método getParameter funciona vien para servlet sencillos, pero si necesitamos recuperar los parámetros POST en el orden enque fueron situados en la página wev o manejar posts multi-parte, podemos escribir nuestro propio para analizar el stram de entrada.
El siguiente ejemplo devuelve los parámetros POST en el orden en que fueron recibidos desde la página Web. Normalmento, los parámetros son almacenados en un Hashtable que no mantiene el orden de secuencia de los elementos almacenados. El ejemplo mantiene una referencia a cada pareja nombre/valoren un vector que puede ser ser analizado para devolver valores en el orden en que fueron recibidos por el servidor.
package auction;
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class PostServlet extends HttpServlet {
private Vector paramOrder;
private Hashtable parameters;
public void init(ServletConfig config)
throws ServletException {
super.init(config);
}
public void service(HttpServletRequest request,
HttpServletResponse response)
throws IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
if(request.getMethod().equals("POST")
&& request.getContentType().equals(
"application/x-www-form-urlencoded")) {
parameters=parsePostData(
request.getContentLength(),
request.getInputStream());
}
for(int i=0;i<paramOrder.size();i++) {
String name=(String)paramOrder.elementAt(i);
String value=getParameter((
String)paramOrder.elementAt(i));
out.println("name="+name+" value="+value);
}
out.println("</body></html>");
out.close();
}
private Hashtable parsePostData(int length,
ServletInputStream instream) {
String valArray[] = null;
int inputLen, offset;
byte[] postedBytes = null;
boolean dataRemaining=true;
String postedBody;
Hashtable ht = new Hashtable();
paramOrder= new Vector(10);
StringBuffer sb = new StringBuffer();
if (length <=0) {
return null;
}
postedBytes = new byte[length];
try {
offset = 0;
while(dataRemaining) {
inputLen = instream.read (postedBytes,
offset,
length - offset);
if (inputLen <= 0) {
throw new IOException ("read error");
}
offset += inputLen;
if((length-offset) ==0) {
dataRemaining=false;
}
}
} catch (IOException e) {
System.out.println("Exception ="+e);
return null;
}
postedBody = new String (postedBytes);
StringTokenizer st =
new StringTokenizer(postedBody, "&");
String key=null;
String val=null;
while (st.hasMoreTokens()) {
String pair = (String)st.nextToken();
int pos = pair.indexOf('=');
if (pos == -1) {
throw new IllegalArgumentException();
}
try {
key = java.net.URLDecoder.decode(
pair.substring(0, pos));
val = java.net.URLDecoder.decode(
pair.substring(pos+1,
pair.length()));
} catch (Exception e) {
throw new IllegalArgumentException();
}
if (ht.containsKey(key)) {
String oldVals[] = (String []) ht.get(key);
valArray = new String[oldVals.length + 1];
for (int i = 0; i < oldVals.length; i++) {
valArray[i] = oldVals[i];
}
valArray[oldVals.length] = val;
} else {
valArray = new String[1];
valArray[0] = val;
}
ht.put(key, valArray);
paramOrder.addElement(key);
}
return ht;
}
public String getParameter(String name) {
String vals[] = (String []) parameters.get(name);
if (vals == null) {
return null;
}
String vallist = vals[0];
for (int i = 1; i < vals.length; i++) {
vallist = vallist + "," + vals[i];
}
return vallist;
}
}
Para saber si una petición es POST o GET, llamados al método getMethod de la clase HttpServletRequest. Para determinar el formato de los datos que están siendo posteados, llamamos al método getContentType de la clase HttpServletRequest. Para sencillas páginas HTML, el tipo devuelto por está llamada será application/x-www-form-urlencoded.
Si necesitamos crear un post con más de una parte como el creado por el siguiente formulario HTML, el servler necesitará ller el stream de entrada desde el post para alcanzar las secciones individuales. Cada sección se dstingue por un límite definido en la cabecera post.
El siguiente ejemplo extrae una descripción y un fichero desde los navegadores del cliente. Lee el stream de entrada buscando una línea que corresponda con un string de límite, lee el contenido de la línea y lueo lee los datos asociados con esa parte. El fichero suvido se muestra simplemente, pero también puede ser escrito en disco:<FORM ACTION="/PostMultiServlet" METHOD="POST" ENCTYPE="multipart/form-data"> <INPUT TYPE="TEXT" NAME="desc" value=""> <INPUT TYPE="FILE" NAME="filecontents" value=""> <INPUT TYPE="SUBMIT" VALUE="Submit" NAME="Submit"> </FORM>
package auction;
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class PostMultiServlet extends HttpServlet {
public void init(ServletConfig config)
throws ServletException {
super.init(config);
}
public void service(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
if (request.getMethod().equals("POST")
&& request.getContentType().startsWith(
"multipart/form-data")) {
int index = request.getContentType().indexOf(
"boundary=");
if (index < 0) {
System.out.println("can't find boundary type");
return;
}
String boundary =
request.getContentType().substring(
index+9);
ServletInputStream instream =
request.getInputStream();
byte[] tmpbuffer = new byte[8192];
int length=0;
String inputLine=null;
boolean moreData=true;
//Skip until form data is reached
length = instream.readLine(
tmpbuffer,
0,
tmpbuffer.length);
inputLine = new String (tmpbuffer, 0, 0,
length);
while(inputLine.indexOf(boundary)
>0 && moreData) {
length = instream.readLine(
tmpbuffer,
0,
tmpbuffer.length);
inputLine = new String (tmpbuffer, 0, 0,
length);
if(inputLine !=null)
System.out.println("input="+inputLine);
if(length<0) {
moreData=false;
}
}
if(moreData) {
length = instream.readLine(
tmpbuffer,
0,
tmpbuffer.length);
inputLine = new String (tmpbuffer, 0, 0,
length);
if(inputLine.indexOf("desc") >=0) {
length = instream.readLine(
tmpbuffer,
0,
tmpbuffer.length);
inputLine = new String (tmpbuffer, 0, 0,
length);
length = instream.readLine(
tmpbuffer,
0,
tmpbuffer.length);
inputLine = new String (tmpbuffer, 0, 0,
length);
System.out.println("desc="+inputLine);
}
}
while(inputLine.indexOf(boundary)
>0 && moreData) {
length = instream.readLine(
tmpbuffer,
0,
tmpbuffer.length);
inputLine = new String (tmpbuffer, 0, 0,
length);
}
if(moreData) {
length = instream.readLine(
tmpbuffer,
0,
tmpbuffer.length);
inputLine = new String (tmpbuffer, 0, 0,
length);
if(inputLine.indexOf("filename") >=0) {
int startindex=inputLine.indexOf(
"filename");
System.out.println("file name="+
inputLine.substring(
startindex+10,
inputLine.indexOf("\"",
startindex+10)));
length = instream.readLine(
tmpbuffer,
0,
tmpbuffer.length);
inputLine = new String (tmpbuffer, 0, 0,
length);
}
}
byte fileBytes[]=new byte[50000];
int offset=0;
if (moreData) {
while(inputLine.indexOf(boundary)
>0 && moreData) {
length = instream.readLine(
tmpbuffer,
0,
tmpbuffer.length);
inputLine = new String (tmpbuffer, 0, 0, length);
if(length>0 && (
inputLine.indexOf(boundary) <0)) {
System.arraycopy(
tmpbuffer,
0,
fileBytes,
offset,
length);
offset+=length;
} else {
moreData=false;
}
}
}
// trim last two newline/return characters
// before using data
for(int i=0;i<offset-2;i++) {
System.out.print((char)fileBytes[i]);
}
}
out.println("</body></html>");
out.close();
}
}
Esto significa que cualquier campo estático o público accedido por el método service deberían estár restringidos a accesos de un thread. el ejemplo de abajo usa la palabra clave synchronized para restringir el acceso a un contador para que sólo pueda ser actualizado por un thread a la vez:
int counter
Boolean lock = new Boolean(true);
synchronized(lock){
counter++;
}
Los datos encriptados que son enviados a través de la red incluyen chequeos para verificar si los dato se han modificado en el tránsito. SSL también autentifica el servidor web a sus clientes proporcionando un certificado de clave pública. en el SSL 3.0 el cliente también puede autentificarse a sí mismo con el servidor, usxando de nuevo un certificado de clave pública.
La clave pública criptográfica (también llamada clave de encriptación asimétrerica) usa una pareja de claves pública y privada. Cualquier mensaje encriptado (hecho ininteligible) con la clave privada de la pareja sólo puede ser desencriptado con la correspondiente clave pública. Los certificados son sentencias firmadas digitalmente generadas por un tercera parte conocidad como "Autoridad de Certificación" Certificate Authority. Esta Autorizar necesita asegurarse de que nosotros somos quien decimos ser porque los clientes se creeran el certificado que reciban. Si es así, este certificado puede contener la clave pública de la pareja de clave pública/privada. El certificado está firmado por la clave privada de la Autoridad de Certificación, y muchos navegadores conocen las claves públicas la mayoría de las Autoridades de Certificación.
Mientras que la encriptaciónde clavepública es buena para propósitos de autentificación, no es tan rápida como la encriptación asimétrica y por eso el protocolo SSL usa ambos tipos de claves en el ciclo de vida de una conexión SSL. El cliente y el servidor empiezan una transación HTTPS con una inicialización de conexión o fase de estrechamiento de manos.
Es en ese momento en el que el servidor es autentificado usando el certificado que el cliente ha recibido. El cliente usa la clave pública del servidor para encriptar los mensajes enviados al servidor. Después de que el cliente haya sido autentificado y el algoritmo de encriptación se ha puesto de acuerdo entre las dos partes, se usan unas nuevas claves de sesión simétrica para encriptar y desencriptar las comunicaciones posteriores.
El algoritmo de encriptación puede ser uno de los más populares algoritmos como "Rivest Shamir and Adleman" (RSA) o "Data Encryption Standard" (DES). Cuando mayor sea el número de bits usados para crear la clave, mayores dificultades para poder romper las claves mediante la fuerza bruta.
HTTPS usando criptografía de clave pública y certificados nos permite proporcionar una gran privacidad a las aplicacioens que necesitan transaciones seguras. Los servidores, navegadores y Java Plug-In tienen sus propias configuraciones para permitir usar Comunicaciones SSL. En general, estos pasos requieren:
HTTPS puede ser usado para cualquier dato, no sólo ara páginas web HTTP. Los programas escritos en lenguaje Java pueden ser descaradoa a trravés de conexiones HTTPS, y podemos abrir una conexión con un servidor HTTPS en el Java Plug-In. Para escribir un programa en Java que use SSL, este necesita una librería SSL y un conecimiento detallado del proceso de negociación HTTPS. Nuestra librería SSL podría cubir los pasos necesarios ya que está información es restringida por el control de exportación de seguridad.