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