3000行代码实现一个servlet server

// All enhancments Copyright (C)1998-2003 by Dmitriy Rogatkin
// this version is compatible with JSDK 2.4
// http://tjws.sourceforge.net
// $Id: Serve.java,v 1.49 2004/02/06 07:10:20 rogatkin Exp $

package Acme.Serve;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringBufferInputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.TimeZone;
import java.util.Vector;

import javax.servlet.RequestDispatcher;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.SingleThreadModel;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import javax.servlet.http.HttpSessionContext;
import javax.servlet.http.HttpUtils;

/// Minimal Java HTTP server class.
// <P>
// This class implements a very small embeddable HTTP server.
// It runs Servlets compatible with the API used by JavaSoft's
// <A HREF="http://java.sun.com/products/java-server/">JavaServer</A> server.
// It comes with default Servlets which provide the usual
// httpd services, returning files and directory listings.
// <P>
// This is not in any sense a competitor for JavaServer.
// JavaServer is a full-fledged HTTP server and more.
// Acme.Serve is tiny, about 1500 lines, and provides only the
// functionality necessary to deliver an Applet's .class files
// and then start up a Servlet talking to the Applet.
// They are both written in Java, they are both web servers, and
// they both implement the Servlet API; other than that they couldn't
// be more different.
// <P>
// This is actually the second HTTP server I've written.
// The other one is called
// <A HREF="http://www.acme.com/software/thttpd/">thttpd</A>,
// it's written in C, and is also pretty small although much more
// featureful than this.
// <P>
// Other Java HTTP servers:
// <UL>
// <LI> The above-mentioned <A HREF="http://java.sun.com/products/java-server/">JavaServer</A>.
// <LI> W3C's <A HREF="http://www.w3.org/pub/WWW/Jigsaw/">Jigsaw</A>.
// <LI> David Wilkinson's <A HREF="http://www.netlink.co.uk/users/cascade/http/">Cascade</A>.
// <LI> Yahoo's <A HREF="http://www.yahoo.com/Computers_and_Internet/Software/Internet/World_Wide_Web/Servers/Java/">list of Java web servers</A>.
// </UL>
// <P>
// A <A HREF="http://www.byte.com/art/9706/sec8/art1.htm">June 1997 BYTE magazine article</A> mentioning this server.<BR>
// A <A HREF="http://www.byte.com/art/9712/sec6/art7.htm">December 1997 BYTE magazine article</A> giving it an Editor's Choice Award of Distinction.<BR>
// <A HREF="/resources/classes/Acme/Serve/Serve.java">Fetch the software.</A><BR>
// <A HREF="/resources/classes/Acme.tar.Z">Fetch the entire Acme package.</A>
// <P>
// @see Acme.Serve.servlet.http.HttpServlet
// @see FileServlet
// @see CgiServlet

// make it final?
public class Serve implements ServletContext, RequestDispatcher
{

	private static final String progName = "Serve";
	public static final String ARG_PORT = "port";
	public static final String ARG_THROTTLES = "throttles";
	public static final String ARG_SERVLETS = "servlets";
	public static final String ARG_REALMS = "realms";
	public static final String ARG_ALIASES = "aliases";
	public static final String ARG_CGI_PATH = "cgi-path";
	public static final String ARG_SESSION_TIMEOUT = "session-timeout";
	public static final String ARG_LOG_OPTIONS = "log-options";
	public static final String ARG_SOCKET_FACTORY = "socketFactory";
	public static final String ARG_QUIET = "quiet";

	public static final String ARG_NOHUP = "nohup";

	protected static final int DEF_SESSION_TIMEOUT = 30; // in minutes
	protected static final int DEF_PORT = 8888;

	/// Main routine, if you want to run this directly as an application.
	public static void main(String[] args)
	{
		Map arguments = new HashMap(20);

		int argc = args.length;
		int argn;
		// Parse args.
		workPath = System.getProperty("user.dir", ".");
		if (argc == 0) // a try to read from file for java -jar server.jar
			try
			{
				BufferedReader br = new BufferedReader(new FileReader(new File(workPath, "cmdparams")));
				StringTokenizer st = new StringTokenizer(br.readLine(), " ");
				args = new String[st.countTokens()];
				argc = args.length; // tail can be nulled
				for (int i = 0; i < argc && st.hasMoreTokens(); i++)
					args[i] = st.nextToken();
			}
			catch (Exception e)
			{ // many can happen
			}
		for (argn = 0; argn < argc && args[argn].charAt(0) == '-';)
		{
			if (args[argn].equals("-p") && argn + 1 < argc)
			{
				++argn;
				arguments.put(ARG_PORT, new Integer(args[argn]));
			}
			else if (args[argn].equals("-t") && argn + 1 < argc)
			{
				++argn;
				arguments.put(ARG_THROTTLES, args[argn]);
			}
			else if (args[argn].equals("-s") && argn + 1 < argc)
			{
				++argn;
				arguments.put(ARG_SERVLETS, args[argn]);
			}
			else if (args[argn].equals("-r") && argn + 1 < argc)
			{
				++argn;
				arguments.put(ARG_REALMS, args[argn]);
			}
			else if (args[argn].equals("-a") && argn + 1 < argc)
			{
				++argn;
				arguments.put(ARG_ALIASES, args[argn]);
			}
			else if (args[argn].equals("-c") && argn + 1 < argc)
			{
				++argn;
				arguments.put(ARG_CGI_PATH, args[argn]);
			}
			else if (args[argn].equals("-e") && argn + 1 < argc)
			{
				++argn;
				try
				{
					arguments.put(ARG_SESSION_TIMEOUT, new Integer(args[argn]));
				}
				catch (NumberFormatException nfe)
				{
				}
			}
			else if (args[argn].startsWith("-l"))
			{
				if (args[argn].length() > 2)
					arguments.put(ARG_LOG_OPTIONS, args[argn].substring(2).toUpperCase());
				else
					arguments.put(ARG_LOG_OPTIONS, "");
			}
			else if (args[argn].startsWith("-nohup"))
			{
				arguments.put(ARG_NOHUP, ARG_NOHUP);

			}
			else if (args[argn].equals("-q"))
			{
				arguments.put(ARG_QUIET, ARG_QUIET);

			}
			else if (args[argn].startsWith("-"))
			{
				if (args[argn].length() > 1)
					arguments.put(args[argn].substring(1),
					//.toUpperCase(),
					argn < argc - 1 ? args[++argn] : "");
			}
			else
				usage();

			++argn;
		}
		if (argn != argc)
			usage();
		/** format path mapping
		from=givenpath;dir=realpath
		*/
		PrintStream printstream = System.err;
		try
		{
			printstream =
				new PrintStream(
					new FileOutputStream(new File(workPath, "AWS-" + System.currentTimeMillis() + ".log")),
					true);
			System.setErr(printstream);
		}
		catch (IOException e)
		{
			System.err.println("IO problem at setting a log stream " + e);
		}
		PathTreeDictionary mappingtable = new PathTreeDictionary();
		if (arguments.get(ARG_ALIASES) != null)
		{
			File file = new File((String) arguments.get(ARG_ALIASES));
			if (file.isAbsolute() == false)
				file = new File(workPath, file.getName());
			if (file.exists() && file.canRead())
			{
				try
				{
					DataInputStream in = new DataInputStream(new FileInputStream(file));
					do
					{
						String mappingstr = in.readLine();
						if (mappingstr == null)
							break;
						StringTokenizer maptokenzr = new StringTokenizer(mappingstr, "=;");
						if (maptokenzr.hasMoreTokens())
						{
							if (maptokenzr.nextToken("=").equalsIgnoreCase("from"))
							{
								if (maptokenzr.hasMoreTokens())
								{
									String srcpath = maptokenzr.nextToken("=;");
									if (maptokenzr.hasMoreTokens()
										&& maptokenzr.nextToken(";=").equalsIgnoreCase("dir"))
										try
										{
											if (maptokenzr.hasMoreTokens())
											{
												String value = maptokenzr.nextToken();
												File mapFile = new File(value);
												if (mapFile.isAbsolute() == false)
													mapFile = new File(workPath, mapFile.getName());
												mappingtable.put(srcpath, mapFile);
											}
										}
										catch (NullPointerException e)
										{
										}
								}
							}
						}
					}
					while (true);
				}
				catch (IOException e)
				{
					System.err.println("Problem reading aliases file: " + arguments.get(ARG_ALIASES) + "/" + e);
				}
			}
			else
				System.err.println("File " + arguments.get(ARG_ALIASES) + " doesn't exist or not readable.");
		}
		/** format realmname=path,user:password,,,,
		*/
		PathTreeDictionary realms = new PathTreeDictionary();
		if (arguments.get(ARG_REALMS) != null)
		{
			try
			{
				File file = new File((String) arguments.get(ARG_REALMS));
				if (file.isAbsolute() == false)
					file = new File(workPath, file.getName());
				DataInputStream in = new DataInputStream(new FileInputStream(file));

				do
				{
					String realmstr = in.readLine();
					if (realmstr == null)
						break;
					StringTokenizer rt = new StringTokenizer(realmstr, "=,:");
					if (rt.hasMoreTokens())
					{
						String realmname = null;
						realmname = rt.nextToken();
						if (rt.hasMoreTokens())
						{
							String realmPath = null;
							realmPath = rt.nextToken();
							if (rt.hasMoreTokens())
							{
								String user = rt.nextToken();
								if (rt.hasMoreTokens())
								{
									String password = rt.nextToken();
									BasicAuthRealm realm = null;
									Object o[] = realms.get(realmPath);
									if (o != null && o[0] != null)
										realm = (BasicAuthRealm) o[0];
									else
									{
										realm = new BasicAuthRealm(realmname);
										realms.put(realmPath, realm);
									}
									realm.put(user, password);
								}
							}
						}
					}
				}
				while (true);
			}
			catch (IOException ioe)
			{
				System.err.println("Problem in reading realms file " + arguments.get(ARG_REALMS) + "/ " + ioe);
			}
		}
		// Create the server.
		serve = new Serve(arguments, printstream);
		File tempFile = arguments.get(ARG_SERVLETS) == null ? null : new File((String) arguments.get(ARG_SERVLETS));
		if (tempFile != null && tempFile.isAbsolute() == false)
			tempFile = new File(workPath, tempFile.getName());
		final File servFile = tempFile;

		// PathTreeDictionary mappingtable = new PathTreeDictionary();
		// File mapFile = new File("D:\\webfiles");
		// mappingtable.put("/", mapFile);
		serve.setMappingTable(mappingtable);
		serve.setRealms(realms);

		new Thread(new Runnable()
		{
			public void run()
			{
				serve.readServlets(servFile);
			}
		}).start();
		// And add the standard Servlets.
		String throttles = (String) arguments.get(ARG_THROTTLES);
		if (throttles == null)
			serve.addDefaultServlets((String) arguments.get(ARG_CGI_PATH));
		else
			try
			{
				serve.addDefaultServlets((String) arguments.get(ARG_CGI_PATH), throttles);
			}
			catch (IOException e)
			{
				System.err.println("Problem reading throttles file: " + e);
				System.exit(1);
			}

		// And run.
		serve.serve();

		//		System.exit( 0 );
	}

