Merge branch 'amnon/future_http1' of github.com:cloudius-systems/seastar-dev

Futurize http server stack, from Amnon.
This commit is contained in:
Avi Kivity
2015-03-30 17:57:51 +03:00
20 changed files with 1390 additions and 54 deletions

73
apps/httpd/demo.json Normal file
View File

@@ -0,0 +1,73 @@
{
"apiVersion": "0.0.1",
"swaggerVersion": "1.2",
"basePath": "{{Protocol}}://{{Host}}",
"resourcePath": "/hello",
"produces": [
"application/json"
],
"apis": [
{
"path": "/hello/world/{var1}/{var2}",
"operations": [
{
"method": "GET",
"summary": "Returns the number of seconds since the system was booted",
"type": "long",
"nickname": "hello_world",
"produces": [
"application/json"
],
"parameters": [
{
"name":"var2",
"description":"Full path of file or directory",
"required":true,
"allowMultiple":true,
"type":"string",
"paramType":"path"
},
{
"name":"var1",
"description":"Full path of file or directory",
"required":true,
"allowMultiple":false,
"type":"string",
"paramType":"path"
},
{
"name":"query_enum",
"description":"The operation to perform",
"required":true,
"allowMultiple":false,
"type":"string",
"paramType":"query",
"enum":["VAL1", "VAL2", "VAL3"]
}
]
}
]
}
],
"models" : {
"my_object": {
"id": "my_object",
"description": "Demonstrate an object",
"properties": {
"var1": {
"type": "string",
"description": "The first parameter in the path"
},
"var2": {
"type": "string",
"description": "The second parameter in the path"
},
"enum_var" : {
"type": "string",
"description": "Demonstrate an enum returned, note this is not the same enum type of the request",
"enum":["VAL1", "VAL2", "VAL3"]
}
}
}
}
}

View File

