import java.io.*;
import java.net.*;
import java.util.*;

class WebServer implements HttpConstants {

  protected static void p(String s) {
	System.out.println(s);
  }

  protected static Properties props = new Properties();
  static Vector threads = new Vector();
  static File root;
  static int port = 80;
  static int timeout = 0;
  static int workers = 10;
  public static ServerSocket ss;

  static void loadProps() throws IOException {
	File f = new File(System.getProperty("user.dir")+File.separator+"WebServer.ini");
	if(f.exists()) {
		InputStream is=new BufferedInputStream(new FileInputStream(f));
		props.load(is);
		is.close();
		String r = props.getProperty("root");
		if(r != null) {
		    root = new File(r);
		    if(!root.exists()) {
			throw new Error(root + " doesn't exist as server root");
		    }
		}
		r = props.getProperty("port");
		if(r != null) {
		    port = Integer.parseInt(r);
		}
		r = props.getProperty("timeout");
		if(r != null) {
		    timeout = Integer.parseInt(r);
		}
		r = props.getProperty("workers");
		if(r != null) {
		    workers = Integer.parseInt(r);
		}
	}
  }

  static void printProps() {
	p("root="+root);
	p("port="+port);
	p("timeout="+timeout);
	p("workers="+workers);
  }

  public static void main(String[] a) throws Exception {
	loadProps();
	printProps();
	for(int i=0;i<workers;i++) {
	  Worker w = new Worker();
	  (new Thread(w, "worker #"+i)).start();
	  threads.addElement(w);
	}
	ss = new ServerSocket(port);
	while (true) {
	Socket s = ss.accept();   Worker w = null;
	  synchronized (threads) {
	     if(threads.isEmpty()) {
		Worker ws = new Worker();
		ws.setSocket(s);
		(new Thread(ws, "additional worker")).start();
	     } else {
		w = (Worker) threads.elementAt(0);
		threads.removeElementAt(0);
		w.setSocket(s);
	     }
	  }
	}
  }
}

class Worker extends WebServer implements HttpConstants, Runnable {
    final static int BUF_SIZE = 2048;

    static final byte[] EOL = {(byte)'\r', (byte)'\n' };
    byte[] buf;
	private Socket s; Worker() {
	    buf = new byte[BUF_SIZE];
	    s = null;
	}

	synchronized void setSocket(Socket s) {
	    this.s = s;
	    notify();
	}

	public synchronized void run() {
	while(true) {
	    if (s == null) {
		try{
		    wait();
		} catch (InterruptedException e) {
		    continue;
		}
	    }
	    try{
		handleClient();
	    } catch (Exception e) {
		e.printStackTrace();
	    }
	    s = null;
	    Vector pool = WebServer.threads;
	    synchronized (pool) {
		if (pool.size() >= WebServer.workers) {
		return;
		} else {
		    pool.addElement(this);
		}
	    }
	}
   }


   void handleClient() throws IOException {
	InputStream is = new BufferedInputStream(s.getInputStream());
	PrintStream ps = new PrintStream(s.getOutputStream());
	s.setSoTimeout(WebServer.timeout);
	s.setTcpNoDelay(true);
	for(int i = 0; i < BUF_SIZE; i++) {
	    buf[i] =0;
	}
	try{
	    int nread = 0, r = 0;

outerloop:
		while (nread < BUF_SIZE) {
		r = is.read(buf, nread, BUF_SIZE - nread);
		if (r == -1) {
		    return;
		}
		int i = nread;
		nread += r;
		for (; i < nread; i++) {
		    if (buf[i] == (byte)'\n' || buf[i] == (byte)'\r') {
			break outerloop;
		    }
		}
	    }
	    boolean doingGet;
	    int index;
	    if (buf[0] == (byte)'G' &&
		buf[1] == (byte)'E' &&
		buf[2] == (byte)'T' &&
		buf[3] == (byte)' ') {
		doingGet = true;
		index = 4;
	    } else if (buf[0] == (byte)'H' &&
		buf[1] == (byte)'E' &&
		buf[2] == (byte)'A' &&
		buf[3] == (byte)'D' &&
		buf[4] == (byte)' ') {
		doingGet = false;
		index = 5;
	    } else {
		ps.print("HTTP/1.0 " + HTTP_BAD_METHOD +
			" unsupported method type:");
		ps.write(buf, 0, 5);
		ps.write(EOL);
		ps.flush();
		s.close();
		return;
	    }

	    int i = 0;
	    for (i = index; i < nread; i++) {
		if (buf[i] == (byte)' ') {
		    break;
		}
	    }
	    String fname = (new String(buf, 0, index,
		i-index)).replace('/', File.separatorChar);
	    if (fname.startsWith(File.separator)) {
		fname = fname.substring(1);
	    }
	    File targ = new File(WebServer.root, fname);
	    if (targ.isDirectory()) {
		File ind = new File(targ, "index.html");
		if (ind.exists()) {
		    targ = ind;
		}
	    }
	    boolean OK = printHeaders(targ, ps);
	    if (doingGet) {
		if (OK) {
		    sendFile(targ, ps);
		} else {
		    send404(targ, ps);
		}
	    }
	} finally {
	    s.close();
	}
    }

