001: package com.quadcap.http.server22;
002:
003: /* Copyright 1997 - 2003 Quadcap Software. All rights reserved.
004: *
005: * This software is distributed under the Quadcap Free Software License.
006: * This software may be used or modified for any purpose, personal or
007: * commercial. Open Source redistributions are permitted. Commercial
008: * redistribution of larger works derived from, or works which bundle
009: * this software requires a "Commercial Redistribution License"; see
010: * http://www.quadcap.com/purchase.
011: *
012: * Redistributions qualify as "Open Source" under one of the following terms:
013: *
014: * Redistributions are made at no charge beyond the reasonable cost of
015: * materials and delivery.
016: *
017: * Redistributions are accompanied by a copy of the Source Code or by an
018: * irrevocable offer to provide a copy of the Source Code for up to three
019: * years at the cost of materials and delivery. Such redistributions
020: * must allow further use, modification, and redistribution of the Source
021: * Code under substantially the same terms as this license.
022: *
023: * Redistributions of source code must retain the copyright notices as they
024: * appear in each source code file, these license terms, and the
025: * disclaimer/limitation of liability set forth as paragraph 6 below.
026: *
027: * Redistributions in binary form must reproduce this Copyright Notice,
028: * these license terms, and the disclaimer/limitation of liability set
029: * forth as paragraph 6 below, in the documentation and/or other materials
030: * provided with the distribution.
031: *
032: * The Software is provided on an "AS IS" basis. No warranty is
033: * provided that the Software is free of defects, or fit for a
034: * particular purpose.
035: *
036: * Limitation of Liability. Quadcap Software shall not be liable
037: * for any damages suffered by the Licensee or any third party resulting
038: * from use of the Software.
039: */
040:
041: import java.util.Date;
042: import java.util.Enumeration;
043: import java.util.Hashtable;
044: import java.util.Properties;
045: import java.util.Random;
046: import java.util.Vector;
047:
048: import java.io.File;
049: import java.io.FileInputStream;
050: import java.io.FileOutputStream;
051: import java.io.InputStream;
052: import java.io.IOException;
053: import java.io.PrintWriter;
054:
055: import java.text.SimpleDateFormat;
056:
057: import java.net.InetAddress;
058: import java.net.MalformedURLException;
059: import java.net.Socket;
060: import java.net.URL;
061: import java.net.URLConnection;
062:
063: import javax.servlet.RequestDispatcher;
064: import javax.servlet.Servlet;
065: import javax.servlet.ServletContext;
066: import javax.servlet.ServletException;
067: import javax.servlet.ServletRequest;
068: import javax.servlet.ServletResponse;
069:
070: import javax.servlet.http.HttpServlet;
071: import javax.servlet.http.HttpServletRequest;
072: import javax.servlet.http.HttpSession;
073: import javax.servlet.http.HttpSessionContext;
074:
075: import com.quadcap.net.server.Server;
076: import com.quadcap.net.server.Worker;
077:
078: import com.quadcap.util.threads.Command;
079: import com.quadcap.util.threads.PeriodicScheduler;
080:
081: import com.quadcap.util.text.OctetMap;
082: import com.quadcap.util.text.Scanner;
083:
084: import com.quadcap.io.dir.Directory;
085:
086: import com.quadcap.util.Config;
087: import com.quadcap.util.Debug;
088: import com.quadcap.util.Util;
089:
090: /**
091: * A basic HTTP server.
092: *
093: * @author Stan Bailes
094: */
095: public class WebServer {
096: Random random = new Random();
097:
098: /**
099: * My (cached) host name
100: */
101: String hostName = null;
102:
103: /**
104: * My servlet contexts
105: */
106: Hashtable contexts = new Hashtable();
107:
108: /**
109: * Default mime types
110: */
111: Hashtable mimeTypes = new Hashtable();
112:
113: WebApplication defaultContext = null;
114:
115: File tmpDir;
116:
117: long sessionCount = 0;
118: static final int defaultInactiveInterval = 60;
119: static final int expireCheckerInterval = 60;
120:
121: PeriodicScheduler expireChecker;
122:
123: PrintWriter reqStream;
124:
125: Server server;
126:
127: /**
128: * Server interface: initialize this server
129: *
130: * @param p the properties used to specify information about the
131: * server's startup.
132: * @exception IOException may be thrown
133: */
134: /*{com.quadcap.http.server22.WebServer.xml}
135: *
136: * <config>
137: * <config-var>
138: * <config-name>tempDir</config-name>
139: * <config-dflt>./repository</config-dflt>
140: * <config-desc>Specifies the name of a directory where temporary
141: * JSP-generated files are placed.</config-desc>
142: * </config-var>
143: *
144: * <config-var>
145: * <config-name>context.*.root</config-name>
146: * <config-dflt><i>none</i></config-dflt>
147: * <config-desc>Specify the context path for a web application.
148: * The application
149: * name is specified by the 'wildcard' portion of the property
150: * name.</config-desc>
151: * </config-var>
152: *
153: * <config-var>
154: * <config-name>context.*.docBase</config-name>
155: * <config-dflt><i>none</i></config-dflt>
156: * <config-desc>Specify the path to the web application's files,
157: * either as
158: * a directory name or the name of a Web Application Archive
159: * (.war) file.</config-desc>
160: * </config-var>
161: *
162: * <config-var>
163: * <config-name>acceptor.*.port</config-name>
164: * <config-dflt><i>none</i></config-dflt>
165: * <config-desc>Specify the port number for the server to listen on.
166: * The
167: * wildcard portion of the name gives the name of the socket
168: * acceptor, and doesn't have any real significance other than
169: * as a unique name for the acceptor.</config-desc>
170: * </config-var>
171: *
172: * <config-var>
173: * <config-name>acceptor.*.queueDepth</config-name>
174: * <config-dflt>32</config-dflt>
175: * <config-desc>Specify the TCP queue depth associated with the named
176: * acceptor.</config-desc>
177: * </config-var>
178: *
179: * <include name="docstubs/com.quadcap.net.server.Server.xml"/>
180: *
181: * </config>
182: */
183: public void init(Properties p) throws Exception {
184: p.put("workerClass", "com.quadcap.http.server22.WebWorker");
185: server = new Server(p, this );
186:
187: String tmp = p.getProperty("tempDir", "repository");
188: tmpDir = new File(tmp);
189: if (!tmpDir.isDirectory() && !tmpDir.mkdirs()) {
190: throw new IOException("Can't create temp directory: " + tmp);
191: }
192:
193: tmp = p.getProperty("requestLog", "web.log");
194: reqStream = new PrintWriter(new FileOutputStream(tmp));
195:
196: InputStream dis = ClassLoader
197: .getSystemResourceAsStream("com/quadcap/http/server22/mime.types");
198: if (dis != null) {
199: try {
200: parseMimeTypes(dis);
201: } finally {
202: dis.close();
203: }
204: }
205:
206: Enumeration e = Config.getMatchingProps(p, "context.*.root");
207: while (e.hasMoreElements()) {
208: String context = e.nextElement().toString();
209: String c = "context." + context;
210: String root = p.getProperty(c + ".root");
211: String docBase = p.getProperty(c + ".docBase");
212: if (docBase == null) {
213: Debug.println(0, "No docBase specified for context "
214: + context);
215: } else {
216: try {
217: WebApplication app = addWebApplication(root,
218: docBase);
219: if (context.equals("default")) {
220: defaultContext = app;
221: }
222: } catch (Throwable t) {
223: Debug.print(t);
224: }
225: }
226: }
227:
228: boolean found = false;
229: e = Config.getMatchingProps(p, "acceptor.*.port");
230: while (e.hasMoreElements()) {
231: found = true;
232: String acceptor = e.nextElement().toString();
233: String a = "acceptor." + acceptor;
234: Properties ap = new Properties();
235: ap.put("port", p.getProperty(a + ".port"));
236: ap
237: .put("queueDepth", p.getProperty(a + ".queueDepth",
238: "32"));
239: Debug.println("startAcceptor: " + ap.get("port"));
240: server.startAcceptor(ap);
241: }
242:
243: expireChecker = new PeriodicScheduler(server.getThreadGroup(),
244: "expire checker", this );
245: expireChecker.add("Check for expired sessions",
246: new ExpireChecker(), expireCheckerInterval * 1000);
247: expireChecker.start();
248: if (!found) {
249: Debug.println(0,
250: "No acceptor.<name>.port properties found!!");
251: }
252: }
253:
254: public void stop() {
255: server.stop();
256: }
257:
258: final void parseMimeTypes(InputStream is) throws IOException {
259: Scanner s = new Scanner(is);
260: s.skipWhile(OctetMap.crlfChars);
261: int c;
262: while ((c = s.peek()) >= 0) {
263: if (c == '#') {
264: s.skipUntil(OctetMap.crlfChars);
265: } else {
266: String mimeType = s.parseWhile(OctetMap.uriChars);
267: s.skipWhile(OctetMap.wsChars);
268: while ((c = s.peek()) >= 0
269: && OctetMap.tokenChars.has(c)) {
270: String ext = s.parseWhile(OctetMap.tokenChars);
271: mimeTypes.put(ext, mimeType);
272: s.skipWhile(OctetMap.wsChars);
273: }
274: s.skipUntil(OctetMap.crlfChars);
275: }
276: s.skipWhile(OctetMap.crlfChars);
277: }
278: is.close();
279: }
280:
281: public void removeWebApplication(String root) {
282: WebApplication app = getContextForRoot(root);
283: if (app != null) {
284: contexts.remove(root);
285: app.shutdown();
286: }
287: }
288:
289: public WebApplication addWebApplication(String root, String docBase)
290: throws IOException, ServletException {
291: // ---- remove trailing slash from context path
292: int len = root.length();
293: if (len > 0 && root.charAt(len - 1) == '/') {
294: root = root.substring(0, len - 1);
295: }
296: //#ifdef DEBUG
297: if (Trace.level() > 1) {
298: Debug.println("addWebApplication(" + root + ", " + docBase
299: + ")");
300: }
301: //#endif
302:
303: WebApplication oldApp = (WebApplication) contexts.get(root);
304: if (oldApp != null) {
305: try {
306: Debug.println("[Shutting down old app for " + root
307: + ": " + oldApp);
308: oldApp.shutdown();
309: } catch (Throwable t) {
310: }
311: }
312:
313: File dir = new File(docBase);
314: Directory d = Directory.getDirectory(dir);
315: WebApplication app = new WebApplication();
316: app.init(this , root, d);
317:
318: contexts.put(root, app);
319:
320: return app;
321: }
322:
323: /**
324: * Returns the name and version of the network service under which the
325: * servlet is running.
326: *
327: * @return the server information string
328: */
329: public String getServerInfo() {
330: //> return \"Quadcap Web Server [defined VERSION]\";
331: //#autogen begin
332: return "Quadcap Web Server 3.4";
333: //#autogen end
334: }
335:
336: /**
337: * Returns the mime type of the specified file, or null if not known.
338: */
339: public String getMimeTypeForExt(String ext) {
340: String type = (String) mimeTypes.get(ext.toLowerCase());
341: return type;
342: }
343:
344: /**
345: * Return the servlet context for the specified path.
346: */
347: public WebApplication getContext(String path) {
348: WebApplication ret = (WebApplication) contexts.get(path);
349: for (int i = path.length() - 1; ret == null && i >= 0; i--) {
350: if (path.charAt(i) == '/') {
351: String subPath = path.substring(0, i);
352: ret = (WebApplication) contexts.get(subPath);
353: }
354: }
355: if (ret == null)
356: ret = defaultContext;
357: Debug.println("defaultContext = " + defaultContext);
358: return ret;
359: }
360:
361: String makeSessionId() {
362: int r = random.nextInt() & 0xfffffff;
363: return "" + (sessionCount++) + "." + r + "."
364: + System.currentTimeMillis();
365: }
366:
367: public void expireSessions() {
368: Enumeration e = contexts.elements();
369: while (e.hasMoreElements()) {
370: WebApplication app = (WebApplication) e.nextElement();
371: app.expireSessions();
372: }
373: }
374:
375: final File getTempDir() {
376: return tmpDir;
377: }
378:
379: public Enumeration getContextRoots() {
380: return contexts.keys();
381: }
382:
383: public WebApplication getContextForRoot(String root) {
384: return (WebApplication) contexts.get(root);
385: }
386:
387: public static void main(String args[]) {
388: Properties p = Config.getProperties();
389: WebServer w = new WebServer();
390: try {
391: w.init(p);
392: } catch (Throwable t) {
393: Debug.print(t);
394: }
395: }
396:
397: static SimpleDateFormat df;
398: static {
399: df = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS");
400: }
401:
402: public void requestLog(String s) {
403: reqStream.println(df.format(new Date()) + " " + s);
404: reqStream.flush();
405: }
406: }
407:
408: class ExpireChecker implements Command {
409: public void execute(Object context) {
410: ((WebServer) context).expireSessions();
411: }
412: }
|