KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jboss > web > WebServer


1 /*
2 * JBoss, Home of Professional Open Source
3 * Copyright 2005, JBoss Inc., and individual contributors as indicated
4 * by the @authors tag. See the copyright.txt in the distribution for a
5 * full listing of individual contributors.
6 *
7 * This is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation; either version 2.1 of
10 * the License, or (at your option) any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this software; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21 */

22 package org.jboss.web;
23
24 import java.io.BufferedInputStream JavaDoc;
25 import java.io.BufferedReader JavaDoc;
26 import java.io.ByteArrayOutputStream JavaDoc;
27 import java.io.DataOutputStream JavaDoc;
28 import java.io.IOException JavaDoc;
29 import java.io.InputStream JavaDoc;
30 import java.io.InputStreamReader JavaDoc;
31 import java.net.InetAddress JavaDoc;
32 import java.net.MalformedURLException JavaDoc;
33 import java.net.ServerSocket JavaDoc;
34 import java.net.Socket JavaDoc;
35 import java.net.URL JavaDoc;
36 import java.net.UnknownHostException JavaDoc;
37 import java.util.Properties JavaDoc;
38
39 import org.jboss.logging.Logger;
40 import org.jboss.util.StringPropertyReplacer;
41 import org.jboss.util.threadpool.BasicThreadPool;
42 import org.jboss.util.threadpool.BasicThreadPoolMBean;
43
44 import EDU.oswego.cs.dl.util.concurrent.ConcurrentReaderHashMap;
45
46 /**
47  * A mini webserver that should be embedded in another application. It can
48  * server any file that is available from classloaders that are registered with
49  * it, including class-files.
50  *
51  * Its primary purpose is to simplify dynamic class-loading in RMI. Create an
52  * instance of it, register a classloader with your classes, start it, and
53  * you'll be able to let RMI-clients dynamically download classes from it.
54  *
55  * It is configured by calling any methods programmatically prior to startup.
56  * @author <a HREF="mailto:marc@jboss.org">Marc Fleury</a>
57  * @author <a HREF="mailto:Scott.Stark@org.jboss">Scott Stark</a>.
58  * @version $Revision: 37459 $
59  * @see WebClassLoader
60  */

