diff --git a/configure.ac b/configure.ac index 50ec1ff0..38a0566c 100644 --- a/configure.ac +++ b/configure.ac @@ -76,6 +76,11 @@ AC_ARG_ENABLE([hpack-tools], [Build HPACK tools [default=check]])], [request_hpack_tools=$enableval], [request_hpack_tools=check]) +AC_ARG_ENABLE([asio-lib], + [AS_HELP_STRING([--enable-asio-lib], + [Build C++ libnghttp2_asio library [default=no]])], + [request_asio_lib=$enableval], [request_asio_lib=no]) + AC_ARG_ENABLE([examples], [AS_HELP_STRING([--enable-examples], [Build examples [default=check]])], @@ -329,6 +334,23 @@ fi AM_CONDITIONAL([HAVE_SPDYLAY], [ test "x${have_spdylay}" = "xyes" ]) +# Check Boost Asio library +have_asio_lib=no + +AX_BOOST_BASE([1.55.0], [have_boost_base=yes], [have_boost_base=no]) + +if test "x${have_boost_base}" = "xyes"; then + AX_BOOST_ASIO() + AX_BOOST_SYSTEM() + AX_BOOST_THREAD() + + if test "x${ax_cv_boost_asio}" = "xyes" && + test "x${ax_cv_boost_system}" = "xyes" && + test "x${ax_cv_boost_thread}" = "xyes"; then + have_asio_lib=yes + fi +fi + # The nghttp, nghttpd and nghttpx under src depend on zlib, OpenSSL # and libevent_openssl enable_app=no @@ -361,6 +383,16 @@ fi AM_CONDITIONAL([ENABLE_HPACK_TOOLS], [ test "x${enable_hpack_tools}" = "xyes" ]) +# C++ library libnghttp2_asio + +enable_asio_lib=no +if test "x${request_asio_lib}" != "xno" && + test "x${have_asio_lib}" = "xyes"; then + enable_asio_lib=yes +fi + +AM_CONDITIONAL([ENABLE_ASIO_LIB], [ test "x${enable_asio_lib}" = "xyes" ]) + # The example programs depend on OpenSSL and libevent_openssl enable_examples=no if test "x${request_examples}" != "xno" && @@ -505,6 +537,8 @@ AC_CONFIG_FILES([ tests/testdata/Makefile third-party/Makefile src/Makefile + src/includes/Makefile + src/libnghttp2_asio.pc examples/Makefile python/Makefile python/setup.py @@ -553,8 +587,14 @@ AC_MSG_NOTICE([summary of build options: Spdylay: ${have_spdylay} Jansson: ${have_jansson} Jemalloc: ${have_jemalloc} + Boost CPPFLAGS: ${BOOST_CPPFLAGS} + Boost LDFLAGS: ${BOOST_LDFLAGS} + Boost::ASIO: ${BOOST_ASIO_LIB} + Boost::System: ${BOOST_SYSTEM_LIB} + Boost::Thread: ${BOOST_THREAD_LIB} Applications: ${enable_app} HPACK tools: ${enable_hpack_tools} + Libnghttp2_asio:${enable_asio_lib} Examples: ${enable_examples} Python bindings:${enable_python_bindings} Failmalloc: ${request_failmalloc} diff --git a/examples/Makefile.am b/examples/Makefile.am index fb3bc79e..7b184a7e 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -27,6 +27,7 @@ AM_CPPFLAGS = \ -Wall \ -I$(top_srcdir)/lib/includes \ -I$(top_builddir)/lib/includes \ + -I$(top_srcdir)/src/includes \ -I$(top_srcdir)/third-party \ @LIBEVENT_OPENSSL_CFLAGS@ \ @OPENSSL_CFLAGS@ \ @@ -48,4 +49,18 @@ libevent_server_SOURCES = libevent-server.c deflate_SOURCES = deflate.c +if ENABLE_ASIO_LIB + +noinst_PROGRAMS += asio-sv + +asio_sv_SOURCES = asio-sv.cc +asio_sv_CPPFLAGS = ${BOOST_CPPFLAGS} ${AM_CPPFLAGS} +asio_sv_LDFLAGS = \ + @JEMALLOC_LIBS@ \ + ${BOOST_LDFLAGS} \ + ${BOOST_SYSTEM_LIB} +asio_sv_LDADD = $(top_builddir)/src/libnghttp2_asio.la + +endif # ENABLE_ASIO_LIB + endif # ENABLE_EXAMPLES diff --git a/examples/asio-sv.cc b/examples/asio-sv.cc new file mode 100644 index 00000000..d5c43c7f --- /dev/null +++ b/examples/asio-sv.cc @@ -0,0 +1,78 @@ +/* + * 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 +#include + +#include + +using namespace nghttp2::asio_http2; +using namespace nghttp2::asio_http2::server; + +int main(int argc, char* argv[]) +{ + try { + // Check command line arguments. + if (argc < 3) { + std::cerr << "Usage: asio-sv " + << "\n"; + return 1; + } + + uint16_t port = std::stoi(argv[1]); + std::size_t num_threads = std::stoi(argv[2]); + + http2 server; + + server.num_threads(num_threads); + + if(argc >= 5) { + server.tls(argv[3], argv[4]); + } + + server.listen + ("*", port, + [](std::shared_ptr req, std::shared_ptr res) + { + res->write_head(200, { header{ "foo", "bar" } }); + res->end("hello, world"); + }); + } catch (std::exception& e) { + std::cerr << "exception: " << e.what() << "\n"; + } + + return 0; +} diff --git a/m4/ax_boost_asio.m4 b/m4/ax_boost_asio.m4 new file mode 100644 index 00000000..b57d4878 --- /dev/null +++ b/m4/ax_boost_asio.m4 @@ -0,0 +1,110 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_boost_asio.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_BOOST_ASIO +# +# DESCRIPTION +# +# Test for Asio library from the Boost C++ libraries. The macro requires a +# preceding call to AX_BOOST_BASE. Further documentation is available at +# . +# +# This macro calls: +# +# AC_SUBST(BOOST_ASIO_LIB) +# +# And sets: +# +# HAVE_BOOST_ASIO +# +# LICENSE +# +# Copyright (c) 2008 Thomas Porschberg +# Copyright (c) 2008 Pete Greenwell +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 16 + +AC_DEFUN([AX_BOOST_ASIO], +[ + AC_ARG_WITH([boost-asio], + AS_HELP_STRING([--with-boost-asio@<:@=special-lib@:>@], + [use the ASIO library from boost - it is possible to specify a certain library for the linker + e.g. --with-boost-asio=boost_system-gcc41-mt-1_34 ]), + [ + if test "$withval" = "no"; then + want_boost="no" + elif test "$withval" = "yes"; then + want_boost="yes" + ax_boost_user_asio_lib="" + else + want_boost="yes" + ax_boost_user_asio_lib="$withval" + fi + ], + [want_boost="yes"] + ) + + if test "x$want_boost" = "xyes"; then + AC_REQUIRE([AC_PROG_CC]) + CPPFLAGS_SAVED="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" + export CPPFLAGS + + LDFLAGS_SAVED="$LDFLAGS" + LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" + export LDFLAGS + + AC_CACHE_CHECK(whether the Boost::ASIO library is available, + ax_cv_boost_asio, + [AC_LANG_PUSH([C++]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ @%:@include + ]], + [[ + + boost::asio::io_service io; + boost::system::error_code timer_result; + boost::asio::deadline_timer t(io); + t.cancel(); + io.run_one(); + return 0; + ]])], + ax_cv_boost_asio=yes, ax_cv_boost_asio=no) + AC_LANG_POP([C++]) + ]) + if test "x$ax_cv_boost_asio" = "xyes"; then + AC_DEFINE(HAVE_BOOST_ASIO,,[define if the Boost::ASIO library is available]) + BN=boost_system + BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/@<:@^\/@:>@*//'` + if test "x$ax_boost_user_asio_lib" = "x"; then + for ax_lib in `ls $BOOSTLIBDIR/libboost_system*.so* $BOOSTLIBDIR/libboost_system*.dylib* $BOOSTLIBDIR/libboost_system*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_system.*\)\.so.*$;\1;' -e 's;^lib\(boost_system.*\)\.dylib.*$;\1;' -e 's;^lib\(boost_system.*\)\.a.*$;\1;' ` ; do + AC_CHECK_LIB($ax_lib, main, [BOOST_ASIO_LIB="-l$ax_lib" AC_SUBST(BOOST_ASIO_LIB) link_thread="yes" break], + [link_thread="no"]) + done + else + for ax_lib in $ax_boost_user_asio_lib $BN-$ax_boost_user_asio_lib; do + AC_CHECK_LIB($ax_lib, main, + [BOOST_ASIO_LIB="-l$ax_lib" AC_SUBST(BOOST_ASIO_LIB) link_asio="yes" break], + [link_asio="no"]) + done + + fi + if test "x$ax_lib" = "x"; then + AC_MSG_ERROR(Could not find a version of the library!) + fi + if test "x$link_asio" = "xno"; then + AC_MSG_ERROR(Could not link against $ax_lib !) + fi + fi + + CPPFLAGS="$CPPFLAGS_SAVED" + LDFLAGS="$LDFLAGS_SAVED" + fi +]) diff --git a/m4/ax_boost_base.m4 b/m4/ax_boost_base.m4 new file mode 100644 index 00000000..d7c9d0d3 --- /dev/null +++ b/m4/ax_boost_base.m4 @@ -0,0 +1,275 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_boost_base.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_BOOST_BASE([MINIMUM-VERSION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +# +# DESCRIPTION +# +# Test for the Boost C++ libraries of a particular version (or newer) +# +# If no path to the installed boost library is given the macro searchs +# under /usr, /usr/local, /opt and /opt/local and evaluates the +# $BOOST_ROOT environment variable. Further documentation is available at +# . +# +# This macro calls: +# +# AC_SUBST(BOOST_CPPFLAGS) / AC_SUBST(BOOST_LDFLAGS) +# +# And sets: +# +# HAVE_BOOST +# +# LICENSE +# +# Copyright (c) 2008 Thomas Porschberg +# Copyright (c) 2009 Peter Adolphs +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 25 + +AC_DEFUN([AX_BOOST_BASE], +[ +AC_ARG_WITH([boost], + [AS_HELP_STRING([--with-boost@<:@=ARG@:>@], + [use Boost library from a standard location (ARG=yes), + from the specified location (ARG=), + or disable it (ARG=no) + @<:@ARG=yes@:>@ ])], + [ + if test "$withval" = "no"; then + want_boost="no" + elif test "$withval" = "yes"; then + want_boost="yes" + ac_boost_path="" + else + want_boost="yes" + ac_boost_path="$withval" + fi + ], + [want_boost="yes"]) + + +AC_ARG_WITH([boost-libdir], + AS_HELP_STRING([--with-boost-libdir=LIB_DIR], + [Force given directory for boost libraries. Note that this will override library path detection, so use this parameter only if default library detection fails and you know exactly where your boost libraries are located.]), + [ + if test -d "$withval" + then + ac_boost_lib_path="$withval" + else + AC_MSG_ERROR(--with-boost-libdir expected directory name) + fi + ], + [ac_boost_lib_path=""] +) + +if test "x$want_boost" = "xyes"; then + boost_lib_version_req=ifelse([$1], ,1.20.0,$1) + boost_lib_version_req_shorten=`expr $boost_lib_version_req : '\([[0-9]]*\.[[0-9]]*\)'` + boost_lib_version_req_major=`expr $boost_lib_version_req : '\([[0-9]]*\)'` + boost_lib_version_req_minor=`expr $boost_lib_version_req : '[[0-9]]*\.\([[0-9]]*\)'` + boost_lib_version_req_sub_minor=`expr $boost_lib_version_req : '[[0-9]]*\.[[0-9]]*\.\([[0-9]]*\)'` + if test "x$boost_lib_version_req_sub_minor" = "x" ; then + boost_lib_version_req_sub_minor="0" + fi + WANT_BOOST_VERSION=`expr $boost_lib_version_req_major \* 100000 \+ $boost_lib_version_req_minor \* 100 \+ $boost_lib_version_req_sub_minor` + AC_MSG_CHECKING(for boostlib >= $boost_lib_version_req) + succeeded=no + + dnl On 64-bit systems check for system libraries in both lib64 and lib. + dnl The former is specified by FHS, but e.g. Debian does not adhere to + dnl this (as it rises problems for generic multi-arch support). + dnl The last entry in the list is chosen by default when no libraries + dnl are found, e.g. when only header-only libraries are installed! + libsubdirs="lib" + ax_arch=`uname -m` + case $ax_arch in + x86_64) + libsubdirs="lib64 libx32 lib lib64" + ;; + ppc64|s390x|sparc64|aarch64|ppc64le) + libsubdirs="lib64 lib lib64 ppc64le" + ;; + esac + + dnl allow for real multi-arch paths e.g. /usr/lib/x86_64-linux-gnu. Give + dnl them priority over the other paths since, if libs are found there, they + dnl are almost assuredly the ones desired. + AC_REQUIRE([AC_CANONICAL_HOST]) + libsubdirs="lib/${host_cpu}-${host_os} $libsubdirs" + + case ${host_cpu} in + i?86) + libsubdirs="lib/i386-${host_os} $libsubdirs" + ;; + esac + + dnl first we check the system location for boost libraries + dnl this location ist chosen if boost libraries are installed with the --layout=system option + dnl or if you install boost with RPM + if test "$ac_boost_path" != ""; then + BOOST_CPPFLAGS="-I$ac_boost_path/include" + for ac_boost_path_tmp in $libsubdirs; do + if test -d "$ac_boost_path"/"$ac_boost_path_tmp" ; then + BOOST_LDFLAGS="-L$ac_boost_path/$ac_boost_path_tmp" + break + fi + done + elif test "$cross_compiling" != yes; then + for ac_boost_path_tmp in /usr /usr/local /opt /opt/local ; do + if test -d "$ac_boost_path_tmp/include/boost" && test -r "$ac_boost_path_tmp/include/boost"; then + for libsubdir in $libsubdirs ; do + if ls "$ac_boost_path_tmp/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi + done + BOOST_LDFLAGS="-L$ac_boost_path_tmp/$libsubdir" + BOOST_CPPFLAGS="-I$ac_boost_path_tmp/include" + break; + fi + done + fi + + dnl overwrite ld flags if we have required special directory with + dnl --with-boost-libdir parameter + if test "$ac_boost_lib_path" != ""; then + BOOST_LDFLAGS="-L$ac_boost_lib_path" + fi + + CPPFLAGS_SAVED="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" + export CPPFLAGS + + LDFLAGS_SAVED="$LDFLAGS" + LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" + export LDFLAGS + + AC_REQUIRE([AC_PROG_CXX]) + AC_LANG_PUSH(C++) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ + @%:@include + ]], [[ + #if BOOST_VERSION >= $WANT_BOOST_VERSION + // Everything is okay + #else + # error Boost version is too old + #endif + ]])],[ + AC_MSG_RESULT(yes) + succeeded=yes + found_system=yes + ],[ + ]) + AC_LANG_POP([C++]) + + + + dnl if we found no boost with system layout we search for boost libraries + dnl built and installed without the --layout=system option or for a staged(not installed) version + if test "x$succeeded" != "xyes"; then + _version=0 + if test "$ac_boost_path" != ""; then + if test -d "$ac_boost_path" && test -r "$ac_boost_path"; then + for i in `ls -d $ac_boost_path/include/boost-* 2>/dev/null`; do + _version_tmp=`echo $i | sed "s#$ac_boost_path##" | sed 's/\/include\/boost-//' | sed 's/_/./'` + V_CHECK=`expr $_version_tmp \> $_version` + if test "$V_CHECK" = "1" ; then + _version=$_version_tmp + fi + VERSION_UNDERSCORE=`echo $_version | sed 's/\./_/'` + BOOST_CPPFLAGS="-I$ac_boost_path/include/boost-$VERSION_UNDERSCORE" + done + fi + else + if test "$cross_compiling" != yes; then + for ac_boost_path in /usr /usr/local /opt /opt/local ; do + if test -d "$ac_boost_path" && test -r "$ac_boost_path"; then + for i in `ls -d $ac_boost_path/include/boost-* 2>/dev/null`; do + _version_tmp=`echo $i | sed "s#$ac_boost_path##" | sed 's/\/include\/boost-//' | sed 's/_/./'` + V_CHECK=`expr $_version_tmp \> $_version` + if test "$V_CHECK" = "1" ; then + _version=$_version_tmp + best_path=$ac_boost_path + fi + done + fi + done + + VERSION_UNDERSCORE=`echo $_version | sed 's/\./_/'` + BOOST_CPPFLAGS="-I$best_path/include/boost-$VERSION_UNDERSCORE" + if test "$ac_boost_lib_path" = ""; then + for libsubdir in $libsubdirs ; do + if ls "$best_path/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi + done + BOOST_LDFLAGS="-L$best_path/$libsubdir" + fi + fi + + if test "x$BOOST_ROOT" != "x"; then + for libsubdir in $libsubdirs ; do + if ls "$BOOST_ROOT/stage/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi + done + if test -d "$BOOST_ROOT" && test -r "$BOOST_ROOT" && test -d "$BOOST_ROOT/stage/$libsubdir" && test -r "$BOOST_ROOT/stage/$libsubdir"; then + version_dir=`expr //$BOOST_ROOT : '.*/\(.*\)'` + stage_version=`echo $version_dir | sed 's/boost_//' | sed 's/_/./g'` + stage_version_shorten=`expr $stage_version : '\([[0-9]]*\.[[0-9]]*\)'` + V_CHECK=`expr $stage_version_shorten \>\= $_version` + if test "$V_CHECK" = "1" -a "$ac_boost_lib_path" = "" ; then + AC_MSG_NOTICE(We will use a staged boost library from $BOOST_ROOT) + BOOST_CPPFLAGS="-I$BOOST_ROOT" + BOOST_LDFLAGS="-L$BOOST_ROOT/stage/$libsubdir" + fi + fi + fi + fi + + CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" + export CPPFLAGS + LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" + export LDFLAGS + + AC_LANG_PUSH(C++) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ + @%:@include + ]], [[ + #if BOOST_VERSION >= $WANT_BOOST_VERSION + // Everything is okay + #else + # error Boost version is too old + #endif + ]])],[ + AC_MSG_RESULT(yes) + succeeded=yes + found_system=yes + ],[ + ]) + AC_LANG_POP([C++]) + fi + + if test "$succeeded" != "yes" ; then + if test "$_version" = "0" ; then + AC_MSG_NOTICE([[We could not detect the boost libraries (version $boost_lib_version_req_shorten or higher). If you have a staged boost library (still not installed) please specify \$BOOST_ROOT in your environment and do not give a PATH to --with-boost option. If you are sure you have boost installed, then check your version number looking in . See http://randspringer.de/boost for more documentation.]]) + else + AC_MSG_NOTICE([Your boost libraries seems to old (version $_version).]) + fi + # execute ACTION-IF-NOT-FOUND (if present): + ifelse([$3], , :, [$3]) + else + AC_SUBST(BOOST_CPPFLAGS) + AC_SUBST(BOOST_LDFLAGS) + AC_DEFINE(HAVE_BOOST,,[define if the Boost library is available]) + # execute ACTION-IF-FOUND (if present): + ifelse([$2], , :, [$2]) + fi + + CPPFLAGS="$CPPFLAGS_SAVED" + LDFLAGS="$LDFLAGS_SAVED" +fi + +]) diff --git a/m4/ax_boost_system.m4 b/m4/ax_boost_system.m4 new file mode 100644 index 00000000..c4c45559 --- /dev/null +++ b/m4/ax_boost_system.m4 @@ -0,0 +1,120 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_boost_system.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_BOOST_SYSTEM +# +# DESCRIPTION +# +# Test for System library from the Boost C++ libraries. The macro requires +# a preceding call to AX_BOOST_BASE. Further documentation is available at +# . +# +# This macro calls: +# +# AC_SUBST(BOOST_SYSTEM_LIB) +# +# And sets: +# +# HAVE_BOOST_SYSTEM +# +# LICENSE +# +# Copyright (c) 2008 Thomas Porschberg +# Copyright (c) 2008 Michael Tindal +# Copyright (c) 2008 Daniel Casimiro +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 17 + +AC_DEFUN([AX_BOOST_SYSTEM], +[ + AC_ARG_WITH([boost-system], + AS_HELP_STRING([--with-boost-system@<:@=special-lib@:>@], + [use the System library from boost - it is possible to specify a certain library for the linker + e.g. --with-boost-system=boost_system-gcc-mt ]), + [ + if test "$withval" = "no"; then + want_boost="no" + elif test "$withval" = "yes"; then + want_boost="yes" + ax_boost_user_system_lib="" + else + want_boost="yes" + ax_boost_user_system_lib="$withval" + fi + ], + [want_boost="yes"] + ) + + if test "x$want_boost" = "xyes"; then + AC_REQUIRE([AC_PROG_CC]) + AC_REQUIRE([AC_CANONICAL_BUILD]) + CPPFLAGS_SAVED="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" + export CPPFLAGS + + LDFLAGS_SAVED="$LDFLAGS" + LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" + export LDFLAGS + + AC_CACHE_CHECK(whether the Boost::System library is available, + ax_cv_boost_system, + [AC_LANG_PUSH([C++]) + CXXFLAGS_SAVE=$CXXFLAGS + + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[@%:@include ]], + [[boost::system::system_category]])], + ax_cv_boost_system=yes, ax_cv_boost_system=no) + CXXFLAGS=$CXXFLAGS_SAVE + AC_LANG_POP([C++]) + ]) + if test "x$ax_cv_boost_system" = "xyes"; then + AC_SUBST(BOOST_CPPFLAGS) + + AC_DEFINE(HAVE_BOOST_SYSTEM,,[define if the Boost::System library is available]) + BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/@<:@^\/@:>@*//'` + + LDFLAGS_SAVE=$LDFLAGS + if test "x$ax_boost_user_system_lib" = "x"; then + for libextension in `ls -r $BOOSTLIBDIR/libboost_system* 2>/dev/null | sed 's,.*/lib,,' | sed 's,\..*,,'` ; do + ax_lib=${libextension} + AC_CHECK_LIB($ax_lib, exit, + [BOOST_SYSTEM_LIB="-l$ax_lib"; AC_SUBST(BOOST_SYSTEM_LIB) link_system="yes"; break], + [link_system="no"]) + done + if test "x$link_system" != "xyes"; then + for libextension in `ls -r $BOOSTLIBDIR/boost_system* 2>/dev/null | sed 's,.*/,,' | sed -e 's,\..*,,'` ; do + ax_lib=${libextension} + AC_CHECK_LIB($ax_lib, exit, + [BOOST_SYSTEM_LIB="-l$ax_lib"; AC_SUBST(BOOST_SYSTEM_LIB) link_system="yes"; break], + [link_system="no"]) + done + fi + + else + for ax_lib in $ax_boost_user_system_lib boost_system-$ax_boost_user_system_lib; do + AC_CHECK_LIB($ax_lib, exit, + [BOOST_SYSTEM_LIB="-l$ax_lib"; AC_SUBST(BOOST_SYSTEM_LIB) link_system="yes"; break], + [link_system="no"]) + done + + fi + if test "x$ax_lib" = "x"; then + AC_MSG_ERROR(Could not find a version of the library!) + fi + if test "x$link_system" = "xno"; then + AC_MSG_ERROR(Could not link against $ax_lib !) + fi + fi + + CPPFLAGS="$CPPFLAGS_SAVED" + LDFLAGS="$LDFLAGS_SAVED" + fi +]) diff --git a/m4/ax_boost_thread.m4 b/m4/ax_boost_thread.m4 new file mode 100644 index 00000000..79e12cdb --- /dev/null +++ b/m4/ax_boost_thread.m4 @@ -0,0 +1,149 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_boost_thread.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_BOOST_THREAD +# +# DESCRIPTION +# +# Test for Thread library from the Boost C++ libraries. The macro requires +# a preceding call to AX_BOOST_BASE. Further documentation is available at +# . +# +# This macro calls: +# +# AC_SUBST(BOOST_THREAD_LIB) +# +# And sets: +# +# HAVE_BOOST_THREAD +# +# LICENSE +# +# Copyright (c) 2009 Thomas Porschberg +# Copyright (c) 2009 Michael Tindal +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 27 + +AC_DEFUN([AX_BOOST_THREAD], +[ + AC_ARG_WITH([boost-thread], + AS_HELP_STRING([--with-boost-thread@<:@=special-lib@:>@], + [use the Thread library from boost - it is possible to specify a certain library for the linker + e.g. --with-boost-thread=boost_thread-gcc-mt ]), + [ + if test "$withval" = "no"; then + want_boost="no" + elif test "$withval" = "yes"; then + want_boost="yes" + ax_boost_user_thread_lib="" + else + want_boost="yes" + ax_boost_user_thread_lib="$withval" + fi + ], + [want_boost="yes"] + ) + + if test "x$want_boost" = "xyes"; then + AC_REQUIRE([AC_PROG_CC]) + AC_REQUIRE([AC_CANONICAL_BUILD]) + CPPFLAGS_SAVED="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" + export CPPFLAGS + + LDFLAGS_SAVED="$LDFLAGS" + LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" + export LDFLAGS + + AC_CACHE_CHECK(whether the Boost::Thread library is available, + ax_cv_boost_thread, + [AC_LANG_PUSH([C++]) + CXXFLAGS_SAVE=$CXXFLAGS + + if test "x$host_os" = "xsolaris" ; then + CXXFLAGS="-pthreads $CXXFLAGS" + elif test "x$host_os" = "xmingw32" ; then + CXXFLAGS="-mthreads $CXXFLAGS" + else + CXXFLAGS="-pthread $CXXFLAGS" + fi + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[@%:@include ]], + [[boost::thread_group thrds; + return 0;]])], + ax_cv_boost_thread=yes, ax_cv_boost_thread=no) + CXXFLAGS=$CXXFLAGS_SAVE + AC_LANG_POP([C++]) + ]) + if test "x$ax_cv_boost_thread" = "xyes"; then + if test "x$host_os" = "xsolaris" ; then + BOOST_CPPFLAGS="-pthreads $BOOST_CPPFLAGS" + elif test "x$host_os" = "xmingw32" ; then + BOOST_CPPFLAGS="-mthreads $BOOST_CPPFLAGS" + else + BOOST_CPPFLAGS="-pthread $BOOST_CPPFLAGS" + fi + + AC_SUBST(BOOST_CPPFLAGS) + + AC_DEFINE(HAVE_BOOST_THREAD,,[define if the Boost::Thread library is available]) + BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/@<:@^\/@:>@*//'` + + LDFLAGS_SAVE=$LDFLAGS + case "x$host_os" in + *bsd* ) + LDFLAGS="-pthread $LDFLAGS" + break; + ;; + esac + if test "x$ax_boost_user_thread_lib" = "x"; then + for libextension in `ls -r $BOOSTLIBDIR/libboost_thread* 2>/dev/null | sed 's,.*/lib,,' | sed 's,\..*,,'`; do + ax_lib=${libextension} + AC_CHECK_LIB($ax_lib, exit, + [BOOST_THREAD_LIB="-l$ax_lib"; AC_SUBST(BOOST_THREAD_LIB) link_thread="yes"; break], + [link_thread="no"]) + done + if test "x$link_thread" != "xyes"; then + for libextension in `ls -r $BOOSTLIBDIR/boost_thread* 2>/dev/null | sed 's,.*/,,' | sed 's,\..*,,'`; do + ax_lib=${libextension} + AC_CHECK_LIB($ax_lib, exit, + [BOOST_THREAD_LIB="-l$ax_lib"; AC_SUBST(BOOST_THREAD_LIB) link_thread="yes"; break], + [link_thread="no"]) + done + fi + + else + for ax_lib in $ax_boost_user_thread_lib boost_thread-$ax_boost_user_thread_lib; do + AC_CHECK_LIB($ax_lib, exit, + [BOOST_THREAD_LIB="-l$ax_lib"; AC_SUBST(BOOST_THREAD_LIB) link_thread="yes"; break], + [link_thread="no"]) + done + + fi + if test "x$ax_lib" = "x"; then + AC_MSG_ERROR(Could not find a version of the library!) + fi + if test "x$link_thread" = "xno"; then + AC_MSG_ERROR(Could not link against $ax_lib !) + else + case "x$host_os" in + *bsd* ) + BOOST_LDFLAGS="-pthread $BOOST_LDFLAGS" + break; + ;; + esac + + fi + fi + + CPPFLAGS="$CPPFLAGS_SAVED" + LDFLAGS="$LDFLAGS_SAVED" + fi +]) diff --git a/src/Makefile.am b/src/Makefile.am index af8a76c5..15dfd698 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -20,6 +20,7 @@ # 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. +SUBDIRS = includes bin_PROGRAMS = check_PROGRAMS = @@ -30,6 +31,7 @@ AM_CPPFLAGS = \ -I$(top_srcdir)/lib/includes \ -I$(top_builddir)/lib/includes \ -I$(top_srcdir)/lib \ + -I$(top_srcdir)/src/includes \ -I$(top_srcdir)/third-party \ @LIBSPDYLAY_CFLAGS@ \ @XML_CPPFLAGS@ \ @@ -163,3 +165,33 @@ inflatehd_SOURCES = inflatehd.cc $(HPACK_TOOLS_COMMON_SRCS) deflatehd_SOURCES = deflatehd.cc $(HPACK_TOOLS_COMMON_SRCS) endif # ENABLE_HPACK_TOOLS + +if ENABLE_ASIO_LIB + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = libnghttp2_asio.pc +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 + +libnghttp2_asio_la_CPPFLAGS= ${BOOST_CPPFLAGS} ${AM_CPPFLAGS} +libnghttp2_asio_la_LDFLAGS = \ + ${BOOST_LDFLAGS} \ + ${BOOST_ASIO_LIB} \ + ${BOOST_THREAD_LIB} \ + ${BOOST_SYSTEM_LIB} \ + @OPENSSL_LIBS@ \ + -no-undefined \ + -version-info 0:0:0 +libnghttp2_asio_la_LIBADD = $(top_builddir)/lib/libnghttp2.la + +endif # ENABLE_ASIO_LIB diff --git a/src/asio_connection.h b/src/asio_connection.h new file mode 100644 index 00000000..4736517b --- /dev/null +++ b/src/asio_connection.h @@ -0,0 +1,187 @@ +/* + * 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: +// +// connection.hpp +// ~~~~~~~~~~~~~~ +// +// 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) +// + +#ifndef HTTP_SERVER2_CONNECTION_HPP +#define HTTP_SERVER2_CONNECTION_HPP + +#include "nghttp2_config.h" + +#include + +#include +#include +#include + +#include +#include "asio_http2_handler.h" +#include "util.h" + +namespace nghttp2 { + +namespace asio_http2 { + +namespace server { + +/// Represents a single connection from a client. +template +class connection + : public std::enable_shared_from_this>, + private boost::noncopyable +{ +public: + /// Construct a connection with the given io_service. + template + explicit connection(request_cb cb, SocketArgs&&... args) + : socket_(std::forward(args)...), + request_cb_(std::move(cb)), + writing_(false) + {} + + /// Start the first asynchronous operation for the connection. + void start() + { + handler_ = std::make_shared + (socket_.get_io_service(), + [this]() + { + do_write(); + }, + request_cb_); + if(handler_->start() != 0) { + return; + } + do_read(); + } + + socket_type& socket() + { + return socket_; + } + + void do_read() + { + auto self = this->shared_from_this(); + + socket_.async_read_some + (boost::asio::buffer(buffer_), + [this, self](const boost::system::error_code& e, + std::size_t bytes_transferred) + { + if (!e) { + if(handler_->on_read(buffer_, bytes_transferred) != 0) { + return; + } + + do_write(); + + if(!writing_ && handler_->should_stop()) { + return; + } + + do_read(); + } + + // If an error occurs then no new asynchronous operations are + // started. This means that all shared_ptr references to the + // connection object will disappear and the object will be + // destroyed automatically after this handler returns. The + // connection class's destructor closes the socket. + }); + } + + void do_write() + { + auto self = this->shared_from_this(); + + if(writing_) { + return; + } + + int rv; + std::size_t nwrite; + + rv = handler_->on_write(outbuf_, nwrite); + + if(rv != 0) { + return; + } + + if(nwrite == 0) { + return; + } + + writing_ = true; + + boost::asio::async_write + (socket_, boost::asio::buffer(outbuf_, nwrite), + [this, self](const boost::system::error_code& e, + std::size_t) + { + if(!e) { + writing_ = false; + + do_write(); + } + }); + + // No new asynchronous operations are started. This means that all + // shared_ptr references to the connection object will disappear and + // the object will be destroyed automatically after this handler + // returns. The connection class's destructor closes the socket. + } + +private: + socket_type socket_; + + request_cb request_cb_; + + std::shared_ptr handler_; + + /// Buffer for incoming data. + boost::array buffer_; + + boost::array outbuf_; + + bool writing_; +}; + +} // namespace server + +} // namespace asio_http2 + +} // namespace nghttp2 + +#endif // HTTP_SERVER2_CONNECTION_HPP diff --git a/src/asio_http2_handler.cc b/src/asio_http2_handler.cc new file mode 100644 index 00000000..be73da0a --- /dev/null +++ b/src/asio_http2_handler.cc @@ -0,0 +1,845 @@ +/* + * 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 + +#include "http2.h" +#include "util.h" + +namespace nghttp2 { + +namespace asio_http2 { + +namespace server { + +extern std::shared_ptr cached_date; + +request::request() + : impl_(util::make_unique()) +{} + +const std::vector
& 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
headers) +{ + return impl_->push(std::move(method), std::move(path), std::move(headers)); +} + +bool request::pushed() const +{ + return impl_->pushed(); +} + +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)); +} + +request_impl& request::impl() +{ + return *impl_; +} + +response::response() + : impl_(util::make_unique()) +{} + +void response::write_head(unsigned int status_code, + std::vector
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)); +} + +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
& 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
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
headers) +{ + if(handler_.expired() || stream_.expired()) { + 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; +} + +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); +} + +void request_impl::handler(std::weak_ptr h) +{ + handler_ = std::move(h); +} + +void request_impl::stream(std::weak_ptr 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
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::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_ || handler_.expired() || stream_.expired()) { + 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::started() const +{ + return started_; +} + +const std::vector
& response_impl::headers() const +{ + return headers_; +} + +void response_impl::handler(std::weak_ptr h) +{ + handler_ = std::move(h); +} + +void response_impl::stream(std::weak_ptr s) +{ + stream_ = std::move(s); +} + +std::pair 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()), + response_(std::make_shared()), + stream_id_(stream_id) +{} + +int32_t http2_stream::get_stream_id() const +{ + return stream_id_; +} + +const std::shared_ptr& http2_stream::get_request() +{ + return request_; +} + +const std::shared_ptr& 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(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(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; + } + + if(!nghttp2_check_header_name(name, namelen) || + !nghttp2_check_header_value(value, valuelen)) { + stream_error(session, stream_id, NGHTTP2_PROTOCOL_ERROR); + + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + + auto& req = stream->get_request()->impl(); + + if(name[0] == ':' && !req.headers().empty()) { + stream_error(session, stream_id, NGHTTP2_PROTOCOL_ERROR); + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + + if(util::streq(":method", name, namelen)) { + if(!req.method().empty()) { + stream_error(session, stream_id, NGHTTP2_PROTOCOL_ERROR); + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + req.method(std::string(value, value + valuelen)); + } else if(util::streq(":scheme", name, namelen)) { + if(!req.scheme().empty()) { + stream_error(session, stream_id, NGHTTP2_PROTOCOL_ERROR); + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + req.scheme(std::string(value, value + valuelen)); + } else if(util::streq(":authority", name, namelen)) { + if(!req.authority().empty()) { + stream_error(session, stream_id, NGHTTP2_PROTOCOL_ERROR); + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + req.authority(std::string(value, value + valuelen)); + } else if(util::streq(":path", name, namelen)) { + if(!req.path().empty()) { + stream_error(session, stream_id, NGHTTP2_PROTOCOL_ERROR); + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + req.path(std::string(value, value + valuelen)); + } else { + if(name[0] == ':') { + stream_error(session, stream_id, NGHTTP2_PROTOCOL_ERROR); + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + + if(util::streq("host", name, namelen)) { + req.host(std::string(value, value + valuelen)); + } + + 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(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.method().empty() || req.scheme().empty() || req.path().empty() || + (req.authority().empty() && req.host().empty())) { + stream_error(session, frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR); + return 0; + } + + 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(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(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(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, connection_write writefun, request_cb cb) + : writefun_(writefun), + request_cb_(std::move(cb)), + 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 = util::defer(callbacks, nghttp2_session_callbacks_del); + + 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 = util::defer(option, nghttp2_option_del); + + 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_handler::create_stream(int32_t stream_id) +{ + auto stream = std::make_shared(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_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(); + nva.reserve(2 + headers.size()); + auto status = std::to_string(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(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_(); +} + +int http2_handler::push_promise(http2_stream& stream, std::string method, + std::string path, + std::vector
headers) +{ + int rv; + + auto& req = stream.get_request()->impl(); + + auto nva = std::vector(); + 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; +} + +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 diff --git a/src/asio_http2_handler.h b/src/asio_http2_handler.h new file mode 100644 index 00000000..7baa8dee --- /dev/null +++ b/src/asio_http2_handler.h @@ -0,0 +1,241 @@ +/* + * 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 +#include +#include +#include +#include +#include + +#include + +#include + +namespace nghttp2 { +namespace asio_http2 { +namespace server { + +class http2_handler; +class http2_stream; + +class request_impl { +public: + request_impl(); + + const std::vector
& 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
headers = {}); + + bool pushed() const; + + void on_data(data_cb cb); + void on_end(void_cb cb); + + void set_header(std::vector
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 h); + void stream(std::weak_ptr s); + void call_on_data(const uint8_t *data, std::size_t len); + void call_on_end(); +private: + std::vector
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 handler_; + std::weak_ptr stream_; + bool pushed_; +}; + +class response_impl { +public: + response_impl(); + void write_head(unsigned int status_code, std::vector
headers = {}); + void end(std::string data = ""); + void end(read_cb cb); + + unsigned int status_code() const; + const std::vector
& headers() const; + bool started() const; + void handler(std::weak_ptr h); + void stream(std::weak_ptr s); + read_cb::result_type call_read(uint8_t *data, std::size_t len); +private: + std::vector
headers_; + read_cb read_cb_; + std::weak_ptr handler_; + std::weak_ptr 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& get_request(); + const std::shared_ptr& get_response(); +private: + std::shared_ptr request_; + std::shared_ptr response_; + int32_t stream_id_; +}; + +struct callback_guard { + callback_guard(http2_handler& h); + ~callback_guard(); + http2_handler& handler; +}; + +typedef std::function connection_write; + +class http2_handler : public std::enable_shared_from_this { +public: + http2_handler(boost::asio::io_service& io_service, + connection_write writefun, + request_cb cb); + + ~http2_handler(); + + int start(); + + std::shared_ptr create_stream(int32_t stream_id); + void close_stream(int32_t stream_id); + std::shared_ptr 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; + + int push_promise(http2_stream& stream, std::string method, + std::string path, + std::vector
headers); + + template + int on_read(const boost::array& 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 + int on_write(boost::array& buffer, std::size_t& len) + { + callback_guard cg(*this); + + len = 0; + + if(buf_) { + std::copy(buf_, 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(data, data + nread, std::begin(buffer) + len); + + len += nread; + } + + return 0; + } + +private: + std::map> streams_; + connection_write writefun_; + request_cb request_cb_; + 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 // HTTP2_HANDLER_H diff --git a/src/asio_http2_impl.cc b/src/asio_http2_impl.cc new file mode 100644 index 00000000..43255e37 --- /dev/null +++ b/src/asio_http2_impl.cc @@ -0,0 +1,181 @@ +/* + * 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_impl.h" + +#include + +#include + +#include + +#include "asio_server.h" +#include "util.h" +#include "ssl.h" + +namespace nghttp2 { + +namespace asio_http2 { + +namespace server { + +http2::http2() + : impl_(util::make_unique()) +{} + +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)); +} + +http2_impl::http2_impl() + : num_threads_(1) +{} + +namespace { +std::array& +get_alpn_token() +{ + static std::array token; + + token[0] = NGHTTP2_PROTO_VERSION_ID_LEN; + std::copy(NGHTTP2_PROTO_VERSION_ID, + NGHTTP2_PROTO_VERSION_ID + NGHTTP2_PROTO_VERSION_ID_LEN, + std::begin(token) + 1); + return token; +} +} // namespace + +void http2_impl::listen(const std::string& address, uint16_t port, + request_cb cb) +{ + std::unique_ptr ssl_ctx; + + if(!private_key_file_.empty() && !certificate_file_.empty()) { + ssl_ctx = util::make_unique + (boost::asio::ssl::context::sslv23); + + ssl_ctx->use_private_key_file(private_key_file_, + boost::asio::ssl::context::pem); + ssl_ctx->use_certificate_chain_file(certificate_file_); + + auto ctx = ssl_ctx->native_handle(); + + SSL_CTX_set_options(ctx, + SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | + SSL_OP_SINGLE_ECDH_USE | + SSL_OP_NO_TICKET | + 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); + + auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + if(ecdh) { + SSL_CTX_set_tmp_ecdh(ctx, ecdh); + EC_KEY_free(ecdh); + } + + SSL_CTX_set_next_protos_advertised_cb + (ctx, + [](SSL *s, const unsigned char **data, unsigned int *len, void *arg) { + auto& token = get_alpn_token(); + + *data = token.data(); + *len = token.size(); + + return SSL_TLSEXT_ERR_OK; + }, nullptr); + } + + server(address, port, num_threads_, std::move(cb), std::move(ssl_ctx)).run(); +} + +void http2_impl::num_threads(size_t num_threads) +{ + num_threads_ = num_threads; +} + +void http2_impl::tls(std::string private_key_file, + std::string certificate_file) +{ + private_key_file_ = std::move(private_key_file); + certificate_file_ = std::move(certificate_file); +} + +template +std::shared_ptr> defer_shared(T&& t, F f) +{ + return std::make_shared>(std::forward(t), + std::forward(f)); +} + +read_cb file_reader(const std::string& path) +{ + auto fd = open(path.c_str(), O_RDONLY); + if(fd == -1) { + return read_cb(); + } + + auto d = defer_shared(static_cast(fd), close); + + 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); + }; +} + +} // namespace server + +} // namespace asio_http2 + +} // namespace nghttp2 diff --git a/src/asio_http2_impl.h b/src/asio_http2_impl.h new file mode 100644 index 00000000..0e1e84c9 --- /dev/null +++ b/src/asio_http2_impl.h @@ -0,0 +1,60 @@ +/* + * 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_HTTP2_IMPL_H +#define ASIO_HTTP2_IMPL_H + +#include "nghttp2_config.h" + +#include + +namespace nghttp2 { + +namespace asio_http2 { + +namespace server { + +class server; + +class http2_impl { +public: + http2_impl(); + void listen(const std::string& address, uint16_t port, + request_cb cb); + void num_threads(size_t num_threads); + void tls(std::string private_key_file, std::string certificate_file); +private: + std::string private_key_file_; + std::string certificate_file_; + std::unique_ptr server_; + std::size_t num_threads_; +}; + +} // namespace server + +} // namespace asio_http2 + +} // namespace nghttp2 + +#endif // ASIO_HTTP2_IMPL_H diff --git a/src/asio_io_service_pool.cc b/src/asio_io_service_pool.cc new file mode 100644 index 00000000..103e5bd7 --- /dev/null +++ b/src/asio_io_service_pool.cc @@ -0,0 +1,109 @@ +/* + * 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: +// +// io_service_pool.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 "asio_server.h" +#include +#include +#include +#include + +namespace nghttp2 { + +namespace asio_http2 { + +namespace server { + +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"); + } + + // Give all the io_services work to do so that their run() functions will not + // exit until they are explicitly stopped. + for (std::size_t i = 0; i < pool_size; ++i) + { + auto io_service = std::make_shared(); + auto work = std::make_shared(*io_service); + io_services_.push_back(io_service); + work_.push_back(work); + } +} + +void io_service_pool::run() +{ + // Create a pool of threads to run all of the io_services. + auto futs = std::vector>(); + + for (std::size_t i = 0; i < io_services_.size(); ++i) { + futs.push_back + (std::async(std::launch::async, + (size_t(boost::asio::io_service::*)(void)) + &boost::asio::io_service::run, + io_services_[i])); + } + + // Wait for all threads in the pool to exit. + for (auto& fut : futs) { + fut.get(); + } +} + +void io_service_pool::stop() +{ + // Explicitly stop all io_services. + for (auto& iosv : io_services_) { + iosv->stop(); + } +} + +boost::asio::io_service& io_service_pool::get_io_service() +{ + // Use a round-robin scheme to choose the next io_service to use. + auto& io_service = *io_services_[next_io_service_]; + ++next_io_service_; + if (next_io_service_ == io_services_.size()) { + next_io_service_ = 0; + } + return io_service; +} + +} // namespace server + +} // namespace asio_http2 + +} // namespace nghttp2 diff --git a/src/asio_io_service_pool.h b/src/asio_io_service_pool.h new file mode 100644 index 00000000..5cc9b31f --- /dev/null +++ b/src/asio_io_service_pool.h @@ -0,0 +1,92 @@ +/* + * 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: +// +// io_service_pool.hpp +// ~~~~~~~~~~~~~~~~~~~ +// +// 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) +// + +#ifndef HTTP_SERVER2_IO_SERVICE_POOL_HPP +#define HTTP_SERVER2_IO_SERVICE_POOL_HPP + +#include "nghttp2_config.h" + +#include +#include +#include +#include + +#include + +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); + + /// Run all io_service objects in the pool. + void run(); + + /// Stop all io_service objects in the pool. + void stop(); + + /// Get an io_service to use. + boost::asio::io_service& get_io_service(); + +private: + typedef std::shared_ptr io_service_ptr; + typedef std::shared_ptr work_ptr; + + /// The pool of io_services. + std::vector io_services_; + + /// The work that keeps the io_services running. + std::vector work_; + + /// The next io_service to use for a connection. + std::size_t next_io_service_; +}; + +} // namespace server + +} // namespace asio_http2 + +} // namespace nghttp2 + +#endif // HTTP_SERVER2_IO_SERVICE_POOL_HPP diff --git a/src/asio_server.cc b/src/asio_server.cc new file mode 100644 index 00000000..50b28575 --- /dev/null +++ b/src/asio_server.cc @@ -0,0 +1,162 @@ +/* + * 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: +// +// server.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 "asio_server.h" + +#include + +namespace nghttp2 { +namespace asio_http2 { +namespace server { + +server::server(const std::string& address, uint16_t port, + std::size_t io_service_pool_size, + request_cb cb, + std::unique_ptr ssl_ctx) + : 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)), + acceptor_(io_service_pool_.get_io_service()), + ssl_ctx_(std::move(ssl_ctx)), + request_cb_(std::move(cb)) +{ + // 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. + signals_.add(SIGINT); + signals_.add(SIGTERM); +#if defined(SIGQUIT) + signals_.add(SIGQUIT); +#endif // defined(SIGQUIT) + signals_.async_wait([this](const boost::system::error_code& error, + int signal_number) + { + io_service_pool_.stop(); + }); + + // Open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR). + boost::asio::ip::tcp::resolver resolver(acceptor_.get_io_service()); + boost::asio::ip::tcp::resolver::query query(address, std::to_string(port)); + boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(query); + acceptor_.open(endpoint.protocol()); + acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); + acceptor_.bind(endpoint); + acceptor_.listen(); + + start_accept(); + + start_timer(); +} + +void server::run() +{ + io_service_pool_.run(); +} + +std::shared_ptr cached_date; + +namespace { +void update_date() +{ + cached_date = std::make_shared(util::http_date(time(nullptr))); +} +} // namespace + +void server::start_timer() +{ + update_date(); + + tick_timer_.async_wait + ([this](const boost::system::error_code& e) + { + tick_timer_.expires_at(tick_timer_.expires_at() + + boost::posix_time::seconds(1)); + start_timer(); + }); +} + +typedef boost::asio::ssl::stream ssl_socket; + +void server::start_accept() +{ + if(ssl_ctx_) { + auto new_connection = + std::make_shared> + (request_cb_, io_service_pool_.get_io_service(), *ssl_ctx_); + + 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(); + } + }); + } + + start_accept(); + }); + } else { + auto new_connection = + std::make_shared> + (request_cb_, io_service_pool_.get_io_service()); + + 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(); + } + + start_accept(); + }); + } +} + +} // namespace server +} // namespace asio_http2 +} // namespace nghttp2 diff --git a/src/asio_server.h b/src/asio_server.h new file mode 100644 index 00000000..a69e9650 --- /dev/null +++ b/src/asio_server.h @@ -0,0 +1,103 @@ +/* + * 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: +// +// server.hpp +// ~~~~~~~~~~ +// +// 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) +// + +#ifndef HTTP_SERVER2_SERVER_HPP +#define HTTP_SERVER2_SERVER_HPP + +#include "nghttp2_config.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "asio_connection.h" +#include "asio_io_service_pool.h" + +namespace nghttp2 { + +namespace asio_http2 { + +namespace server { + +/// 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, + request_cb cb, + std::unique_ptr ssl_ctx); + + /// Run the server's io_service loop. + void run(); + +private: + /// Initiate an asynchronous accept operation. + void start_accept(); + + void start_timer(); + + /// The pool of io_service objects used to perform asynchronous operations. + io_service_pool io_service_pool_; + + /// The signal_set is used to register for process termination notifications. + boost::asio::signal_set signals_; + + boost::asio::deadline_timer tick_timer_; + + /// Acceptor used to listen for incoming connections. + boost::asio::ip::tcp::acceptor acceptor_; + + std::unique_ptr ssl_ctx_; + + request_cb request_cb_; +}; + +} // namespace server + +} // namespace asio_http2 + +} // namespace nghttp2 + +#endif // HTTP_SERVER2_SERVER_HPP diff --git a/src/includes/Makefile.am b/src/includes/Makefile.am new file mode 100644 index 00000000..e642d5f3 --- /dev/null +++ b/src/includes/Makefile.am @@ -0,0 +1,23 @@ +# 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. +nobase_include_HEADERS = nghttp2/asio_http2.h diff --git a/src/includes/nghttp2/asio_http2.h b/src/includes/nghttp2/asio_http2.h new file mode 100644 index 00000000..6a7cee54 --- /dev/null +++ b/src/includes/nghttp2/asio_http2.h @@ -0,0 +1,177 @@ +/* + * 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_HTTP2_H +#define ASIO_HTTP2_H + +#include +#include +#include +#include +#include + +namespace nghttp2 { + +namespace asio_http2 { + +struct header { + std::string name; + std::string value; +}; + +namespace server { + +class request_impl; +class response_impl; + +typedef std::function data_cb; +typedef std::function void_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). Currently, +// returning std::make_pair(0, false) is reserved for future use. +typedef std::function + (uint8_t *buf, std::size_t len)> read_cb; + +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
& 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
headers = {}); + + // Returns true if this is pushed request. + bool pushed() const; + + // Application must not call this directly. + request_impl& impl(); +private: + std::unique_ptr 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
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. Not implemented yet. + 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 impl_; +}; + +typedef std::function, + std::shared_ptr)> 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. + 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); +private: + std::unique_ptr impl_; +}; + +// 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); + +} // namespace server + +} // namespace asio_http2 + +} // namespace nghttp2 + +#endif // ASIO_HTTP2_H diff --git a/src/libnghttp2_asio.pc.in b/src/libnghttp2_asio.pc.in new file mode 100644 index 00000000..ec4fbbb1 --- /dev/null +++ b/src/libnghttp2_asio.pc.in @@ -0,0 +1,33 @@ +# 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. +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libnghttp2_asio +Description: HTTP/2 C++ library +URL: https://github.com/tatsuhiro-t/nghttp2 +Version: @VERSION@ +Libs: -L${libdir} -lnghttp2_asio +Cflags: -I${includedir}