001: /*
002: * Server.java
003: *
004: * Brazil project web application Framework,
005: * export version: 1.1
006: * Copyright (c) 1998-2000 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, rinaldo, suhler.
022: *
023: * Version: 1.35
024: * Created by suhler on 98/09/14
025: * Last modified by suhler on 00/11/06 10:53:17
026: */
027:
028: package sunlabs.brazil.server;
029:
030: import java.io.IOException;
031: import java.net.InetAddress;
032: import java.net.ServerSocket;
033: import java.net.Socket;
034: import java.util.Properties;
035:
036: /**
037: * Yet another HTTP/1.1 server.
038: * This class is the core of a light weight Web Server. This server
039: * is started as a Thread listening on the supplied port, and
040: * dispatches to an implementation of
041: * a {@link Handler} to service http requests. If no handler is
042: * supplied, then the {@link FileHandler} is used.
043: * A {@link ChainHandler} is provided to allow multiple handlers in one server.
044: * <p>
045: * Limitations:
046: * <ul>
047: * <li>Starts a new thread for each connection. This may be expensive.
048: * </ul>
049: *
050: * @author Stephen Uhler (stephen.uhler@sun.com)
051: * @author Colin Stevens (colin.stevens@sun.com)
052: * @version 1.35, 00/11/06
053: */
054:
055: public class Server extends Thread {
056: /**
057: * The listening socket. Every time a new socket is accepted,
058: * a new thread is created to read the HTTP requests from it.
059: */
060: public ServerSocket listen;
061:
062: /**
063: * The main Handler whose <code>respond</code> method is called for
064: * every HTTP request. The <code>respond</code> method must be
065: * thread-safe since it handles HTTP requests concurrently from all the
066: * accepted sockets.
067: *
068: * @see Handler#respond
069: */
070: private String handlerName;
071: public Handler handler;
072:
073: /**
074: * Hashtable containing arbitrary information that may be of interest to
075: * a Handler. This table is available to both methods of the
076: * {@link Handler} interface, as {@link Server#props} in the
077: * {@link Handler#init(Server, String)}
078: * method, and as the default properties of
079: * {@link Request#props} in the {@link Handler#respond(Request)}
080: * method.
081: */
082:
083: public Properties props = null;
084:
085: /**
086: * The hostname that this Server should use to identify itself in
087: * an HTTP Redirect. If <code>null</code>, the hostname is derived
088: * by calling <code>InetAddress.getHostAddress</code>.
089: * <p>
090: * <code>InetAddress.getHostName</code> would generally be the wrong
091: * thing to return because it returns only the base machine name
092: * <code>xxx</code> and not the machine name as it needs to appear
093: * to the rest of the network, such as <code>xxx.yyy.com</code>.
094: * <p>
095: * The default value is <code>null</code>.
096: */
097:
098: public String hostName = null;
099:
100: /**
101: * The protocol used to access this resource. Normally <code>http</code>, but
102: * can be changed for <code>ssl</code> to <code>https</code>
103: */
104:
105: public String protocol = "http";
106:
107: /**
108: * If non-null, restrict connections to just the specified ip addresses.
109: * <p>
110: * The default value is <code>null</code>.
111: */
112: public InetAddress[] restrict = null;
113:
114: /**
115: * The string to return as the value for the "Server:" line in the HTTP
116: * response header. If <code>null</code>, then no "Server:" line is
117: * returned.
118: */
119: public String name = "Brazil/1.0";
120:
121: /**
122: * The handler is passed a prefix to identify which items in the
123: * properties object are relevent. By convention, non-empty strings
124: * end with ".", allowing nested prefixes to be easily distinguished.
125: */
126:
127: public String prefix = "";
128:
129: /**
130: * Time in milliseconds before this Server closes an idle socket or
131: * in-progress request.
132: * <p>
133: * The default value is <code>30000</code>.
134: */
135: public int timeout = 30000;
136:
137: /**
138: * Maximum number of consecutive requests allowed on a single
139: * kept-alive socket.
140: * <p>
141: * The default value is <code>25</code>.
142: */
143: public int maxRequests = 25;
144:
145: /**
146: * The max number of threads allowed for the entire VM
147: */
148: public int maxThreads = 250;
149:
150: /**
151: * Default buffer size for copies to and from client sockets.
152: */
153: public int bufsize = 8192;
154:
155: /**
156: * Count of accepted connections so far.
157: */
158: public int acceptCount = 0;
159:
160: /**
161: * Count of HTTP requests received so far.
162: */
163: public int requestCount = 0;
164:
165: /**
166: * Count of errors that occurred so far.
167: */
168: public int errorCount = 0;
169:
170: /**
171: * The diagnostic level. 0->least, 5->most
172: */
173:
174: public int logLevel = LOG_LOG;
175:
176: /**
177: * If set, the server will terminate with an initialization failure
178: * just before creating the listen socket.
179: */
180:
181: public boolean initFailure = false;
182:
183: ThreadGroup group;
184:
185: /**
186: * Create a server using the provided listener socket.
187: * <p>
188: * This server will call the <code>Handler.respond</code> method
189: * of the specified handler. The specified handler should either
190: * respond to the request or perform further dispatches to other
191: * handlers.
192: *
193: * @param listen
194: * The socket this server should listen to.
195: * For ordinary sockets, this is simply: <code>
196: * new ServerSocket(port)</code>, where <code>port</code>
197: * is the network port to listen on. Alternate implementations
198: * of <code>ServerSocket</code>, such as <b>ssl</b> versions
199: * may be used instead.
200: * @param handlerName
201: * The name of the handler used to process http requests.
202: * It must implement the {@link Handler} interface.
203: * @param props
204: * Arbitrary information made available to the handler.
205: * May be <code>null</code>.
206: *
207: * @see FileHandler
208: * @see ChainHandler
209: */
210:
211: public Server(ServerSocket listen, String handlerName,
212: Properties props) {
213: setup(listen, handlerName, props);
214: }
215:
216: /**
217: * Set up the server. this allows a server to be created with
218: * newInstance() followed by setup(), instead of using the
219: * above initializer, making it easier to start sub-classes
220: * of the server.
221: */
222: public Server() {
223: }
224:
225: public boolean setup(ServerSocket listen, String handlerName,
226: Properties props) {
227: if (this .props != null) {
228: return false; // alreasdy initialized
229: }
230: if (props == null) {
231: props = new Properties();
232: }
233: this .listen = listen;
234: this .handlerName = handlerName;
235: this .props = props;
236: return true;
237: }
238:
239: boolean init() throws IOException {
240: if (props == null) {
241: log(LOG_ERROR, "server", "Not properly initialized!");
242: return false;
243: }
244: group = new ThreadGroup(prefix);
245: if (hostName == null) {
246: hostName = InetAddress.getLocalHost().getHostAddress();
247: }
248: if (Thread.currentThread().getName().startsWith("Thread-")) {
249: Thread.currentThread().setName("server");
250: }
251:
252: handler = ChainHandler.initHandler(this , prefix, handlerName);
253:
254: if (handler == null) {
255: log(LOG_WARNING, handlerName, "handler did not initialize");
256: return false;
257: }
258: if (initFailure) {
259: log(LOG_ERROR, handlerName, "Initilization failure");
260: return false;
261: }
262: return true;
263: }
264:
265: /**
266: * Loops, accepting socket connections and replying to HTTP requests.
267: * This is called indirectly via Thread.start().
268: * <p>
269: * Many things in the server are not initialized until this point,
270: * because the user may have set some related configuration options
271: * between the time this server was allocated and the time it was
272: * started. For instance, the main <code>Handler</code> is not
273: * initialized until now, because its <code>Handler.init</code> method
274: * may have wanted to examine server member variables such as
275: * <code>hostName</code> or <code>bufsize</code>.
276: */
277: public void run() {
278: try {
279: if (init() == false) {
280: return;
281: }
282:
283: listen.setSoTimeout(0);
284: while (true) {
285: /*
286: * Blocks until we have a connection on the socket.
287: */
288: Socket sock = listen.accept();
289:
290: allowed: if (restrict != null) {
291: InetAddress addr = sock.getInetAddress();
292: for (int i = 0; i < restrict.length; i++) {
293: if (restrict[i].equals(addr)) {
294: break allowed;
295: }
296: }
297: log(LOG_DIAGNOSTIC, addr, "rejected request");
298: sock.close();
299: continue;
300: }
301:
302: // A pseudo-busy loop!!!
303:
304: boolean warn = false;
305: while (Thread.activeCount() > maxThreads) {
306: if (!warn) {
307: log(LOG_WARNING, sock, "Too many threads: "
308: + acceptCount);
309: }
310: Thread.yield();
311: warn = true;
312: }
313:
314: String name = sock.getInetAddress().getHostName() + "-"
315: + acceptCount++;
316:
317: log(LOG_INFORMATIONAL, name, "new connection");
318: new Thread(group, new Connection(this , sock), name)
319: .start();
320: }
321: } catch (IOException e) {
322: /*
323: * Quit anyhow.
324: */
325: } finally {
326: try {
327: Thread[] sub = new Thread[100];
328: int count;
329: while ((count = group.enumerate(sub, true)) > 0) {
330: for (int i = 0; i < count; i++) {
331: sub[i].interrupt();
332: sub[i].join();
333: }
334: yield();
335: }
336: } catch (Exception e) {
337: }
338:
339: group = null;
340: }
341: }
342:
343: /**
344: * Stop the server, and kill all pending requests
345: */
346: public void close() {
347: try {
348: this .interrupt();
349: this .join();
350: } catch (Exception e) {
351: }
352:
353: log(LOG_WARNING, null, "server stopped");
354: }
355:
356: public static final int LOG_ERROR = 1; // most severe
357: public static final int LOG_WARNING = 2;
358: public static final int LOG_LOG = 3;
359: public static final int LOG_INFORMATIONAL = 4;
360: public static final int LOG_DIAGNOSTIC = 5; // least useful
361:
362: /**
363: * Logs information about the socket to <code>System.out</code>.
364: *
365: * @param level Controls the verbosity (0=least 5=most)
366: * @param obj The object that the message relates to.
367: * @param message The message to be logged.
368: */
369:
370: public void log(int level, Object obj, String message) {
371: if (level <= logLevel) {
372: System.out.print("LOG: " + level + " " + prefix
373: + listen.getLocalPort() + "-"
374: + Thread.currentThread().getName() + ": ");
375: if (obj != null) {
376: System.out.print(obj);
377: System.out.print(": ");
378: }
379: System.out.println(message);
380: }
381: }
382: }
|