    boolean printHeaders(File targ, PrintStream ps) throws IOException {
	boolean ret = false;
	int rCode = 0;
	if (!targ.exists()) {
	    rCode = HTTP_NOT_FOUND;
	    ps.print("HTTP/1.0 " + HTTP_NOT_FOUND + " not found");
	    ps.write(EOL);
	    ret = false;
	} else {
	    rCode = HTTP_OK;
	    ps.print("HTTP/1.0 " + HTTP_OK+" OK");
	    ps.write(EOL);
	    ret = true;
	}
	ps.print("Server: Simple Java");
	ps.write(EOL);
	ps.print("Date: " + (new Date()));
	ps.write(EOL);
	if (ret) {
	if (!targ.isDirectory()) {
	    ps.print("Content-length: "+targ.length());
	    ps.write(EOL);
	    ps.print("Last Modified: " + (new
		Date(targ.lastModified())));
	    ps.write(EOL);
	    String name = targ.getName();
	    int ind = name.lastIndexOf('.');
	    String ct = null;
	    if (ind > 0) {
		ct = (String) map.get(name.substring(ind));
	    }
	    if (ct == null) {
		ct = "unknown/unknown";
	    }
	    ps.print("Content-type: " + ct);
	    ps.write(EOL);
	} else {
	    ps.print("Content-type: text/html");
	    ps.write(EOL);
	}
    }
    return ret;
}

    void send404(File targ, PrintStream ps) throws IOException {
	ps.write(EOL);
	ps.write(EOL);
	ps.println("Not Found\n\n"+
		"The requested resource was not found.\n");
    }

    void sendFile(File targ, PrintStream ps) throws IOException {
	InputStream is = null;
	ps.write(EOL);
	if (targ.isDirectory()) {
	    return;
	} else {
	    is = new FileInputStream(targ.getAbsolutePath());
	}

	try{
	    int n;
	    while ((n = is.read(buf)) > 0) {
		ps.write(buf, 0, n);
	    }
	} finally {
		is.close();
	}
    }

    static java.util.Hashtable map = new java.util.Hashtable(); static {
	fillMap();
    }

    static void setSuffix(String k, String v) {
	map.put(k, v);
    }

    static void fillMap() {
	setSuffix("", "content/unknown");
	setSuffix(".uu", "application/octet-stream");
	setSuffix(".exe", "application/octet-stream");
	setSuffix(".ps", "application/postscript");
	setSuffix(".zip", "application/zip");
	setSuffix(".sh", "application/x-shar");
	setSuffix(".tar", "application/x-tar");
	setSuffix(".snd", "audio/basic");
	setSuffix(".au", "audio/basic");
	setSuffix(".wav", "audio/x-wav");
	setSuffix(".gif", "image/gif");
	setSuffix(".jpg", "image/jpeg");
	setSuffix(".jpeg", "image/jpeg");
	setSuffix(".htm", "text/html");
	setSuffix(".html", "text/html");
	setSuffix(".text", "text/plain");
	setSuffix(".c", "text/plain");
	setSuffix(".cc", "text/plain");
	setSuffix(".c++", "text/plain");
	setSuffix(".h", "text/plain");
	setSuffix(".pi", "text/plain");
	setSuffix(".txt", "text/plain");
	setSuffix(".Java", "text/plain");
    }
}

interface HttpConstants {
    public static final int HTTP_OK = 200;
    public static final int HTTP_CREATED = 201;
    public static final int HTTP_ACCEPTED = 202;
    public static final int HTTP_NOT_AUTHORITATIVE = 203;
    public static final int HTTP_NO_CONTENT = 204;
    public static final int HTTP_RESET = 205;
    public static final int HTTP_PARTIAL = 206;

    public static final int HTTP_MULT_CHOICE = 300;
    public static final int HTTP_MOVED_PERM = 301;
    public static final int HTTP_MOVED_TEMP = 302;
    public static final int HTTP_SEE_OTHER = 303;
    public static final int HTTP_NOT_MODIFIED = 304;
    public static final int HTTP_USE_PROXY = 305;

    public static final int HTTP_BAD_REQUEST = 400;
    public static final int HTTP_UNAUTHORIZED = 401;
    public static final int HTTP_PAYMENT_REQUIRED = 402;
    public static final int HTTP_FORBIDDEN = 403;
    public static final int HTTP_NOT_FOUND = 404;
    public static final int HTTP_BAD_METHOD = 405;
    public static final int HTTP_NOT_ACCEPTABLE = 406;
    public static final int HTTP_PROXY_AUTH = 407;
    public static final int HTTP_CLIENT_TIMEOUT = 408;
    public static final int HTTP_CONFLICT = 409;
    public static final int HTTP_GONE = 410;
    public static final int HTTP_LENGTH_REQUIRED = 411;
    public static final int HTTP_PRECON_FAILED = 412;
    public static final int HTTP_ENTITY_TOO_LARGE = 413;
    public static final int HTTP_REQ_TOO_LONG = 414;
    public static final int HTTP_UNSUPPORTED_TYPE = 415;

    public static final int HTTP_SERVER_ERROR = 500;
    public static final int HTTP_INTERNAL_ERROR = 501;
    public static final int HTTP_BAD_GATEWAY = 502;
    public static final int HTTP_UNAVAILABLE = 503;
    public static final int HTTP_GATEWAY_TIMEOUT = 504;
    public static final int HTTP_VERSION = 505;
}