	private static void usage()
	{
		System.err.println(
			"usage:  "
				+ progName
				+ " [-p port] [-s servletpropertiesfile] [-a aliasmappingfile] [-l[a][r]] [-c cgi-bin-dir] [-e [-]duration_in_minutes] [-nohup] [-socketFactory class name and other parameters}");
		System.exit(1);
	}

	/**
	 * Stops the server
	 * 
	 * @throws IOException
	 */
	public static void stop() throws IOException
	{
		if (serve != null)
		{
			serve.notifyStop();
		}
	}

	/**
	 * Starts the server.
	 * 
	 * @param port the port to use
	 * @param servlets an array of strings of the form {"servletName", "servletClass"} for example
	 *        {"RequestHeader", "com.datasweep.compatibility.servlet.RequestHeaderServlet"}
	 * @param aliases an array of strings of the form {"/", "fully qualified path to webroot directory"}
	 * @param consoleQuit to have server listen for a 'q' on the console to quit
	 */
	public static void start(int port, String[] servlets, String[] aliases, boolean consoleQuit, boolean quiet)
	{
		Map arguments = new HashMap(20);

		// port
		arguments.put(ARG_PORT, new Integer(port));

		// nohup
		if (!consoleQuit)
		{
			arguments.put(ARG_NOHUP, ARG_NOHUP);
		}

		// logging
		PrintStream printstream = System.err;

		if (quiet)
		{
			arguments.put(ARG_QUIET, ARG_QUIET);
		}

		// aliases
		// PathTreeDictionary mappingtable = new PathTreeDictionary();
		// File mapFile = new File("D:\\webfiles");
		// mappingtable.put("/", mapFile);
		PathTreeDictionary mappingtable = new PathTreeDictionary();
		for (int i = 0; i < aliases.length; i = i + 2)
		{
			mappingtable.put(aliases[i], new File(aliases[i + 1]));
		}

		// Create the server.
		serve = new Serve(arguments, printstream);
		serve.setMappingTable(mappingtable);

		// servlets
		for (int i = 0; i < servlets.length; i = i + 2)
		{
			serve.addServlet(servlets[i], servlets[i + 1]);
		}

		// And add the standard Servlets.
		serve.addDefaultServlets(null);
		serve.serve();

	}

	private void readServlets(File servFile)
	{
		/** servlet.properties file format
		servlet.<servletname>.code=<servletclass>
		servlet.<servletname>.initArgs=<name=value>,<name=value>
		    */
		Hashtable servletstbl, parameterstbl;
		servletstbl = new Hashtable();
		parameterstbl = new Hashtable();
		if (servFile != null && servFile.exists() && servFile.canRead())
		{
			try
			{
				DataInputStream in = new DataInputStream(new FileInputStream(servFile));
				/** format of servlet.cfg file
				servlet_name;servlet_class;init_parameter1=value1;init_parameter2=value2...
				*/
				do
				{
					String servletdsc = in.readLine();
					if (servletdsc == null)
						break;
					StringTokenizer dsctokenzr = new StringTokenizer(servletdsc, ".=,", false);
					if (dsctokenzr.hasMoreTokens())
					{
						if (!dsctokenzr.nextToken().equalsIgnoreCase("servlet"))
						{
							System.err.println("No leading 'servlet' keyword, the sentence is skipped");
							break;
						}
						if (dsctokenzr.hasMoreTokens())
						{
							String servletname = dsctokenzr.nextToken();

							if (dsctokenzr.hasMoreTokens())
							{
								String lt = dsctokenzr.nextToken();
								if (lt.equalsIgnoreCase("code"))
								{
									if (dsctokenzr.hasMoreTokens())
										servletstbl.put(servletname, dsctokenzr.nextToken("="));
								}
								else if (lt.equalsIgnoreCase("initArgs"))
								{
									Hashtable initparams = new Hashtable();
									while (dsctokenzr.hasMoreTokens())
									{
										String key = dsctokenzr.nextToken("=,");
										if (dsctokenzr.hasMoreTokens())
											initparams.put(key, dsctokenzr.nextToken(",="));
									}
									parameterstbl.put(servletname, initparams);
								}
								else
									System.err.println(
										"Unrecognized token " + lt + " in " + servletdsc + ", the line's skipped");
							}
						}
					}
				}
				while (true);
			}
			catch (IOException e)
			{
				System.err.println("Problem reading cfg file: " + e);
			}
			Enumeration se = servletstbl.keys();
			String servletname;
			while (se.hasMoreElements())
			{
				servletname = (String) se.nextElement();
				addServlet(
					servletname,
					(String) servletstbl.get(servletname),
					(Hashtable) parameterstbl.get(servletname));
			}
		}

	}

