mirror of
https://github.com/scylladb/scylladb.git
synced 2026-04-24 02:20:37 +00:00
Merge branch 'amnon/future_http1' of github.com:cloudius-systems/seastar-dev
Futurize http server stack, from Amnon.
This commit is contained in:
73
apps/httpd/demo.json
Normal file
73
apps/httpd/demo.json
Normal 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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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] {
|
||||
|
||||
16
configure.py
16
configure.py
@@ -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
|
||||
|
||||
@@ -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] {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
129
http/file_handler.cc
Normal 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
161
http/file_handler.hh
Normal 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
86
http/function_handlers.hh
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
114
http/httpd.hh
114
http/httpd.hh
@@ -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
65
http/json_path.cc
Normal 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
135
http/json_path.hh
Normal 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_ */
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
410
json/json2code.py
Executable 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)
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user