Merge branch 'asio-client'

This commit is contained in:
Tatsuhiro Tsujikawa 2015-03-06 02:46:06 +09:00
commit 2ed47cdd19
60 changed files with 4282 additions and 1559 deletions

View File

@ -1019,7 +1019,7 @@ libnghttp2_asio: High level HTTP/2 C++ library
libnghttp2_asio is C++ library built on top of libnghttp2 and provides
high level abstraction API to build HTTP/2 applications. It depends
on Boost::ASIO library and OpenSSL. Currently libnghttp2_asio
provides server side API.
provides client and server API.
libnghttp2_asio is not built by default. Use ``--enable-asio-lib``
configure flag to build libnghttp2_asio. The required Boost libraries
@ -1035,7 +1035,7 @@ HTTP/2 server looks like this:
.. code-block:: cpp
#include <nghttp2/asio_http2.h>
#include <nghttp2/asio_http2_server.h>
using namespace nghttp2::asio_http2;
using namespace nghttp2::asio_http2::server;
@ -1043,11 +1043,12 @@ HTTP/2 server looks like this:
int main(int argc, char *argv[]) {
http2 server;
server.listen("*", 3000, [](const std::shared_ptr<request> &req,
const std::shared_ptr<response> &res) {
res->write_head(200);
res->end("hello, world");
server.handle("/", [](const request &req, const response &res) {
res.write_head(200);
res.end("hello, world\n");
});
server.listen_and_serve("*", 3000);
}
For more details, see the documentation of libnghttp2_asio.

View File

@ -58,10 +58,18 @@ endif # ENABLE_TINY_NGHTTPD
if ENABLE_ASIO_LIB
noinst_PROGRAMS += asio-sv asio-sv2 asio-sv3
noinst_PROGRAMS += asio-sv asio-sv2 asio-cl
ASIOCPPFLAGS = ${BOOST_CPPFLAGS} ${AM_CPPFLAGS}
ASIOLDADD = $(top_builddir)/src/libnghttp2_asio.la @JEMALLOC_LIBS@
ASIOLDADD = $(top_builddir)/lib/libnghttp2.la \
$(top_builddir)/src/libnghttp2_asio.la @JEMALLOC_LIBS@ \
$(top_builddir)/third-party/libhttp-parser.la \
${BOOST_LDFLAGS} \
${BOOST_ASIO_LIB} \
${BOOST_THREAD_LIB} \
${BOOST_SYSTEM_LIB} \
@OPENSSL_LIBS@ \
@APPLDFLAGS@
asio_sv_SOURCES = asio-sv.cc
asio_sv_CPPFLAGS = ${ASIOCPPFLAGS}
@ -71,9 +79,9 @@ asio_sv2_SOURCES = asio-sv2.cc
asio_sv2_CPPFLAGS = ${ASIOCPPFLAGS}
asio_sv2_LDADD = ${ASIOLDADD}
asio_sv3_SOURCES = asio-sv3.cc
asio_sv3_CPPFLAGS = ${ASIOCPPFLAGS}
asio_sv3_LDADD = ${ASIOLDADD}
asio_cl_SOURCES = asio-cl.cc
asio_cl_CPPFLAGS = ${ASIOCPPFLAGS}
asio_cl_LDADD = ${ASIOLDADD}
endif # ENABLE_ASIO_LIB

120
examples/asio-cl.cc Normal file
View File

@ -0,0 +1,120 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <iostream>
#include <string>
#include <nghttp2/asio_http2_client.h>
using boost::asio::ip::tcp;
using namespace nghttp2::asio_http2;
using namespace nghttp2::asio_http2::client;
void print_header(const header_map &h) {
for (auto &kv : h) {
std::cerr << kv.first << ": " << kv.second.value << "\n";
}
std::cerr << std::endl;
}
void print_header(const response &res) {
std::cerr << "HTTP/2 " << res.status_code() << "\n";
print_header(res.header());
}
void print_header(const request &req) {
auto &uri = req.uri();
std::cerr << req.method() << " " << uri.scheme << "://" << uri.host
<< uri.path;
if (!uri.raw_query.empty()) {
std::cerr << "?" << uri.raw_query;
}
std::cerr << " HTTP/2\n";
print_header(req.header());
}
int main(int argc, char *argv[]) {
try {
boost::asio::io_service io_service;
boost::asio::ssl::context tls_ctx(boost::asio::ssl::context::sslv23);
tls_ctx.set_default_verify_paths();
// disabled to make development easier...
// tls_ctx.set_verify_mode(boost::asio::ssl::verify_peer);
configure_tls_context(tls_ctx);
session sess(io_service, tls_ctx, "localhost", "3000");
sess.on_connect([&sess](tcp::resolver::iterator endpoint_it) {
std::cerr << "connected to " << (*endpoint_it).endpoint() << std::endl;
boost::system::error_code ec;
auto req = sess.submit(ec, "GET", "https://localhost:3000/",
{{"cookie", {"foo=bar", true}}});
if (ec) {
std::cerr << "error: " << ec.message() << std::endl;
return;
}
req->on_response([&sess, req](const response &res) {
std::cerr << "response header was received" << std::endl;
print_header(res);
res.on_data([&sess](const uint8_t *data, std::size_t len) {
std::cerr.write(reinterpret_cast<const char *>(data), len);
std::cerr << std::endl;
});
});
req->on_close([&sess](uint32_t error_code) {
std::cerr << "request done with error_code=" << error_code << std::endl;
});
req->on_push([](const request &push_req) {
std::cerr << "push request was received" << std::endl;
print_header(push_req);
push_req.on_response([](const response &res) {
std::cerr << "push response header was received" << std::endl;
res.on_data([](const uint8_t *data, std::size_t len) {
std::cerr.write(reinterpret_cast<const char *>(data), len);
std::cerr << std::endl;
});
});
});
});
sess.on_error([](const boost::system::error_code &ec) {
std::cerr << "error: " << ec.message() << std::endl;
});
io_service.run();
} catch (std::exception &e) {
std::cerr << "exception: " << e.what() << "\n";
}
return 0;
}

View File

@ -37,7 +37,7 @@
#include <iostream>
#include <string>
#include <nghttp2/asio_http2.h>
#include <nghttp2/asio_http2_server.h>
using namespace nghttp2::asio_http2;
using namespace nghttp2::asio_http2::server;
@ -62,11 +62,47 @@ int main(int argc, char *argv[]) {
server.tls(argv[3], argv[4]);
}
server.listen("*", port, [](const std::shared_ptr<request> &req,
const std::shared_ptr<response> &res) {
res->write_head(200, {header{"foo", "bar"}});
res->end("hello, world");
server.handle("/", [](const request &req, const response &res) {
res.write_head(200, {{"foo", {"bar"}}});
res.end("hello, world\n");
});
server.handle("/secret/", [](const request &req, const response &res) {
res.write_head(200);
res.end("under construction!\n");
});
server.handle("/push", [](const request &req, const response &res) {
boost::system::error_code ec;
auto push = res.push(ec, "GET", "/push/1");
if (!ec) {
push->write_head(200);
push->end("server push FTW!\n");
}
res.write_head(200);
res.end("you'll receive server push!\n");
});
server.handle("/delay", [](const request &req, const response &res) {
res.write_head(200);
auto timer = std::make_shared<boost::asio::deadline_timer>(
res.io_service(), boost::posix_time::seconds(3));
auto closed = std::make_shared<bool>();
res.on_close([timer, closed](uint32_t error_code) {
timer->cancel();
*closed = true;
});
timer->async_wait([&res, closed](const boost::system::error_code &ec) {
if (ec || *closed) {
return;
}
res.end("finally!\n");
});
});
server.listen_and_serve("*", port);
} catch (std::exception &e) {
std::cerr << "exception: " << e.what() << "\n";
}

View File

@ -40,7 +40,7 @@
#include <iostream>
#include <string>
#include <nghttp2/asio_http2.h>
#include <nghttp2/asio_http2_server.h>
using namespace nghttp2::asio_http2;
using namespace nghttp2::asio_http2::server;
@ -66,12 +66,11 @@ int main(int argc, char *argv[]) {
server.tls(argv[4], argv[5]);
}
server.listen("*", port, [&docroot](const std::shared_ptr<request> &req,
const std::shared_ptr<response> &res) {
auto path = percent_decode(req->path());
server.handle("/", [&docroot](const request &req, const response &res) {
auto path = percent_decode(req.uri().path);
if (!check_path(path)) {
res->write_head(404);
res->end();
res.write_head(404);
res.end();
return;
}
@ -82,22 +81,26 @@ int main(int argc, char *argv[]) {
path = docroot + path;
auto fd = open(path.c_str(), O_RDONLY);
if (fd == -1) {
res->write_head(404);
res->end();
res.write_head(404);
res.end();
return;
}
auto headers = std::vector<header>();
auto header = header_map();
struct stat stbuf;
if (stat(path.c_str(), &stbuf) == 0) {
headers.push_back(
header{"content-length", std::to_string(stbuf.st_size)});
headers.push_back(header{"last-modified", http_date(stbuf.st_mtime)});
header.emplace("content-length",
header_value{std::to_string(stbuf.st_size)});
header.emplace("last-modified",
header_value{http_date(stbuf.st_mtime)});
}
res->write_head(200, std::move(headers));
res->end(file_reader_from_fd(fd));
res.write_head(200, std::move(header));
res.end(file_generator_from_fd(fd));
});
server.listen_and_serve("*", port);
} catch (std::exception &e) {
std::cerr << "exception: " << e.what() << "\n";
}

View File

@ -1,142 +0,0 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2014 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// We wrote this code based on the original code which has the
// following license:
//
// main.cpp
// ~~~~~~~~
//
// Copyright (c) 2003-2013 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <unistd.h>
#include <iostream>
#include <string>
#include <deque>
#include <nghttp2/asio_http2.h>
using namespace nghttp2::asio_http2;
using namespace nghttp2::asio_http2::server;
int main(int argc, char *argv[]) {
try {
// Check command line arguments.
if (argc < 4) {
std::cerr << "Usage: asio-sv3 <port> <threads> <tasks> "
<< " <private-key-file> <cert-file>\n";
return 1;
}
uint16_t port = std::stoi(argv[1]);
std::size_t num_threads = std::stoi(argv[2]);
std::size_t num_concurrent_tasks = std::stoi(argv[3]);
http2 server;
server.num_threads(num_threads);
if (argc >= 5) {
server.tls(argv[4], argv[5]);
}
server.num_concurrent_tasks(num_concurrent_tasks);
server.listen("*", port, [](const std::shared_ptr<request> &req,
const std::shared_ptr<response> &res) {
res->write_head(200);
auto msgq = std::make_shared<std::deque<std::string>>();
res->end([msgq](uint8_t * buf, std::size_t len)
-> std::pair<ssize_t, bool> {
if (msgq->empty()) {
// if msgq is empty, tells the library that don't call
// this callback until we call res->resume(). This is
// done by returing std::make_pair(0, false).
return std::make_pair(0, false);
}
auto msg = std::move(msgq->front());
msgq->pop_front();
if (msg.empty()) {
// The empty message signals the end of response in
// this simple protocol.
return std::make_pair(0, true);
}
auto nwrite = std::min(len, msg.size());
std::copy(std::begin(msg), std::begin(msg) + nwrite, buf);
if (msg.size() > nwrite) {
msgq->push_front(msg.substr(nwrite));
}
return std::make_pair(nwrite, false);
});
req->run_task([res, msgq](channel &channel) {
// executed in different thread from request callback
// was called.
// Using res and msgq is not safe inside this callback.
// But using them in callback passed to channel::post is
// safe.
// We just emit simple message "message N\n" in every 1
// second and 3 times in total.
for (std::size_t i = 0; i < 3; ++i) {
msgq->push_back("message " + std::to_string(i + 1) + "\n");
channel.post([res]() {
// executed in same thread where
// request callback was called.
// Tells library we have new message.
res->resume();
});
sleep(1);
}
// Send empty message to signal the end of response
// body.
msgq->push_back("");
channel.post([res]() {
// executed in same thread where request
// callback was called.
res->resume();
});
});
});
} catch (std::exception &e) {
std::cerr << "exception: " << e.what() << "\n";
}
return 0;
}

View File

@ -173,13 +173,32 @@ DISTCLEANFILES = $(pkgconfig_DATA)
lib_LTLIBRARIES = libnghttp2_asio.la
libnghttp2_asio_la_SOURCES = \
asio_connection.h \
asio_server.cc asio_server.h \
asio_io_service_pool.cc asio_io_service_pool.h \
asio_http2_handler.cc asio_http2_handler.h \
asio_http2_impl.cc asio_http2_impl.h \
util.cc util.h http2.cc http2.h \
ssl.cc ssl.h
ssl.cc ssl.h \
asio_common.cc asio_common.h \
asio_io_service_pool.cc asio_io_service_pool.h \
asio_server_http2.cc \
asio_server_http2_impl.cc asio_server_http2_impl.h \
asio_server.cc asio_server.h \
asio_server_http2_handler.cc asio_server_http2_handler.h \
asio_server_connection.h \
asio_server_request.cc \
asio_server_request_impl.cc asio_server_request_impl.h \
asio_server_response.cc \
asio_server_response_impl.cc asio_server_response_impl.h \
asio_server_stream.cc asio_server_stream.h \
asio_server_serve_mux.cc asio_server_serve_mux.h \
asio_server_request_handler.cc asio_server_request_handler.h \
asio_client_session.cc \
asio_client_session_impl.cc asio_client_session_impl.h \
asio_client_session_tcp_impl.cc asio_client_session_tcp_impl.h \
asio_client_session_tls_impl.cc asio_client_session_tls_impl.h \
asio_client_response.cc \
asio_client_response_impl.cc asio_client_response_impl.h \
asio_client_request.cc \
asio_client_request_impl.cc asio_client_request_impl.h \
asio_client_stream.cc asio_client_stream.h \
asio_client_tls_context.cc asio_client_tls_context.h
libnghttp2_asio_la_CPPFLAGS = ${AM_CPPFLAGS} ${BOOST_CPPFLAGS}
libnghttp2_asio_la_LDFLAGS = -no-undefined -version-info 0:0:0

View File

@ -0,0 +1,63 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "nghttp2_config.h"
#include <nghttp2/asio_http2_client.h>
#include "asio_client_request_impl.h"
#include "template.h"
namespace nghttp2 {
namespace asio_http2 {
namespace client {
request::request() : impl_(make_unique<request_impl>()) {}
request::~request() {}
void request::cancel(uint32_t error_code) const { impl_->cancel(error_code); }
void request::on_response(response_cb cb) const {
impl_->on_response(std::move(cb));
}
void request::on_push(request_cb cb) const { impl_->on_push(std::move(cb)); }
void request::on_close(close_cb cb) const { impl_->on_close(std::move(cb)); }
const uri_ref &request::uri() const { return impl_->uri(); }
const std::string &request::method() const { return impl_->method(); }
const header_map &request::header() const { return impl_->header(); }
void request::resume() const { impl_->resume(); }
request_impl &request::impl() const { return *impl_; }
} // namespace client
} // namespace asio_http2
} // namespace nghttp2

View File

@ -0,0 +1,105 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "asio_client_request_impl.h"
#include "asio_client_stream.h"
#include "asio_client_session_impl.h"
#include "template.h"
namespace nghttp2 {
namespace asio_http2 {
namespace client {
request_impl::request_impl() : strm_(nullptr) {}
void request_impl::cancel(uint32_t error_code) {
auto sess = strm_->session();
sess->cancel(*strm_, error_code);
}
void request_impl::on_response(response_cb cb) { response_cb_ = std::move(cb); }
void request_impl::call_on_response(response &res) {
if (response_cb_) {
response_cb_(res);
}
}
void request_impl::on_push(request_cb cb) { push_request_cb_ = std::move(cb); }
void request_impl::call_on_push(request &push_req) {
if (push_request_cb_) {
push_request_cb_(push_req);
}
};
void request_impl::on_close(close_cb cb) { close_cb_ = std::move(cb); }
void request_impl::call_on_close(uint32_t error_code) {
if (close_cb_) {
close_cb_(error_code);
}
}
void request_impl::on_read(generator_cb cb) { generator_cb_ = std::move(cb); }
generator_cb::result_type request_impl::call_on_read(uint8_t *buf,
std::size_t len,
uint32_t *data_flags) {
if (generator_cb_) {
return generator_cb_(buf, len, data_flags);
}
*data_flags |= NGHTTP2_DATA_FLAG_EOF;
return 0;
}
void request_impl::resume() {
auto sess = strm_->session();
sess->resume(*strm_);
}
void request_impl::header(header_map h) { header_ = std::move(h); }
header_map &request_impl::header() { return header_; }
const header_map &request_impl::header() const { return header_; }
void request_impl::stream(class stream *strm) { strm_ = strm; }
void request_impl::uri(uri_ref uri) { uri_ = std::move(uri); }
const uri_ref &request_impl::uri() const { return uri_; }
uri_ref &request_impl::uri() { return uri_; }
void request_impl::method(std::string s) { method_ = std::move(s); }
const std::string &request_impl::method() const { return method_; }
} // namespace client
} // namespace asio_http2
} // namespace nghttp2

View File

@ -0,0 +1,91 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef ASIO_CLIENT_REQUEST_IMPL_H
#define ASIO_CLIENT_REQUEST_IMPL_H
#include "nghttp2_config.h"
#include <nghttp2/asio_http2_client.h>
namespace nghttp2 {
namespace asio_http2 {
namespace client {
class response;
class stream;
class request_impl {
public:
request_impl();
request_impl(const request_impl &) = delete;
request_impl &operator=(const request_impl &) = delete;
void cancel(uint32_t error_code);
void on_response(response_cb cb);
void call_on_response(response &res);
void on_push(request_cb cb);
void call_on_push(request &push_req);
void on_close(close_cb cb);
void call_on_close(uint32_t error_code);
void on_read(generator_cb cb);
generator_cb::result_type call_on_read(uint8_t *buf, std::size_t len,
uint32_t *data_flags);
void resume();
void header(header_map h);
header_map &header();
const header_map &header() const;
void stream(class stream *strm);
void uri(uri_ref uri);
const uri_ref &uri() const;
uri_ref &uri();
void method(std::string s);
const std::string &method() const;
private:
header_map header_;
response_cb response_cb_;
request_cb push_request_cb_;
close_cb close_cb_;
generator_cb generator_cb_;
class stream *strm_;
uri_ref uri_;
std::string method_;
};
} // namespace client
} // namespace asio_http2
} // namespace nghttp2
#endif // ASIO_CLIENT_REQUEST_IMPL_H

View File

@ -0,0 +1,53 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "nghttp2_config.h"
#include <nghttp2/asio_http2_client.h>
#include "asio_client_response_impl.h"
#include "template.h"
namespace nghttp2 {
namespace asio_http2 {
namespace client {
response::response() : impl_(make_unique<response_impl>()) {}
response::~response() {}
void response::on_data(data_cb cb) const { impl_->on_data(std::move(cb)); }
int response::status_code() const { return impl_->status_code(); }
int64_t response::content_length() const { return impl_->content_length(); }
const header_map &response::header() const { return impl_->header(); }
response_impl &response::impl() const { return *impl_; }
} // namespace client
} // namespace asio_http2
} // namespace nghttp2

View File

@ -0,0 +1,57 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "asio_client_response_impl.h"
#include "template.h"
namespace nghttp2 {
namespace asio_http2 {
namespace client {
response_impl::response_impl() : content_length_(-1), status_code_(0) {}
void response_impl::on_data(data_cb cb) { data_cb_ = std::move(cb); }
void response_impl::call_on_data(const uint8_t *data, std::size_t len) {
if (data_cb_) {
data_cb_(data, len);
}
}
void response_impl::status_code(int sc) { status_code_ = sc; }
int response_impl::status_code() const { return status_code_; }
void response_impl::content_length(int64_t n) { content_length_ = n; }
int64_t response_impl::content_length() const { return content_length_; }
header_map &response_impl::header() { return header_; }
const header_map &response_impl::header() const { return header_; }
} // namespace client
} // namespace asio_http2
} // namespace nghttp2

View File

@ -0,0 +1,69 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef ASIO_CLIENT_RESPONSE_IMPL_H
#define ASIO_CLIENT_RESPONSE_IMPL_H
#include "nghttp2_config.h"
#include <nghttp2/asio_http2_client.h>
namespace nghttp2 {
namespace asio_http2 {
namespace client {
class response_impl {
public:
response_impl();
response_impl(const response_impl &) = delete;
response_impl &operator=(const response_impl &) = delete;
void on_data(data_cb cb);
void call_on_data(const uint8_t *data, std::size_t len);
void status_code(int sc);
int status_code() const;
void content_length(int64_t n);
int64_t content_length() const;
header_map &header();
const header_map &header() const;
private:
data_cb data_cb_;
header_map header_;
int64_t content_length_;
int status_code_;
};
} // namespace client
} // namespace asio_http2
} // namespace nghttp2
#endif // ASIO_CLIENT_RESPONSE_IMPL_H

View File

@ -0,0 +1,87 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "nghttp2_config.h"
#include <nghttp2/asio_http2_client.h>
#include "asio_client_session_tcp_impl.h"
#include "asio_client_session_tls_impl.h"
#include "asio_common.h"
#include "template.h"
namespace nghttp2 {
namespace asio_http2 {
namespace client {
using boost::asio::ip::tcp;
session::session(boost::asio::io_service &io_service, const std::string &host,
const std::string &service)
: impl_(make_unique<session_tcp_impl>(io_service, host, service)) {}
session::session(boost::asio::io_service &io_service,
boost::asio::ssl::context &tls_ctx, const std::string &host,
const std::string &service)
: impl_(make_unique<session_tls_impl>(io_service, tls_ctx, host, service)) {
}
session::~session() {}
void session::on_connect(connect_cb cb) const {
impl_->on_connect(std::move(cb));
}
void session::on_error(error_cb cb) const { impl_->on_error(std::move(cb)); }
void session::shutdown() const { impl_->shutdown(); }
boost::asio::io_service &session::io_service() const {
return impl_->io_service();
}
const request *session::submit(boost::system::error_code &ec,
const std::string &method,
const std::string &uri, header_map h) const {
return impl_->submit(ec, method, uri, generator_cb(), std::move(h));
}
const request *session::submit(boost::system::error_code &ec,
const std::string &method,
const std::string &uri, std::string data,
header_map h) const {
return impl_->submit(ec, method, uri, string_generator(std::move(data)),
std::move(h));
}
const request *session::submit(boost::system::error_code &ec,
const std::string &method,
const std::string &uri, generator_cb cb,
header_map h) const {
return impl_->submit(ec, method, uri, std::move(cb), std::move(h));
}
} // namespace client
} // namespace asio_http2
} // nghttp2

View File

@ -0,0 +1,586 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "asio_client_session_impl.h"
#include <iostream>
#include "asio_client_stream.h"
#include "asio_client_request_impl.h"
#include "asio_client_response_impl.h"
#include "asio_common.h"
#include "template.h"
#include "util.h"
#include "http2.h"
namespace nghttp2 {
namespace asio_http2 {
namespace client {
session_impl::session_impl(boost::asio::io_service &io_service)
: wblen_(0), io_service_(io_service), resolver_(io_service),
session_(nullptr), data_pending_(nullptr), data_pendinglen_(0),
writing_(false), inside_callback_(false) {}
session_impl::~session_impl() {
// finish up all active stream
for (auto &p : streams_) {
auto &strm = p.second;
auto &req = strm->request().impl();
req.call_on_close(NGHTTP2_INTERNAL_ERROR);
}
nghttp2_session_del(session_);
}
void session_impl::start_resolve(const std::string &host,
const std::string &service) {
resolver_.async_resolve({host, service},
[this](const boost::system::error_code &ec,
tcp::resolver::iterator endpoint_it) {
if (ec) {
not_connected(ec);
return;
}
start_connect(endpoint_it);
});
}
void session_impl::connected(tcp::resolver::iterator endpoint_it) {
if (!setup_session()) {
return;
}
socket().set_option(boost::asio::ip::tcp::no_delay(true));
std::copy_n(NGHTTP2_CLIENT_CONNECTION_PREFACE,
NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN, std::begin(wb_));
wblen_ = NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN;
do_write();
do_read();
auto &connect_cb = on_connect();
if (connect_cb) {
connect_cb(endpoint_it);
}
}
void session_impl::not_connected(const boost::system::error_code &ec) {
auto &error_cb = on_error();
if (error_cb) {
error_cb(ec);
}
}
void session_impl::on_connect(connect_cb cb) { connect_cb_ = std::move(cb); }
void session_impl::on_error(error_cb cb) { error_cb_ = std::move(cb); }
const connect_cb &session_impl::on_connect() const { return connect_cb_; }
const error_cb &session_impl::on_error() const { return error_cb_; }
namespace {
int on_begin_headers_callback(nghttp2_session *session,
const nghttp2_frame *frame, void *user_data) {
if (frame->hd.type != NGHTTP2_PUSH_PROMISE) {
return 0;
}
auto sess = static_cast<session_impl *>(user_data);
sess->create_push_stream(frame->push_promise.promised_stream_id);
return 0;
}
} // namespace
namespace {
int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen, uint8_t flags,
void *user_data) {
auto sess = static_cast<session_impl *>(user_data);
stream *strm;
switch (frame->hd.type) {
case NGHTTP2_HEADERS: {
strm = sess->find_stream(frame->hd.stream_id);
if (!strm) {
return 0;
}
// ignore trailers
if (frame->headers.cat == NGHTTP2_HCAT_HEADERS &&
!strm->expect_final_response()) {
return 0;
}
auto token = http2::lookup_token(name, namelen);
auto &res = strm->response().impl();
if (token == http2::HD__STATUS) {
res.status_code(util::parse_uint(value, valuelen));
} else {
if (token == http2::HD_CONTENT_LENGTH) {
res.content_length(util::parse_uint(value, valuelen));
}
res.header().emplace(
std::string(name, name + namelen),
header_value{std::string(value, value + valuelen),
(flags & NGHTTP2_NV_FLAG_NO_INDEX) != 0});
}
break;
}
case NGHTTP2_PUSH_PROMISE: {
strm = sess->find_stream(frame->push_promise.promised_stream_id);
if (!strm) {
return 0;
}
auto &req = strm->request().impl();
auto &uri = req.uri();
switch (http2::lookup_token(name, namelen)) {
case http2::HD__METHOD:
req.method(std::string(value, value + valuelen));
break;
case http2::HD__SCHEME:
uri.scheme.assign(value, value + valuelen);
break;
case http2::HD__PATH:
split_path(uri, value, value + valuelen);
break;
case http2::HD__AUTHORITY:
uri.host.assign(value, value + valuelen);
break;
case http2::HD_HOST:
if (uri.host.empty()) {
uri.host.assign(value, value + valuelen);
}
// fall through
default:
req.header().emplace(
std::string(name, name + namelen),
header_value{std::string(value, value + valuelen),
(flags & NGHTTP2_NV_FLAG_NO_INDEX) != 0});
}
break;
}
default:
return 0;
}
return 0;
}
} // namespace
namespace {
int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame,
void *user_data) {
auto sess = static_cast<session_impl *>(user_data);
auto strm = sess->find_stream(frame->hd.stream_id);
switch (frame->hd.type) {
case NGHTTP2_DATA: {
if (!strm) {
return 0;
}
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
strm->response().impl().call_on_data(nullptr, 0);
}
break;
}
case NGHTTP2_HEADERS: {
if (!strm) {
return 0;
}
// ignore trailers
if (frame->headers.cat == NGHTTP2_HCAT_HEADERS &&
!strm->expect_final_response()) {
return 0;
}
if (strm->expect_final_response()) {
// wait for final response
return 0;
}
auto &req = strm->request().impl();
req.call_on_response(strm->response());
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
strm->response().impl().call_on_data(nullptr, 0);
}
break;
}
case NGHTTP2_PUSH_PROMISE: {
if (!strm) {
return 0;
}
auto push_strm = sess->find_stream(frame->push_promise.promised_stream_id);
if (!push_strm) {
return 0;
}
strm->request().impl().call_on_push(push_strm->request());
break;
}
}
return 0;
}
} // namespace
namespace {
int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
int32_t stream_id, const uint8_t *data,
size_t len, void *user_data) {
auto sess = static_cast<session_impl *>(user_data);
auto strm = sess->find_stream(stream_id);
if (!strm) {
return 0;
}
auto &res = strm->response().impl();
res.call_on_data(data, len);
return 0;
}
} // namespace
namespace {
int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
uint32_t error_code, void *user_data) {
auto sess = static_cast<session_impl *>(user_data);
auto strm = sess->pop_stream(stream_id);
if (!strm) {
return 0;
}
strm->request().impl().call_on_close(error_code);
return 0;
}
} // namespace
bool session_impl::setup_session() {
nghttp2_session_callbacks *callbacks;
nghttp2_session_callbacks_new(&callbacks);
defer(nghttp2_session_callbacks_del, callbacks);
nghttp2_session_callbacks_set_on_begin_headers_callback(
callbacks, on_begin_headers_callback);
nghttp2_session_callbacks_set_on_header_callback(callbacks,
on_header_callback);
nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
on_frame_recv_callback);
nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
callbacks, on_data_chunk_recv_callback);
nghttp2_session_callbacks_set_on_stream_close_callback(
callbacks, on_stream_close_callback);
auto rv = nghttp2_session_client_new(&session_, callbacks, this);
if (rv != 0) {
auto &error_cb = on_error();
if (error_cb) {
error_cb(make_error_code(static_cast<nghttp2_error>(rv)));
}
return false;
}
nghttp2_settings_entry iv = {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100};
nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, &iv, 1);
return true;
}
void session_impl::cancel(stream &strm, uint32_t error_code) {
nghttp2_submit_rst_stream(session_, NGHTTP2_FLAG_NONE, strm.stream_id(),
error_code);
signal_write();
}
void session_impl::resume(stream &strm) {
nghttp2_session_resume_data(session_, strm.stream_id());
signal_write();
}
stream *session_impl::find_stream(int32_t stream_id) {
auto it = streams_.find(stream_id);
if (it == std::end(streams_)) {
return nullptr;
}
return (*it).second.get();
}
std::unique_ptr<stream> session_impl::pop_stream(int32_t stream_id) {
auto it = streams_.find(stream_id);
if (it == std::end(streams_)) {
return nullptr;
}
auto strm = std::move((*it).second);
streams_.erase(it);
return strm;
}
stream *session_impl::create_push_stream(int32_t stream_id) {
auto strm = create_stream();
strm->stream_id(stream_id);
auto p = streams_.emplace(stream_id, std::move(strm));
assert(p.second);
return (*p.first).second.get();
}
std::unique_ptr<stream> session_impl::create_stream() {
return make_unique<stream>(this);
}
const request *session_impl::submit(boost::system::error_code &ec,
const std::string &method,
const std::string &uri, generator_cb cb,
header_map h) {
ec.clear();
http_parser_url u{};
// TODO Handle CONNECT method
if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
ec = make_error_code(boost::system::errc::invalid_argument);
return nullptr;
}
if ((u.field_set & (1 << UF_SCHEMA)) == 0 ||
(u.field_set & (1 << UF_HOST)) == 0) {
ec = make_error_code(boost::system::errc::invalid_argument);
return nullptr;
}
auto strm = create_stream();
auto &req = strm->request().impl();
auto &uref = req.uri();
http2::copy_url_component(uref.scheme, &u, UF_SCHEMA, uri.c_str());
http2::copy_url_component(uref.host, &u, UF_HOST, uri.c_str());
http2::copy_url_component(uref.raw_path, &u, UF_PATH, uri.c_str());
http2::copy_url_component(uref.raw_query, &u, UF_QUERY, uri.c_str());
if (util::ipv6_numeric_addr(uref.host.c_str())) {
uref.host = "[" + uref.host;
uref.host += "]";
}
if (u.field_set & (1 << UF_PORT)) {
uref.host += ":";
uref.host += util::utos(u.port);
}
uref.path = percent_decode(uref.raw_path);
auto path = uref.raw_path;
if (u.field_set & (1 << UF_QUERY)) {
path += "?";
path += uref.raw_query;
}
auto nva = std::vector<nghttp2_nv>();
nva.reserve(3 + h.size());
nva.push_back(http2::make_nv_ls(":method", method));
nva.push_back(http2::make_nv_ls(":scheme", uref.scheme));
nva.push_back(http2::make_nv_ls(":path", path));
nva.push_back(http2::make_nv_ls(":authority", uref.host));
for (auto &kv : h) {
nva.push_back(
http2::make_nv(kv.first, kv.second.value, kv.second.sensitive));
}
req.header(std::move(h));
nghttp2_data_provider *prdptr = nullptr;
nghttp2_data_provider prd;
if (cb) {
strm->request().impl().on_read(std::move(cb));
prd.source.ptr = strm.get();
prd.read_callback =
[](nghttp2_session *session, int32_t stream_id, uint8_t *buf,
size_t length, uint32_t *data_flags, nghttp2_data_source *source,
void *user_data) -> ssize_t {
auto strm = static_cast<stream *>(source->ptr);
return strm->request().impl().call_on_read(buf, length, data_flags);
};
prdptr = &prd;
}
auto stream_id = nghttp2_submit_request(session_, nullptr, nva.data(),
nva.size(), prdptr, strm.get());
if (stream_id < 0) {
ec = make_error_code(static_cast<nghttp2_error>(stream_id));
return nullptr;
}
signal_write();
strm->stream_id(stream_id);
auto p = streams_.emplace(stream_id, std::move(strm));
assert(p.second);
return &(*p.first).second->request();
}
void session_impl::shutdown() {
nghttp2_session_terminate_session(session_, NGHTTP2_NO_ERROR);
signal_write();
}
boost::asio::io_service &session_impl::io_service() { return io_service_; }
void session_impl::signal_write() {
if (!inside_callback_) {
do_write();
}
}
bool session_impl::should_stop() const {
return !writing_ && !nghttp2_session_want_read(session_) &&
!nghttp2_session_want_write(session_);
}
namespace {
struct callback_guard {
callback_guard(session_impl &sess) : sess(sess) { sess.enter_callback(); }
~callback_guard() { sess.leave_callback(); }
session_impl &sess;
};
} // namespace
void session_impl::enter_callback() {
assert(!inside_callback_);
inside_callback_ = true;
}
void session_impl::leave_callback() {
assert(inside_callback_);
inside_callback_ = false;
}
void session_impl::do_read() {
read_socket([this](const boost::system::error_code &ec,
std::size_t bytes_transferred) {
if (ec) {
if (ec.value() == boost::asio::error::operation_aborted) {
shutdown_socket();
}
return;
}
{
callback_guard cg(*this);
auto rv =
nghttp2_session_mem_recv(session_, rb_.data(), bytes_transferred);
if (rv != static_cast<ssize_t>(bytes_transferred)) {
shutdown_socket();
return;
}
}
do_write();
if (should_stop()) {
shutdown_socket();
return;
}
do_read();
});
}
void session_impl::do_write() {
if (writing_) {
return;
}
if (data_pending_) {
std::copy_n(data_pending_, data_pendinglen_, std::begin(wb_) + wblen_);
wblen_ += data_pendinglen_;
data_pending_ = nullptr;
data_pendinglen_ = 0;
}
{
callback_guard cg(*this);
for (;;) {
const uint8_t *data;
auto n = nghttp2_session_mem_send(session_, &data);
if (n < 0) {
shutdown_socket();
return;
}
if (n == 0) {
break;
}
if (wblen_ + n > wb_.size()) {
data_pending_ = data;
data_pendinglen_ = n;
break;
}
std::copy_n(data, n, std::begin(wb_) + wblen_);
wblen_ += n;
}
}
if (wblen_ == 0) {
return;
}
writing_ = true;
write_socket([this](const boost::system::error_code &ec, std::size_t n) {
if (ec) {
return;
}
wblen_ = 0;
writing_ = false;
do_write();
});
}
} // namespace client
} // namespace asio_http2
} // nghttp2