61 public class WebServer
62    implements Runnable JavaDoc
63 {
64    // Constants -----------------------------------------------------
65

66    // Attributes ----------------------------------------------------
67
private static Logger log = Logger.getLogger(WebServer.class);
68    /**
69     * The port the web server listens on
70     */

71    private int port = 8083;
72
73    /**
74     * The interface to bind to. This is useful for multi-homed hosts that want
75     * control over which interfaces accept connections.
76     */

77    private InetAddress JavaDoc bindAddress;
78    /**
79     * The serverSocket listen queue depth
80     */

81    private int backlog = 50;
82
83    /**
84     * The map of class loaders registered with the web server
85     */

86    private final ConcurrentReaderHashMap loaderMap = new ConcurrentReaderHashMap();
87    /**
88     * The web server http listening socket
89     */

90    private ServerSocket JavaDoc server = null;
91    /**
92     * A flag indicating if the server should attempt to download classes from
93     * thread context class loader when a request arrives that does not have a
94     * class loader key prefix.
95     */

96    private boolean downloadServerClasses = true;
97    /**
98     * A flag indicating if the server should attempt to download resources,
99     * i.e. resource paths that don't end in .class
100     */

101    private boolean downloadResources = false;
102    /**
103     * The class wide mapping of type suffixes(class, txt) to their mime type
104     * string used as the Content-Type header for the vended classes/resources
105     */

106    private static final Properties JavaDoc mimeTypes = new Properties JavaDoc();
107    /**
108     * The thread pool used to manage listening threads
109     */

110    private BasicThreadPoolMBean threadPool;
111
112    // Public --------------------------------------------------------
113

114    /**
115     * Set the http listening port
116     */

117    public void setPort(int p)
118    {
119       port = p;
120    }
121
122    /**
123     * Get the http listening port
124     * @return the http listening port
125     */

126    public int getPort()
127    {
128       return port;
129    }
130
131    public String JavaDoc getBindAddress()
132    {
133       String JavaDoc address = null;
134       if (bindAddress != null)
135          address = bindAddress.getHostAddress();
136       return address;
137    }
138
139    public String JavaDoc getBindHostname()
140    {
141       return bindAddress.getHostName();
142    }
143
144    public void setBindAddress(String JavaDoc host)
145    {
146       try
147       {
148          if (host != null)
149          {
150             String JavaDoc h = StringPropertyReplacer.replaceProperties(host);
151             bindAddress = InetAddress.getByName(h);
152          }
153       }
154       catch (UnknownHostException JavaDoc e)
155       {
156          String JavaDoc msg = "Invalid host address specified: " + host;
157          log.error(msg, e);
158       }
159    }
160
161    /**
162     * Get the server sockets listen queue depth
163     * @return the listen queue depth
164     */

165    public int getBacklog()
166    {
167       return backlog;
168    }
169
170    /**
171     * Set the server sockets listen queue depth
172     */

173    public void setBacklog(int backlog)
174    {
175       if (backlog <= 0)
176          backlog = 50;
177       this.backlog = backlog;
178    }
179
180    public boolean getDownloadServerClasses()
181    {
182       return downloadServerClasses;
183    }
184
185    public void setDownloadServerClasses(boolean flag)
186    {
187       downloadServerClasses = flag;
188    }
189    
190    public boolean getDownloadResources()
191    {
192       return downloadResources;
193    }
194
195    public void setDownloadResources(boolean flag)
196    {
197       downloadResources = flag;
198    }
199
200
201    public BasicThreadPoolMBean getThreadPool()
202    {
203       return threadPool;
204    }
205
206    public void setThreadPool(BasicThreadPoolMBean threadPool)
207    {
208       this.threadPool = threadPool;
209    }
210
211    /**
212     * Augment the type suffix to mime type mappings
213     * @param extension - the type extension without a period(class, txt)
214     * @param type - the mime type string
215     */

216    public void addMimeType(String JavaDoc extension, String JavaDoc type)
217    {
218       mimeTypes.put(extension, type);
219    }
220
221    /**
222     * Start the web server on port and begin listening for requests.
223     */

224    public void start() throws Exception JavaDoc
225    {
226       if (threadPool == null)
227          threadPool = new BasicThreadPool("ClassLoadingPool");
228       try
229       {
230          server = new ServerSocket JavaDoc(port, backlog, bindAddress);
231          log.debug("Started server: " + server);
232          listen();
233       }
234       catch(java.net.BindException JavaDoc be)
235       {
236           throw new Exception JavaDoc("Port "+port+" already in use.",be);
237       }
238       catch (IOException JavaDoc e)
239       {
240          throw e;
241       }
242    }
243
244    /**
245     * Close the web server listening socket
246     */

247    public void stop()
248    {
249       try
250       {
251          ServerSocket JavaDoc srv = server;
252          server = null;
253          srv.close();
254       }
255       catch (Exception JavaDoc e)
256       {
257       }
258    }
259
260    /**
261     * Add a class loader to the web server map and return the URL that should be
262     * used as the annotated codebase for classes that are to be available via
263     * RMI dynamic classloading. The codebase URL is formed by taking the
264     * java.rmi.server.codebase system property and adding a subpath unique for
265     * the class loader instance.
266     * @param cl - the ClassLoader instance to begin serving download requests
267     * for
268     * @return the annotated codebase to use if java.rmi.server.codebase is set,
269     * null otherwise.
270     * @see #getClassLoaderKey(ClassLoader)
271     */

272    public URL JavaDoc addClassLoader(ClassLoader JavaDoc cl)
273    {
274       String JavaDoc key = (cl instanceof WebClassLoader) ?
275          ((WebClassLoader) cl).getKey() :
276          getClassLoaderKey(cl);
277       loaderMap.put(key, cl);
278       URL JavaDoc loaderURL = null;
279       String JavaDoc codebase = System.getProperty("java.rmi.server.codebase");
280       if (codebase != null)
281       {
282          if (codebase.endsWith("/") == false)
283             codebase += '/';
284          codebase += key;
285          codebase += '/';
286          try
287          {
288             loaderURL = new URL JavaDoc(codebase);
289          }
290          catch (MalformedURLException JavaDoc e)
291          {
292             log.error("invalid url", e);
293          }
294       }
295       log.trace("Added ClassLoader: " + cl + " URL: " + loaderURL);
296       return loaderURL;
297    }
298
299    /**
300     * Remove a class loader previously added via addClassLoader
301     * @param cl - the ClassLoader previously added via addClassLoader
302     */

303    public void removeClassLoader(ClassLoader JavaDoc cl)
304    {
305       String JavaDoc key = getClassLoaderKey(cl);
306       loaderMap.remove(key);
307    }
308
309    // Runnable implementation ---------------------------------------
310
/**
311     * Listen threads entry point. Here we accept a client connection and located
312     * requested classes/resources using the class loader specified in the http
313     * request.
314     */

315    public void run()
316    {
317       // Return if the server has been stopped
318
if (server == null)
319          return;
320
321       // Accept a connection
322
Socket JavaDoc socket = null;
323       try
324       {
325          socket = server.accept();
326       }
327       catch (IOException JavaDoc e)
328       {
329          // If the server is not null meaning we were not stopped report the err
330
if (server != null)
331             log.error("Failed to accept connection", e);
332          return;
333       }
334
335       // Create a new thread to accept the next connection
336
listen();
337
338       try
339       {
340          // Get the request socket output stream
341
DataOutputStream JavaDoc out = new DataOutputStream JavaDoc(socket.getOutputStream());
342          try
343          {
344             String JavaDoc httpCode = "200 OK";
345             // Get the requested item from the HTTP header
346
BufferedReader JavaDoc in = new BufferedReader JavaDoc(new InputStreamReader JavaDoc(socket.getInputStream()));
347             String JavaDoc rawPath = getPath(in);
348
349             // Parse the path into the class loader key and file path.
350
//
351
// The class loader key is a string whose format is
352
// "ClassName[oid]", where the oid substring may contain '/'
353
// chars. The expected form of the raw path is:
354
//
355
// "SomeClassName[some/object/id]/some/file/path"
356
//
357
// The class loader key is "SomeClassName[some/object/id]"
358
// and the file path is "some/file/path"
359

360             int endOfKey = rawPath.indexOf(']');
361             String JavaDoc filePath = rawPath.substring(endOfKey + 2);
362             String JavaDoc loaderKey = rawPath.substring(0, endOfKey + 1);
363             log.trace("loaderKey = " + loaderKey);
364             log.trace("filePath = " + filePath);
365             ClassLoader JavaDoc loader = (ClassLoader JavaDoc) loaderMap.get(loaderKey);
366             /* If we did not find a class loader check to see if the raw path
367                begins with className + '[' + class loader key + ']' by looking for
368                an '[' char. If it does not and downloadServerClasses is true use
369                the thread context class loader and set filePath to the rawPath
370             */

371             if (loader == null && rawPath.indexOf('[') < 0 && downloadServerClasses)
372             {
373                filePath = rawPath;
374                log.trace("No loader, reset filePath = " + filePath);
375                loader = Thread.currentThread().getContextClassLoader();
376             }
377             log.trace("loader = " + loader);
378             byte[] bytes = {};
379             if (loader != null && filePath.endsWith(".class"))
380             {
381                // A request for a class file
382
String JavaDoc className = filePath.substring(0, filePath.length() - 6).replace('/', '.');
383                log.trace("loading className = " + className);
384                Class JavaDoc clazz = loader.loadClass(className);
385                URL JavaDoc clazzUrl = clazz.getProtectionDomain().getCodeSource().getLocation();
386                log.trace("clazzUrl = " + clazzUrl);
387                if (clazzUrl == null)
388                {
389                   // Does the WebClassLoader of clazz
390
// have the ability to obtain the bytecodes of clazz?
391
bytes = ((WebClassLoader) clazz.getClassLoader()).getBytes(clazz);
392                   if (bytes == null)
393                      throw new Exception JavaDoc("Class not found: " + className);
394                }
395                else
396                {
397                   if (clazzUrl.getFile().endsWith("/") == false)
398                   {
399                      clazzUrl = new URL JavaDoc("jar:" + clazzUrl + "!/" + filePath);
400                   }
401                   // this is a hack for the AOP ClassProxyFactory
402
else if (clazzUrl.getFile().indexOf("/org_jboss_aop_proxy$") < 0)
403                   {
404                      clazzUrl = new URL JavaDoc(clazzUrl, filePath);
405                   }
406
407                   // Retrieve bytecodes
408
log.trace("new clazzUrl: " + clazzUrl);
409                   bytes = getBytes(clazzUrl);
410                }
411             }
412             else if (loader != null && filePath.length() > 0 && downloadServerClasses && downloadResources)
413             {
414                // Try getting resource
415
log.trace("loading resource = " + filePath);
416                URL JavaDoc resourceURL = loader.getResource(filePath);
417                if (resourceURL == null)
418                   httpCode = "404 Resource not found:" + filePath;
419                else
420                {
421                   // Retrieve bytes
422
log.trace("resourceURL = " + resourceURL);
423                   bytes = getBytes(resourceURL);
424                }
425             }
426             else
427             {
428                httpCode = "404 Not Found";
429             }
430
431
432             // Send bytecodes/resource data in response (assumes HTTP/1.0 or later)
433
try
434             {
435                log.trace("HTTP code=" + httpCode + ", Content-Length: " + bytes.length);
436                // The HTTP 1.0 header
437
out.writeBytes("HTTP/1.0 " + httpCode + "\r\n");
438                out.writeBytes("Content-Length: " + bytes.length + "\r\n");
439                out.writeBytes("Content-Type: " + getMimeType(filePath));
440                out.writeBytes("\r\n\r\n");
441                // The response body
442
out.write(bytes);
443                out.flush();
444             }
445             catch (IOException JavaDoc ie)
446             {
447                return;
448             }
449          }
450          catch (Throwable JavaDoc e)
451          {
452             try
453             {
454                log.trace("HTTP code=404 " + e.getMessage());
455                // Write out error response
456
out.writeBytes("HTTP/1.0 400 " + e.getMessage() + "\r\n");
457                out.writeBytes("Content-Type: text/html\r\n\r\n");
458                out.flush();
459             }
460             catch (IOException JavaDoc ex)
461             {
462                // Ignore
463
}
464          }
465       }
466       catch (IOException JavaDoc ex)
467       {
468          log.error("error writting response", ex);
469       }
470       finally
471       {
472          // Close the client request socket
473
try
474          {
475             socket.close();
476          }
477          catch (IOException JavaDoc e)
478          {
479          }
480       }
481    }
482
483    // Protected -----------------------------------------------------
484
/**
485     * Create the string key used as the key into the loaderMap.
486     * @return The class loader instance key.
487     */

488    protected String JavaDoc getClassLoaderKey(ClassLoader JavaDoc cl)
489    {
490       String JavaDoc className = cl.getClass().getName();
491       int dot = className.lastIndexOf('.');
492       if (dot >= 0)
493          className = className.substring(dot + 1);
494       String JavaDoc key = className + '[' + cl.hashCode() + ']';
495       return key;
496    }
497
498    protected void listen()
499    {
500       threadPool.getInstance().run(this);
501    }
502
503    /**
504     * @return the path portion of the HTTP request header.
505     */

506    protected String JavaDoc getPath(BufferedReader JavaDoc in) throws IOException JavaDoc
507    {
508       String JavaDoc line = in.readLine();
509       log.trace("raw request=" + line);
510       // Find the request path by parsing the 'REQUEST_TYPE filePath HTTP_VERSION' string
511
int start = line.indexOf(' ') + 1;
512       int end = line.indexOf(' ', start + 1);
513       // The file minus the leading '/'
514
String JavaDoc filePath = line.substring(start + 1, end);
515       return filePath;
516    }
517
518    /**
519     * Read the local class/resource contents into a byte array.
520     */

521    protected byte[] getBytes(URL JavaDoc url) throws IOException JavaDoc
522    {
523       InputStream JavaDoc in = new BufferedInputStream JavaDoc(url.openStream());
524       log.debug("Retrieving " + url);
525       ByteArrayOutputStream JavaDoc out = new ByteArrayOutputStream JavaDoc();
526       byte[] tmp = new byte[1024];
527       int bytes;
528       while ((bytes = in.read(tmp)) != -1)
529       {
530          out.write(tmp, 0, bytes);
531       }
532       in.close();
533       return out.toByteArray();
534    }
535
536    /**
537     * Lookup the mime type for the suffix of the path argument.
538     * @return the mime-type string for path.
539     */

540    protected String JavaDoc getMimeType(String JavaDoc path)
541    {
542       int dot = path.lastIndexOf(".");
543       String JavaDoc type = "text/html";
544       if (dot >= 0)
545       {
546          // The suffix is the type extension without the '.'
547
String JavaDoc suffix = path.substring(dot + 1);
548          String JavaDoc mimeType = mimeTypes.getProperty(suffix);
549          if (mimeType != null)
550             type = mimeType;
551       }
552       return type;
553    }
554
555 }
556
Popular Tags