// httpd - attempt at implementing a local HTTP daemon in java // Mostly stolen from Java in a Nutshell, pp. 145-147 // The rest authored by John Comeau // Notes: /* 1. So far it only works with Netscape. (I have version 1.22.) IE 3.0 and Cello both hang unless I set the boolean "suicide" to true, in which case Cello will show the page and IE pops up a dialogue box informing you that the server was reset. Apparently both IE and Cello expect the server to close the connection, and I have no idea what else IE wants to see before it will show me a page. Only Netscape seems smart enough to realize when a file is done transmitting. 2. Putting in a schlock 5-sec time delay makes it work with both Netscape and IE. 3. 4/12/97 removing join(), now works OK with all three. Had to remove check for "HTTP/1.0" because Cello doesn't issue it on subsequent GETs. Also don't use "localhost" with Cello, use 127.0.0.1 instead. */ import java.io.*; import java.net.*; import java.util.*; public class httpd extends Thread { public final static int DEFAULT_PORT = 80; protected int port; protected ServerSocket listen_socket; protected Watchdog watchdog; protected int errors; public static final boolean debug = true; public static final boolean suicide = false; // die after first customer /* Log an error message. Make sure to redirect STDOUT if you want a logfile. For example, C:\> httpd >%temp%\httpd.log */ public static void log(String str) { System.out.println(new Date().toGMTString() + " - " + str); } // Exit with an error message, when an exception occurs public static void fail(Exception e, String msg) { log(msg + ": " + e); System.exit(1); } // Create a ServerSocket on which to listen for connections // Then start the thread public httpd(int port) { if(port == 0) port = DEFAULT_PORT; this.port = port; try { listen_socket = new ServerSocket(port); } catch (IOException e) {fail(e, "Exception creating server socket");} System.out.println("HTTP Server: Listening on port " + port); watchdog = new Watchdog(this); this.start(); } // The body of the server thread public void run() { while (true) { try { Socket client_socket = listen_socket.accept(); Connection c = new Connection(client_socket); if (suicide) { c.join(0); // wait for connection to die... break; } } catch (Exception e) { log("Nonfatal error in run(): " + e); this.errors++; } } } // Start the server up, listening on an optionally specified port public static void main(String[] args) { int port = 0; if (args.length == 1) { try {port = Integer.parseInt(args[0]);} catch (NumberFormatException e) {port = 0;} } new httpd(port); } } // This class handles all communication with a client class Connection extends Thread { protected Socket client; protected DataInputStream in; protected PrintStream out; protected Vector request; protected StringTokenizer getfile; protected File file; public final static String HOMEPAGE = "/index.html"; protected byte[] buffer; String line; String cmdline; String command; String filename; String protocol; String type; boolean badrequest; public static final boolean debug = true; public static final boolean delay = true; // Initialize the streams and start the thread public Connection(Socket client_socket) { client = client_socket; try { in = new DataInputStream(client.getInputStream()); out = new PrintStream(client.getOutputStream()); } catch (IOException e) { try {client.close();} catch (IOException e2) {;} System.out.println("Exception while getting socket streams " + e); return; } this.start(); } // provide the service public void run() { try { request = new Vector(); for(;;) { // read in a line line = in.readLine(); if (line == null || line.trim().length() == 0) break; System.out.println(line); // show what we got request.addElement(line.trim()); } System.out.println("Responding to client..."); if (debug) { System.out.println("Address=" + client.getInetAddress()); System.out.println("Port=" + client.getPort()); System.out.println("Local Port=" + client.getLocalPort()); } command = ""; // init to null strings filename = ""; protocol = ""; try { getfile = new StringTokenizer( request.elementAt(0).toString()); command = getfile.nextToken(); // GET or HEAD, no POST filename = getfile.nextToken(); protocol = getfile.nextToken(); } catch (Exception e) { if (filename.equals("")) badrequest = true; if (debug) { System.out.println("Error parsing command: command=" + command + ", filename=" + filename + ", protocol=" + protocol); } } try { if ( !command.equals("GET") && !command.equals("HEAD")) badrequest = true; } catch (Exception e) { badrequest = true; } if (badrequest) { if (debug) { System.out.println("Error interpreting command: command=" + command + ", filename=" + filename + ", protocol=" + protocol); } out.print("HTTP/1.0 403 NFW\r\n"); out.print("Server: Java1.0\r\n"); out.print("Date: " + new Date().toGMTString() + "\r\n"); out.print("Content-Type: text/html\r\n\r\n"); out.print("

No Freakin' Way!" + "

\r\n\r\n"); return; // goes to "finally" clause below before exiting } if (filename.equals("/")) filename = HOMEPAGE; try { file = new File(filename); in = new DataInputStream(new FileInputStream(file)); buffer = new byte[(int) file.length()]; in.read(buffer); } catch (Exception e) { out.print("HTTP/1.0 404 Not Found\r\n"); out.print("Server: Java1.0\r\n"); out.print("Date: " + new Date().toGMTString() + "\r\n"); out.print("Content-Type: text/html\r\n\r\n"); out.print("

Say what???" + "

\r\n\r\n"); return; // to "finally" block below } out.print("HTTP/1.0 200 OK\r\n"); out.print("Server: Java1.0\r\n"); out.print("Date: " + new Date().toGMTString() + "\r\n"); try { type = filename.substring( filename.lastIndexOf('.') + 1).toLowerCase(); } catch (StringIndexOutOfBoundsException e) { type = ""; } if (debug) System.out.println("Type=\"" + type + "\""); out.print("Content-Type: "); if (type.equals("txt")) out.print("text/plain\r\n"); else if (type.equals("jpg") || type.equals("jpeg")) out.print("image/jpeg\r\n"); else if (type.equals("gif")) out.print("image/gif\r\n"); else out.print("text/html\r\n"); out.print("Content-Length: " + file.length() + "\r\n\r\n"); if (command.equals("GET")) out.write(buffer); } catch (Exception e){;} finally { if (delay) try {sleep(5000);} catch (Exception e) {;} try {client.close();} catch (IOException e) { System.out.println(e); } System.out.println("Closed connection to client..."); this.stop(); } } } // Put watchdog to work, cleaning up if 10 errors or more in a second class Watchdog extends Thread { protected httpd server; protected Watchdog(httpd s) { server = s; this.start(); } // should really synchronize on errors but I don't know how... public synchronized void run() { for (;;) { try {this.wait(1000);} catch (InterruptedException e) {;} if (server.errors >= 10) { server.log(server.errors + " errors logged in one second, aborting..."); System.exit(1); } else { server.errors = 0; } } } }