Quadcap Embeddable Server

com/quadcap/http/server22/WebServer.java

Go to the documentation of this file.
00001 package com.quadcap.http.server22; 00002 00003 /* Copyright 1997 - 2003 Quadcap Software. All rights reserved. 00004 * 00005 * This software is distributed under the Quadcap Free Software License. 00006 * This software may be used or modified for any purpose, personal or 00007 * commercial. Open Source redistributions are permitted. Commercial 00008 * redistribution of larger works derived from, or works which bundle 00009 * this software requires a "Commercial Redistribution License"; see 00010 * http://www.quadcap.com/purchase. 00011 * 00012 * Redistributions qualify as "Open Source" under one of the following terms: 00013 * 00014 * Redistributions are made at no charge beyond the reasonable cost of 00015 * materials and delivery. 00016 * 00017 * Redistributions are accompanied by a copy of the Source Code or by an 00018 * irrevocable offer to provide a copy of the Source Code for up to three 00019 * years at the cost of materials and delivery. Such redistributions 00020 * must allow further use, modification, and redistribution of the Source 00021 * Code under substantially the same terms as this license. 00022 * 00023 * Redistributions of source code must retain the copyright notices as they 00024 * appear in each source code file, these license terms, and the 00025 * disclaimer/limitation of liability set forth as paragraph 6 below. 00026 * 00027 * Redistributions in binary form must reproduce this Copyright Notice, 00028 * these license terms, and the disclaimer/limitation of liability set 00029 * forth as paragraph 6 below, in the documentation and/or other materials 00030 * provided with the distribution. 00031 * 00032 * The Software is provided on an "AS IS" basis. No warranty is 00033 * provided that the Software is free of defects, or fit for a 00034 * particular purpose. 00035 * 00036 * Limitation of Liability. Quadcap Software shall not be liable 00037 * for any damages suffered by the Licensee or any third party resulting 00038 * from use of the Software. 00039 */ 00040 00041 import java.util.Date; 00042 import java.util.Enumeration; 00043 import java.util.Hashtable; 00044 import java.util.Properties; 00045 import java.util.Random; 00046 import java.util.Vector; 00047 00048 import java.io.File; 00049 import java.io.FileInputStream; 00050 import java.io.FileOutputStream; 00051 import java.io.InputStream; 00052 import java.io.IOException; 00053 import java.io.PrintWriter; 00054 00055 import java.text.SimpleDateFormat; 00056 00057 import java.net.InetAddress; 00058 import java.net.MalformedURLException; 00059 import java.net.Socket; 00060 import java.net.URL; 00061 import java.net.URLConnection; 00062 00063 import javax.servlet.RequestDispatcher; 00064 import javax.servlet.Servlet; 00065 import javax.servlet.ServletContext; 00066 import javax.servlet.ServletException; 00067 import javax.servlet.ServletRequest; 00068 import javax.servlet.ServletResponse; 00069 00070 import javax.servlet.http.HttpServlet; 00071 import javax.servlet.http.HttpServletRequest; 00072 import javax.servlet.http.HttpSession; 00073 import javax.servlet.http.HttpSessionContext; 00074 00075 import com.quadcap.net.server.Server; 00076 import com.quadcap.net.server.Worker; 00077 00078 import com.quadcap.util.threads.Command; 00079 import com.quadcap.util.threads.PeriodicScheduler; 00080 00081 import com.quadcap.util.text.OctetMap; 00082 import com.quadcap.util.text.Scanner; 00083 00084 import com.quadcap.io.dir.Directory; 00085 00086 import com.quadcap.util.Config; 00087 import com.quadcap.util.Debug; 00088 import com.quadcap.util.Util; 00089 00090 /** 00091 * A basic HTTP server. 00092 * 00093 * @author Stan Bailes 00094 */ 00095 public class WebServer { 00096 Random random = new Random(); 00097 00098 /** 00099 * My (cached) host name 00100 */ 00101 String hostName = null; 00102 00103 /** 00104 * My servlet contexts 00105 */ 00106 Hashtable contexts = new Hashtable(); 00107 00108 /** 00109 * Default mime types 00110 */ 00111 Hashtable mimeTypes = new Hashtable(); 00112 00113 WebApplication defaultContext = null; 00114 00115 File tmpDir; 00116 00117 long sessionCount = 0; 00118 static final int defaultInactiveInterval = 60; 00119 static final int expireCheckerInterval = 60; 00120 00121 PeriodicScheduler expireChecker; 00122 00123 PrintWriter reqStream; 00124 00125 Server server; 00126 00127 /** 00128 * Server interface: initialize this server 00129 * 00130 * @param p the properties used to specify information about the 00131 * server's startup. 00132 * @exception IOException may be thrown 00133 */ 00134 /*{com.quadcap.http.server22.WebServer.xml} 00135 * 00136 * <config> 00137 * <config-var> 00138 * <config-name>tempDir</config-name> 00139 * <config-dflt>./repository</config-dflt> 00140 * <config-desc>Specifies the name of a directory where temporary 00141 * JSP-generated files are placed.</config-desc> 00142 * </config-var> 00143 * 00144 * <config-var> 00145 * <config-name>context.*.root</config-name> 00146 * <config-dflt><i>none</i></config-dflt> 00147 * <config-desc>Specify the context path for a web application. 00148 * The application 00149 * name is specified by the 'wildcard' portion of the property 00150 * name.</config-desc> 00151 * </config-var> 00152 * 00153 * <config-var> 00154 * <config-name>context.*.docBase</config-name> 00155 * <config-dflt><i>none</i></config-dflt> 00156 * <config-desc>Specify the path to the web application's files, 00157 * either as 00158 * a directory name or the name of a Web Application Archive 00159 * (.war) file.</config-desc> 00160 * </config-var> 00161 * 00162 * <config-var> 00163 * <config-name>acceptor.*.port</config-name> 00164 * <config-dflt><i>none</i></config-dflt> 00165 * <config-desc>Specify the port number for the server to listen on. 00166 * The 00167 * wildcard portion of the name gives the name of the socket 00168 * acceptor, and doesn't have any real significance other than 00169 * as a unique name for the acceptor.</config-desc> 00170 * </config-var> 00171 * 00172 * <config-var> 00173 * <config-name>acceptor.*.queueDepth</config-name> 00174 * <config-dflt>32</config-dflt> 00175 * <config-desc>Specify the TCP queue depth associated with the named 00176 * acceptor.</config-desc> 00177 * </config-var> 00178 * 00179 * <include name="docstubs/com.quadcap.net.server.Server.xml"/> 00180 * 00181 * </config> 00182 */ 00183 public void init(Properties p) 00184 throws Exception 00185 { 00186 p.put("workerClass", "com.quadcap.http.server22.WebWorker"); 00187 server = new Server(p, this); 00188 00189 String tmp = p.getProperty("tempDir", "repository"); 00190 tmpDir = new File(tmp); 00191 if (!tmpDir.isDirectory() && !tmpDir.mkdirs()) { 00192 throw new IOException("Can't create temp directory: " + tmp); 00193 } 00194 00195 tmp = p.getProperty("requestLog", "web.log"); 00196 reqStream = new PrintWriter(new FileOutputStream(tmp)); 00197 00198 InputStream dis = ClassLoader.getSystemResourceAsStream( 00199 "com/quadcap/http/server22/mime.types"); 00200 if (dis != null) { 00201 try { 00202 parseMimeTypes(dis); 00203 } finally { 00204 dis.close(); 00205 } 00206 } 00207 00208 Enumeration e = Config.getMatchingProps(p, "context.*.root"); 00209 while (e.hasMoreElements()) { 00210 String context = e.nextElement().toString(); 00211 String c = "context." + context; 00212 String root = p.getProperty(c + ".root"); 00213 String docBase = p.getProperty(c + ".docBase"); 00214 if (docBase == null) { 00215 Debug.println(0, "No docBase specified for context " + 00216 context); 00217 } else { 00218 try { 00219 WebApplication app = addWebApplication(root, docBase); 00220 if (context.equals("default")) { 00221 defaultContext = app; 00222 } 00223 } catch (Throwable t) { 00224 Debug.print(t); 00225 } 00226 } 00227 } 00228 00229 boolean found = false; 00230 e = Config.getMatchingProps(p, "acceptor.*.port"); 00231 while (e.hasMoreElements()) { 00232 found = true; 00233 String acceptor = e.nextElement().toString(); 00234 String a = "acceptor." + acceptor; 00235 Properties ap = new Properties(); 00236 ap.put("port", p.getProperty(a + ".port")); 00237 ap.put("queueDepth", p.getProperty(a + ".queueDepth", "32")); 00238 Debug.println("startAcceptor: " + ap.get("port")); 00239 server.startAcceptor(ap); 00240 } 00241 00242 expireChecker = new PeriodicScheduler(server.getThreadGroup(), 00243 "expire checker", 00244 this); 00245 expireChecker.add("Check for expired sessions", 00246 new ExpireChecker(), 00247 expireCheckerInterval * 1000); 00248 expireChecker.start(); 00249 if (!found) { 00250 Debug.println(0, "No acceptor.<name>.port properties found!!"); 00251 } 00252 } 00253 00254 public void stop() { 00255 server.stop(); 00256 } 00257 00258 final void parseMimeTypes(InputStream is) throws IOException { 00259 Scanner s = new Scanner(is); 00260 s.skipWhile(OctetMap.crlfChars); 00261 int c; 00262 while ((c = s.peek()) >= 0) { 00263 if (c == '#') { 00264 s.skipUntil(OctetMap.crlfChars); 00265 } else { 00266 String mimeType = s.parseWhile(OctetMap.uriChars); 00267 s.skipWhile(OctetMap.wsChars); 00268 while ((c = s.peek()) >= 0 && OctetMap.tokenChars.has(c)) { 00269 String ext = s.parseWhile(OctetMap.tokenChars); 00270 mimeTypes.put(ext, mimeType); 00271 s.skipWhile(OctetMap.wsChars); 00272 } 00273 s.skipUntil(OctetMap.crlfChars); 00274 } 00275 s.skipWhile(OctetMap.crlfChars); 00276 } 00277 is.close(); 00278 } 00279 00280 public void removeWebApplication(String root) { 00281 WebApplication app = getContextForRoot(root); 00282 if (app != null) { 00283 contexts.remove(root); 00284 app.shutdown(); 00285 } 00286 } 00287 00288 public WebApplication addWebApplication(String root, String docBase) 00289 throws IOException, ServletException 00290 { 00291 // ---- remove trailing slash from context path 00292 int len = root.length(); 00293 if (len > 0 && root.charAt(len-1) == '/') { 00294 root = root.substring(0, len-1); 00295 } 00296 //#ifdef DEBUG 00297 if (Trace.level() > 1) { 00298 Debug.println("addWebApplication(" + root + ", " + docBase + ")"); 00299 } 00300 //#endif 00301 00302 WebApplication oldApp = (WebApplication)contexts.get(root); 00303 if (oldApp != null) { 00304 try { 00305 Debug.println("[Shutting down old app for " + root + ": " + 00306 oldApp); 00307 oldApp.shutdown(); 00308 } catch (Throwable t) { 00309 } 00310 } 00311 00312 File dir = new File(docBase); 00313 Directory d = Directory.getDirectory(dir); 00314 WebApplication app = new WebApplication(); 00315 app.init(this, root, d); 00316 00317 contexts.put(root, app); 00318 00319 00320 return app; 00321 } 00322 00323 /** 00324 * Returns the name and version of the network service under which the 00325 * servlet is running. 00326 * 00327 * @return the server information string 00328 */ 00329 public String getServerInfo() { 00330 //> return \"Quadcap Web Server [defined VERSION]\"; 00331 //#autogen begin 00332 return "Quadcap Web Server 3.4"; 00333 //#autogen end 00334 } 00335 00336 /** 00337 * Returns the mime type of the specified file, or null if not known. 00338 */ 00339 public String getMimeTypeForExt(String ext) { 00340 String type = (String)mimeTypes.get(ext.toLowerCase()); 00341 return type; 00342 } 00343 00344 /** 00345 * Return the servlet context for the specified path. 00346 */ 00347 public WebApplication getContext(String path) { 00348 WebApplication ret = (WebApplication)contexts.get(path); 00349 for (int i = path.length() - 1; ret == null && i >= 0; i--) { 00350 if (path.charAt(i) == '/') { 00351 String subPath = path.substring(0, i); 00352 ret = (WebApplication)contexts.get(subPath); 00353 } 00354 } 00355 if (ret == null) ret = defaultContext; 00356 Debug.println("defaultContext = " + defaultContext); 00357 return ret; 00358 } 00359 00360 String makeSessionId() { 00361 int r = random.nextInt() & 0xfffffff; 00362 return "" + (sessionCount++) + "." + r + "." + 00363 System.currentTimeMillis(); 00364 } 00365 00366 public void expireSessions() { 00367 Enumeration e = contexts.elements(); 00368 while (e.hasMoreElements()) { 00369 WebApplication app = (WebApplication)e.nextElement(); 00370 app.expireSessions(); 00371 } 00372 } 00373 00374 final File getTempDir() { 00375 return tmpDir; 00376 } 00377 00378 public Enumeration getContextRoots() { 00379 return contexts.keys(); 00380 } 00381 00382 public WebApplication getContextForRoot(String root) { 00383 return (WebApplication)contexts.get(root); 00384 } 00385 00386 public static void main(String args[]) { 00387 Properties p = Config.getProperties(); 00388 WebServer w = new WebServer(); 00389 try { 00390 w.init(p); 00391 } catch (Throwable t) { 00392 Debug.print(t); 00393 } 00394 } 00395 00396 static SimpleDateFormat df; 00397 static { 00398 df = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS"); 00399 } 00400 public void requestLog(String s) { 00401 reqStream.println(df.format(new Date()) + " " + s); 00402 reqStream.flush(); 00403 } 00404 } 00405 00406 00407 class ExpireChecker implements Command { 00408 public void execute(Object context) { 00409 ((WebServer)context).expireSessions(); 00410 } 00411 } 00412