View File

@ -0,0 +1,120 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef ASIO_CLIENT_SESSION_IMPL_H
#define ASIO_CLIENT_SESSION_IMPL_H
#include "nghttp2_config.h"
#include <boost/array.hpp>
#include <nghttp2/asio_http2_client.h>
namespace nghttp2 {
namespace asio_http2 {
namespace client {
class stream;
using boost::asio::ip::tcp;
class session_impl {
public:
session_impl(boost::asio::io_service &io_service);
virtual ~session_impl();
void start_resolve(const std::string &host, const std::string &service);
void connected(tcp::resolver::iterator endpoint_it);
void not_connected(const boost::system::error_code &ec);
void on_connect(connect_cb cb);
void on_error(error_cb cb);
const connect_cb &on_connect() const;
const error_cb &on_error() const;
void cancel(stream &strm, uint32_t error_code);
void resume(stream &strm);
std::unique_ptr<stream> create_stream();
std::unique_ptr<stream> pop_stream(int32_t stream_id);
stream *create_push_stream(int32_t stream_id);
stream *find_stream(int32_t stream_id);
const request *submit(boost::system::error_code &ec,
const std::string &method, const std::string &uri,
generator_cb cb, header_map h);
virtual void start_connect(tcp::resolver::iterator endpoint_it) = 0;
virtual tcp::socket &socket() = 0;
virtual void read_socket(std::function<
void(const boost::system::error_code &ec, std::size_t n)> h) = 0;
virtual void write_socket(std::function<
void(const boost::system::error_code &ec, std::size_t n)> h) = 0;
virtual void shutdown_socket() = 0;
void shutdown();
boost::asio::io_service &io_service();
void signal_write();
void enter_callback();
void leave_callback();
void do_read();
void do_write();
protected:
boost::array<uint8_t, 8192> rb_;
boost::array<uint8_t, 65536> wb_;
std::size_t wblen_;
private:
bool should_stop() const;
bool setup_session();
boost::asio::io_service &io_service_;
tcp::resolver resolver_;
std::map<int32_t, std::unique_ptr<stream>> streams_;
connect_cb connect_cb_;
error_cb error_cb_;
nghttp2_session *session_;
const uint8_t *data_pending_;
std::size_t data_pendinglen_;
bool writing_;
bool inside_callback_;
};
} // namespace client
} // namespace asio_http2
} // namespace nghttp2
#endif // ASIO_CLIENT_SESSION_IMPL_H

