001: /*
002: * Main.java
003: *
004: * Brazil project web application Framework,
005: * export version: 1.1
006: * Copyright (c) 1998-2001 Sun Microsystems, Inc.
007: *
008: * Sun Public License Notice
009: *
010: * The contents of this file are subject to the Sun Public License Version
011: * 1.0 (the "License"). You may not use this file except in compliance with
012: * the License. A copy of the License is included as the file "license.terms",
013: * and also available at http://www.sun.com/
014: *
015: * The Original Code is from:
016: * Brazil project web application Framework release 1.1.
017: * The Initial Developer of the Original Code is: suhler.
018: * Portions created by suhler are Copyright (C) Sun Microsystems, Inc.
019: * All Rights Reserved.
020: *
021: * Contributor(s): cstevens, suhler.
022: *
023: * Version: 1.24
024: * Created by suhler on 98/09/14
025: * Last modified by suhler on 01/01/16 17:10:48
026: */
027:
028: package sunlabs.brazil.server;
029:
030: /**
031: * Start an HTTP/1.1 server. The port number and handler class
032: * are provided as arguments.
033: *
034: * @author Stephen Uhler
035: * @author Colin Stevens
036: * @version 1.24, 01/16/01
037: */
038:
039: import sunlabs.brazil.util.Format;
040: import java.io.File;
041: import java.io.FileInputStream;
042: import java.io.InputStream;
043: import java.io.PrintStream;
044: import java.io.IOException;
045: import java.net.InetAddress;
046: import java.net.ServerSocket;
047: import java.net.BindException;
048: import java.net.UnknownHostException;
049: import java.util.Enumeration;
050: import java.util.Hashtable;
051: import java.util.Properties;
052: import java.util.Vector;
053: import java.util.StringTokenizer;
054: import java.lang.ClassNotFoundException;
055: import java.lang.IllegalAccessException;
056: import java.lang.InstantiationException;
057:
058: /**
059: * Sample <b>main</b> program for starting an http server.
060: *
061: * A new thread is started for each
062: * {@link Server},
063: * listening on a socket for HTTP connections.
064: * As each connection is accepted, a
065: * {@link Request} object is constructed,
066: * and the registered
067: * {@link Handler} is called.
068: * The configuration properties required by the server
069: * and the handler (or handlers), are gathered
070: * from command line arguments and configuration files specified on the
071: * command line.
072: * <p>
073: * The command line arguments are processed in order from left to
074: * right, with the results being accumulated in a properties object.
075: * The server is then started with {@link Server#props} set to the
076: * final value of the properties.
077: * Some of the properties are interpreted directly by the server,
078: * such as the port to listen on, or the handler to use
079: * (see {@link Server} for the complete list). The rest
080: * are arbitrary name/value pairs that may be used by the handler.
081: * <p>
082: * Although any of the options may be specified as name/value pairs,
083: * some of them: the ones interpreted by the server, the default
084: * handler ({@link FileHandler}, or {@link Main},
085: * may be prefixed with a "-".
086: * Those options are explained below:
087: * <dl>
088: * <dt> -p(ort) <dd>The network port number to run the server on (defaults to 8080)
089: * <dt> -r(oot) <dd>The document root directory, used by the FileHandler (defaults to .)
090: * <dt> -h(andler) <dd>The document handler class
091: * (defaults to {@link FileHandler sunlabs.brazil.handler.FileHandler})
092: * <dt> -c(onfig) <dd>A java properties file to add to the current properties.
093: * There may be several <i>-config</i> options. Each
094: * file is added to the current properties.
095: * If the properties file contains a <code>root</code>
096: * property, it is treated specially. See below.
097: * If the config file is not found in the filesystem,
098: * it is read from the jar file, with this class as the
099: * virtual current directory if a relative path
100: * is provided.
101: * <dt> -i(p) <dd>A space seperated list of hosts allowed to access this server
102: * If none are supplied, any host may connect. The ip addresses
103: * are resolved once, at startup time.
104: * <dt> -l(og) <dd>The log level (0->none, 5->max)
105: * Causes diagnostic output on the standard output.
106: * <dt> -s(tart) <dd>Start a server.
107: * Allows multiple servers to be started at once.
108: * As soon as a <i>-s</i> is processed, as server is
109: * started as if all the options had been processed,
110: * then the current properties are cleared.
111: * Any options that follow are used for the next server.
112: * <dt> -S(ubstitute) <dd>Perform ${..} substitutions on the current
113: * values.
114: * </dl>
115: * <p>
116: * Following these options, any additional additional pairs of
117: * names and values (no "-"'s allowed) are placed directly in
118: * {@link Server#props}.
119: * <p>
120: * If the resource "/sunlabs/brazil/server/config" is found, it is used
121: * to initialize the configuration.
122: * <p>
123: * If a non absolute <code>root</code> property is specified in a
124: * <i>configuration</i> file, it is modified to resolve relative to the
125: * directory containing the <i>configuration</i> file, and not the directory
126: * in which the server was started. If multiple <i>configuration</i> files
127: * with root properties (or <code>-r</code> options, or "root" properties)
128: * are specified, the last one tekes precidence.
129: * <p>
130: * The "serverClass" property may be set to the string to use as the server's
131: * class, instead of "sunlabs.brazil.server.server"
132: */
133:
134: public class Main {
135: static final String CONFIG = "/sunlabs/brazil/server/config";
136:
137: public static void
138: main(String[] args)
139: throws Exception
140: {
141: boolean started = false;
142: Properties config = new Properties();
143: initProps(config);
144:
145: /*
146: * Try to initialize the server from a resource in the
147: * jar file, if available
148: */
149:
150: {
151: InputStream in = Main.class.getResourceAsStream(CONFIG);
152: if (in != null) {
153: config.load(in);
154: System.out.println("Found default config file");
155: in.close();
156: }
157: }
158:
159: int i=0;
160: try {
161: String rootBase = null;
162: String root = null;
163:
164: for (i = 0; i < args.length; i++) {
165: started = false;
166: if (args[i].startsWith("-he")) {
167: throw new Exception(); // go to usage.
168: } else if (args[i].startsWith("-ha")) {
169: config.put("handler",args[++i]);
170: } else if (args[i].startsWith("-r")) {
171: root = args[++i];
172: rootBase = null;
173: config.put(FileHandler.ROOT, root);
174: } else if (args[i].startsWith("-ho")) {
175: config.put("host",args[++i]);
176: } else if (args[i].startsWith("-de")) {
177: config.put("default",args[++i]);
178: } else if (args[i].startsWith("-ip")) {
179: config.put("restrict",config.getProperty("restrict","") + " " +
180: InetAddress.getByName(args[++i]));
181: } else if (args[i].startsWith("-c")) {
182: String oldRoot = config.getProperty(FileHandler.ROOT);
183: File f = new File(args[++i]);
184:
185: /*
186: * Look for config file in filesystem. If found, slurp
187: * it in, and adjust the "root" if needed". Otherwise,
188: * look for it in the jar file (and leave the root alone).
189: */
190:
191: if (f.canRead()) {
192: try {
193: FileInputStream in = new FileInputStream(f);
194: config.load(in);
195: in.close();
196: } catch (Exception e) {
197: System.out.println("Warning: " + e);
198: continue;
199: }
200: String newRoot = config.getProperty(FileHandler.ROOT);
201: if (newRoot != oldRoot) {
202: root = newRoot;
203: rootBase = f.getPath();
204: }
205: } else {
206: InputStream in =Main.class.getResourceAsStream(args[i]);
207: if (in != null) {
208: config.load(in);
209: rootBase = null;
210: in.close();
211: }
212: }
213: } else if (args[i].startsWith("-p")) {
214: config.put("port",args[++i]);
215: } else if (args[i].startsWith("-S")) {
216: Enumeration enum = config.propertyNames();
217: while(enum.hasMoreElements()) {
218: String key = (String) enum.nextElement();
219: config.put(key,
220: Format.subst(config, config.getProperty(key)));
221: }
222: } else if (args[i].startsWith("-l")) {
223: config.put("log",args[++i]);
224: } else if (args[i].startsWith("-s")) {
225: if (startServer(config)) {
226: System.out.println("Server started on " +
227: config.getProperty("port", "8080"));
228: }
229:
230: // make sure servers do not share the same properties
231:
232: config = new Properties();
233: initProps(config);
234: started = true;
235: } else if (args[i].equals(FileHandler.ROOT)) {
236: root = args[++i];
237: rootBase = null;
238: config.put(FileHandler.ROOT, root);
239: } else if (!args[i].startsWith("-")) {
240: config.put(args[i],args[++i]);
241: } else {
242: System.out.println("Invalid flag : " + args[i]);
243: throw new Exception(); // go to usage.
244: }
245: }
246:
247: /*
248: * The last thing that specified a root wins.
249: *
250: * If the root was last specified on the command line, use that
251: * as the base root of the system.
252: *
253: * If the last thing that specified a root was a config file,
254: * then the root in the config file must be combined with the
255: * basename of that config file to produce the real root.
256: *
257: * If the root wasn't specified at all by anything, then that
258: * is equivalent to leaving the root as the current dir.
259: */
260:
261: if ((rootBase != null) && (new File(root).isAbsolute() == false)) {
262: rootBase = rootBase.replace('/', File.separatorChar);
263: rootBase = new File(rootBase).getParent();
264: if (rootBase != null) {
265: config.put(FileHandler.ROOT,
266: rootBase + File.separator + root);
267: }
268: }
269: } catch (ArrayIndexOutOfBoundsException e) {
270: System.out.println("Missing argument after: " + args[i-1]);
271: return;
272: } catch (Exception e) {
273: e.printStackTrace();
274: System.out.println("Usage: Main -conf <file> -port <port> " +
275: "-handler <class name> -root <doc_root> -ip <host> " +
276: "<name value>...");
277: return;
278: }
279:
280: if (!started && startServer(config)) {
281: System.out.println("Server started on " +
282: config.getProperty("port", "8080"));
283: }
284: }
285:
286: /**
287: * Start a server using the supplied properties. The following
288: * entries are treated. Specially:
289: * <dl>
290: * <dt> handler
291: * <dd> The name of the handler class (defaults to file handler)
292: * <dt> host
293: * <dd> The host name for this server
294: * <dt> log
295: * <dd> Diagnostic output level 0-5 (5=most output)
296: * <dt> maxReqests
297: * <dd> max number of requests for a single socket (default 25)
298: * <dt> listenQueue
299: * <dd> max size of the OS'slisten queue for server sockets
300: * <dt> maxThreads
301: * <dd> max number of threads allowed (defaults to 250)
302: * <dt> port
303: * <dd> Server port (default 8080)
304: * <dt> defaultPrefix
305: * <dd> prefix into the properties file, normally the empty string "".
306: * <dt> restrict
307: * <dd> list of hosts allowed to connect (defaults to no restriction)
308: * <dt> timeout
309: * <dd> The maximum time to wait for a client to send a complete request.
310: * Defaults to 30 seconds.
311: * <dt> interfaceHost
312: * <dd> If specified, a host name that represents the network to server.
313: * This is for hosts with multiple ip addresses. If no network
314: * host is specified, then connections for all interfaces are
315: * accepted
316: * </dl>
317: * @param config The configuration properties for the server
318: */
319:
320: public static boolean startServer(Properties config) {
321: String handler = FileHandler.class.getName();
322: int port = 8080;
323: int queue = 1024;
324:
325: handler = config.getProperty("handler", handler);
326: try {
327: String str = config.getProperty("port");
328: port = Integer.decode(str).intValue();
329: } catch (Exception e) {
330: }
331: try {
332: String str = config.getProperty("listenQueue");
333: queue = Integer.decode(str).intValue();
334: } catch (Exception e) {
335: }
336:
337: Server server;
338: String interfaceHost = config.getProperty("interfaceHost");
339: String serverClass = config.getProperty("serverClass");
340: try {
341: ServerSocket listen;
342: if (interfaceHost != null) {
343: listen = new ServerSocket(port, queue, InetAddress
344: .getByName(interfaceHost));
345: } else {
346: listen = new ServerSocket(port, queue);
347: }
348: if (serverClass != null) {
349: server = (Server) Class.forName(serverClass)
350: .newInstance();
351: server.setup(listen, handler, config);
352: } else {
353: server = new Server(listen, handler, config);
354: }
355: } catch (ClassNotFoundException e) {
356: System.out.println(serverClass
357: + " not found for class Server");
358: return false;
359: } catch (IllegalAccessException e) {
360: System.out.println(serverClass + " not available");
361: return false;
362: } catch (InstantiationException e) {
363: System.out.println(serverClass + " not instantiatable");
364: return false;
365: } catch (ClassCastException e) {
366: System.out.println(serverClass
367: + " is not a sub-class of server");
368: return false;
369: } catch (UnknownHostException e) {
370: System.out.println(interfaceHost
371: + " doesn't represent a known interface");
372: return false;
373: } catch (BindException e) {
374: System.out.println("Port " + port + " is already in use");
375: return false;
376: } catch (IOException e) {
377: System.out.println("Unable to start server on port " + port
378: + " :" + e);
379: return false;
380: }
381:
382: server.hostName = config.getProperty("host", server.hostName);
383: server.prefix = config.getProperty("defaultPrefix",
384: server.prefix);
385:
386: /*
387: * Set the max number of http requests permitted for this connection.
388: */
389: try {
390: String str = config.getProperty("maxThreads");
391: server.maxThreads = Integer.decode(str).intValue();
392: } catch (Exception e) {
393: }
394:
395: try {
396: String str = config.getProperty("timeout");
397: server.timeout = Integer.decode(str).intValue() * 1000;
398: } catch (Exception e) {
399: }
400:
401: /*
402: * Turn off keep alives entirely
403: */
404: if (config.containsKey("noKeepAlives")) {
405: server.maxRequests = 0;
406: }
407: try {
408: String str = config.getProperty("log");
409: server.logLevel = Integer.decode(str).intValue();
410: } catch (Exception e) {
411: }
412:
413: {
414: Vector restrict = new Vector();
415: String str = config.getProperty("restrict", "");
416: StringTokenizer st = new StringTokenizer(str);
417: while (st.hasMoreTokens()) {
418: try {
419: InetAddress addr = InetAddress.getByName(st
420: .nextToken());
421: restrict.addElement(addr);
422: } catch (Exception e) {
423: }
424: }
425: if (restrict.size() > 0) {
426: server.restrict = new InetAddress[restrict.size()];
427: restrict.copyInto(server.restrict);
428: }
429: }
430:
431: {
432: String str = config.getProperty("init", "");
433: StringTokenizer st = new StringTokenizer(str);
434: while (st.hasMoreTokens()) {
435: String init = st.nextToken();
436: server.log(Server.LOG_DIAGNOSTIC, "initializing", init);
437:
438: Object obj = initObject(server, init);
439:
440: if (obj == null) {
441: server.log(Server.LOG_DIAGNOSTIC, init,
442: "didn't initialize");
443: }
444: }
445: }
446:
447: server.start();
448: return true;
449: }
450:
451: private static Object initObject(Server server, String name) {
452: String className = server.props.getProperty(name + ".class");
453: if (className == null) {
454: className = name;
455: }
456: String prefix = name + ".";
457:
458: Object obj = null;
459: try {
460: Class type = Class.forName(className);
461: obj = type.newInstance();
462:
463: Class[] types = new Class[] { Server.class, String.class };
464: Object[] args = new Object[] { server, prefix };
465:
466: Object result = type.getMethod("init", types).invoke(obj,
467: args);
468: if (Boolean.FALSE.equals(result)) {
469: return null;
470: }
471: return obj;
472: } catch (ClassNotFoundException e) {
473: server.log(Server.LOG_WARNING, className, "no such class");
474: } catch (IllegalArgumentException e) {
475: server.log(Server.LOG_WARNING, className, "no such class");
476: } catch (NoSuchMethodException e) {
477: return obj;
478: } catch (Exception e) {
479: server.log(Server.LOG_WARNING, name, "error initializing");
480: e.printStackTrace();
481: }
482: return null;
483: }
484:
485: /**
486: * Initialize a properties file with some standard mime types
487: * The {@link FileHandler} only delivers files whose suffixes
488: * are known to map to mime types. The server is started with
489: * the suffixes: .html, .txt, .gif, .jpg, .css, .class, and .jar
490: * predefined. If additional types are required, they should be supplied as
491: * command line arguments.
492: */
493:
494: public static void initProps(Properties config) {
495: config.put("mime.html", "text/html");
496: config.put("mime.txt", "text/plain");
497: config.put("mime.gif", "image/gif");
498: config.put("mime.jpg", "image/jpeg");
499: config.put("mime.css", "text/x-css-stylesheet");
500: config.put("mime.class", "application/octet-stream");
501: config.put("mime.jar", "application/octet-stream");
502: config.put("mime.jib", "application/octet-stream");
503: }
504: }
|