aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAntony Woods <acron1@gmail.com>2014-10-24 09:40:09 +0100
committerAntony Woods <acron1@gmail.com>2014-10-24 09:40:09 +0100
commit693aac108de4dc62811b9a2737d895ada474cfb2 (patch)
tree44cc722afea8b6ef26095cd27aa64b97ec48d74e
parent27bf11d35c8ce44fd9fef656e2975712846b9bb2 (diff)
parent4b3b8070e75ce0fc181e5c012c47da2a1e7a918e (diff)
downloadcrow-693aac108de4dc62811b9a2737d895ada474cfb2.tar.gz
crow-693aac108de4dc62811b9a2737d895ada474cfb2.zip
Post-pull commit
-rw-r--r--.gitmodules3
-rw-r--r--README.md18
-rw-r--r--amalgamate/crow_all.h452
-rw-r--r--examples/CMakeLists.txt4
-rw-r--r--examples/example.cpp19
-rw-r--r--examples/example_with_all.cpp94
m---------http-parser0
-rw-r--r--include/ci_map.h2
-rw-r--r--include/common.h2
-rw-r--r--include/http_connection.h3
-rw-r--r--include/http_request.h7
-rw-r--r--include/http_server.h1
-rw-r--r--include/json.h1
-rw-r--r--include/logging.h18
-rw-r--r--include/middleware_context.h2
-rw-r--r--include/parser.h17
-rw-r--r--include/query_string.h308
-rw-r--r--include/routing.h10
-rw-r--r--tests/unittest.cpp118
19 files changed, 996 insertions, 83 deletions
diff --git a/.gitmodules b/.gitmodules
index 95da8a7..e69de29 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +0,0 @@
-[submodule "http-parser"]
- path = http-parser
- url = https://github.com/joyent/http-parser
diff --git a/README.md b/README.md
index c6fa8a4..1400c0d 100644
--- a/README.md
+++ b/README.md
@@ -115,3 +115,21 @@ ctest
#### OSX
brew install boost google-perftools
+### Attributions
+
+Crow uses the following libraries.
+
+ qs_parse
+
+ https://github.com/bartgrantham/qs_parse
+
+ Copyright (c) 2010 Bart Grantham
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
diff --git a/amalgamate/crow_all.h b/amalgamate/crow_all.h
index 7991dcc..a0b15cf 100644
--- a/amalgamate/crow_all.h
+++ b/amalgamate/crow_all.h
@@ -364,62 +364,312 @@ template <typename F, typename Set>
#pragma once
+#include <stdio.h>
+#include <string.h>
+#include <string>
+#include <vector>
+#include <iostream>
+// ----------------------------------------------------------------------------
+// qs_parse (modified)
+// https://github.com/bartgrantham/qs_parse
+// ----------------------------------------------------------------------------
+/* Similar to strncmp, but handles URL-encoding for either string */
+int qs_strncmp(const char * s, const char * qs, size_t n);
-namespace crow
+/* Finds the beginning of each key/value pair and stores a pointer in qs_kv.
+ * Also decodes the value portion of the k/v pair *in-place*. In a future
+ * enhancement it will also have a compile-time option of sorting qs_kv
+ * alphabetically by key. */
+int qs_parse(char * qs, char * qs_kv[], int qs_kv_size);
+
+
+/* Used by qs_parse to decode the value portion of a k/v pair */
+int qs_decode(char * qs);
+
+
+/* Looks up the value according to the key on a pre-processed query string
+ * A future enhancement will be a compile-time option to look up the key
+ * in a pre-sorted qs_kv array via a binary search. */
+//char * qs_k2v(const char * key, char * qs_kv[], int qs_kv_size);
+ char * qs_k2v(const char * key, char * const * qs_kv, int qs_kv_size, int nth);
+
+
+/* Non-destructive lookup of value, based on key. User provides the
+ * destinaton string and length. */
+char * qs_scanvalue(const char * key, const char * qs, char * val, size_t val_len);
+
+// TODO: implement sorting of the qs_kv array; for now ensure it's not compiled
+#undef _qsSORTING
+
+// isxdigit _is_ available in <ctype.h>, but let's avoid another header instead
+#define CROW_QS_ISHEX(x) ((((x)>='0'&&(x)<='9') || ((x)>='A'&&(x)<='F') || ((x)>='a'&&(x)<='f')) ? 1 : 0)
+#define CROW_QS_HEX2DEC(x) (((x)>='0'&&(x)<='9') ? (x)-48 : ((x)>='A'&&(x)<='F') ? (x)-55 : ((x)>='a'&&(x)<='f') ? (x)-87 : 0)
+#define CROW_QS_ISQSCHR(x) ((((x)=='=')||((x)=='#')||((x)=='&')||((x)=='\0')) ? 0 : 1)
+
+inline int qs_strncmp(const char * s, const char * qs, size_t n)
{
- namespace detail
+ int i=0;
+ unsigned char u1, u2, unyb, lnyb;
+
+ while(n-- > 0)
{
- template <typename ... Middlewares>
- struct partial_context
- : public black_magic::pop_back<Middlewares...>::template rebind<partial_context>
- , public black_magic::last_element_type<Middlewares...>::type::context
+ u1 = (unsigned char) *s++;
+ u2 = (unsigned char) *qs++;
+
+ if ( ! CROW_QS_ISQSCHR(u1) ) { u1 = '\0'; }
+ if ( ! CROW_QS_ISQSCHR(u2) ) { u2 = '\0'; }
+
+ if ( u1 == '+' ) { u1 = ' '; }
+ if ( u1 == '%' ) // easier/safer than scanf
{
- using parent_context = typename black_magic::pop_back<Middlewares...>::template rebind<::crow::detail::partial_context>;
- template <int N>
- using partial = typename std::conditional<N == sizeof...(Middlewares)-1, partial_context, typename parent_context::template partial<N>>::type;
+ unyb = (unsigned char) *s++;
+ lnyb = (unsigned char) *s++;
+ if ( CROW_QS_ISHEX(unyb) && CROW_QS_ISHEX(lnyb) )
+ u1 = (CROW_QS_HEX2DEC(unyb) * 16) + CROW_QS_HEX2DEC(lnyb);
+ else
+ u1 = '\0';
+ }
- template <typename T>
- typename T::context& get()
+ if ( u2 == '+' ) { u2 = ' '; }
+ if ( u2 == '%' ) // easier/safer than scanf
+ {
+ unyb = (unsigned char) *qs++;
+ lnyb = (unsigned char) *qs++;
+ if ( CROW_QS_ISHEX(unyb) && CROW_QS_ISHEX(lnyb) )
+ u2 = (CROW_QS_HEX2DEC(unyb) * 16) + CROW_QS_HEX2DEC(lnyb);
+ else
+ u2 = '\0';
+ }
+
+ if ( u1 != u2 )
+ return u1 - u2;
+ if ( u1 == '\0' )
+ return 0;
+ i++;
+ }
+ if ( CROW_QS_ISQSCHR(*qs) )
+ return -1;
+ else
+ return 0;
+}
+
+
+inline int qs_parse(char * qs, char * qs_kv[], int qs_kv_size)
+{
+ int i, j;
+ char * substr_ptr;
+
+ for(i=0; i<qs_kv_size; i++) qs_kv[i] = NULL;
+
+ // find the beginning of the k/v substrings
+ if ( (substr_ptr = strchr(qs, '?')) != NULL )
+ substr_ptr++;
+ else
+ substr_ptr = qs;
+
+ i=0;
+ while(i<qs_kv_size)
+ {
+ qs_kv[i] = substr_ptr;
+ j = strcspn(substr_ptr, "&");
+ if ( substr_ptr[j] == '\0' ) { break; }
+ substr_ptr += j + 1;
+ i++;
+ }
+ i++; // x &'s -> means x iterations of this loop -> means *x+1* k/v pairs
+
+ // we only decode the values in place, the keys could have '='s in them
+ // which will hose our ability to distinguish keys from values later
+ for(j=0; j<i; j++)
+ {
+ substr_ptr = qs_kv[j] + strcspn(qs_kv[j], "=&#");
+ if ( substr_ptr[0] == '&' ) // blank value: skip decoding
+ substr_ptr[0] = '\0';
+ else
+ qs_decode(++substr_ptr);
+ }
+
+#ifdef _qsSORTING
+// TODO: qsort qs_kv, using qs_strncmp() for the comparison
+#endif
+
+ return i;
+}
+
+
+inline int qs_decode(char * qs)
+{
+ int i=0, j=0;
+
+ while( CROW_QS_ISQSCHR(qs[j]) )
+ {
+ if ( qs[j] == '+' ) { qs[i] = ' '; }
+ else if ( qs[j] == '%' ) // easier/safer than scanf
+ {
+ if ( ! CROW_QS_ISHEX(qs[j+1]) || ! CROW_QS_ISHEX(qs[j+2]) )
{
- return static_cast<typename T::context&>(*this);
+ qs[i] = '\0';
+ return i;
}
- };
+ qs[i] = (CROW_QS_HEX2DEC(qs[j+1]) * 16) + CROW_QS_HEX2DEC(qs[j+2]);
+ j+=2;
+ }
+ else
+ {
+ qs[i] = qs[j];
+ }
+ i++; j++;
+ }
+ qs[i] = '\0';
- template <>
- struct partial_context<>
+ return i;
+}
+
+
+inline char * qs_k2v(const char * key, char * const * qs_kv, int qs_kv_size, int nth = 0)
+{
+ int i;
+ size_t key_len, skip;
+
+ key_len = strlen(key);
+
+#ifdef _qsSORTING
+// TODO: binary search for key in the sorted qs_kv
+#else // _qsSORTING
+ for(i=0; i<qs_kv_size; i++)
+ {
+ // we rely on the unambiguous '=' to find the value in our k/v pair
+ if ( qs_strncmp(key, qs_kv[i], key_len) == 0 )
+ {
+ skip = strcspn(qs_kv[i], "=");
+ if ( qs_kv[i][skip] == '=' )
+ skip++;
+ // return (zero-char value) ? ptr to trailing '\0' : ptr to value
+ if(nth == 0)
+ return qs_kv[i] + skip;
+ else
+ --nth;
+ }
+ }
+#endif // _qsSORTING
+
+ return NULL;
+}
+
+
+inline char * qs_scanvalue(const char * key, const char * qs, char * val, size_t val_len)
+{
+ size_t i, key_len;
+ const char * tmp;
+
+ // find the beginning of the k/v substrings
+ if ( (tmp = strchr(qs, '?')) != NULL )
+ qs = tmp + 1;
+
+ key_len = strlen(key);
+ while(qs[0] != '#' && qs[0] != '\0')
+ {
+ if ( qs_strncmp(key, qs, key_len) == 0 )
+ break;
+ qs += strcspn(qs, "&") + 1;
+ }
+
+ if ( qs[0] == '\0' ) return NULL;
+
+ qs += strcspn(qs, "=&#");
+ if ( qs[0] == '=' )
+ {
+ qs++;
+ i = strcspn(qs, "&=#");
+ strncpy(val, qs, (val_len-1)<(i+1) ? (val_len-1) : (i+1));
+ qs_decode(val);
+ }
+ else
+ {
+ if ( val_len > 0 )
+ val[0] = '\0';
+ }
+
+ return val;
+}
+// ----------------------------------------------------------------------------
+
+
+namespace crow
+{
+ class query_string
+ {
+ public:
+ static const int MAX_KEY_VALUE_PAIRS_COUNT = 256;
+
+ query_string()
{
- template <int>
- using partial = partial_context;
- };
- template <int N, typename Context, typename Container, typename CurrentMW, typename ... Middlewares>
- bool middleware_call_helper(Container& middlewares, request& req, response& res, Context& ctx);
+ }
- template <typename ... Middlewares>
- struct context : private partial_context<Middlewares...>
- //struct context : private Middlewares::context... // simple but less type-safe
+ query_string(std::string url)
+ : url_(std::move(url))
{
- template <int N, typename Context, typename Container>
- friend typename std::enable_if<(N==0)>::type after_handlers_call_helper(Container& middlewares, Context& ctx, request& req, response& res);
- template <int N, typename Context, typename Container>
- friend typename std::enable_if<(N>0)>::type after_handlers_call_helper(Container& middlewares, Context& ctx, request& req, response& res);
+ if (url_.empty())
+ return;
- template <int N, typename Context, typename Container, typename CurrentMW, typename ... Middlewares2>
- friend bool middleware_call_helper(Container& middlewares, request& req, response& res, Context& ctx);
+ key_value_pairs_.resize(MAX_KEY_VALUE_PAIRS_COUNT);
- template <typename T>
- typename T::context& get()
+ int count = qs_parse(&url_[0], &key_value_pairs_[0], MAX_KEY_VALUE_PAIRS_COUNT);
+ key_value_pairs_.resize(count);
+ }
+
+ void clear()
+ {
+ key_value_pairs_.clear();
+ url_.clear();
+ }
+
+ friend std::ostream& operator<<(std::ostream& os, const query_string& qs)
+ {
+ os << "[ ";
+ for(size_t i = 0; i < qs.key_value_pairs_.size(); ++i) {
+ if (i)
+ os << ", ";
+ os << qs.key_value_pairs_[i];
+ }
+ os << " ]";
+ return os;
+
+ }
+
+ char* get (const std::string& name) const
+ {
+ char* ret = qs_k2v(name.c_str(), key_value_pairs_.data(), key_value_pairs_.size());
+ return ret;
+ }
+
+ std::vector<char*> get_list (const std::string& name) const
+ {
+ std::vector<char*> ret;
+ std::string plus = name + "[]";
+ char* element = nullptr;
+
+ int count = 0;
+ while(1)
{
- return static_cast<typename T::context&>(*this);
+ element = qs_k2v(plus.c_str(), key_value_pairs_.data(), key_value_pairs_.size(), count++);
+ if (!element)
+ break;
+ ret.push_back(element);
}
+ return ret;
+ }
- template <int N>
- using partial = typename partial_context<Middlewares...>::template partial<N>;
- };
- }
-}
+
+ private:
+ std::string url_;
+ std::vector<char*> key_value_pairs_;
+ };
+
+} // end namespace
@@ -435,8 +685,6 @@ namespace crow
-using namespace std;
-
namespace crow
{
enum class LogLevel
@@ -450,13 +698,13 @@ namespace crow
class ILogHandler {
public:
- virtual void log(string message, LogLevel level) = 0;
+ virtual void log(std::string message, LogLevel level) = 0;
};
class CerrLogHandler : public ILogHandler {
public:
- void log(string message, LogLevel level) override {
- cerr << message;
+ void log(std::string message, LogLevel level) override {
+ std::cerr << message;
}
};
@@ -464,18 +712,18 @@ namespace crow
private:
//
- static string timestamp()
+ static std::string timestamp()
{
char date[32];
time_t t = time(0);
strftime(date, sizeof(date), "%Y-%m-%d %H:%M:%S", gmtime(&t));
- return string(date);
+ return std::string(date);
}
public:
- logger(string prefix, LogLevel level) : level_(level) {
+ logger(std::string prefix, LogLevel level) : level_(level) {
#ifdef CROW_ENABLE_LOGGING
stringstream_ << "(" << timestamp() << ") [" << prefix << "] ";
#endif
@@ -484,7 +732,7 @@ namespace crow
~logger() {
#ifdef CROW_ENABLE_LOGGING
if(level_ >= get_current_log_level()) {
- stringstream_ << endl;
+ stringstream_ << std::endl;
get_handler_ref()->log(stringstream_.str(), level_);
}
#endif
@@ -530,7 +778,7 @@ namespace crow
}
//
- ostringstream stringstream_;
+ std::ostringstream stringstream_;
LogLevel level_;
};
}
@@ -566,6 +814,7 @@ namespace crow
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/operators.hpp>
+#include <vector>
#if defined(__GNUG__) || defined(__clang__)
#define crow_json_likely(x) __builtin_expect(x, 1)
@@ -5313,8 +5562,10 @@ namespace crow
#pragma once
+#include <vector>
#include <string>
#include <stdexcept>
+#include <iostream>
@@ -5440,7 +5691,9 @@ constexpr crow::HTTPMethod operator "" _method(const char* str, size_t len)
#pragma once
+#include <boost/algorithm/string/predicate.hpp>
#include <boost/functional/hash.hpp>
+#include <unordered_map>
namespace crow
{
@@ -5480,6 +5733,8 @@ namespace crow
+
+
namespace crow
{
template <typename T>
@@ -5496,7 +5751,9 @@ namespace crow
struct request
{
HTTPMethod method;
+ std::string raw_url;
std::string url;
+ query_string url_params;
ci_map headers;
std::string body;
@@ -5507,8 +5764,8 @@ namespace crow
{
}
- request(HTTPMethod method, std::string url, ci_map headers, std::string body)
- : method(method), url(std::move(url)), headers(std::move(headers)), body(std::move(body))
+ request(HTTPMethod method, std::string raw_url, std::string url, query_string url_params, ci_map headers, std::string body)
+ : method(method), raw_url(std::move(raw_url)), url(std::move(url)), url_params(std::move(url_params)), headers(std::move(headers)), body(std::move(body))
{
}
@@ -5532,6 +5789,10 @@ namespace crow
#include <string>
#include <unordered_map>
#include <boost/algorithm/string.hpp>
+#include <boost/tokenizer.hpp>
+#include <algorithm>
+
+
@@ -5550,7 +5811,7 @@ namespace crow
static int on_url(http_parser* self_, const char* at, size_t length)
{
HTTPParser* self = static_cast<HTTPParser*>(self_);
- self->url.insert(self->url.end(), at, at+length);
+ self->raw_url.insert(self->raw_url.end(), at, at+length);
return 0;
}
static int on_header_field(http_parser* self_, const char* at, size_t length)
@@ -5606,6 +5867,11 @@ namespace crow
static int on_message_complete(http_parser* self_)
{
HTTPParser* self = static_cast<HTTPParser*>(self_);
+
+ // url params
+ self->url = self->raw_url.substr(0, self->raw_url.find("?"));
+ self->url_params = query_string(self->raw_url);
+
self->process_message();
return 0;
}
@@ -5641,10 +5907,12 @@ namespace crow
void clear()
{
url.clear();
+ raw_url.clear();
header_building_state = 0;
header_field.clear();
header_value.clear();
headers.clear();
+ url_params.clear();
body.clear();
}
@@ -5660,7 +5928,7 @@ namespace crow
request to_request() const
{
- return request{(HTTPMethod)method, std::move(url), std::move(headers), std::move(body)};
+ return request{(HTTPMethod)method, std::move(raw_url), std::move(url), std::move(url_params), std::move(headers), std::move(body)};
}
bool check_version(int major, int minor) const
@@ -5668,11 +5936,14 @@ namespace crow
return http_major == major && http_minor == minor;
}
+ std::string raw_url;
std::string url;
+
int header_building_state = 0;
std::string header_field;
std::string header_value;
ci_map headers;
+ query_string url_params;
std::string body;
Handler* handler_;
@@ -5807,6 +6078,7 @@ namespace crow
#include <unordered_map>
#include <memory>
#include <boost/lexical_cast.hpp>
+#include <vector>
@@ -6435,16 +6707,13 @@ public:
void handle(const request& req, response& res)
{
- // remove url params
- auto editedUrl = req.url.substr(0, req.url.find("?"));
-
- auto found = trie_.find(editedUrl);
+ auto found = trie_.find(req.url);
unsigned rule_index = found.first;
if (!rule_index)
{
- CROW_LOG_DEBUG << "Cannot match rules " << editedUrl;
+ CROW_LOG_DEBUG << "Cannot match rules " << req.url;
res = response(404);
res.end();
return;
@@ -6455,7 +6724,7 @@ public:
if ((rules_[rule_index]->methods() & (1<<(uint32_t)req.method)) == 0)
{
- CROW_LOG_DEBUG << "Rule found but method mismatch: " << editedUrl << " with " << method_name(req.method) << "(" << (uint32_t)req.method << ") / " << rules_[rule_index]->methods();
+ CROW_LOG_DEBUG << "Rule found but method mismatch: " << req.url << " with " << method_name(req.method) << "(" << (uint32_t)req.method << ") / " << rules_[rule_index]->methods();
res = response(404);
res.end();
return;
@@ -6480,6 +6749,71 @@ public:
#pragma once
+
+
+
+
+
+
+
+
+namespace crow
+{
+ namespace detail
+ {
+ template <typename ... Middlewares>
+ struct partial_context
+ : public black_magic::pop_back<Middlewares...>::template rebind<partial_context>
+ , public black_magic::last_element_type<Middlewares...>::type::context
+ {
+ using parent_context = typename black_magic::pop_back<Middlewares...>::template rebind<::crow::detail::partial_context>;
+ template <int N>
+ using partial = typename std::conditional<N == sizeof...(Middlewares)-1, partial_context, typename parent_context::template partial<N>>::type;
+
+ template <typename T>
+ typename T::context& get()
+ {
+ return static_cast<typename T::context&>(*this);
+ }
+ };
+
+ template <>
+ struct partial_context<>
+ {
+ template <int>
+ using partial = partial_context;
+ };
+
+ template <int N, typename Context, typename Container, typename CurrentMW, typename ... Middlewares>
+ bool middleware_call_helper(Container& middlewares, request& req, response& res, Context& ctx);
+
+ template <typename ... Middlewares>
+ struct context : private partial_context<Middlewares...>
+ //struct context : private Middlewares::context... // simple but less type-safe
+ {
+ template <int N, typename Context, typename Container>
+ friend typename std::enable_if<(N==0)>::type after_handlers_call_helper(Container& middlewares, Context& ctx, request& req, response& res);
+ template <int N, typename Context, typename Container>
+ friend typename std::enable_if<(N>0)>::type after_handlers_call_helper(Container& middlewares, Context& ctx, request& req, response& res);
+
+ template <int N, typename Context, typename Container, typename CurrentMW, typename ... Middlewares2>
+ friend bool middleware_call_helper(Container& middlewares, request& req, response& res, Context& ctx);
+
+ template <typename T>
+ typename T::context& get()
+ {
+ return static_cast<typename T::context&>(*this);
+ }
+
+ template <int N>
+ using partial = typename partial_context<Middlewares...>::template partial<N>;
+ };
+ }
+}
+
+
+
+#pragma once
#include <boost/algorithm/string/trim.hpp>
@@ -6665,6 +6999,7 @@ namespace crow
#include <boost/array.hpp>
#include <atomic>
#include <chrono>
+#include <vector>
@@ -6900,7 +7235,7 @@ namespace crow
void complete_request()
{
- CROW_LOG_INFO << "Response: " << this << ' ' << req_.url << ' ' << res.code << ' ' << close_connection_;
+ CROW_LOG_INFO << "Response: " << this << ' ' << req_.raw_url << ' ' << res.code << ' ' << close_connection_;
if (need_to_call_after_handlers_)
{
@@ -7173,6 +7508,7 @@ namespace crow
#include <cstdint>
#include <atomic>
#include <future>
+#include <vector>
#include <memory>
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index 2d449e4..1fcec94 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -9,6 +9,10 @@ if (Tcmalloc_FOUND)
target_link_libraries(example ${Tcmalloc_LIBRARIES})
endif(Tcmalloc_FOUND)
+add_executable(example_with_all example_with_all.cpp)
+#target_link_libraries(example crow)
+target_link_libraries(example_with_all ${Boost_LIBRARIES} )
+
add_custom_command(OUTPUT example_test.py
COMMAND ${CMAKE_COMMAND} -E
copy ${PROJECT_SOURCE_DIR}/example_test.py ${CMAKE_CURRENT_BINARY_DIR}/example_test.py
diff --git a/examples/example.cpp b/examples/example.cpp
index 48b381c..686e823 100644
--- a/examples/example.cpp
+++ b/examples/example.cpp
@@ -5,7 +5,7 @@
class ExampleLogHandler : public crow::ILogHandler {
public:
- void log(string message, crow::LogLevel level) override {
+ void log(std::string message, crow::LogLevel level) override {
// cerr << "ExampleLogHandler -> " << message;
}
};
@@ -99,6 +99,23 @@ int main()
return crow::response{os.str()};
});
+ CROW_ROUTE(app, "/params")
+ ([](const crow::request& req){
+ std::ostringstream os;
+ os << "Params: " << req.url_params << "\n\n";
+ os << "The key 'foo' was " << (req.url_params.get("foo") == nullptr ? "not " : "") << "found.\n";
+ if(req.url_params.get("pew") != nullptr) {
+ double countD = boost::lexical_cast<double>(req.url_params.get("pew"));
+ os << "The value of 'pew' is " << countD << '\n';
+ }
+ auto count = req.url_params.get_list("count");
+ os << "The key 'count' contains " << count.size() << " value(s).\n";
+ for(const auto& countVal : count) {
+ os << " - " << countVal << '\n';
+ }
+ return crow::response{os.str()};
+ });
+
// ignore all log
crow::logger::setLogLevel(crow::LogLevel::DEBUG);
//crow::logger::setHandler(std::make_shared<ExampleLogHandler>());
diff --git a/examples/example_with_all.cpp b/examples/example_with_all.cpp
new file mode 100644
index 0000000..55e2715
--- /dev/null
+++ b/examples/example_with_all.cpp
@@ -0,0 +1,94 @@
+#include "../amalgamate/crow_all.h"
+
+#include <sstream>
+
+class ExampleLogHandler : public crow::ILogHandler {
+ public:
+ void log(std::string message, crow::LogLevel level) override {
+// cerr << "ExampleLogHandler -> " << message;
+ }
+};
+
+int main()
+{
+ crow::SimpleApp app;
+
+ CROW_ROUTE(app, "/")
+ .name("hello")
+ ([]{
+ return "Hello World!";
+ });
+
+ CROW_ROUTE(app, "/about")
+ ([](){
+ return "About Crow example.";
+ });
+
+ // simple json response
+ CROW_ROUTE(app, "/json")
+ ([]{
+ crow::json::wvalue x;
+ x["message"] = "Hello, World!";
+ return x;
+ });
+
+ CROW_ROUTE(app,"/hello/<int>")
+ ([](int count){
+ if (count > 100)
+ return crow::response(400);
+ std::ostringstream os;
+ os << count << " bottles of beer!";
+ return crow::response(os.str());
+ });
+
+ CROW_ROUTE(app,"/add/<int>/<int>")
+ ([](const crow::request& req, crow::response& res, int a, int b){
+ std::ostringstream os;
+ os << a+b;
+ res.write(os.str());
+ res.end();
+ });
+
+ // Compile error with message "Handler type is mismatched with URL paramters"
+ //CROW_ROUTE(app,"/another/<int>")
+ //([](int a, int b){
+ //return crow::response(500);
+ //});
+
+ // more json example
+ CROW_ROUTE(app, "/add_json")
+ ([](const crow::request& req){
+ auto x = crow::json::load(req.body);
+ if (!x)
+ return crow::response(400);
+ int sum = x["a"].i()+x["b"].i();
+ std::ostringstream os;
+ os << sum;
+ return crow::response{os.str()};
+ });
+
+ CROW_ROUTE(app, "/params")
+ ([](const crow::request& req){
+ std::ostringstream os;
+ os << "Params: " << req.url_params << "\n\n";
+ os << "The key 'foo' was " << (req.url_params.get("foo") == nullptr ? "not " : "") << "found.\n";
+ if(req.url_params.get("pew") != nullptr) {
+ double countD = boost::lexical_cast<double>(req.url_params.get("pew"));
+ os << "The value of 'pew' is " << countD << '\n';
+ }
+ auto count = req.url_params.get_list("count");
+ os << "The key 'count' contains " << count.size() << " value(s).\n";
+ for(const auto& countVal : count) {
+ os << " - " << countVal << '\n';
+ }
+ return crow::response{os.str()};
+ });
+
+ // ignore all log
+ crow::logger::setLogLevel(crow::LogLevel::DEBUG);
+ //crow::logger::setHandler(std::make_shared<ExampleLogHandler>());
+
+ app.port(18080)
+ .multithreaded()
+ .run();
+}
diff --git a/http-parser b/http-parser
deleted file mode 160000
-Subproject 5b951d74bd66ec9d38448e0a85b1cf8b85d97db
diff --git a/include/ci_map.h b/include/ci_map.h
index 9b48f0b..155d54f 100644
--- a/include/ci_map.h
+++ b/include/ci_map.h
@@ -1,6 +1,8 @@
#pragma once
+#include <boost/algorithm/string/predicate.hpp>
#include <boost/functional/hash.hpp>
+#include <unordered_map>
namespace crow
{
diff --git a/include/common.h b/include/common.h
index 5720b46..619f4d5 100644
--- a/include/common.h
+++ b/include/common.h
@@ -1,7 +1,9 @@
#pragma once
+#include <vector>
#include <string>
#include <stdexcept>
+#include <iostream>
#include "utility.h"
namespace crow
diff --git a/include/http_connection.h b/include/http_connection.h
index 46eae18..db069c3 100644
--- a/include/http_connection.h
+++ b/include/http_connection.h
@@ -5,6 +5,7 @@
#include <boost/array.hpp>
#include <atomic>
#include <chrono>
+#include <vector>
#include "http_parser_merged.h"
@@ -232,7 +233,7 @@ namespace crow
void complete_request()
{
- CROW_LOG_INFO << "Response: " << this << ' ' << req_.url << ' ' << res.code << ' ' << close_connection_;
+ CROW_LOG_INFO << "Response: " << this << ' ' << req_.raw_url << ' ' << res.code << ' ' << close_connection_;
if (need_to_call_after_handlers_)
{
diff --git a/include/http_request.h b/include/http_request.h
index 331bc80..1319b2d 100644
--- a/include/http_request.h
+++ b/include/http_request.h
@@ -2,6 +2,7 @@
#include "common.h"
#include "ci_map.h"
+#include "query_string.h"
namespace crow
{
@@ -19,7 +20,9 @@ namespace crow
struct request
{
HTTPMethod method;
+ std::string raw_url;
std::string url;
+ query_string url_params;
ci_map headers;
std::string body;
@@ -30,8 +33,8 @@ namespace crow
{
}
- request(HTTPMethod method, std::string url, ci_map headers, std::string body)
- : method(method), url(std::move(url)), headers(std::move(headers)), body(std::move(body))
+ request(HTTPMethod method, std::string raw_url, std::string url, query_string url_params, ci_map headers, std::string body)
+ : method(method), raw_url(std::move(raw_url)), url(std::move(url)), url_params(std::move(url_params)), headers(std::move(headers)), body(std::move(body))
{
}
diff --git a/include/http_server.h b/include/http_server.h
index 32e0e42..bd35ba1 100644
--- a/include/http_server.h
+++ b/include/http_server.h
@@ -5,6 +5,7 @@
#include <cstdint>
#include <atomic>
#include <future>
+#include <vector>
#include <memory>
diff --git a/include/json.h b/include/json.h
index 9306e2a..d79fe88 100644
--- a/include/json.h
+++ b/include/json.h
@@ -10,6 +10,7 @@
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/operators.hpp>
+#include <vector>
#if defined(__GNUG__) || defined(__clang__)
#define crow_json_likely(x) __builtin_expect(x, 1)
diff --git a/include/logging.h b/include/logging.h
index 8f5b833..0d77071 100644
--- a/include/logging.h
+++ b/include/logging.h
@@ -9,8 +9,6 @@
#include "settings.h"
-using namespace std;
-
namespace crow
{
enum class LogLevel
@@ -24,13 +22,13 @@ namespace crow
class ILogHandler {
public:
- virtual void log(string message, LogLevel level) = 0;
+ virtual void log(std::string message, LogLevel level) = 0;
};
class CerrLogHandler : public ILogHandler {
public:
- void log(string message, LogLevel level) override {
- cerr << message;
+ void log(std::string message, LogLevel level) override {
+ std::cerr << message;
}
};
@@ -38,18 +36,18 @@ namespace crow
private:
//
- static string timestamp()
+ static std::string timestamp()
{
char date[32];
time_t t = time(0);
strftime(date, sizeof(date), "%Y-%m-%d %H:%M:%S", gmtime(&t));
- return string(date);
+ return std::string(date);
}
public:
- logger(string prefix, LogLevel level) : level_(level) {
+ logger(std::string prefix, LogLevel level) : level_(level) {
#ifdef CROW_ENABLE_LOGGING
stringstream_ << "(" << timestamp() << ") [" << prefix << "] ";
#endif
@@ -58,7 +56,7 @@ namespace crow
~logger() {
#ifdef CROW_ENABLE_LOGGING
if(level_ >= get_current_log_level()) {
- stringstream_ << endl;
+ stringstream_ << std::endl;
get_handler_ref()->log(stringstream_.str(), level_);
}
#endif
@@ -104,7 +102,7 @@ namespace crow
}
//
- ostringstream stringstream_;
+ std::ostringstream stringstream_;
LogLevel level_;
};
}
diff --git a/include/middleware_context.h b/include/middleware_context.h
index 980a821..daaaa5c 100644
--- a/include/middleware_context.h
+++ b/include/middleware_context.h
@@ -1,6 +1,8 @@
#pragma once
#include "utility.h"
+#include "http_request.h"
+#include "http_response.h"
namespace crow
{
diff --git a/include/parser.h b/include/parser.h
index 869061c..f6b748b 100644
--- a/include/parser.h
+++ b/include/parser.h
@@ -3,7 +3,10 @@
#include <string>
#include <unordered_map>
#include <boost/algorithm/string.hpp>
+#include <boost/tokenizer.hpp>
+#include <algorithm>
+#include "http_parser_merged.h"
#include "http_request.h"
namespace crow
@@ -20,7 +23,7 @@ namespace crow
static int on_url(http_parser* self_, const char* at, size_t length)
{
HTTPParser* self = static_cast<HTTPParser*>(self_);
- self->url.insert(self->url.end(), at, at+length);
+ self->raw_url.insert(self->raw_url.end(), at, at+length);
return 0;
}
static int on_header_field(http_parser* self_, const char* at, size_t length)
@@ -76,6 +79,11 @@ namespace crow
static int on_message_complete(http_parser* self_)
{
HTTPParser* self = static_cast<HTTPParser*>(self_);
+
+ // url params
+ self->url = self->raw_url.substr(0, self->raw_url.find("?"));
+ self->url_params = query_string(self->raw_url);
+
self->process_message();
return 0;
}
@@ -111,10 +119,12 @@ namespace crow
void clear()
{
url.clear();
+ raw_url.clear();
header_building_state = 0;
header_field.clear();
header_value.clear();
headers.clear();
+ url_params.clear();
body.clear();
}
@@ -130,7 +140,7 @@ namespace crow
request to_request() const
{
- return request{(HTTPMethod)method, std::move(url), std::move(headers), std::move(body)};
+ return request{(HTTPMethod)method, std::move(raw_url), std::move(url), std::move(url_params), std::move(headers), std::move(body)};
}
bool check_version(int major, int minor) const
@@ -138,11 +148,14 @@ namespace crow
return http_major == major && http_minor == minor;
}
+ std::string raw_url;
std::string url;
+
int header_building_state = 0;
std::string header_field;
std::string header_value;
ci_map headers;
+ query_string url_params;
std::string body;
Handler* handler_;
diff --git a/include/query_string.h b/include/query_string.h
new file mode 100644
index 0000000..d0a93ea
--- /dev/null
+++ b/include/query_string.h
@@ -0,0 +1,308 @@
+#pragma once
+
+#include <stdio.h>
+#include <string.h>
+#include <string>
+#include <vector>
+#include <iostream>
+
+// ----------------------------------------------------------------------------
+// qs_parse (modified)
+// https://github.com/bartgrantham/qs_parse
+// ----------------------------------------------------------------------------
+/* Similar to strncmp, but handles URL-encoding for either string */
+int qs_strncmp(const char * s, const char * qs, size_t n);
+
+
+/* Finds the beginning of each key/value pair and stores a pointer in qs_kv.
+ * Also decodes the value portion of the k/v pair *in-place*. In a future
+ * enhancement it will also have a compile-time option of sorting qs_kv
+ * alphabetically by key. */
+int qs_parse(char * qs, char * qs_kv[], int qs_kv_size);
+
+
+/* Used by qs_parse to decode the value portion of a k/v pair */
+int qs_decode(char * qs);
+
+
+/* Looks up the value according to the key on a pre-processed query string
+ * A future enhancement will be a compile-time option to look up the key
+ * in a pre-sorted qs_kv array via a binary search. */
+//char * qs_k2v(const char * key, char * qs_kv[], int qs_kv_size);
+ char * qs_k2v(const char * key, char * const * qs_kv, int qs_kv_size, int nth);
+
+
+/* Non-destructive lookup of value, based on key. User provides the
+ * destinaton string and length. */
+char * qs_scanvalue(const char * key, const char * qs, char * val, size_t val_len);
+
+// TODO: implement sorting of the qs_kv array; for now ensure it's not compiled
+#undef _qsSORTING
+
+// isxdigit _is_ available in <ctype.h>, but let's avoid another header instead
+#define CROW_QS_ISHEX(x) ((((x)>='0'&&(x)<='9') || ((x)>='A'&&(x)<='F') || ((x)>='a'&&(x)<='f')) ? 1 : 0)
+#define CROW_QS_HEX2DEC(x) (((x)>='0'&&(x)<='9') ? (x)-48 : ((x)>='A'&&(x)<='F') ? (x)-55 : ((x)>='a'&&(x)<='f') ? (x)-87 : 0)
+#define CROW_QS_ISQSCHR(x) ((((x)=='=')||((x)=='#')||((x)=='&')||((x)=='\0')) ? 0 : 1)
+
+inline int qs_strncmp(const char * s, const char * qs, size_t n)
+{
+ int i=0;
+ unsigned char u1, u2, unyb, lnyb;
+
+ while(n-- > 0)
+ {
+ u1 = (unsigned char) *s++;
+ u2 = (unsigned char) *qs++;
+
+ if ( ! CROW_QS_ISQSCHR(u1) ) { u1 = '\0'; }
+ if ( ! CROW_QS_ISQSCHR(u2) ) { u2 = '\0'; }
+
+ if ( u1 == '+' ) { u1 = ' '; }
+ if ( u1 == '%' ) // easier/safer than scanf
+ {
+ unyb = (unsigned char) *s++;
+ lnyb = (unsigned char) *s++;
+ if ( CROW_QS_ISHEX(unyb) && CROW_QS_ISHEX(lnyb) )
+ u1 = (CROW_QS_HEX2DEC(unyb) * 16) + CROW_QS_HEX2DEC(lnyb);
+ else
+ u1 = '\0';
+ }
+
+ if ( u2 == '+' ) { u2 = ' '; }
+ if ( u2 == '%' ) // easier/safer than scanf
+ {
+ unyb = (unsigned char) *qs++;
+ lnyb = (unsigned char) *qs++;
+ if ( CROW_QS_ISHEX(unyb) && CROW_QS_ISHEX(lnyb) )
+ u2 = (CROW_QS_HEX2DEC(unyb) * 16) + CROW_QS_HEX2DEC(lnyb);
+ else
+ u2 = '\0';
+ }
+
+ if ( u1 != u2 )
+ return u1 - u2;
+ if ( u1 == '\0' )
+ return 0;
+ i++;
+ }
+ if ( CROW_QS_ISQSCHR(*qs) )
+ return -1;
+ else
+ return 0;
+}
+
+
+inline int qs_parse(char * qs, char * qs_kv[], int qs_kv_size)
+{
+ int i, j;
+ char * substr_ptr;
+
+ for(i=0; i<qs_kv_size; i++) qs_kv[i] = NULL;
+
+ // find the beginning of the k/v substrings
+ if ( (substr_ptr = strchr(qs, '?')) != NULL )
+ substr_ptr++;
+ else
+ substr_ptr = qs;
+
+ i=0;
+ while(i<qs_kv_size)
+ {
+ qs_kv[i] = substr_ptr;
+ j = strcspn(substr_ptr, "&");
+ if ( substr_ptr[j] == '\0' ) { break; }
+ substr_ptr += j + 1;
+ i++;
+ }
+ i++; // x &'s -> means x iterations of this loop -> means *x+1* k/v pairs
+
+ // we only decode the values in place, the keys could have '='s in them
+ // which will hose our ability to distinguish keys from values later
+ for(j=0; j<i; j++)
+ {
+ substr_ptr = qs_kv[j] + strcspn(qs_kv[j], "=&#");
+ if ( substr_ptr[0] == '&' ) // blank value: skip decoding
+ substr_ptr[0] = '\0';
+ else
+ qs_decode(++substr_ptr);
+ }
+
+#ifdef _qsSORTING
+// TODO: qsort qs_kv, using qs_strncmp() for the comparison
+#endif
+
+ return i;
+}
+
+
+inline int qs_decode(char * qs)
+{
+ int i=0, j=0;
+
+ while( CROW_QS_ISQSCHR(qs[j]) )
+ {
+ if ( qs[j] == '+' ) { qs[i] = ' '; }
+ else if ( qs[j] == '%' ) // easier/safer than scanf
+ {
+ if ( ! CROW_QS_ISHEX(qs[j+1]) || ! CROW_QS_ISHEX(qs[j+2]) )
+ {
+ qs[i] = '\0';
+ return i;
+ }
+ qs[i] = (CROW_QS_HEX2DEC(qs[j+1]) * 16) + CROW_QS_HEX2DEC(qs[j+2]);
+ j+=2;
+ }
+ else
+ {
+ qs[i] = qs[j];
+ }
+ i++; j++;
+ }
+ qs[i] = '\0';
+
+ return i;
+}
+
+
+inline char * qs_k2v(const char * key, char * const * qs_kv, int qs_kv_size, int nth = 0)
+{
+ int i;
+ size_t key_len, skip;
+
+ key_len = strlen(key);
+
+#ifdef _qsSORTING
+// TODO: binary search for key in the sorted qs_kv
+#else // _qsSORTING
+ for(i=0; i<qs_kv_size; i++)
+ {
+ // we rely on the unambiguous '=' to find the value in our k/v pair
+ if ( qs_strncmp(key, qs_kv[i], key_len) == 0 )
+ {
+ skip = strcspn(qs_kv[i], "=");
+ if ( qs_kv[i][skip] == '=' )
+ skip++;
+ // return (zero-char value) ? ptr to trailing '\0' : ptr to value
+ if(nth == 0)
+ return qs_kv[i] + skip;
+ else
+ --nth;
+ }
+ }
+#endif // _qsSORTING
+
+ return NULL;
+}
+
+
+inline char * qs_scanvalue(const char * key, const char * qs, char * val, size_t val_len)
+{
+ size_t i, key_len;
+ const char * tmp;
+
+ // find the beginning of the k/v substrings
+ if ( (tmp = strchr(qs, '?')) != NULL )
+ qs = tmp + 1;
+
+ key_len = strlen(key);
+ while(qs[0] != '#' && qs[0] != '\0')
+ {
+ if ( qs_strncmp(key, qs, key_len) == 0 )
+ break;
+ qs += strcspn(qs, "&") + 1;
+ }
+
+ if ( qs[0] == '\0' ) return NULL;
+
+ qs += strcspn(qs, "=&#");
+ if ( qs[0] == '=' )
+ {
+ qs++;
+ i = strcspn(qs, "&=#");
+ strncpy(val, qs, (val_len-1)<(i+1) ? (val_len-1) : (i+1));
+ qs_decode(val);
+ }
+ else
+ {
+ if ( val_len > 0 )
+ val[0] = '\0';
+ }
+
+ return val;
+}
+// ----------------------------------------------------------------------------
+
+
+namespace crow
+{
+ class query_string
+ {
+ public:
+ static const int MAX_KEY_VALUE_PAIRS_COUNT = 256;
+
+ query_string()
+ {
+
+ }
+
+ query_string(std::string url)
+ : url_(std::move(url))
+ {
+ if (url_.empty())
+ return;
+
+ key_value_pairs_.resize(MAX_KEY_VALUE_PAIRS_COUNT);
+
+ int count = qs_parse(&url_[0], &key_value_pairs_[0], MAX_KEY_VALUE_PAIRS_COUNT);
+ key_value_pairs_.resize(count);
+ }
+
+ void clear()
+ {
+ key_value_pairs_.clear();
+ url_.clear();
+ }
+
+ friend std::ostream& operator<<(std::ostream& os, const query_string& qs)
+ {
+ os << "[ ";
+ for(size_t i = 0; i < qs.key_value_pairs_.size(); ++i) {
+ if (i)
+ os << ", ";
+ os << qs.key_value_pairs_[i];
+ }
+ os << " ]";
+ return os;
+
+ }
+
+ char* get (const std::string& name) const
+ {
+ char* ret = qs_k2v(name.c_str(), key_value_pairs_.data(), key_value_pairs_.size());
+ return ret;
+ }
+
+ std::vector<char*> get_list (const std::string& name) const
+ {
+ std::vector<char*> ret;
+ std::string plus = name + "[]";
+ char* element = nullptr;
+
+ int count = 0;
+ while(1)
+ {
+ element = qs_k2v(plus.c_str(), key_value_pairs_.data(), key_value_pairs_.size(), count++);
+ if (!element)
+ break;
+ ret.push_back(element);
+ }
+ return ret;
+ }
+
+
+ private:
+ std::string url_;
+ std::vector<char*> key_value_pairs_;
+ };
+
+} // end namespace
diff --git a/include/routing.h b/include/routing.h
index e62cbce..9f607f1 100644
--- a/include/routing.h
+++ b/include/routing.h
@@ -6,6 +6,7 @@
#include <unordered_map>
#include <memory>
#include <boost/lexical_cast.hpp>
+#include <vector>
#include "common.h"
#include "http_response.h"
@@ -629,16 +630,13 @@ public:
void handle(const request& req, response& res)
{
- // remove url params
- auto editedUrl = req.url.substr(0, req.url.find("?"));
-
- auto found = trie_.find(editedUrl);
+ auto found = trie_.find(req.url);
unsigned rule_index = found.first;
if (!rule_index)
{
- CROW_LOG_DEBUG << "Cannot match rules " << editedUrl;
+ CROW_LOG_DEBUG << "Cannot match rules " << req.url;
res = response(404);
res.end();
return;
@@ -649,7 +647,7 @@ public:
if ((rules_[rule_index]->methods() & (1<<(uint32_t)req.method)) == 0)
{
- CROW_LOG_DEBUG << "Rule found but method mismatch: " << editedUrl << " with " << method_name(req.method) << "(" << (uint32_t)req.method << ") / " << rules_[rule_index]->methods();
+ CROW_LOG_DEBUG << "Rule found but method mismatch: " << req.url << " with " << method_name(req.method) << "(" << (uint32_t)req.method << ") / " << rules_[rule_index]->methods();
res = response(404);
res.end();
return;
diff --git a/tests/unittest.cpp b/tests/unittest.cpp
index dc04a5f..4dec727 100644
--- a/tests/unittest.cpp
+++ b/tests/unittest.cpp
@@ -11,6 +11,7 @@
#include "json.h"
#include "mustache.h"
#include "middleware.h"
+#include "query_string.h"
using namespace std;
using namespace crow;
@@ -830,6 +831,123 @@ TEST(bug_quick_repeated_request)
server.stop();
}
+TEST(simple_url_params)
+{
+ static char buf[2048];
+
+ SimpleApp app;
+
+ query_string last_url_params;
+
+ CROW_ROUTE(app, "/params")
+ ([&last_url_params](const crow::request& req){
+ last_url_params = move(req.url_params);
+ return "OK";
+ });
+
+ ///params?h=1&foo=bar&lol&count[]=1&count[]=4&pew=5.2
+
+ decltype(app)::server_t server(&app, 45451);
+ auto _ = async(launch::async, [&]{server.run();});
+ asio::io_service is;
+ std::string sendmsg;
+
+ // check single presence
+ sendmsg = "GET /params?foobar\r\n\r\n";
+ {
+ asio::ip::tcp::socket c(is);
+ c.connect(asio::ip::tcp::endpoint(asio::ip::address::from_string("127.0.0.1"), 45451));
+ c.send(asio::buffer(sendmsg));
+ c.receive(asio::buffer(buf, 2048));
+ c.close();
+
+ ASSERT_TRUE(last_url_params.get("missing") == nullptr);
+ ASSERT_TRUE(last_url_params.get("foobar") != nullptr);
+ ASSERT_TRUE(last_url_params.get_list("missing").empty());
+ }
+ // check multiple presence
+ sendmsg = "GET /params?foo&bar&baz\r\n\r\n";
+ {
+ asio::ip::tcp::socket c(is);
+ c.connect(asio::ip::tcp::endpoint(asio::ip::address::from_string("127.0.0.1"), 45451));
+ c.send(asio::buffer(sendmsg));
+ c.receive(asio::buffer(buf, 2048));
+ c.close();
+
+ ASSERT_TRUE(last_url_params.get("missing") == nullptr);
+ ASSERT_TRUE(last_url_params.get("foo") != nullptr);
+ ASSERT_TRUE(last_url_params.get("bar") != nullptr);
+ ASSERT_TRUE(last_url_params.get("baz") != nullptr);
+ }
+ // check single value
+ sendmsg = "GET /params?hello=world\r\n\r\n";
+ {
+ asio::ip::tcp::socket c(is);
+ c.connect(asio::ip::tcp::endpoint(asio::ip::address::from_string("127.0.0.1"), 45451));
+ c.send(asio::buffer(sendmsg));
+ c.receive(asio::buffer(buf, 2048));
+ c.close();
+
+ ASSERT_EQUAL(string(last_url_params.get("hello")), "world");
+ }
+ // check multiple value
+ sendmsg = "GET /params?hello=world&left=right&up=down\r\n\r\n";
+ {
+ asio::ip::tcp::socket c(is);
+ c.connect(asio::ip::tcp::endpoint(asio::ip::address::from_string("127.0.0.1"), 45451));
+ c.send(asio::buffer(sendmsg));
+ c.receive(asio::buffer(buf, 2048));
+ c.close();
+
+ ASSERT_EQUAL(string(last_url_params.get("hello")), "world");
+ ASSERT_EQUAL(string(last_url_params.get("left")), "right");
+ ASSERT_EQUAL(string(last_url_params.get("up")), "down");
+ }
+ // check multiple value, multiple types
+ sendmsg = "GET /params?int=100&double=123.45&boolean=1\r\n\r\n";
+ {
+ asio::ip::tcp::socket c(is);
+ c.connect(asio::ip::tcp::endpoint(asio::ip::address::from_string("127.0.0.1"), 45451));
+ c.send(asio::buffer(sendmsg));
+ c.receive(asio::buffer(buf, 2048));
+ c.close();
+
+ ASSERT_EQUAL(boost::lexical_cast<int>(last_url_params.get("int")), 100);
+ ASSERT_EQUAL(boost::lexical_cast<double>(last_url_params.get("double")), 123.45);
+ ASSERT_EQUAL(boost::lexical_cast<bool>(last_url_params.get("boolean")), true);
+ }
+ // check single array value
+ sendmsg = "GET /params?tmnt[]=leonardo\r\n\r\n";
+ {
+ asio::ip::tcp::socket c(is);
+
+ c.connect(asio::ip::tcp::endpoint(asio::ip::address::from_string("127.0.0.1"), 45451));
+ c.send(asio::buffer(sendmsg));
+ c.receive(asio::buffer(buf, 2048));
+ c.close();
+
+ ASSERT_TRUE(last_url_params.get("tmnt") == nullptr);
+ ASSERT_EQUAL(last_url_params.get_list("tmnt").size(), 1);
+ ASSERT_EQUAL(string(last_url_params.get_list("tmnt")[0]), "leonardo");
+ }
+ // check multiple array value
+ sendmsg = "GET /params?tmnt[]=leonardo&tmnt[]=donatello&tmnt[]=raphael\r\n\r\n";
+ {
+ asio::ip::tcp::socket c(is);
+
+ c.connect(asio::ip::tcp::endpoint(asio::ip::address::from_string("127.0.0.1"), 45451));
+ c.send(asio::buffer(sendmsg));
+ c.receive(asio::buffer(buf, 2048));
+ c.close();
+
+ ASSERT_EQUAL(last_url_params.get_list("tmnt").size(), 3);
+ ASSERT_EQUAL(string(last_url_params.get_list("tmnt")[0]), "leonardo");
+ ASSERT_EQUAL(string(last_url_params.get_list("tmnt")[1]), "donatello");
+ ASSERT_EQUAL(string(last_url_params.get_list("tmnt")[2]), "raphael");
+ }
+ server.stop();
+}
+
int main()
{
return testmain();