sunlabs.brazil.server.Main Maven / Gradle / Ivy
Show all versions of sunlabs.brazil Show documentation
/*
* Main.java
*
* Brazil project web application toolkit,
* export version: 2.3
* Copyright (c) 1998-2007 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, suhler.
*
* Version: 2.6
* Created by suhler on 98/09/14
* Last modified by suhler on 07/03/07 09:15:31
*
* Version Histories:
*
* 2.6 07/03/07-09:15:31 (suhler)
* minor doc fix
*
* 2.5 06/01/18-13:13:27 (suhler)
* add more default mime types
*
* 2.4 05/07/13-10:19:46 (suhler)
* add "-x" and "-D" flags
*
* 2.3 04/11/30-15:19:41 (suhler)
* fixed sccs version string
*
* 2.2 04/08/30-09:02:48 (suhler)
* "enum" became a reserved word, change to "enumer".
*
* 2.1 02/10/01-16:34:51 (suhler)
* version change
*
* 1.30 02/07/24-10:47:48 (suhler)
* doc updates
*
* 1.29 02/04/19-15:01:59 (drach)
* Removed dependency on BServletServerSocket
*
* 1.28 02/04/02-17:50:25 (drach)
* Add hooks so BrazilServlet can share code
*
* 1.27 01/08/21-10:59:49 (suhler)
* add support for "maxPost"
*
* 1.26 01/06/05-22:12:43 (drach)
* Reduce access control for brazil.servlet package
*
* 1.25 01/05/30-08:33:46 (drach)
* Reduce access control on selected fields and methods.
*
* 1.24 01/01/16-17:10:48 (suhler)
* add "timeout" to alter default server.timeout
*
* 1.23 00/12/04-08:58:26 (suhler)
* added -S option to perform ${..} substitution on config values
*
* 1.22 00/07/10-15:11:58 (cstevens)
* Main.main() and ResourceHandler.getResourceStream() didn't work under
* Windows due to platform-dependant file names. Class.getResourceAsStream()
* requires '/' as separator, while File.getParent() requires '\'. Added code
* to turn '/' into '\' and vice versa as necessary. Both methods now accept
* either '\' or '/' in file names and/or resource names.
*
* 1.21 00/05/17-10:38:35 (suhler)
* - added documentation for "root" property resolution
* - config files are seached for in the "jar" file if not found in the filesystem
* - main accepts a class name as as an alternate "Server" class
*
* 1.20 00/05/15-15:38:04 (cstevens)
* "root" property was being resolved w.r.t. the wrong config file. The last
* specification of the "root" property is now resolved w.r.t. the config file
* that specified the "root", not the last config file seen (which might be in
* a different directory).
*
* 1.19 00/04/17-16:39:31 (cstevens)
* root is relative to config file, so can type "run -config foo/bar/config.foo"
* and the "root" specified in that config file is relative to "foo/bar".
*
* 1.18 00/04/12-15:59:19 (cstevens)
* unneeded getClass()
*
* 1.17 99/11/16-19:09:07 (cstevens)
* expunge Server.initHandler and Server.initObject.
*
* 1.16 99/11/09-20:23:50 (cstevens)
* bugs revealed by writing tests.
*
* 1.15 99/11/03-10:38:37 (suhler)
* added "interfaceHost" option to restrict server socket to a single interface
*
* 1.14 99/10/26-18:53:34 (cstevens)
* inits and default values got broken by last set of changes.
*
* 1.13 99/10/14-12:53:02 (cstevens)
* Main runs all the classes specified in the "init" line in the config file
* before starting the server.
*
* 1.12 99/09/29-16:10:36 (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.11 99/07/30-10:53:13 (suhler)
* added "listenQueue" property to set the initial socket
* listen Q. defaults to 1024.
*
* 1.10 99/07/22-15:03:09 (suhler)
* add maxThreads option to set the mac # of allowable threads
*
* 1.9 99/07/09-10:05:57 (suhler)
* config file searched for in jar file first
*
* 1.8 99/03/30-09:25:11 (suhler)
* documentation updates
*
* 1.7 99/02/10-10:31:33 (suhler)
* fixed bug in previous version:
* - rename "prefix" to "defaultPrefix"
*
* 1.6 99/02/03-14:12:11 (suhler)
* - better error checking (still need more)
* - can start multiple servers at once (-start option)
* - can set initial property prefix (set "prefix" on the command line)
*
* 1.5 98/12/09-15:03:57 (suhler)
* added message for port in use
*
* 1.4 98/11/17-09:58:26 (suhler)
* Added "maxRequests" and "noKeepAlives" parameters to server.
*
* 1.3 98/09/22-16:58:17 (suhler)
* fixed arg parsing to handle extra properties on cmd line
*
* 1.2 98/09/21-14:51:22 (suhler)
* changed the package names
*
* 1.2 98/09/14-18:03:08 (Codemgr)
* SunPro Code Manager data about conflicts, renames, etc...
* Name history : 2 1 server/Main.java
* Name history : 1 0 Main.java
*
* 1.1 98/09/14-18:03:07 (suhler)
* date and time created 98/09/14 18:03:07 by suhler
*
*/
package sunlabs.brazil.server;
/**
* Start an HTTP/1.1 server. The port number and handler class
* are provided as arguments.
*
* @author Stephen Uhler
* @author Colin Stevens
* @version 2.6
*/
import sunlabs.brazil.util.Format;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.BindException;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import java.util.Vector;
import java.util.StringTokenizer;
import java.lang.ClassNotFoundException;
import java.lang.IllegalAccessException;
import java.lang.InstantiationException;
/**
* Sample main program for starting an http server.
*
* A new thread is started for each
* {@link Server},
* listening on a socket for HTTP connections.
* As each connection is accepted, a
* {@link Request} object is constructed,
* and the registered
* {@link Handler} is called.
* The configuration properties required by the server
* and the handler (or handlers), are gathered
* from command line arguments and configuration files specified on the
* command line.
*
* The command line arguments are processed in order from left to
* right, with the results being accumulated in a properties object.
* The server is then started with {@link Server#props} set to the
* final value of the properties.
* Some of the properties are interpreted directly by the server,
* such as the port to listen on, or the handler to use
* (see {@link Server} for the complete list). The rest
* are arbitrary name/value pairs that may be used by the handler.
*
* Although any of the options may be specified as name/value pairs,
* some of them: the ones interpreted by the server, the default
* handler ({@link FileHandler}, or {@link Main},
* may be prefixed with a "-".
* Those options are explained below:
*
* - -p(ort)
- The network port number to run the server on (defaults to 8080)
*
- -r(oot)
- The document root directory, used by the FileHandler (defaults to .)
*
- -h(andler)
- The document handler class
* (defaults to {@link FileHandler sunlabs.brazil.handler.FileHandler})
*
- -c(onfig)
- A java properties file to add to the current properties.
* There may be several -config options. Each
* file is added to the current properties.
* If the properties file contains a
root
* property, it is treated specially. See below.
* If the config file is not found in the filesystem,
* it is read from the jar file, with this class as the
* virtual current directory if a relative path
* is provided.
* - -i(p)
- A space seperated list of hosts allowed to access this server
* If none are supplied, any host may connect. The ip addresses
* are resolved once, at startup time.
*
- -l(og)
- The log level (0->none, 5->max)
* Causes diagnostic output on the standard output.
*
- -s(tart)
- Start a server.
* Allows multiple servers to be started at once.
* As soon as a -s is processed, as server is
* started as if all the options had been processed,
* then the current properties are cleared.
* Any options that follow are used for the next server.
*
- -D(elay) n
- delay "n" seconds. This is useful in conjuction
* with "-s" to allow the previous server to initialize
* before proceeding.
*
- -S(ubstitute)
- Perform ${..} substitutions on the current
* values.
*
- -x
- Don't read the default resource config (only if
* first).
*
*
* Following these options, any additional additional pairs of
* names and values (no "-"'s allowed) are placed directly in
* {@link Server#props}.
*
* If the resource "/sunlabs/brazil/server/config" is found, it is used
* to initialize the configuration.
*
* If a non absolute root
property is specified in a
* configuration file, it is modified to resolve relative to the
* directory containing the configuration file, and not the directory
* in which the server was started. If multiple configuration files
* with root properties (or -r
options, or "root" properties)
* are specified, the last one tekes precedence.
*
* The "serverClass" property may be set to the string to use as the server's
* class, instead of "sunlabs.brazil.server.Server"
*/
public class Main {
static final String CONFIG = "/sunlabs/brazil/server/config";
public static void
main(String[] args)
throws Exception
{
boolean started = false;
Properties config = new Properties();
initProps(config);
/*
* Try to initialize the server from a resource in the
* jar file, if available (unless first arg is "-x").
*/
if (args.length==0 || !args[0].equals("-x")) {
InputStream in = Main.class.getResourceAsStream(CONFIG);
if (in != null) {
config.load(in);
System.out.println("Found default config file");
in.close();
}
}
int i=0;
try {
String rootBase = null;
String root = null;
for (i = 0; i < args.length; i++) {
started = false;
if (args[i].startsWith("-he")) {
throw new Exception(); // go to usage.
} else if (args[i].startsWith("-ha")) {
config.put("handler",args[++i]);
} else if (args[i].startsWith("-r")) {
root = args[++i];
rootBase = null;
config.put(FileHandler.ROOT, root);
} else if (args[i].startsWith("-ho")) {
config.put("host",args[++i]);
} else if (args[i].startsWith("-de")) {
config.put("default",args[++i]);
} else if (args[i].startsWith("-ip")) {
config.put("restrict",config.getProperty("restrict","") + " " +
InetAddress.getByName(args[++i]));
} else if (args[i].startsWith("-c")) {
String oldRoot = config.getProperty(FileHandler.ROOT);
File f = new File(args[++i]);
/*
* Look for config file in filesystem. If found, slurp
* it in, and adjust the "root" if needed". Otherwise,
* look for it in the jar file (and leave the root alone).
*/
if (f.canRead()) {
try {
FileInputStream in = new FileInputStream(f);
config.load(in);
in.close();
} catch (Exception e) {
System.out.println("Warning: " + e);
continue;
}
String newRoot = config.getProperty(FileHandler.ROOT);
if (newRoot != oldRoot) {
root = newRoot;
rootBase = f.getPath();
}
} else {
InputStream in =Main.class.getResourceAsStream(args[i]);
if (in != null) {
config.load(in);
rootBase = null;
in.close();
}
}
} else if (args[i].startsWith("-p")) {
config.put("port",args[++i]);
} else if (args[i].startsWith("-D")) {
try {
System.out.println("Pausing...");
Thread.sleep(Integer.decode(args[++i]).intValue()*1000);
} catch (Exception e) {}
} else if (args[i].startsWith("-S")) {
Enumeration enumer = config.propertyNames();
while(enumer.hasMoreElements()) {
String key = (String) enumer.nextElement();
config.put(key,
Format.subst(config, config.getProperty(key)));
}
} else if (args[i].startsWith("-l")) {
config.put("log",args[++i]);
} else if (args[i].startsWith("-s")) {
if (startServer(config)) {
System.out.println("Server started on " +
config.getProperty("port", "8080"));
}
// make sure servers do not share the same properties
config = new Properties();
initProps(config);
started = true;
} else if (args[i].equals(FileHandler.ROOT)) {
root = args[++i];
rootBase = null;
config.put(FileHandler.ROOT, root);
} else if (!args[i].startsWith("-")) {
config.put(args[i],args[++i]);
} else if (args[i].startsWith("-x")) {
// ignore
} else {
System.out.println("Invalid flag : " + args[i]);
throw new Exception(); // go to usage.
}
}
/*
* The last thing that specified a root wins.
*
* If the root was last specified on the command line, use that
* as the base root of the system.
*
* If the last thing that specified a root was a config file,
* then the root in the config file must be combined with the
* basename of that config file to produce the real root.
*
* If the root wasn't specified at all by anything, then that
* is equivalent to leaving the root as the current dir.
*/
if ((rootBase != null) && (new File(root).isAbsolute() == false)) {
rootBase = rootBase.replace('/', File.separatorChar);
rootBase = new File(rootBase).getParent();
if (rootBase != null) {
config.put(FileHandler.ROOT,
rootBase + File.separator + root);
}
}
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Missing argument after: " + args[i-1]);
return;
} catch (Exception e) {
e.printStackTrace();
System.out.println("Usage: Main -conf -port " +
"-handler -root -ip " +
"...");
return;
}
if (!started && startServer(config)) {
System.out.println("Server started on " +
config.getProperty("port", "8080"));
}
}
/**
* Start a server using the supplied properties. The following
* entries are treated. Specially:
*
* - handler
*
- The name of the handler class (defaults to file handler)
*
- host
*
- The host name for this server
*
- log
*
- Diagnostic output level 0-5 (5=most output)
*
- maxRequests
*
- max number of requests for a single socket (default 25)
* when using persistent connections.
*
- listenQueue
*
- max size of the OS's listen queue for server sockets
*
- maxPost
*
- max size of a content-length for a Post or Put in bytes.
* (defaults to 2Meg). The absolute limit is 2GB.
*
- maxThreads
*
- max number of threads allowed (defaults to 250)
*
- port
*
- Server port (default 8080)
*
- defaultPrefix
*
- prefix into the properties file, normally the empty string "".
*
- restrict
*
- list of hosts allowed to connect (defaults to no restriction)
*
- timeout
*
- The maximum time to wait for a client to send a complete request.
* Defaults to 30 seconds.
*
- interfaceHost
*
- If specified, a host name that represents the network to server.
* This is for hosts with multiple ip addresses. If no network
* host is specified, then connections for all interfaces are
* accepted
*
* @param config The configuration properties for the server
*/
public static boolean
startServer(Properties config)
{
String handler = FileHandler.class.getName();
int port = 8080;
int queue = 1024;
// is the method invoked from a servlet?
boolean servlet = config.getProperty("servlet_name") != null;
handler = config.getProperty("handler", handler);
try {
String str = config.getProperty("port");
port = Integer.decode(str).intValue();
} catch (Exception e) {}
try {
String str = config.getProperty("listenQueue");
queue = Integer.decode(str).intValue();
} catch (Exception e) {}
Server server = null;
String interfaceHost = config.getProperty("interfaceHost");
String serverClass = config.getProperty("serverClass");
String errMsg = null;
try {
ServerSocket listen;
if (servlet) {
listen = (ServerSocket)Class.forName(
"sunlabs.brazil.servlet.BServletServerSocket"
).newInstance();
} else if (interfaceHost != null) {
listen = new ServerSocket(port, queue,
InetAddress.getByName(interfaceHost));
} else {
System.out.println("Creating server socket on port: " + port);
listen = new ServerSocket(port, queue);
}
if (serverClass != null) {
server = (Server) Class.forName(serverClass).newInstance();
server.setup(listen, handler, config);
} else {
server = new Server(listen, handler, config);
}
} catch (ClassNotFoundException e) {
errMsg = serverClass + " not found for class Server";
} catch (IllegalAccessException e) {
errMsg = serverClass + " not available";
} catch (InstantiationException e) {
errMsg = serverClass + " not instantiatable";
} catch (ClassCastException e) {
errMsg = serverClass + " is not a sub-class of server";
} catch (UnknownHostException e) {
errMsg = interfaceHost +
" doesn't represent a known interface";
} catch (BindException e) {
errMsg = "Port " + port + " is already in use: " + e;
} catch (IOException e) {
errMsg = "Unable to start server on port " + port +
" :" + e;
}
if (errMsg != null) {
if (servlet) {
config.put("_errMsg", errMsg);
} else {
System.out.println(errMsg);
}
return false;
}
server.hostName = config.getProperty("host", server.hostName);
server.prefix = config.getProperty("defaultPrefix", server.prefix);
try {
String str = config.getProperty("maxRequests");
server.maxRequests = Integer.decode(str).intValue();
} catch (Exception e) {}
try {
String str = config.getProperty("maxThreads");
server.maxThreads = Integer.decode(str).intValue();
} catch (Exception e) {}
try {
String str = config.getProperty("maxPost");
server.maxPost = Integer.decode(str).intValue();
} catch (Exception e) {}
try {
String str = config.getProperty("timeout");
server.timeout = Integer.decode(str).intValue() * 1000;
} catch (Exception e) {}
/*
* Turn off keep alives entirely
*/
if (config.containsKey("noKeepAlives")) {
server.maxRequests = 0;
}
try {
String str = config.getProperty("log");
server.logLevel = Integer.decode(str).intValue();
} catch (Exception e) {}
{
Vector restrict = new Vector();
String str = config.getProperty("restrict", "");
StringTokenizer st = new StringTokenizer(str);
while (st.hasMoreTokens()) {
try {
InetAddress addr = InetAddress.getByName(st.nextToken());
restrict.addElement(addr);
} catch (Exception e) {}
}
if (restrict.size() > 0) {
server.restrict = new InetAddress[restrict.size()];
restrict.copyInto(server.restrict);
}
}
{
String str = config.getProperty("init", "");
StringTokenizer st = new StringTokenizer(str);
while (st.hasMoreTokens()) {
String init = st.nextToken();
server.log(Server.LOG_DIAGNOSTIC, "initializing", init);
Object obj = initObject(server, init);
if (obj == null) {
server.log(Server.LOG_DIAGNOSTIC, init,
"didn't initialize");
}
}
}
if (servlet) {
config.put("_server", server);
} else {
server.start();
}
return true;
}
public static Object
initObject(Server server, String name)
{
String className = server.props.getProperty(name + ".class");
if (className == null) {
className = name;
}
String prefix = name + ".";
Object obj = null;
try {
Class type = Class.forName(className);
obj = type.newInstance();
Class[] types = new Class[] {Server.class, String.class};
Object[] args = new Object[] {server, prefix};
Object result = type.getMethod("init", types).invoke(obj, args);
if (Boolean.FALSE.equals(result)) {
return null;
}
return obj;
} catch (ClassNotFoundException e) {
server.log(Server.LOG_WARNING, className, "no such class");
} catch (IllegalArgumentException e) {
server.log(Server.LOG_WARNING, className, "no such class");
} catch (NoSuchMethodException e) {
return obj;
} catch (Exception e) {
server.log(Server.LOG_WARNING, name, "error initializing");
e.printStackTrace();
}
return null;
}
/**
* Initialize a properties file with some standard mime types
* The {@link FileHandler} only delivers files whose suffixes
* are known to map to mime types. The server is started with
* the suffixes: .html, .txt, .gif, .jpg, pdf, .png, .css, .class, and .jar
* predefined. If additional types are required, they should be supplied as
* command line arguments.
*/
public static void
initProps(Properties config) {
config.put("mime.html", "text/html");
config.put("mime.txt", "text/plain");
config.put("mime.css", "text/css");
config.put("mime.gif", "image/gif");
config.put("mime.jpg", "image/jpeg");
config.put("mime.png", "image/png");
config.put("mime.pdf", "application/pdf");
config.put("mime.class", "application/octet-stream");
config.put("mime.jar", "application/octet-stream");
config.put("mime.jib", "application/octet-stream");
}
}