relay/service.cc
Go to the documentation of this file.
00001 /* 00002 * Copyright 2006-2009 Savarese Software Research Corporation 00003 * 00004 * Licensed under the Apache License, Version 2.0 (the "License"); 00005 * you may not use this file except in compliance with the License. 00006 * You may obtain a copy of the License at 00007 * 00008 * https://www.savarese.com/software/ApacheLicense-2.0 00009 * 00010 * Unless required by applicable law or agreed to in writing, software 00011 * distributed under the License is distributed on an "AS IS" BASIS, 00012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 00013 * See the License for the specific language governing permissions and 00014 * limitations under the License. 00015 */ 00016 00017 #include <ssrc/wispers/relay/service.h> 00018 #include <ssrc/wispers/renderer/util.h> 00019 #include <ssrc/wispers/utility/PropertiesToString.h> 00020 00021 #include <boost/filesystem/operations.hpp> 00022 00023 #ifdef WSPR_DEBUG 00024 # include <ssrc/wispers/utility/print_memusage.h> 00025 #endif 00026 00027 __BEGIN_NS_SSRC_WSPR_RELAY 00028 00029 using namespace NS_SSRC_WSPR_LUA; 00030 using NS_SSRC_WSPR_RENDERER::init_path_pattern; 00031 using NS_SSRC_WSPR_SESSION::SessionData; 00032 using NS_SSRC_WSPR_SESSION::session_ptr; 00033 using NS_SSRC_WSPR_UTILITY::PropertiesToString; 00034 00035 Relay::Relay(caller_type & caller, const RelayInitializer & initializer) 00036 SSRC_DECL_THROW(LoadError, std::invalid_argument) : 00037 super(caller, initializer), 00038 _service_conf_dir(initializer.service_config_dir), 00039 _to_table(super::_lua_state) 00040 { 00041 add_service_type(protocol_traits::service_type()); 00042 // WARNING: normalize() is deprecated 00043 _service_conf_dir.normalize(); 00044 00045 if(!boost::filesystem::is_directory(_service_conf_dir)) 00046 throw std::invalid_argument(initializer.service_config_dir); 00047 00048 init_path_pattern(_post_path_pattern, 00049 initializer.content_dirs, 00050 initializer.post_leaf_pattern); 00051 00052 WISP_SERVICE_REQUEST(MessageClearCaches); 00053 } 00054 00055 inline void Relay::clear_caches() { 00056 _action_config_cache.clear(); 00057 clear_template_cache(); 00058 } 00059 00060 void Relay::process_request(const MessageClearCaches & msg, 00061 const MessageInfo &) 00062 { 00063 #ifdef WSPR_DEBUG 00064 std::cerr << name() << ": Clearing caches." << std::endl; 00065 #endif 00066 clear_caches(); 00067 } 00068 00069 void Relay::process_request(const MessageReloadConfig & msg, 00070 const MessageInfo & msginfo) 00071 { 00072 clear_caches(); 00073 super::process_request(msg, msginfo); 00074 } 00075 00076 namespace { 00077 00078 inline Relay::action_config_ptr 00079 action_config(lua_State *state, const string & action_name) { 00080 Relay::action_config_ptr config(new ActionConfig); 00081 00082 config->action = action_name; 00083 config->one_way = get_field<bool>(false, state, -1, "one_way"); 00084 config->requires_session = 00085 get_field<bool>(false, state, -1, "requires_session"); 00086 config->verify_session = 00087 get_field<bool>(config->requires_session, state, -1, "verify_session"); 00088 config->updates_access_time = 00089 get_field<bool>(true, state, -1, "updates_access_time"); 00090 config->clears_session = 00091 get_field<bool>(false, state, -1, "clears_session"); 00092 // TODO: Need a more flexible session cookie creation configuration system. 00093 // Session creator should return cookie configuration info in 00094 // MessageResponse. 00095 config->creates_session = 00096 get_field<unsigned int>(0, state, -1, "creates_session"); 00097 config->creates_secure_session = 00098 get_field<bool>(false, state, -1, "creates_secure_session"); 00099 config->permits_get = get_field<bool>(false, state, -1, "permits_get"); 00100 config->result_is_event = 00101 get_field<bool>(false, state, -1, "result_is_event"); 00102 config->target = get_field<string>(state, -1, "target"); 00103 config->call = get_field<string>(action_name, state, -1, "call"); 00104 config->redirect = get_field<string>(state, -1, "redirect"); 00105 config->failure_redirect = 00106 get_field<string>(state, -1, "redirect_on_failure"); 00107 config->result_template = get_field<string>(state, -1, "template"); 00108 config->cache_result_template = 00109 get_field<bool>(false, state, -1, "cache_template"); 00110 config->failure_template = 00111 get_field<string>(state, -1, "template_on_failure"); 00112 00113 if(config->result_template.empty()) { 00114 config->result_template = config->call; 00115 config->result_template.append(".result"); 00116 } 00117 00118 return config; 00119 } 00120 00121 } 00122 00123 00124 // TOOO: Decide whether or not it's better to prefetch all the 00125 // actions from the file and cache them instead of just the 00126 // single action. For a long-running system, the extra file accesses 00127 // get amortized over many requests. 00128 inline const Relay::action_config_ptr 00129 Relay::get_action(const string & service_name, const string & action_name) { 00130 const string && key = service_name + "#" + action_name; 00131 action_config_map::const_iterator it = _action_config_cache.find(key); 00132 00133 if(it != _action_config_cache.end()) { 00134 return it->second; 00135 } 00136 00137 path && action_path = _service_conf_dir / service_name; 00138 action_path.replace_extension(".conf"); 00139 00140 if(boost::filesystem::is_regular_file(action_path)) { 00141 // For now, action file returns a table whose keys are action names 00142 if(luaL_dofile(_lua_state, action_path.string().c_str())) { 00143 #ifdef WSPR_DEBUG 00144 std::cerr << "ActionConfig load error: " << key << " : " 00145 << lua_tostring(_lua_state, -1) << std::endl; 00146 #endif 00147 lua_pop(_lua_state, 1); 00148 return action_config_ptr(); 00149 } 00150 00151 lua_getfield(_lua_state, -1, action_name.c_str()); 00152 00153 if(!lua_istable(_lua_state, -1)) { 00154 lua_pop(_lua_state, 2); 00155 return action_config_ptr(); 00156 } 00157 00158 const action_config_ptr && action = action_config(_lua_state, action_name); 00159 00160 lua_pop(_lua_state, 2); 00161 00162 if(action->invalid()) 00163 return action_config_ptr(); 00164 00165 std::pair<action_config_map::iterator,bool> result = 00166 _action_config_cache.insert(action_config_map::value_type(key, action)); 00167 00168 if(result.second) { 00169 return action; 00170 } 00171 } 00172 00173 return action_config_ptr(); 00174 } 00175 00180 // Find leaf or request URL and see if it matches something 00181 // acceptable. Render error on anything unexpected (factor out error 00182 // rendering from Renderer into a separate method). Read service 00183 // config file, defining the action mappings. Next examine query string 00184 // and convert to a WebServiceRequest. Send request 00185 // using future. Complete request with continuation. 00186 void Relay::http_post(const request_ptr & request, response_ptr & response, 00187 parameter_map && parameters) 00188 { 00189 using namespace NS_SSRC_WSPR_FCGI; 00190 00191 const HTTPRequestMethod request_method = request->http_request_method(); 00192 const boost::regex & path_pattern = (request_method == MethodPost ? 00193 _post_path_pattern : _get_path_pattern); 00194 HTTPStatusCode status = StatusBadRequest; 00195 00196 const path script_name = request->script_name(); 00197 path && document_path = request->document_root() / script_name; 00198 path && parent_path = _template_dir / script_name; 00199 00200 // WARNING: normalize() is deprecated 00201 document_path.normalize(); 00202 parent_path.normalize(); 00203 00204 #ifdef WSPR_DEBUG 00205 std::cerr << "DOCUMENT PATH: " << document_path.string() 00206 << "\nSCRIPT NAME: " << script_name.string() 00207 << "\nQUERY STRING: " << request->query_string() 00208 << "\nPATTERN: " << path_pattern << std::endl; 00209 #endif 00210 00211 do { 00212 if(!boost::regex_match(document_path.string(), path_pattern)) 00213 break; 00214 00215 parameter_map::const_iterator it = parameters.find("action"); 00216 00217 if(it == parameters.end()) 00218 break; 00219 00220 const action_config_ptr action_ptr = 00221 get_action(parent_path.filename().string(), it->second); 00222 00223 if(!action_ptr || (request_method == MethodGet && !action_ptr->permits_get)) 00224 break; 00225 00226 FinishPostContext context(request_method, request, response, 00227 parent_path, action_ptr, 00228 std::forward<parameter_map>(parameters)); 00229 const ActionConfig & action = *action_ptr; 00230 00231 if(action.requires_session) { 00232 const sid_type & sid = response->session_id(); 00233 00234 status = StatusForbidden; 00235 00236 if(sid.empty()) 00237 break; 00238 00239 // TODO: Should we always fetch the session whether or not the 00240 // action requires a session? At the very least, we should 00241 // probably touch it. So we need an UpdateAccessTime one way 00242 // call. 00243 _caller.future_callp<CallGetSession>( 00244 boost::bind(&Relay::continue_post, this, context, _1), 00245 SessionProtocol::service_group(), 00246 sid, 00247 action.updates_access_time); 00248 response->suspend(); 00249 } else if(action.creates_session) { 00250 // Login cross-site request forgery protection via HTTP header insertion. 00251 // We disallow non-JavaScript form submission to create sessions. 00252 const char *xsrf = 00253 context.request->fcgx_get_param("HTTP_WISPERS_XSRF_LOGIN"); 00254 00255 if(xsrf == 0 || *xsrf != '1') 00256 break; 00257 00258 if(!action.one_way && !action.clears_session) { 00259 _caller.future_callp<CallCreateSession>( 00260 boost::bind(&Relay::continue_post, this, context, _1), 00261 SessionProtocol::service_group(), 00262 response->session_id()); 00263 response->suspend(); 00264 } 00265 } 00266 00267 if(action.clears_session) 00268 response->clear_session_id(); 00269 00270 if(!response->suspended()) 00271 continue_post(context, MessageSingleQueryResult()); 00272 00273 status = StatusOK; 00274 } while(0); 00275 00276 if(status != StatusOK) { 00277 send_error(status, request, response, find_error_template(parent_path)); 00278 } 00279 } 00280 00281 void Relay::continue_post(FinishPostContext & context, 00282 const MessageSingleQueryResult & session_result) 00283 { 00284 using namespace NS_SSRC_WSPR_FCGI; 00285 00286 HTTPStatusCode status = StatusBadRequest; 00287 const ActionConfig & action = *context.action; 00288 response_type & response = *context.response; 00289 00290 do { 00291 response.resume(); 00292 00293 if(action.requires_session && !session_result.found) { 00294 response.clear_session_id(); 00295 status = StatusForbidden; 00296 break; 00297 } 00298 00299 // Cross-site request forgery protection via double-cookie submission 00300 // for XMLHttpRequest and via xsrf_token for vanilla form submission. 00301 // It is the developer's responsibility to never set 00302 // permits_get = true for actions that modify data or have side-effects. 00303 //if(sid != request->header_value("Wispers-Verify-Session")) 00304 if(action.verify_session && context.request_method != MethodGet && 00305 session_result.result.sid != 00306 context.request->env_value("HTTP_WISPERS_VERIFY_SESSION")) 00307 { 00308 parameter_map::const_iterator && it = 00309 context.parameters.find("wspr_xsrf_token"); 00310 if(it == context.parameters.end() || 00311 it->second != session_result.result.xsrf_token) 00312 break; 00313 } 00314 00315 session_ptr session(session_result.found ? 00316 new SessionData(session_result.result) : 0); 00317 00318 if(action.one_way) { 00319 _caller.sendp<CallOneWay>(action.target, 00320 action.call, 00321 context.parameters, 00322 session); 00323 if(!action.redirect.empty()) 00324 response.send_redirect(action.redirect); 00325 } else { 00326 _caller.future_callp<CallTwoWay>( 00327 boost::bind(&Relay::finish_post, this, context, _1), 00328 action.target, 00329 action.call, 00330 context.parameters, 00331 session); 00332 response.suspend(); 00333 } 00334 00335 status = StatusOK; 00336 } while(0); 00337 00338 if(status != StatusOK) { 00339 send_error(status, context.request, context.response, 00340 find_error_template(context.parent_path)); 00341 } else if(!response.suspended() && !response.completed()) 00342 response.complete(); 00343 } 00344 00345 00346 namespace { 00347 string to_json_event(const Properties *node) { 00348 std::ostringstream json; 00349 00350 json << "/*("; 00351 00352 if(node) { 00353 PropertiesToString to_json(json, PropertiesToString::ToJSON); 00354 to_json(*node); 00355 } 00356 00357 json << ")*/"; 00358 return json.str(); 00359 } 00360 } 00361 00362 void Relay::finish_post(FinishPostContext & context, 00363 const MessageResponse & result) 00364 { 00365 using namespace NS_SSRC_WSPR_FCGI; 00366 00367 HTTPStatusCode status = StatusInternalServerError; 00368 const ActionConfig & action = *context.action; 00369 response_type & response = *context.response; 00370 00371 do { 00372 response.resume(); 00373 00374 lua_rawgeti(_lua_state, LUA_REGISTRYINDEX, _lua_init_global_environment_ref); 00375 try { 00376 pcall(_lua_state); 00377 } catch(const LuaCallError & lce) { 00378 #ifdef WSPR_DEBUG 00379 std::cerr << "ERROR! " << lce.what() << std::endl; 00380 #endif 00381 set_value(_lua_state, lce.what(), "wspr", "message", "debug"); 00382 status = StatusInternalServerError; 00383 break; 00384 } 00385 00386 const string & status_msg = result.status_message(); 00387 if(!status_msg.empty()) { 00388 set_value(_lua_state, status_msg, "wspr", "message", "status"); 00389 } 00390 00391 if(!result.error()) { 00392 // Note: An action that is requires_session and clears_session should 00393 // still return the session data, so we don't do a 00394 // !action.clears_session check. 00395 00396 if(action.requires_session && !result.session) { 00397 response.clear_session_id(); 00398 status = StatusForbidden; 00399 break; 00400 } 00401 00402 if(action.creates_session && !action.clears_session) { 00403 if(!result.session) 00404 response.clear_session_id(); 00405 else if(response.session_id() != result.session->sid) 00406 response.set_session_id(result.session->sid, action.creates_session, 00407 action.creates_secure_session); 00408 } 00409 00410 // TODO: pull this out into a separate execution flow to avoid 00411 // calling init_global_environment. Refactor entire function to avoid 00412 // so many conditional statements. Also, optimize this to minimize 00413 // memory allocations. 00414 if(action.result_is_event) { 00415 status = StatusOK; 00416 string && content = 00417 to_json_event(result.template_data->find("wspr", "event")); 00418 response.complete(&content[0], content.size(), true); 00419 break; 00420 } 00421 00422 path template_path = action.result_template; 00423 string redirect = action.redirect; 00424 00425 if(result.failed()) { 00426 set_value(_lua_state, true, "wspr", "status", "failure"); 00427 00428 if(!action.failure_template.empty()) { 00429 template_path = action.failure_template; 00430 redirect.clear(); 00431 } 00432 00433 if(!action.failure_redirect.empty()) 00434 redirect = action.failure_redirect; 00435 } 00436 00437 if(!redirect.empty()) { 00438 response.send_redirect(redirect); 00439 status = StatusOK; 00440 break; 00441 } 00442 00443 // Render result. 00444 if(template_path.has_root_path()) 00445 template_path = _template_dir / template_path; 00446 else 00447 template_path = context.parent_path / template_path.relative_path(); 00448 00449 // WARNING: normalize() is deprecated 00450 template_path.normalize(); 00451 00452 // Limit all templates to fall under the template directory. 00453 // We don't want to allow arbitrary access to the file system. 00454 // TODO: Make this a reusable function because it's also done in 00455 // renderer. 00456 #ifdef WSPR_DEBUG 00457 std::cerr << "PREFIX: " << _template_prefix << std::endl 00458 << "TEMPLATE PATH: " << template_path.string() << std::endl; 00459 #endif 00460 00461 if(template_path.string().find(_template_prefix) != 0) 00462 break; 00463 00464 lua_getglobal(_lua_state, "wspr"); 00465 if(result.template_data) { 00466 properties_to_table(_to_table, *result.template_data); 00467 } else { 00468 lua_newtable(_lua_state); 00469 } 00470 lua_setfield(_lua_state, -2, "response"); 00471 if(result.session) { 00472 properties_to_table(_to_table, *(result.session->attributes)); 00473 push_value(_lua_state, result.session->xsrf_token); 00474 lua_setfield(_lua_state, -2, "xsrf_token"); 00475 lua_setfield(_lua_state, -2, "session"); 00476 } 00477 lua_pop(_lua_state, 1); 00478 00479 try { 00480 lua_rawgeti(_lua_state, LUA_REGISTRYINDEX, _lua_render_ref); 00481 00482 // TODO: Clean this up! 00483 const bool cache_template = 00484 (action.cache_result_template && !result.failed()); 00485 const path && ltp_file = 00486 load_template_args(template_path, cache_template, cache_template); 00487 00488 try { 00489 if(result.session) { 00490 pcall_nopop(5, 2, _lua_state, ltp_file.string(), 00491 &context.request, &context.response, &result.session); 00492 } else { 00493 pcall_nopop(4, 2, _lua_state, ltp_file.string(), 00494 &context.request, &context.response); 00495 } 00496 } catch(const LuaCallError & lce) { 00497 #ifdef WSPR_DEBUG 00498 std::cerr << "LuaCallError: " << lce.what() << std::endl; 00499 #endif 00500 set_value(_lua_state, lce.what(), "wspr", "message", "debug"); 00501 uncache_template(ltp_file.string()); 00502 break; 00503 } 00504 } catch(const LoadError & le) { 00505 #ifdef WSPR_DEBUG 00506 std::cerr << "LoadError: " << le.what() << std::endl; 00507 #endif 00508 lua_pop(_lua_state, 1); 00509 set_value(_lua_state, le.what(), "wspr", "message", "debug"); 00510 status = StatusNotFound; 00511 break; 00512 } catch(const LuaCallError & lce) { 00513 #ifdef WSPR_DEBUG 00514 std::cerr << "LuaCallError: " << lce.what() << std::endl; 00515 #endif 00516 set_value(_lua_state, lce.what(), "wspr", "message", "debug"); 00517 status = StatusInternalServerError; 00518 break; 00519 } 00520 00521 status = StatusOK; 00522 00523 if(lua_isstring(_lua_state, -2)) { 00524 if(!response.suspended() && !response.completed()) { 00525 response.set_status(StatusOK); 00526 00527 std::size_t content_length = 0; 00528 const char *content = lua_tolstring(_lua_state, -2, &content_length); 00529 00530 // TODO: What about content type??? Define in action.conf??? 00531 00532 response.complete(content, content_length, 00533 get_field<bool>(_lua_state, -1, "wspr", "client_cache_disabled")); 00534 } 00535 } else { 00536 send_error(static_cast<HTTPStatusCode>(lua_tointeger(_lua_state, -2)), 00537 context.request, context.response, 00538 find_error_template(context.parent_path, -1)); 00539 } 00540 00541 lua_pop(_lua_state, 2); 00542 } 00543 } while(0); 00544 00545 if(status != StatusOK) { 00546 send_error(status, context.request, context.response, 00547 find_error_template(context.parent_path)); 00548 } 00549 00550 lua_rawgeti(_lua_state, LUA_REGISTRYINDEX, _lua_init_global_environment_ref); 00551 // Ignore errors. 00552 lua_pcall(_lua_state, 0, 0, 0); 00553 00554 #ifdef WSPR_DEBUG 00555 std::cerr << "END FINISH POST\nLUA MEMUSE: " 00556 << (lua_gc(_lua_state, LUA_GCCOUNT, 0)*1024 + 00557 lua_gc(_lua_state, LUA_GCCOUNTB, 0)) 00558 << std::endl; 00559 00560 lua_gc(_lua_state, LUA_GCCOLLECT, 0); 00561 00562 std::cerr << "LUA MEMUSE: " << (lua_gc(_lua_state, LUA_GCCOUNT, 0)*1024 + 00563 lua_gc(_lua_state, LUA_GCCOUNTB, 0)) 00564 << std::endl; 00565 # if defined(WISPERS_HAVE_GET_MEMUSAGE) 00566 NS_SSRC_WSPR_UTILITY::print_memusage(); 00567 # endif 00568 00569 #endif 00570 } 00571 00572 __END_NS_SSRC_WSPR_RELAY
Copyright © 2006-2011 Savarese Software Research Corporation. All rights reserved.