sunlabs.brazil.server.Server Maven / Gradle / Ivy
Show all versions of sunlabs.brazil Show documentation
/*
* Server.java
*
* Brazil project web application toolkit,
* export version: 2.3
* Copyright (c) 1998-2005 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.4
* Created by suhler on 98/09/14
* Last modified by suhler on 05/07/12 12:46:41
*
* Version Histories:
*
* 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:
*
* - Starts a new thread for each connection. This may be expensive.
*
*
* @author Stephen Uhler ([email protected])
* @author Colin Stevens ([email protected])
* @version 2.4
*/
public class Server
extends Thread
{
/**
* 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/2.0";
/**
* 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);
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();
}
this.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);
}
}
}