View File

@ -0,0 +1,69 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "asio_client_session_tcp_impl.h"
namespace nghttp2 {
namespace asio_http2 {
namespace client {
session_tcp_impl::session_tcp_impl(boost::asio::io_service &io_service,
const std::string &host,
const std::string &service)
: session_impl(io_service), socket_(io_service) {
start_resolve(host, service);
}
session_tcp_impl::~session_tcp_impl() {}
void session_tcp_impl::start_connect(tcp::resolver::iterator endpoint_it) {
boost::asio::async_connect(socket_, endpoint_it,
[this](const boost::system::error_code &ec,
tcp::resolver::iterator endpoint_it) {
if (ec) {
not_connected(ec);
return;
}
connected(endpoint_it);
});
}
tcp::socket &session_tcp_impl::socket() { return socket_; }
void session_tcp_impl::read_socket(
std::function<void(const boost::system::error_code &ec, std::size_t n)> h) {
socket_.async_read_some(boost::asio::buffer(rb_), h);
}
void session_tcp_impl::write_socket(
std::function<void(const boost::system::error_code &ec, std::size_t n)> h) {
boost::asio::async_write(socket_, boost::asio::buffer(wb_, wblen_), h);
}
void session_tcp_impl::shutdown_socket() { socket_.close(); }
} // namespace client
} // namespace asio_http2
} // namespace nghttp2

View File

@ -0,0 +1,60 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef ASIO_CLIENT_SESSION_TCP_IMPL_H
#define ASIO_CLIENT_SESSION_TCP_IMPL_H
#include "asio_client_session_impl.h"
#include <nghttp2/asio_http2_client.h>
namespace nghttp2 {
namespace asio_http2 {
namespace client {
using boost::asio::ip::tcp;
class session_tcp_impl : public session_impl {
public:
session_tcp_impl(boost::asio::io_service &io_service, const std::string &host,
const std::string &service);
virtual ~session_tcp_impl();
virtual void start_connect(tcp::resolver::iterator endpoint_it);
virtual tcp::socket &socket();
virtual void read_socket(std::function<
void(const boost::system::error_code &ec, std::size_t n)> h);
virtual void write_socket(std::function<
void(const boost::system::error_code &ec, std::size_t n)> h);
virtual void shutdown_socket();
private:
tcp::socket socket_;
};
} // namespace client
} // namespace asio_http2
} // namespace nghttp2
#endif // ASIO_CLIENT_SESSION_TCP_IMPL_H

View File

@ -0,0 +1,85 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "asio_client_session_tls_impl.h"
namespace nghttp2 {
namespace asio_http2 {
namespace client {
session_tls_impl::session_tls_impl(boost::asio::io_service &io_service,
boost::asio::ssl::context &tls_ctx,
const std::string &host,
const std::string &service)
: session_impl(io_service), socket_(io_service, tls_ctx) {
// this callback setting is no effect is
// ssl::context::set_verify_mode(boost::asio::ssl::verify_peer) is
// not used, which is what we want.
socket_.set_verify_callback(boost::asio::ssl::rfc2818_verification(host));
start_resolve(host, service);
}
session_tls_impl::~session_tls_impl() {}
void session_tls_impl::start_connect(tcp::resolver::iterator endpoint_it) {
boost::asio::async_connect(socket(), endpoint_it,
[this](const boost::system::error_code &ec,
tcp::resolver::iterator endpoint_it) {
if (ec) {
not_connected(ec);
return;
}
socket_.async_handshake(
boost::asio::ssl::stream_base::client,
[this, endpoint_it](const boost::system::error_code &ec) {
if (ec) {
not_connected(ec);
return;
}
connected(endpoint_it);
});
});
}
tcp::socket &session_tls_impl::socket() { return socket_.next_layer(); }
void session_tls_impl::read_socket(
std::function<void(const boost::system::error_code &ec, std::size_t n)> h) {
socket_.async_read_some(boost::asio::buffer(rb_), h);
}
void session_tls_impl::write_socket(
std::function<void(const boost::system::error_code &ec, std::size_t n)> h) {
boost::asio::async_write(socket_, boost::asio::buffer(wb_, wblen_), h);
}
void session_tls_impl::shutdown_socket() {
socket_.async_shutdown([](const boost::system::error_code &ec) {});
}
} // namespace client
} // namespace asio_http2
} // namespace nghttp2

View File

@ -0,0 +1,63 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef ASIO_CLIENT_SESSION_TLS_IMPL_H
#define ASIO_CLIENT_SESSION_TLS_IMPL_H
#include "asio_client_session_impl.h"
#include <nghttp2/asio_http2_client.h>
namespace nghttp2 {
namespace asio_http2 {
namespace client {
using boost::asio::ip::tcp;
using ssl_socket = boost::asio::ssl::stream<tcp::socket>;
class session_tls_impl : public session_impl {
public:
session_tls_impl(boost::asio::io_service &io_service,
boost::asio::ssl::context &tls_ctx, const std::string &host,
const std::string &service);
virtual ~session_tls_impl();
virtual void start_connect(tcp::resolver::iterator endpoint_it);
virtual tcp::socket &socket();
virtual void read_socket(std::function<
void(const boost::system::error_code &ec, std::size_t n)> h);
virtual void write_socket(std::function<
void(const boost::system::error_code &ec, std::size_t n)> h);
virtual void shutdown_socket();
private:
ssl_socket socket_;
};
} // namespace client
} // namespace asio_http2
} // namespace nghttp2
#endif // ASIO_CLIENT_SESSION_TLS_IMPL_H

59
src/asio_client_stream.cc Normal file
View File

@ -0,0 +1,59 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "asio_client_stream.h"
#include "asio_client_request_impl.h"
#include "asio_client_response_impl.h"
#include "asio_client_session_impl.h"
namespace nghttp2 {
namespace asio_http2 {
namespace client {
stream::stream(session_impl *sess) : sess_(sess), stream_id_(0) {
request_.impl().stream(this);
}
void stream::stream_id(int32_t stream_id) { stream_id_ = stream_id; }
int32_t stream::stream_id() const { return stream_id_; }
class request &stream::request() {
return request_;
}
class response &stream::response() {
return response_;
}
session_impl *stream::session() const { return sess_; }
bool stream::expect_final_response() const {
return response_.status_code() / 100 == 1;
}
} // namespace client
} // namespace asio_http2
} // namespace nghttp2

68
src/asio_client_stream.h Normal file
View File

@ -0,0 +1,68 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef ASIO_CLIENT_STREAM_H
#define ASIO_CLIENT_STREAM_H
#include "nghttp2_config.h"
#include <nghttp2/asio_http2_client.h>
namespace nghttp2 {
namespace asio_http2 {
namespace client {
class request;
class response;
class session_impl;
class stream {
public:
stream(session_impl *sess);
stream(const stream &) = delete;
stream &operator=(const stream &) = delete;
void stream_id(int32_t stream_id);
int32_t stream_id() const;
class request &request();
class response &response();
session_impl *session() const;
bool expect_final_response() const;
private:
nghttp2::asio_http2::client::request request_;
nghttp2::asio_http2::client::response response_;
session_impl *sess_;
uint32_t stream_id_;
};
} // namespace client
} // namespace asio_http2
} // namespace nghttp2
#endif // ASIO_CLIENT_STREAM_H

View File

@ -0,0 +1,58 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "asio_client_tls_context.h"
#include <openssl/ssl.h>
#include <boost/asio/ssl.hpp>
#include "ssl.h"
#include "util.h"
namespace nghttp2 {
namespace asio_http2 {
namespace client {
namespace {
int client_select_next_proto_cb(SSL *ssl, unsigned char **out,
unsigned char *outlen, const unsigned char *in,
unsigned int inlen, void *arg) {
if (!util::select_h2(const_cast<const unsigned char **>(out), outlen, in,
inlen)) {
return SSL_TLSEXT_ERR_NOACK;
}
return SSL_TLSEXT_ERR_OK;
}
} // namespace
void configure_tls_context(boost::asio::ssl::context &tls_ctx) {
auto ctx = tls_ctx.native_handle();
SSL_CTX_set_next_proto_select_cb(ctx, client_select_next_proto_cb, nullptr);
}
} // namespace client
} // namespace asio_http2
} // namespace nghttp2

View File

@ -0,0 +1,32 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef ASIO_CLIENT_TLS_CONTEXT_H
#define ASIO_CLIENT_TLS_CONTEXT_H
#include "nghttp2_config.h"
#include <nghttp2/asio_http2_client.h>
#endif // ASIO_CLIENT_TLS_CONTEXT_H

116
src/asio_common.cc Normal file
View File

