RTBKit  0.9
Open-source framework to create real-time ad bidding systems.
soa/service/rest_request_router.cc
00001 /* rest_request_router.cc
00002    Jeremy Barnes, 15 November 2012
00003    Copyright (c) 2012 Datacratic Inc.  All rights reserved.
00004 
00005 */
00006 
00007 #include "rest_request_router.h"
00008 #include "jml/utils/vector_utils.h"
00009 #include "jml/arch/exception_handler.h"
00010 #include "jml/utils/set_utils.h"
00011 
00012 
00013 using namespace std;
00014 
00015 
00016 namespace Datacratic {
00017 
00018 
00019 /*****************************************************************************/
00020 /* PATH SPEC                                                                 */
00021 /*****************************************************************************/
00022 
00023 std::ostream & operator << (std::ostream & stream, const PathSpec & path)
00024 {
00025     return stream << path.path;
00026 }
00027 
00028 
00029 /*****************************************************************************/
00030 /* REQUEST FILTER                                                            */
00031 /*****************************************************************************/
00032 
00033 std::ostream & operator << (std::ostream & stream, const RequestFilter & filter)
00034 {
00035     return stream;
00036 }
00037 
00038 
00039 /*****************************************************************************/
00040 /* REQUEST REQUEST PARSING CONTEXT                                           */
00041 /*****************************************************************************/
00042 
00043 std::ostream & operator << (std::ostream & stream,
00044                             const RestRequestParsingContext & context)
00045 {
00046     return stream << context.resources << " " << context.remaining;
00047 }
00048 
00049 
00050 /*****************************************************************************/
00051 /* REST REQUEST ROUTER                                                       */
00052 /*****************************************************************************/
00053 
00054 RestRequestRouter::
00055 RestRequestRouter()
00056     : terminal(false)
00057 {
00058 }
00059 
00060 RestRequestRouter::
00061 RestRequestRouter(const OnProcessRequest & processRequest,
00062                   const std::string & description,
00063                   bool terminal,
00064                   const Json::Value & argHelp)
00065     : rootHandler(processRequest),
00066       description(description),
00067       terminal(terminal),
00068       argHelp(argHelp)
00069 {
00070 }
00071 
00072 RestRequestRouter::
00073 ~RestRequestRouter()
00074 {
00075 }
00076     
00077 RestServiceEndpoint::OnHandleRequest
00078 RestRequestRouter::
00079 requestHandler() const
00080 {
00081     return std::bind(&RestRequestRouter::handleRequest,
00082                      this,
00083                      std::placeholders::_1,
00084                      std::placeholders::_2);
00085 }
00086 
00087 void
00088 RestRequestRouter::
00089 handleRequest(const RestServiceEndpoint::ConnectionId & connection,
00090               const RestRequest & request) const
00091 {
00092     //JML_TRACE_EXCEPTIONS(false);
00093 
00094     RestRequestParsingContext context(request);
00095     MatchResult res = processRequest(connection, request, context);
00096     if (res == MR_NO) {
00097         connection.sendErrorResponse(404, "unknown resource " + request.resource);
00098     }
00099 }
00100 
00101 RestRequestRouter::
00102 MatchResult
00103 RestRequestRouter::
00104 processRequest(const RestServiceEndpoint::ConnectionId & connection,
00105                const RestRequest & request,
00106                RestRequestParsingContext & context) const
00107 {
00108     bool debug = false;
00109 
00110     if (debug) {
00111         cerr << "processing request " << request
00112              << " with context " << context
00113              << " against route " << description 
00114              << " with " << subRoutes.size() << " subroutes" << endl;
00115     }
00116 
00117     if (rootHandler && (!terminal || context.remaining.empty()))
00118         return rootHandler(connection, request, context);
00119         
00120     for (auto & sr: subRoutes) {
00121         if (debug)
00122             cerr << "  trying subroute " << sr.router->description << endl;
00123         try {
00124             MatchResult mr = sr.process(request, context, connection);
00125             //cerr << "returned " << mr << endl;
00126             if (mr == MR_YES || mr == MR_ERROR)
00127                 return mr;
00128         } catch (const std::exception & exc) {
00129             connection.sendErrorResponse(500, ML::format("threw exception: %s",
00130                                                          exc.what()));
00131         } catch (...) {
00132             connection.sendErrorResponse(500, "unknown exception");
00133         }
00134     }
00135 
00136     return MR_NO;
00137     //connection.sendErrorResponse(404, "invalid route for "
00138     //                             + request.resource);
00139 }
00140 
00141 RestRequestRouter::MatchResult
00142 RestRequestRouter::Route::
00143 process(const RestRequest & request,
00144         const RestRequestParsingContext & context,
00145         const RestServiceEndpoint::ConnectionId & connection) const
00146 {
00147     using namespace std;
00148 
00149     bool debug = false;
00150 
00151     if (debug) {
00152         cerr << "verb = " << request.verb << " filter.verbs = " << filter.verbs
00153              << endl;
00154     }
00155     if (!filter.verbs.empty()
00156         && !filter.verbs.count(request.verb))
00157         return MR_NO;
00158 
00159     RestRequestParsingContext matched = context;
00160     switch (path.type) {
00161     case PathSpec::STRING: {
00162         std::string::size_type pos = context.remaining.find(path.path);
00163         if (pos == 0) {
00164             using namespace std;
00165             //cerr << "matched string " << pos << endl;
00166             matched.resources.push_back(path.path);
00167             matched.remaining = string(matched.remaining, path.path.size());
00168             break;
00169         }
00170         else return MR_NO;
00171     }
00172     case PathSpec::REGEX: {
00173         boost::smatch results;
00174         bool found
00175             = boost::regex_search(context.remaining,
00176                                   results,
00177                                   path.rex)
00178             && !results.prefix().matched;  // matches from the start
00179         
00180         //cerr << "matching regex " << path.path << " against "
00181         //     << context.remaining << " with found " << found << endl;
00182         if (!found)
00183             return MR_NO;
00184         for (unsigned i = 0;  i < results.size();  ++i)
00185             matched.resources.push_back(results[i]);
00186         matched.remaining = std::string(matched.remaining,
00187                                         results[0].length());
00188         break;
00189     }
00190     case PathSpec::NONE:
00191     default:
00192         throw ML::Exception("unknown rest request type");
00193     }
00194 
00195     return router->processRequest(connection, request, matched);
00196 }
00197 
00198 void
00199 RestRequestRouter::
00200 addRoute(PathSpec path, RequestFilter filter,
00201          const std::shared_ptr<RestRequestRouter> & handler)
00202 {
00203     if (rootHandler)
00204         throw ML::Exception("can't add a sub-route to a terminal route");
00205 
00206     Route route;
00207     route.path = path;
00208     route.filter = filter;
00209     route.router = handler;
00210 
00211     subRoutes.emplace_back(std::move(route));
00212 }
00213 
00214 void
00215 RestRequestRouter::
00216 addRoute(PathSpec path, RequestFilter filter,
00217          const std::string & description,
00218          const OnProcessRequest & cb,
00219          const Json::Value & argHelp)
00220 {
00221     addRoute(path, filter,
00222              std::make_shared<RestRequestRouter>(cb, description, true, argHelp));
00223 }
00224 
00225 void
00226 RestRequestRouter::
00227 addHelpRoute(PathSpec path, RequestFilter filter)
00228 {
00229     OnProcessRequest helpRoute
00230         = [=] (const RestServiceEndpoint::ConnectionId & connection,
00231                const RestRequest & request,
00232                const RestRequestParsingContext & context)
00233         {
00234             Json::Value help;
00235             getHelp(help, "", set<string>());
00236             connection.sendResponse(200, help);
00237 
00238             return MR_YES;
00239         };
00240 
00241     addRoute(path, filter, "Get help on the available API commands",
00242              helpRoute, Json::Value());
00243 }
00244 
00245 void
00246 RestRequestRouter::
00247 getHelp(Json::Value & result, const std::string & currentPath,
00248         const std::set<std::string> & verbs)
00249 {
00250     auto getVerbsStr = [] (const std::set<std::string> & verbs)
00251         {
00252             string verbsStr;
00253             for (auto v: verbs) {
00254                 if (!verbsStr.empty())
00255                     verbsStr += ",";
00256                 else verbsStr += " ";
00257                 verbsStr += v;
00258             }
00259             
00260             return verbsStr;
00261         };
00262 
00263     Json::Value & v = result[currentPath + getVerbsStr(verbs)];
00264 
00265     v["description"] = description;
00266     if (!argHelp.isNull())
00267         v["arguments"] = argHelp;
00268     
00269     for (unsigned i = 0;  i < subRoutes.size();  ++i) {
00270         string path = currentPath + subRoutes[i].path.getPathDesc();
00271         Json::Value & sri = result[path + getVerbsStr(subRoutes[i].filter.verbs)];
00272         subRoutes[i].path.getHelp(sri);
00273         subRoutes[i].filter.getHelp(sri);
00274         subRoutes[i].router->getHelp(result, path, subRoutes[i].filter.verbs);
00275     }
00276 }
00277 
00278 RestRequestRouter &
00279 RestRequestRouter::
00280 addSubRouter(PathSpec path, const std::string & description)
00281 {
00282     // TODO: check it doesn't exist
00283     Route route;
00284     route.path = path;
00285     route.router.reset(new RestRequestRouter());
00286     route.router->description = description;
00287 
00288     subRoutes.push_back(route);
00289     return *route.router;
00290 }
00291 
00292 
00293 } // namespace Datacratic
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator