1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 package org.archive.crawler;
27
28 import java.io.File;
29 import java.io.FileNotFoundException;
30 import java.io.IOException;
31 import java.net.UnknownHostException;
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.Collection;
35 import java.util.Iterator;
36 import java.util.List;
37 import java.util.NoSuchElementException;
38
39 import org.mortbay.http.HashUserRealm;
40 import org.mortbay.http.HttpListener;
41 import org.mortbay.http.HttpServer;
42 import org.mortbay.http.NCSARequestLog;
43 import org.mortbay.http.RequestLog;
44 import org.mortbay.http.SocketListener;
45 import org.mortbay.jetty.Server;
46 import org.mortbay.jetty.servlet.WebApplicationContext;
47 import org.mortbay.util.InetAddrPort;
48
49
50 /***
51 * Wrapper for embedded Jetty server.
52 *
53 * Loads up all webapps under webapp directory.
54 *
55 */
56 public class SimpleHttpServer
57 {
58 private int port;
59 private Server server = null;
60
61 /***
62 * Default web port.
63 */
64 public static final int DEFAULT_PORT = 8080;
65
66 /***
67 * Webapp contexts returned out of a server start.
68 */
69 private List<WebApplicationContext> contexts
70 = new ArrayList<WebApplicationContext>();
71
72 /***
73 * Name of the root webapp.
74 */
75 private static final String ROOT_WEBAPP = "root";
76
77 /***
78 * Name of the admin webapp.
79 */
80 private static final String ADMIN_WEBAPP = "admin";
81
82 /***
83 * List of webapps to deploy.
84 */
85 private static final List webapps =
86 Arrays.asList(new String [] {ROOT_WEBAPP, ADMIN_WEBAPP});
87
88
89 public SimpleHttpServer() throws Exception {
90 this(DEFAULT_PORT, true);
91 }
92
93 public SimpleHttpServer(int port, boolean expandWebapps)
94 throws Exception {
95 this(SimpleHttpServer.webapps, port, expandWebapps);
96 }
97
98 /***
99 * @param name Name of webapp to load.
100 * @param context Where to mount the webapp. If passed context is
101 * null or empty string, we'll use '/' + <code>name</code> else if
102 * passed '/' then we'll add the webapp as the root webapp.
103 * @param port Port to run on.
104 * @param expandWebapps True if we're to expand the webapp passed.
105 * @throws Exception
106 * @deprecated Use SimpleHttpServer(name,context,hosts,port,expandWebapps)
107 */
108 public SimpleHttpServer(boolean localhostOnly, String name, String context,
109 int port, boolean expandWebapps)
110 throws Exception {
111 this(name, context, determineHosts(localhostOnly), port, expandWebapps);
112 }
113
114
115 /***
116 * Constructor.
117 *
118 * @param name Name of webapp to load
119 * @param context Where to mount the webap. If null or empty string,
120 * we'll use '/' + <code>name</code>; if passed '/'
121 * then we'll add the webapp as the root webapp
122 * @param hosts list of hosts to bind to
123 * @param port port to listen on
124 * @param expandWebapps true to expand webapp passed
125 * @throws Exception
126 */
127 public SimpleHttpServer(String name, String context,
128 Collection<String> hosts, int port, boolean expandWebapps)
129 throws Exception {
130 initialize(hosts, port);
131 addWebapp(name, context, expandWebapps);
132 this.server.setRequestLog(getServerLogging());
133 }
134
135
136 /***
137 * @param webapps List of webapps to load.
138 * @param port Port to run on.
139 * @param expandWebapps True if we're to expand the webapps found.
140 * @throws Exception
141 */
142 public SimpleHttpServer(List webapps, int port, boolean expandWebapps)
143 throws Exception {
144 initialize(null, port);
145
146
147
148
149
150 for (Iterator i = webapps.iterator(); i.hasNext();) {
151 addWebapp((String)i.next(), null, expandWebapps);
152 }
153 this.server.setRequestLog(getServerLogging());
154 }
155
156 /***
157 * Add a webapp.
158 * @param name Name of webapp to add.
159 * @param context Context to add the webapp on.
160 * @param expand True if we should expand the webapps.
161 * @throws IOException
162 */
163 protected void addWebapp(String name, String context, boolean expand)
164 throws IOException {
165 File ptr = new File(getWARSPath(), name + ".war");
166 if (!ptr.exists()) {
167 ptr = new File(getWARSPath(), name);
168 if (!ptr.exists()) {
169 throw new FileNotFoundException(ptr.getAbsolutePath());
170 }
171 }
172
173 if (context == null || context.length() <= 0) {
174 context = "/" + ((name.equals(ROOT_WEBAPP))? "": name);
175 }
176 WebApplicationContext c =
177 this.server. addWebApplication(context, ptr.getAbsolutePath());
178 if (context.equals("/")) {
179
180 this.server.setRootWebApp(name);
181 }
182
183 c.setExtractWAR(expand);
184
185 c.getServletHandler().getSessionManager().setMaxInactiveInterval(86400);
186 this.contexts.add(c);
187 }
188
189 /***
190 * Initialize the server.
191 * Called from constructors.
192 * @param port Port to start the server on.
193 * @deprecated Use initialize(Collection<String>, port) instead
194 */
195 protected void initialize(int port, boolean localhostOnly) {
196 Collection<String> hosts = determineHosts(localhostOnly);
197 initialize(hosts, port);
198 }
199
200
201 /***
202 * Initialize the server. Called from constructors.
203 *
204 * @param hosts the hostnames to bind to; if empty or null, will bind
205 * to all interfaces
206 * @param port the port to listen on
207 */
208 protected void initialize(Collection<String> hosts, int port) {
209 this.server = new Server();
210 this.port = port;
211 if (hosts.isEmpty()) {
212 SocketListener listener = new SocketListener();
213 listener.setPort(port);
214 this.server.addListener(listener);
215 return;
216 }
217
218 for (String host: hosts) try {
219 InetAddrPort addr = new InetAddrPort(host, port);
220 SocketListener listener = new SocketListener(addr);
221 this.server.addListener(listener);
222 } catch (UnknownHostException e) {
223 e.printStackTrace();
224 }
225 }
226
227
228 private static Collection<String> determineHosts(boolean lho) {
229 Collection<String> hosts = new ArrayList<String>();
230 if (lho) {
231 hosts.add("127.0.0.1");
232 }
233 return hosts;
234 }
235
236
237 /***
238 * Setup log files.
239 * @return RequestLog instance to add to a server.
240 * @throws Exception
241 */
242 protected RequestLog getServerLogging() throws Exception {
243
244
245 NCSARequestLog a = new NCSARequestLog(Heritrix.getHeritrixOut());
246 a.setRetainDays(90);
247 a.setAppend(true);
248 a.setExtended(false);
249 a.setBuffered(false);
250 a.setLogTimeZone("GMT");
251 a.start();
252 return a;
253 }
254
255 /***
256 * Return the directory that holds the WARs we're to deploy.
257 *
258 * @return Return webapp path (Path returned has a trailing '/').
259 * @throws IOException
260 */
261 private static String getWARSPath() throws IOException {
262 String webappsPath = Heritrix.getWarsdir().getAbsolutePath();
263 if (!webappsPath.endsWith(File.separator))
264 {
265 webappsPath = webappsPath + File.separator;
266 }
267 return webappsPath;
268 }
269
270 /***
271 * Start the server.
272 *
273 * @throws Exception if problem starting server or if server already
274 * started.
275 */
276 public synchronized void startServer()
277 throws Exception {
278
279 this.server.start();
280 }
281
282 /***
283 * Stop the running server.
284 *
285 * @throws InterruptedException
286 */
287 public synchronized void stopServer() throws InterruptedException {
288
289 if (this.server != null)
290 {
291 this.server.stop();
292 }
293 }
294
295
296
297
298 protected void finalize()
299 throws Throwable {
300
301 stopServer();
302 super.finalize();
303 }
304
305 /***
306 * @return Port server is running on.
307 */
308 public int getPort() {
309
310 return this.port;
311 }
312
313 /***
314 * @return Server reference.
315 */
316 public HttpServer getServer() {
317
318 return this.server;
319 }
320
321 /***
322 * @param contextName Name of context to look for. Possible names would be
323 * '/admin', '/', or '/selftest'.
324 *
325 * @return named context.
326 */
327 private WebApplicationContext getContext(String contextName) {
328
329 WebApplicationContext context = null;
330
331 if (this.contexts == null)
332 {
333 throw new NullPointerException("No contexts available.");
334 }
335
336 if (!contextName.startsWith("/")) {
337 contextName = '/' + contextName;
338 }
339 for (Iterator i = this.contexts.iterator(); i.hasNext();)
340 {
341 WebApplicationContext c = (WebApplicationContext)i.next();
342 if (c.getHttpContextName().equals(contextName))
343 {
344 context = c;
345 break;
346 }
347 }
348
349 if (context == null)
350 {
351 throw new NoSuchElementException("Unknown webapp: " + contextName);
352 }
353
354 return context;
355 }
356
357 /***
358 * Setup a realm on the server named for the webapp and add to the
359 * passed webapp's context.
360 *
361 * Used by the selftest to check digest authentication is working.
362 * For this all to work, the <code>web.xml</code> needs to set with
363 * a security constraint that points to a realm named for the passed
364 * webapp, <code>webappName</code>.
365 *
366 * @param realmName Name of realm to configure.
367 * @param contextName Name of context we're using with this realm.
368 * If null, we'll use the realm name as context name.
369 * @param authProperties Path to file that holds the auth login and
370 * password.
371 * @return Hash of user realms.
372 *
373 * @throws IOException
374 */
375 public HashUserRealm setAuthentication(String realmName,
376 String contextName, String authProperties)
377 throws IOException {
378 HashUserRealm realm =
379 (authProperties != null && authProperties.length() > 0)?
380 new HashUserRealm(realmName, authProperties):
381 new HashUserRealm(realmName);
382 this.server.addRealm(realm);
383 if (contextName == null || contextName.length() <= 0) {
384 contextName = realmName;
385 }
386 WebApplicationContext context = getContext(contextName);
387 context.setRealmName(realmName);
388 return realm;
389 }
390
391 public void setAuthentication(String realmName, String contextName,
392 String username, String password, String role)
393 throws IOException {
394 HashUserRealm realm = setAuthentication(realmName, contextName,
395 null);
396 realm.put(username, password);
397 realm.addUserToRole(username, role);
398 }
399
400
401 /***
402 * Reset the administrator login info.
403 *
404 * @param realmAndRoleName for our use, always 'admin'
405 * @param oldUsername previous username to replace/disable
406 * @param newUsername new username (may be same as old)
407 * @param newPassword new password
408 */
409 public void resetAuthentication(String realmAndRoleName,
410 String oldUsername, String newUsername, String newPassword) {
411 HashUserRealm realm = (HashUserRealm)this.server.
412 getRealm(realmAndRoleName);
413 realm.remove(oldUsername);
414 realm.put(newUsername,newPassword);
415 realm.addUserToRole(newUsername, realmAndRoleName);
416 }
417
418 /***
419 * Get path to named webapp.
420 *
421 * @param name Name of webpp. Possible names are 'admin' or 'selftest'.
422 *
423 * @return Path to deployed webapp.
424 */
425 public File getWebappPath(String name) {
426
427 if (this.server == null) {
428 throw new NullPointerException("Server does not exist");
429 }
430 String contextName =
431 (name.equals(this.server.getRootWebApp()))? "/": "/" + name;
432 return new
433 File(getContext(contextName).getServletHandler().getRealPath("/"));
434 }
435
436 /***
437 * @return Returns the root webapp name.
438 */
439 public static String getRootWebappName()
440 {
441 return ROOT_WEBAPP;
442 }
443
444
445 /***
446 * Returns the hosts that the server is listening on.
447 *
448 * @return the hosts that the server is listening on.
449 */
450 public Collection<String> getHosts() {
451 ArrayList<String> result = new ArrayList<String>();
452 for (HttpListener listener: server.getListeners()) {
453 result.add(listener.getHost());
454 }
455 return result;
456 }
457 }