PageRenderTime 25ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/soa/service/rest_request_router.cc

https://gitlab.com/max-crow/rtbkit-p
C++ | 423 lines | 324 code | 73 blank | 26 comment | 50 complexity | 4da36a3f244e22f07ad3081a0c6856df MD5 | raw file
  1. /* rest_request_router.cc
  2. Jeremy Barnes, 15 November 2012
  3. Copyright (c) 2012 Datacratic Inc. All rights reserved.
  4. */
  5. #include "rest_request_router.h"
  6. #include "jml/utils/vector_utils.h"
  7. #include "jml/arch/exception_handler.h"
  8. #include "jml/utils/set_utils.h"
  9. #include "jml/utils/file_functions.h"
  10. using namespace std;
  11. namespace Datacratic {
  12. /*****************************************************************************/
  13. /* PATH SPEC */
  14. /*****************************************************************************/
  15. std::ostream & operator << (std::ostream & stream, const PathSpec & path)
  16. {
  17. return stream << path.path;
  18. }
  19. /*****************************************************************************/
  20. /* REQUEST FILTER */
  21. /*****************************************************************************/
  22. std::ostream & operator << (std::ostream & stream, const RequestFilter & filter)
  23. {
  24. return stream;
  25. }
  26. /*****************************************************************************/
  27. /* REST REQUEST PARSING CONTEXT */
  28. /*****************************************************************************/
  29. std::ostream & operator << (std::ostream & stream,
  30. const RestRequestParsingContext & context)
  31. {
  32. return stream << context.resources << " " << context.remaining;
  33. }
  34. /*****************************************************************************/
  35. /* REST REQUEST ROUTER */
  36. /*****************************************************************************/
  37. RestRequestRouter::
  38. RestRequestRouter()
  39. : terminal(false)
  40. {
  41. }
  42. RestRequestRouter::
  43. RestRequestRouter(const OnProcessRequest & processRequest,
  44. const std::string & description,
  45. bool terminal,
  46. const Json::Value & argHelp)
  47. : rootHandler(processRequest),
  48. description(description),
  49. terminal(terminal),
  50. argHelp(argHelp)
  51. {
  52. }
  53. RestRequestRouter::
  54. ~RestRequestRouter()
  55. {
  56. }
  57. RestServiceEndpoint::OnHandleRequest
  58. RestRequestRouter::
  59. requestHandler() const
  60. {
  61. return std::bind(&RestRequestRouter::handleRequest,
  62. this,
  63. std::placeholders::_1,
  64. std::placeholders::_2);
  65. }
  66. void
  67. RestRequestRouter::
  68. handleRequest(const RestServiceEndpoint::ConnectionId & connection,
  69. const RestRequest & request) const
  70. {
  71. //JML_TRACE_EXCEPTIONS(false);
  72. RestRequestParsingContext context(request);
  73. MatchResult res = processRequest(connection, request, context);
  74. if (res == MR_NO) {
  75. connection.sendErrorResponse(404, "unknown resource " + request.verb + " " + request.resource);
  76. }
  77. }
  78. static std::string getVerbsStr(const std::set<std::string> & verbs)
  79. {
  80. string verbsStr;
  81. for (auto v: verbs) {
  82. if (!verbsStr.empty())
  83. verbsStr += ",";
  84. verbsStr += v;
  85. }
  86. return verbsStr;
  87. }
  88. RestRequestRouter::
  89. MatchResult
  90. RestRequestRouter::
  91. processRequest(const RestServiceEndpoint::ConnectionId & connection,
  92. const RestRequest & request,
  93. RestRequestParsingContext & context) const
  94. {
  95. bool debug = false;
  96. if (debug) {
  97. cerr << "processing request " << request
  98. << " with context " << context
  99. << " against route " << description
  100. << " with " << subRoutes.size() << " subroutes" << endl;
  101. }
  102. if (request.verb == "OPTIONS") {
  103. Json::Value help;
  104. std::set<std::string> verbs;
  105. this->options(verbs, help, request, context);
  106. RestParams headers = { { "Allow", getVerbsStr(verbs) } };
  107. if (verbs.empty())
  108. connection.sendHttpResponse(400, "", "", headers);
  109. else
  110. connection.sendHttpResponse(200, help.toStyledString(),
  111. "application/json",
  112. headers);
  113. return MR_YES;
  114. }
  115. if (rootHandler && (!terminal || context.remaining.empty()))
  116. return rootHandler(connection, request, context);
  117. for (auto & sr: subRoutes) {
  118. if (debug)
  119. cerr << " trying subroute " << sr.router->description << endl;
  120. try {
  121. MatchResult mr = sr.process(request, context, connection);
  122. //cerr << "returned " << mr << endl;
  123. if (mr == MR_YES || mr == MR_ASYNC || mr == MR_ERROR)
  124. return mr;
  125. } catch (const std::exception & exc) {
  126. connection.sendErrorResponse(500, ML::format("threw exception: %s",
  127. exc.what()));
  128. } catch (...) {
  129. connection.sendErrorResponse(500, "unknown exception");
  130. }
  131. }
  132. return MR_NO;
  133. //connection.sendErrorResponse(404, "invalid route for "
  134. // + request.resource);
  135. }
  136. void
  137. RestRequestRouter::
  138. options(std::set<std::string> & verbsAccepted,
  139. Json::Value & help,
  140. const RestRequest & request,
  141. RestRequestParsingContext & context) const
  142. {
  143. for (auto & sr: subRoutes) {
  144. sr.options(verbsAccepted, help, request, context);
  145. }
  146. }
  147. bool
  148. RestRequestRouter::Route::
  149. matchPath(const RestRequest & request,
  150. RestRequestParsingContext & context) const
  151. {
  152. switch (path.type) {
  153. case PathSpec::STRING: {
  154. std::string::size_type pos = context.remaining.find(path.path);
  155. if (pos == 0) {
  156. using namespace std;
  157. //cerr << "context string " << pos << endl;
  158. context.resources.push_back(path.path);
  159. context.remaining = string(context.remaining, path.path.size());
  160. break;
  161. }
  162. else return false;
  163. }
  164. case PathSpec::REGEX: {
  165. boost::smatch results;
  166. bool found
  167. = boost::regex_search(context.remaining,
  168. results,
  169. path.rex)
  170. && !results.prefix().matched; // matches from the start
  171. //cerr << "matching regex " << path.path << " against "
  172. // << context.remaining << " with found " << found << endl;
  173. if (!found)
  174. return false;
  175. for (unsigned i = 0; i < results.size(); ++i)
  176. context.resources.push_back(results[i]);
  177. context.remaining = std::string(context.remaining,
  178. results[0].length());
  179. break;
  180. }
  181. case PathSpec::NONE:
  182. default:
  183. throw ML::Exception("unknown rest request type");
  184. }
  185. return true;
  186. }
  187. RestRequestRouter::MatchResult
  188. RestRequestRouter::Route::
  189. process(const RestRequest & request,
  190. RestRequestParsingContext & context,
  191. const RestServiceEndpoint::ConnectionId & connection) const
  192. {
  193. using namespace std;
  194. bool debug = false;
  195. if (debug) {
  196. cerr << "verb = " << request.verb << " filter.verbs = " << filter.verbs
  197. << endl;
  198. }
  199. if (!filter.verbs.empty()
  200. && !filter.verbs.count(request.verb))
  201. return MR_NO;
  202. // At the end, make sure we put the context back to how it was
  203. RestRequestParsingContext::StateGuard guard(&context);
  204. if (!matchPath(request, context))
  205. return MR_NO;
  206. if (extractObject)
  207. extractObject(connection, request, context);
  208. if (connection.responseSent())
  209. return MR_YES;
  210. return router->processRequest(connection, request, context);
  211. }
  212. void
  213. RestRequestRouter::Route::
  214. options(std::set<std::string> & verbsAccepted,
  215. Json::Value & help,
  216. const RestRequest & request,
  217. RestRequestParsingContext & context) const
  218. {
  219. RestRequestParsingContext::StateGuard guard(&context);
  220. if (!matchPath(request, context))
  221. return;
  222. if (context.remaining.empty()) {
  223. verbsAccepted.insert(filter.verbs.begin(), filter.verbs.end());
  224. string path = "";//this->path.getPathDesc();
  225. Json::Value & sri = help[path + getVerbsStr(filter.verbs)];
  226. this->path.getHelp(sri);
  227. filter.getHelp(sri);
  228. router->getHelp(help, path, filter.verbs);
  229. }
  230. router->options(verbsAccepted, help, request, context);
  231. }
  232. void
  233. RestRequestRouter::
  234. addRoute(PathSpec path, RequestFilter filter,
  235. const std::shared_ptr<RestRequestRouter> & handler,
  236. ExtractObject extractObject)
  237. {
  238. if (rootHandler)
  239. throw ML::Exception("can't add a sub-route to a terminal route");
  240. Route route;
  241. route.path = path;
  242. route.filter = filter;
  243. route.router = handler;
  244. route.extractObject = extractObject;
  245. subRoutes.emplace_back(std::move(route));
  246. }
  247. void
  248. RestRequestRouter::
  249. addRoute(PathSpec path, RequestFilter filter,
  250. const std::string & description,
  251. const OnProcessRequest & cb,
  252. const Json::Value & argHelp,
  253. ExtractObject extractObject)
  254. {
  255. addRoute(path, filter,
  256. std::make_shared<RestRequestRouter>(cb, description, true, argHelp),
  257. extractObject);
  258. }
  259. void
  260. RestRequestRouter::
  261. addHelpRoute(PathSpec path, RequestFilter filter)
  262. {
  263. OnProcessRequest helpRoute
  264. = [=] (const RestServiceEndpoint::ConnectionId & connection,
  265. const RestRequest & request,
  266. const RestRequestParsingContext & context)
  267. {
  268. Json::Value help;
  269. getHelp(help, "", set<string>());
  270. connection.sendResponse(200, help);
  271. return MR_YES;
  272. };
  273. addRoute(path, filter, "Get help on the available API commands",
  274. helpRoute, Json::Value());
  275. }
  276. void
  277. RestRequestRouter::
  278. getHelp(Json::Value & result, const std::string & currentPath,
  279. const std::set<std::string> & verbs) const
  280. {
  281. Json::Value & v = result[(currentPath.empty() ? "" : currentPath + " ")
  282. + getVerbsStr(verbs)];
  283. v["description"] = description;
  284. if (!argHelp.isNull())
  285. v["arguments"] = argHelp;
  286. for (unsigned i = 0; i < subRoutes.size(); ++i) {
  287. string path = currentPath + subRoutes[i].path.getPathDesc();
  288. Json::Value & sri = result[(path.empty() ? "" : path + " ")
  289. + getVerbsStr(subRoutes[i].filter.verbs)];
  290. subRoutes[i].path.getHelp(sri);
  291. subRoutes[i].filter.getHelp(sri);
  292. subRoutes[i].router->getHelp(result, path, subRoutes[i].filter.verbs);
  293. }
  294. }
  295. RestRequestRouter &
  296. RestRequestRouter::
  297. addSubRouter(PathSpec path, const std::string & description, ExtractObject extractObject,
  298. std::shared_ptr<RestRequestRouter> subRouter)
  299. {
  300. // TODO: check it doesn't exist
  301. Route route;
  302. route.path = path;
  303. if (subRouter)
  304. route.router = subRouter;
  305. else route.router.reset(new RestRequestRouter());
  306. route.router->description = description;
  307. route.extractObject = extractObject;
  308. subRoutes.push_back(route);
  309. return *route.router;
  310. }
  311. RestRequestRouter::OnProcessRequest
  312. RestRequestRouter::
  313. getStaticRouteHandler(const string dir) const {
  314. RestRequestRouter::OnProcessRequest staticRoute
  315. = [dir] (const RestServiceEndpoint::ConnectionId & connection,
  316. const RestRequest & request,
  317. const RestRequestParsingContext & context) {
  318. string path = context.resources.back();
  319. cerr << "static content for " << path << endl;
  320. if (path.find("..") != string::npos) {
  321. throw ML::Exception("not dealing with path with .. in it");
  322. }
  323. string filename = dir + "/" + path;
  324. ML::filter_istream stream(filename);
  325. ML::File_Read_Buffer buf(filename);
  326. string mimeType = "text/plain";
  327. if (filename.find(".html") != string::npos) {
  328. mimeType = "text/html";
  329. }
  330. else if (filename.find(".js") != string::npos) {
  331. mimeType = "application/javascript";
  332. }
  333. else if (filename.find(".css") != string::npos) {
  334. mimeType = "text/css";
  335. }
  336. string result(buf.start(), buf.end());
  337. connection.sendResponse(200, result, mimeType);
  338. return RestRequestRouter::MR_YES;
  339. };
  340. return staticRoute;
  341. }
  342. void RestRequestRouter::
  343. serveStaticDirectory(const std::string & route, const std::string & dir) {
  344. addRoute(Rx(route + "/(.*)", "<resource>"),
  345. "GET", "Static content",
  346. getStaticRouteHandler(dir),
  347. Json::Value());
  348. }
  349. } // namespace Datacratic