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
Copyright © 2006-2011 Savarese Software Research Corporation. All rights reserved.