Il programma HTTPServer definisce il funzionamento di base di un Server Web multithreading che implementa il protocollo HTTP 1.1 .
Il server :
  • può essere configurato;
  • supporta la registrazione delle richieste HTTP ricevute dai Client;
  • supporta solo richieste GET.

In pratica il Browser effettua la richiesta:

GET URL_assoluta_file /HTTP1.x

ed il Server , dopo opportuni controlli, risponde:

HTTP1.x 200
Cache-Control : no-cache
Cache-Control : private
// Expire : 0
Quest'ultimo solo nel caso in cui venga trasmesso un archivio Jar.

HTTPServer.java:

import java.net.*;
import java.io.*;
import java.lang.*;
import java.util.*;

public class HTTPServer {
static final String NAME = "HTTPServer";
static final String VERSION = "1.0";
ServerConfiguration config = new ServerConfiguration();
Logger logger = new Logger();

/* MAIN */

// Crea un istanza di HTTPServer e richiama il metodo Run della stessa
  public static void main(String args[]) {

HTTPServer server = new HTTPServer();
if(args.length > 0) server.processCommandLine(args);
server.run();
  }

   public HTTPServer() {

super();   }

// Valutazione dei parametri passati nella linea di comando

  public void processCommandLine(String[] args) {

for(int i=0;i < args.length;++i) { if(args[i].equals("-CONFIG")) { if(i+1 < args.length) config.processConfigurationFile(args[i+1]); else System.out.println("Configuration file argument is missing.");
break;
}
}
String logFile = config.getLogFile();
if(logFile!="") logger = new Logger(logFile,config.echoToConsole());
  }

/* Visualizzazione versione del Server */

public void displayVersionInfo() {

System.out.println("HTTPServer version "+VERSION); }

/* Corpo del programma */
// Si crea un nuovo ServerSocket, si assegna il socket alla variabile server, quindi si esegue un ciclo infinito nel quale si rimane in attesa dell'arrivo di una connessione, si crea un'istanza socket per gestirla

  public void run() {

displayVersionInfo(); // Versione del Server
config.display(); // Caratteristiche del Server
try { // Si apre la socket del Server e si definisce la porta utilizzata
ServerSocket server = new ServerSocket(config.getServerPort());
int localPort = server.getLocalPort();
logger.datedLog(NAME+" is listening on port "+localPort+".");
do { // Si attende la connesione di un client
Socket client = server.accept();
// Ricevuta la richiesta si apre per il servizio un nuovo ServerThread che estende la classe Thread
(new HTTPServerThread(client,config,logger)).start();
} while(true);
} catch(IOException ex) { logger.datedLog("Unable to listen on "+config.getServerPort()+".");
System.exit(0);
}
  }
}

/********************************************************************

Ogni volta che un cliente si connette, si apre un Thread nuovo e al quale si affida la gestione della connessione.

********************************************************************/

class HTTPServerThread extends Thread {
Socket client;
ServerConfiguration config;
Logger logger;

  public HTTPServerThread(Socket client,ServerConfiguration config, Logger logger) {

this.client = client;
this.config = config;
this.logger = logger;
  }

// Corpo del Thread

  public void run() {

try { // Descrive il tipo di connessione instaurato con il cliente
describeConnection(client);
// Si creano oggetti delle classi HTTPOutputStream e HTTPInputStream per comunicare col Client. I flussi sono memorizzati in buffer per mogliorare le prestazioni di I/O
HTTPOutputStream outStream = new HTTPOutputStream(
new BufferedOutputStream(client.getOutputStream()));
HTTPInputStream inStream = new HTTPInputStream(client.getInputStream());
// Cattura le richieste HTTP di input
HTTPRequest request = inStream.getRequest();
request.log(logger);
if(request.isGetRequest()) { String response = request.prepareResponse();
String ext = request.getExtName(config);
// Se il file trasmesso è un archivio ".jar", comunica al Browser di non trattenerlo poiché nella Cache ha validità per 0 sec.
if (ext.equals("jar")) response +="Expire : 0 \n";
outStream.println(response);
outStream.println();
logger.datedLog("Send msg: "+ response);
processGetRequest(request,outStream);
}
logger.datedLog("Request completed. Closing connection.");
}catch(IOException ex) { logger.datedLog("IOException occured when processing request."); }
try { client.close();
logger.datedLog("Connection with client closed.");
}catch(IOException ex) { logger.datedLog("IOException occured when closing socket."); }
  }

