/* * 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: *
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);
}
}
}