Adding the routes object

The route object is the url dispatcher, it uses it matching rules to
find what handler should handle a request, perform the call and handle
exceptions that hanend during the handling.

Signed-off-by: Amnon Heiman <amnon@cloudius-systems.com>
This commit is contained in:
Amnon Heiman
2015-02-28 01:53:04 +02:00
parent 1866091b3d
commit 8fccdb77a0
2 changed files with 289 additions and 0 deletions

114
http/routes.cc Normal file
View File

@@ -0,0 +1,114 @@
/*
* This file is open source software, licensed to you under the terms
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
* distributed with this work for additional information regarding copyright
* ownership. You may not use this file except in compliance with the License.
*
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Copyright 2015 Cloudius Systems
*/
#include "routes.hh"
#include "reply.hh"
#include "exception.hh"
namespace httpd {
using namespace std;
void verify_param(const request& req, const sstring& param) {
if (req.get_query_param(param) == "") {
throw missing_param_exception(param);
}
}
routes::~routes() {
for (int i = 0; i < NUM_OPERATION; i++) {
for (auto kv : _map[i]) {
delete kv.second;
}
}
for (int i = 0; i < NUM_OPERATION; i++) {
for (auto r : _rules[i]) {
delete r;
}
}
}
void routes::handle(const sstring& path, request& req, reply& rep) {
handler_base* handler = get_handler(str2type(req._method),
normalize_url(path), req.param);
if (handler != nullptr) {
try {
for (auto& i : handler->_mandatory_param) {
verify_param(req, i);
}
handler->handle(path, &req.param, req, rep);
} catch (const redirect_exception& _e) {
rep.add_header("Location", _e.url).set_status(_e.status()).done(
"json");
return;
} catch (const base_exception& _e) {
json_exception e(_e);
rep.set_status(_e.status(), e.to_json()).done("json");
} catch (exception& _e) {
json_exception e(_e);
cerr << "exception was caught for " << path << ": " << _e.what()
<< endl;
rep.set_status(reply::status_type::internal_server_error,
e.to_json()).done("json");
return;
}
} else {
json_exception ex(not_found_exception("Not found"));
rep.set_status(reply::status_type::not_found, ex.to_json()).done(
"json");
}
}
sstring routes::normalize_url(const sstring& url) {
if (url.length() < 2 || url.at(url.length() - 1) != '/') {
return url;
}
return url.substr(0, url.length() - 1);
}
handler_base* routes::get_handler(operation_type type, const sstring& url,
parameters& params) {
handler_base* handler = get_exact_match(type, url);
if (handler != nullptr) {
return handler;
}
for (auto rule = _rules[type].cbegin(); rule != _rules[type].cend();
++rule) {
handler = (*rule)->get(url, params);
if (handler != nullptr) {
return handler;
}
params.clear();
}
return nullptr;
}
routes& routes::add(operation_type type, const url& url,
handler_base* handler) {
match_rule* rule = new match_rule(handler);
rule->add_str(url._path);
rule->add_param(url._param, true);
return add(rule, type);
}
}

175
http/routes.hh Normal file
View File

@@ -0,0 +1,175 @@
/*
* This file is open source software, licensed to you under the terms
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
* distributed with this work for additional information regarding copyright
* ownership. You may not use this file except in compliance with the License.
*
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Copyright 2015 Cloudius Systems
*/
#ifndef ROUTES_HH_
#define ROUTES_HH_
#include "matchrules.hh"
#include "handlers.hh"
#include "common.hh"
#include "reply.hh"
#include <boost/program_options/variables_map.hpp>
#include <unordered_map>
#include <vector>
namespace httpd {
/**
* The url helps defining a route.
*/
class url {
public:
/**
* Move constructor
*/
url(url&&) = default;
/**
* Construct with a url path as it's parameter
* @param path the url path to be used
*/
url(const sstring& path)
: _path(path) {
}
/**
* Adds a parameter that matches untill the end of the URL.
* @param param the parmaeter name
* @return the current url
*/
url& remainder(const sstring& param) {
this->_param = param;
return *this;
}
sstring _path;
sstring _param;
};
/**
* routes object do the request dispatching according to the url.
* It uses two decision mechanism exact match, if a url matches exactly
* (an optional leading slash is permitted) it is choosen
* If not, the matching rules are used.
* matching rules are evaluated by their insertion order
*/
class routes {
public:
/**
* The destructor deletes the match rules and handlers
*/
~routes();
/**
* adding a handler as an exact match
* @param url the url to match (note that url should start with /)
* @param handler the desire handler
* @return it self
*/
routes& put(operation_type type, const sstring& url,
handler_base* handler) {
_map[type][url] = handler;
return *this;
}
/**
* add a rule to be used.
* rules are search only if an exact match was not found.
* rules are search by the order they were added.
* First in higher priority
* @param rule a rule to add
* @param type the operation type
* @return it self
*/
routes& add(match_rule* rule, operation_type type = GET) {
_rules[type].push_back(rule);
return *this;
}
/**
* Add a url match to a handler:
* Example routes.add(GET, url("/api").remainder("path"), handler);
* @param type
* @param url
* @param handler
* @return
*/
routes& add(operation_type type, const url& url, handler_base* handler);
/**
* the main entry point.
* the general handler calls this method with the request
* the method takes the headers from the request and find the
* right handler.
* It then call the handler with the parameters (if they exists) found in the url
* @param path the url path found
* @param req the http request
* @param rep the http reply
*/
void handle(const sstring& path, httpd::request& req, httpd::reply& rep);
private:
/**
* Search and return an exact match
* @param url the request url
* @return the handler if exists or nullptr if it does not
*/
handler_base* get_exact_match(operation_type type, const sstring& url) {
return (_map[type].find(url) == _map[type].end()) ?
nullptr : _map[type][url];
}
/**
* Search and return a handler by the operation type and url
* @param type the http operation type
* @param url the request url
* @param params a parameter object that will be filled during the match
* @return a handler based on the type/url match
*/
handler_base* get_handler(operation_type type, const sstring& url,
parameters& params);
/**
* Normalize the url to remove the last / if exists
* and get the parameter part
* @param url the full url path
* @param param_part will hold the string with the parameters
* @return the url from the request without the last /
*/
sstring normalize_url(const sstring& url);
std::unordered_map<sstring, handler_base*> _map[NUM_OPERATION];
std::vector<match_rule*> _rules[NUM_OPERATION];
};
/**
* A helper function that check if a parameter is found in the params object
* if it does not the function would throw a parameter not found exception
* @param params the parameters object
* @param param the parameter to look for
*/
void verify_param(const httpd::request& req, const sstring& param);
}
#endif /* ROUTES_HH_ */