@ -0,0 +1,116 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "asio_common.h"
#include <memory>
#include "util.h"
#include "template.h"
namespace nghttp2 {
namespace asio_http2 {
class nghttp2_category_impl : public boost::system::error_category {
public:
const char *name() const noexcept { return "nghttp2"; }
std::string message(int ev) const { return nghttp2_strerror(ev); }
};
const boost::system::error_category &nghttp2_category() noexcept {
static nghttp2_category_impl cat;
return cat;
}
boost::system::error_code make_error_code(nghttp2_error ev) {
return boost::system::error_code(static_cast<int>(ev), nghttp2_category());
}
generator_cb string_generator(std::string data) {
auto strio = std::make_shared<std::pair<std::string, size_t>>(std::move(data),
data.size());
return [strio](uint8_t *buf, size_t len, uint32_t *data_flags) {
auto &data = strio->first;
auto &left = strio->second;
auto n = std::min(len, left);
std::copy_n(data.c_str() + data.size() - left, n, buf);
left -= n;
if (left == 0) {
*data_flags |= NGHTTP2_DATA_FLAG_EOF;
}
return n;
};
}
generator_cb deferred_generator() {
return [](uint8_t *buf, size_t len,
uint32_t *data_flags) { return NGHTTP2_ERR_DEFERRED; };
}
template <typename F, typename... T>
std::shared_ptr<Defer<F, T...>> defer_shared(F &&f, T &&... t) {
return std::make_shared<Defer<F, T...>>(std::forward<F>(f),
std::forward<T>(t)...);
}
generator_cb file_generator(const std::string &path) {
auto fd = open(path.c_str(), O_RDONLY);
if (fd == -1) {
return generator_cb();
}
return file_generator_from_fd(fd);
}
generator_cb file_generator_from_fd(int fd) {
auto d = defer_shared(close, fd);
return [fd, d](uint8_t *buf, size_t len, uint32_t *data_flags)
-> generator_cb::result_type {
ssize_t n;
while ((n = read(fd, buf, len)) == -1 && errno == EINTR)
;
if (n == -1) {
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
if (n == 0) {
*data_flags |= NGHTTP2_DATA_FLAG_EOF;
}
return n;
};
}
bool check_path(const std::string &path) { return util::check_path(path); }
std::string percent_decode(const std::string &s) {
return util::percentDecode(std::begin(s), std::end(s));
}
std::string http_date(int64_t t) { return util::http_date(t); }
} // namespace asio_http2
} // namespace nghttp2

65
src/asio_common.h Normal file
View File

@ -0,0 +1,65 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef ASIO_COMMON_H
#define ASIO_COMMON_H
#include "nghttp2_config.h"
#include <string>
#include <nghttp2/asio_http2.h>
#include "util.h"
namespace nghttp2 {
namespace asio_http2 {
boost::system::error_code make_error_code(nghttp2_error ev);
generator_cb string_generator(std::string data);
// Returns generator_cb, which just returns NGHTTP2_ERR_DEFERRED
generator_cb deferred_generator();
template <typename InputIt>
void split_path(uri_ref &dst, InputIt first, InputIt last) {
auto path_last = std::find(first, last, '?');
InputIt query_first;
if (path_last == last) {
query_first = path_last = last;
} else {
query_first = path_last + 1;
}
dst.path = util::percentDecode(first, path_last);
dst.raw_path.assign(first, path_last);
dst.raw_query.assign(query_first, last);
}
} // namespace asio_http2
} // namespace nghttp2
#endif // ASIO_COMMON_H

View File

@ -1,711 +0,0 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2014 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "asio_http2_handler.h"
#include <iostream>
#include "http2.h"
#include "util.h"
#include "template.h"
namespace nghttp2 {
namespace asio_http2 {
channel::channel() : impl_(make_unique<channel_impl>()) {}
void channel::post(void_cb cb) { impl_->post(std::move(cb)); }
channel_impl &channel::impl() { return *impl_; }
channel_impl::channel_impl() : strand_(nullptr) {}
void channel_impl::post(void_cb cb) { strand_->post(std::move(cb)); }
void channel_impl::strand(boost::asio::io_service::strand *strand) {
strand_ = strand;
}
namespace server {
extern std::shared_ptr<std::string> cached_date;
request::request() : impl_(make_unique<request_impl>()) {}
const std::vector<header> &request::headers() const { return impl_->headers(); }
const std::string &request::method() const { return impl_->method(); }
const std::string &request::scheme() const { return impl_->scheme(); }
const std::string &request::authority() const { return impl_->authority(); }
const std::string &request::host() const { return impl_->host(); }
const std::string &request::path() const { return impl_->path(); }
bool request::push(std::string method, std::string path,
std::vector<header> headers) {
return impl_->push(std::move(method), std::move(path), std::move(headers));
}
bool request::pushed() const { return impl_->pushed(); }
bool request::closed() const { return impl_->closed(); }
void request::on_data(data_cb cb) { return impl_->on_data(std::move(cb)); }
void request::on_end(void_cb cb) { return impl_->on_end(std::move(cb)); }
bool request::run_task(thread_cb start) {
return impl_->run_task(std::move(start));
}
request_impl &request::impl() { return *impl_; }
response::response() : impl_(make_unique<response_impl>()) {}
void response::write_head(unsigned int status_code,
std::vector<header> headers) {
impl_->write_head(status_code, std::move(headers));
}
void response::end(std::string data) { impl_->end(std::move(data)); }
void response::end(read_cb cb) { impl_->end(std::move(cb)); }
void response::resume() { impl_->resume(); }
unsigned int response::status_code() const { return impl_->status_code(); }
bool response::started() const { return impl_->started(); }
response_impl &response::impl() { return *impl_; }
request_impl::request_impl() : pushed_(false) {}
const std::vector<header> &request_impl::headers() const { return headers_; }
const std::string &request_impl::method() const { return method_; }
const std::string &request_impl::scheme() const { return scheme_; }
const std::string &request_impl::authority() const { return authority_; }
const std::string &request_impl::host() const { return host_; }
const std::string &request_impl::path() const { return path_; }
void request_impl::set_header(std::vector<header> headers) {
headers_ = std::move(headers);
}
void request_impl::add_header(std::string name, std::string value) {
headers_.push_back(header{std::move(name), std::move(value)});
}
void request_impl::method(std::string arg) { method_ = std::move(arg); }
void request_impl::scheme(std::string arg) { scheme_ = std::move(arg); }
void request_impl::authority(std::string arg) { authority_ = std::move(arg); }
void request_impl::host(std::string arg) { host_ = std::move(arg); }
void request_impl::path(std::string arg) { path_ = std::move(arg); }
bool request_impl::push(std::string method, std::string path,
std::vector<header> headers) {
if (closed()) {
return false;
}
auto handler = handler_.lock();
auto stream = stream_.lock();
auto rv = handler->push_promise(*stream, std::move(method), std::move(path),
std::move(headers));
return rv == 0;
}
bool request_impl::pushed() const { return pushed_; }
void request_impl::pushed(bool f) { pushed_ = f; }
bool request_impl::closed() const {
return handler_.expired() || stream_.expired();
}
void request_impl::on_data(data_cb cb) { on_data_cb_ = std::move(cb); }
void request_impl::on_end(void_cb cb) { on_end_cb_ = std::move(cb); }
bool request_impl::run_task(thread_cb start) {
if (closed()) {
return false;
}
auto handler = handler_.lock();
return handler->run_task(std::move(start));
}
void request_impl::handler(std::weak_ptr<http2_handler> h) {
handler_ = std::move(h);
}
void request_impl::stream(std::weak_ptr<http2_stream> s) {
stream_ = std::move(s);
}
void request_impl::call_on_data(const uint8_t *data, std::size_t len) {
if (on_data_cb_) {
on_data_cb_(data, len);
}
}
void request_impl::call_on_end() {
if (on_end_cb_) {
on_end_cb_();
}
}
response_impl::response_impl() : status_code_(200), started_(false) {}
unsigned int response_impl::status_code() const { return status_code_; }
void response_impl::write_head(unsigned int status_code,
std::vector<header> headers) {
status_code_ = status_code;
headers_ = std::move(headers);
}
void response_impl::end(std::string data) {
if (started_) {
return;
}
auto strio = std::make_shared<std::pair<std::string, size_t>>(std::move(data),
data.size());
auto read_cb = [strio](uint8_t *buf, size_t len) {
auto nread = std::min(len, strio->second);
memcpy(buf, strio->first.c_str(), nread);
strio->second -= nread;
if (strio->second == 0) {
return std::make_pair(nread, true);
}
return std::make_pair(nread, false);
};
end(std::move(read_cb));
}
void response_impl::end(read_cb cb) {
if (started_ || closed()) {
return;
}
read_cb_ = std::move(cb);
started_ = true;
auto handler = handler_.lock();
auto stream = stream_.lock();
if (handler->start_response(*stream) != 0) {
handler->stream_error(stream->get_stream_id(), NGHTTP2_INTERNAL_ERROR);
return;
}
if (!handler->inside_callback()) {
handler->initiate_write();
}
}
bool response_impl::closed() const {
return handler_.expired() || stream_.expired();
}
void response_impl::resume() {
if (closed()) {
return;
}
auto handler = handler_.lock();
auto stream = stream_.lock();
handler->resume(*stream);
if (!handler->inside_callback()) {
handler->initiate_write();
}
}
bool response_impl::started() const { return started_; }
const std::vector<header> &response_impl::headers() const { return headers_; }
void response_impl::handler(std::weak_ptr<http2_handler> h) {
handler_ = std::move(h);
}
void response_impl::stream(std::weak_ptr<http2_stream> s) {
stream_ = std::move(s);
}
std::pair<ssize_t, bool> response_impl::call_read(uint8_t *data,
std::size_t len) {
if (read_cb_) {
return read_cb_(data, len);
}
return std::make_pair(0, true);
}
http2_stream::http2_stream(int32_t stream_id)
: request_(std::make_shared<request>()),
response_(std::make_shared<response>()), stream_id_(stream_id) {}
int32_t http2_stream::get_stream_id() const { return stream_id_; }
const std::shared_ptr<request> &http2_stream::get_request() { return request_; }
const std::shared_ptr<response> &http2_stream::get_response() {
return response_;
}
namespace {
int stream_error(nghttp2_session *session, int32_t stream_id,
uint32_t error_code) {
return nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id,
error_code);
}
} // namespace
namespace {
int on_begin_headers_callback(nghttp2_session *session,
const nghttp2_frame *frame, void *user_data) {
auto handler = static_cast<http2_handler *>(user_data);
if (frame->hd.type != NGHTTP2_HEADERS ||
frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
return 0;
}
handler->create_stream(frame->hd.stream_id);
return 0;
}
} // namespace
namespace {
int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen, uint8_t flags,
void *user_data) {
auto handler = static_cast<http2_handler *>(user_data);
auto stream_id = frame->hd.stream_id;
if (frame->hd.type != NGHTTP2_HEADERS ||
frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
return 0;
}
auto stream = handler->find_stream(stream_id);
if (!stream) {
return 0;
}
auto &req = stream->get_request()->impl();
switch (nghttp2::http2::lookup_token(name, namelen)) {
case nghttp2::http2::HD__METHOD:
req.method(std::string(value, value + valuelen));
break;
case nghttp2::http2::HD__SCHEME:
req.scheme(std::string(value, value + valuelen));
break;
case nghttp2::http2::HD__AUTHORITY:
req.authority(std::string(value, value + valuelen));
break;
case nghttp2::http2::HD__PATH:
req.path(std::string(value, value + valuelen));
break;
case nghttp2::http2::HD_HOST:
req.host(std::string(value, value + valuelen));
// fall through
default:
req.add_header(std::string(name, name + namelen),
std::string(value, value + valuelen));
}
return 0;
}
} // namespace
namespace {
int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame,
void *user_data) {
auto handler = static_cast<http2_handler *>(user_data);
auto stream = handler->find_stream(frame->hd.stream_id);
switch (frame->hd.type) {
case NGHTTP2_DATA:
if (!stream) {
break;
}
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
stream->get_request()->impl().call_on_end();
}
break;
case NGHTTP2_HEADERS: {
if (!stream || frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
break;
}
auto &req = stream->get_request()->impl();
if (req.host().empty()) {
req.host(req.authority());
}
handler->call_on_request(*stream);
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
stream->get_request()->impl().call_on_end();
}
break;
}
}
return 0;
}
} // namespace
namespace {
int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
int32_t stream_id, const uint8_t *data,
size_t len, void *user_data) {
auto handler = static_cast<http2_handler *>(user_data);
auto stream = handler->find_stream(stream_id);
if (!stream) {
return 0;
}
stream->get_request()->impl().call_on_data(data, len);
return 0;
}
} // namespace
namespace {
int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
uint32_t error_code, void *user_data) {
auto handler = static_cast<http2_handler *>(user_data);
handler->close_stream(stream_id);
return 0;
}
} // namespace
namespace {
int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame,
void *user_data) {
auto handler = static_cast<http2_handler *>(user_data);
if (frame->hd.type != NGHTTP2_PUSH_PROMISE) {
return 0;
}
auto stream = handler->find_stream(frame->push_promise.promised_stream_id);
if (!stream) {
return 0;
}
handler->call_on_request(*stream);
return 0;
}
} // namespace
namespace {
int on_frame_not_send_callback(nghttp2_session *session,
const nghttp2_frame *frame, int lib_error_code,
void *user_data) {
if (frame->hd.type != NGHTTP2_HEADERS) {
return 0;
}
// Issue RST_STREAM so that stream does not hang around.
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id,
NGHTTP2_INTERNAL_ERROR);
return 0;
}
} // namespace
http2_handler::http2_handler(boost::asio::io_service &io_service,
boost::asio::io_service &task_io_service_,
connection_write writefun, request_cb cb)
: writefun_(writefun), request_cb_(std::move(cb)), io_service_(io_service),
task_io_service_(task_io_service_),
strand_(std::make_shared<boost::asio::io_service::strand>(io_service_)),
session_(nullptr), buf_(nullptr), buflen_(0), inside_callback_(false) {}
http2_handler::~http2_handler() { nghttp2_session_del(session_); }
int http2_handler::start() {
int rv;
nghttp2_session_callbacks *callbacks;
rv = nghttp2_session_callbacks_new(&callbacks);
if (rv != 0) {
return -1;
}
auto cb_del = defer(nghttp2_session_callbacks_del, callbacks);
nghttp2_session_callbacks_set_on_begin_headers_callback(
callbacks, on_begin_headers_callback);
nghttp2_session_callbacks_set_on_header_callback(callbacks,
on_header_callback);
nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
on_frame_recv_callback);
nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
callbacks, on_data_chunk_recv_callback);
nghttp2_session_callbacks_set_on_stream_close_callback(
callbacks, on_stream_close_callback);
nghttp2_session_callbacks_set_on_frame_send_callback(callbacks,
on_frame_send_callback);
nghttp2_session_callbacks_set_on_frame_not_send_callback(
callbacks, on_frame_not_send_callback);
nghttp2_option *option;
rv = nghttp2_option_new(&option);
if (rv != 0) {
return -1;
}
auto opt_del = defer(nghttp2_option_del, option);
nghttp2_option_set_recv_client_preface(option, 1);
rv = nghttp2_session_server_new2(&session_, callbacks, this, option);
if (rv != 0) {
return -1;
}
nghttp2_settings_entry ent{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100};
nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, &ent, 1);
return 0;
}
std::shared_ptr<http2_stream> http2_handler::create_stream(int32_t stream_id) {
auto stream = std::make_shared<http2_stream>(stream_id);
streams_.emplace(stream_id, stream);
auto self = shared_from_this();
auto &req = stream->get_request()->impl();
auto &res = stream->get_response()->impl();
req.handler(self);
req.stream(stream);
res.handler(self);
res.stream(stream);
return stream;
}
void http2_handler::close_stream(int32_t stream_id) {
streams_.erase(stream_id);
}
std::shared_ptr<http2_stream> http2_handler::find_stream(int32_t stream_id) {
auto i = streams_.find(stream_id);
if (i == std::end(streams_)) {
return nullptr;
}
return (*i).second;
}
void http2_handler::call_on_request(http2_stream &stream) {
request_cb_(stream.get_request(), stream.get_response());
}
bool http2_handler::should_stop() const {
return !nghttp2_session_want_read(session_) &&
!nghttp2_session_want_write(session_);
}
int http2_handler::start_response(http2_stream &stream) {
int rv;
auto &res = stream.get_response()->impl();
auto &headers = res.headers();
auto nva = std::vector<nghttp2_nv>();
nva.reserve(2 + headers.size());
auto status = util::utos(res.status_code());
auto date = cached_date;
nva.push_back(nghttp2::http2::make_nv_ls(":status", status));
nva.push_back(nghttp2::http2::make_nv_ls("date", *date));
for (auto &hd : headers) {
nva.push_back(nghttp2::http2::make_nv(hd.name, hd.value));
}
nghttp2_data_provider prd;
prd.source.ptr = &stream;
prd.read_callback =
[](nghttp2_session *session, int32_t stream_id, uint8_t *buf,
size_t length, uint32_t *data_flags, nghttp2_data_source *source,
void *user_data) -> ssize_t {
auto &stream = *static_cast<http2_stream *>(source->ptr);
auto rv = stream.get_response()->impl().call_read(buf, length);
if (rv.first < 0) {
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
if (rv.second) {
*data_flags |= NGHTTP2_DATA_FLAG_EOF;
} else if (rv.first == 0) {
return NGHTTP2_ERR_DEFERRED;
}
return rv.first;
};
rv = nghttp2_submit_response(session_, stream.get_stream_id(), nva.data(),
nva.size(), &prd);
if (rv != 0) {
return -1;
}
return 0;
}
void http2_handler::enter_callback() {
assert(!inside_callback_);
inside_callback_ = true;
}
void http2_handler::leave_callback() {
assert(inside_callback_);
inside_callback_ = false;
}
bool http2_handler::inside_callback() const { return inside_callback_; }
void http2_handler::stream_error(int32_t stream_id, uint32_t error_code) {
::nghttp2::asio_http2::server::stream_error(session_, stream_id, error_code);
}
void http2_handler::initiate_write() { writefun_(); }
void http2_handler::resume(http2_stream &stream) {
nghttp2_session_resume_data(session_, stream.get_stream_id());
}
int http2_handler::push_promise(http2_stream &stream, std::string method,
std::string path, std::vector<header> headers) {
int rv;
auto &req = stream.get_request()->impl();
auto nva = std::vector<nghttp2_nv>();
nva.reserve(5 + headers.size());
nva.push_back(nghttp2::http2::make_nv_ls(":method", method));
nva.push_back(nghttp2::http2::make_nv_ls(":scheme", req.scheme()));
if (!req.authority().empty()) {
nva.push_back(nghttp2::http2::make_nv_ls(":authority", req.authority()));
}
nva.push_back(nghttp2::http2::make_nv_ls(":path", path));
if (!req.host().empty()) {
nva.push_back(nghttp2::http2::make_nv_ls("host", req.host()));
}
for (auto &hd : headers) {
nva.push_back(nghttp2::http2::make_nv(hd.name, hd.value));
}
rv = nghttp2_submit_push_promise(session_, NGHTTP2_FLAG_NONE,
stream.get_stream_id(), nva.data(),
nva.size(), nullptr);
if (rv < 0) {
return -1;
}
auto promised_stream = create_stream(rv);
auto &promised_req = promised_stream->get_request()->impl();
promised_req.pushed(true);
promised_req.method(std::move(method));
promised_req.scheme(req.scheme());
promised_req.authority(req.authority());
promised_req.path(std::move(path));
promised_req.host(req.host());
promised_req.set_header(std::move(headers));
if (!req.host().empty()) {
promised_req.add_header("host", req.host());
}
return 0;
}
bool http2_handler::run_task(thread_cb start) {
auto strand = strand_;
try {
task_io_service_.post([start, strand]() {
channel chan;
chan.impl().strand(strand.get());
start(chan);
});
return true;
} catch (std::exception &ex) {
return false;
}
}
boost::asio::io_service &http2_handler::io_service() { return io_service_; }
callback_guard::callback_guard(http2_handler &h) : handler(h) {
handler.enter_callback();
}
callback_guard::~callback_guard() { handler.leave_callback(); }
} // namespace server
} // namespace asio_http2
} // namespace nghttp2

View File

