Savarese Software Research Corporation
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

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