Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 26 additions & 16 deletions include/webframe.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,16 @@ namespace webframe
class request
{
public:
/**
* @brief URL variables extracted from the request path
* @details If the request path matches a route with variables, the variables will be extracted
* and stored in this vector. The order of the variables corresponds to the order of the placeholders
* in the route definition.
*
* TODO: find a cleaner way to do this.
*/
std::vector<std::string> path_variables;

/**
* @brief get the HTTP method of the request
* @return the HTTP method of the request
Expand All @@ -86,30 +96,30 @@ namespace webframe
virtual std::string get_path() const = 0;

virtual std::string get_uri() const = 0;

/**
* @brief get the value of a specific header
* @param key the header key
* @param value reference to the header value
* @return true if the header exists, false otherwise. The string reference will only
* @return true if the header exists, false otherwise. The string reference will only
* be set if the header exists.
*/
virtual bool get_header(const std::string &key, std::string &value) const = 0;

/**
* @brief get the body of the request as a pointer and size
* @return a pair containing a pointer to the body data and the size of the body
* @details The body data is not guaranteed to be null-terminated. The pointer and size are only
* valid for the duration of the request handling. If the request does not have a body, the pointer
* @details The body data is not guaranteed to be null-terminated. The pointer and size are only
* valid for the duration of the request handling. If the request does not have a body, the pointer
* will be null and the size will be zero.
*/
virtual std::pair<const uint8_t *, size_t> get_body() const = 0;

/**
* @brief read the body of the request using a callback
* @param callback a function to be called with the body data and size
* @details The callback will be called with chunks of the body data. None of the runtimes are
* currently capable of streaming request bodies, so the callback will be called at most once with the
* @details The callback will be called with chunks of the body data. None of the runtimes are
* currently capable of streaming request bodies, so the callback will be called at most once with the
* entire body. If there is no request body, the callback will not be called.
*/
virtual void read_body(const std::function<void(const uint8_t *, size_t)> &callback) const = 0;
Expand All @@ -126,34 +136,34 @@ namespace webframe
/**
* @brief set the HTTP status code of the response
* @param status_code the HTTP status code
* @details The status code is not checked against valid HTTP status codes. It can be set to
* @details The status code is not checked against valid HTTP status codes. It can be set to
* any integer value, but there is no guarantee that the runtime implementation will accept it.
*/
virtual void set_status(int status_code) = 0;

/**
* @brief set a header of the response
* @param key the header key
* @param value the header value
* @details The headers are not validated, and there is no standard way to handle duplicate entries.
*/
virtual void set_header(const std::string &key, const std::string &value) = 0;

/**
* @brief set the body of the response
* @param data pointer to the body data
* @param size the size of the body data
* @details This must be called once after the status and headers have been set. If it is called
* before, the status will be 200 and the headers will be empty. There is no standard way to handle
* @details This must be called once after the status and headers have been set. If it is called
* before, the status will be 200 and the headers will be empty. There is no standard way to handle
* multiple calls.
*/
virtual void set_body(const uint8_t *data, size_t size) = 0;

/**
* @brief write the body of the response using a callback
* @param callback a function to be called with a chunk to set the body data and size.
* @details The callback will be called with chunks of the body data until it returns false to indicate
* end-of-stream. The callback will always be called at least once, and will only populate the chunk if
* @details The callback will be called with chunks of the body data until it returns false to indicate
* end-of-stream. The callback will always be called at least once, and will only populate the chunk if
* the data is not null and the size is greater than zero.
*/
virtual void write_body(const std::function<bool(std::pair<const uint8_t *, size_t> &)> &callback) = 0;
Expand All @@ -162,7 +172,7 @@ namespace webframe
/**
* @class runtime
* @brief abstract interface for WebFrame runtimes
* @details Given an application and a router, the runtime is responsible for abstracting HTTP traffic from the
* @details Given an application and a router, the runtime is responsible for abstracting HTTP traffic from the
* underlying platform. The user will rarely, if ever, interact with this interface directly.
*/
class runtime
Expand All @@ -178,7 +188,7 @@ namespace webframe
* @param a the application to dispatch
* @param r the router to use for dispatching requests
* @return the exit code of the application
* @details This is only used for the Win32 desktop runtime. You should not call this directly. Use the WEBFRAME_MAIN macro
* @details This is only used for the Win32 desktop runtime. You should not call this directly. Use the WEBFRAME_MAIN macro
* to define the entry point of your application, and it will call this function for you.
*/
virtual int dispatch(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow, application *a, router *r) = 0;
Expand All @@ -190,7 +200,7 @@ namespace webframe
* @param a the application to dispatch
* @param r the router to use for dispatching requests
* @return the exit code of the application
* @details This is used for all non-Win32 runtimes, including Windows servers. Like the other dispatch function, it
* @details This is used for all non-Win32 runtimes, including Windows servers. Like the other dispatch function, it
* should not be called directly.
*/
virtual int dispatch(int argc, const char **argv, application *a, router *r) = 0;
Expand Down
35 changes: 32 additions & 3 deletions include/webframe/router.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,55 @@
#ifndef WEBFRAME_ROUTER_HPP
#define WEBFRAME_ROUTER_HPP

#include <memory>
#include <queue>
#include <string>
#include <unordered_map>
#include <vector>

namespace webframe
{
class request;
class response;
class handler;

class tree_node
{
public:
tree_node() = default;
~tree_node() = default;
void add(const std::string& path, handler* h);
handler* find(const std::string& path, std::vector<std::string>& variables) const;
private:
enum class node_type
{
leaf,
token,
wildcard
};
static void split_path(const std::string& path, std::queue<std::string>& tokens);
tree_node *push_token(const std::string& token);
tree_node *push_wildcard();
tree_node *next(const std::string& token, std::vector<std::string>& variables) const;
tree_node *next_token(const std::string& token) const;
tree_node *next_wildcard(const std::string& token, std::vector<std::string>& variables) const;
std::unique_ptr<std::unordered_map<std::string, std::unique_ptr<tree_node>>> _token_nodes;
std::unique_ptr<tree_node> _wildcard_node;
node_type _type = node_type::leaf;
handler *_handler = nullptr;
};

class router
{
public:
router() = default;
~router() = default;
void add_route(const std::string& path, handler* h);
handler *find_route(const std::string& path);
handler *find_route(const std::string& path, std::vector<std::string>& variables) const;
void set_default(handler* h);
handler* get_default();
handler* get_default() const;
private:
std::unordered_map<std::string, handler*> _handlers;
tree_node _root;
handler* _default_handler = nullptr;
};
}
Expand Down
149 changes: 134 additions & 15 deletions src/router.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,22 @@
class global_default_handler : public webframe::handler
{
protected:
void handle_get(const webframe::request* req, webframe::response* res) override
void handle_get(const webframe::request *req, webframe::response *res) override
{
throw webframe::exception::not_found;
}

void handle_post(const webframe::request* req, webframe::response* res) override
void handle_post(const webframe::request *req, webframe::response *res) override
{
throw webframe::exception::not_found;
}

void handle_put(const webframe::request* req, webframe::response* res) override
void handle_put(const webframe::request *req, webframe::response *res) override
{
throw webframe::exception::not_found;
}

void handle_delete(const webframe::request* req, webframe::response* res) override
void handle_delete(const webframe::request *req, webframe::response *res) override
{
throw webframe::exception::not_found;
}
Expand All @@ -46,27 +46,146 @@ static global_default_handler g_default_handler;

namespace webframe
{
void router::add_route(const std::string& path, handler* h)
void tree_node::add(const std::string &path, handler *h)
{
_handlers[path] = h;
std::queue<std::string> tokens;
split_path(path, tokens);
tree_node *current = this;
while (!tokens.empty())
{
const std::string &token = tokens.front();
if (token == "*")
{
current = current->push_wildcard();
}
else
{
current = current->push_token(token);
}
tokens.pop();
}
current->_handler = h;
}

void router::set_default(handler* h)
handler *tree_node::find(const std::string &path, std::vector<std::string> &variables) const
{
_default_handler = h;
std::queue<std::string> tokens;
split_path(path, tokens);
const tree_node *current = this;
while (!tokens.empty() && current)
{
const std::string &token = tokens.front();
current = current->next(token, variables);
tokens.pop();
}
return current ? current->_handler : nullptr;
}

void tree_node::split_path(const std::string &path, std::queue<std::string> &tokens)
{
size_t start, end;
if (!path.empty() && path != "/")
{
start = 0;
while (start < path.size())
{
size_t end = path.find('/', start);
if (end == std::string::npos)
{
end = path.size();
}
if (end > start)
{
tokens.push(path.substr(start, end - start));
}
start = end + 1;
}
}
}

tree_node *tree_node::push_token(const std::string &token)
{
_type = node_type::token;
if (!_token_nodes)
{
_token_nodes = std::make_unique<std::unordered_map<std::string, std::unique_ptr<tree_node>>>();
}
auto &node_ptr = (*_token_nodes)[token];
if (!node_ptr)
{
node_ptr = std::make_unique<tree_node>();
}
return node_ptr.get();
}

tree_node *tree_node::push_wildcard()
{
_type = node_type::wildcard;
if (!_wildcard_node)
{
_wildcard_node = std::make_unique<tree_node>();
}
return _wildcard_node.get();
}

tree_node *tree_node::next(const std::string &token, std::vector<std::string> &variables) const
{
tree_node *node(nullptr);
switch(_type)
{
case node_type::token:
node = next_token(token);
break;
case node_type::wildcard:
node = next_wildcard(token, variables);
break;
default:
break;
}
return node;
}

handler* router::find_route(const std::string& path)
tree_node *tree_node::next_token(const std::string &token) const
{
auto it = _handlers.find(path);
if (it != _handlers.end()) {
return it->second;
tree_node *node(nullptr);
if (_token_nodes)
{
auto it = _token_nodes->find(token);
if (it != _token_nodes->end())
{
node = it->second.get();
}
}
return get_default();
return node;
}

tree_node *tree_node::next_wildcard(const std::string& token, std::vector<std::string> &variables) const
{
if (_wildcard_node)
{
variables.push_back(token);
}
return _wildcard_node ? _wildcard_node.get() : nullptr;
}

void router::add_route(const std::string &path, handler *h)
{
_root.add(path, h);
}

void router::set_default(handler *h)
{
_default_handler = h;
}

handler *router::find_route(const std::string &path, std::vector<std::string> &variables) const
{
handler *h = _root.find(path, variables);
return h ? h : get_default();
}

handler* router::get_default()
handler *router::get_default() const
{
return _default_handler ? _default_handler : &g_default_handler;
return _default_handler ? _default_handler : &g_default_handler;
}
}
2 changes: 1 addition & 1 deletion src/runtimes/desktop/webview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace webframe
bool sent(false);
webframe::desktop::request req(&request);
webframe::desktop::response res(response.get(), sent);
webframe::handler *handler = _router->find_route(req.get_path());
webframe::handler *handler = _router->find_route(req.get_path(), req.path_variables);
handler->handle_request(&req, &res);
if (!sent)
{
Expand Down
2 changes: 1 addition & 1 deletion src/runtimes/server/runtime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ static void evhttp_callback(struct evhttp_request *req, void *arg)
webframe::router *runtime = static_cast<webframe::router *>(arg);
webframe::server::request request(req);
webframe::server::response response(req, sent_response, status);
webframe::handler *handler = runtime->find_route(request.get_path());
webframe::handler *handler = runtime->find_route(request.get_path(), request.path_variables);
handler->handle_request(&request, &response);
if (!sent_response)
{
Expand Down
3 changes: 2 additions & 1 deletion tests/src/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ namespace webframe::test

void client::run(const webframe::request *request, webframe::response *response)
{
webframe::handler *handler = _router->find_route(request->get_path());
std::vector<std::string> variables;
webframe::handler *handler = _router->find_route(request->get_path(), variables);
handler->handle_request(request, response);
}
}
Loading