	int port;
	String hostName;
	private PrintStream logStream;
	private boolean useAccLog;
	private boolean showUserAgent;
	private boolean showReferer;
	private boolean quiet = false;
	protected PathTreeDictionary registry;
	protected PathTreeDictionary realms;
	private PathTreeDictionary mappingtable;
	private Hashtable attributes;
	sun.misc.BASE64Decoder base64Dec = new sun.misc.BASE64Decoder();
	// for sessions
	int uniqer;
	HttpSessionContextImpl sessions;
	static int expiredIn;
	protected Map arguments;
	protected static Serve serve;
	protected static String workPath;

	/// Constructor.
	public Serve(Map arguments, PrintStream logStream)
	{
		this.arguments = arguments;
		this.logStream = logStream;
		registry = new PathTreeDictionary();
		realms = new PathTreeDictionary();
		attributes = new Hashtable();
		sessions = new HttpSessionContextImpl();
		setAccessLogged();
		expiredIn =
			arguments.get(ARG_SESSION_TIMEOUT) != null
				? ((Integer) arguments.get(ARG_SESSION_TIMEOUT)).intValue()
				: DEF_SESSION_TIMEOUT;
		port = arguments.get(ARG_PORT) != null ? ((Integer) arguments.get(ARG_PORT)).intValue() : DEF_PORT;
		quiet = (arguments.get(ARG_QUIET) != null);
	}

	public Serve()
	{
		this(new HashMap(), System.err);
	}

	void setAccessLogged()
	{
		String logflags = (String) arguments.get(ARG_LOG_OPTIONS);
		if (logflags != null)
		{
			useAccLog = true;
			showUserAgent = logflags.indexOf('A') >= 0;
			showReferer = logflags.indexOf('R') >= 0;
		}
	}

	boolean isAccessLogged()
	{
		return useAccLog;
	}
	boolean isShowReferer()
	{
		return showReferer;
	}
	boolean isShowUserAgent()
	{
		return showUserAgent;
	}

	/// Register a Servlet by class name.  Registration consists of a URL
	// pattern, which can contain wildcards, and the class name of the Servlet
	// to launch when a matching URL comes in.  Patterns are checked for
	// matches in the order they were added, and only the first match is run.
	public void addServlet(String urlPat, String className)
	{
		addServlet(urlPat, className, (Hashtable) null);
	}

	public void addServlet(String urlPat, String className, Hashtable initParams)
	{
		// Check if we're allowed to make one of these.
		SecurityManager security = System.getSecurityManager();
		if (security != null)
		{
			int i = className.lastIndexOf('.');
			if (i != -1)
			{
				security.checkPackageAccess(className.substring(0, i));
				security.checkPackageDefinition(className.substring(0, i));
			}
		}

		// Make a new one.
		try
		{
			addServlet(urlPat, (Servlet) Class.forName(className).newInstance(), initParams);
			return;
		}
		catch (ClassNotFoundException e)
		{
			log("Class not found: " + className);
		}
		catch (ClassCastException e)
		{
			log("Class cast problem: " + e.getMessage());
		}
		catch (InstantiationException e)
		{
			log("Instantiation problem: " + e.getMessage());
		}
		catch (IllegalAccessException e)
		{
			log("Illegal class access: " + e.getMessage());
		}
		catch (Exception e)
		{
			log("Unexpected problem creating servlet: " + e, e);
		}
	}

	/// Register a Servlet.  Registration consists of a URL pattern,
	// which can contain wildcards, and the Servlet to
	// launch when a matching URL comes in.  Patterns are checked for
	// matches in the order they were added, and only the first match is run.
	public void addServlet(String urlPat, Servlet servlet)
	{
		addServlet(urlPat, servlet, (Hashtable) null);
	}

	public void addServlet(String urlPat, Servlet servlet, Hashtable initParams)
	{
		try
		{
			servlet.init(new ServeConfig((ServletContext) this, initParams, urlPat));
			registry.put(urlPat, servlet);
		}
		catch (ServletException e)
		{
			log("Problem initializing servlet: " + e);
		}
	}

	/// Register a standard set of Servlets.  These will return
	// files or directory listings, and run CGI programs, much like a
	// standard HTTP server.
	// <P>
	// Because of the pattern checking order, this should be called
	// <B>after</B> you've added any custom Servlets.
	// <P>
	// The current set of default servlet mappings:
	// <UL>
	// <LI> If enabled, *.cgi goes to CgiServlet, and gets run as a CGI program.
	// <LI> * goes to FileServlet, and gets served up as a file or directory.
	// </UL>
	// @param cgi whether to run CGI programs
	// TODO: provide user specified CGI directory
	public void addDefaultServlets(String cgi)
	{
		if (cgi != null)
			addServlet("/" + cgi, new Acme.Serve.CgiServlet());
		addServlet("/", new Acme.Serve.FileServlet());
	}

	/// Register a standard set of Servlets, with throttles.
	// @param cgi whether to run CGI programs
	// @param throttles filename to read FileServlet throttle settings from
	public void addDefaultServlets(String cgi, String throttles) throws IOException
	{
		if (cgi != null)
			addServlet("/" + cgi, new Acme.Serve.CgiServlet());
		addServlet("/", new Acme.Serve.FileServlet(throttles, null));
	}

	// Run the server.  Returns only on errors.
	boolean running = true;
	protected ServerSocket serverSocket;
	protected Thread ssclThread;
	boolean do_exit = false;
	public void serve()
	{
		try
		{
			serverSocket = createServerSocket();
		}
		catch (IOException e)
		{
			log("Server socket: " + e);
			return;
		}
		hostName = serverSocket.getInetAddress().getHostName();
		if (arguments.get(ARG_NOHUP) == null)
			new Thread(new Runnable()
		{
			public void run()
			{
				BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
				String line;
				while (true)
				{
					try
					{
						System.out.print("Press \"q\" <ENTER>, for gracefully stopping the server ");
						line = in.readLine();
						if (line != null && line.length() > 0 && line.charAt(0) == 'q')
						{
							notifyStop();
							do_exit = true;
							break;
						}
					}
					catch (IOException e)
					{
						System.err.println("Exception in reading from console " + e);
						break;
					}
				}
			}
		}, "Stop Monitor").start();
		// else create kill signal handler
		if (expiredIn > 0)
		{
			ssclThread = new Thread(new Runnable()
			{
				public void run()
				{
					while (running)
					{
						try
						{
							Thread.sleep(expiredIn * 60 * 1000);
						}
						catch (InterruptedException ie)
						{
							if (running == false)
								break;
						}
						Enumeration e = sessions.keys();
						while (e.hasMoreElements())
						{
							Object sid = e.nextElement();
							if (sid != null)
							{
								AcmeSession as = (AcmeSession) sessions.get(sid);
								if (as != null
									&& (as.getMaxInactiveInterval() * 1000
										< System.currentTimeMillis() - as.getLastAccessedTime()
										|| !as.isValid()))
								{ //log("sesion is timeouted, last accessed " + new Date(as.getLastAccessedTime()));
									// hashtable is synchronized impl
									as = (AcmeSession) sessions.remove(sid);
									if (as != null && as.isValid())
										try
										{
											as.invalidate();
										}
										catch (IllegalStateException ise)
										{

										}
								}
							}
						}
					}
				}
			}, "Session cleaner");
			ssclThread.setPriority(Thread.MIN_PRIORITY);
			ssclThread.start();
		}
		else
			expiredIn = -expiredIn;
		if (!quiet)
		{
			System.out.println("WebServer :" + port + " is ready.");
		}
		try
		{
			while (running)
			{
				Socket socket = serverSocket.accept();
				new ServeConnection(socket, this);
			}
		}
		catch (IOException e)
		{
			log("Accept: " + e);
		}
		finally
		{
			try
			{
				serverSocket.close();
			}
			catch (IOException e)
			{
			}
			destroyAllServlets();
			if (do_exit)
			{
				System.exit(0);
			}
		}
	}

	protected void notifyStop() throws IOException
	{
		running = false;
		if (serverSocket != null)
			serverSocket.close();
		if (ssclThread != null)
			ssclThread.interrupt();
	}

