diff --git a/speedy/.gitignore b/speedy/.gitignore index f47cb2045..bd250b222 100644 --- a/speedy/.gitignore +++ b/speedy/.gitignore @@ -1 +1,4 @@ *.out +*.dSYM/ +*.gdb +.gdb_history diff --git a/speedy/Makefile b/speedy/Makefile index caecf5cfc..98078a83b 100644 --- a/speedy/Makefile +++ b/speedy/Makefile @@ -1,7 +1,8 @@ CXX=clang++ CFLAGS=-std=c++11 -Wall -LDFLAGS=-luv -lhttp_parser -INC=-I../ +LDFLAGS=-luv -lhttp_parser -lr3 +# debug only +INC=-I../ -g -O0 -fno-inline-functions SOURCES=$(wildcard *.cpp) EXECUTABLE=test.out # OBJECTS=$(SOURCES:.cpp=.o) diff --git a/speedy/README.md b/speedy/README.md index 36ef6b25e..19edc5494 100644 --- a/speedy/README.md +++ b/speedy/README.md @@ -21,5 +21,11 @@ make install * https://github.com/c9s/r3 ``` -brew install r3 (OSX) +./autogen.sh +./configure +make +sudo make install ``` + +## NOTE +r3_include.h is custom r3 header file because of compilation problem related to redefinition of bool diff --git a/speedy/r3_include.h b/speedy/r3_include.h new file mode 100644 index 000000000..1fe192047 --- /dev/null +++ b/speedy/r3_include.h @@ -0,0 +1,228 @@ +/* + * r3.h + * Copyright (C) 2014 c9s <yoanlin93@gmail.com> + * + * Distributed under terms of the MIT license. + */ +#ifndef R3_NODE_H +#define R3_NODE_H + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <pcre.h> +#include <stdbool.h> +#include <r3/str_array.h> +#include <r3/r3_str.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct _edge; +struct _node; +struct _route; +typedef struct _edge edge; +typedef struct _node node; +typedef struct _route route; + +struct _node { + edge ** edges; + // edge ** edge_table; + + // edges are mostly less than 255 + unsigned char compare_type; // compare_type: pcre, opcode, string + unsigned char edge_len; + unsigned char endpoint; // endpoint, should be zero for non-endpoint nodes + unsigned char ov_cnt; // capture vector array size for pcre + + // almost less than 255 + unsigned char edge_cap; + unsigned char route_len; + unsigned char route_cap; + // <-- here comes a char[1] struct padding for alignment since we have 4 char above. + + + /** compile-time variables here.... **/ + + /* the combined regexp pattern string from pattern_tokens */ + pcre * pcre_pattern; + pcre_extra * pcre_extra; + + route ** routes; + + char * combined_pattern; + + /** + * the pointer of route data + */ + void * data; +}; + +#define r3_node_edge_pattern(node,i) node->edges[i]->pattern +#define r3_node_edge_pattern_len(node,i) node->edges[i]->pattern_len + +struct _edge { + char * pattern; // 8 bytes + node * child; // 8 bytes + unsigned char pattern_len; // 1 byte + unsigned char opcode:4; // 4 bit + unsigned char has_slug:1; // 1 bit +}; + +struct _route { + char * path; + int path_len; + + int request_method; // can be (GET || POST) + + char * host; // required host name + int host_len; + + void * data; + + char * remote_addr_pattern; + int remote_addr_pattern_len; +}; + +typedef struct { + str_array * vars; + const char * path; // current path to dispatch + int path_len; // the length of the current path + int request_method; // current request method + + void * data; // route ptr + + char * host; // the request host + int host_len; + + char * remote_addr; + int remote_addr_len; +} match_entry; + + + + + + + + +node * r3_tree_create(int cap); + +node * r3_node_create(); + +void r3_tree_free(node * tree); + +edge * r3_node_connectl(node * n, const char * pat, int len, int strdup, node *child); + +#define r3_node_connect(n, pat, child) r3_node_connectl(n, pat, strlen(pat), 0, child) + +edge * r3_node_find_edge(const node * n, const char * pat, int pat_len); + +void r3_node_append_edge(node *n, edge *child); + + +edge * r3_node_find_common_prefix(node *n, const char *path, int path_len, int *prefix_len, char **errstr); + +node * r3_tree_insert_pathl(node *tree, const char *path, int path_len, void * data); + +#define r3_tree_insert_pathl(tree, path, path_len, data) r3_tree_insert_pathl_ex(tree, path, path_len, NULL , data, NULL) + + + +route * r3_tree_insert_routel(node *tree, int method, const char *path, int path_len, void *data); + +route * r3_tree_insert_routel_ex(node *tree, int method, const char *path, int path_len, void *data, char **errstr); + +#define r3_tree_insert_routel(n, method, path, path_len, data) r3_tree_insert_routel_ex(n, method, path, path_len, data, NULL) + +#define r3_tree_insert_path(n,p,d) r3_tree_insert_pathl_ex(n,p,strlen(p), NULL, d, NULL) + +#define r3_tree_insert_route(n,method,path,data) r3_tree_insert_routel(n, method, path, strlen(path), data) + + +/** + * The private API to insert a path + */ +node * r3_tree_insert_pathl_ex(node *tree, const char *path, int path_len, route * route, void * data, char ** errstr); + +void r3_tree_dump(const node * n, int level); + + +edge * r3_node_find_edge_str(const node * n, const char * str, int str_len); + + +int r3_tree_compile(node *n, char** errstr); + +int r3_tree_compile_patterns(node * n, char** errstr); + +node * r3_tree_matchl(const node * n, const char * path, int path_len, match_entry * entry); + +#define r3_tree_match(n,p,e) r3_tree_matchl(n,p, strlen(p), e) + +// node * r3_tree_match_entry(node * n, match_entry * entry); +#define r3_tree_match_entry(n, entry) r3_tree_matchl(n, entry->path, entry->path_len, entry) + +bool r3_node_has_slug_edges(const node *n); + +edge * r3_edge_createl(const char * pattern, int pattern_len, node * child); + +node * r3_edge_branch(edge *e, int dl); + +void r3_edge_free(edge * edge); + + + + + +route * r3_route_create(const char * path); + +route * r3_route_createl(const char * path, int path_len); + + +void r3_node_append_route(node * n, route * route); + +void r3_route_free(route * route); + +int r3_route_cmp(const route *r1, const match_entry *r2); + +route * r3_tree_match_route(const node *n, match_entry * entry); + +#define r3_route_create(p) r3_route_createl(p, strlen(p)) + + +#define METHOD_GET 2 +#define METHOD_POST 2<<1 +#define METHOD_PUT 2<<2 +#define METHOD_DELETE 2<<3 +#define METHOD_PATCH 2<<4 +#define METHOD_HEAD 2<<5 +#define METHOD_OPTIONS 2<<6 + + + +int r3_pattern_to_opcode(const char * pattern, int pattern_len); + +enum { NODE_COMPARE_STR, NODE_COMPARE_PCRE, NODE_COMPARE_OPCODE }; + +enum { OP_EXPECT_MORE_DIGITS = 1, OP_EXPECT_MORE_WORDS, OP_EXPECT_NOSLASH, OP_EXPECT_NODASH, OP_EXPECT_MORE_ALPHA }; + + + +match_entry * match_entry_createl(const char * path, int path_len); + +#define match_entry_create(path) match_entry_createl(path,strlen(path)) + +void match_entry_free(match_entry * entry); + + + + +#ifdef __cplusplus +} +#endif + + + + +#endif /* !R3_NODE_H */ diff --git a/speedy/speedy.hpp b/speedy/speedy.hpp index a208aee5a..cc17b7bd5 100644 --- a/speedy/speedy.hpp +++ b/speedy/speedy.hpp @@ -1,22 +1,32 @@ #ifndef MEMGRAPH_SPEEDY_HPP #define MEMGRAPH_SPEEDY_HPP +#include <vector> + #include "io/uv/uv.hpp" #include "http/http.hpp" +#include "r3_include.h" namespace speedy { +typedef unsigned int uint; + class Speedy { private: http::HttpServer server; http::Ipv4 ip; + node *n; + std::vector<http::request_cb_t> callbacks; + void store_index(int method, const std::string &path); public: Speedy(uv::UvLoop& loop, const http::Ipv4& ip); - void get(const std::string path, http::request_cb_t callback); + void get(const std::string &path, http::request_cb_t callback); + void post(const std::string &path, http::request_cb_t callback); + void put(const std::string &path, http::request_cb_t callback); + void del(const std::string &path, http::request_cb_t callback); void listen(); - ~Speedy(); }; } diff --git a/speedy/speedy.inl b/speedy/speedy.inl index 36ebefa58..b56d7cd60 100644 --- a/speedy/speedy.inl +++ b/speedy/speedy.inl @@ -2,31 +2,94 @@ #define MEMGRAPH_SPEEDY_INL #include "speedy.hpp" +#include <http_parser.h> namespace speedy { -Speedy::Speedy(uv::UvLoop& loop, const http::Ipv4& ip) : server(loop), ip(ip) +int r3_request_method(http::Method method) { + switch (method) { + case http::Method::GET: return METHOD_GET; + case http::Method::POST: return METHOD_POST; + case http::Method::PUT: return METHOD_PUT; + case http::Method::DELETE: return METHOD_DELETE; + case http::Method::HEAD: return METHOD_HEAD; + } } -void Speedy::get(const std::string path, http::request_cb_t callback) +// TODO: better implementation + +Speedy::Speedy(uv::UvLoop& loop, const http::Ipv4& ip) : server(loop), ip(ip) { + n = r3_tree_create(100); +} + +void Speedy::store_index(int method, const std::string &path) +{ + void *ptr = malloc(sizeof(uint)); + *((uint *)ptr) = callbacks.size() - 1; + r3_tree_insert_routel(n, method, path.c_str(), path.size(), ptr); +} + +void Speedy::get(const std::string &path, http::request_cb_t callback) +{ + callbacks.push_back(callback); + store_index(METHOD_GET, path); + + // TODO: something like this + // this solution doesn't work, currenlty I don't know why + // r3_tree_insert_pathl(n, path.c_str(), path.size(), &callbacks.back()); +} + +void Speedy::post(const std::string &path, http::request_cb_t callback) +{ + callbacks.push_back(callback); + store_index(METHOD_POST, path); +} + +void Speedy::put(const std::string &path, http::request_cb_t callback) +{ + callbacks.push_back(callback); + store_index(METHOD_PUT, path); +} + +void Speedy::del(const std::string &path, http::request_cb_t callback) +{ + callbacks.push_back(callback); + store_index(METHOD_DELETE, path); } void Speedy::listen() { - server.listen(ip, [](http::Request& req, http::Response& res) { - res.send(req.url); + char *errstr = NULL; + int err = r3_tree_compile(n, &errstr); + if (err) { + std::cout << "R3 compile error" << std::endl; + } + + server.listen(ip, [this](http::Request& req, http::Response& res) { + auto url = req.url; + auto c_url = url.c_str(); + match_entry *entry = match_entry_create(c_url); + entry->request_method = r3_request_method(req.method); + route *r = r3_tree_match_route(this->n, entry); + match_entry_free(entry); + if (r) { + int index = *((int *)r->data); + auto callback = this->callbacks[index]; + callback(req, res); + // TODO: and something like this + // auto callback = *reinterpret_cast<http::request_cb_t*>(n->data); + // callback(req, res); + } else { + res.send("Not found"); + } }); std::cout << "Server is UP" << std::endl; } -Speedy::~Speedy() -{ -} - } #endif diff --git a/speedy/test.cpp b/speedy/test.cpp index fc829001f..a0d07c2e8 100644 --- a/speedy/test.cpp +++ b/speedy/test.cpp @@ -2,14 +2,65 @@ #include "speedy.hpp" +const char *test_url_1 = "/test1"; +const char *test_url_2 = "/test2"; +const char *test_url_3 = "/test3"; +const char *test_response = "test"; + +void test_get(const http::request_cb_t &&callback, speedy::Speedy &app) { + app.get(test_url_3, callback); +} + +void test_post(const http::request_cb_t &&callback, speedy::Speedy &app) { + app.post(test_url_3, callback); +} + +void test_put(const http::request_cb_t &&callback, speedy::Speedy &app) { + app.put(test_url_3, callback); +} + +void test_delete(const http::request_cb_t &&callback, speedy::Speedy &app) { + app.del(test_url_3, callback); +} + +auto test_callback = [](http::Request& req, http::Response& res) { + res.send(test_response); +}; + int main(void) { + // speedy init uv::UvLoop loop; http::Ipv4 ip("0.0.0.0", 3400); - speedy::Speedy app(loop, ip); - app.listen(); + // GET methods + app.get(test_url_1, test_callback); + app.get(test_url_2, [](http::Request& req, http::Response& res) { + res.send(test_response); + }); + test_get(test_callback, app); + // POST examples + app.post(test_url_1, test_callback); + app.post(test_url_2, [](http::Request& req, http::Response& res) { + res.send(test_response); + }); + test_post(test_callback, app); + // PUT examples + app.put(test_url_1, test_callback); + app.put(test_url_2, [](http::Request& req, http::Response& res) { + res.send(test_response); + }); + test_put(test_callback, app); + // DELETE examples + app.del(test_url_1, test_callback); + app.del(test_url_2, [](http::Request& req, http::Response& res) { + res.send(test_response); + }); + test_delete(test_callback, app); + + // app run + app.listen(); loop.run(uv::UvLoop::Mode::Default); return 0; diff --git a/speedy/test.py b/speedy/test.py new file mode 100644 index 000000000..961684567 --- /dev/null +++ b/speedy/test.py @@ -0,0 +1,22 @@ +import requests + +endpoint = 'http://localhost:3400/test%s' +methods = [('GET', requests.get), ('POST', requests.post), ('PUT', requests.put), ('DELETE', requests.delete)] + +print '' +isAllFine = True +for index in range(1, 4): + for name, method in methods: + url = endpoint % index + r = method(url) + if r.status_code == 200 and r.text == 'test': + print name, url, 'PASS' + else: + print name, url, 'FAIL' + isAllFine = False +print '' +if isAllFine: + print 'Great. All tests have passed :)' +else: + print 'Fuckup. Something went wrong!' +print ''