mirror of
https://github.com/CrowCpp/Crow.git
synced 2024-06-07 21:10:44 +00:00
Post-pull commit
This commit is contained in:
commit
693aac108d
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
||||
[submodule "http-parser"]
|
||||
path = http-parser
|
||||
url = https://github.com/joyent/http-parser
|
18
README.md
18
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.
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
||||
/* 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
|
||||
{
|
||||
namespace detail
|
||||
class query_string
|
||||
{
|
||||
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
|
||||
public:
|
||||
static const int MAX_KEY_VALUE_PAIRS_COUNT = 256;
|
||||
|
||||
query_string()
|
||||
{
|
||||
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>;
|
||||
};
|
||||
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
|
||||
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
|
@ -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
|
||||
|
@ -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>());
|
||||
|
94
examples/example_with_all.cpp
Normal file
94
examples/example_with_all.cpp
Normal file
@ -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();
|
||||
}
|
@ -1 +0,0 @@
|
||||
Subproject commit 5b951d74bd66ec9d38448e0a85b1cf8b85d97db3
|
@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/functional/hash.hpp>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace crow
|
||||
{
|
||||
|
@ -1,7 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
#include <iostream>
|
||||
#include "utility.h"
|
||||
|
||||
namespace crow
|
||||
|
@ -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_)
|
||||
{
|
||||
|
@ -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))
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include <cstdint>
|
||||
#include <atomic>
|
||||
#include <future>
|
||||
#include <vector>
|
||||
|
||||
#include <memory>
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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_;
|
||||
};
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "utility.h"
|
||||
#include "http_request.h"
|
||||
#include "http_response.h"
|
||||
|
||||
namespace crow
|
||||
{
|
||||
|
@ -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_;
|
||||
|
308
include/query_string.h
Normal file
308
include/query_string.h
Normal file
@ -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
|
@ -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;
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user