@@ -21,6 +21,9 @@
#include "http/httpd.hh"
#include "http/handlers.hh"
#include "http/function_handlers.hh"
#include "http/file_handler.hh"
#include "apps/httpd/demo.json.hh"
namespace bpo = boost::program_options;
@@ -28,13 +31,33 @@ using namespace httpd;
class handl : public httpd::handler_base {
public:
virtual void handle(const sstring& path, parameters* params,
httpd::const_req& req, httpd::reply& rep) {
rep._content = "hello";
rep.done("html");
virtual future<std::unique_ptr<reply> > handle(const sstring& path,
std::unique_ptr<request> req, std::unique_ptr<reply> rep) {
rep->_content = "hello";
rep->done("html");
return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
}
};
void set_routes(routes& r) {
function_handler* h1 = new function_handler([](const_req req) {
return "hello";
});
r.add(operation_type::GET, url("/"), h1);
r.add(operation_type::GET, url("/file").remainder("path"),
new directory_handler("/"));
demo_json::hello_world.set(r,
[](const_req req) {
demo_json::my_object obj;
obj.var1 = req.param.at("var1");
obj.var2 = req.param.at("var2");
demo_json::ns_hello_world::query_enum v = demo_json::ns_hello_world::str2query_enum(req.query_parameters.at("query_enum"));
// This demonstrate enum conversion
obj.enum_var = v;
return obj;
});
}
int main(int ac, char** av) {
app_template app;
app.add_options()("port", bpo::value<uint16_t>()->default_value(10000),
@@ -46,8 +69,7 @@ int main(int ac, char** av) {
auto server = new distributed<http_server>;
server->start().then([server = std::move(server), port] () mutable {
server->invoke_on_all([](http_server& server) {
handl* h1 = new handl();
server._routes.add(operation_type::GET, url("/"), h1);
set_routes(server._routes);
});
server->invoke_on_all(&http_server::listen, ipv4_addr {port});
}).then([port] {

View File

@@ -210,7 +210,7 @@ deps = {
'seastar.pc': [],
'apps/seastar/seastar': ['apps/seastar/main.cc'] + core,
'tests/test-reactor': ['tests/test-reactor.cc'] + core,
'apps/httpd/httpd': ['http/common.cc', 'http/routes.cc', 'json/json_elements.cc', 'json/formatter.cc', 'http/matcher.cc', 'http/mime_types.cc', 'http/httpd.cc', 'http/reply.cc', 'http/request_parser.rl', 'apps/httpd/main.cc'] + libnet + core,
'apps/httpd/httpd': ['apps/httpd/demo.json', 'http/json_path.cc', 'http/file_handler.cc', 'http/common.cc', 'http/routes.cc', 'json/json_elements.cc', 'json/formatter.cc', 'http/matcher.cc', 'http/mime_types.cc', 'http/httpd.cc', 'http/reply.cc', 'http/request_parser.rl', 'apps/httpd/main.cc'] + libnet + core,
'apps/memcached/memcached': ['apps/memcached/memcache.cc'] + memcache_base,
'tests/memcached/test_ascii_parser': ['tests/memcached/test_ascii_parser.cc'] + memcache_base,
'tests/fileiotest': ['tests/fileiotest.cc'] + core,
@@ -230,7 +230,7 @@ deps = {
'apps/seawreck/seawreck': ['apps/seawreck/seawreck.cc', 'apps/seawreck/http_response_parser.rl'] + core + libnet,
'tests/blkdiscard_test': ['tests/blkdiscard_test.cc'] + core,
'tests/sstring_test': ['tests/sstring_test.cc'] + core,
'tests/httpd': ['http/common.cc', 'http/routes.cc', 'json/json_elements.cc', 'json/formatter.cc', 'http/matcher.cc', 'tests/httpd.cc', 'http/mime_types.cc', 'http/reply.cc'] + core,
'tests/httpd': ['http/common.cc', 'http/routes.cc', 'http/json_path.cc', 'json/json_elements.cc', 'json/formatter.cc', 'http/matcher.cc', 'tests/httpd.cc', 'http/mime_types.cc', 'http/reply.cc'] + core,
'tests/allocator_test': ['tests/allocator_test.cc', 'core/memory.cc', 'core/posix.cc'],
'tests/output_stream_test': ['tests/output_stream_test.cc'] + core + libnet,
'tests/udp_zero_copy': ['tests/udp_zero_copy.cc'] + core + libnet,
@@ -321,6 +321,9 @@ with open(buildfile, 'w') as f:
rule gen
command = echo -e $text > $out
description = GEN $out
rule swagger
command = json/json2code.py -f $in -o $out
description = SWAGGER $out
''').format(**globals()))
for mode in build_modes:
modeval = modes[mode]
@@ -347,6 +350,7 @@ with open(buildfile, 'w') as f:
artifacts = str.join(' ', ('$builddir/' + mode + '/' + x for x in build_artifacts))))
compiles = {}
ragels = {}
swaggers = {}
for binary in build_artifacts:
srcs = deps[binary]
objs = ['$builddir/' + mode + '/' + src.replace('.cc', '.o')
@@ -375,15 +379,21 @@ with open(buildfile, 'w') as f:
elif src.endswith('.rl'):
hh = '$builddir/' + mode + '/gen/' + src.replace('.rl', '.hh')
ragels[hh] = src
elif src.endswith('.json'):
hh = '$builddir/' + mode + '/gen/' + src + '.hh'
swaggers[hh] = src
else:
raise Exception('No rule for ' + src)
for obj in compiles:
src = compiles[obj]
gen_headers = ragels.keys()
gen_headers = list(ragels.keys()) + list(swaggers.keys())
f.write('build {}: cxx.{} {} || {} \n'.format(obj, mode, src, ' '.join(gen_headers)))
for hh in ragels:
src = ragels[hh]
f.write('build {}: ragel {}\n'.format(hh, src))
for hh in swaggers:
src = swaggers[hh]
f.write('build {}: swagger {}\n'.format(hh,src))
f.write(textwrap.dedent('''\
rule configure
command = python3 configure.py $configure_args

View File

@@ -392,6 +392,45 @@ reactor::open_file_dma(sstring name, open_flags flags) {
});
}
directory_entry_type stat_to_entry_type(__mode_t type) {
if (S_ISDIR(type)) {
return directory_entry_type::directory;
}
if (S_ISBLK(type)) {
return directory_entry_type::block_device;
}
if (S_ISCHR(type)) {
return directory_entry_type::char_device;
}
if (S_ISFIFO(type)) {
return directory_entry_type::fifo;
}
if (S_ISLNK(type)) {
return directory_entry_type::link;
}
return directory_entry_type::regular;
}
future<std::experimental::optional<directory_entry_type>>
reactor::file_type(sstring name) {
return _thread_pool.submit<syscall_result_extra<struct stat>>([name] {
struct stat st;
auto ret = stat(name.c_str(), &st);
return wrap_syscall(ret, st);
}).then([] (syscall_result_extra<struct stat> sr) {
if (long(sr.result) == -1) {
if (sr.result != ENOENT && sr.result != ENOTDIR) {
sr.throw_if_error();
}
return make_ready_future<std::experimental::optional<directory_entry_type> >
(std::experimental::optional<directory_entry_type>() );
}
return make_ready_future<std::experimental::optional<directory_entry_type> >
(std::experimental::optional<directory_entry_type>(stat_to_entry_type(sr.extra.st_mode)) );
});
}
future<file>
reactor::open_directory(sstring name) {
return _thread_pool.submit<syscall_result<int>>([name] {

View File

@@ -749,6 +749,7 @@ public:
future<file> open_file_dma(sstring name, open_flags flags);
future<file> open_directory(sstring name);
future<std::experimental::optional<directory_entry_type>> file_type(sstring name);
template <typename Func>
future<io_event> submit_io(Func prepare_io);

View File

@@ -209,6 +209,63 @@ public:
return npos;
}
/**
* find_last_of find the last occurrence of c in the string.
* When pos is specified, the search only includes characters
* at or before position pos.
*
*/
size_t find_last_of (char_type c, size_t pos = npos) const noexcept {
const char_type* str_start = str();
if (size()) {
if (pos >= size()) {
pos = size() - 1;
}
const char_type* p = str_start + pos + 1;
do {
p--;
if (*p == c) {
return (p - str_start);
}
} while (p != str_start);
}
return npos;
}
/**
* Append a C substring.
* @param s The C string to append.
* @param n The number of characters to append.
* @return Reference to this string.
*/
basic_sstring& append (const char_type* s, size_t n) {
basic_sstring ret(initialized_later(), size() + n);
std::copy(begin(), end(), ret.begin());
std::copy(s, s + n, ret.begin() + size());
*this = ret;
return *this;
}
/**
* Returns a read/write reference to the data at the last
* element of the string.
* This function shall not be called on empty strings.
*/
reference
back() noexcept {
return operator[](size() - 1);
}
/**
* Returns a read-only (constant) reference to the data at the last
* element of the string.
* This function shall not be called on empty strings.
*/
const_reference
back() const noexcept {
return operator[](size() - 1);
}
basic_sstring substr(size_t from, size_t len = npos) const {
if (from > size()) {
throw std::out_of_range("sstring::substr out of range");

129
http/file_handler.cc Normal file
View File

@@ -0,0 +1,129 @@
/*
* This file is open source software, licensed to you under the terms
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
* distributed with this work for additional information regarding copyright
* ownership. You may not use this file except in compliance with the License.
*
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Copyright 2015 Cloudius Systems
*/
#include "file_handler.hh"
#include <algorithm>
#include <iostream>
#include "core/reactor.hh"
#include "core/fstream.hh"
#include "core/shared_ptr.hh"
#include "core/app-template.hh"
#include "exception.hh"
namespace httpd {
directory_handler::directory_handler(const sstring& doc_root,
file_transformer* transformer)
: file_interaction_handler(transformer), doc_root(doc_root) {
}
future<std::unique_ptr<reply>> directory_handler::handle(const sstring& path,
std::unique_ptr<request> req, std::unique_ptr<reply> rep) {
sstring full_path = doc_root + req->param["path"];
auto h = this;
return engine().file_type(full_path).then(
[h, full_path, req = std::move(req), rep = std::move(rep)](auto val) mutable {
if (val) {
if (val.value() == directory_entry_type::directory) {
if (h->redirect_if_needed(*req.get(), *rep.get())) {
return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
}
full_path += "/index.html";
}
return h->read(full_path, std::move(req), std::move(rep));
}
rep->set_status(reply::status_type::not_found).done();
return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
});
}
file_interaction_handler::~file_interaction_handler() {
delete transformer;
}
sstring file_interaction_handler::get_extension(const sstring& file) {
size_t last_slash_pos = file.find_last_of('/');
size_t last_dot_pos = file.find_last_of('.');
sstring extension;
if (last_dot_pos != sstring::npos && last_dot_pos > last_slash_pos) {
extension = file.substr(last_dot_pos + 1);
}
return extension;
}
struct reader {
reader(file f, std::unique_ptr<reply> rep)
: is(
make_file_input_stream(make_lw_shared<file>(std::move(f)),
0, 4096)), _rep(std::move(rep)) {
}
input_stream<char> is;
std::unique_ptr<reply> _rep;
// for input_stream::consume():
template<typename Done>
void operator()(temporary_buffer<char> data, Done&& done) {
if (data.empty()) {
done(std::move(data));
_rep->done();
} else {
_rep->_content.append(data.get(), data.size());
}
}
};
future<std::unique_ptr<reply>> file_interaction_handler::read(
const sstring& file_name, std::unique_ptr<request> req,
std::unique_ptr<reply> rep) {
sstring extension = get_extension(file_name);
rep->set_content_type(extension);
return engine().open_file_dma(file_name, open_flags::ro).then(
[rep = std::move(rep)](file f) mutable {
std::shared_ptr<reader> r = std::make_shared<reader>(std::move(f), std::move(rep));
return r->is.consume(*r).then([r]() {
r->_rep->done();
return make_ready_future<std::unique_ptr<reply>>(std::move(r->_rep));
});
});
}
bool file_interaction_handler::redirect_if_needed(const request& req,
reply& rep) const {
if (req._url.length() == 0 || req._url.back() != '/') {
rep.set_status(reply::status_type::moved_permanently);
rep._headers["Location"] = req.get_url() + "/";
rep.done();
return true;
}
return false;
}
future<std::unique_ptr<reply>> file_handler::handle(const sstring& path,
std::unique_ptr<request> req, std::unique_ptr<reply> rep) {
if (force_path && redirect_if_needed(*req.get(), *rep.get())) {
return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
}
return read(file, std::move(req), std::move(rep));
}
}

161
http/file_handler.hh Normal file
View File

@@ -0,0 +1,161 @@
/*
* This file is open source software, licensed to you under the terms
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
* distributed with this work for additional information regarding copyright
* ownership. You may not use this file except in compliance with the License.
*
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Copyright 2015 Cloudius Systems
*/
#ifndef HTTP_FILE_HANDLER_HH_
#define HTTP_FILE_HANDLER_HH_
#include "handlers.hh"
namespace httpd {
/**
* This is a base class for file transformer.
*
* File transformer adds the ability to modify a file content before returning
* the results.
*
* The transformer decides according to the file extension if transforming is
* needed.
*/
class file_transformer {
public:
/**
* Any file transformer should implement this method.
* @param content the content to transform
* @param req the request
* @param extension the file extension originating the content
*/
virtual void transform(sstring& content, const request& req,
const sstring& extension) = 0;
virtual ~file_transformer() = default;
};
/**
* A base class for handlers that interact with files.
* directory and file handlers both share some common logic
* with regards to file handling.
* they both needs to read a file from the disk, optionally transform it,
* and return the result or page not found on error
*/
class file_interaction_handler : public handler_base {
public:
file_interaction_handler(file_transformer* p = nullptr)
: transformer(p) {
}
~file_interaction_handler();
/**
* Allows setting a transformer to be used with the files returned.
* @param t the file transformer to use
* @return this
*/
file_interaction_handler* set_transformer(file_transformer* t) {
transformer = t;
return this;
}
/**
* if the url ends without a slash redirect
* @param req the request
* @param rep the reply
* @return true on redirect
*/
bool redirect_if_needed(const request& req, reply& rep) const;
/**
* A helper method that returns the file extension.
* @param file the file to check
* @return the file extension
*/
static sstring get_extension(const sstring& file);
protected:
/**
* read a file from the disk and return it in the replay.
* @param file the full path to a file on the disk
* @param req the reuest
* @param rep the reply
*/
future<std::unique_ptr<reply> > read(const sstring& file,
std::unique_ptr<request> req, std::unique_ptr<reply> rep);
file_transformer* transformer;
};
/**
* The directory handler get a disk path in the
* constructor.
* and expect a path parameter in the handle method.
* it would concatenate the two and return the file
* e.g. if the path is /usr/mgmt/public in the path
* parameter is index.html
* handle will return the content of /usr/mgmt/public/index.html
*/
class directory_handler : public file_interaction_handler {
public:
/**
* The directory handler map a base path and a path parameter to a file
* @param doc_root the root directory to search the file from.
* For example if the root is '/usr/mgmt/public' and the path parameter
* will be '/css/style.css' the file wil be /usr/mgmt/public/css/style.css'
*/
explicit directory_handler(const sstring& doc_root,
file_transformer* transformer = nullptr);
future<std::unique_ptr<reply>> handle(const sstring& path,
std::unique_ptr<request> req, std::unique_ptr<reply> rep) override;
private:
sstring doc_root;
};
/**
* The file handler get a path to a file on the disk
* in the constructor.
* it will always return the content of the file.
*/
class file_handler : public file_interaction_handler {
public:
/**
* The file handler map a file to a url
* @param file the full path to the file on the disk
*/
explicit file_handler(const sstring& file, file_transformer* transformer =
nullptr, bool force_path = true)
: file_interaction_handler(transformer), file(file), force_path(
force_path) {
}
future<std::unique_ptr<reply>> handle(const sstring& path,
std::unique_ptr<request> req, std::unique_ptr<reply> rep) override;
private:
sstring file;
bool force_path;
};
}
#endif /* HTTP_FILE_HANDLER_HH_ */

86
http/function_handlers.hh Normal file
View File

@@ -0,0 +1,86 @@
/*
* This file is open source software, licensed to you under the terms
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
* distributed with this work for additional information regarding copyright
* ownership. You may not use this file except in compliance with the License.
*
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Copyright 2015 Cloudius Systems
*/
#pragma once
#include "handlers.hh"
#include <functional>
#include "json/json_elements.hh"
namespace httpd {
/**
* A request function is a lambda expression that gets only the request
* as its parameter
*/
typedef std::function<sstring(const_req req)> request_function;
/**
* A handle function is a lambda expression that gets request and reply
*/
typedef std::function<sstring(const_req req, reply&)> handle_function;
/**
* A json request function is a lambda expression that gets only the request
* as its parameter and return a json response.
* Using the json response is done implicitly.
*/
typedef std::function<json::json_return_type(const_req req)> json_request_function;
/**
* The function handler get a lambda expression in the constructor.
* it will call that expression to get the result
* This is suited for very simple handlers
*
*/
class function_handler : public handler_base {
public:
function_handler(const handle_function & f_handle, const sstring& type)
: _f_handle(f_handle), _type(type) {
}
function_handler(const request_function & _handle, const sstring& type)
: _f_handle([_handle](const_req req, reply& rep) {
return _handle(req);
}), _type(type) {
}
function_handler(const json_request_function& _handle)
: _f_handle([_handle](const_req req, reply& rep) {
json::json_return_type res = _handle(req);
return res._res;
}), _type("json") {
}
future<std::unique_ptr<reply>> handle(const sstring& path,
std::unique_ptr<request> req, std::unique_ptr<reply> rep) override {
rep->_content += _f_handle(*req.get(), *rep.get());
rep->done(_type);
return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
}
protected:
handle_function _f_handle;
sstring _type;
};
}

View File

@@ -25,6 +25,7 @@
#include "request.hh"
#include "common.hh"
#include "reply.hh"
#include "core/future-util.hh"
#include <unordered_map>
@@ -48,8 +49,8 @@ public:
* @param req the original request
* @param rep the reply
*/
virtual void handle(const sstring& path, parameters* params,
httpd::const_req& req, httpd::reply& rep) = 0;
virtual future<std::unique_ptr<reply> > handle(const sstring& path,
std::unique_ptr<request> req, std::unique_ptr<reply> rep) = 0;
virtual ~handler_base() = default;

View File

@@ -26,6 +26,7 @@
#include "http/request.hh"
#include "core/reactor.hh"
#include "core/sstring.hh"
#include <experimental/string_view>
#include "core/app-template.hh"
#include "core/circular_buffer.hh"
#include "core/distributed.hh"
@@ -86,7 +87,7 @@ public:
try {
f.get();
} catch (std::exception& ex) {
std::cout << "request error " << ex.what() << "\n";
std::cerr << "request error " << ex.what() << std::endl;
}
});
do_accepts(which);
@@ -94,7 +95,7 @@ public:
try {
f.get();
} catch (std::exception& ex) {
std::cout << "accept failed: " << ex.what() << "\n";
std::cerr << "accept failed: " << ex.what() << std::endl;
}
});
}
@@ -140,16 +141,19 @@ public:
}
future<> read_one() {
_parser.init();
return _read_buf.consume(_parser).then([this] {
return _read_buf.consume(_parser).then([this] () mutable {
if (_parser.eof()) {
_done = true;
return make_ready_future<>();
}
++_server._requests_served;
_req = _parser.get_parsed_request();
return _replies.not_full().then([this] {
_done = generate_reply(std::move(_req));
});
std::unique_ptr<httpd::request> req = _parser.get_parsed_request();
return _replies.not_full().then([req = std::move(req), this] () mutable {
return generate_reply(std::move(req));
}).then([this](bool done) {
_done = done;
});
});
}
future<> respond() {
@@ -199,7 +203,92 @@ public:
return write_reply_headers(++hi);
});
}
bool generate_reply(std::unique_ptr<request> req) {
static short hex_to_byte(char c) {
if (c >='a' && c <= 'z') {
return c - 'a' + 10;
} else if (c >='A' && c <= 'Z') {
return c - 'A' + 10;
}
return c - '0';
}
/**
* Convert a hex encoded 2 bytes substring to char
*/
static char hexstr_to_char(const std::experimental::string_view& in, size_t from) {
return static_cast<char>(hex_to_byte(in[from]) * 16 + hex_to_byte(in[from + 1]));
}
/**
* URL_decode a substring and place it in the given out sstring
*/
static bool url_decode(const std::experimental::string_view& in, sstring& out) {
size_t pos = 0;
char buff[in.length()];
for (size_t i = 0; i < in.length(); ++i) {
if (in[i] == '%') {
if (i + 3 <= in.size()) {
buff[pos++] = hexstr_to_char(in, i + 1);
i += 2;
} else {
return false;
}
} else if (in[i] == '+') {
buff[pos++] = ' ';
} else {
buff[pos++] = in[i];
}
}
out = sstring(buff, pos);
return true;
}
/**
* Add a single query parameter to the parameter list
*/
static void add_param(request& req, const std::experimental::string_view& param) {
size_t split = param.find('=');
if (split >= param.length() - 1) {
sstring key;
if (url_decode(param.substr(0,split) , key)) {
req.query_parameters[key] = "";
}
} else {
sstring key;
sstring value;
if (url_decode(param.substr(0,split), key)
&& url_decode(param.substr(split + 1), value)) {
req.query_parameters[key] = value;
}
}
}
/**
* Set the query parameters in the request objects.
* query param appear after the question mark and are separated
* by the ampersand sign
*/
static sstring set_query_param(request& req) {
size_t pos = req._url.find('?');
if (pos == sstring::npos) {
return req._url;
}
size_t curr = pos + 1;
size_t end_param;
std::experimental::string_view url = req._url;
while ((end_param = req._url.find('&', curr)) != sstring::npos) {
add_param(req, url.substr(curr, end_param - curr) );
curr = end_param + 1;
}
add_param(req, url.substr(pos + 1));
return req._url.substr(0, pos);
}
future<bool> generate_reply(std::unique_ptr<request> req) {
auto resp = std::make_unique<reply>();
bool conn_keep_alive = false;
bool conn_close = false;
@@ -226,10 +315,13 @@ public:
// HTTP/0.9 goes here
should_close = true;
}
_server._routes.handle(req->_url, *(req.get()), *(resp.get()));
sstring url = set_query_param(*req.get());
return _server._routes.handle(url, std::move(req), std::move(resp)).
// Caller guarantees enough room
_replies.push(std::move(resp));
return should_close;
then([this, should_close](std::unique_ptr<reply> rep) {
this->_replies.push(std::move(rep));
return make_ready_future<bool>(should_close);
});
}
future<> write_body() {
return _write_buf.write(_resp->_content.begin(),

65
http/json_path.cc Normal file
View File

@@ -0,0 +1,65 @@
/*
* This file is open source software, licensed to you under the terms
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
* distributed with this work for additional information regarding copyright
* ownership. You may not use this file except in compliance with the License.
*
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Copyright 2015 Cloudius Systems
*/
#include "json_path.hh"
namespace httpd {
using namespace std;
void path_description::set(routes& _routes, handler_base* handler) const {
for (auto& i : mandatory_queryparams) {
handler->mandatory(i);
}
if (params.size() == 0)
_routes.put(operations.method, path, handler);
else {
match_rule* rule = new match_rule(handler);
rule->add_str(path);
for (auto i = params.begin(); i != params.end(); ++i) {
rule->add_param(std::get<0>(*i), std::get<1>(*i));
}
_routes.add(rule, operations.method);
}
}
void path_description::set(routes& _routes,
const json_request_function& f) const {
set(_routes, new function_handler(f));
}
path_description::path_description(const sstring& path, operation_type method,
const sstring& nickname,
const std::vector<std::pair<sstring, bool>>& path_parameters,
const std::vector<sstring>& mandatory_params)
: path(path), operations(method, nickname) {
for (auto man : mandatory_params) {
pushmandatory_param(man);
}
for (auto param : path_parameters) {
params.push_back(param);
}
}
}

135
http/json_path.hh Normal file
View File

@@ -0,0 +1,135 @@
/*
* This file is open source software, licensed to you under the terms
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
* distributed with this work for additional information regarding copyright
* ownership. You may not use this file except in compliance with the License.
*
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Copyright 2015 Cloudius Systems
*/
#ifndef JSON_PATH_HH_
#define JSON_PATH_HH_
#include <vector>
#include <unordered_map>
#include <tuple>
#include "common.hh"
#include "core/sstring.hh"
#include "routes.hh"
#include "function_handlers.hh"
namespace httpd {
/**
* A json_operation contain a method and a nickname.
* operation are associated to a path, that can
* have multiple methods
*/
struct json_operation {
/**
* default constructor
*/
json_operation()
: method(GET) {
}
/**
* Construct with assignment
* @param method the http method type
* @param nickname the http nickname
*/
json_operation(operation_type method, const sstring& nickname)
: method(method), nickname(nickname) {
}
operation_type method;
sstring nickname;
};
/**
* path description holds the path in the system.
* It maps a nickname to an operation, which allows
* defining the operation (path and method) by its
* nickname.
*
* the description are taken from the json swagger
* definition file, during auto code generation in the
* compilation.
*/
struct path_description {
/**
* default empty constructor
*/
path_description() = default;
/**
* constructor for path with parameters
* The constructor is used by
* @param path the url path
* @param method the http method
* @param nickname the nickname
*/
path_description(const sstring& path, operation_type method,
const sstring& nickname,
const std::vector<std::pair<sstring, bool>>& path_parameters,
const std::vector<sstring>& mandatory_params);
/**
* Add a parameter to the path definition
* for example, if the url should match /file/{path}
* The constructor would be followed by a call to
* pushparam("path")
*
* @param param the name of the parameters, this name will
* be used by the handler to identify the parameters.
* A name can appear at most once in a description
* @param all_path when set to true the parameter will assume to match
* until the end of the url.
* This is useful for situation like file path with
* a rule like /file/{path} and a url /file/etc/hosts.
* path should be equal to /ets/hosts and not only /etc
* @return the current path description
*/
path_description* pushparam(const sstring& param,
bool all_path = false) {
params.push_back( { param, all_path });
return this;
}
/**
* adds a mandatory query parameter to the path
* this parameter will be check before calling a handler
* @param param the parameter to head
* @return a pointer to the current path description
*/
path_description* pushmandatory_param(const sstring& param) {
mandatory_queryparams.push_back(param);
return this;
}
std::vector<std::pair<sstring, bool>> params;
sstring path;
json_operation operations;
std::vector<sstring> mandatory_queryparams;
void set(routes& _routes, handler_base* handler) const;
void set(routes& _routes, const json_request_function& f) const;
};
}
#endif /* JSON_PATH_HH_ */

View File

@@ -93,7 +93,7 @@ struct request {
* Get the request protocol name. Can be either "http" or "https".
*/
sstring get_protocol_name() const {
return protocol_name;
return "http";
}
/**

View File

@@ -89,7 +89,7 @@ http_version = 'HTTP/' (digit '.' digit) >mark %store_version;
field = tchar+ >mark %store_field_name;
value = any* >mark %store_value;
start_line = ((operation sp uri sp http_version) -- crlf) crlf;
header_1st = (field sp_ht* ':' value :> crlf) %assign_field;
header_1st = (field sp_ht* ':' sp_ht* value :> crlf) %assign_field;
header_cont = (sp_ht+ value sp_ht* crlf) %extend_field;
header = header_1st header_cont*;
main := start_line header* :> (crlf @done);

View File

@@ -47,35 +47,41 @@ routes::~routes() {
}
void routes::handle(const sstring& path, request& req, reply& rep) {
handler_base* handler = get_handler(str2type(req._method),
normalize_url(path), req.param);
future<std::unique_ptr<reply> > routes::handle(const sstring& path, std::unique_ptr<request> req, std::unique_ptr<reply> rep) {
handler_base* handler = get_handler(str2type(req->_method),
normalize_url(path), req->param);
if (handler != nullptr) {
try {
for (auto& i : handler->_mandatory_param) {
verify_param(req, i);
verify_param(*req.get(), i);
}
handler->handle(path, &req.param, req, rep);
auto r = handler->handle(path, std::move(req), std::move(rep));
return r;
} catch (const redirect_exception& _e) {
rep.add_header("Location", _e.url).set_status(_e.status()).done(
rep.reset(new reply());
rep->add_header("Location", _e.url).set_status(_e.status()).done(
"json");
return;
} catch (const base_exception& _e) {
rep.reset(new reply());
json_exception e(_e);
rep.set_status(_e.status(), e.to_json()).done("json");
rep->set_status(_e.status(), e.to_json()).done("json");
} catch (exception& _e) {
rep.reset(new reply());
json_exception e(_e);
cerr << "exception was caught for " << path << ": " << _e.what()
<< endl;
rep.set_status(reply::status_type::internal_server_error,
rep->set_status(reply::status_type::internal_server_error,
e.to_json()).done("json");
return;
}
} else {
rep.reset(new reply());
json_exception ex(not_found_exception("Not found"));
rep.set_status(reply::status_type::not_found, ex.to_json()).done(
rep->set_status(reply::status_type::not_found, ex.to_json()).done(
"json");
}
cerr << "Failed with " << path << " " << rep->_content << endl;
return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
}
sstring routes::normalize_url(const sstring& url) {
@@ -107,7 +113,9 @@ routes& routes::add(operation_type type, const url& url,
handler_base* handler) {
match_rule* rule = new match_rule(handler);
rule->add_str(url._path);
rule->add_param(url._param, true);
if (url._param != "") {
rule->add_param(url._param, true);
}
return add(rule, type);
}

View File

@@ -30,6 +30,7 @@
#include <boost/program_options/variables_map.hpp>
#include <unordered_map>
#include <vector>
#include "core/future-util.hh"
namespace httpd {
@@ -125,7 +126,7 @@ public:
* @param req the http request
* @param rep the http reply
*/
void handle(const sstring& path, httpd::request& req, httpd::reply& rep);
future<std::unique_ptr<reply> > handle(const sstring& path, std::unique_ptr<request> req, std::unique_ptr<reply> rep);
private:

410
json/json2code.py Executable file
View File

@@ -0,0 +1,410 @@
#!/usr/bin/env python
import json
import sys
import re
import glob
import argparse
import os
parser = argparse.ArgumentParser(description="""Generate C++ class for json
handling from swagger definition""")
parser.add_argument('--outdir', help='the output directory', default='autogen')
parser.add_argument('-o', help='Output file', default='')
parser.add_argument('-f', help='input file', default='api-java.json')
parser.add_argument('-ns', help="""namespace when set struct will be created
under the namespace""", default='')
parser.add_argument('-jsoninc', help='relative path to the jsaon include',
default='json/')
parser.add_argument('-jsonns', help='set the json namespace', default='json')
parser.add_argument('-indir', help="""when set all json file in the given
directory will be parsed, do not use with -f""", default='')
parser.add_argument('-debug', help='debug level 0 -quite,1-error,2-verbose',
default='1', type=int)
parser.add_argument('-combined', help='set the name of the combined file',
default='autogen/pathautogen.ee')
config = parser.parse_args()
valid_vars = {'string': 'sstring', 'int': 'int', 'double': 'double',
'float': 'float', 'long': 'long', 'boolean': 'bool', 'char': 'char',
'datetime': 'json::date_time'}
current_file = ''
spacing = " "
def getitem(d, key, name):
if key in d:
return d[key]
else:
raise Exception("'" + key + "' not found in " + name)
def fprint(f, *args):
for arg in args:
f.write(arg)
def fprintln(f, *args):
for arg in args:
f.write(arg)
f.write('\n')
def open_namespace(f, ns=config.ns):
fprintln(f, "namespace ", ns , ' {\n')
def close_namespace(f):
fprintln(f, '}')
def add_include(f, includes):
for include in includes:
fprintln(f, '#include ', include)
fprintln(f, "")
def trace_verbose(*params):
if config.debug > 1:
print(''.join(params))
def trace_err(*params):
if config.debug > 0:
print(current_file + ':' + ''.join(params))
def valid_type(param):
if param in valid_vars:
return valid_vars[param]
trace_err("Type [", param, "] not defined")
return param
def type_change(param, member):
if param == "array":
if "items" not in member:
trace_err("array without item declaration in ", param)
return ""
item = member["items"]
if "type" in item:
t = item["type"]
elif "$ref" in item:
t = item["$ref"]
else:
trace_err("array items with no type or ref declaration ", param)
return ""
return "json_list< " + valid_type(t) + " >"
return "json_element< " + valid_type(param) + " >"
def print_ind_comment(f, ind, *params):
fprintln(f, ind, "/**")
for s in params:
fprintln(f, ind, " * ", s)
fprintln(f, ind, " */")
def print_comment(f, *params):
print_ind_comment(f, spacing, *params)
def print_copyrights(f):
fprintln(f, "/*")
fprintln(f, "* Copyright (C) 2014 Cloudius Systems, Ltd.")
fprintln(f, "*")
fprintln(f, "* This work is open source software, licensed under the",
" terms of the")
fprintln(f, "* BSD license as described in the LICENSE f in the top-",
"level directory.")
fprintln(f, "*")
fprintln(f, "* This is an Auto-Generated-code ")
fprintln(f, "* Changes you do in this file will be erased on next",
" code generation")
fprintln(f, "*/\n")
def print_h_file_headers(f, name):
print_copyrights(f)
fprintln(f, "#ifndef __JSON_AUTO_GENERATED_" + name)
fprintln(f, "#define __JSON_AUTO_GENERATED_" + name + "\n")
def clean_param(param):
match = re.match(r"(^[^\}]+)\s*}", param)
if match:
return match.group(1)
return param
def get_parameter_by_name(obj, name):
for p in obj["parameters"]:
if p["name"] == name:
return p
trace_err ("No Parameter declaration found for ", name)
def clear_path_ending(path):
if not path or path[-1] != '/':
return path
return path[0:-1]
def add_path(f, path, details):
if "summary" in details:
print_comment(f, details["summary"])
if "{" in path:
vals = path.split("{")
vals.reverse()
fprintln(f, spacing, 'path_description::add_path("', clear_path_ending(vals.pop()),
'",', details["method"], ',"', details["nickname"], '")')
while vals:
param = clean_param(vals.pop())
param_type = get_parameter_by_name(details, param)
if ("allowMultiple" in param_type and
param_type["allowMultiple"] == True):
fprintln(f, spacing, ' ->pushparam("', param, '",true)')
else:
fprintln(f, spacing, ' ->pushparam("', param, '")')
else:
fprintln(f, spacing, 'path_description::add_path("', clear_path_ending(path), '",',
details["method"], ',"', details["nickname"], '")')
if "parameters" in details:
for param in details["parameters"]:
if "required" in param and param["required"] and param["paramType"] == "query":
fprintln(f, spacing, ' ->pushmandatory_param("', param["name"], '")')
fprintln(f, spacing, ";")
def get_base_name(param):
return os.path.basename(param)
def is_model_valid(name, model):
if name in valid_vars:
return ""
properties = getitem(model[name], "properties", name)
for var in properties:
type = getitem(properties[var], "type", name + ":" + var)
if type == "array":
type = getitem(getitem(properties[var], "items", name + ":" + var), "type", name + ":" + var + ":items")
if type not in valid_vars:
if type not in model:
raise Exception("Unknown type '" + type + "' in Model '" + name + "'")
return type
valid_vars[name] = name
return ""
def resolve_model_order(data):
res = []
models = set()
for model_name in data:
visited = set(model_name)
missing = is_model_valid(model_name, data)
resolved = missing == ''
if not resolved:
stack = [model_name]
while not resolved:
if missing in visited:
raise Exception("Cyclic dependency found: " + missing)
missing_depends = is_model_valid(missing, data)
if missing_depends == '':
if missing not in models:
res.append(missing)
models.add(missing)
resolved = len(stack) == 0
if not resolved:
missing = stack.pop()
else:
stack.append(missing)
missing = missing_depends
elif model_name not in models:
res.append(model_name)
models.add(model_name)
return res
def create_h_file(data, hfile_name, api_name, init_method, base_api):
if config.o != '':
hfile = open(config.o, "w")
else:
hfile = open(config.outdir + "/" + hfile_name, "w")
print_h_file_headers(hfile, api_name)
add_include(hfile, ['"core/sstring.hh"', '"' + config.jsoninc +
'json_elements.hh"', '"http/json_path.hh"'])
add_include(hfile, ['<iostream>'])
open_namespace(hfile, "httpd")
open_namespace(hfile, api_name)
if "models" in data:
models_order = resolve_model_order(data["models"])
for model_name in models_order:
model = data["models"][model_name]
if 'description' in model:
print_ind_comment(hfile, "", model["description"])
fprintln(hfile, "struct ", model_name, " : public json::json_base {")
member_init = ''
member_assignment = ''
member_copy = ''
for member_name in model["properties"]:
member = model["properties"][member_name]
if "description" in member:
print_comment(hfile, member["description"])
if "enum" in member:
enum_name = model_name + "_" + member_name
fprintln(hfile, " enum class ", enum_name, " {")
for enum_entry in member["enum"]:
fprintln(hfile, " ", enum_entry, ", ")
fprintln(hfile, "NUM_ITEMS};")
wrapper = member_name + "_wrapper"
fprintln(hfile, " struct ", wrapper, " : public jsonable {")
fprintln(hfile, " ", wrapper, "() = default;")
fprintln(hfile, " virtual std::string to_json() const {")
fprintln(hfile, " switch(v) {")
for enum_entry in member["enum"]:
fprintln(hfile, " case ", enum_name, "::", enum_entry, ": return \"\\\"", enum_entry, "\\\"\";")
fprintln(hfile, " default: return \"Unknown\";")
fprintln(hfile, " }")
fprintln(hfile, " }")
fprintln(hfile, " template<class T>")
fprintln(hfile, " ", wrapper, "(const T& _v) {")
fprintln(hfile, " switch(_v) {")
for enum_entry in member["enum"]:
fprintln(hfile, " case T::", enum_entry, ": v = ", enum_name, "::", enum_entry, "; break;")
fprintln(hfile, " default: v = ", enum_name, "::NUM_ITEMS;")
fprintln(hfile, " }")
fprintln(hfile, " }")
fprintln(hfile, " ", enum_name, " v;")
fprintln(hfile, " };")
fprintln(hfile, " ", config.jsonns, "::json_element<",
member_name, "_wrapper> ",
member_name, ";\n")
else:
fprintln(hfile, " ", config.jsonns, "::",
type_change(member["type"], member), " ",
member_name, ";\n")
member_init += " add(&" + member_name + ',"'
member_init += member_name + '");\n'
member_assignment += " " + member_name + " = " + "e." + member_name + ";\n"
member_copy += " e." + member_name + " = " + member_name + ";\n"
fprintln(hfile, "void register_params() {")
fprintln(hfile, member_init)
fprintln(hfile, '}')
fprintln(hfile, model_name, '() {')
fprintln(hfile, ' register_params();')
fprintln(hfile, '}')
fprintln(hfile, model_name, '(const ' + model_name + ' & e) {')
fprintln(hfile, ' register_params();')
fprintln(hfile, member_assignment)
fprintln(hfile, '}')
fprintln(hfile, "template<class T>")
fprintln(hfile, model_name, "& operator=(const ", "T& e) {")
fprintln(hfile, member_assignment)
fprintln(hfile, " return *this;")
fprintln(hfile, "}")
fprintln(hfile, model_name, "& operator=(const ", model_name, "& e) {")
fprintln(hfile, member_assignment)
fprintln(hfile, " return *this;")
fprintln(hfile, "}")
fprintln(hfile, "template<class T>")
fprintln(hfile, model_name, "& update(T& e) {")
fprintln(hfile, member_copy)
fprintln(hfile, " return *this;")
fprintln(hfile, "}")
fprintln(hfile, "};\n\n")
# print_ind_comment(hfile, "", "Initialize the path")
# fprintln(hfile, init_method + "(const std::string& description);")
fprintln(hfile, 'static const sstring name = "', base_api, '";')
for item in data["apis"]:
path = item["path"]
if "operations" in item:
for oper in item["operations"]:
if "summary" in oper:
print_comment(hfile, oper["summary"])
vals = path.split("{")
vals.reverse()
fprintln(hfile, 'static const path_description ', getitem(oper, "nickname", oper), '("', clear_path_ending(vals.pop()),
'",', oper["method"], ',"', oper["nickname"], '",')
fprint(hfile, '{')
first = True
while vals:
path_param = clean_param(vals.pop())
path_param_type = get_parameter_by_name(oper, path_param)
if first == True:
first = False
else:
fprint(hfile, "\n,")
if ("allowMultiple" in path_param_type and
path_param_type["allowMultiple"] == True):
fprint(hfile, '{', '"', path_param , '", true', '}')
else:
fprint(hfile, '{', '"', path_param , '", false', '}')
fprint(hfile, '}')
fprint(hfile, ',{')
first = True
if "parameters" in oper:
enum_definitions = ""
for param in oper["parameters"]:
if "required" in param and param["required"] and param["paramType"] == "query":
if first == True:
first = False
else:
fprint(hfile, "\n,")
fprint(hfile, '"', param["name"], '"')
if "enum" in param:
enum_definitions = enum_definitions + 'namespace ns_' + oper["nickname"] + '{\n'
enm = param["name"]
enum_definitions = enum_definitions + 'enum class ' + enm + ' {'
for val in param["enum"]:
enum_definitions = enum_definitions + val + ", "
enum_definitions = enum_definitions + 'NUM_ITEMS};\n'
enum_definitions = enum_definitions + enm + ' str2' + enm + '(const sstring& str) {\n'
enum_definitions = enum_definitions + ' static const sstring arr[] = {"' + '","'.join(param["enum"]) + '"};\n'
enum_definitions = enum_definitions + ' int i;\n'
enum_definitions = enum_definitions + ' for (i=0; i < ' + str(len(param["enum"])) + '; i++) {\n'
enum_definitions = enum_definitions + ' if (arr[i] == str) {return (' + enm + ')i;}\n}\n'
enum_definitions = enum_definitions + ' return (' + enm + ')i;\n'
enum_definitions = enum_definitions + '}\n}\n'
fprintln(hfile, '});')
fprintln(hfile, enum_definitions)
close_namespace(hfile)
close_namespace(hfile)
hfile.write("#endif //__JSON_AUTO_GENERATED_HEADERS\n")
hfile.close()
def parse_file(param, combined):
global current_file
trace_verbose("parsing ", param, " file")
try:
json_data = open(param)
data = json.load(json_data)
json_data.close()
except:
type, value, tb = sys.exc_info()
print("Bad formatted JSON file '" + param + "' error ", value.message)
sys.exit(-1)
try:
base_file_name = get_base_name(param)
current_file = base_file_name
hfile_name = base_file_name + ".hh"
api_name = base_file_name.replace('.', '_')
base_api = base_file_name.replace('.json', '')
init_method = "void " + api_name + "_init_path"
trace_verbose("creating ", hfile_name)
if (combined):
fprintln(combined, '#include "', base_file_name, ".cc", '"')
create_h_file(data, hfile_name, api_name, init_method, base_api)
except:
type, value, tb = sys.exc_info()
print("Error while parsing JSON file '" + param + "' error ", value.message)
sys.exit(-1)
if "indir" in config and config.indir != '':
combined = open(config.combined, "w")
for f in glob.glob(os.path.join(config.indir, "*.json")):
parse_file(f, combined)
else:
parse_file(config.f, None)

View File

@@ -7,6 +7,7 @@
#include <boost/test/included/unit_test.hpp>
#include "http/httpd.hh"
#include "http/handlers.hh"
#include "http/matcher.hh"
#include "http/matchrules.hh"
@@ -18,10 +19,10 @@ using namespace httpd;
class handl : public httpd::handler_base {
public:
virtual void handle(const sstring& path, parameters* params,
httpd::const_req& req, httpd::reply& rep)
{
virtual future<std::unique_ptr<reply> > handle(const sstring& path,
std::unique_ptr<request> req, std::unique_ptr<reply> rep) {
rep->done("html");
return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
}
};
@@ -77,24 +78,53 @@ BOOST_AUTO_TEST_CASE(test_formatter)
}
BOOST_AUTO_TEST_CASE(test_decode_url) {
request req;
req._url = "/a?q=%23%24%23";
sstring url = http_server::connection::set_query_param(req);
BOOST_REQUIRE_EQUAL(url, "/a");
BOOST_REQUIRE_EQUAL(req.get_query_param("q"), "#$#");
}
BOOST_AUTO_TEST_CASE(test_routes)
{
BOOST_AUTO_TEST_CASE(test_routes) {
handl* h1 = new handl();
handl* h2 = new handl();
routes route;
route.add(operation_type::GET, url("/api").remainder("path"), h1);
route.add(operation_type::GET, url("/"), h2);
request req;
reply rep;
BOOST_CHECK_NO_THROW(route.handle("/api", req, rep));
BOOST_REQUIRE_EQUAL((int)rep._status, (int)reply::status_type::ok);
BOOST_REQUIRE_EQUAL(req.param["path"], "");
BOOST_CHECK_NO_THROW(route.handle("/", req, rep));
BOOST_REQUIRE_EQUAL((int)rep._status, (int)reply::status_type::ok);
BOOST_CHECK_NO_THROW(route.handle("/api/abc", req, rep));
BOOST_REQUIRE_EQUAL(req.param["path"], "/abc");
BOOST_CHECK_NO_THROW(route.handle("/ap", req, rep));
BOOST_REQUIRE_EQUAL((int)rep._status, (int)reply::status_type::not_found);
std::unique_ptr<request> req = std::make_unique<request>();
std::unique_ptr<reply> rep = std::make_unique<reply>();
BOOST_CHECK_NO_THROW(
route.handle("/api", std::move(req), std::move(rep)).then(
[&rep](std::unique_ptr<reply> _rep) {
rep = std::move(_rep);
}));
BOOST_REQUIRE_EQUAL((int )rep->_status, (int )reply::status_type::ok);
req.reset(new request);
rep.reset(new reply);
BOOST_CHECK_NO_THROW(
route.handle("/", std::move(req), std::move(rep)).then(
[&rep](std::unique_ptr<reply> _rep) {
rep = std::move(_rep);
}));
BOOST_REQUIRE_EQUAL((int )rep->_status, (int )reply::status_type::ok);
req.reset(new request);
rep.reset(new reply);
BOOST_CHECK_NO_THROW(
route.handle("/api/abc", std::move(req), std::move(rep)).then(
[&rep](std::unique_ptr<reply> _rep) {
rep = std::move(_rep);
}));
req.reset(new request);
rep.reset(new reply);
BOOST_CHECK_NO_THROW(
route.handle("/ap", std::move(req), std::move(rep)).then(
[&rep](std::unique_ptr<reply> _rep) {
rep = std::move(_rep);
}));
BOOST_REQUIRE_EQUAL((int )rep->_status,
(int )reply::status_type::not_found);
}

View File

@@ -75,3 +75,19 @@ BOOST_AUTO_TEST_CASE(test_at_sstring) {
s.at(1) = 'd';
BOOST_REQUIRE_EQUAL(s, "adcde");
}
BOOST_AUTO_TEST_CASE(test_find_last_sstring) {
BOOST_REQUIRE_EQUAL(sstring("ababa").find_last_of('a'), 4);
BOOST_REQUIRE_EQUAL(sstring("ababa").find_last_of('a',5), 4);
BOOST_REQUIRE_EQUAL(sstring("ababa").find_last_of('a',4), 4);
BOOST_REQUIRE_EQUAL(sstring("ababa").find_last_of('a',3), 2);
BOOST_REQUIRE_EQUAL(sstring("ababa").find_last_of('x'), sstring::npos);
BOOST_REQUIRE_EQUAL(sstring("").find_last_of('a'), sstring::npos);
}
BOOST_AUTO_TEST_CASE(test_append) {
BOOST_REQUIRE_EQUAL(sstring("aba").append("1234", 3), "aba123");
BOOST_REQUIRE_EQUAL(sstring("aba").append("1234", 4), "aba1234");
BOOST_REQUIRE_EQUAL(sstring("aba").append("1234", 0), "aba");
}