	public static interface SocketFactory
	{
		public ServerSocket createSocket(Map arguments) throws IOException, IllegalArgumentException;
	}

	protected ServerSocket createServerSocket() throws IOException
	{
		String socketFactoryClass = (String) arguments.get(ARG_SOCKET_FACTORY);
		if (socketFactoryClass != null)
			try
			{
				return ((SocketFactory) Class.forName(socketFactoryClass).newInstance()).createSocket(arguments);
			}
			catch (Exception e)
			{
				System.err.println(
					"Couldn't create custom socket factory "
						+ socketFactoryClass
						+ " or call creation method. Standard socket will be created. "
						+ e);
			}
		return new ServerSocket(port, 1000);
	}

	// Methods from ServletContext.

	/// Gets a servlet by name.
	// @param name the servlet name
	// @return null if the servlet does not exist
	public Servlet getServlet(String name)
	{
		try
		{
			return (Servlet) ((Object[]) registry.get(name))[0];
		}
		catch (NullPointerException npe)
		{
			return null;
		}
	}

	/// Enumerates the servlets in this context (server). Only servlets that
	// are accesible will be returned.  This enumeration always includes the
	// servlet itself.
	public Enumeration getServlets()
	{
		return registry.elements();
	}

	/// Enumerates the names of the servlets in this context (server). Only
	// servlets that are accesible will be returned.  This enumeration always
	// includes the servlet itself.
	public Enumeration getServletNames()
	{
		return registry.keys();
	}

	/// Destroys all currently-loaded servlets.
	public void destroyAllServlets()
	{
		Enumeration en = registry.elements();
		while (en.hasMoreElements())
		{
			Servlet servlet = (Servlet) en.nextElement();
			servlet.destroy();
		}
		registry = new PathTreeDictionary();
		// invalidate all sessions
		Enumeration e = sessions.keys();
		while (e.hasMoreElements())
		{
			Object sid = e.nextElement();
			if (sid != null)
			{
				AcmeSession as = (AcmeSession) sessions.get(sid);
				if (as != null)
				{
					as = (AcmeSession) sessions.remove(sid);
					if (as != null && as.isValid())
						try
						{
							as.invalidate();
						}
						catch (IllegalStateException ise)
						{

						}
				}
			}
		}
	}

	public void setMappingTable(PathTreeDictionary mappingtable)
	{
		this.mappingtable = mappingtable;
	}

	public void setRealms(PathTreeDictionary realms)
	{
		this.realms = realms;
	}

	Object getSession(String id)
	{
		return sessions.get(id);
	}

	HttpSession createSession()
	{
		HttpSession result = new AcmeSession(generateSessionId(), expiredIn * 60, this, sessions);
		sessions.put(result.getId(), result);
		return result;
	}

	void removeSession(String id)
	{
		sessions.remove(id);
	}

	/// Write information to the servlet log.
	// @param message the message to log
	public void log(String message)
	{
		if (logStream != null && !quiet)
		{
			Date date = new Date(System.currentTimeMillis());
			logStream.println("[" + date.toString() + "] " + message);
		}
	}

	public void log(String message, Throwable throwable)
	{
		StringWriter sw = new StringWriter();
		throwable.printStackTrace(new PrintWriter(sw));
		log(message + '\n' + sw);
	}

	/// Write a stack trace to the servlet log.
	// @param exception where to get the stack trace
	// @param message the message to log
	public void log(Exception exception, String message)
	{
		StringWriter sw = new StringWriter();
		exception.printStackTrace(new PrintWriter(sw));
		log("" + sw + '\n' + message);
	}

	/// Applies alias rules to the specified virtual path and returns the
	// corresponding real path.  It returns null if the translation
	// cannot be performed.
	// @param path the path to be translated
	public String getRealPath(String path)
	{
		//		System.err.print("["+path+"]->[");
		try
		{
			path = new String(path.getBytes("ISO-8859-1"), "UTF-8");
		}
		catch (Exception ee)
		{ // no encoding
		}
		if (mappingtable != null)
		{
			// try find first sub-path
			Object[] os = mappingtable.get(path);
			if (os[0] == null)
				return null;
			int slpos = ((Integer) os[1]).intValue();
			if (slpos > 0)
			{
				if (path.length() > slpos)
					path = path.substring(slpos + 1);
				else
					path = "";
			}
			else if (path.length() > 0)
			{
				char s = path.charAt(0);
				if (s == '/' || s == '\\')
					path = path.substring(1);
			}
			//			System.err.println("Base:"+((String)os[0])+"\npath="+path+"\n pos="+slpos+']');

			return (os[0].toString()) + File.separatorChar + path;
		}
		return path;
	}

	/// Returns the MIME type of the specified file.
	// @param file file name whose MIME type is required
	public String getMimeType(String file)
	{
		file = file.toUpperCase();

		if (file.endsWith(".HTML") || file.endsWith(".HTM"))
			return "text/html";
		if (file.endsWith(".TXT"))
			return "text/plain";
		if (file.endsWith(".XML"))
			return "text/xml";
		if (file.endsWith(".CSS"))
			return "text/css";
		if (file.endsWith(".SGML") || file.endsWith(".SGM"))
			return "text/x-sgml";
		// Image
		if (file.endsWith(".GIF"))
			return "image/gif";
		if (file.endsWith(".JPG") || file.endsWith(".JPEG") || file.endsWith(".JPE"))
			return "image/jpeg";
		if (file.endsWith(".PNG"))
			return "image/png";
		if (file.endsWith(".BMP"))
			return "image/bmp";
		if (file.endsWith(".TIF") || file.endsWith(".TIFF"))
			return "image/tiff";
		if (file.endsWith(".RGB"))
			return "image/x-rgb";
		if (file.endsWith(".XPM"))
			return "image/x-xpixmap";
		if (file.endsWith(".XBM"))
			return "image/x-xbitmap";
		if (file.endsWith(".SVG"))
			return "image/svg-xml ";
		if (file.endsWith(".SVGZ"))
			return "image/svg-xml ";
		// Audio
		if (file.endsWith(".AU") || file.endsWith(".SND"))
			return "audio/basic";
		if (file.endsWith(".MID") || file.endsWith(".MIDI") || file.endsWith(".RMI") || file.endsWith(".KAR"))
			return "audio/mid";
		if (file.endsWith(".MPGA") || file.endsWith(".MP2") || file.endsWith(".MP3"))
			return "audio/mpeg";
		if (file.endsWith(".WAV"))
			return "audio/wav";
		if (file.endsWith(".AIFF") || file.endsWith(".AIFC"))
			return "audio/aiff";
		if (file.endsWith(".AIF"))
			return "audio/x-aiff";
		if (file.endsWith(".RA"))
			return "audio/x-realaudio";
		if (file.endsWith(".RPM"))
			return "audio/x-pn-realaudio-plugin";
		if (file.endsWith(".RAM"))
			return "audio/x-pn-realaudio";
		if (file.endsWith(".SD2"))
			return "audio/x-sd2";
		// Application
		if (file.endsWith(".BIN")
			|| file.endsWith(".DMS")
			|| file.endsWith(".LHA")
			|| file.endsWith(".LZH")
			|| file.endsWith(".EXE")
			|| file.endsWith(".DLL")
			|| file.endsWith(".CLASS"))
			return "application/octet-stream";
		if (file.endsWith(".HQX"))
			return "application/mac-binhex40";
		if (file.endsWith(".PS") || file.endsWith(".AI") || file.endsWith(".EPS"))
			return "application/postscript";
		if (file.endsWith(".PDF"))
			return "application/pdf";
		if (file.endsWith(".RTF"))
			return "application/rtf";
		if (file.endsWith(".DOC"))
			return "application/msword";
		if (file.endsWith(".PPT"))
			return "application/powerpoint";
		if (file.endsWith(".FIF"))
			return "application/fractals";
		if (file.endsWith(".P7C"))
			return "application/pkcs7-mime";
		// Application/x
		if (file.endsWith(".JS"))
			return "application/x-javascript";
		if (file.endsWith(".Z"))
			return "application/x-compress";
		if (file.endsWith(".GZ"))
			return "application/x-gzip";
		if (file.endsWith(".TAR"))
			return "application/x-tar";
		if (file.endsWith(".TGZ"))
			return "application/x-compressed";
		if (file.endsWith(".ZIP"))
			return "application/x-zip-compressed";
		if (file.endsWith(".DIR") || file.endsWith(".DCR") || file.endsWith(".DXR"))
			return "application/x-director";
		if (file.endsWith(".DVI"))
			return "application/x-dvi";
		if (file.endsWith(".TEX"))
			return "application/x-tex";
		if (file.endsWith(".LATEX"))
			return "application/x-latex";
		if (file.endsWith(".TCL"))
			return "application/x-tcl";
		if (file.endsWith(".CER") || file.endsWith(".CRT") || file.endsWith(".DER"))
			return "application/x-x509-ca-cert";
		// Video
		if (file.endsWith(".MPG") || file.endsWith(".MPE") || file.endsWith(".MPEG"))
			return "video/mpeg";
		if (file.endsWith(".QT") || file.endsWith(".MOV"))
			return "video/quicktime";
		if (file.endsWith(".AVI"))
			return "video/x-msvideo";
		if (file.endsWith(".MOVIE"))
			return "video/x-sgi-movie";
		// Chemical
		if (file.endsWith(".PDB") || file.endsWith(".XYZ"))
			return "chemical/x-pdb";
		// X-
		if (file.endsWith(".ICE"))
			return "x-conference/x-cooltalk";
		if (file.endsWith(".WRL") || file.endsWith(".VRML"))
			return "x-world/x-vrml";
		if (file.endsWith(".WML"))
			return "text/vnd.wap.wml";
		if (file.endsWith(".WMLC"))
			return "application/vnd.wap.wmlc";
		if (file.endsWith(".WMLS"))
			return "text/vnd.wap.wmlscript";
		if (file.endsWith(".WMLSC"))
			return "application/vnd.wap.wmlscriptc";
		if (file.endsWith(".WBMP"))
			return "image/vnd.wap.wbmp";

		return null;
	}

