001: /*
002: * SimpleHttpServer
003: *
004: * $Id: SimpleHttpServer.java 4666 2006-09-26 17:53:28Z paul_jack $
005: *
006: * Created on Jul 11, 2003
007: *
008: * Copyright (C) 2003 Internet Archive.
009: *
010: * This file is part of the Heritrix web crawler (crawler.archive.org).
011: *
012: * Heritrix is free software; you can redistribute it and/or modify
013: * it under the terms of the GNU Lesser Public License as published by
014: * the Free Software Foundation; either version 2.1 of the License, or
015: * any later version.
016: *
017: * Heritrix is distributed in the hope that it will be useful,
018: * but WITHOUT ANY WARRANTY; without even the implied warranty of
019: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
020: * GNU Lesser Public License for more details.
021: *
022: * You should have received a copy of the GNU Lesser Public License
023: * along with Heritrix; if not, write to the Free Software
024: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
025: */
026: package org.archive.crawler;
027:
028: import java.io.File;
029: import java.io.FileNotFoundException;
030: import java.io.IOException;
031: import java.net.UnknownHostException;
032: import java.util.ArrayList;
033: import java.util.Arrays;
034: import java.util.Collection;
035: import java.util.Iterator;
036: import java.util.List;
037: import java.util.NoSuchElementException;
038:
039: import org.mortbay.http.HashUserRealm;
040: import org.mortbay.http.HttpListener;
041: import org.mortbay.http.HttpServer;
042: import org.mortbay.http.NCSARequestLog;
043: import org.mortbay.http.RequestLog;
044: import org.mortbay.http.SocketListener;
045: import org.mortbay.jetty.Server;
046: import org.mortbay.jetty.servlet.WebApplicationContext;
047: import org.mortbay.util.InetAddrPort;
048:
049: /**
050: * Wrapper for embedded Jetty server.
051: *
052: * Loads up all webapps under webapp directory.
053: *
054: */
055: public class SimpleHttpServer {
056: private int port;
057: private Server server = null;
058:
059: /**
060: * Default web port.
061: */
062: public static final int DEFAULT_PORT = 8080;
063:
064: /**
065: * Webapp contexts returned out of a server start.
066: */
067: private List<WebApplicationContext> contexts = new ArrayList<WebApplicationContext>();
068:
069: /**
070: * Name of the root webapp.
071: */
072: private static final String ROOT_WEBAPP = "root";
073:
074: /**
075: * Name of the admin webapp.
076: */
077: private static final String ADMIN_WEBAPP = "admin";
078:
079: /**
080: * List of webapps to deploy.
081: */
082: private static final List webapps = Arrays.asList(new String[] {
083: ROOT_WEBAPP, ADMIN_WEBAPP });
084:
085: public SimpleHttpServer() throws Exception {
086: this (DEFAULT_PORT, true);
087: }
088:
089: public SimpleHttpServer(int port, boolean expandWebapps)
090: throws Exception {
091: this (SimpleHttpServer.webapps, port, expandWebapps);
092: }
093:
094: /**
095: * @param name Name of webapp to load.
096: * @param context Where to mount the webapp. If passed context is
097: * null or empty string, we'll use '/' + <code>name</code> else if
098: * passed '/' then we'll add the webapp as the root webapp.
099: * @param port Port to run on.
100: * @param expandWebapps True if we're to expand the webapp passed.
101: * @throws Exception
102: * @deprecated Use SimpleHttpServer(name,context,hosts,port,expandWebapps)
103: */
104: public SimpleHttpServer(boolean localhostOnly, String name,
105: String context, int port, boolean expandWebapps)
106: throws Exception {
107: this (name, context, determineHosts(localhostOnly), port,
108: expandWebapps);
109: }
110:
111: /**
112: * Constructor.
113: *
114: * @param name Name of webapp to load
115: * @param context Where to mount the webap. If null or empty string,
116: * we'll use '/' + <code>name</code>; if passed '/'
117: * then we'll add the webapp as the root webapp
118: * @param hosts list of hosts to bind to
119: * @param port port to listen on
120: * @param expandWebapps true to expand webapp passed
121: * @throws Exception
122: */
123: public SimpleHttpServer(String name, String context,
124: Collection<String> hosts, int port, boolean expandWebapps)
125: throws Exception {
126: initialize(hosts, port);
127: addWebapp(name, context, expandWebapps);
128: this .server.setRequestLog(getServerLogging());
129: }
130:
131: /**
132: * @param webapps List of webapps to load.
133: * @param port Port to run on.
134: * @param expandWebapps True if we're to expand the webapps found.
135: * @throws Exception
136: */
137: public SimpleHttpServer(List webapps, int port,
138: boolean expandWebapps) throws Exception {
139: initialize(null, port);
140:
141: // Add each of the webapps in turn. If we're passed the root webapp,
142: // give it special handling -- assume its meant to be server root and
143: // its meant to be mounted on '/'. The below also favors the war file
144: // if its present.
145: for (Iterator i = webapps.iterator(); i.hasNext();) {
146: addWebapp((String) i.next(), null, expandWebapps);
147: }
148: this .server.setRequestLog(getServerLogging());
149: }
150:
151: /**
152: * Add a webapp.
153: * @param name Name of webapp to add.
154: * @param context Context to add the webapp on.
155: * @param expand True if we should expand the webapps.
156: * @throws IOException
157: */
158: protected void addWebapp(String name, String context, boolean expand)
159: throws IOException {
160: File ptr = new File(getWARSPath(), name + ".war");
161: if (!ptr.exists()) {
162: ptr = new File(getWARSPath(), name);
163: if (!ptr.exists()) {
164: throw new FileNotFoundException(ptr.getAbsolutePath());
165: }
166: }
167: // If webapp name is for root, mount it on '/', else '/WEBAPP_NAME'.
168: if (context == null || context.length() <= 0) {
169: context = "/" + ((name.equals(ROOT_WEBAPP)) ? "" : name);
170: }
171: WebApplicationContext c = this .server.addWebApplication(
172: context, ptr.getAbsolutePath());
173: if (context.equals("/")) {
174: // If we've just mounted the root webapp, make it the root.
175: this .server.setRootWebApp(name);
176: }
177: // Selftest depends on finding the extracted WARs. TODO: Fix.
178: c.setExtractWAR(expand);
179: // let login sessions last 24 hours
180: c.getServletHandler().getSessionManager()
181: .setMaxInactiveInterval(86400);
182: this .contexts.add(c);
183: }
184:
185: /**
186: * Initialize the server.
187: * Called from constructors.
188: * @param port Port to start the server on.
189: * @deprecated Use initialize(Collection<String>, port) instead
190: */
191: protected void initialize(int port, boolean localhostOnly) {
192: Collection<String> hosts = determineHosts(localhostOnly);
193: initialize(hosts, port);
194: }
195:
196: /**
197: * Initialize the server. Called from constructors.
198: *
199: * @param hosts the hostnames to bind to; if empty or null, will bind
200: * to all interfaces
201: * @param port the port to listen on
202: */
203: protected void initialize(Collection<String> hosts, int port) {
204: this .server = new Server();
205: this .port = port;
206: if (hosts.isEmpty()) {
207: SocketListener listener = new SocketListener();
208: listener.setPort(port);
209: this .server.addListener(listener);
210: return;
211: }
212:
213: for (String host : hosts)
214: try {
215: InetAddrPort addr = new InetAddrPort(host, port);
216: SocketListener listener = new SocketListener(addr);
217: this .server.addListener(listener);
218: } catch (UnknownHostException e) {
219: e.printStackTrace();
220: }
221: }
222:
223: private static Collection<String> determineHosts(boolean lho) {
224: Collection<String> hosts = new ArrayList<String>();
225: if (lho) {
226: hosts.add("127.0.0.1");
227: }
228: return hosts;
229: }
230:
231: /**
232: * Setup log files.
233: * @return RequestLog instance to add to a server.
234: * @throws Exception
235: */
236: protected RequestLog getServerLogging() throws Exception {
237: // Have accesses go into the stdout/stderr log for now. Later, if
238: // demand, we'll have accesses go into their own file.
239: NCSARequestLog a = new NCSARequestLog(Heritrix.getHeritrixOut());
240: a.setRetainDays(90);
241: a.setAppend(true);
242: a.setExtended(false);
243: a.setBuffered(false);
244: a.setLogTimeZone("GMT");
245: a.start();
246: return a;
247: }
248:
249: /**
250: * Return the directory that holds the WARs we're to deploy.
251: *
252: * @return Return webapp path (Path returned has a trailing '/').
253: * @throws IOException
254: */
255: private static String getWARSPath() throws IOException {
256: String webappsPath = Heritrix.getWarsdir().getAbsolutePath();
257: if (!webappsPath.endsWith(File.separator)) {
258: webappsPath = webappsPath + File.separator;
259: }
260: return webappsPath;
261: }
262:
263: /**
264: * Start the server.
265: *
266: * @throws Exception if problem starting server or if server already
267: * started.
268: */
269: public synchronized void startServer() throws Exception {
270:
271: this .server.start();
272: }
273:
274: /**
275: * Stop the running server.
276: *
277: * @throws InterruptedException
278: */
279: public synchronized void stopServer() throws InterruptedException {
280:
281: if (this .server != null) {
282: this .server.stop();
283: }
284: }
285:
286: /* (non-Javadoc)
287: * @see java.lang.Object#finalize()
288: */
289: protected void finalize() throws Throwable {
290:
291: stopServer();
292: super .finalize();
293: }
294:
295: /**
296: * @return Port server is running on.
297: */
298: public int getPort() {
299:
300: return this .port;
301: }
302:
303: /**
304: * @return Server reference.
305: */
306: public HttpServer getServer() {
307:
308: return this .server;
309: }
310:
311: /**
312: * @param contextName Name of context to look for. Possible names would be
313: * '/admin', '/', or '/selftest'.
314: *
315: * @return named context.
316: */
317: private WebApplicationContext getContext(String contextName) {
318:
319: WebApplicationContext context = null;
320:
321: if (this .contexts == null) {
322: throw new NullPointerException("No contexts available.");
323: }
324:
325: if (!contextName.startsWith("/")) {
326: contextName = '/' + contextName;
327: }
328: for (Iterator i = this .contexts.iterator(); i.hasNext();) {
329: WebApplicationContext c = (WebApplicationContext) i.next();
330: if (c.getHttpContextName().equals(contextName)) {
331: context = c;
332: break;
333: }
334: }
335:
336: if (context == null) {
337: throw new NoSuchElementException("Unknown webapp: "
338: + contextName);
339: }
340:
341: return context;
342: }
343:
344: /**
345: * Setup a realm on the server named for the webapp and add to the
346: * passed webapp's context.
347: *
348: * Used by the selftest to check digest authentication is working.
349: * For this all to work, the <code>web.xml</code> needs to set with
350: * a security constraint that points to a realm named for the passed
351: * webapp, <code>webappName</code>.
352: *
353: * @param realmName Name of realm to configure.
354: * @param contextName Name of context we're using with this realm.
355: * If null, we'll use the realm name as context name.
356: * @param authProperties Path to file that holds the auth login and
357: * password.
358: * @return Hash of user realms.
359: *
360: * @throws IOException
361: */
362: public HashUserRealm setAuthentication(String realmName,
363: String contextName, String authProperties)
364: throws IOException {
365: HashUserRealm realm = (authProperties != null && authProperties
366: .length() > 0) ? new HashUserRealm(realmName,
367: authProperties) : new HashUserRealm(realmName);
368: this .server.addRealm(realm);
369: if (contextName == null || contextName.length() <= 0) {
370: contextName = realmName;
371: }
372: WebApplicationContext context = getContext(contextName);
373: context.setRealmName(realmName);
374: return realm;
375: }
376:
377: public void setAuthentication(String realmName, String contextName,
378: String username, String password, String role)
379: throws IOException {
380: HashUserRealm realm = setAuthentication(realmName, contextName,
381: null);
382: realm.put(username, password);
383: realm.addUserToRole(username, role);
384: }
385:
386: /**
387: * Reset the administrator login info.
388: *
389: * @param realmAndRoleName for our use, always 'admin'
390: * @param oldUsername previous username to replace/disable
391: * @param newUsername new username (may be same as old)
392: * @param newPassword new password
393: */
394: public void resetAuthentication(String realmAndRoleName,
395: String oldUsername, String newUsername, String newPassword) {
396: HashUserRealm realm = (HashUserRealm) this .server
397: .getRealm(realmAndRoleName);
398: realm.remove(oldUsername);
399: realm.put(newUsername, newPassword);
400: realm.addUserToRole(newUsername, realmAndRoleName);
401: }
402:
403: /**
404: * Get path to named webapp.
405: *
406: * @param name Name of webpp. Possible names are 'admin' or 'selftest'.
407: *
408: * @return Path to deployed webapp.
409: */
410: public File getWebappPath(String name) {
411:
412: if (this .server == null) {
413: throw new NullPointerException("Server does not exist");
414: }
415: String contextName = (name.equals(this .server.getRootWebApp())) ? "/"
416: : "/" + name;
417: return new File(getContext(contextName).getServletHandler()
418: .getRealPath("/"));
419: }
420:
421: /**
422: * @return Returns the root webapp name.
423: */
424: public static String getRootWebappName() {
425: return ROOT_WEBAPP;
426: }
427:
428: /**
429: * Returns the hosts that the server is listening on.
430: *
431: * @return the hosts that the server is listening on.
432: */
433: public Collection<String> getHosts() {
434: ArrayList<String> result = new ArrayList<String>();
435: for (HttpListener listener : server.getListeners()) {
436: result.add(listener.getHost());
437: }
438: return result;
439: }
440: }
|