@ -1,265 +0,0 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2014 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef HTTP2_HANDLER_H
#define HTTP2_HANDLER_H
#include "nghttp2_config.h"
#include <map>
#include <vector>
#include <functional>
#include <string>
#include <boost/array.hpp>
#include <boost/asio.hpp>
#include <nghttp2/nghttp2.h>
#include <nghttp2/asio_http2.h>
namespace nghttp2 {
namespace asio_http2 {
class channel_impl {
public:
channel_impl();
void post(void_cb cb);
void strand(boost::asio::io_service::strand *strand);
private:
boost::asio::io_service::strand *strand_;
};
namespace server {
class http2_handler;
class http2_stream;
class request_impl {
public:
request_impl();
const std::vector<header> &headers() const;
const std::string &method() const;
const std::string &scheme() const;
const std::string &authority() const;
const std::string &host() const;
const std::string &path() const;
bool push(std::string method, std::string path,
std::vector<header> headers = {});
bool pushed() const;
bool closed() const;
void on_data(data_cb cb);
void on_end(void_cb cb);
bool run_task(thread_cb start);
void set_header(std::vector<header> headers);
void add_header(std::string name, std::string value);
void method(std::string method);
void scheme(std::string scheme);
void authority(std::string authority);
void host(std::string host);
void path(std::string path);
void pushed(bool f);
void handler(std::weak_ptr<http2_handler> h);
void stream(std::weak_ptr<http2_stream> s);
void call_on_data(const uint8_t *data, std::size_t len);
void call_on_end();
private:
std::vector<header> headers_;
std::string method_;
std::string scheme_;
std::string authority_;
std::string host_;
std::string path_;
data_cb on_data_cb_;
void_cb on_end_cb_;
std::weak_ptr<http2_handler> handler_;
std::weak_ptr<http2_stream> stream_;
bool pushed_;
};
class response_impl {
public:
response_impl();
void write_head(unsigned int status_code, std::vector<header> headers = {});
void end(std::string data = "");
void end(read_cb cb);
void resume();
bool closed() const;
unsigned int status_code() const;
const std::vector<header> &headers() const;
bool started() const;
void handler(std::weak_ptr<http2_handler> h);
void stream(std::weak_ptr<http2_stream> s);
read_cb::result_type call_read(uint8_t *data, std::size_t len);
private:
std::vector<header> headers_;
read_cb read_cb_;
std::weak_ptr<http2_handler> handler_;
std::weak_ptr<http2_stream> stream_;
unsigned int status_code_;
bool started_;
};
class http2_stream {
public:
http2_stream(int32_t stream_id);
int32_t get_stream_id() const;
const std::shared_ptr<request> &get_request();
const std::shared_ptr<response> &get_response();
private:
std::shared_ptr<request> request_;
std::shared_ptr<response> response_;
int32_t stream_id_;
};
struct callback_guard {
callback_guard(http2_handler &h);
~callback_guard();
http2_handler &handler;
};
typedef std::function<void(void)> connection_write;
class http2_handler : public std::enable_shared_from_this<http2_handler> {
public:
http2_handler(boost::asio::io_service &io_service,
boost::asio::io_service &task_io_service,
connection_write writefun, request_cb cb);
~http2_handler();
int start();
std::shared_ptr<http2_stream> create_stream(int32_t stream_id);
void close_stream(int32_t stream_id);
std::shared_ptr<http2_stream> find_stream(int32_t stream_id);
void call_on_request(http2_stream &stream);
bool should_stop() const;
int start_response(http2_stream &stream);
void stream_error(int32_t stream_id, uint32_t error_code);
void initiate_write();
void enter_callback();
void leave_callback();
bool inside_callback() const;
void resume(http2_stream &stream);
int push_promise(http2_stream &stream, std::string method, std::string path,
std::vector<header> headers);
bool run_task(thread_cb start);
boost::asio::io_service &io_service();
template <size_t N>
int on_read(const boost::array<uint8_t, N> &buffer, std::size_t len) {
callback_guard cg(*this);
int rv;
rv = nghttp2_session_mem_recv(session_, buffer.data(), len);
if (rv < 0) {
return -1;
}
return 0;
}
template <size_t N>
int on_write(boost::array<uint8_t, N> &buffer, std::size_t &len) {
callback_guard cg(*this);
len = 0;
if (buf_) {
std::copy_n(buf_, buflen_, std::begin(buffer));
len += buflen_;
buf_ = nullptr;
buflen_ = 0;
}
for (;;) {
const uint8_t *data;
auto nread = nghttp2_session_mem_send(session_, &data);
if (nread < 0) {
return -1;
}
if (nread == 0) {
break;
}
if (len + nread > buffer.size()) {
buf_ = data;
buflen_ = nread;
break;
}
std::copy_n(data, nread, std::begin(buffer) + len);
len += nread;
}
return 0;
}
private:
std::map<int32_t, std::shared_ptr<http2_stream>> streams_;
connection_write writefun_;
request_cb request_cb_;
boost::asio::io_service &io_service_;
boost::asio::io_service &task_io_service_;
std::shared_ptr<boost::asio::io_service::strand> strand_;
nghttp2_session *session_;
const uint8_t *buf_;
std::size_t buflen_;
bool inside_callback_;
};
} // namespace server
} // namespace asio_http2
} // namespace nghttp
#endif // HTTP2_HANDLER_H

View File

@ -33,22 +33,15 @@
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "asio_io_service_pool.h"
#include "asio_server.h"
#include <stdexcept>
#include <future>
#include <boost/thread/thread.hpp>
#include <boost/bind.hpp>
namespace nghttp2 {
namespace asio_http2 {
namespace server {
io_service_pool::io_service_pool(std::size_t pool_size,
std::size_t thread_pool_size)
: next_io_service_(0), thread_pool_size_(thread_pool_size) {
io_service_pool::io_service_pool(std::size_t pool_size) : next_io_service_(0) {
if (pool_size == 0) {
throw std::runtime_error("io_service_pool size is 0");
}
@ -61,16 +54,9 @@ io_service_pool::io_service_pool(std::size_t pool_size,
io_services_.push_back(io_service);
work_.push_back(work);
}
auto work = std::make_shared<boost::asio::io_service::work>(task_io_service_);
work_.push_back(work);
}
void io_service_pool::run() {
for (std::size_t i = 0; i < thread_pool_size_; ++i) {
thread_pool_.create_thread([this]() { task_io_service_.run(); });
}
// Create a pool of threads to run all of the io_services.
auto futs = std::vector<std::future<std::size_t>>();
@ -85,8 +71,6 @@ void io_service_pool::run() {
for (auto &fut : futs) {
fut.get();
}
thread_pool_.join_all();
}
void io_service_pool::stop() {
@ -94,8 +78,6 @@ void io_service_pool::stop() {
for (auto &iosv : io_services_) {
iosv->stop();
}
task_io_service_.stop();
}
boost::asio::io_service &io_service_pool::get_io_service() {
@ -108,12 +90,6 @@ boost::asio::io_service &io_service_pool::get_io_service() {
return io_service;
}
boost::asio::io_service &io_service_pool::get_task_io_service() {
return task_io_service_;
}
} // namespace server
} // namespace asio_http2
} // namespace nghttp2

View File

@ -34,14 +34,14 @@
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef HTTP_SERVER2_IO_SERVICE_POOL_HPP
#define HTTP_SERVER2_IO_SERVICE_POOL_HPP
#ifndef ASIO_IO_SERVICE_POOL_H
#define ASIO_IO_SERVICE_POOL_H
#include "nghttp2_config.h"
#include <vector>
#include <memory>
#include <boost/asio.hpp>
#include <boost/noncopyable.hpp>
#include <boost/thread.hpp>
@ -51,13 +51,11 @@ namespace nghttp2 {
namespace asio_http2 {
namespace server {
/// A pool of io_service objects.
class io_service_pool : private boost::noncopyable {
public:
/// Construct the io_service pool.
explicit io_service_pool(std::size_t pool_size, std::size_t thread_pool_size);
explicit io_service_pool(std::size_t pool_size);
/// Run all io_service objects in the pool.
void run();
@ -68,31 +66,19 @@ public:
/// Get an io_service to use.
boost::asio::io_service &get_io_service();
boost::asio::io_service &get_task_io_service();
private:
typedef std::shared_ptr<boost::asio::io_service> io_service_ptr;
typedef std::shared_ptr<boost::asio::io_service::work> work_ptr;
/// The pool of io_services.
std::vector<io_service_ptr> io_services_;
boost::asio::io_service task_io_service_;
boost::thread_group thread_pool_;
std::vector<std::shared_ptr<boost::asio::io_service>> io_services_;
/// The work that keeps the io_services running.
std::vector<work_ptr> work_;
std::vector<std::shared_ptr<boost::asio::io_service::work>> work_;
/// The next io_service to use for a connection.
std::size_t next_io_service_;
std::size_t thread_pool_size_;
};
} // namespace server
} // namespace asio_http2
} // namespace nghttp2
#endif // HTTP_SERVER2_IO_SERVICE_POOL_HPP
#endif // ASIO_IO_SERVICE_POOL_H

View File

@ -38,19 +38,21 @@
#include <boost/date_time/posix_time/posix_time.hpp>
#include "asio_server_connection.h"
#include "util.h"
namespace nghttp2 {
namespace asio_http2 {
namespace server {
server::server(const std::string &address, uint16_t port,
std::size_t io_service_pool_size, std::size_t thread_pool_size,
request_cb cb,
std::size_t io_service_pool_size, serve_mux &mux,
std::unique_ptr<boost::asio::ssl::context> ssl_ctx, int backlog)
: io_service_pool_(io_service_pool_size, thread_pool_size),
: io_service_pool_(io_service_pool_size),
signals_(io_service_pool_.get_io_service()),
tick_timer_(io_service_pool_.get_io_service(),
boost::posix_time::seconds(1)),
ssl_ctx_(std::move(ssl_ctx)), request_cb_(std::move(cb)) {
ssl_ctx_(std::move(ssl_ctx)), mux_(mux) {
// Register to handle the signals that indicate when the server should exit.
// It is safe to register for the same signal multiple times in a program,
// provided all registration for the specified signal is made through Asio.
@ -83,7 +85,9 @@ server::server(const std::string &address, uint16_t port,
acceptors_.push_back(std::move(acceptor));
}
start_accept();
for (auto &acceptor : acceptors_) {
start_accept(acceptor);
}
start_timer();
}
@ -110,50 +114,44 @@ void server::start_timer() {
typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_socket;
void server::start_accept() {
void server::start_accept(boost::asio::ip::tcp::acceptor &acceptor) {
if (ssl_ctx_) {
auto new_connection = std::make_shared<connection<ssl_socket>>(
request_cb_, io_service_pool_.get_task_io_service(),
io_service_pool_.get_io_service(), *ssl_ctx_);
mux_, io_service_pool_.get_io_service(), *ssl_ctx_);
for (auto &acceptor : acceptors_) {
acceptor.async_accept(
new_connection->socket().lowest_layer(),
[this, new_connection](const boost::system::error_code &e) {
if (!e) {
new_connection->socket().lowest_layer().set_option(
boost::asio::ip::tcp::no_delay(true));
new_connection->socket().async_handshake(
boost::asio::ssl::stream_base::server,
[new_connection](const boost::system::error_code &e) {
if (!e) {
new_connection->start();
}
});
}
acceptor.async_accept(
new_connection->socket().lowest_layer(),
[this, &acceptor, new_connection](const boost::system::error_code &e) {
if (!e) {
new_connection->socket().lowest_layer().set_option(
boost::asio::ip::tcp::no_delay(true));
new_connection->socket().async_handshake(
boost::asio::ssl::stream_base::server,
[new_connection](const boost::system::error_code &e) {
if (!e) {
new_connection->start();
}
});
}
start_accept();
});
}
start_accept(acceptor);
});
} else {
auto new_connection =
std::make_shared<connection<boost::asio::ip::tcp::socket>>(
request_cb_, io_service_pool_.get_task_io_service(),
io_service_pool_.get_io_service());
mux_, io_service_pool_.get_io_service());
for (auto &acceptor : acceptors_) {
acceptor.async_accept(
new_connection->socket(),
[this, new_connection](const boost::system::error_code &e) {
if (!e) {
new_connection->socket().set_option(
boost::asio::ip::tcp::no_delay(true));
new_connection->start();
}
acceptor.async_accept(
new_connection->socket(),
[this, &acceptor, new_connection](const boost::system::error_code &e) {
if (!e) {
new_connection->socket().set_option(
boost::asio::ip::tcp::no_delay(true));
new_connection->start();
}
start_accept();
});
}
start_accept(acceptor);
});
}
}

View File

@ -34,21 +34,19 @@
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef HTTP_SERVER2_SERVER_HPP
#define HTTP_SERVER2_SERVER_HPP
#ifndef ASIO_SERVER_H
#define ASIO_SERVER_H
#include "nghttp2_config.h"
#include <string>
#include <vector>
#include <memory>
#include <boost/noncopyable.hpp>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <nghttp2/asio_http2.h>
#include <nghttp2/asio_http2_server.h>
#include "asio_connection.h"
#include "asio_io_service_pool.h"
namespace nghttp2 {
@ -57,14 +55,15 @@ namespace asio_http2 {
namespace server {
class serve_mux;
/// The top-level class of the HTTP server.
class server : private boost::noncopyable {
public:
/// Construct the server to listen on the specified TCP address and port, and
/// serve up files from the given directory.
explicit server(const std::string &address, uint16_t port,
std::size_t io_service_pool_size,
std::size_t thread_pool_size, request_cb cb,
std::size_t io_service_pool_size, serve_mux &mux_,
std::unique_ptr<boost::asio::ssl::context> ssl_ctx,
int backlog = -1);
@ -73,7 +72,7 @@ public:
private:
/// Initiate an asynchronous accept operation.
void start_accept();
void start_accept(boost::asio::ip::tcp::acceptor &acceptor);
void start_timer();
@ -90,7 +89,7 @@ private:
std::unique_ptr<boost::asio::ssl::context> ssl_ctx_;
request_cb request_cb_;
serve_mux &mux_;
};
} // namespace server
@ -99,4 +98,4 @@ private:
} // namespace nghttp2
#endif // HTTP_SERVER2_SERVER_HPP
#endif // ASIO_SERVER_H

View File

@ -34,19 +34,20 @@
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef HTTP_SERVER2_CONNECTION_HPP
#define HTTP_SERVER2_CONNECTION_HPP
#ifndef ASIO_SERVER_CONNECTION_H
#define ASIO_SERVER_CONNECTION_H
#include "nghttp2_config.h"
#include <memory>
#include <boost/asio.hpp>
#include <boost/noncopyable.hpp>
#include <boost/array.hpp>
#include <nghttp2/asio_http2.h>
#include "asio_http2_handler.h"
#include <nghttp2/asio_http2_server.h>
#include "asio_server_http2_handler.h"
#include "asio_server_serve_mux.h"
#include "util.h"
namespace nghttp2 {
@ -62,16 +63,14 @@ class connection : public std::enable_shared_from_this<connection<socket_type>>,
public:
/// Construct a connection with the given io_service.
template <typename... SocketArgs>
explicit connection(request_cb cb, boost::asio::io_service &task_io_service,
SocketArgs &&... args)
: socket_(std::forward<SocketArgs>(args)...), request_cb_(std::move(cb)),
task_io_service_(task_io_service), writing_(false) {}
explicit connection(serve_mux &mux, SocketArgs &&... args)
: socket_(std::forward<SocketArgs>(args)...), mux_(mux), writing_(false) {
}
/// Start the first asynchronous operation for the connection.
void start() {
handler_ = std::make_shared<http2_handler>(
socket_.get_io_service(), task_io_service_, [this]() { do_write(); },
request_cb_);
handler_ = std::make_shared<http2_handler>(socket_.get_io_service(),
[this]() { do_write(); }, mux_);
if (handler_->start() != 0) {
return;
}
@ -149,9 +148,7 @@ public:
private:
socket_type socket_;
request_cb request_cb_;
boost::asio::io_service &task_io_service_;
serve_mux &mux_;
std::shared_ptr<http2_handler> handler_;
@ -169,4 +166,4 @@ private:
} // namespace nghttp2
#endif // HTTP_SERVER2_CONNECTION_HPP
#endif // ASIO_SERVER_CONNECTION_H

63
src/asio_server_http2.cc Normal file
View File

@ -0,0 +1,63 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2014 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "nghttp2_config.h"
#include <nghttp2/asio_http2_server.h>
#include "asio_server_http2_impl.h"
#include "asio_server.h"
#include "template.h"
namespace nghttp2 {
namespace asio_http2 {
namespace server {
http2::http2() : impl_(make_unique<http2_impl>()) {}
http2::~http2() {}
void http2::listen_and_serve(const std::string &address, uint16_t port) {
impl_->listen_and_serve(address, port);
}
void http2::num_threads(size_t num_threads) { impl_->num_threads(num_threads); }
void http2::tls(std::string private_key_file, std::string certificate_file) {
impl_->tls(std::move(private_key_file), std::move(certificate_file));
}
void http2::backlog(int backlog) { impl_->backlog(backlog); }
bool http2::handle(std::string pattern, request_cb cb) {
return impl_->handle(std::move(pattern), std::move(cb));
}
} // namespace server
} // namespace asio_http2
} // namespace nghttp2

View File

@ -0,0 +1,439 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2014 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "asio_server_http2_handler.h"
#include <iostream>
#include "asio_common.h"
#include "asio_server_serve_mux.h"
#include "asio_server_stream.h"
#include "asio_server_request_impl.h"
#include "asio_server_response_impl.h"
#include "http2.h"
#include "util.h"
#include "template.h"
namespace nghttp2 {
namespace asio_http2 {
namespace server {
extern std::shared_ptr<std::string> cached_date;
namespace {
int stream_error(nghttp2_session *session, int32_t stream_id,
uint32_t error_code) {
return nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id,
error_code);
}
} // namespace
namespace {
int on_begin_headers_callback(nghttp2_session *session,
const nghttp2_frame *frame, void *user_data) {
auto handler = static_cast<http2_handler *>(user_data);
if (frame->hd.type != NGHTTP2_HEADERS ||
frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
return 0;
}
handler->create_stream(frame->hd.stream_id);
return 0;
}
} // namespace
namespace {
int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen, uint8_t flags,
void *user_data) {
auto handler = static_cast<http2_handler *>(user_data);
auto stream_id = frame->hd.stream_id;
if (frame->hd.type != NGHTTP2_HEADERS ||
frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
return 0;
}
auto strm = handler->find_stream(stream_id);
if (!strm) {
return 0;
}
auto &req = strm->request().impl();
auto &uref = req.uri();
switch (nghttp2::http2::lookup_token(name, namelen)) {
case nghttp2::http2::HD__METHOD:
req.method(std::string(value, value + valuelen));
break;
case nghttp2::http2::HD__SCHEME:
uref.scheme.assign(value, value + valuelen);
break;
case nghttp2::http2::HD__AUTHORITY:
uref.host.assign(value, value + valuelen);
break;
case nghttp2::http2::HD__PATH:
split_path(uref, value, value + valuelen);
break;
case nghttp2::http2::HD_HOST:
if (uref.host.empty()) {
uref.host.assign(value, value + valuelen);
}
// fall through
default:
req.header().emplace(std::string(name, name + namelen),
header_value{std::string(value, value + valuelen),
(flags & NGHTTP2_NV_FLAG_NO_INDEX) != 0});
}
return 0;
}
} // namespace
namespace {
int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame,
void *user_data) {
auto handler = static_cast<http2_handler *>(user_data);
auto strm = handler->find_stream(frame->hd.stream_id);
switch (frame->hd.type) {
case NGHTTP2_DATA:
if (!strm) {
break;
}
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
strm->request().impl().call_on_data(nullptr, 0);
}
break;
case NGHTTP2_HEADERS: {
if (!strm || frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
break;
}
handler->call_on_request(*strm);
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
strm->request().impl().call_on_data(nullptr, 0);
}
break;
}
}
return 0;
}
} // namespace
namespace {
int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
int32_t stream_id, const uint8_t *data,
size_t len, void *user_data) {
auto handler = static_cast<http2_handler *>(user_data);
auto strm = handler->find_stream(stream_id);
if (!strm) {
return 0;
}
strm->request().impl().call_on_data(data, len);
return 0;
}
} // namespace
namespace {
int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
uint32_t error_code, void *user_data) {
auto handler = static_cast<http2_handler *>(user_data);
auto strm = handler->find_stream(stream_id);
if (!strm) {
return 0;
}
strm->response().impl().call_on_close(error_code);
handler->close_stream(stream_id);
return 0;
}
} // namespace
namespace {
int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame,
void *user_data) {
auto handler = static_cast<http2_handler *>(user_data);
if (frame->hd.type != NGHTTP2_PUSH_PROMISE) {
return 0;
}
auto strm = handler->find_stream(frame->push_promise.promised_stream_id);
if (!strm) {
return 0;
}
auto &res = strm->response().impl();
res.push_promise_sent();
return 0;
}
} // namespace
namespace {
int on_frame_not_send_callback(nghttp2_session *session,
const nghttp2_frame *frame, int lib_error_code,
void *user_data) {
if (frame->hd.type != NGHTTP2_HEADERS) {
return 0;
}
// Issue RST_STREAM so that stream does not hang around.
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id,
NGHTTP2_INTERNAL_ERROR);
return 0;
}
} // namespace
http2_handler::http2_handler(boost::asio::io_service &io_service,
connection_write writefun, serve_mux &mux)
: writefun_(writefun), mux_(mux), io_service_(io_service),
session_(nullptr), buf_(nullptr), buflen_(0), inside_callback_(false) {}
http2_handler::~http2_handler() { nghttp2_session_del(session_); }
int http2_handler::start() {
int rv;
nghttp2_session_callbacks *callbacks;
rv = nghttp2_session_callbacks_new(&callbacks);
if (rv != 0) {
return -1;
}
auto cb_del = defer(nghttp2_session_callbacks_del, callbacks);
nghttp2_session_callbacks_set_on_begin_headers_callback(
callbacks, on_begin_headers_callback);
nghttp2_session_callbacks_set_on_header_callback(callbacks,
on_header_callback);
nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
on_frame_recv_callback);
nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
callbacks, on_data_chunk_recv_callback);
nghttp2_session_callbacks_set_on_stream_close_callback(
callbacks, on_stream_close_callback);
nghttp2_session_callbacks_set_on_frame_send_callback(callbacks,
on_frame_send_callback);
nghttp2_session_callbacks_set_on_frame_not_send_callback(
callbacks, on_frame_not_send_callback);
nghttp2_option *option;
rv = nghttp2_option_new(&option);
if (rv != 0) {
return -1;
}
auto opt_del = defer(nghttp2_option_del, option);
nghttp2_option_set_recv_client_preface(option, 1);
rv = nghttp2_session_server_new2(&session_, callbacks, this, option);
if (rv != 0) {
return -1;
}
nghttp2_settings_entry ent{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100};
nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, &ent, 1);
return 0;
}
stream *http2_handler::create_stream(int32_t stream_id) {
auto p = streams_.emplace(stream_id, make_unique<stream>(this, stream_id));
assert(p.second);
return (*p.first).second.get();
}
void http2_handler::close_stream(int32_t stream_id) {
streams_.erase(stream_id);
}
stream *http2_handler::find_stream(int32_t stream_id) {
auto i = streams_.find(stream_id);
if (i == std::end(streams_)) {
return nullptr;
}
return (*i).second.get();
}
void http2_handler::call_on_request(stream &strm) {
auto cb = mux_.handler(strm.request().impl());
cb(strm.request(), strm.response());
}
bool http2_handler::should_stop() const {
return !nghttp2_session_want_read(session_) &&
!nghttp2_session_want_write(session_);
}
int http2_handler::start_response(stream &strm) {
int rv;
auto &res = strm.response().impl();
auto &header = res.header();
auto nva = std::vector<nghttp2_nv>();
nva.reserve(2 + header.size());
auto status = util::utos(res.status_code());
auto date = cached_date;
nva.push_back(nghttp2::http2::make_nv_ls(":status", status));
nva.push_back(nghttp2::http2::make_nv_ls("date", *date));
for (auto &hd : header) {
nva.push_back(nghttp2::http2::make_nv(hd.first, hd.second.value,
hd.second.sensitive));
}
nghttp2_data_provider prd;
prd.source.ptr = &strm;
prd.read_callback =
[](nghttp2_session *session, int32_t stream_id, uint8_t *buf,
size_t length, uint32_t *data_flags, nghttp2_data_source *source,
void *user_data) -> ssize_t {
auto &strm = *static_cast<stream *>(source->ptr);
return strm.response().impl().call_read(buf, length, data_flags);
};
rv = nghttp2_submit_response(session_, strm.get_stream_id(), nva.data(),
nva.size(), &prd);
if (rv != 0) {
return -1;
}
signal_write();
return 0;
}
void http2_handler::enter_callback() {
assert(!inside_callback_);
inside_callback_ = true;
}
void http2_handler::leave_callback() {
assert(inside_callback_);
inside_callback_ = false;
}
void http2_handler::stream_error(int32_t stream_id, uint32_t error_code) {
::nghttp2::asio_http2::server::stream_error(session_, stream_id, error_code);
signal_write();
}
void http2_handler::signal_write() {
if (!inside_callback_) {
initiate_write();
}
}
void http2_handler::initiate_write() { writefun_(); }
void http2_handler::resume(stream &strm) {
nghttp2_session_resume_data(session_, strm.get_stream_id());
signal_write();
}
response *http2_handler::push_promise(boost::system::error_code &ec,
stream &strm, std::string method,
std::string raw_path_query,
header_map h) {
int rv;
ec.clear();
auto &req = strm.request().impl();
auto nva = std::vector<nghttp2_nv>();
nva.reserve(4 + h.size());
nva.push_back(nghttp2::http2::make_nv_ls(":method", method));
nva.push_back(nghttp2::http2::make_nv_ls(":scheme", req.uri().scheme));
nva.push_back(nghttp2::http2::make_nv_ls(":authority", req.uri().host));
nva.push_back(nghttp2::http2::make_nv_ls(":path", raw_path_query));
for (auto &hd : h) {
nva.push_back(nghttp2::http2::make_nv(hd.first, hd.second.value,
hd.second.sensitive));
}
rv = nghttp2_submit_push_promise(session_, NGHTTP2_FLAG_NONE,
strm.get_stream_id(), nva.data(), nva.size(),
nullptr);
if (rv < 0) {
ec = make_error_code(static_cast<nghttp2_error>(rv));
return nullptr;
}
auto promised_strm = create_stream(rv);
auto &promised_req = promised_strm->request().impl();
promised_req.header(std::move(h));
promised_req.method(std::move(method));
auto &uref = promised_req.uri();
uref.scheme = req.uri().scheme;
uref.host = req.uri().host;
split_path(uref, std::begin(raw_path_query), std::end(raw_path_query));
auto &promised_res = promised_strm->response().impl();
promised_res.pushed(true);
signal_write();
return &promised_strm->response();
}
boost::asio::io_service &http2_handler::io_service() { return io_service_; }
callback_guard::callback_guard(http2_handler &h) : handler(h) {
handler.enter_callback();
}
callback_guard::~callback_guard() { handler.leave_callback(); }
} // namespace server
} // namespace asio_http2
} // namespace nghttp2