	/// Returns the name and version of the web server under which the servlet
	// is running.
	// Same as the CGI variable SERVER_SOFTWARE.
	public String getServerInfo()
	{
		return Serve.Identification.serverName
			+ " "
			+ Serve.Identification.serverVersion
			+ " ("
			+ Serve.Identification.serverUrl
			+ ")";
	}

	/// Returns the value of the named attribute of the network service, or
	// null if the attribute does not exist.  This method allows access to
	// additional information about the service, not already provided by
	// the other methods in this interface.
	public Object getAttribute(String name)
	{
		// This server does not support attributes.
		return attributes.get(name);
	}

	/////////////////// JSDK 2.1 extensions //////////////////////////
	public void removeAttribute(String name)
	{
		attributes.remove(name);
	}

	public void setAttribute(String name, Object object)
	{
		if (object != null)
			attributes.put(name, object);
		else
			attributes.remove(name);
	}

	public Enumeration getAttributeNames()
	{
		return attributes.keys();
	}

	public ServletContext getContext(String uripath)
	{
		return this; // only root context supported
	}

	public int getMajorVersion()
	{
		return 2; // support 2.x
	}

	public int getMinorVersion()
	{
		return 3; // support 2.3
	}

	// 2.3

	/**
	 * Returns a directory-like listing of all the paths to resources within the web application whose longest sub-path matches the supplied path argument. Paths indicating subdirectory paths end with a '/'. The returned paths are all relative to the root
	 *  of the web application and have a leading '/'. For example, for a web application containing
	 * <p>
	 * /welcome.html <br>
	 * /catalog/index.html<br>
	 * /catalog/products.html<br>
	 * /catalog/offers/books.html<br>
	 * /catalog/offers/music.html<br>
	 * /customer/login.jsp<br>
	 * /WEB-INF/web.xml<br>
	 * /WEB-INF/classes/com.acme.OrderServlet.class,
	 * <p>
	 * getResourcePaths("/") returns {"/welcome.html", "/catalog/", "/customer/", "/WEB-INF/"}<br>
	 * getResourcePaths("/catalog/") returns {"/catalog/index.html", "/catalog/products.html", "/catalog/offers/"}.
	 * <p>
	 * 
	 * @param the - partial path used to match the resources, which must start with a /
	 * @return a Set containing the directory listing, or null if there are no resources in the web application whose path begins with the supplied path.
	 * @since Servlet 2.3 
	 * 
	 */
	public java.util.Set getResourcePaths(java.lang.String path)
	{
		// TODO: implement
		return null;
	}

	/**
	 * Returns the name of this web application correponding to this ServletContext as specified in the deployment descriptor for this web application by the display-name element.
	 * @return The name of the web application or null if no name has been declared in the deployment descriptor.
	 *
	 * @since Servlet 2.3 
	 */
	public java.lang.String getServletContextName()
	{
		//return null;//"ROOT";
		throw new RuntimeException("getServletContextName is not supported.");
	}

	// only root relative in this implementation
	public URL getResource(String path) throws MalformedURLException
	{
		return new URL("http", hostName, port, path);
	}

	public InputStream getResourceAsStream(String path)
	{
		return null; // we don't provide resources in this way
	}

	public RequestDispatcher getRequestDispatcher(String urlpath)
	{
		return this; // we don't provide resource dispatching in this way
	}

	// no way to specify parameters for context
	public String getInitParameter(String param)
	{
		return null;
	}

	public Enumeration getInitParameterNames()
	{
		return null;
	}

	public RequestDispatcher getNamedDispatcher(String name)
	{
		return this;
	}

	synchronized String generateSessionId()
	{
		return "-" + System.currentTimeMillis() + '-' + (uniqer++) + '-' + Math.round(Math.random() * 1000);
	}

	public void forward(ServletRequest _request, ServletResponse _response)
		throws ServletException, java.io.IOException
	{
	}

	public void include(ServletRequest _request, ServletResponse _response)
		throws ServletException, java.io.IOException
	{
	}

	final static class Identification
	{
		public static final String serverName = "Rogatkin's JWS based on Acme.Serve";
		public static final String serverVersion = "$Revision: 1.49 $";
		public static final String serverUrl = "http://tjws.sourceforge.net";

		/// Write a standard-format HTML address for this server.
		public static void writeAddress(OutputStream o) throws IOException
		{
			PrintStream p = new PrintStream(o);
			p.println("<ADDRESS><A HREF=\"" + serverUrl + "\">" + serverName + " " + serverVersion + "</A></ADDRESS>");
		}

		public static void writeAddress(StringBuffer sb) throws IOException
		{
			sb.append("<ADDRESS><A HREF=\"" + serverUrl + "\">" + serverName + " " + serverVersion + "</A></ADDRESS>");
		}
	}
}

class ServeConfig implements ServletConfig
{

	private ServletContext context;
	private Hashtable init_params;
	private String servletName;

	public ServeConfig(ServletContext context)
	{
		this(context, null, "undefined");
	}

	public ServeConfig(ServletContext context, Hashtable initParams, String servletName)
	{
		this.context = context;
		this.init_params = initParams;
		this.servletName = servletName;
	}

	// Methods from ServletConfig.

	/// Returns the context for the servlet.
	public ServletContext getServletContext()
	{
		return context;
	}

	/// Gets an initialization parameter of the servlet.
	// @param name the parameter name
	public String getInitParameter(String name)
	{
		// This server supports servlet init params. :)
		if (init_params != null)
			return (String) init_params.get(name);
		return null;
	}

	/// Gets the names of the initialization parameters of the servlet.
	// @param name the parameter name
	public Enumeration getInitParameterNames()
	{
		// This server does:) support servlet init params.
		if (init_params != null)
			return init_params.keys();
		return new Vector().elements();
	}
	// 2.2
	public String getServletName()
	{
		return servletName;
	}
}

///////////////////////////////////////////////////////////////////////
/**
 * provides request/response
 */
