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:
114
http/routes.cc
Normal file
114
http/routes.cc
Normal 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
175
http/routes.hh
Normal 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_ */
|
||||
Reference in New Issue
Block a user