Main Page | Class Hierarchy | Alphabetical List | Class List | File List | Class Members | File Members | Related Pages

ServletProvider.d

Go to the documentation of this file.
00001 /*******************************************************************************
00002 
00003         @file ServletProvider.d
00004         
00005         Copyright (c) 2004 Kris Bell
00006         
00007         This software is provided 'as-is', without any express or implied
00008         warranty. In no event will the authors be held liable for damages
00009         of any kind arising from the use of this software.
00010         
00011         Permission is hereby granted to anyone to use this software for any 
00012         purpose, including commercial applications, and to alter it and/or 
00013         redistribute it freely, subject to the following restrictions:
00014         
00015         1. The origin of this software must not be misrepresented; you must 
00016            not claim that you wrote the original software. If you use this 
00017            software in a product, an acknowledgment within documentation of 
00018            said product would be appreciated but is not required.
00019 
00020         2. Altered source versions must be plainly marked as such, and must 
00021            not be misrepresented as being the original software.
00022 
00023         3. This notice may not be removed or altered from any distribution
00024            of the source.
00025 
00026         4. Derivative works are permitted, but they must carry this notice
00027            in full and credit the original source.
00028 
00029 
00030                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
00031 
00032      
00033         @version        Initial version, April 2004      
00034         @author         Kris
00035 
00036 
00037 *******************************************************************************/
00038 
00039 module mango.servlet.ServletProvider;
00040 
00041 private import  std.regexp;
00042  
00043 private import  mango.utils.Text;
00044    
00045 private import  mango.io.Uri,
00046                 mango.io.Exception;
00047 
00048 private import  mango.cache.HashMap,
00049                 mango.cache.Payload,
00050                 mango.cache.QueuedCache;
00051 
00052 private import  mango.servlet.Servlet,
00053                 mango.servlet.ServletConfig,
00054                 mango.servlet.ServletContext,
00055                 mango.servlet.ServletRequest,
00056                 mango.servlet.ServletResponse;
00057 
00058 private import  mango.http.server.HttpRequest,
00059                 mango.http.server.HttpResponse;
00060 
00061 private import  mango.http.server.model.IProvider,
00062                 mango.http.server.model.IProviderBridge;
00063 
00064 
00065 /******************************************************************************
00066 
00067         ServletProvider is a servlet-host implementation; you bind one of
00068         these to an HttpServer instance and you're up and running. This 
00069         particular implementation has a few quirks worth noting:
00070 
00071         @li each http request is mapped to a servlet instance via a set of
00072            regex patterns. That is far too expensive to figure out upon 
00073            each request, so a cache of Uri request versus mapped servlets 
00074            is maintained. Subsequent requests go straight to the cached 
00075            servlet mapping. The cache size should be specified as large 
00076            enough to contain the vast majority of valid path requests.
00077 
00078         @li the mapping of a servlet to a request does not follow the spec
00079            to the letter. Specifically, the namespace is not seperated out 
00080            into isolated sections for each of the four mapping types, so 
00081            it's a case of "first found wins" regarding the matching of 
00082            patterns to URL paths. This should not cause any problem in the
00083            general case, since it's very rare to have such a servlet-naming
00084            conflict. The tradeoff is one runtime lookup versus four; we 
00085            chose the former.
00086 
00087         @li the "default context" implemented here has an empty name, as
00088            opposed to a name of "/". This simplifies the code but might 
00089            look alien; there's a getDefaultContext() method to alleviate
00090            such concerns.
00091 
00092         Overall this should be pretty fast, since it doesn't cause any
00093         memory allocation whatsoever (once operationally primed).
00094 
00095 ******************************************************************************/
00096 
00097 class ServletProvider : IProvider
00098 {
00099         private QueuedCache             cache;
00100         private HashMap                 proxies;
00101         private HashMap                 contexts;
00102         private ServletMapping[]        mappings;
00103 
00104         /**********************************************************************
00105 
00106                 Construct a ServletProvider with a default path-mapping
00107                 cache of 2048 entries. We also create the default context
00108                 here.
00109 
00110         **********************************************************************/
00111 
00112         this (uint urls = 2048)
00113         {
00114                 // small, low contention hashmap for proxies and contexts
00115                 proxies = new HashMap (128, 0.75, 1);
00116                 contexts = new HashMap (128, 0.75, 1);
00117 
00118                 // medium concurrency hashmap for the url cache
00119                 cache = new QueuedCache (urls, 16);
00120 
00121                 // create the default context. Note that this is not 
00122                 // named '/', permitting usage of context.getName() 
00123                 // to be greatly simplified
00124                 addContext (new ServletContext ("")); 
00125         }
00126 
00127         /**********************************************************************
00128 
00129                 IProvider interface method
00130 
00131         **********************************************************************/
00132 
00133         HttpRequest createRequest (IProviderBridge bridge)
00134         {
00135                 return new ServletRequest (bridge);
00136         }
00137 
00138         /**********************************************************************
00139 
00140                 IProvider interface method
00141 
00142         **********************************************************************/
00143 
00144         HttpResponse createResponse (IProviderBridge bridge)
00145         {
00146                 return new ServletResponse (bridge);
00147         }
00148 
00149         /**********************************************************************
00150 
00151                 Return the name of this provider
00152 
00153         **********************************************************************/
00154 
00155         override char[] toString()
00156         {
00157                 return "Servlet";
00158         }
00159 
00160         /**********************************************************************
00161 
00162                 Return the default context. This is used for those 
00163                 servlets which don't have a context of their own, and
00164                 is effectively a backwards-compatability hack.
00165 
00166         **********************************************************************/
00167 
00168         ServletContext getDefaultContext ()
00169         {
00170                 return getContext ("");
00171         }
00172 
00173         /**********************************************************************
00174 
00175                 Return the named context, or null if the name is unregistered
00176 
00177         **********************************************************************/
00178 
00179         ServletContext getContext (char[] name)
00180         in {
00181            assert (name !== null);
00182            }
00183         body
00184         {
00185                 return cast(ServletContext) contexts.get (name);
00186         }
00187 
00188         /**********************************************************************
00189 
00190                 Register a servlet context. The name is provided by the 
00191                 context itself.
00192 
00193         **********************************************************************/
00194 
00195         ServletContext addContext (ServletContext context)
00196         in {
00197            assert (context);
00198            }
00199         body
00200         {
00201                 contexts.put (context.getName, context);
00202                 return context;
00203         }
00204 
00205         /**********************************************************************
00206 
00207                 lookup and cast HashMap entry
00208 
00209         **********************************************************************/
00210 
00211         private final ServletProxy lookupProxy (char[] name)
00212         {
00213                 return cast(ServletProxy) proxies.get (name);
00214         }
00215 
00216         /**********************************************************************
00217 
00218                 Add a uri-mapping for the named servlet. The servlet should
00219                 have been registered previously.
00220 
00221         **********************************************************************/
00222 
00223         void addMapping (char[] pattern, char[] servlet)
00224         in {
00225            assert (servlet.length);
00226            }
00227         body
00228         {
00229                 ServletProxy proxy = lookupProxy (servlet);
00230                 if (proxy)
00231                     addMapping (pattern, proxy);
00232                 else
00233                    throw new ServletException ("Invalid servlet mapping argument");
00234         }
00235                 
00236         /**********************************************************************
00237 
00238                 Add a uri-mapping for the specified servlet. We follow the
00239                 Java spec in terms of pattern support, but the namespace is
00240                 not seperated for the four different pattern types. That is,
00241                 all the mappings are placed into a single namespace.
00242 
00243         **********************************************************************/
00244 
00245         void addMapping (char[] pattern, IRegisteredServlet servlet)
00246         in {
00247            assert (servlet);
00248            }
00249         body
00250         {
00251                 // context is always used, even when it's "" for the default context
00252                 char[] context = "^" ~ servlet.getContext.getName();
00253 
00254                 // check for default context specifier
00255                 if (pattern is null || pattern == "/")
00256                     pattern = "";
00257 
00258                 int i = Text.indexOf (pattern, '*');
00259                 if (i == 0)
00260                     // file extension 
00261                     pattern = "/.+\\" ~ pattern[1..pattern.length] ~ "$";
00262                 else
00263                    if (i > 0)
00264                        // path extension
00265                        pattern = pattern[0..i] ~ ".*";
00266                    else
00267                       if (pattern.length == 0)
00268                           // default
00269                           pattern = "/.*";
00270                       else
00271                          // explicit filename
00272                          pattern = pattern ~ "$";
00273                 
00274                 // prepend the context ...
00275                 pattern = context ~ pattern;
00276 
00277                 // add to list of mappings
00278                 mappings ~= new ServletMapping (servlet, new RegExp(pattern, null));
00279                 
00280                 version (Debug)
00281                          printf ("Pattern '%.*s'\n", pattern);
00282         }
00283 
00284         /**********************************************************************
00285 
00286                 Return the servlet registered with the specified name, or
00287                 null if there is no such servlet.
00288 
00289         **********************************************************************/
00290 
00291         IRegisteredServlet getServlet (char[] name)
00292         in {
00293            assert (name.length);
00294            }
00295         body
00296         {
00297                 return lookupProxy (name);
00298         }
00299 
00300         /**********************************************************************
00301 
00302                 Register a servlet with the specified name. The servlet 
00303                 is associated with the default context.
00304 
00305         **********************************************************************/
00306 
00307         IRegisteredServlet addServlet (Servlet servlet, char[] name)
00308         {
00309                 return addServlet (servlet, name, getDefaultContext());
00310         }
00311 
00312         /**********************************************************************
00313 
00314                 Register a servlet with the specified name and context 
00315 
00316         **********************************************************************/
00317 
00318         IRegisteredServlet addServlet (Servlet servlet, char[] name, char[] context)
00319         in {
00320            assert (context !== null);
00321            }
00322         body
00323         {
00324                 // backward compatability for default context ...
00325                 if (context == "/")
00326                     context = "";
00327 
00328                 return addServlet (servlet, name, getContext (context));
00329         }
00330 
00331         /**********************************************************************
00332 
00333                 Register a servlet with the specified name and context 
00334 
00335         **********************************************************************/
00336 
00337         IRegisteredServlet addServlet (Servlet servlet, char[] name, ServletContext context)
00338         in {
00339            assert (context !== null);
00340            }
00341         body
00342         {
00343                 return addServlet (servlet, name, new ServletConfig (context));
00344         }
00345 
00346         /**********************************************************************
00347 
00348                 Register a servlet with the specified name and configuration 
00349 
00350         **********************************************************************/
00351 
00352         IRegisteredServlet addServlet (Servlet servlet, char[] name, ServletConfig config)
00353         in {
00354            assert (name.length);
00355            assert (config !== null);
00356            assert (servlet !== null);
00357            assert (config.getServletContext() !== null);
00358            }
00359         body
00360         {
00361                 ServletProxy proxy = new ServletProxy (servlet, name, config.getServletContext());
00362                 proxies.put (name, proxy);
00363 
00364                 // initialize this servlet
00365                 servlet.init (config);
00366 
00367                 return proxy;
00368         }
00369 
00370         /**********************************************************************
00371 
00372                 Scan the servlet mappings, looking for one that matches
00373                 the specified path. The first match found is returned.
00374 
00375         **********************************************************************/
00376 
00377         private PathMapping constructPathMapping (char[] path)
00378         {
00379                 foreach (ServletMapping m; mappings)
00380                          if (m.regex.test (path))
00381                              return new PathMapping (m, path);
00382                 return null;
00383         }
00384 
00385         /**********************************************************************
00386 
00387                 IProvider interface method. This is where the real work 
00388                 is done, and where optimization efforts should be focused.
00389                 The process itself is straightforward:
00390 
00391                 @li we lookup the mapping cache to see if we've processed
00392                     the request before. If not, we create a new mapping
00393 
00394                 @li we then setup the input parameters to the servlet, and
00395                     invoke the latter.
00396 
00397                 @li lastly, we flush the response
00398 
00399                 All exceptions are caught and logged.
00400 
00401         **********************************************************************/
00402 
00403         void service (HttpRequest req, HttpResponse res)
00404         {
00405                 PathMapping             pm;
00406                 char[]                  path;
00407                 ServletRequest          request;
00408                 ServletResponse         response;
00409                 bool                    addToCache;
00410 
00411                 // we know what these are since we created them (above)
00412                 request = cast(ServletRequest) req;
00413                 response = cast(ServletResponse) res;
00414 
00415                 // retrieve the requested uri
00416                 path = request.getUri.getPath();
00417                 
00418                 // lookup servlet for this path
00419                 pm = cast (PathMapping) cache.get (path);
00420 
00421                 // construct a new cache entry if not found
00422                 if (pm is null)
00423                    {
00424                    // take a copy of the path since we're gonna' hold onto it
00425                    pm = constructPathMapping (path.dup);
00426 
00427                    // did we find a matching servlet?                
00428                    if (pm is null)
00429                        // nope; go home ...
00430                        return response.sendError (HttpResponses.NotFound);
00431 
00432                    // add this new URI path to the cache
00433                    addToCache = true;
00434                    }
00435 
00436                 // ready to go ...
00437                 try {
00438                     // initialize the servlet environment
00439                     request.set (pm.mapping.proxy.getName, pm.mapping.proxy.getContext);
00440 
00441                     // execute servlet
00442                     pm.mapping.proxy.getServlet.service (request, response);
00443 
00444                     // flush output on behalf of servlet ...
00445                     response.flush (response.getWriter);
00446 
00447                     // processed successfully? add new URI path to cache ...
00448                     if (addToCache && (response.getStatus.code == HttpResponseCode.OK))
00449                         cache.put (pm.path, pm);
00450 
00451                     } catch (UnavailableException ux)
00452                              response.sendError (HttpResponses.ServiceUnavailable, ux.toString);
00453 
00454                       catch (ServletException sx)
00455                              error (response, sx);
00456 
00457                       catch (Object ex)
00458                              error (response, ex);
00459         }
00460 
00461         /**********************************************************************
00462 
00463                 handle internal errors
00464 
00465         **********************************************************************/
00466 
00467         private void error (ServletResponse response, Object x)
00468         {
00469                 response.sendError (HttpResponses.InternalServerError, x.toString);
00470                 getDefaultContext().log ("Internal error:", x);
00471         }
00472 }
00473 
00474 
00475 /******************************************************************************
00476 
00477         Models a servlet that has been registered.
00478 
00479 ******************************************************************************/
00480 
00481 interface IRegisteredServlet
00482 {    
00483         /**********************************************************************
00484 
00485                 Return the servlet name
00486 
00487         **********************************************************************/
00488 
00489         char[] getName ();
00490 
00491         /**********************************************************************
00492 
00493                 Return the servlet instance
00494 
00495         **********************************************************************/
00496 
00497         Servlet getServlet ();
00498         
00499         /**********************************************************************
00500 
00501                 Return the servlet context
00502 
00503         **********************************************************************/
00504 
00505         ServletContext getContext ();           
00506 }
00507    
00508 
00509 /******************************************************************************
00510 
00511         ServletProxy is a wrapper that combines the servlet along with 
00512         its context and name. This is what's created when a servlet is 
00513         registered.
00514 
00515 ******************************************************************************/
00516 
00517 private class ServletProxy : IRegisteredServlet
00518 {       
00519         private char[]          name;
00520         private Servlet         servlet;
00521         private ServletContext  context;
00522         
00523         /**********************************************************************
00524 
00525                 Construct the wrapper with all necessary attributes
00526 
00527         **********************************************************************/
00528 
00529         this (Servlet servlet, char[] name, ServletContext context)
00530         {
00531                 this.name = name;
00532                 this.servlet = servlet;
00533                 this.context = context;
00534         }
00535 
00536         /**********************************************************************
00537 
00538                 Return the servlet name
00539 
00540         **********************************************************************/
00541 
00542         char[] getName ()
00543         {
00544                 return name;
00545         }
00546 
00547         /**********************************************************************
00548 
00549                 Return the servlet instance
00550 
00551         **********************************************************************/
00552 
00553         Servlet getServlet ()
00554         {
00555                 return servlet;
00556         }
00557         
00558         /**********************************************************************
00559 
00560                 Return the servlet context
00561 
00562         **********************************************************************/
00563 
00564         ServletContext getContext ()
00565         {
00566                 return context;
00567         }          
00568 }
00569 
00570         
00571 /******************************************************************************
00572 
00573         PathMapping instances are held in the mapping cache, and relate
00574         a servlet to a given uri request. Method constructPathMapping()
00575         produces these.
00576 
00577 ******************************************************************************/
00578 
00579 private class PathMapping : Payload
00580 {       
00581         private char[]          path;
00582         private ServletMapping  mapping;
00583 /*
00584         private short           end,
00585                                 start;
00586 */        
00587         /**********************************************************************
00588 
00589                 Construct the mapping with all necessary attributes
00590 
00591         **********************************************************************/
00592 
00593         this (ServletMapping mapping, char[] path)
00594         {
00595                 this.path = path;
00596                 this.mapping = mapping;
00597 
00598                 //start = mapping.regex.pmatch[0].rm_so;
00599                 //end   = mapping.regex.pmatch[0].rm_eo;
00600         }
00601 }
00602 
00603         
00604 /******************************************************************************
00605 
00606         Relate a servlet to a regular expression. These are constructed 
00607         via the addMapping() methods, and there may be more than one for
00608         any particular servlet. 
00609 
00610 ******************************************************************************/
00611 
00612 private class ServletMapping
00613 {       
00614         private RegExp                  regex;
00615         private IRegisteredServlet      proxy;
00616 
00617         /**********************************************************************
00618 
00619                 Construct the mapping with all necessary attributes
00620 
00621         **********************************************************************/
00622 
00623         this (IRegisteredServlet proxy, RegExp regex)
00624         {
00625                 this.regex = regex; 
00626                 this.proxy = proxy;       
00627         }
00628 }
00629 

Generated on Tue Jan 25 21:18:23 2005 for Mango by doxygen 1.3.6