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