  void describeConnection(Socket client) {

String destName = client.getInetAddress().getHostName();
String destAddr = client.getInetAddress().getHostAddress();
int destPort = client.getPort();
logger.datedLog("Accepted connection to "+destName+" ("
+destAddr+")"+" on port "+destPort+".");
  }

void processGetRequest(HTTPRequest request,HTTPOutputStream outStream) throws IOException   {

String Registrati = "C:\\Inetpub\\WwwRoot\\Registrati.html";
String Registrato = "C:\\Inetpub\\WwwRoot\\Registrato.html";
String fileName = request.getFileName(config);

/* Attraverso HTTPServer possono essere richiesti solo i file :

  • Default.html
  • Plug-In.html
  • Home.html
e tutti i componenti (immagini o Applet) in esso contenuti.
Un primo controllo può basarsi proprio su queste considerazioni. Se un utente richiede i file:
  • Registrati.html
  • Registrato.html
la richiesta deve essere rifiutata oppure commutata in "Default.html".
E' evidente che tutto ciò può essere migliorato.
Un utente molto esperto potrebbe richiedere direttamente una nostra applicazione. Grazie però al principio di località appena esposto ( HTTPServer: Default.html,Home.Html,Plu-In.html e componenti mentre HTTPsServer: Regestrati.html, Registrato.html e componenti) possiamo supporre che la protezione del nostro software possa essere garantita da un comune FireWall. /*

if((fileName.equals(Registrati)) || (fileName.equals(Registrato)) ) {

logger.datedLog("File request not accept.");
fileName="C:\\Inetpub\\WwwRoot\\Default.html";
}
File file = new File(fileName);
if(file.exists()) { String fullPath = file.getCanonicalPath();
if(inServerRoot(fullPath)) { int len = (int) file.length();
sendFile(outStream,file);
} else logger.datedLog("File is not in server root.");
} else logger.datedLog("File "+file.getCanonicalPath()+" does not exist.");
  }

  public boolean inServerRoot(String fileName) {

String serverRoot = config.getServerRoot();
int fileLength = fileName.length();
int rootLength = serverRoot.length();
if(fileLength < rootLength) return false;
if(serverRoot.equals(fileName.substring(0,rootLength))) return true;
return false;
  }

   void sendFile(HTTPOutputStream out,File file) {

try { DataInputStream in = new DataInputStream(new FileInputStream(file));
int len = (int) file.length();
byte buffer[] = new byte[len];
in.readFully(buffer);
in.close();
for(int i=0;i < len;++i) out.write(buffer[i]);
out.flush();
out.close();
logger.datedLog("File sent: "+file.getCanonicalPath());
logger.log("Number of bytes: "+len);
} catch(Exception ex) { logger.datedLog("Error retrieving "+file); }
  }

}

class HTTPInputStream extends FilterInputStream {
  public HTTPInputStream(InputStream in) {

super(in);   }

  public String readLine() throws IOException {

StringBuffer result=new StringBuffer();
boolean finished = false;
boolean cr = false;
do { int ch = -1;
ch = read();
if(ch==-1) return result.toString();
result.append((char) ch);
if(cr && ch==10){ result.setLength(result.length()-2);
return result.toString();
}
if(ch==13) cr = true;
else cr=false;
} while (!finished);
return result.toString();
  }

  public HTTPRequest getRequest() throws IOException {

HTTPRequest request = new HTTPRequest();
String line;
do { line = readLine();
if(line.length() > 0) request.addLine(line);
else break;
} while(true);
return request;
  }

}

class HTTPOutputStream extends FilterOutputStream {
   public HTTPOutputStream(OutputStream out) {

super(out);   }

   public void println() throws IOException {

write(13);
write(10);
  }

  public void println(String s) throws IOException {

for(int i=0;i < s.length();++i) write(s.charAt(i));
println();
  }
}

class HTTPRequest {
  Vector lines = new Vector();

  public HTTPRequest() {
  }

   public void addLine(String line) {

lines.addElement(line);   }

  boolean isGetRequest() {

if(lines.size() > 0) { String firstLine = (String) lines.elementAt(0);
if(firstLine.length() > 0) if(firstLine.substring(0,3).equalsIgnoreCase("GET")) return true;
}
return false;
  }

  String getFileName(ServerConfiguration config) {

if(lines.size() > 0) { String firstLine = (String) lines.elementAt(0);
String fileName = firstLine.substring(firstLine.indexOf(" ")+1);
int n = fileName.indexOf(" ");
if(n!=-1) fileName = fileName.substring(0,n);
try { if(fileName.charAt(0) == '/') fileName = fileName.substring(1); } catch(StringIndexOutOfBoundsException ex) {}
if(fileName.equals("")) fileName = config.getDefaultFile();
if(fileName.charAt(fileName.length()-1)=='/') fileName+=config.getDefaultFile(); return config.getServerRoot()+fileName;
}else return "";
  }

  String getExtName(ServerConfiguration config) {

if(lines.size() > 0) { String firstLine = (String) lines.elementAt(0);
String extName = firstLine.substring(firstLine.indexOf(" ")+1);
int n = extName.indexOf(" ");
if(n!=-1) extName = extName.substring(0,n);
try { if(extName.charAt(0) == '/') extName = extName.substring(1); } catch(StringIndexOutOfBoundsException ex) {}
if(extName.equals("")) extName = config.getDefaultFile();
if(extName.charAt(extName.length()-1)=='/') extName+=config.getDefaultFile(); n = extName.indexOf(".") + 1;
extName = extName.substring(n,extName.length());
return extName;
} else return "";
  }

  String prepareResponse() {

String lineOut="HTTP/";
for (int i=0;i < lines.size();++i) { if (i==0) { String line = (String) lines.elementAt(0);
int n = line.indexOf("1");
line=line.substring(n,line.length());
lineOut+=line;
}
}
// Ritorna lo Stato del Server
lineOut+=" 200 \n";
// Ritorna di altri eventuali parametri
lineOut+="Cache-Control : no-cache \n";
lineOut+="Cache-Control : private \n";
// lineOut+="Expire : 0 \n"; Nel caso si decida di non permettere la memorizzazione in Cache di ogni file che il Server trasmette
return lineOut;
  }

   void log (Logger logger) {

logger.datedLog("Received the following request:");
for(int i=0;i < lines.size();++i) logger.log((String) lines.elementAt(i));
  }
}

// Questa classe si occupa della configurazione del Server Web

class ServerConfiguration {
static final char CONFIG_COMMENT_CHAR = '#';
int serverPort = 80;
String serverRoot = "C:\\Inetpub\\WwwRoot\\";
String defaultFile = "default.html";
String logFile = "log.txt";
boolean echoLogToConsole = true;

  public ServerConfiguration() {
  }

  public char getCommentChar() {

return CONFIG_COMMENT_CHAR;   }

  public int getServerPort() {

return serverPort;   }

  public String getServerRoot() {

return serverRoot;   }

   public String getDefaultFile() {

return defaultFile;   }

  public String getLogFile() {

return logFile;   }

  public boolean echoToConsole() {

return echoLogToConsole;   }

  public void display() {

System.out.println(" serverPort: "+serverPort);
System.out.println(" serverRoot: "+serverRoot);
System.out.println(" defaultFile: "+defaultFile);
System.out.println(" logFile: "+logFile);
System.out.println(" echoLogToConsole: "+echoLogToConsole);
  }

  public void processConfigurationFile(String fname) {

try { File file = new File(fname);
if(file.exists()) { BufferedReader reader = new BufferedReader(new FileReader(file));
String line;
while((line=reader.readLine())!=null) processConfigurationLine(line); reader.close();
}
}catch(Exception ex) { System.out.println("Unable to process configuration file."); }
  }

  public void processConfigurationLine(String line) throws NumberFormatException {

line = removeLeadingWhiteSpace(line);
if(line.length()==0) return;
int n;
int n1 = line.indexOf(' ');
int n2 = line.indexOf('\t');
if(n1 == -1) n = n2;
else if(n2 == -1) n = n1;
else if(n1 < n2) n = n1;
else n = n2;
if(n==-1 || n==line.length()-1) return;
String param = line.substring(0,n);
String value = line.substring(n+1);
if(param.equals("serverPort")) serverPort = (new Integer(value)).intValue(); else if(param.equals("serverRoot")){ serverRoot = value;
if(!serverRoot.equals("")){ char ch = serverRoot.charAt(serverRoot.length()-1);
if(ch!='/' && ch!='\\') serverRoot+="/";
}
}else if(param.equals("defaultFile")) defaultFile = value; else if(param.equals("logFile")) logFile = value; else if(param.equals("echoLogToConsole")) echoLogToConsole = (new Boolean(value)).booleanValue();
  }

  String removeLeadingWhiteSpace(String line) {

boolean finished = false;
do { if(line.length()==0) return "";
char ch = line.charAt(0);
if(ch==CONFIG_COMMENT_CHAR) return "";
if(ch!=' ' && ch!='\t') return line;
line=line.substring(1);
} while (!finished);
return "";
  }
}

// Esempio di file di configurazione: config.txt

// Questa classe si occupa della registrazione delle operazioni effettuate. // La registrazione può essere effettuata su console o su file.

class Logger {

public String logFile;
public boolean echoLogToConsole = true;
public BufferedWriter writer = null;

  public Logger() {
  }

  public Logger(String fname, boolean echo) {

logFile = fname;
echoLogToConsole = echo;
try { writer = new BufferedWriter(new FileWriter(fname,true)); }catch(IOException ex){}
  }

  void logMsg(String msg) {

if(writer!=null) { try { writer.write(msg);
writer.newLine();
writer.flush();
} catch(IOException ex) {}
}
if(echoLogToConsole) System.out.println(msg);
  }

   public synchronized void log(String msg) {

logMsg(" "+msg);   }

  public synchronized void datedLog(String msg) {

logMsg((new Date()).toString()+" "+msg);   }
}

b6.gif - 3872 Bytes