View File

@ -0,0 +1,161 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2014 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef ASIO_SERVER_HTTP2_HANDLER_H
#define ASIO_SERVER_HTTP2_HANDLER_H
#include "nghttp2_config.h"
#include <map>
#include <functional>
#include <string>
#include <boost/array.hpp>
#include <nghttp2/asio_http2_server.h>
namespace nghttp2 {
namespace asio_http2 {
namespace server {
class http2_handler;
class stream;
class serve_mux;
struct callback_guard {
callback_guard(http2_handler &h);
~callback_guard();
http2_handler &handler;
};
using connection_write = std::function<void(void)>;
class http2_handler : public std::enable_shared_from_this<http2_handler> {
public:
http2_handler(boost::asio::io_service &io_service, connection_write writefun,
serve_mux &mux);
~http2_handler();
int start();
stream *create_stream(int32_t stream_id);
void close_stream(int32_t stream_id);
stream *find_stream(int32_t stream_id);
void call_on_request(stream &s);
bool should_stop() const;
int start_response(stream &s);
void stream_error(int32_t stream_id, uint32_t error_code);
void initiate_write();
void enter_callback();
void leave_callback();
void resume(stream &s);
response *push_promise(boost::system::error_code &ec, stream &s,
std::string method, std::string raw_path_query,
header_map h);
void signal_write();
boost::asio::io_service &io_service();
template <size_t N>
int on_read(const boost::array<uint8_t, N> &buffer, std::size_t len) {
callback_guard cg(*this);
int rv;
rv = nghttp2_session_mem_recv(session_, buffer.data(), len);
if (rv < 0) {
return -1;
}
return 0;
}
template <size_t N>
int on_write(boost::array<uint8_t, N> &buffer, std::size_t &len) {
callback_guard cg(*this);
len = 0;
if (buf_) {
std::copy_n(buf_, buflen_, std::begin(buffer));
len += buflen_;
buf_ = nullptr;
buflen_ = 0;
}
for (;;) {
const uint8_t *data;
auto nread = nghttp2_session_mem_send(session_, &data);
if (nread < 0) {
return -1;
}
if (nread == 0) {
break;
}
if (len + nread > buffer.size()) {
buf_ = data;
buflen_ = nread;
break;
}
std::copy_n(data, nread, std::begin(buffer) + len);
len += nread;
}
return 0;
}
private:
std::map<int32_t, std::shared_ptr<stream>> streams_;
connection_write writefun_;
serve_mux &mux_;
boost::asio::io_service &io_service_;
nghttp2_session *session_;
const uint8_t *buf_;
std::size_t buflen_;
bool inside_callback_;
};
} // namespace server
} // namespace asio_http2
} // namespace nghttp
#endif // ASIO_SERVER_HTTP2_HANDLER_H

View File

@ -22,14 +22,10 @@
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "asio_http2_impl.h"
#include <boost/asio/ssl.hpp>
#include "asio_server_http2_impl.h"
#include <openssl/ssl.h>
#include <nghttp2/nghttp2.h>
#include "asio_server.h"
#include "util.h"
#include "ssl.h"
@ -41,28 +37,7 @@ namespace asio_http2 {
namespace server {
http2::http2() : impl_(make_unique<http2_impl>()) {}
http2::~http2() {}
void http2::listen(const std::string &address, uint16_t port, request_cb cb) {
impl_->listen(address, port, std::move(cb));
}
void http2::num_threads(size_t num_threads) { impl_->num_threads(num_threads); }
void http2::tls(std::string private_key_file, std::string certificate_file) {
impl_->tls(std::move(private_key_file), std::move(certificate_file));
}
void http2::num_concurrent_tasks(size_t num_concurrent_tasks) {
impl_->num_concurrent_tasks(num_concurrent_tasks);
}
void http2::backlog(int backlog) { impl_->backlog(backlog); }
http2_impl::http2_impl()
: num_threads_(1), num_concurrent_tasks_(1), backlog_(-1) {}
http2_impl::http2_impl() : num_threads_(1), backlog_(-1) {}
namespace {
std::vector<unsigned char> &get_alpn_token() {
@ -71,8 +46,7 @@ std::vector<unsigned char> &get_alpn_token() {
}
} // namespace
void http2_impl::listen(const std::string &address, uint16_t port,
request_cb cb) {
void http2_impl::listen_and_serve(const std::string &address, uint16_t port) {
std::unique_ptr<boost::asio::ssl::context> ssl_ctx;
if (!private_key_file_.empty() && !certificate_file_.empty()) {
@ -92,7 +66,6 @@ void http2_impl::listen(const std::string &address, uint16_t port,
SSL_OP_CIPHER_SERVER_PREFERENCE);
SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);
SSL_CTX_set_mode(ctx, SSL_MODE_RELEASE_BUFFERS);
SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
SSL_CTX_set_cipher_list(ctx, ssl::DEFAULT_CIPHER_LIST);
@ -115,8 +88,7 @@ void http2_impl::listen(const std::string &address, uint16_t port,
nullptr);
}
server(address, port, num_threads_, num_concurrent_tasks_, std::move(cb),
std::move(ssl_ctx), backlog_).run();
server(address, port, num_threads_, mux_, std::move(ssl_ctx), backlog_).run();
}
void http2_impl::num_threads(size_t num_threads) { num_threads_ = num_threads; }
@ -127,57 +99,14 @@ void http2_impl::tls(std::string private_key_file,
certificate_file_ = std::move(certificate_file);
}
void http2_impl::num_concurrent_tasks(size_t num_concurrent_tasks) {
num_concurrent_tasks_ = num_concurrent_tasks;
}
void http2_impl::backlog(int backlog) { backlog_ = backlog; }
bool http2_impl::handle(std::string pattern, request_cb cb) {
return mux_.handle(std::move(pattern), std::move(cb));
}
} // namespace server
template <typename F, typename... T>
std::shared_ptr<Defer<F, T...>> defer_shared(F &&f, T &&... t) {
return std::make_shared<Defer<F, T...>>(std::forward<F>(f),
std::forward<T>(t)...);
}
read_cb file_reader(const std::string &path) {
auto fd = open(path.c_str(), O_RDONLY);
if (fd == -1) {
return read_cb();
}
return file_reader_from_fd(fd);
}
read_cb file_reader_from_fd(int fd) {
auto d = defer_shared(close, fd);
return [fd, d](uint8_t *buf, size_t len) -> read_cb::result_type {
int rv;
while ((rv = read(fd, buf, len)) == -1 && errno == EINTR)
;
if (rv == -1) {
return std::make_pair(-1, false);
}
if (rv == 0) {
return std::make_pair(rv, true);
}
return std::make_pair(rv, false);
};
}
bool check_path(const std::string &path) { return util::check_path(path); }
std::string percent_decode(const std::string &s) {
return util::percentDecode(std::begin(s), std::end(s));
}
std::string http_date(int64_t t) { return util::http_date(t); }
} // namespace asio_http2
} // namespace nghttp2

View File

@ -22,12 +22,14 @@
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef ASIO_HTTP2_IMPL_H
#define ASIO_HTTP2_IMPL_H
#ifndef ASIO_SERVER_HTTP2_IMPL_H
#define ASIO_SERVER_HTTP2_IMPL_H
#include "nghttp2_config.h"
#include <nghttp2/asio_http2.h>
#include <nghttp2/asio_http2_server.h>
#include "asio_server_serve_mux.h"
namespace nghttp2 {
@ -40,19 +42,19 @@ class server;
class http2_impl {
public:
http2_impl();
void listen(const std::string &address, uint16_t port, request_cb cb);
void listen_and_serve(const std::string &address, uint16_t port);
void num_threads(size_t num_threads);
void tls(std::string private_key_file, std::string certificate_file);
void num_concurrent_tasks(size_t num_concurrent_tasks);
void backlog(int backlog);
bool handle(std::string pattern, request_cb cb);
private:
std::string private_key_file_;
std::string certificate_file_;
std::unique_ptr<server> server_;
std::size_t num_threads_;
std::size_t num_concurrent_tasks_;
int backlog_;
serve_mux mux_;
};
} // namespace server
@ -61,4 +63,4 @@ private:
} // namespace nghttp2
#endif // ASIO_HTTP2_IMPL_H
#endif // ASIO_SERVER_HTTP2_IMPL_H

View File

@ -0,0 +1,55 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "nghttp2_config.h"
#include <nghttp2/asio_http2_server.h>
#include "asio_server_request_impl.h"
#include "template.h"
namespace nghttp2 {
namespace asio_http2 {
namespace server {
request::request() : impl_(make_unique<request_impl>()) {}
request::~request() {}
const header_map &request::header() const { return impl_->header(); }
const std::string &request::method() const { return impl_->method(); }
const uri_ref &request::uri() const { return impl_->uri(); }
void request::on_data(data_cb cb) const {
return impl_->on_data(std::move(cb));
}
request_impl &request::impl() const { return *impl_; }
} // namespace server
} // namespace asio_http2
} // namespace nghttp2

