/* * Server.java * * Brazil project web application toolkit, * export version: 2.3 * Copyright (c) 1998-2008 Sun Microsystems, Inc. * * Sun Public License Notice * * The contents of this file are subject to the Sun Public License Version * 1.0 (the "License"). You may not use this file except in compliance with * the License. A copy of the License is included as the file "license.terms", * and also available at http://www.sun.com/ * * The Original Code is from: * Brazil project web application toolkit release 2.3. * The Initial Developer of the Original Code is: suhler. * Portions created by suhler are Copyright (C) Sun Microsystems, Inc. * All Rights Reserved. * * Contributor(s): cstevens, drach, rinaldo, suhler. * * Version: 2.6 * Created by suhler on 98/09/14 * Last modified by suhler on 08/07/02 10:40:18 * * Version Histories: * * 2.6 08/07/02-10:40:18 (suhler) * diag fixes * * 2.5 08/02/20-13:06:50 (suhler) * Add server version id * * 2.4 05/07/12-12:46:41 (suhler) * better failure diags * * 2.3 04/11/30-15:11:26 (suhler) * fixed sccs version string * * 2.2 04/09/14-13:48:15 (suhler) * added "restart" method to enable server restarts with new configuration * * 2.1 02/10/01-16:34:53 (suhler) * version change * * 1.46 02/02/12-09:17:47 (suhler) * Don't exit silently if localHost can't resolve. * . * * 1.45 01/08/21-10:59:39 (suhler) * add "maxPost" to limit size of request data * * 1.44 01/08/13-09:09:30 (drach) * Change server.props back to Properties object. * * 1.43 01/08/07-14:19:29 (drach) * Move PropertiesList debug initialization to earliest possible point. * * 1.42 01/08/03-15:29:58 (drach) * Add PropertiesList * * 1.41 01/07/16-16:51:09 (suhler) * bump default server id * * 1.40 01/06/05-22:13:06 (drach) * Reduce access control for brazil.servlet package * * 1.39 01/04/12-11:28:46 (suhler) * remove shadow "name" * * 1.38 01/04/12-10:23:48 (suhler) * Fixed a performance bug (introduced version 1.18) that caused a * reverse DNS lookup on every connection bu calling: * sock.getInetAddress().getHostName() * * 1.37 01/03/12-17:30:19 (cstevens) * Close listening socket when Server finished running, to make testing easier. * * 1.36 01/03/05-16:22:06 (cstevens) * log message consistency * * 1.35 00/11/06-10:53:17 (suhler) * Make initFailure public * * 1.34 00/10/17-09:31:42 (suhler) * Added instance variable "initFailure" which may be set externally, causing * the server to stop after initialization completes. * * 1.33 00/06/28-15:22:24 (cstevens) * Merged changes between child workspace "/home/cstevens/ws/brazil/naws" and * parent workspace "/export/ws/brazil/naws". * * 1.30.1.1 00/06/28-15:16:59 (cstevens) * * 1.32 00/05/31-13:51:41 (suhler) * docs * * 1.31 00/05/17-10:39:09 (suhler) * Added null constructor so Server can be instantiaded with newInstance() * * 1.30 00/04/12-15:59:41 (cstevens) * Server uses initHandler to init initial handler. * * 1.29 00/03/29-16:43:59 (cstevens) * documentation * * 1.28 00/03/10-17:11:49 (cstevens) * Formatting of log messages. * * 1.27 99/11/17-15:37:17 (cstevens) * init * * 1.26 99/11/16-19:09:18 (cstevens) * expunge Server.initHandler and Server.initObject. * * 1.25 99/11/16-14:37:07 (cstevens) * Server.java: * 1. Name printed for thread in log message when a connection is accepted * should agree with the name printed for the thread when the Request does * something. * 2. Thread.interrupt() issues interacting with the test scripts. * * 1.24 99/11/09-20:24:30 (cstevens) * bugs revealed by writing tests. * * 1.23 99/11/03-17:52:45 (cstevens) * MultiHostHandler. * * 1.22 99/10/26-18:54:59 (cstevens) * Eliminate public methods Server.initHandler() and Server.initObject(). * Get rid of public variables Request.server and Request.sock: * A. In all cases, Request.server was not necessary; it was mainly used for * constructing the absolute URL for a redirect, so Request.redirect() was * rewritten to take an absolute or relative URL and do the right thing. * B. Request.sock was changed to Request.getSock(); it is still rarely used * for diagnostics and logging (e.g., ChainSawHandler). * * 1.21 99/10/25-15:40:34 (cstevens) * spelling * * 1.20 99/10/25-15:39:16 (cstevens) * Don't make initObject and initHandler public * * 1.19 99/10/14-14:57:38 (cstevens) * resolve wilcard imports. * * 1.18 99/10/14-14:16:21 (cstevens) * merge issues. logging "null" should be ignored. * * 1.17 99/10/14-13:17:32 (cstevens) * Merged changes between child workspace "/home/cstevens/ws/brazil/naws" and * parent workspace "/export/ws/brazil/naws". * * 1.15.1.3 99/10/14-13:11:08 (cstevens) * @author & @version * * 1.15.1.2 99/10/14-12:56:36 (cstevens) * Server.initFields removed * Server.initHandler and Server.initObject have extensive documentation now * that will hopefully make their existence compelling. * * 1.16 99/10/11-12:37:10 (suhler) * Merged changes between child workspace "/home/suhler/brazil/naws" and * parent workspace "/net/mack.eng/export/ws/brazil/naws". * * 1.14.1.1 99/10/11-12:32:42 (suhler) * Change to server, allowing it to stop. This uses undocumented * behavior of the JDK, so I'm ntsure its a good idea * * 1.15.1.1 99/10/08-16:53:35 (cstevens) * * 1.15 99/10/07-13:01:39 (cstevens) * javadoc lint. * * 1.14 99/10/04-17:16:28 (cstevens) * merge conflict * * 1.13 99/10/04-16:11:09 (cstevens) * Merged changes between child workspace "/home/cstevens/ws/brazil/naws" and * parent workspace "/export/ws/brazil/naws". * * 1.11.1.2 99/10/01-13:07:06 (cstevens) * Getting better with server.initHandler(); * * 1.12 99/10/01-11:44:05 (suhler) * Merged changes between child workspace "/home/suhler/brazil/naws" and * parent workspace "/net/mack.eng/export/ws/brazil/naws". * * 1.11.1.1 99/10/01-11:27:15 (cstevens) * Change logging to show prefix of Handler generating the log message. * * 1.11 99/09/30-12:12:14 (cstevens) * better logging. * handler= line in config file can contain either name of class, in which * case prefix will be "" or a symbolic name, in which case prefix will be * name + ".". This preserved backwards compatibility, but allows symbolic * names to be used. * * 1.10 99/09/29-16:10:56 (cstevens) * Consistent way of initializing Handlers and other things that want to get * attributes from the config file. Convenience method that constructs the * object, sets (via reflection) all the variables in the object that correspond * to values specified in the config file, and then calls init() on the object. * * 1.9.1.1 99/09/22-16:04:08 (suhler) * - put each server in its own thread group * - change the way log messages are generated (the "socket" argument is * no longer used and should be removed * - added a close() to stop the server (untested) * * 1.9 99/07/22-15:04:39 (suhler) * Added public maxThreads field to limit the max # of threads that * may be spawned by the server at once * * 1.8 99/06/29-14:35:09 (suhler) * added serverUrl method that overrides the default host name * * 1.7 99/06/04-13:55:01 (suhler) * modify log level * * 1.6 99/03/30-09:27:19 (suhler) * - documentation update * - bug fix: too many open files would kill server * * 1.5 99/02/03-14:13:09 (suhler) * added defaultPrefix to override the default, ("") * * 1.4 98/12/09-15:04:12 (suhler) * added thread count to log message * * 1.3 98/09/21-14:52:05 (suhler) * changed the package names * * 1.2 98/09/17-17:59:14 (rinaldo) * * 1.2 98/09/14-18:03:10 (Codemgr) * SunPro Code Manager data about conflicts, renames, etc... * Name history : 2 1 server/Server.java * Name history : 1 0 Server.java * * 1.1 98/09/14-18:03:09 (suhler) * date and time created 98/09/14 18:03:09 by suhler * */ package sunlabs.brazil.server; import sunlabs.brazil.properties.PropertiesList; import java.io.IOException; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.UnknownHostException; import java.util.Properties; /** * Yet another HTTP/1.1 server. * This class is the core of a light weight Web Server. This server * is started as a Thread listening on the supplied port, and * dispatches to an implementation of * a {@link Handler} to service http requests. If no handler is * supplied, then the {@link FileHandler} is used. * A {@link ChainHandler} is provided to allow multiple handlers in one server. *

* Limitations: *

* * @author Stephen Uhler (stephen.uhler@sun.com) * @author Colin Stevens (colin.stevens@sun.com) * @version 2.6 */ public class Server extends Thread { /** * Server version string. Should match release version. */ public final static String version = "2.3"; /** * The listening socket. Every time a new socket is accepted, * a new thread is created to read the HTTP requests from it. */ public ServerSocket listen; /** * The main Handler whose respond method is called for * every HTTP request. The respond method must be * thread-safe since it handles HTTP requests concurrently from all the * accepted sockets. * * @see Handler#respond */ private String handlerName; public Handler handler; /** * Hashtable containing arbitrary information that may be of interest to * a Handler. This table is available to both methods of the * {@link Handler} interface, as {@link Server#props} in the * {@link Handler#init(Server, String)} * method, and as the default properties of * {@link Request#props} in the {@link Handler#respond(Request)} * method. */ public Properties props = null; /** * The hostname that this Server should use to identify itself in * an HTTP Redirect. If null, the hostname is derived * by calling InetAddress.getHostAddress. *

* InetAddress.getHostName would generally be the wrong * thing to return because it returns only the base machine name * xxx and not the machine name as it needs to appear * to the rest of the network, such as xxx.yyy.com. *

* The default value is null. */ public String hostName = null; /** * The protocol used to access this resource. Normally http, but * can be changed for ssl to https */ public String protocol = "http"; /** * If non-null, restrict connections to just the specified ip addresses. *

* The default value is null. */ public InetAddress[] restrict = null; /** * The string to return as the value for the "Server:" line in the HTTP * response header. If null, then no "Server:" line is * returned. */ public String name = "Brazil/" + version; /** * The handler is passed a prefix to identify which items in the * properties object are relevent. By convention, non-empty strings * end with ".", allowing nested prefixes to be easily distinguished. */ public String prefix = ""; /** * Time in milliseconds before this Server closes an idle socket or * in-progress request. *

* The default value is 30000. */ public int timeout = 30000; /** * Maximum number of consecutive requests allowed on a single * kept-alive socket. *

* The default value is 25. */ public int maxRequests = 25; /** * The max number of threads allowed for the entire VM * (default is 250). */ public int maxThreads = 250; /** * Maximum amout of POST data allowed per request (in bytes) * (default = 2Meg). */ public int maxPost = 2097152; // 2 Meg /** * Default buffer size for copies to and from client sockets. * (default is 8192) */ public int bufsize = 8192; /** * Count of accepted connections so far. */ public int acceptCount = 0; /** * Count of HTTP requests received so far. */ public int requestCount = 0; /** * Count of errors that occurred so far. */ public int errorCount = 0; /** * The diagnostic level. 0->least, 5->most */ public int logLevel = LOG_LOG; /** * If set, the server will terminate with an initialization failure * just before creating the listen socket. */ public boolean initFailure = false; ThreadGroup group; /** * Create a server using the provided listener socket. *

* This server will call the Handler.respond method * of the specified handler. The specified handler should either * respond to the request or perform further dispatches to other * handlers. * * @param listen * The socket this server should listen to. * For ordinary sockets, this is simply: * new ServerSocket(port), where port * is the network port to listen on. Alternate implementations * of ServerSocket, such as ssl versions * may be used instead. * @param handlerName * The name of the handler used to process http requests. * It must implement the {@link Handler} interface. * @param props * Arbitrary information made available to the handler. * May be null. * * @see FileHandler * @see ChainHandler */ public Server(ServerSocket listen, String handlerName, Properties props) { setup(listen, handlerName, props); } /** * Set up the server. this allows a server to be created with * newInstance() followed by setup(), instead of using the * above initializer, making it easier to start sub-classes * of the server. */ public Server() {} public boolean setup(ServerSocket listen, String handlerName, Properties props) { if (this.props != null) { return false; // alreasdy initialized } if (props == null) { props = new Properties(); } this.listen = listen; this.handlerName = handlerName; this.props=props; if (props.get("debugProps") != null) { PropertiesList.debug = true; } return true; } public boolean init() { if (props == null) { log(LOG_ERROR, "server", "Not properly initialized!"); return false; } group = new ThreadGroup(prefix); if (hostName == null) { try { hostName = InetAddress.getLocalHost().getHostAddress(); } catch (UnknownHostException e) { log(LOG_ERROR, "server", "Can't find my own name, using \"localhost\"" + " (redirects may not work)"); hostName="localhost"; } } if (Thread.currentThread().getName().startsWith("Thread-")) { Thread.currentThread().setName("server"); } handler = ChainHandler.initHandler(this, prefix, handlerName); log(LOG_DIAGNOSTIC, this, "Created handler: " + handler); if (handler == null) { return false; } if (initFailure) { log(LOG_ERROR, handlerName, "Initilization failure"); return false; } return true; } /** * Restart the server with a new handler. * @param newHandler Name of the handler to restart the server with */ public synchronized boolean restart(String newHandler) { String oldHandlerName=handlerName; Handler oldHandler=handler; handlerName=newHandler; if (init()) { log(LOG_INFORMATIONAL, this, "restarting with: " + newHandler); return true; } else { log(LOG_WARNING, this, newHandler + " is invalid, retaining old handler"); handlerName = oldHandlerName; handler=oldHandler; return false; } } /** * Loops, accepting socket connections and replying to HTTP requests. * This is called indirectly via Thread.start(). *

* Many things in the server are not initialized until this point, * because the user may have set some related configuration options * between the time this server was allocated and the time it was * started. For instance, the main Handler is not * initialized until now, because its Handler.init method * may have wanted to examine server member variables such as * hostName or bufsize. */ public void run() { try { if (init() == false) { return; } listen.setSoTimeout(0); while (true) { /* * Blocks until we have a connection on the socket. */ Socket sock = listen.accept(); String threadName = sock.getInetAddress().getHostAddress(); log(LOG_INFORMATIONAL, threadName, "new connection"); allowed: if (restrict != null) { InetAddress addr = sock.getInetAddress(); for (int i = 0; i < restrict.length; i++) { if (restrict[i].equals(addr)) { break allowed; } } log(LOG_DIAGNOSTIC, addr, "rejected request"); sock.close(); continue; } // A pseudo-busy loop!!! boolean warn=false; while (Thread.activeCount() > maxThreads) { if (!warn) { log(LOG_WARNING, sock, "Too many threads: " + acceptCount); } Thread.yield(); warn = true; } new Thread(group, new Connection(this, sock), threadName + "-" + acceptCount).start(); acceptCount++; } } catch (IOException e) { System.err.println("Server failed to start: " + e); } finally { try { listen.close(); Thread[] sub = new Thread[100]; int count; while ((count = group.enumerate(sub, true)) > 0) { for (int i = 0; i < count; i++) { sub[i].interrupt(); sub[i].join(); } yield(); } } catch (Exception e) {} group = null; } } /** * Stop the server, and kill all pending requests */ public void close() { try { this.interrupt(); this.join(); } catch (Exception e) {} log(LOG_WARNING, null, "server stopped"); } public static final int LOG_ERROR=1; // most severe public static final int LOG_WARNING=2; public static final int LOG_LOG=3; public static final int LOG_INFORMATIONAL=4; public static final int LOG_DIAGNOSTIC=5; // least useful /** * Logs information about the socket to System.out. * * @param level Controls the verbosity (0=least 5=most) * @param obj The object that the message relates to. * @param message The message to be logged. */ public void log(int level, Object obj, String message) { if (level <= logLevel) { System.out.print("LOG: " + level + " " + prefix + listen.getLocalPort() + "-" + Thread.currentThread().getName() + ": "); if (obj != null) { System.out.print(obj); System.out.print(": "); } System.out.println(message); } } }