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

Generated on Sun Nov 7 19:06:52 2004 for Mango by doxygen 1.3.6