Savarese Software Research Corporation
renderer/service.cc
Go to the documentation of this file.
00001 /* Copyright 2006-2011 Savarese Software Research Corporation
00002  *
00003  * Licensed under the Apache License, Version 2.0 (the "License");
00004  * you may not use this file except in compliance with the License.
00005  * You may obtain a copy of the License at
00006  *
00007  *     https://www.savarese.com/software/ApacheLicense-2.0
00008  *
00009  * Unless required by applicable law or agreed to in writing, software
00010  * distributed under the License is distributed on an "AS IS" BASIS,
00011  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00012  * See the License for the specific language governing permissions and
00013  * limitations under the License.
00014  */
00015 
00016 #include <ssrc/wispers/renderer/service.h>
00017 #include <ssrc/wispers/renderer/util.h>
00018 #include <ssrc/wispers/utility/Properties.h>
00019 
00020 #include <boost/filesystem/operations.hpp>
00021 
00022 __BEGIN_NS_SSRC_WSPR_RENDERER
00023 
00024 using namespace NS_SSRC_WSPR_LUA;
00025 
00026 Renderer::Renderer(caller_type & caller,
00027                    const RendererInitializer & initializer)
00028   SSRC_DECL_THROW(LoadError, std::invalid_argument) :
00029   super_fcgi(), super_service(caller),
00030   _global_env_file(initializer.global_env),
00031   _template_prefix(),
00032   _template_dir(),
00033   _get_path_pattern(),
00034   _lua_state(luaL_newstate()),
00035   _lua_render_ref(LUA_NOREF),
00036   _lua_load_environment_ref(LUA_NOREF),
00037   _lua_uncache_template_ref(LUA_NOREF),
00038   _lua_uncache_environment_ref(LUA_NOREF),
00039   _lua_clear_rendering_cache_ref(LUA_NOREF),
00040   _lua_init_global_environment_ref(LUA_NOREF)
00041 {
00042   add_service_type(protocol_traits::service_type());
00043 
00044   luaL_openlibs(_lua_state);
00045 
00046   init_path_pattern(_get_path_pattern,
00047                     initializer.content_dirs,
00048                     initializer.get_leaf_pattern);
00049 
00050   if(!_global_env_file.empty() &&
00051      boost::filesystem::is_regular_file(_global_env_file))
00052   {
00053     path global_env_path = _global_env_file;
00054 
00055     if(initializer.template_dir.empty()) {
00056       _template_dir = global_env_path.parent_path();
00057     } else {
00058       _template_dir = initializer.template_dir;
00059 
00060       if(!boost::filesystem::is_directory(_template_dir)) {
00061         throw std::invalid_argument(initializer.template_dir);
00062       }
00063     }
00064 
00065     prepend_package_path(_lua_state,
00066                          initializer.module_dirs.begin(),
00067                          initializer.module_dirs.end());
00068     load_config(_lua_state);
00069 
00070     _template_prefix = _template_dir.string();
00071     _template_prefix.append("/");
00072   } else {
00073     throw std::invalid_argument(_global_env_file);
00074   }
00075 
00076   WISP_SERVICE_REQUEST(MessageReloadConfig);
00077 }
00078 
00079 Renderer::~Renderer() {
00080   unref_globals(_lua_state);
00081   lua_close(_lua_state);
00082 }
00083 
00087 void Renderer::transition(State state) {
00088   switch(state) {
00089   case Starting:
00090     state = Started;
00091     break;
00092   case Stopping:
00093     state = Stopped;
00094     break;
00095   default:
00096     break;
00097   }
00098 
00099   super_service::transition(state);
00100 }
00101 
00102 utility::properties_ptr Renderer::get_status() {
00103   utility::properties_ptr && status = ServiceProtocolProcessor::get_status();
00104   utility::Properties *lua_status = status->create_node("lua");
00105 
00106   lua_status->set(lua_gc(_lua_state, LUA_GCCOUNT, 0), "gccount");
00107   lua_status->set(lua_gc(_lua_state, LUA_GCCOUNTB, 0), "gccountb");
00108 
00109   return status;
00110 }
00111 
00112 void Renderer::clear_template_cache() {
00113   lua_rawgeti(_lua_state, LUA_REGISTRYINDEX, _lua_clear_rendering_cache_ref);
00114   try {
00115     NS_SSRC_WSPR_LUA::pcall(_lua_state);
00116     // Run garbage collector to free memory immediately.
00117     lua_gc(_lua_state, LUA_GCCOLLECT, 0);
00118   } catch(const LuaCallError & lce) {
00119     // TODO: log
00120 #ifdef WSPR_DEBUG
00121     std::cerr << "clear_template_cache: caught unexpected LuaCallError: "
00122               << lce.what();
00123 #endif
00124   }
00125 }
00126 
00127 #define LTP_RENDER_FUNCTION_NAME "render"
00128 #define LTP_LOAD_ENVIRONMENT_FUNCTION_NAME "load_environment"
00129 #define LTP_UNCACHE_TEMPLATE_FUNCTION_NAME "uncache_template"
00130 #define LTP_UNCACHE_ENVIRONMENT_FUNCTION_NAME "uncache_environment"
00131 #define LTP_CLEAR_RENDERING_CACHE_FUNCTION_NAME "clear_rendering_cache"
00132 #define LTP_INIT_GLOBAL_ENVIRONMENT_FUNCTION_NAME "init_global_environment"
00133 void Renderer::ref_globals(lua_State *state) {
00134   lua_getglobal(state, LTP_RENDER_FUNCTION_NAME);
00135   _lua_render_ref = luaL_ref(state, LUA_REGISTRYINDEX);
00136   lua_getglobal(state, LTP_LOAD_ENVIRONMENT_FUNCTION_NAME);
00137   _lua_load_environment_ref = luaL_ref(state, LUA_REGISTRYINDEX);
00138   lua_getglobal(state, LTP_UNCACHE_TEMPLATE_FUNCTION_NAME);
00139   _lua_uncache_template_ref = luaL_ref(state, LUA_REGISTRYINDEX);
00140   lua_getglobal(state, LTP_UNCACHE_ENVIRONMENT_FUNCTION_NAME);
00141   _lua_uncache_environment_ref = luaL_ref(state, LUA_REGISTRYINDEX);
00142   lua_getglobal(state, LTP_CLEAR_RENDERING_CACHE_FUNCTION_NAME);
00143   _lua_clear_rendering_cache_ref = luaL_ref(state, LUA_REGISTRYINDEX);
00144   lua_getglobal(state, LTP_INIT_GLOBAL_ENVIRONMENT_FUNCTION_NAME);
00145   _lua_init_global_environment_ref = luaL_ref(state, LUA_REGISTRYINDEX);
00146 }
00147 #undef LTP_RENDER_FUNCTION_NAME
00148 #undef LTP_UNCACHE_TEMPLATE_FUNCTION_NAME
00149 #undef LTP_UNCACHE_ENVIRONMENT_FUNCTION_NAME
00150 #undef LTP_CLEAR_RENDERING_CACHE_FUNCTION_NAME
00151 #undef LTP_INIT_GLOBAL_ENVIRONMENT_FUNCTION_NAME
00152 
00153 void Renderer::unref_globals(lua_State *state) {
00154   luaL_unref(state, LUA_REGISTRYINDEX, _lua_render_ref);
00155   luaL_unref(state, LUA_REGISTRYINDEX, _lua_load_environment_ref);
00156   luaL_unref(state, LUA_REGISTRYINDEX, _lua_uncache_template_ref);
00157   luaL_unref(state, LUA_REGISTRYINDEX, _lua_uncache_environment_ref);
00158   luaL_unref(state, LUA_REGISTRYINDEX, _lua_clear_rendering_cache_ref);
00159   luaL_unref(state, LUA_REGISTRYINDEX, _lua_init_global_environment_ref);
00160   _lua_render_ref =  _lua_load_environment_ref =
00161     _lua_uncache_template_ref = _lua_uncache_environment_ref =
00162     _lua_clear_rendering_cache_ref = _lua_init_global_environment_ref =
00163     LUA_NOREF;
00164 }
00165 
00166 void Renderer::load_config(lua_State *state) SSRC_DECL_THROW(LoadError) {
00167   create_value(state, _template_dir.string(), "global", "template", "dir");
00168 
00169   // We give the global environment file the option to override
00170   // wspr.template.dir as well as to add it to package.path.
00171   if(luaL_dofile(state, _global_env_file.c_str()))
00172     throw LoadError(lua_tostring(state, -1));
00173 
00174   path error_template =
00175     get_value<string>(state, "wspr", "template", "error");
00176 
00177   if(error_template.empty())
00178     set_value(state, "/error.html", "wspr", "template", "error");
00179 
00180   error_template = get_value<string>(state, "global", "template", "dir");
00181 
00182   if(!error_template.empty() && _template_dir != error_template)
00183     _template_dir = error_template;
00184 
00185   set_value(state, _template_dir.string(), "global", "template", "dir");
00186   ref_globals(state);
00187 }
00188 
00189 void Renderer::reload_config() {
00190   lua_State *state = luaL_newstate();
00191 
00192 #ifdef WSPR_DEBUG
00193   std::cerr << "reload_config\n";
00194 #endif
00195 
00196   luaL_openlibs(state);
00197   set_value(state, get_value<string>(_lua_state, "package", "path"),
00198             "package", "path");
00199 
00200   try {
00201     unref_globals(_lua_state);
00202     load_config(state);
00203     // Only switch to new state if load_config doesn't throw.
00204     lua_close(_lua_state);
00205     set_lua_state(state);
00206   } catch(const LoadError & le) {
00207     // TODO: log error
00208 #ifdef WSPR_DEBUG
00209     std::cerr << "reload_config: LoadError: " << le.what() << std::endl;
00210 #endif
00211     lua_close(state);
00212     ref_globals(_lua_state);
00213   }
00214 }
00215 
00216 void Renderer::process_fcgi_request(const fcgx_request_ptr & request) {
00217   using namespace NS_SSRC_WSPR_FCGI;
00218 
00219   request_ptr http_request(new request_type(request));
00220   response_ptr http_response(new response_type(http_request));
00221 
00222   const HTTPRequestMethod method = http_request->http_request_method();
00223 
00224   if(method <= MethodMax) {
00225     (this->*(RequestMethodHandler[method]))(http_request, http_response);
00226   } else {
00227     http_response->send_error(StatusMethodNotImplemented);
00228   }
00229 
00230   if(!http_response->completed() && !http_response->suspended()) {
00231     // TODO: Log this.  All responses should either complete or suspend
00232     // before getting here, else it's a bug.
00233     http_response->send_error(StatusInternalServerError);
00234   }
00235 }
00236 
00237 // Changed approach to throw exception on failing to find template.
00238 path Renderer::load_template_args(const path & template_path,
00239                                   const char * template_key,
00240                                   const bool cache_environment,
00241                                   const bool cache_template)
00242   SSRC_DECL_THROW(LoadError, LuaCallError)
00243 {
00244   // We don't perform any security checks on the paths because
00245   // they should be performed before calling this function.  The
00246   // document_path regex match is sufficient validation.
00247   path ltp_file(template_path.string() + ".lua");
00248 
00249   if(boost::filesystem::is_regular_file(ltp_file)) {
00250     try {
00251       load_environment(ltp_file.string(), cache_environment);
00252     } catch(const LuaCallError & lce) {
00253       uncache_environment(ltp_file.string());
00254       throw;
00255     }
00256 
00257     // TODO: Be more robust and pop any extra return values.  This
00258     // code assumes no return values when top of stack is not a table.
00259     if(!lua_istable(_lua_state, -1))
00260       throw LoadError(string("load_template_args expected table, got: ").append(lua_typename(_lua_state, lua_type(_lua_state, -1))));
00261 
00262     // Now the table returned by the environment is on the top of the stack.
00263     const string && file =
00264       get_field<string>(_lua_state, -1, "wspr", "template", template_key);
00265 
00266     if(file.empty()) {
00267       ltp_file = template_path;
00268       ltp_file.replace_extension(".ltp");
00269     } else {
00270       ltp_file = file;
00271 
00272       if(!ltp_file.has_root_path()) {
00273         ltp_file = template_path.parent_path() / ltp_file.relative_path();
00274       } else {
00275         ltp_file = _template_dir / ltp_file.relative_path();
00276       }
00277 
00278       ltp_file.normalize();
00279     }
00280 
00281     if((ltp_file.string().find(_template_prefix) != 0) ||
00282        !boost::filesystem::is_regular_file(ltp_file))
00283     {
00284       lua_pop(_lua_state, 1);
00285       throw LoadError(string("invalid template: ").append(ltp_file.string()));
00286     }
00287   } else {
00288     ltp_file = template_path;
00289     ltp_file.replace_extension(".ltp");
00290 
00291     if(!boost::filesystem::is_regular_file(ltp_file)) {
00292       throw LoadError(string("missing template: ").append(ltp_file.string()));
00293     }
00294 
00295     // Push empty rendering environment.
00296     lua_newtable(_lua_state);
00297   }
00298 
00299   // When provided, the rendering environment specifies whether or not the
00300   // template should be cached.  Otherwise, the host program specifies.
00301   if(cache_template) {
00302     set_field(_lua_state, -1, cache_template, "cache_template");
00303   }
00304 
00305   return ltp_file;
00306 }
00307 
00308 path Renderer::find_error_template(const path & error_template,
00309                                    const path & parent_path)
00310 {
00311   path et(error_template);
00312 
00313   if(et.empty()) {
00314     et = _template_dir / "error.html";
00315   } else {
00316     if(!et.has_root_path()) {
00317       et = parent_path / et.relative_path();
00318     } else {
00319       // Force root paths to be interpreted relative to template directory.
00320       et = _template_dir / error_template.relative_path();
00321     }
00322     // Limit all templates to fall under the template directory.
00323     // We don't want to allow arbitrary access to the file system.
00324     if(et.string().find(_template_prefix) != 0 ||
00325        !boost::filesystem::is_regular_file(et))
00326     {
00327       et = _template_dir / "error.html";
00328     }
00329   }
00330 
00331   return et;
00332 }
00333 
00334 
00335 void Renderer::send_error(const HTTPStatusCode status,
00336                           const request_ptr & request,
00337                           response_ptr & response,
00338                           const path & error_template)
00339 {
00340   using namespace NS_SSRC_WSPR_FCGI;
00341 
00342   response->set_status(status);
00343   lua_rawgeti(_lua_state, LUA_REGISTRYINDEX, _lua_render_ref);
00344 
00345   try {
00346     path && et = load_template_args(error_template, "error", true);
00347 
00348     try {
00349       pcall_nopop(4, 2, _lua_state, et.string(), &request, &response);
00350     } catch(const LuaCallError & lce) {
00351 #ifdef WSPR_DEBUG
00352       std::cerr << "send_error: LuaCallError: " << lce.what() << std::endl;
00353 #endif
00354       uncache_template(et.string());
00355       response->send_error(StatusInternalServerError);
00356       return;
00357     }
00358   } catch(const LoadError & le) {
00359 #ifdef WSPR_DEBUG
00360       std::cerr << "send_error: LoadError: " << le.what() << std::endl;
00361 #endif
00362     lua_pop(_lua_state, 1);
00363     response->send_error(StatusInternalServerError);
00364     return;
00365   } catch(const LuaCallError & lce) {
00366 #ifdef WSPR_DEBUG
00367       std::cerr << "send_error: LuaCallError: " << lce.what() << std::endl;
00368 #endif
00369     response->send_error(StatusInternalServerError);
00370     return;
00371   }
00372 
00373   std::size_t content_length = 0;
00374   const char *content = lua_tolstring(_lua_state, -2, &content_length);
00375 
00376   if(content) {
00377     response->set_content_type("text/html");
00378     response->complete(content, content_length, CacheDisable);
00379   } else {
00380     response->send_error(static_cast<HTTPStatusCode>(lua_tointeger(_lua_state, -2)));
00381   }
00382 
00383   lua_pop(_lua_state, 2);
00384 }
00385 
00386 void Renderer::render_and_cache(const request_ptr & request,
00387                                 response_ptr & response,
00388                                 const bool flush)
00389 {
00390   using namespace NS_SSRC_WSPR_FCGI;
00391 
00392   HTTPStatusCode status = StatusNotFound;
00393 
00394   path && request_uri   = request->request_uri();
00395   path && document_path = request->document_root() / request_uri;
00396   path && template_path = _template_dir / request_uri;
00397   const string & str = document_path.string();
00398 
00399   if(!str.empty() && str[str.size() - 1] == '/') {
00400     document_path /= "index.html";
00401     template_path /= "index.html";
00402   }
00403   // WARNING: normalize() is deprecated
00404   document_path.normalize();
00405   template_path.normalize();
00406 
00407   do {
00408     lua_rawgeti(_lua_state, LUA_REGISTRYINDEX, _lua_init_global_environment_ref);
00409     try {
00410       pcall(_lua_state);
00411     } catch(const LuaCallError & lce) {
00412 #ifdef WSPR_DEBUG
00413       std::cerr << "LuaCallError: " << lce.what() << std::endl;
00414 #endif
00415       set_value(_lua_state, lce.what(), "wspr", "message", "debug");
00416       status = StatusInternalServerError;
00417       break;
00418     }
00419 
00420     if(!boost::regex_match(document_path.string(), _get_path_pattern))
00421       break;
00422 
00423     try {
00424       lua_rawgeti(_lua_state, LUA_REGISTRYINDEX, _lua_render_ref);
00425 
00426       const path && ltp_file = load_template_args(template_path);
00427 
00428       try {
00429         pcall_nopop(4, 2, _lua_state, ltp_file.string(), &request, &response);
00430       } catch(const LuaCallError & lce) {
00431 #ifdef WSPR_DEBUG
00432         std::cerr << "LuaCallError: " << lce.what() << std::endl;
00433 #endif
00434         set_value(_lua_state, lce.what(), "wspr", "message", "debug");
00435         status = StatusInternalServerError;
00436         uncache_template(ltp_file.string());
00437         break;
00438       }
00439 
00440     } catch(const LoadError & le) {
00441 #ifdef WSPR_DEBUG
00442       std::cerr << "LoadError: " << le.what() << std::endl;
00443 #endif
00444       lua_pop(_lua_state, 1);
00445       set_value(_lua_state, le.what(), "wspr", "message", "debug");
00446       break;
00447     } catch(const LuaCallError & lce) {
00448 #ifdef WSPR_DEBUG
00449       std::cerr << "LuaCallError: " << lce.what() << std::endl;
00450 #endif
00451       set_value(_lua_state, lce.what(), "wspr", "message", "debug");
00452       break;
00453     }
00454 
00455     if(lua_isstring(_lua_state, -2)) {
00456       status = StatusOK;
00457       response->set_status(StatusOK);
00458 
00459       std::size_t content_length = 0;
00460       const char *content = lua_tolstring(_lua_state, -2, &content_length);
00461 
00462       if(content) {
00463         bool cache_content =
00464           get_field<bool>(true, _lua_state, -1, "wspr", "template", "cache_content");
00465 
00466         if(cache_content) {
00467           cache_to_file(content, content_length, document_path);
00468         }
00469 
00470         if(!flush) {
00471           content_length = 0;
00472         }
00473 
00474         response->set_content_type(get_value<string>("text/html", _lua_state,
00475                                                      "global", "content_type",
00476                                                      template_path.extension().c_str()));
00477 
00478         response->complete(content, content_length,
00479                            get_field<bool>(_lua_state, -1, "wspr", "client_cache_disabled"));
00480       }
00481     } else {
00482       send_error(static_cast<HTTPStatusCode>(lua_tointeger(_lua_state, -2)),
00483                  request, response,
00484                  find_error_template(template_path.parent_path(), -1));
00485       status = StatusOK;
00486     }
00487 
00488     lua_pop(_lua_state, 2);
00489   } while(0);
00490 
00491   if(status != StatusOK) {
00492     send_error(status, request, response,
00493                find_error_template(template_path.parent_path()));
00494   }
00495 
00496   lua_rawgeti(_lua_state, LUA_REGISTRYINDEX, _lua_init_global_environment_ref);
00497   try {
00498     pcall(_lua_state);
00499   } catch(const LuaCallError & lce) {
00500     // TODO: log
00501   }
00502 }
00503 __END_NS_SSRC_WSPR_RENDERER

Savarese Software Research Corporation
Copyright © 2006-2011 Savarese Software Research Corporation. All rights reserved.