Skip to content

Commit b0312bb

Browse files
authored
added tree to request router (#38)
1 parent 8275e9d commit b0312bb

6 files changed

Lines changed: 196 additions & 37 deletions

File tree

include/webframe.hpp

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,16 @@ namespace webframe
7474
class request
7575
{
7676
public:
77+
/**
78+
* @brief URL variables extracted from the request path
79+
* @details If the request path matches a route with variables, the variables will be extracted
80+
* and stored in this vector. The order of the variables corresponds to the order of the placeholders
81+
* in the route definition.
82+
*
83+
* TODO: find a cleaner way to do this.
84+
*/
85+
std::vector<std::string> path_variables;
86+
7787
/**
7888
* @brief get the HTTP method of the request
7989
* @return the HTTP method of the request
@@ -86,30 +96,30 @@ namespace webframe
8696
virtual std::string get_path() const = 0;
8797

8898
virtual std::string get_uri() const = 0;
89-
99+
90100
/**
91101
* @brief get the value of a specific header
92102
* @param key the header key
93103
* @param value reference to the header value
94-
* @return true if the header exists, false otherwise. The string reference will only
104+
* @return true if the header exists, false otherwise. The string reference will only
95105
* be set if the header exists.
96106
*/
97107
virtual bool get_header(const std::string &key, std::string &value) const = 0;
98108

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

108118
/**
109119
* @brief read the body of the request using a callback
110120
* @param callback a function to be called with the body data and size
111-
* @details The callback will be called with chunks of the body data. None of the runtimes are
112-
* currently capable of streaming request bodies, so the callback will be called at most once with the
121+
* @details The callback will be called with chunks of the body data. None of the runtimes are
122+
* currently capable of streaming request bodies, so the callback will be called at most once with the
113123
* entire body. If there is no request body, the callback will not be called.
114124
*/
115125
virtual void read_body(const std::function<void(const uint8_t *, size_t)> &callback) const = 0;
@@ -126,34 +136,34 @@ namespace webframe
126136
/**
127137
* @brief set the HTTP status code of the response
128138
* @param status_code the HTTP status code
129-
* @details The status code is not checked against valid HTTP status codes. It can be set to
139+
* @details The status code is not checked against valid HTTP status codes. It can be set to
130140
* any integer value, but there is no guarantee that the runtime implementation will accept it.
131141
*/
132142
virtual void set_status(int status_code) = 0;
133-
143+
134144
/**
135145
* @brief set a header of the response
136146
* @param key the header key
137147
* @param value the header value
138148
* @details The headers are not validated, and there is no standard way to handle duplicate entries.
139149
*/
140150
virtual void set_header(const std::string &key, const std::string &value) = 0;
141-
151+
142152
/**
143153
* @brief set the body of the response
144154
* @param data pointer to the body data
145155
* @param size the size of the body data
146-
* @details This must be called once after the status and headers have been set. If it is called
147-
* before, the status will be 200 and the headers will be empty. There is no standard way to handle
156+
* @details This must be called once after the status and headers have been set. If it is called
157+
* before, the status will be 200 and the headers will be empty. There is no standard way to handle
148158
* multiple calls.
149159
*/
150160
virtual void set_body(const uint8_t *data, size_t size) = 0;
151161

152162
/**
153163
* @brief write the body of the response using a callback
154164
* @param callback a function to be called with a chunk to set the body data and size.
155-
* @details The callback will be called with chunks of the body data until it returns false to indicate
156-
* end-of-stream. The callback will always be called at least once, and will only populate the chunk if
165+
* @details The callback will be called with chunks of the body data until it returns false to indicate
166+
* end-of-stream. The callback will always be called at least once, and will only populate the chunk if
157167
* the data is not null and the size is greater than zero.
158168
*/
159169
virtual void write_body(const std::function<bool(std::pair<const uint8_t *, size_t> &)> &callback) = 0;
@@ -162,7 +172,7 @@ namespace webframe
162172
/**
163173
* @class runtime
164174
* @brief abstract interface for WebFrame runtimes
165-
* @details Given an application and a router, the runtime is responsible for abstracting HTTP traffic from the
175+
* @details Given an application and a router, the runtime is responsible for abstracting HTTP traffic from the
166176
* underlying platform. The user will rarely, if ever, interact with this interface directly.
167177
*/
168178
class runtime
@@ -178,7 +188,7 @@ namespace webframe
178188
* @param a the application to dispatch
179189
* @param r the router to use for dispatching requests
180190
* @return the exit code of the application
181-
* @details This is only used for the Win32 desktop runtime. You should not call this directly. Use the WEBFRAME_MAIN macro
191+
* @details This is only used for the Win32 desktop runtime. You should not call this directly. Use the WEBFRAME_MAIN macro
182192
* to define the entry point of your application, and it will call this function for you.
183193
*/
184194
virtual int dispatch(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow, application *a, router *r) = 0;
@@ -190,7 +200,7 @@ namespace webframe
190200
* @param a the application to dispatch
191201
* @param r the router to use for dispatching requests
192202
* @return the exit code of the application
193-
* @details This is used for all non-Win32 runtimes, including Windows servers. Like the other dispatch function, it
203+
* @details This is used for all non-Win32 runtimes, including Windows servers. Like the other dispatch function, it
194204
* should not be called directly.
195205
*/
196206
virtual int dispatch(int argc, const char **argv, application *a, router *r) = 0;

include/webframe/router.hpp

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,55 @@
1919
#ifndef WEBFRAME_ROUTER_HPP
2020
#define WEBFRAME_ROUTER_HPP
2121

22+
#include <memory>
23+
#include <queue>
2224
#include <string>
2325
#include <unordered_map>
26+
#include <vector>
2427

2528
namespace webframe
2629
{
2730
class request;
2831
class response;
2932
class handler;
3033

34+
class tree_node
35+
{
36+
public:
37+
tree_node() = default;
38+
~tree_node() = default;
39+
void add(const std::string& path, handler* h);
40+
handler* find(const std::string& path, std::vector<std::string>& variables) const;
41+
private:
42+
enum class node_type
43+
{
44+
leaf,
45+
token,
46+
wildcard
47+
};
48+
static void split_path(const std::string& path, std::queue<std::string>& tokens);
49+
tree_node *push_token(const std::string& token);
50+
tree_node *push_wildcard();
51+
tree_node *next(const std::string& token, std::vector<std::string>& variables) const;
52+
tree_node *next_token(const std::string& token) const;
53+
tree_node *next_wildcard(const std::string& token, std::vector<std::string>& variables) const;
54+
std::unique_ptr<std::unordered_map<std::string, std::unique_ptr<tree_node>>> _token_nodes;
55+
std::unique_ptr<tree_node> _wildcard_node;
56+
node_type _type = node_type::leaf;
57+
handler *_handler = nullptr;
58+
};
59+
3160
class router
3261
{
3362
public:
3463
router() = default;
3564
~router() = default;
3665
void add_route(const std::string& path, handler* h);
37-
handler *find_route(const std::string& path);
66+
handler *find_route(const std::string& path, std::vector<std::string>& variables) const;
3867
void set_default(handler* h);
39-
handler* get_default();
68+
handler* get_default() const;
4069
private:
41-
std::unordered_map<std::string, handler*> _handlers;
70+
tree_node _root;
4271
handler* _default_handler = nullptr;
4372
};
4473
}

src/router.cpp

Lines changed: 134 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,22 @@
2121
class global_default_handler : public webframe::handler
2222
{
2323
protected:
24-
void handle_get(const webframe::request* req, webframe::response* res) override
24+
void handle_get(const webframe::request *req, webframe::response *res) override
2525
{
2626
throw webframe::exception::not_found;
2727
}
2828

29-
void handle_post(const webframe::request* req, webframe::response* res) override
29+
void handle_post(const webframe::request *req, webframe::response *res) override
3030
{
3131
throw webframe::exception::not_found;
3232
}
3333

34-
void handle_put(const webframe::request* req, webframe::response* res) override
34+
void handle_put(const webframe::request *req, webframe::response *res) override
3535
{
3636
throw webframe::exception::not_found;
3737
}
3838

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

4747
namespace webframe
4848
{
49-
void router::add_route(const std::string& path, handler* h)
49+
void tree_node::add(const std::string &path, handler *h)
5050
{
51-
_handlers[path] = h;
51+
std::queue<std::string> tokens;
52+
split_path(path, tokens);
53+
tree_node *current = this;
54+
while (!tokens.empty())
55+
{
56+
const std::string &token = tokens.front();
57+
if (token == "*")
58+
{
59+
current = current->push_wildcard();
60+
}
61+
else
62+
{
63+
current = current->push_token(token);
64+
}
65+
tokens.pop();
66+
}
67+
current->_handler = h;
5268
}
5369

54-
void router::set_default(handler* h)
70+
handler *tree_node::find(const std::string &path, std::vector<std::string> &variables) const
5571
{
56-
_default_handler = h;
72+
std::queue<std::string> tokens;
73+
split_path(path, tokens);
74+
const tree_node *current = this;
75+
while (!tokens.empty() && current)
76+
{
77+
const std::string &token = tokens.front();
78+
current = current->next(token, variables);
79+
tokens.pop();
80+
}
81+
return current ? current->_handler : nullptr;
82+
}
83+
84+
void tree_node::split_path(const std::string &path, std::queue<std::string> &tokens)
85+
{
86+
size_t start, end;
87+
if (!path.empty() && path != "/")
88+
{
89+
start = 0;
90+
while (start < path.size())
91+
{
92+
size_t end = path.find('/', start);
93+
if (end == std::string::npos)
94+
{
95+
end = path.size();
96+
}
97+
if (end > start)
98+
{
99+
tokens.push(path.substr(start, end - start));
100+
}
101+
start = end + 1;
102+
}
103+
}
104+
}
105+
106+
tree_node *tree_node::push_token(const std::string &token)
107+
{
108+
_type = node_type::token;
109+
if (!_token_nodes)
110+
{
111+
_token_nodes = std::make_unique<std::unordered_map<std::string, std::unique_ptr<tree_node>>>();
112+
}
113+
auto &node_ptr = (*_token_nodes)[token];
114+
if (!node_ptr)
115+
{
116+
node_ptr = std::make_unique<tree_node>();
117+
}
118+
return node_ptr.get();
119+
}
120+
121+
tree_node *tree_node::push_wildcard()
122+
{
123+
_type = node_type::wildcard;
124+
if (!_wildcard_node)
125+
{
126+
_wildcard_node = std::make_unique<tree_node>();
127+
}
128+
return _wildcard_node.get();
129+
}
130+
131+
tree_node *tree_node::next(const std::string &token, std::vector<std::string> &variables) const
132+
{
133+
tree_node *node(nullptr);
134+
switch(_type)
135+
{
136+
case node_type::token:
137+
node = next_token(token);
138+
break;
139+
case node_type::wildcard:
140+
node = next_wildcard(token, variables);
141+
break;
142+
default:
143+
break;
144+
}
145+
return node;
57146
}
58147

59-
handler* router::find_route(const std::string& path)
148+
tree_node *tree_node::next_token(const std::string &token) const
60149
{
61-
auto it = _handlers.find(path);
62-
if (it != _handlers.end()) {
63-
return it->second;
150+
tree_node *node(nullptr);
151+
if (_token_nodes)
152+
{
153+
auto it = _token_nodes->find(token);
154+
if (it != _token_nodes->end())
155+
{
156+
node = it->second.get();
157+
}
64158
}
65-
return get_default();
159+
return node;
160+
}
161+
162+
tree_node *tree_node::next_wildcard(const std::string& token, std::vector<std::string> &variables) const
163+
{
164+
if (_wildcard_node)
165+
{
166+
variables.push_back(token);
167+
}
168+
return _wildcard_node ? _wildcard_node.get() : nullptr;
169+
}
170+
171+
void router::add_route(const std::string &path, handler *h)
172+
{
173+
_root.add(path, h);
174+
}
175+
176+
void router::set_default(handler *h)
177+
{
178+
_default_handler = h;
179+
}
180+
181+
handler *router::find_route(const std::string &path, std::vector<std::string> &variables) const
182+
{
183+
handler *h = _root.find(path, variables);
184+
return h ? h : get_default();
66185
}
67186

68-
handler* router::get_default()
187+
handler *router::get_default() const
69188
{
70-
return _default_handler ? _default_handler : &g_default_handler;
189+
return _default_handler ? _default_handler : &g_default_handler;
71190
}
72191
}

src/runtimes/desktop/webview.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ namespace webframe
1818
bool sent(false);
1919
webframe::desktop::request req(&request);
2020
webframe::desktop::response res(response.get(), sent);
21-
webframe::handler *handler = _router->find_route(req.get_path());
21+
webframe::handler *handler = _router->find_route(req.get_path(), req.path_variables);
2222
handler->handle_request(&req, &res);
2323
if (!sent)
2424
{

src/runtimes/server/runtime.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ static void evhttp_callback(struct evhttp_request *req, void *arg)
3939
webframe::router *runtime = static_cast<webframe::router *>(arg);
4040
webframe::server::request request(req);
4141
webframe::server::response response(req, sent_response, status);
42-
webframe::handler *handler = runtime->find_route(request.get_path());
42+
webframe::handler *handler = runtime->find_route(request.get_path(), request.path_variables);
4343
handler->handle_request(&request, &response);
4444
if (!sent_response)
4545
{

tests/src/client.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ namespace webframe::test
2626

2727
void client::run(const webframe::request *request, webframe::response *response)
2828
{
29-
webframe::handler *handler = _router->find_route(request->get_path());
29+
std::vector<std::string> variables;
30+
webframe::handler *handler = _router->find_route(request->get_path(), variables);
3031
handler->handle_request(request, response);
3132
}
3233
}

0 commit comments

Comments
 (0)