View File

@ -0,0 +1,77 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "asio_server_request_handler.h"
#include "util.h"
#include "http2.h"
namespace nghttp2 {
namespace asio_http2 {
namespace server {
namespace {
std::string create_html(int status_code) {
std::string res;
res.reserve(512);
auto status = ::nghttp2::http2::get_status_string(status_code);
res += R"(<!DOCTYPE html><html lang="en"><title>)";
res += status;
res += "</title><body><h1>";
res += status;
res += "</h1></body></html>";
return res;
}
} // namespace
request_cb redirect_handler(int status_code, std::string uri) {
return [status_code, uri](const request &req, const response &res) {
header_map h;
h.emplace("location", header_value{std::move(uri)});
std::string html;
if (req.method() == "GET") {
html = create_html(status_code);
}
h.emplace("content-length", header_value{util::utos(html.size())});
res.write_head(status_code, std::move(h));
res.end(std::move(html));
};
}
request_cb status_handler(int status_code) {
return [status_code](const request &req, const response &res) {
auto html = create_html(status_code);
header_map h;
h.emplace("content-length", header_value{util::utos(html.size())});
h.emplace("content-type", header_value{"text/html; charset=utf-8"});
res.write_head(status_code, std::move(h));
res.end(std::move(html));
};
}
} // namespace server
} // namespace asio_http2
} // namespace nghttp2

View File

@ -0,0 +1,32 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef ASIO_SERVER_REQUEST_HANDLER_H
#define ASIO_SERVER_REQUEST_HANDLER_H
#include "nghttp2_config.h"
#include <nghttp2/asio_http2_server.h>
#endif // ASIO_SERVER_REQUEST_HANDLER_H

View File

@ -0,0 +1,59 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "asio_server_request_impl.h"
namespace nghttp2 {
namespace asio_http2 {
namespace server {
request_impl::request_impl() : strm_(nullptr) {}
const header_map &request_impl::header() const { return header_; }
const std::string &request_impl::method() const { return method_; }
const uri_ref &request_impl::uri() const { return uri_; }
uri_ref &request_impl::uri() { return uri_; }
void request_impl::header(header_map h) { header_ = std::move(h); }
header_map &request_impl::header() { return header_; }
void request_impl::method(std::string arg) { method_ = std::move(arg); }
void request_impl::on_data(data_cb cb) { on_data_cb_ = std::move(cb); }
void request_impl::stream(class stream *s) { strm_ = s; }
void request_impl::call_on_data(const uint8_t *data, std::size_t len) {
if (on_data_cb_) {
on_data_cb_(data, len);
}
}
} // namespace server
} // namespace asio_http2
} // namespace nghttp2

View File

@ -0,0 +1,69 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef ASIO_SERVER_REQUEST_IMPL_H
#define ASIO_SERVER_REQUEST_IMPL_H
#include "nghttp2_config.h"
#include <nghttp2/asio_http2_server.h>
namespace nghttp2 {
namespace asio_http2 {
namespace server {
class stream;
class request_impl {
public:
request_impl();
void header(header_map h);
const header_map &header() const;
header_map &header();
void method(std::string method);
const std::string &method() const;
const uri_ref &uri() const;
uri_ref &uri();
void on_data(data_cb cb);
void stream(class stream *s);
void call_on_data(const uint8_t *data, std::size_t len);
private:
class stream *strm_;
header_map header_;
std::string method_;
uri_ref uri_;
data_cb on_data_cb_;
};
} // namespace server
} // namespace asio_http2
} // namespace nghttp2
#endif // ASIO_SERVER_REQUEST_IMPL_H

View File

@ -0,0 +1,71 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "nghttp2_config.h"
#include <nghttp2/asio_http2_server.h>
#include "asio_server_response_impl.h"
#include "template.h"
namespace nghttp2 {
namespace asio_http2 {
namespace server {
response::response() : impl_(make_unique<response_impl>()) {}
response::~response() {}
void response::write_head(unsigned int status_code, header_map h) const {
impl_->write_head(status_code, std::move(h));
}
void response::end(std::string data) const { impl_->end(std::move(data)); }
void response::end(generator_cb cb) const { impl_->end(std::move(cb)); }
void response::on_close(close_cb cb) const { impl_->on_close(std::move(cb)); }
void response::cancel(uint32_t error_code) const { impl_->cancel(error_code); }
const response *response::push(boost::system::error_code &ec,
std::string method, std::string path,
header_map h) const {
return impl_->push(ec, std::move(method), std::move(path), std::move(h));
}
void response::resume() const { impl_->resume(); }
unsigned int response::status_code() const { return impl_->status_code(); }
boost::asio::io_service &response::io_service() const {
return impl_->io_service();
}
response_impl &response::impl() const { return *impl_; }
} // namespace server
} // namespace asio_http2
} // namespace nghttp2

View File

@ -0,0 +1,166 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "asio_server_response_impl.h"
#include "asio_server_stream.h"
#include "asio_server_request_impl.h"
#include "asio_server_http2_handler.h"
#include "asio_common.h"
namespace nghttp2 {
namespace asio_http2 {
namespace server {
response_impl::response_impl()
: strm_(nullptr), generator_cb_(deferred_generator()), status_code_(200),
state_(response_state::INITIAL), pushed_(false),
push_promise_sent_(false) {}
unsigned int response_impl::status_code() const { return status_code_; }
void response_impl::write_head(unsigned int status_code, header_map h) {
if (state_ != response_state::INITIAL) {
return;
}
status_code_ = status_code;
header_ = std::move(h);
state_ = response_state::HEADER_DONE;
if (pushed_ && !push_promise_sent_) {
return;
}
start_response();
}
void response_impl::end(std::string data) {
end(string_generator(std::move(data)));
}
void response_impl::end(generator_cb cb) {
if (state_ == response_state::BODY_STARTED) {
return;
}
generator_cb_ = std::move(cb);
if (state_ == response_state::INITIAL) {
write_head(status_code_);
} else {
// generator_cb is changed, start writing in case it is deferred.
auto handler = strm_->handler();
handler->resume(*strm_);
}
state_ = response_state::BODY_STARTED;
}
namespace {
bool expect_response_body(const std::string &method, int status_code) {
return method != "HEAD" && status_code / 100 != 1 && status_code != 304 &&
status_code != 204;
}
} // namespace
void response_impl::start_response() {
auto handler = strm_->handler();
auto &req = strm_->request().impl();
// if response body is not expected, nullify it so that HEADERS has
// END_STREAM flag set.
if (!expect_response_body(req.method(), status_code_)) {
state_ = response_state::BODY_STARTED;
generator_cb_ = generator_cb();
}
if (handler->start_response(*strm_) != 0) {
handler->stream_error(strm_->get_stream_id(), NGHTTP2_INTERNAL_ERROR);
return;
}
}
void response_impl::on_close(close_cb cb) { close_cb_ = std::move(cb); }
void response_impl::call_on_close(uint32_t error_code) {
if (close_cb_) {
close_cb_(error_code);
}
}
void response_impl::cancel(uint32_t error_code) {
auto handler = strm_->handler();
handler->stream_error(strm_->get_stream_id(), error_code);
}
response *response_impl::push(boost::system::error_code &ec, std::string method,
std::string raw_path_query, header_map h) const {
auto handler = strm_->handler();
return handler->push_promise(ec, *strm_, std::move(method),
std::move(raw_path_query), std::move(h));
}
void response_impl::resume() {
auto handler = strm_->handler();
handler->resume(*strm_);
}
boost::asio::io_service &response_impl::io_service() {
return strm_->handler()->io_service();
}
void response_impl::pushed(bool f) { pushed_ = f; }
void response_impl::push_promise_sent() {
if (push_promise_sent_) {
return;
}
push_promise_sent_ = true;
if (state_ == response_state::INITIAL) {
return;
}
start_response();
}
const header_map &response_impl::header() const { return header_; }
void response_impl::stream(class stream *s) { strm_ = s; }
generator_cb::result_type
response_impl::call_read(uint8_t *data, std::size_t len, uint32_t *data_flags) {
if (generator_cb_) {
return generator_cb_(data, len, data_flags);
}
*data_flags |= NGHTTP2_DATA_FLAG_EOF;
return 0;
}
} // namespace server
} // namespace asio_http2
} // namespace nghttp2

View File

@ -0,0 +1,91 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef ASIO_SERVER_RESPONSE_IMPL_H
#define ASIO_SERVER_RESPONSE_IMPL_H
#include "nghttp2_config.h"
#include <nghttp2/asio_http2_server.h>
namespace nghttp2 {
namespace asio_http2 {
namespace server {
class stream;
enum class response_state {
INITIAL,
// response_impl::write_head() was called
HEADER_DONE,
// response_impl::end() was called
BODY_STARTED,
};
class response_impl {
public:
response_impl();
void write_head(unsigned int status_code, header_map h = {});
void end(std::string data = "");
void end(generator_cb cb);
void on_close(close_cb cb);
void resume();
void cancel(uint32_t error_code);
response *push(boost::system::error_code &ec, std::string method,
std::string raw_path_query, header_map h = {}) const;
boost::asio::io_service &io_service();
void start_response();
unsigned int status_code() const;
const header_map &header() const;
void pushed(bool f);
void push_promise_sent();
void stream(class stream *s);
generator_cb::result_type call_read(uint8_t *data, std::size_t len,
uint32_t *data_flags);
void call_on_close(uint32_t error_code);
private:
class stream *strm_;
header_map header_;
generator_cb generator_cb_;
close_cb close_cb_;
unsigned int status_code_;
response_state state_;
// true if this is pushed stream's response
bool pushed_;
// true if PUSH_PROMISE is sent if this is response of a pushed
// stream
bool push_promise_sent_;
};
} // namespace server
} // namespace asio_http2
} // namespace nghttp2
#endif // ASIO_SERVER_RESPONSE_IMPL_H

View File

@ -0,0 +1,132 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "asio_server_serve_mux.h"
#include "asio_server_request_impl.h"
#include "asio_server_request_handler.h"
#include "util.h"
#include "http2.h"
namespace nghttp2 {
namespace asio_http2 {
namespace server {
bool serve_mux::handle(std::string pattern, request_cb cb) {
if (pattern.empty() || !cb) {
return false;
}
auto it = mux_.find(pattern);
if (it != std::end(mux_) && (*it).second.user_defined) {
return false;
}
// if pattern ends with '/' (e.g., /foo/), add implicit permanent
// redirect for '/foo'.
if (pattern.size() >= 2 && pattern.back() == '/') {
auto redirect_pattern = pattern.substr(0, pattern.size() - 1);
auto it = mux_.find(redirect_pattern);
if (it == std::end(mux_) || !(*it).second.user_defined) {
std::string path;
if (pattern[0] == '/') {
path = pattern;
} else {
// skip host part
path = pattern.substr(pattern.find('/'));
}
if (it == std::end(mux_)) {
mux_.emplace(std::move(redirect_pattern),
handler_entry{false,
redirect_handler(301, std::move(path)),
pattern});
} else {
(*it).second = handler_entry{
false, redirect_handler(301, std::move(path)), pattern};
}
}
}
mux_.emplace(pattern, handler_entry{true, std::move(cb), pattern});
return true;
}
request_cb serve_mux::handler(request_impl &req) const {
auto &path = req.uri().path;
if (req.method() != "CONNECT") {
auto clean_path = ::nghttp2::http2::path_join(
nullptr, 0, nullptr, 0, path.c_str(), path.size(), nullptr, 0);
if (clean_path != path) {
auto new_uri = util::percent_encode_path(clean_path);
auto &uref = req.uri();
if (!uref.raw_query.empty()) {
new_uri += "?";
new_uri += uref.raw_query;
}
return redirect_handler(301, std::move(new_uri));
}
}
auto &host = req.uri().host;
auto cb = match(host + path);
if (cb) {
return cb;
}
cb = match(path);
if (cb) {
return cb;
}
return status_handler(404);
}
request_cb serve_mux::match(const std::string &path) const {
const handler_entry *ent = nullptr;
size_t best = 0;
for (auto &kv : mux_) {
auto &pattern = kv.first;
if (!util::startsWith(path, pattern)) {
continue;
}
if (path.size() == pattern.size() || pattern.back() == '/' ||
path[pattern.size()] == '/') {
if (!ent || best < pattern.size()) {
best = pattern.size();
ent = &kv.second;
}
}
}
if (ent) {
return ent->cb;
}
return request_cb();
}
} // namespace server
} // namespace asio_http2
} // namespace nghttp2

View File

@ -0,0 +1,64 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef ASIO_SERVER_SERVE_MUX_H
#define ASIO_SERVER_SERVE_MUX_H
#include "nghttp2_config.h"
#include <nghttp2/asio_http2_server.h>
namespace nghttp2 {
namespace asio_http2 {
namespace server {
class request_impl;
// port from go's ServeMux
struct handler_entry {
bool user_defined;
request_cb cb;
std::string pattern;
};
class serve_mux {
public:
bool handle(std::string pattern, request_cb cb);
request_cb handler(request_impl &req) const;
request_cb match(const std::string &path) const;
private:
std::map<std::string, handler_entry> mux_;
};
} // namespace server
} // namespace asio_http2
} // namespace nghttp2
#endif // ASIO_SERVER_SERVE_MUX_H

55
src/asio_server_stream.cc Normal file
View File

@ -0,0 +1,55 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "asio_server_stream.h"
#include "asio_server_http2_handler.h"
#include "asio_server_request_impl.h"
#include "asio_server_response_impl.h"
namespace nghttp2 {
namespace asio_http2 {
namespace server {
stream::stream(http2_handler *h, int32_t stream_id)
: handler_(h), stream_id_(stream_id) {
request_.impl().stream(this);
response_.impl().stream(this);
}
int32_t stream::get_stream_id() const { return stream_id_; }
class request &stream::request() {
return request_;
}
class response &stream::response() {
return response_;
}
http2_handler *stream::handler() const { return handler_; }
} // namespace server
} // namespace asio_http2
} // namespace nghttp2

59
src/asio_server_stream.h Normal file
View File

@ -0,0 +1,59 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef ASIO_SERVER_STREAM_H
#define ASIO_SERVER_STREAM_H
#include "nghttp2_config.h"
#include <nghttp2/asio_http2_server.h>
namespace nghttp2 {
namespace asio_http2 {
namespace server {
class http2_handler;
class stream {
public:
stream(http2_handler *h, int32_t stream_id);
int32_t get_stream_id() const;
class request &request();
class response &response();
http2_handler *handler() const;
private:
http2_handler *handler_;
class request request_;
class response response_;
int32_t stream_id_;
};
} // namespace server
} // namespace asio_http2
} // namespace nghttp2
#endif // ASIO_SERVER_STREAM_H

View File

@ -20,4 +20,5 @@
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
nobase_include_HEADERS = nghttp2/asio_http2.h
nobase_include_HEADERS = nghttp2/asio_http2.h nghttp2/asio_http2_client.h \
nghttp2/asio_http2_server.h

View File