class ServeConnection implements Runnable, HttpServletRequest, HttpServletResponse
{

	private Socket socket;
	private Serve serve;

	private ServletInputStream in;
	private ServletOutputStream out;

	private Hashtable formParameters;
	private Hashtable attributes = new Hashtable();

	public final static String WWWFORMURLENCODE = "application/x-www-form-urlencoded";
	public final static String TRANSFERENCODING = "Transfer-Encoding";
	public final static String CHUNKED = "chunked";
	public final static String CONTENTLENGTH = "Content-Length";
	public final static String CONTENTTYPE = "Content-Type";
	public final static String SETCOOKIE = "Set-Cookie";
	public final static String COOKIE = "Cookie";

	public final static String SESSION_COOKIE_NAME = "JSESSIONID";
	// URL rewriting http://www.myserver.com/catalog/index.html;jsessionid=mysession1928
	// like: http://www.sun.com/2001-0227/sunblade/;$sessionid$AD5RQ0IAADJAZAMTA1LU5YQ

	private String reqMethod; // == null by default
	private String reqUriPath;
	private String reqProtocol;
	private String reqCharEncoding;
	private String remoteUser;
	private String authType;
	private boolean oneOne; // HTTP/1.1 or better
	private boolean reqMime;
	String reqQuery = null;
	private Vector reqHeaderNames = new Vector();
	private Vector reqHeaderValues = new Vector();
	private Locale locale; // = java.util.Locale.getDefault();
	private int uriLen;
	private static final Hashtable EMPTYHASHTABLE = new Hashtable();

	private Vector outCookies;
	private Vector inCookies;

	private String sessionCookieValue;

	/// Constructor.
	public ServeConnection(Socket socket, Serve serve)
	{
		// Save arguments.
		this.socket = socket;
		this.serve = serve;

		// Start a separate thread to read and handle the request.
		Thread thread = new Thread(this, "Request handler");
		thread.start();
	}

	// Methods from Runnable.
	public void run()
	{
		try
		{
			// Get the streams.
			in = new ServeInputStream(socket.getInputStream());
			out = new ServeOutputStream(socket.getOutputStream(), this);
		}
		catch (IOException e)
		{
			problem("Getting streams: " + e.getMessage(), SC_BAD_REQUEST);
		}

		parseRequest();

		if (serve.isAccessLogged())
		{
			serve.log(
				socket.getInetAddress().toString()
					+ ' '
					+ reqMethod
					+ ' '
					+ reqUriPath
					+ ' '
					+ resCode
					+ (serve.isShowReferer() ? "| " + getHeader("Referer") : "")
					+ (serve.isShowUserAgent() ? "| " + getHeader("User-Agent") : ""));
		}

		try
		{
			socket.close();
		}
		catch (IOException e)
		{ /* ignore */
		}
	}

	private void parseRequest()
	{
		byte[] lineBytes = new byte[4096];
		int len;
		String line;

		try
		{
			// Read the first line of the request.
			len = in.readLine(lineBytes, 0, lineBytes.length);
			if (len == -1 || len == 0)
			{
				problem("Empty request", SC_BAD_REQUEST);
				return;
			}
			line = new String(lineBytes, 0, len);
			StringTokenizer ust = new StringTokenizer(line);
			reqProtocol = null;
			if (ust.hasMoreTokens())
			{
				reqMethod = ust.nextToken();
				if (ust.hasMoreTokens())
				{
					reqUriPath = Acme.Utils.urlDecoder(ust.nextToken());
					if (ust.hasMoreTokens())
					{
						reqProtocol = ust.nextToken();
						oneOne = !reqProtocol.toUpperCase().equals("HTTP/1.0");
						reqMime = true;
						// Read the rest of the lines.
						String s;
						while ((s = ((ServeInputStream) in).readLine()) != null)
						{
							if (s.length() == 0)
								break;

							int c = s.indexOf(':', 0);
							if (c > 0)
							{
								String key = s.substring(0, c).trim();
								String value = s.substring(c + 1, s.length()).trim();
								reqHeaderNames.addElement(key.toLowerCase());
								reqHeaderValues.addElement(value);
							}
							else
								serve.log("header field without ':'");
						}
					}
					else
					{
						reqProtocol = "HTTP/0.9";
						oneOne = false;
						reqMime = false;
					}
				}
			}
			if (reqProtocol == null)
			{
				problem("Malformed request line", SC_BAD_REQUEST);
				return;
			}
			// Check Host: header in HTTP/1.1 requests.
			if (oneOne)
			{
				String host = getHeader("host");
				if (host == null)
				{
					problem("Host header missing on HTTP/1.1 request", SC_BAD_REQUEST);
					return;
				}
			}

			// Decode %-sequences.
			//reqUriPath = decode( reqUriPath );
			// Split off query string, if any.
			int qmark = reqUriPath.indexOf('?');
			if (qmark > -1)
			{
				reqQuery = reqUriPath.substring(qmark + 1);
				reqUriPath = reqUriPath.substring(0, qmark);
			}
			if (CHUNKED.equals(getHeader(TRANSFERENCODING)))
			{
				setHeader(CONTENTLENGTH, null);
				((ServeInputStream) in).chunking(true);
			}

			Object[] os = serve.registry.get(reqUriPath);
			if (os != null)
			{
				uriLen = ((Integer) os[1]).intValue();
				runServlet((HttpServlet) os[0]);
			}
		}
		catch (IOException e)
		{
			problem("Reading request: " + e.getMessage(), SC_BAD_REQUEST);
		}
	}

	private void runServlet(HttpServlet servlete)
	{
		// Set default response fields.
		setStatus(SC_OK);
		setDateHeader("Date", System.currentTimeMillis());
		setHeader("Server", Serve.Identification.serverName + "/" + Serve.Identification.serverVersion);
		setHeader("MIME-Version", "1.0");
		try
		{
			parseCookies();
			if (authenificate())
			{
				if (servlete instanceof SingleThreadModel)
					synchronized (servlete)
					{
						servlete.service((ServletRequest) this, (ServletResponse) this);
					}
				else
					servlete.service((ServletRequest) this, (ServletResponse) this);
			}
		}
		catch (IOException e)
		{
			e.printStackTrace();
			problem("IO problem running servlet: " + e.toString(), SC_BAD_REQUEST);
		}
		catch (ServletException e)
		{
			problem("problem running servlet: " + e.toString(), SC_BAD_REQUEST);
		}
		catch (Exception e)
		{
			problem("unexpected problem running servlet: " + e.toString(), SC_INTERNAL_SERVER_ERROR);
			e.printStackTrace();
		}
	}

	private boolean authenificate() throws IOException
	{
		Object[] o = serve.realms.get(getPathInfo());
		BasicAuthRealm realm = null;
		if (o != null)
			realm = (BasicAuthRealm) o[0];
		//System.err.println("looking for realm for path "+getPathInfo()+" in "+serve.realms+" found "+realm);
		if (realm == null)
			return true;

		String credentials = getHeader("Authorization");

		if (credentials != null)
		{
			credentials = credentials.substring(credentials.indexOf(' ') + 1);
			try
			{
				ByteArrayOutputStream baos = new ByteArrayOutputStream();
				serve.base64Dec.decodeBuffer(new StringBufferInputStream(credentials), baos);
				credentials = baos.toString();
			}
			catch (IOException e)
			{
				e.printStackTrace();
			}
			int i = credentials.indexOf(':');
			String user = credentials.substring(0, i);
			String password = credentials.substring(i + 1);
			remoteUser = user;
			authType = "Basic"; // support only basic authenification
			String realPassword = (String) realm.get(user);
			//System.err.println("User "+user+" Password "+password+" real "+realPassword);
			if (realPassword != null && realPassword.equals(password))
				return true;
		}

		setStatus(SC_UNAUTHORIZED);
		setHeader("WWW-Authenticate", "basic realm=\"" + realm.name() + '"');
		writeHeaders();
		return false;
	}