@ -30,205 +30,86 @@
#include <string>
#include <vector>
#include <functional>
#include <map>
#include <boost/system/error_code.hpp>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <nghttp2/nghttp2.h>
namespace boost {
namespace system {
template <> struct is_error_code_enum<nghttp2_error> {
BOOST_STATIC_CONSTANT(bool, value = true);
};
} // namespace system
} // namespace boost
namespace nghttp2 {
namespace asio_http2 {
struct header {
std::string name;
struct header_value {
// header field value
std::string value;
// true if the header field value is sensitive information, such as
// authorization information or short length secret cookies. If
// true, those header fields are not indexed by HPACK (but still
// huffman-encoded), which results in lesser compression.
bool sensitive;
};
// header fields. The header field name must be lower-cased.
using header_map = std::multimap<std::string, header_value>;
const boost::system::error_category &nghttp2_category() noexcept;
struct uri_ref {
std::string scheme;
std::string host;
// form after percent-encoding decoded
std::string path;
// original path, percent-encoded
std::string raw_path;
// original query, percent-encoded
std::string raw_query;
std::string fragment;
};
// Callback function when data is arrived. EOF is indicated by
// passing 0 to the second parameter.
typedef std::function<void(const uint8_t *, std::size_t)> data_cb;
typedef std::function<void(void)> void_cb;
typedef std::function<void(const boost::system::error_code &ec)> error_cb;
// Callback function when request and response are finished. The
// parameter indicates the cause of closure.
typedef std::function<void(uint32_t)> close_cb;
// Callback function to generate response body. The implementation of
// this callback must fill at most |len| bytes data to |buf|. The
// return value is pair of written bytes and bool value indicating
// that this is the end of the body. If the end of the body was
// reached, return true. If there is error and application wants to
// terminate stream, return std::make_pair(-1, false). Returning
// std::make_pair(0, false) tells the library that don't call this
// callback until application calls response::resume(). This is
// useful when there is no data to send at the moment but there will
// be more to come in near future.
typedef std::function<std::pair<ssize_t, bool>(uint8_t *buf, std::size_t len)>
read_cb;
class channel_impl;
class channel {
public:
// Application must not call this directly.
channel();
// Schedules the execution of callback |cb| in the same thread where
// request callback is called. Therefore, it is same to use request
// or response object in |cb|. The callbacks are executed in the
// same order they are posted though same channel object if they are
// posted from the same thread.
void post(void_cb cb);
// Application must not call this directly.
channel_impl &impl();
private:
std::unique_ptr<channel_impl> impl_;
};
typedef std::function<void(channel &)> thread_cb;
namespace server {
class request_impl;
class response_impl;
class request {
public:
// Application must not call this directly.
request();
// Returns request headers. The pusedo headers, which start with
// colon (;), are exluced from this list.
const std::vector<header> &headers() const;
// Returns method (e.g., GET).
const std::string &method() const;
// Returns scheme (e.g., https).
const std::string &scheme() const;
// Returns authority (e.g., example.org). This could be empty
// string. In this case, check host().
const std::string &authority() const;
// Returns host (e.g., example.org). If host header field is not
// present, this value is copied from authority().
const std::string &host() const;
// Returns path (e.g., /index.html).
const std::string &path() const;
// Sets callback when chunk of request body is received.
void on_data(data_cb cb);
// Sets callback when request was completed.
void on_end(void_cb cb);
// Pushes resource denoted by |path| using |method|. The additional
// headers can be given in |headers|. request_cb will be called for
// pushed resource later on. This function returns true if it
// succeeds, or false.
bool push(std::string method, std::string path,
std::vector<header> headers = {});
// Returns true if this is pushed request.
bool pushed() const;
// Returns true if stream has been closed.
bool closed() const;
// Runs function |start| in one of background threads. Returns true
// if scheduling task was done successfully.
//
// Since |start| is called in different thread, calling any method
// of request or response object in the callback may cause undefined
// behavior. To safely use them, use channel::post(). A callback
// passed to channel::post() is executed in the same thread where
// request callback is called, so it is safe to use request or
// response object. Example::
bool run_task(thread_cb start);
// Application must not call this directly.
request_impl &impl();
private:
std::unique_ptr<request_impl> impl_;
};
class response {
public:
// Application must not call this directly.
response();
// Write response header using |status_code| (e.g., 200) and
// additional headers in |headers|.
void write_head(unsigned int status_code, std::vector<header> headers = {});
// Sends |data| as request body. No further call of end() is
// allowed.
void end(std::string data = "");
// Sets callback |cb| as a generator of the response body. No
// further call of end() is allowed.
void end(read_cb cb);
// Resumes deferred response.
void resume();
// Returns status code.
unsigned int status_code() const;
// Returns true if response has been started.
bool started() const;
// Application must not call this directly.
response_impl &impl();
private:
std::unique_ptr<response_impl> impl_;
};
// This is so called request callback. Called every time request is
// received.
typedef std::function<void(const std::shared_ptr<request> &,
const std::shared_ptr<response> &)> request_cb;
class http2_impl;
class http2 {
public:
http2();
~http2();
// Starts listening connection on given address and port. The
// incoming requests are handled by given callback |cb|.
void listen(const std::string &address, uint16_t port, request_cb cb);
// Sets number of native threads to handle incoming HTTP request.
// It defaults to 1.
void num_threads(size_t num_threads);
// Sets TLS private key file and certificate file. Both files must
// be in PEM format.
void tls(std::string private_key_file, std::string certificate_file);
// Sets number of background threads to run concurrent tasks (see
// request::run_task()). It defaults to 1. This is not the number
// of thread to handle incoming HTTP request. For this purpose, see
// num_threads().
void num_concurrent_tasks(size_t num_concurrent_tasks);
// Sets the maximum length to which the queue of pending
// connections.
void backlog(int backlog);
private:
std::unique_ptr<http2_impl> impl_;
};
} // namespace server
// Callback function to generate response body. This function has the
// same semantics with nghttp2_data_source_read_callback. Just source
// and user_data parameters are removed.
//
// Basically, write at most |len| bytes to |data| and returns the
// number of bytes written. If there is no data left to send, set
// NGHTTP2_DATA_FLAG_EOF to *data_flags (e.g., *data_flags |=
// NGHTTP2_DATA_FLAG_EOF). If there is still data to send but they
// are not available right now, return NGHTTP2_ERR_DEFERRED. In case
// of the error and request/response must be closed, return
// NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE.
typedef std::function<
ssize_t(uint8_t *buf, std::size_t len, uint32_t *data_flags)> generator_cb;
// Convenient function to create function to read file denoted by
// |path|. This can be passed to response::end().
read_cb file_reader(const std::string &path);
generator_cb file_generator(const std::string &path);
// Like file_reader(const std::string&), but it takes opened file
// Like file_generator(const std::string&), but it takes opened file
// descriptor. The passed descriptor will be closed when returned
// function object is destroyed.
read_cb file_reader_from_fd(int fd);
generator_cb file_generator_from_fd(int fd);
// Validates path so that it does not contain directory traversal
// vector. Returns true if path is safe. The |path| must start with

View File

@ -0,0 +1,184 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef ASIO_HTTP2_CLIENT_H
#define ASIO_HTTP2_CLIENT_H
#include <nghttp2/asio_http2.h>
namespace nghttp2 {
namespace asio_http2 {
namespace client {
class response_impl;
class response {
public:
// Application must not call this directly.
response();
~response();
// Sets callback which is invoked when chunk of response body is
// received.
void on_data(data_cb cb) const;
// Returns status code.
int status_code() const;
// Returns content-length. -1 if it is unknown.
int64_t content_length() const;
// Returns the response header fields. The pusedo header fields,
// which start with colon (:), are exluced from this list.
const header_map &header() const;
// Application must not call this directly.
response_impl &impl() const;
private:
std::unique_ptr<response_impl> impl_;
};
class request;
using response_cb = std::function<void(const response &)>;
using request_cb = std::function<void(const request &)>;
using connect_cb =
std::function<void(boost::asio::ip::tcp::resolver::iterator)>;
class request_impl;
class request {
public:
// Application must not call this directly.
request();
~request();
// Sets callback which is invoked when response header is received.
void on_response(response_cb cb) const;
// Sets callback which is invoked when push request header is
// received.
void on_push(request_cb cb) const;
// Sets callback which is invoked when this request and response are
// finished. After the invocation of this callback, the application
// must not access request and response object.
void on_close(close_cb cb) const;
// Cancels this request and response with given error code.
void cancel(uint32_t error_code = NGHTTP2_INTERNAL_ERROR) const;
// Resumes deferred uploading.
void resume() const;
// Returns method (e.g., GET).
const std::string &method() const;
// Returns request URI, split into components.
const uri_ref &uri() const;
// Returns request header fields. The pusedo header fields, which
// start with colon (:), are exluced from this list.
const header_map &header() const;
// Application must not call this directly.
request_impl &impl() const;
private:
std::unique_ptr<request_impl> impl_;
};
class session_impl;
class session {
public:
// Starts HTTP/2 session by connecting to |host| and |service|
// (e.g., "80") using clear text TCP connection.
session(boost::asio::io_service &io_service, const std::string &host,
const std::string &service);
// Starts HTTP/2 session by connecting to |host| and |service|
// (e.g., "443") using encrypted SSL/TLS connection.
session(boost::asio::io_service &io_service,
boost::asio::ssl::context &tls_context, const std::string &host,
const std::string &service);
~session();
// Sets callback which is invoked after connection is established.
void on_connect(connect_cb cb) const;
// Sets callback which is invoked there is connection level error
// and session is terminated.
void on_error(error_cb cb) const;
// Shutdowns connection.
void shutdown() const;
// Returns underlying io_service object.
boost::asio::io_service &io_service() const;
// Submits request to server using |method| (e.g., "GET"), |uri|
// (e.g., "http://localhost/") and optionally additional header
// fields. This function returns pointer to request object if it
// succeeds, or nullptr and |ec| contains error message.
const request *submit(boost::system::error_code &ec,
const std::string &method, const std::string &uri,
header_map h = {}) const;
// Submits request to server using |method| (e.g., "GET"), |uri|
// (e.g., "http://localhost/") and optionally additional header
// fields. The |data| is request body. This function returns
// pointer to request object if it succeeds, or nullptr and |ec|
// contains error message.
const request *submit(boost::system::error_code &ec,
const std::string &method, const std::string &uri,
std::string data, header_map h = {}) const;
// Submits request to server using |method| (e.g., "GET"), |uri|
// (e.g., "http://localhost/") and optionally additional header
// fields. The |cb| is used to generate request body. This
// function returns pointer to request object if it succeeds, or
// nullptr and |ec| contains error message.
const request *submit(boost::system::error_code &ec,
const std::string &method, const std::string &uri,
generator_cb cb, header_map h = {}) const;
private:
std::unique_ptr<session_impl> impl_;
};
// configure |tls_ctx| for client use. Currently, we just set NPN
// callback for HTTP/2.
void configure_tls_context(boost::asio::ssl::context &tls_ctx);
} // namespace client
} // namespace asio_http2
} // namespace nghttp2
#endif // ASIO_HTTP2_CLIENT_H

View File

@ -0,0 +1,170 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2015 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef ASIO_HTTP2_SERVER_H
#define ASIO_HTTP2_SERVER_H
#include <nghttp2/asio_http2.h>
namespace nghttp2 {
namespace asio_http2 {
namespace server {
class request_impl;
class response_impl;
class request {
public:
// Application must not call this directly.
request();
~request();
// Returns request header fields. The pusedo header fields, which
// start with colon (:), are exluced from this list.
const header_map &header() const;
// Returns method (e.g., GET).
const std::string &method() const;
// Returns request URI, split into components.
const uri_ref &uri() const;
// Sets callback which is invoked when chunk of request body is
// received.
void on_data(data_cb cb) const;
// Application must not call this directly.
request_impl &impl() const;
private:
std::unique_ptr<request_impl> impl_;
};
class response {
public:
// Application must not call this directly.
response();
~response();
// Write response header using |status_code| (e.g., 200) and
// additional header fields in |h|.
void write_head(unsigned int status_code, header_map h = {}) const;
// Sends |data| as request body. No further call of end() is
// allowed.
void end(std::string data = "") const;
// Sets callback as a generator of the response body. No further
// call of end() is allowed.
void end(generator_cb cb) const;
// Sets callback which is invoked when this request and response are
// finished. After the invocation of this callback, the application
// must not access request and response object.
void on_close(close_cb cb) const;
// Cancels this request and response with given error code.
void cancel(uint32_t error_code = NGHTTP2_INTERNAL_ERROR) const;
// Resumes deferred response.
void resume() const;
// Pushes resource denoted by |raw_path_query| using |method|. The
// additional header fields can be given in |h|. This function
// returns pointer to response object for promised stream, otherwise
// nullptr and error code is filled in |ec|. Be aware that the
// header field name given in |h| must be lower-cased.
const response *push(boost::system::error_code &ec, std::string method,
std::string raw_path_query, header_map h = {}) const;
// Returns status code.
unsigned int status_code() const;
// Returns boost::asio::io_service this response is running on.
boost::asio::io_service &io_service() const;
// Application must not call this directly.
response_impl &impl() const;
private:
std::unique_ptr<response_impl> impl_;
};
// This is so called request callback. Called every time request is
// received. The life time of |request| and |response| objects end
// when callback set by response::on_close() is called. After that,
// the application must not access to those objects.
typedef std::function<void(const request &, const response &)> request_cb;
class http2_impl;
class http2 {
public:
http2();
~http2();
// Starts listening connection on given address and port and serves
// incoming requests.
void listen_and_serve(const std::string &address, uint16_t port);
// Registers request handler |cb| with path pattern |pattern|. This
// function will fail and returns false if same pattern has been
// already registered or |pattern| is empty string. Otherwise
// returns true. The pattern match rule is the same as
// net/http/ServeMux in golang.
bool handle(std::string pattern, request_cb cb);
// Sets number of native threads to handle incoming HTTP request.
// It defaults to 1.
void num_threads(size_t num_threads);
// Sets TLS private key file and certificate file. Both files must
// be in PEM format.
void tls(std::string private_key_file, std::string certificate_file);
// Sets the maximum length to which the queue of pending
// connections.
void backlog(int backlog);
private:
std::unique_ptr<http2_impl> impl_;
};
// Returns request handler to do redirect to |uri| using
// |status_code|. The |uri| appears in "location" header field as is.
request_cb redirect_handler(int status_code, std::string uri);
// Returns request handler to reply with given |status_code| and HTML
// including message about status code.
request_cb status_handler(int status_code);
} // namespace server
} // namespace asio_http2
} // namespace nghttp2
#endif // ASIO_HTTP2_SERVER_H

View File

@ -126,6 +126,8 @@ int main(int argc, char *argv[]) {
!CU_add_test(pSuite, "util_to_token68", shrpx::test_util_to_token68) ||
!CU_add_test(pSuite, "util_percent_encode_token",
shrpx::test_util_percent_encode_token) ||
!CU_add_test(pSuite, "util_percent_encode_path",
shrpx::test_util_percent_encode_path) ||
!CU_add_test(pSuite, "util_percent_decode",
shrpx::test_util_percent_decode) ||
!CU_add_test(pSuite, "util_quote_string",

View File

@ -39,7 +39,7 @@ std::string create_error_html(unsigned int status_code) {
std::string res;
res.reserve(512);
auto status = http2::get_status_string(status_code);
res += "<!DOCTYPE html><html lang=en><title>";
res += R"(<!DOCTYPE html><html lang="en"><title>)";
res += status;
res += "</title><body><h1>";
res += status;

View File

@ -70,6 +70,13 @@ bool inRFC3986UnreservedChars(const char c) {
std::find(&unreserved[0], &unreserved[4], c) != &unreserved[4];
}
bool in_rfc3986_sub_delims(const char c) {
static const char sub_delims[] = {'!', '$', '&', '\'', '(', ')',
'*', '+', ',', ';', '='};
return std::find(std::begin(sub_delims), std::end(sub_delims), c) !=
std::end(sub_delims);
}
std::string percentEncode(const unsigned char *target, size_t len) {
std::string dest;
for (size_t i = 0; i < len; ++i) {
@ -91,6 +98,21 @@ std::string percentEncode(const std::string &target) {
target.size());
}
std::string percent_encode_path(const std::string &s) {
std::string dest;
for (auto c : s) {
if (inRFC3986UnreservedChars(c) || in_rfc3986_sub_delims(c) || c == '/') {
dest += c;
continue;
}
dest += "%";
dest += UPPER_XDIGITS[(c >> 4) & 0x0f];
dest += UPPER_XDIGITS[(c & 0x0f)];
}
return dest;
}
bool in_token(char c) {
static const char extra[] = {'!', '#', '$', '%', '&', '\'', '*', '+',
'-', '.', '^', '_', '`', '|', '~'};
@ -137,25 +159,6 @@ uint32_t hex_to_uint(char c) {
return c;
}
std::string percentDecode(std::string::const_iterator first,
std::string::const_iterator last) {
std::string result;
for (; first != last; ++first) {
if (*first == '%') {
if (first + 1 != last && first + 2 != last && isHexDigit(*(first + 1)) &&
isHexDigit(*(first + 2))) {
result += (hex_to_uint(*(first + 1)) << 4) + hex_to_uint(*(first + 2));
first += 2;
continue;
}
result += *first;
continue;
}
result += *first;
}
return result;
}
std::string quote_string(const std::string &target) {
auto cnt = std::count(std::begin(target), std::end(target), '"');

View File

@ -162,6 +162,8 @@ bool isHexDigit(const char c);
bool inRFC3986UnreservedChars(const char c);
bool in_rfc3986_sub_delims(const char c);
// Returns true if |c| is in token (HTTP-p1, Section 3.2.6)
bool in_token(char c);
@ -175,8 +177,27 @@ std::string percentEncode(const unsigned char *target, size_t len);
std::string percentEncode(const std::string &target);
std::string percentDecode(std::string::const_iterator first,
std::string::const_iterator last);
// percent-encode path component of URI |s|.
std::string percent_encode_path(const std::string &s);
template <typename InputIt>
std::string percentDecode(InputIt first, InputIt last) {
std::string result;
for (; first != last; ++first) {
if (*first == '%') {
if (first + 1 != last && first + 2 != last && isHexDigit(*(first + 1)) &&
isHexDigit(*(first + 2))) {
result += (hex_to_uint(*(first + 1)) << 4) + hex_to_uint(*(first + 2));
first += 2;
continue;
}
result += *first;
continue;
}
result += *first;
}
return result;
}
// Percent encode |target| if character is not in token or '%'.
std::string percent_encode_token(const std::string &target);

View File

@ -139,6 +139,11 @@ void test_util_percent_encode_token(void) {
CU_ASSERT("http%202" == util::percent_encode_token("http 2"));
}
void test_util_percent_encode_path(void) {
CU_ASSERT("/foo1/bar%3F&/%0A" == util::percent_encode_path("/foo1/bar?&/"
"\x0a"));
}
void test_util_percent_decode(void) {
{
std::string s = "%66%6F%6f%62%61%72";

View File

@ -33,6 +33,7 @@ void test_util_inp_strlower(void);
void test_util_to_base64(void);
void test_util_to_token68(void);
void test_util_percent_encode_token(void);
void test_util_percent_encode_path(void);
void test_util_percent_decode(void);
void test_util_quote_string(void);
void test_util_utox(void);