	private void problem(String logMessage, int resCode)
	{
		serve.log(logMessage);
		try
		{
			sendError(resCode);
		}
		catch (IllegalStateException e)
		{ /* ignore */
		}
		catch (IOException e)
		{ /* ignore */
		}
	}

	private String decode(String str)
	{
		StringBuffer result = new StringBuffer();
		int l = str.length();
		for (int i = 0; i < l; ++i)
		{
			char c = str.charAt(i);
			if (c == '%' && i + 2 < l)
			{
				char c1 = str.charAt(i + 1);
				char c2 = str.charAt(i + 2);
				if (isHexit(c1) && isHexit(c2))
				{
					result.append((char) (hexit(c1) * 16 + hexit(c2)));
					i += 2;
				}
				else
					result.append(c);
			}
			else if (c == '+')
				result.append(' ');
			else
				result.append(c);
		}
		return result.toString();
	}

	private boolean isHexit(char c)
	{
		String legalChars = "0123456789abcdefABCDEF";
		return (legalChars.indexOf(c) != -1);
	}

	private int hexit(char c)
	{
		if (c >= '0' && c <= '9')
			return c - '0';
		if (c >= 'a' && c <= 'f')
			return c - 'a' + 10;
		if (c >= 'A' && c <= 'F')
			return c - 'A' + 10;
		return 0; // shouldn't happen, we're guarded by isHexit()
	}

	void parseCookies()
	{
		if (inCookies == null)
			inCookies = new Vector();
		try
		{
			String cookie_name;
			String cookie_value;
			String cookie_path;
			String cookies = getHeader(COOKIE);
			if (cookies == null)
				return;
			//Enumeration e = getHeaders(COOKIE);
			//while(e.hasMoreElements())
			//	cookies += (String)e.nextElement();
			StringTokenizer st = new StringTokenizer(cookies, ";", true);
			// TODO: write a parser to avoid tokenizers
			while (st.hasMoreTokens())
			{
				StringTokenizer st2 = new StringTokenizer(st.nextToken(), "=");
				if (st2.hasMoreTokens())
				{
					cookie_name = st2.nextToken().trim();
					if (st2.hasMoreTokens())
					{
						cookie_value = st2.nextToken(",").trim();
						if (cookie_value.length() > 0 && cookie_value.charAt(0) == '=')
							cookie_value = cookie_value.substring(1);
						cookie_path = "/";
						while (st2.hasMoreTokens())
						{
							String cookie_atr = st2.nextToken();
							if ("$Version".equalsIgnoreCase(cookie_atr)
								|| "$Path".equalsIgnoreCase(cookie_atr)
								|| "$Domain".equalsIgnoreCase(cookie_atr))
								continue;
							cookie_path = st2.nextToken();
						}
						Cookie cookie = new Cookie(cookie_name, cookie_value);
						//System.err.println("Cookie set:"+cookie_name+':'+cookie_value);
						cookie.setPath(cookie_path);
						inCookies.addElement(cookie);
						if (SESSION_COOKIE_NAME.equals(cookie_name) && sessionCookieValue == null)
						{
							sessionCookieValue = cookie_value;
							try
							{
								((AcmeSession) serve.getSession(sessionCookieValue)).userTouch();
							}
							catch (Throwable t)
							{
							}
						}
					}
				}
			}
		}
		catch (Throwable e)
		{
			System.err.println("prepareCookies(): " + e);
			e.printStackTrace();
		}
	}

	// Methods from ServletRequest.

	/// Returns the size of the request entity data, or -1 if not known.
	// Same as the CGI variable CONTENT_LENGTH.
	public int getContentLength()
	{
		return getIntHeader(CONTENTLENGTH, -1);
	}

	/// Returns the MIME type of the request entity data, or null if
	// not known.
	// Same as the CGI variable CONTENT_TYPE.
	public String getContentType()
	{
		return getHeader(CONTENTTYPE);
	}

	/// Returns the protocol and version of the request as a string of
	// the form <protocol>/<major version>.<minor version>.
	// Same as the CGI variable SERVER_PROTOCOL.
	public String getProtocol()
	{
		return reqProtocol;
	}

	///  Returns the scheme of the URL used in this request, for example
	// "http", "https", or "ftp".  Different schemes have different rules
	// for constructing URLs, as noted in RFC 1738.  The URL used to create
	// a request may be reconstructed using this scheme, the server name
	// and port, and additional information such as URIs.
	public String getScheme()
	{
		if (socket.getClass().getName().toUpperCase().indexOf("SSL") < 0)
			return "http";
		else
			return "https";
	}

	/// Returns the host name of the server as used in the <host> part of
	// the request URI.
	// Same as the CGI variable SERVER_NAME.
	public String getServerName()
	{
		String serverName;
		int serverPort = 80;
		serverName = getHeader("Host");
		if (serverName != null && serverName.length() > 0)
		{
			int colon = serverName.indexOf(':');
			if (colon >= 0)
			{
				if (colon < serverName.length())
					serverPort = Integer.parseInt(serverName.substring(colon + 1));
				serverName = serverName.substring(0, colon);
			}
		}

		if (serverName == null)
		{
			try
			{
				serverName = InetAddress.getLocalHost().getHostName();
			}
			catch (java.net.UnknownHostException ignore)
			{
				serverName = "127.0.0.0";
			}
		}

		int slash = serverName.indexOf("/");
		if (slash >= 0)
			serverName = serverName.substring(slash + 1);
		return serverName;
	}

	/// Returns the port number on which this request was received as used in
	// the <port> part of the request URI.
	// Same as the CGI variable SERVER_PORT.
	public int getServerPort()
	{
		return socket.getLocalPort();
	}

	/// Returns the IP address of the agent that sent the request.
	// Same as the CGI variable REMOTE_ADDR.
	public String getRemoteAddr()
	{
		return socket.getInetAddress().toString();
	}

	/// Returns the fully qualified host name of the agent that sent the
	// request.
	// Same as the CGI variable REMOTE_HOST.
	public String getRemoteHost()
	{
		String result = socket.getInetAddress().getHostName();
		return result != null ? result : getRemoteAddr();
	}

	/// Applies alias rules to the specified virtual path and returns the
	// corresponding real path, or null if the translation can not be
	// performed for any reason.  For example, an HTTP servlet would
	// resolve the path using the virtual docroot, if virtual hosting is
	// enabled, and with the default docroot otherwise.  Calling this
	// method with the string "/" as an argument returns the document root.
	public String getRealPath(String path)
	{
		return serve.getRealPath(path);
	}

	/// Returns an input stream for reading request data.
	// @exception IllegalStateException if getReader has already been called
	// @exception IOException on other I/O-related errors
	public ServletInputStream getInputStream() throws IOException
	{
		synchronized (in)
		{
			if (((ServeInputStream) in).isReturnedAsReader())
				throw new IllegalStateException("Already returned as a reader.");
			((ServeInputStream) in).setReturnedAsReader(true);
		}
		return in;
	}

	/// Returns a buffered reader for reading request data.
	// @exception UnsupportedEncodingException if the character set encoding isn't supported
	// @exception IllegalStateException if getInputStream has already been called
	// @exception IOException on other I/O-related errors
	public BufferedReader getReader()
	{
		synchronized (in)
		{
			if (((ServeInputStream) in).isReturnedAsStream())
				throw new IllegalStateException("Already returned as a stream.");
			((ServeInputStream) in).setReturnedAsStream(true);
		}
		if (reqCharEncoding != null)
			try
			{
				return new BufferedReader(new InputStreamReader(in, reqCharEncoding));
			}
			catch (UnsupportedEncodingException uee)
			{
			}
		return new BufferedReader(new InputStreamReader(in));
	}

	private Hashtable getParametersFromRequest()
	{
		Hashtable result = null;
		//System.out.println("Req:"+reqMethod+" con:"+getContentType()+" eq "+WWWFORMURLENCODE.equals(getContentType()));
		if ("GET".equals(reqMethod))
		{
			if (reqQuery != null)
				try
				{
					result = HttpUtils.parseQueryString(reqQuery);
				}
				catch (IllegalArgumentException ex)
				{
				}
		}
		else if ("POST".equals(reqMethod))
			if (WWWFORMURLENCODE.equals(getContentType()))
				try
				{
					result = HttpUtils.parsePostData(getContentLength(), getInputStream());
					if (reqQuery != null && reqQuery.length() > 0)
					{
						result.putAll(HttpUtils.parseQueryString(reqQuery));
					}
				}
				catch (Exception ex)
				{
					serve.log("Exception " + ex + " at parsing post data of length " + getContentLength());
				}
			else
				try
				{
					if (reqQuery != null)
						result = HttpUtils.parseQueryString(reqQuery);
				}
				catch (Exception ex)
				{
				}
		return result != null ? result : EMPTYHASHTABLE;
	}

	/// Returns the parameter names for this request.
	public Enumeration getParameterNames()
	{
		if (formParameters == null)
			formParameters = getParametersFromRequest();
		return formParameters.keys();
	}

	/// Returns the value of the specified query string parameter, or null
	// if not found.
	// @param name the parameter name
	public String getParameter(String name)
	{
		String[] params = getParameterValues(name);
		if (params == null || params.length == 0)
			return null;

		return params[0];
	}

	/// Returns the values of the specified parameter for the request as an
	// array of strings, or null if the named parameter does not exist.
	public String[] getParameterValues(String name)
	{
		if (formParameters == null)
			getParameterNames();

		return (String[]) formParameters.get(name);
	}

	/// Returns the value of the named attribute of the request, or null if
	// the attribute does not exist.  This method allows access to request
	// information not already provided by the other methods in this interface.
	public Object getAttribute(String name)
	{
		return attributes.get(name);
	}

	// Methods from HttpServletRequest.

	/// Gets the array of cookies found in this request.
	public Cookie[] getCookies()
	{
		Cookie[] cookieArray = new Cookie[inCookies.size()];
		inCookies.copyInto(cookieArray);
		return cookieArray;
	}

	/// Returns the method with which the request was made. This can be "GET",
	// "HEAD", "POST", or an extension method.
	// Same as the CGI variable REQUEST_METHOD.
	public String getMethod()
	{
		return reqMethod;
	}

	/***
	  Returns the part of this request's URL from the protocol name up to
	  the query string in the first line of the HTTP request.
	  To reconstruct an URL with a scheme and host,
	  use HttpUtils.getRequestURL(javax.servlet.http.HttpServletRequest).
	*/
	/// Returns the full request URI.
	public String getRequestURI()
	{
		return reqUriPath;
	}

	/** Reconstructs the URL the client used to make the request.
	 *  The returned URL contains a protocol, server name, port number,
	 *  and server path, but it does not include query string parameters. <br>
	 *  Because this method returns a StringBuffer, not a string, you can modify the
	 *  URL easily, for example, to append query parameters. 
	 * <p>
	 * This method is useful for creating redirect messages and for reporting errors.
	 *
	 * @return a StringBuffer object containing the reconstructed URL
	 * @since 2.3
	 */
	public java.lang.StringBuffer getRequestURL()
	{
		return new StringBuffer()
			.append(getScheme())
			.append("://")
			.append(serve.hostName)
			.append(serve.port == 80 ? "" : String.valueOf(serve.port))
			.append(getRequestURI());
	}

	/// Returns the part of the request URI that referred to the servlet being
	// invoked.
	// Analogous to the CGI variable SCRIPT_NAME.
	public String getServletPath()
	{
		// In this server, the entire path is regexp-matched against the
		// servlet pattern, so there's no good way to distinguish which
		// part refers to the servlet.
		return uriLen > 0 ? reqUriPath.substring(0, uriLen) : "";
	}

	/// Returns optional extra path information following the servlet path, but
	// immediately preceding the query string.  Returns null if not specified.
	// Same as the CGI variable PATH_INFO.
	public String getPathInfo()
	{
		// In this server, the entire path is regexp-matched against the
		// servlet pattern, so there's no good way to distinguish which
		// part refers to the servlet.
		return uriLen >= reqUriPath.length() ? null : reqUriPath.substring(uriLen);
	}

	/// Returns extra path information translated to a real path.  Returns
	// null if no extra path information was specified.
	// Same as the CGI variable PATH_TRANSLATED.
	public String getPathTranslated()
	{
		// In this server, the entire path is regexp-matched against the
		// servlet pattern, so there's no good way to distinguish which
		// part refers to the servlet.
		return getRealPath(getPathInfo());
	}

	/// Returns the query string part of the servlet URI, or null if not known.
	// Same as the CGI variable QUERY_STRING.
	public String getQueryString()
	{
		return reqQuery;
	}

	/// Returns the name of the user making this request, or null if not known.
	// Same as the CGI variable REMOTE_USER.
	public String getRemoteUser()
	{
		return remoteUser;
	}

	/// Returns the authentication scheme of the request, or null if none.
	// Same as the CGI variable AUTH_TYPE.
	public String getAuthType()
	{
		return authType;
	}

	/// Returns the value of a header field, or null if not known.
	// Same as the information passed in the CGI variabled HTTP_*.
	// @param name the header field name
	public String getHeader(String name)
	{
		int i = reqHeaderNames.indexOf(name.toLowerCase());
		if (i == -1)
			return null;
		return (String) reqHeaderValues.elementAt(i);
	}

	public int getIntHeader(String name)
	{
		return getIntHeader(name, 0);
	}

	/// Returns the value of an integer header field.
	// @param name the header field name
	// @param def the integer value to return if header not found or invalid
	public int getIntHeader(String name, int def)
	{
		String val = getHeader(name);
		if (val == null)
			return def;
		try
		{
			return Integer.parseInt(val);
		}
		catch (Exception e)
		{
			return def;
		}
	}

	/// Returns the value of a long header field.
	// @param name the header field name
	// @param def the long value to return if header not found or invalid
	public long getLongHeader(String name, long def)
	{
		String val = getHeader(name);
		if (val == null)
			return def;
		try
		{
			return Long.parseLong(val);
		}
		catch (Exception e)
		{
			return def;
		}
	}

	public long getDateHeader(String name)
	{
		String val = getHeader(name);
		if (val == null)
			return 0;
		try
		{
			return headerdateformat.parse(val).getTime();
		}
		catch (Exception e)
		{
			throw new IllegalArgumentException(
				"Value " + val + " can't be converted to Date using " + headerdateformat.toPattern());
		}

	}
	/// Returns the value of a date header field.
	// @param name the header field name
	// @param def the date value to return if header not found or invalid
	public long getDateHeader(String name, long def)
	{
		String val = getHeader(name);
		if (val == null)
			return def;
		try
		{
			return DateFormat.getDateInstance().parse(val).getTime();
		}
		catch (Exception e)
		{
			return def;
		}
	}

	/// Returns an Enumeration of the header names.
	public Enumeration getHeaderNames()
	{
		return reqHeaderNames.elements();
	}

	/// Gets the current valid session associated with this request, if
	// create is false or, if necessary, creates a new session for the
	// request, if create is true.
	// <P>
	// Note: to ensure the session is properly maintained, the servlet
	// developer must call this method (at least once) before any output
	// is written to the response.
	// <P>
	// Additionally, application-writers need to be aware that newly
	// created sessions (that is, sessions for which HttpSession.isNew
	// returns true) do not have any application-specific state.
	public synchronized HttpSession getSession(boolean create)
	{
		HttpSession result = null;
		if (sessionCookieValue != null)
		{
			result = (HttpSession) serve.getSession(sessionCookieValue);
			if (result != null && ((AcmeSession) result).isValid() == false)
			{
				serve.removeSession(sessionCookieValue);
				result = null;
			}
		}
		if (result == null && create)
		{
			result = serve.createSession();
		}
		if (result != null)
			sessionCookieValue = result.getId();
		return result;
	}

	// JSDK 2.1
	public HttpSession getSession()
	{
		return getSession(true);
	}

	public boolean isRequestedSessionIdFromURL()
	{</p
  

你可能感兴趣的:(C++,socket,servlet,cgi,WAP)