diff --git a/.gitignore b/.gitignore index e2dc999f..8c089da4 100644 --- a/.gitignore +++ b/.gitignore @@ -54,4 +54,3 @@ _VC_ROOT/ .depend.MSVC *.pyd *.egg-info/ -python/nghttp2.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f571f71..1c8d87a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,9 +52,8 @@ endif() include(GNUInstallDirs) -# For Python bindings and documentation -# (Must be called before PythonLibs for matching versions.) -find_package(PythonInterp) +# For documentation +find_package(Python3 COMPONENTS Interpreter) # Auto-detection of features that can be toggled find_package(OpenSSL 1.0.1) @@ -84,13 +83,6 @@ set(ENABLE_HPACK_TOOLS_DEFAULT ${JANSSON_FOUND}) # 2.0.8 is required because we use evconnlistener_set_error_cb() find_package(Libevent 2.0.8 COMPONENTS core extra openssl) set(ENABLE_EXAMPLES_DEFAULT ${LIBEVENT_OPENSSL_FOUND}) -find_package(Cython) -find_package(PythonLibs) -if(CYTHON_FOUND AND PYTHONLIBS_FOUND) - set(ENABLE_PYTHON_BINDINGS_DEFAULT ON) -else() - set(ENABLE_PYTHON_BINDINGS_DEFAULT OFF) -endif() find_package(LibXml2 2.6.26) set(WITH_LIBXML2_DEFAULT ${LIBXML2_FOUND}) @@ -99,8 +91,7 @@ set(WITH_JEMALLOC_DEFAULT ${JEMALLOC_FOUND}) include(CMakeOptions.txt) -if(ENABLE_LIB_ONLY AND (ENABLE_APP OR ENABLE_HPACK_TOOLS OR ENABLE_EXAMPLES OR - ENABLE_PYTHON_BINDINGS)) +if(ENABLE_LIB_ONLY AND (ENABLE_APP OR ENABLE_HPACK_TOOLS OR ENABLE_EXAMPLES)) # Remember when disabled options are disabled for later diagnostics. set(ENABLE_LIB_ONLY_DISABLED_OTHERS 1) else() @@ -110,7 +101,6 @@ if(ENABLE_LIB_ONLY) set(ENABLE_APP OFF) set(ENABLE_HPACK_TOOLS OFF) set(ENABLE_EXAMPLES OFF) - set(ENABLE_PYTHON_BINDINGS OFF) endif() # Do not disable assertions based on CMAKE_BUILD_TYPE. @@ -156,20 +146,6 @@ cmake_pop_check_state() # Additional libraries required for programs under src directory. set(APP_LIBRARIES) -if(ENABLE_PYTHON_BINDINGS) - if(NOT (CYTHON_FOUND AND PYTHONLIBS_FOUND)) - message(FATAL_ERROR "python bindings were requested " - "(ENABLE_PYTHON_BINDINGS=1) but dependencies are not met.") - endif() - if(NOT PYTHON_VERSION_STRING STREQUAL PYTHONLIBS_VERSION_STRING) - message(FATAL_ERROR - "Python executable and library must have the same version!" - " Found Python ${PYTHON_VERSION_STRING} and" - " PythonLibs ${PYTHONLIBS_VERSION_STRING}" - ) - endif() -endif() - set(CMAKE_THREAD_PREFER_PTHREAD 1) find_package(Threads) if(CMAKE_USE_PTHREADS_INIT) @@ -463,7 +439,6 @@ set(sbindir "${CMAKE_INSTALL_FULL_SBINDIR}") foreach(name lib/libnghttp2.pc lib/includes/nghttp2/nghttp2ver.h - python/setup.py integration-tests/config.go integration-tests/setenv doc/conf.py @@ -474,7 +449,6 @@ foreach(name doc/tutorial-hpack.rst doc/nghttpx-howto.rst doc/h2load-howto.rst - doc/python-apiref.rst doc/building-android-binary.rst doc/nghttp2.h.rst doc/nghttp2ver.h.rst @@ -502,7 +476,6 @@ add_subdirectory(third-party) add_subdirectory(src) #add_subdirectory(src/includes) add_subdirectory(examples) -add_subdirectory(python) add_subdirectory(tests) #add_subdirectory(tests/testdata) add_subdirectory(integration-tests) @@ -530,10 +503,8 @@ message(STATUS "summary of build options: WARNCFLAGS: ${WARNCFLAGS} CXX1XCXXFLAGS: ${CXX1XCXXFLAGS} Python: - Python: ${PYTHON_EXECUTABLE} - PYTHON_VERSION: ${PYTHON_VERSION_STRING} - Library version:${PYTHONLIBS_VERSION_STRING} - Cython: ${CYTHON_EXECUTABLE} + Python: ${Python3_EXECUTABLE} + Python3_VERSION: ${Python3_VERSION} Test: CUnit: ${HAVE_CUNIT} (LIBS='${CUNIT_LIBRARIES}') Failmalloc: ${ENABLE_FAILMALLOC} @@ -559,7 +530,6 @@ message(STATUS "summary of build options: Applications: ${ENABLE_APP} HPACK tools: ${ENABLE_HPACK_TOOLS} Examples: ${ENABLE_EXAMPLES} - Python bindings:${ENABLE_PYTHON_BINDINGS} Threading: ${ENABLE_THREADS} HTTP/3(EXPERIMENTAL): ${ENABLE_HTTP3} ") diff --git a/CMakeOptions.txt b/CMakeOptions.txt index 0a432ad8..238d5b1b 100644 --- a/CMakeOptions.txt +++ b/CMakeOptions.txt @@ -9,10 +9,8 @@ option(ENABLE_HPACK_TOOLS "Build HPACK tools" ${ENABLE_HPACK_TOOLS_DEFAULT}) option(ENABLE_EXAMPLES "Build examples" ${ENABLE_EXAMPLES_DEFAULT}) -option(ENABLE_PYTHON_BINDINGS "Build Python bindings" - ${ENABLE_PYTHON_BINDINGS_DEFAULT}) option(ENABLE_FAILMALLOC "Build failmalloc test program" ON) -option(ENABLE_LIB_ONLY "Build libnghttp2 only. This is a short hand for -DENABLE_APP=0 -DENABLE_EXAMPLES=0 -DENABLE_HPACK_TOOLS=0 -DENABLE_PYTHON_BINDINGS=0") +option(ENABLE_LIB_ONLY "Build libnghttp2 only. This is a short hand for -DENABLE_APP=0 -DENABLE_EXAMPLES=0 -DENABLE_HPACK_TOOLS=0") option(ENABLE_STATIC_LIB "Build libnghttp2 in static mode also") option(ENABLE_SHARED_LIB "Build libnghttp2 as a shared library" ON) option(ENABLE_STATIC_CRT "Build libnghttp2 against the MS LIBCMT[d]") diff --git a/Dockerfile.android b/Dockerfile.android index f2e66c41..b38b0727 100644 --- a/Dockerfile.android +++ b/Dockerfile.android @@ -33,7 +33,7 @@ RUN apt-get update && \ apt-get install -y unzip make binutils autoconf \ automake autotools-dev libtool pkg-config git \ curl dpkg-dev libxml2-dev genisoimage libc6-i386 \ - lib32stdc++6 python3 && \ + lib32stdc++6 && \ rm -rf /var/cache/apt/* # Download NDK @@ -112,7 +112,6 @@ RUN autoreconf -i && \ --host=$TARGET \ --build=`dpkg-architecture -qDEB_BUILD_GNU_TYPE` \ --without-libxml2 \ - --disable-python-bindings \ --disable-examples \ --disable-threads \ CPPFLAGS="-fPIE -I$PREFIX/include" \ diff --git a/Makefile.am b/Makefile.am index 5b0df446..a7cb1af2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -20,14 +20,9 @@ # 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 = lib third-party src bpf examples python tests integration-tests \ +SUBDIRS = lib third-party src bpf examples tests integration-tests \ doc contrib script -# Now with python setuptools, make uninstall will leave many files we -# cannot easily remove (e.g., easy-install.pth). Disable it for -# distcheck rule. -AM_DISTCHECK_CONFIGURE_FLAGS = --disable-python-bindings - ACLOCAL_AMFLAGS = -I m4 dist_doc_DATA = README.rst @@ -42,7 +37,6 @@ EXTRA_DIST = nghttpx.conf.sample proxy.pac.sample android-config android-env \ cmake/FindLibev.cmake \ cmake/FindCUnit.cmake \ cmake/Version.cmake \ - cmake/FindCython.cmake \ cmake/FindLibevent.cmake \ cmake/FindJansson.cmake \ cmake/FindLibcares.cmake \ diff --git a/README.rst b/README.rst index ae455eb2..2cfe358d 100644 --- a/README.rst +++ b/README.rst @@ -103,12 +103,6 @@ To mitigate heap fragmentation in long running server programs Alpine Linux currently does not support malloc replacement due to musl limitations. See details in issue `#762 `_. -The Python bindings (deprecated) require the following packages: - -* cython >= 0.19 -* python >= 3.8 -* python-setuptools - To enable mruby support for nghttpx, `mruby `_ is required. We need to build mruby with C++ ABI explicitly turned on, and probably need other @@ -400,7 +394,6 @@ Build nghttp2: $ git submodule update --init $ autoreconf -i $ ./configure --with-mruby --with-neverbleed --enable-http3 --with-libbpf \ - --disable-python-bindings \ CC=clang-14 CXX=clang++-14 \ PKG_CONFIG_PATH="$PWD/../openssl/build/lib/pkgconfig:$PWD/../nghttp3/build/lib/pkgconfig:$PWD/../ngtcp2/build/lib/pkgconfig:$PWD/../libbpf/build/lib64/pkgconfig" \ LDFLAGS="$LDFLAGS -Wl,-rpath,$PWD/../openssl/build/lib -Wl,-rpath,$PWD/../libbpf/build/lib64" @@ -1425,124 +1418,6 @@ associated value includes the state of the dynamic header table after the corresponding header set was processed. The format is the same as ``deflatehd``. -Python bindings ---------------- - -Python bindings have been deprecated. - -The ``python`` directory contains nghttp2 Python bindings. The -bindings currently provide HPACK compressor and decompressor classes -and an HTTP/2 server. - -The extension module is called ``nghttp2``. - -``make`` will build the bindings and target Python version is -determined by the ``configure`` script. If the detected Python version is not -what you expect, specify a path to Python executable in a ``PYTHON`` -variable as an argument to configure script (e.g., ``./configure -PYTHON=/usr/bin/python3.8``). - -The following example code illustrates basic usage of the HPACK compressor -and decompressor in Python: - -.. code-block:: python - - import binascii - import nghttp2 - - deflater = nghttp2.HDDeflater() - inflater = nghttp2.HDInflater() - - data = deflater.deflate([(b'foo', b'bar'), - (b'baz', b'buz')]) - print(binascii.b2a_hex(data)) - - hdrs = inflater.inflate(data) - print(hdrs) - -The ``nghttp2.HTTP2Server`` class builds on top of the asyncio event -loop. On construction, *RequestHandlerClass* must be given, which -must be a subclass of ``nghttp2.BaseRequestHandler`` class. - -The ``BaseRequestHandler`` class is used to handle the HTTP/2 stream. -By default, it does nothing. It must be subclassed to handle each -event callback method. - -The first callback method invoked is ``on_headers()``. It is called -when HEADERS frame, which includes the request header fields, has arrived. - -If the request has a request body, ``on_data(data)`` is invoked for each -chunk of received data. - -Once the entire request is received, ``on_request_done()`` is invoked. - -When the stream is closed, ``on_close(error_code)`` is called. - -The application can send a response using ``send_response()`` method. -It can be used in ``on_headers()``, ``on_data()`` or -``on_request_done()``. - -The application can push resources using the ``push()`` method. It must be -used before the ``send_response()`` call. - -The following instance variables are available: - -client_address - Contains a tuple of the form (host, port) referring to the - client's address. - -stream_id - Stream ID of this stream. - -scheme - Scheme of the request URI. This is a value of :scheme header - field. - -method - Method of this stream. This is a value of :method header field. - -host - This is a value of :authority or host header field. - -path - This is a value of :path header field. - -The following example illustrates the HTTP2Server and -BaseRequestHandler usage: - -.. code-block:: python - - #!/usr/bin/env python3 - - import io, ssl - import nghttp2 - - class Handler(nghttp2.BaseRequestHandler): - - def on_headers(self): - self.push(path='/css/bootstrap.css', - request_headers = [('content-length', '3')], - status=200, - body='foo') - - self.push(path='/js/bootstrap.js', - method='GET', - request_headers = [('content-length', '10')], - status=200, - body='foobarbuzz') - - self.send_response(status=200, - headers = [('content-type', 'text/plain')], - body=io.BytesIO(b'nghttp2-python FTW')) - - ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23) - ctx.options = ssl.OP_ALL | ssl.OP_NO_SSLv2 - ctx.load_cert_chain('server.crt', 'server.key') - - # give None to ssl to make the server non-SSL/TLS - server = nghttp2.HTTP2Server(('127.0.0.1', 8443), Handler, ssl=ctx) - server.serve_forever() - Contribution ------------ diff --git a/android-config b/android-config index e66074c8..4a137dea 100755 --- a/android-config +++ b/android-config @@ -30,7 +30,6 @@ --host=$TARGET \ --build=`dpkg-architecture -qDEB_BUILD_GNU_TYPE` \ --without-libxml2 \ - --disable-python-bindings \ --disable-examples \ --disable-threads \ CPPFLAGS="-fPIE -I$PREFIX/include" \ diff --git a/cmake/FindCython.cmake b/cmake/FindCython.cmake deleted file mode 100644 index 04aed1f8..00000000 --- a/cmake/FindCython.cmake +++ /dev/null @@ -1,44 +0,0 @@ -# Find the Cython compiler. -# -# This code sets the following variables: -# -# CYTHON_EXECUTABLE -# -# See also UseCython.cmake - -#============================================================================= -# Copyright 2011 Kitware, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#============================================================================= - -# Use the Cython executable that lives next to the Python executable -# if it is a local installation. -find_package( PythonInterp ) -if( PYTHONINTERP_FOUND ) - get_filename_component( _python_path ${PYTHON_EXECUTABLE} PATH ) - find_program( CYTHON_EXECUTABLE - NAMES cython cython.bat cython3 - HINTS ${_python_path} - ) -else() - find_program( CYTHON_EXECUTABLE - NAMES cython cython.bat cython3 - ) -endif() - - -include( FindPackageHandleStandardArgs ) -FIND_PACKAGE_HANDLE_STANDARD_ARGS( Cython REQUIRED_VARS CYTHON_EXECUTABLE ) - -mark_as_advanced( CYTHON_EXECUTABLE ) diff --git a/configure.ac b/configure.ac index 90412919..6554bae6 100644 --- a/configure.ac +++ b/configure.ac @@ -87,11 +87,6 @@ AC_ARG_ENABLE([examples], [Build examples [default=check]])], [request_examples=$enableval], [request_examples=check]) -AC_ARG_ENABLE([python-bindings], - [AS_HELP_STRING([--enable-python-bindings], - [Build Python bindings [default=no]])], - [request_python_bindings=$enableval], [request_python_bindings=no]) - AC_ARG_ENABLE([failmalloc], [AS_HELP_STRING([--disable-failmalloc], [Do not build failmalloc test program])], @@ -99,7 +94,7 @@ AC_ARG_ENABLE([failmalloc], AC_ARG_ENABLE([lib-only], [AS_HELP_STRING([--enable-lib-only], - [Build libnghttp2 only. This is a short hand for --disable-app --disable-examples --disable-hpack-tools --disable-python-bindings])], + [Build libnghttp2 only. This is a short hand for --disable-app --disable-examples --disable-hpack-tools])], [request_lib_only=$enableval], [request_lib_only=no]) AC_ARG_ENABLE([http3], @@ -167,11 +162,6 @@ AC_ARG_WITH([neverbleed], [Use neverbleed [default=no]])], [request_neverbleed=$withval], [request_neverbleed=no]) -AC_ARG_WITH([cython], - [AS_HELP_STRING([--with-cython=PATH], - [Use cython in given PATH])], - [cython_path=$withval], []) - AC_ARG_WITH([libngtcp2], [AS_HELP_STRING([--with-libngtcp2], [Use libngtcp2 [default=check]])], @@ -188,8 +178,6 @@ AC_ARG_WITH([libbpf], [request_libbpf=$withval], [request_libbpf=no]) dnl Define variables -AC_ARG_VAR([CYTHON], [the Cython executable]) - AC_ARG_VAR([LIBEV_CFLAGS], [C compiler flags for libev, skipping any checks]) AC_ARG_VAR([LIBEV_LIBS], [linker flags for libev, skipping any checks]) @@ -215,16 +203,10 @@ PKG_PROG_PKG_CONFIG([0.20]) AM_PATH_PYTHON([3.8],, [:]) -if test "x$request_python_bindings" = "xyes" && - test "x$PYTHON" = "x:"; then - AC_MSG_ERROR([python was requested (enable-python-bindings) but not found]) -fi - if [test "x$request_lib_only" = "xyes"]; then request_app=no request_hpack_tools=no request_examples=no - request_python_bindings=no request_http3=no request_libxml2=no request_jansson=no @@ -242,19 +224,6 @@ if [test "x$request_lib_only" = "xyes"]; then request_libbpf=no fi -if test "x$request_python_bindings" != "xno" && - test "x$PYTHON" != "x:"; then - # version check is broken - AX_PYTHON_DEVEL() -fi - -if test "x${cython_path}" = "x"; then - AC_CHECK_PROGS([CYTHON], [cython.py cython]) -else - CYTHON=${cython_path} - AC_SUBST([CYTHON]) -fi - if test "x$GCC" = "xyes" -o "x$CC" = "xclang" ; then AC_DEFINE([NGHTTP2_NORETURN], [__attribute__((noreturn))], [Hint to the compiler that a function never return]) else @@ -857,27 +826,6 @@ AM_CONDITIONAL([ENABLE_THIRD_PARTY], [ test "x${enable_third_party}" = "xyes" ]) AM_CONDITIONAL([HAVE_MRUBY], [test "x${have_mruby}" = "xyes"]) AM_CONDITIONAL([HAVE_NEVERBLEED], [test "x${have_neverbleed}" = "xyes"]) -# Python bindings -enable_python_bindings=no -if test "x${request_python_bindings}" != "xno" && - test "x${CYTHON}" != "x" && - test "x${PYTHON}" != "x:" && - test "x${PYTHON_VERSION}" != "x"; then - enable_python_bindings=yes -fi - -if test "x${request_python_bindings}" = "xyes" && - test "x${enable_python_bindings}" != "xyes"; then - AC_MSG_ERROR([python bindings were requested (--enable-python-bindings) but dependencies are not met.]) -fi - -AM_CONDITIONAL([ENABLE_PYTHON_BINDINGS], - [test "x${enable_python_bindings}" = "xyes"]) - -# Produce cython conditional, so that we can distribute generated C -# source -AM_CONDITIONAL([HAVE_CYTHON], [test "x${CYTHON}" != "x"]) - # failmalloc tests enable_failmalloc=no if test "x${request_failmalloc}" = "xyes"; then @@ -1126,8 +1074,6 @@ AC_CONFIG_FILES([ src/Makefile bpf/Makefile examples/Makefile - python/Makefile - python/setup.py integration-tests/Makefile integration-tests/config.go integration-tests/setenv @@ -1140,7 +1086,6 @@ AC_CONFIG_FILES([ doc/tutorial-hpack.rst doc/nghttpx-howto.rst doc/h2load-howto.rst - doc/python-apiref.rst doc/building-android-binary.rst doc/nghttp2.h.rst doc/nghttp2ver.h.rst @@ -1185,10 +1130,6 @@ AC_MSG_NOTICE([summary of build options: Python: Python: ${PYTHON} PYTHON_VERSION: ${PYTHON_VERSION} - pyexecdir: ${pyexecdir} - PYTHON_CPPFLAGS:${PYTHON_CPPFLAGS} - PYTHON_LIBS: ${PYTHON_LIBS} - Cython: ${CYTHON} Test: CUnit: ${have_cunit} (CFLAGS='${CUNIT_CFLAGS}' LIBS='${CUNIT_LIBS}') Failmalloc: ${enable_failmalloc} @@ -1215,7 +1156,6 @@ AC_MSG_NOTICE([summary of build options: Applications: ${enable_app} HPACK tools: ${enable_hpack_tools} Examples: ${enable_examples} - Python bindings:${enable_python_bindings} Threading: ${enable_threads} HTTP/3 (EXPERIMENTAL): ${enable_http3} ]) diff --git a/doc/.gitignore b/doc/.gitignore index b0cdabf6..ed9e6349 100644 --- a/doc/.gitignore +++ b/doc/.gitignore @@ -13,7 +13,6 @@ nghttp2_*.rst nghttp2ver.h.rst nghttpx-howto.rst package_README.rst -python-apiref.rst tutorial-client.rst tutorial-hpack.rst tutorial-server.rst diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index be28410a..531791fc 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -180,7 +180,6 @@ set(EXTRA_DIST sources/tutorial-hpack.rst sources/nghttpx-howto.rst sources/h2load-howto.rst - sources/python-apiref.rst sources/building-android-binary.rst sources/contribute.rst _exts/rubydomain/LICENSE.rubydomain @@ -268,7 +267,7 @@ add_custom_command( apiref.rst ${APIDOCS} COMMAND - "${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/mkapiref.py" + "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/mkapiref.py" apiref.rst macros.rst enums.rst types.rst . ${apiref_SOURCES} DEPENDS diff --git a/doc/Makefile.am b/doc/Makefile.am index f070f9dd..c7184031 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -206,7 +206,6 @@ EXTRA_DIST = \ sources/tutorial-hpack.rst \ sources/nghttpx-howto.rst \ sources/h2load-howto.rst \ - sources/python-apiref.rst \ sources/building-android-binary.rst \ sources/contribute.rst \ sources/security.rst \ diff --git a/doc/python-apiref.rst.in b/doc/python-apiref.rst.in deleted file mode 100644 index 5fd40dee..00000000 --- a/doc/python-apiref.rst.in +++ /dev/null @@ -1 +0,0 @@ -.. include:: @top_srcdir@/doc/sources/python-apiref.rst diff --git a/doc/sources/index.rst b/doc/sources/index.rst index 2c2589da..b03c3489 100644 --- a/doc/sources/index.rst +++ b/doc/sources/index.rst @@ -31,7 +31,6 @@ Contents: h2load-howto programmers-guide apiref - python-apiref nghttp2.h nghttp2ver.h Source diff --git a/doc/sources/python-apiref.rst b/doc/sources/python-apiref.rst deleted file mode 100644 index 81f083ad..00000000 --- a/doc/sources/python-apiref.rst +++ /dev/null @@ -1,444 +0,0 @@ -Python API Reference -==================== - -.. warning:: - - Python bindings have been deprecated due to maintenance issue. It - will not get any updates. It will be removed at the end of 2022. - -.. py:module:: nghttp2 - -nghttp2 offers some high level Python API to C library. The bindings -currently provide HPACK compressor and decompressor classes and HTTP/2 -server class. - -The extension module is called ``nghttp2``. - -``make`` will build the bindings. The target Python version is -determined by configure script. If the detected Python version is not -what you expect, specify a path to Python executable in ``PYTHON`` -variable as an argument to configure script (e.g., ``./configure -PYTHON=/usr/bin/python3.8``). - -HPACK API ---------- - -.. py:class:: HDDeflater(hd_table_bufsize_max=DEFLATE_MAX_HEADER_TABLE_SIZE) - - This class is used to perform header compression. The - *hd_table_bufsize_max* limits the usage of header table in the - given amount of bytes. The default value is - :py:data:`DEFLATE_MAX_HEADER_TABLE_SIZE`. This is necessary - because the deflater and inflater share the same amount of header - table and the inflater decides that number. The deflater may not - want to use all header table size because of limited memory - availability. In that case, *hd_table_bufsize_max* can be used to - cap the upper limit of table size whatever the header table size is - chosen by the inflater. - - .. py:method:: deflate(headers) - - Deflates the *headers*. The *headers* must be sequence of tuple - of name/value pair, which are byte strings (not unicode string). - - This method returns the deflated header block in byte string. - Raises the exception if any error occurs. - - .. py:method:: set_no_refset(no_refset) - - Tells the deflater not to use reference set if *no_refset* is - evaluated to ``True``. If that happens, on each subsequent - invocation of :py:meth:`deflate()`, deflater will clear up - refersent set. - - .. py:method:: change_table_size(hd_table_bufsize_max) - - Changes header table size to *hd_table_bufsize_max* byte. if - *hd_table_bufsize_max* is strictly larger than - ``hd_table_bufsize_max`` given in constructor, - ``hd_table_bufsize_max`` is used as header table size instead. - - Raises the exception if any error occurs. - - .. py:method:: get_hd_table() - - Returns copy of current dynamic header table. - -The following example shows how to deflate header name/value pairs: - -.. code-block:: python - - import binascii, nghttp2 - - deflater = nghttp2.HDDeflater() - - res = deflater.deflate([(b'foo', b'bar'), - (b'baz', b'buz')]) - - print(binascii.b2a_hex(res)) - - -.. py:class:: HDInflater() - - This class is used to perform header decompression. - - .. py:method:: inflate(data) - - Inflates the deflated header block *data*. The *data* must be - byte string. - - Raises the exception if any error occurs. - - .. py:method:: change_table_size(hd_table_bufsize_max) - - Changes header table size to *hd_table_bufsize_max* byte. - - Raises the exception if any error occurs. - - .. py:method:: get_hd_table() - - Returns copy of current dynamic header table. - -The following example shows how to inflate deflated header block: - -.. code-block:: python - - deflater = nghttp2.HDDeflater() - - data = deflater.deflate([(b'foo', b'bar'), - (b'baz', b'buz')]) - - inflater = nghttp2.HDInflater() - - hdrs = inflater.inflate(data) - - print(hdrs) - - -.. py:function:: print_hd_table(hdtable) - - Convenient function to print *hdtable* to the standard output. The - *hdtable* is the one retrieved by - :py:meth:`HDDeflater.get_hd_table()` or - :py:meth:`HDInflater.get_hd_table()`. This function does not work - if header name/value cannot be decoded using UTF-8 encoding. - - In output, ``s=N`` means the entry occupies ``N`` bytes in header - table. If ``r=y``, then the entry is in the reference set. - -.. py:data:: DEFAULT_HEADER_TABLE_SIZE - - The default header table size, which is 4096 as per HTTP/2 - specification. - -.. py:data:: DEFLATE_MAX_HEADER_TABLE_SIZE - - The default header table size for deflater. The initial value - is 4096. - -HTTP/2 servers --------------- - -.. note:: - - We use :py:mod:`asyncio` for HTTP/2 server classes, and ALPN. - Therefore, Python 3.8 or later is required to use these objects. - To explicitly configure nghttp2 build to use Python 3.8, specify - the ``PYTHON`` variable to the path to Python 3.8 executable when - invoking configure script like this: - - .. code-block:: text - - $ ./configure PYTHON=/usr/bin/python3.8 - -.. py:class:: HTTP2Server(address, RequestHandlerClass, ssl=None) - - This class builds on top of the :py:mod:`asyncio` event loop. On - construction, *RequestHandlerClass* must be given, which must be a - subclass of :py:class:`BaseRequestHandler` class. - - The *address* must be a tuple of hostname/IP address and port to - bind. If hostname/IP address is ``None``, all interfaces are - assumed. - - To enable SSL/TLS, specify instance of :py:class:`ssl.SSLContext` - in *ssl*. Before passing *ssl* to - :py:func:`BaseEventLoop.create_server`, ALPN protocol identifiers - are set using :py:meth:`ssl.SSLContext.set_npn_protocols`. - - To disable SSL/TLS, omit *ssl* or specify ``None``. - - .. py:method:: serve_forever() - - Runs server and processes incoming requests forever. - -.. py:class:: BaseRequestHandler(http2, stream_id) - - The class is used to handle the single HTTP/2 stream. By default, - it does not nothing. It must be subclassed to handle each event - callback method. - - The first callback method invoked is :py:meth:`on_headers()`. It is - called when HEADERS frame, which includes request header fields, is - arrived. - - If request has request body, :py:meth:`on_data()` is invoked for - each chunk of received data chunk. - - When whole request is received, :py:meth:`on_request_done()` is - invoked. - - When stream is closed, :py:meth:`on_close()` is called. - - The application can send response using :py:meth:`send_response()` - method. It can be used in :py:meth:`on_headers()`, - :py:meth:`on_data()` or :py:meth:`on_request_done()`. - - The application can push resource using :py:meth:`push()` method. - It must be used before :py:meth:`send_response()` call. - - A :py:class:`BaseRequestHandler` has the following instance - variables: - - .. py:attribute:: client_address - - Contains a tuple of the form ``(host, port)`` referring to the - client's address. - - .. py:attribute:: stream_id - - Stream ID of this stream - - .. py:attribute:: scheme - - Scheme of the request URI. This is a value of ``:scheme`` - header field. - - .. py:attribute:: method - - Method of this stream. This is a value of ``:method`` header - field. - - .. py:attribute:: host - - This is a value of ``:authority`` or ``host`` header field. - - .. py:attribute:: path - - This is a value of ``:path`` header field. - - .. py:attribute:: headers - - Request header fields. - - A :py:class:`BaseRequestHandler` has the following methods: - - .. py:method:: on_headers() - - Called when request HEADERS is arrived. By default, this method - does nothing. - - .. py:method:: on_data(data) - - Called when a chunk of request body *data* is arrived. This - method will be called multiple times until all data are - received. By default, this method does nothing. - - .. py:method:: on_request_done() - - Called when whole request was received. By default, this method - does nothing. - - .. py:method:: on_close(error_code) - - Called when stream is about to close. The *error_code* - indicates the reason of closure. If it is ``0``, the stream is - going to close without error. - - .. py:method:: send_response(status=200, headers=None, body=None) - - Send response. The *status* is HTTP status code. The *headers* - is additional response headers. The *:status* header field will - be appended by the library. The *body* is the response body. - It could be ``None`` if response body is empty. Or it must be - instance of either ``str``, ``bytes``, :py:class:`io.IOBase` or - callable, called body generator, which takes one parameter, - size. The body generator generates response body. It can pause - generation of response so that it can wait for slow backend data - generation. When invoked, it should return tuple, byte string - at most size length and flag. The flag is either - :py:data:`DATA_OK`, :py:data:`DATA_EOF` or - :py:data:`DATA_DEFERRED`. For non-empty byte string and it is - not the last chunk of response, :py:data:`DATA_OK` must be - returned as flag. If this is the last chunk of the response - (byte string could be ``None``), :py:data:`DATA_EOF` must be - returned as flag. If there is no data available right now, but - additional data are anticipated, return tuple (``None``, - :py:data:`DATA_DEFERRED`). When data arrived, call - :py:meth:`resume()` and restart response body transmission. - - Only the body generator can pause response body generation; - instance of :py:class:`io.IOBase` must not block. - - If instance of ``str`` is specified as *body*, it will be - encoded using UTF-8. - - The *headers* is a list of tuple of the form ``(name, - value)``. The ``name`` and ``value`` can be either byte string - or unicode string. In the latter case, they will be encoded - using UTF-8. - - Raises the exception if any error occurs. - - .. py:method:: push(path, method='GET', request_headers=None, status=200, headers=None, body=None) - - Push a specified resource. The *path* is a path portion of - request URI for this resource. The *method* is a method to - access this resource. The *request_headers* is additional - request headers to access this resource. The ``:scheme``, - ``:method``, ``:authority`` and ``:path`` are appended by the - library. The ``:scheme`` and ``:authority`` are inherited from - request header fields of the associated stream. - - The *status* is HTTP status code. The *headers* is additional - response headers. The ``:status`` header field is appended by - the library. The *body* is the response body. It has the same - semantics of *body* parameter of :py:meth:`send_response()`. - - The headers and request_headers are a list of tuple of the form - ``(name, value)``. The ``name`` and ``value`` can be either byte - string or unicode string. In the latter case, they will be - encoded using UTF-8. - - Returns an instance of ``RequestHandlerClass`` specified in - :py:class:`HTTP2Server` constructor for the pushed resource. - - Raises the exception if any error occurs. - - .. py:method:: resume() - - Signals the restarting of response body transmission paused by - ``DATA_DEFERRED`` from the body generator (see - :py:meth:`send_response()` about the body generator). It is not - an error calling this method while response body transmission is - not paused. - -.. py:data:: DATA_OK - - ``DATA_OK`` indicates non empty data is generated from body generator. - -.. py:data:: DATA_EOF - - ``DATA_EOF`` indicates the end of response body. - -.. py:data:: DATA_DEFERRED - - ``DATA_DEFERRED`` indicates that data are not available right now - and response should be paused. - -The following example illustrates :py:class:`HTTP2Server` and -:py:class:`BaseRequestHandler` usage: - -.. code-block:: python - - #!/usr/bin/env python3 - - import io, ssl - - import nghttp2 - - class Handler(nghttp2.BaseRequestHandler): - - def on_headers(self): - self.push(path='/css/style.css', - request_headers = [('content-type', 'text/css')], - status=200, - body='body{margin:0;}') - - self.send_response(status=200, - headers = [('content-type', 'text/plain')], - body=io.BytesIO(b'nghttp2-python FTW')) - - ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23) - ctx.options = ssl.OP_ALL | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 - ctx.load_cert_chain('server.crt', 'server.key') - - # give None to ssl to make the server non-SSL/TLS - server = nghttp2.HTTP2Server(('127.0.0.1', 8443), Handler, ssl=ctx) - server.serve_forever() - -The following example illustrates HTTP/2 server using asynchronous -response body generation. This is simplified reverse proxy: - -.. code-block:: python - - #!/usr/bin/env python3 - - import ssl - import os - import urllib - import asyncio - import io - - import nghttp2 - - @asyncio.coroutine - def get_http_header(handler, url): - url = urllib.parse.urlsplit(url) - ssl = url.scheme == 'https' - if url.port == None: - if url.scheme == 'https': - port = 443 - else: - port = 80 - else: - port = url.port - - connect = asyncio.open_connection(url.hostname, port, ssl=ssl) - reader, writer = yield from connect - req = 'GET {path} HTTP/1.0\r\n\r\n'.format(path=url.path or '/') - writer.write(req.encode('utf-8')) - # skip response header fields - while True: - line = yield from reader.readline() - line = line.rstrip() - if not line: - break - # read body - while True: - b = yield from reader.read(4096) - if not b: - break - handler.buf.write(b) - writer.close() - handler.buf.seek(0) - handler.eof = True - handler.resume() - - class Body: - def __init__(self, handler): - self.handler = handler - self.handler.eof = False - self.handler.buf = io.BytesIO() - - def generate(self, n): - buf = self.handler.buf - data = buf.read1(n) - if not data and not self.handler.eof: - return None, nghttp2.DATA_DEFERRED - return data, nghttp2.DATA_EOF if self.handler.eof else nghttp2.DATA_OK - - class Handler(nghttp2.BaseRequestHandler): - - def on_headers(self): - body = Body(self) - asyncio.async(get_http_header( - self, 'http://localhost' + self.path.decode('utf-8'))) - self.send_response(status=200, body=body.generate) - - ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23) - ctx.options = ssl.OP_ALL | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 - ctx.load_cert_chain('server.crt', 'server.key') - - server = nghttp2.HTTP2Server(('127.0.0.1', 8443), Handler, ssl=ctx) - server.serve_forever() diff --git a/docker/Dockerfile b/docker/Dockerfile index 18331c5d..bf28ccb0 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -47,7 +47,7 @@ RUN git clone --depth 1 https://github.com/nghttp2/nghttp2.git && \ git submodule update --init && \ autoreconf -i && \ ./configure --disable-examples --disable-hpack-tools \ - --disable-python-bindings --with-mruby --with-neverbleed \ + --with-mruby --with-neverbleed \ --enable-http3 --with-libbpf \ CC=clang CXX=clang++ \ LIBTOOL_LDFLAGS="-static-libtool-libs" \ diff --git a/lib/Makefile.msvc b/lib/Makefile.msvc index f649c0bd..611b39d0 100644 --- a/lib/Makefile.msvc +++ b/lib/Makefile.msvc @@ -6,15 +6,8 @@ # The MIT License apply. # -# -# Choose your weapons: -# Set 'USE_CYTHON=1' to build and install the 'nghttp2.pyd' Python extension. -# THIS_MAKEFILE := $(lastword $(MAKEFILE_LIST)) -USE_CYTHON := 0 -#USE_CYTHON := 1 - _VERSION := $(shell grep AC_INIT ../configure.ac | cut -d'[' -f3 | sed -e 's/-DEV//g' -e 's/], //g') _VERSION := $(subst ., ,$(_VERSION)) VER_MAJOR := $(word 1,$(_VERSION)) @@ -102,7 +95,7 @@ NGHTTP2_OBJ_D := $(addprefix $(OBJ_DIR)/d_, $(notdir $(NGHTTP2_SRC:.c=.obj))) clean_nghttp2_pyd_0 clean_nghttp2_pyd_1 -all: intro includes/nghttp2/nghttp2ver.h $(OBJ_DIR) $(TARGETS) build_nghttp2_pyd_$(USE_CYTHON) +all: intro includes/nghttp2/nghttp2ver.h $(OBJ_DIR) $(TARGETS) @echo 'Welcome to NgHTTP2 (release + debug).' @echo 'Do a "make -f Makefile.MSVC install" at own risk!' @@ -121,7 +114,7 @@ $(OBJ_DIR): install: includes/nghttp2/nghttp2.h includes/nghttp2/nghttp2ver.h \ $(TARGETS) \ - copy_headers_and_libs install_nghttp2_pyd_$(USE_CYTHON) + copy_headers_and_libs # # This MUST be done before using the 'install_nghttp2_pyd_1' rule. @@ -160,31 +153,6 @@ $(DLL_D): $(NGHTTP2_OBJ_D) $(OBJ_DIR)/d_nghttp2.res WIN_OBJDIR:=$(shell cygpath -w $(abspath $(OBJ_DIR))) WIN_OBJDIR:=$(subst \,/,$(WIN_OBJDIR)) -../python/setup.py: ../python/setup.py.in $(THIS_MAKEFILE) - cd ../python ; \ - echo '# $(GENERATED). DO NOT EDIT.' > setup.py ; \ - sed -e 's/@top_srcdir@/../' \ - -e 's%@top_builddir@%$(WIN_OBJDIR)%' \ - -e 's/@PACKAGE_VERSION@/$(VERSION)/' setup.py.in >> setup.py ; - -build_nghttp2_pyd_0: ; - -build_nghttp2_pyd_1: $(addprefix ../python/, setup.py nghttp2.pyx) - cd ../python ; \ - python setup.py build_ext -i -f bdist_wininst - -install_nghttp2_pyd_0: ; - -install_nghttp2_pyd_1: $(addprefix ../python/, setup.py nghttp2.pyx) - cd ../python ; \ - pip install . - -clean_nghttp2_pyd_0: ; - -clean_nghttp2_pyd_1: - cd ../python ; \ - rm -fR build dist - $(OBJ_DIR)/r_%.obj: %.c $(THIS_MAKEFILE) $(CC) $(CFLAGS_R) $(CFLAGS) -Fo$@ -c $< @echo @@ -262,7 +230,7 @@ clean: rm -f $(OBJ_DIR)/* includes/nghttp2/nghttp2ver.h @echo -vclean realclean: clean clean_nghttp2_pyd_$(USE_CYTHON) +vclean realclean: clean - rm -rf $(OBJ_DIR) - rm -f .depend.MSVC diff --git a/m4/ax_python_devel.m4 b/m4/ax_python_devel.m4 deleted file mode 100644 index 9d4eecf7..00000000 --- a/m4/ax_python_devel.m4 +++ /dev/null @@ -1,368 +0,0 @@ -# =========================================================================== -# https://www.gnu.org/software/autoconf-archive/ax_python_devel.html -# =========================================================================== -# -# SYNOPSIS -# -# AX_PYTHON_DEVEL([version]) -# -# DESCRIPTION -# -# Note: Defines as a precious variable "PYTHON_VERSION". Don't override it -# in your configure.ac. -# -# This macro checks for Python and tries to get the include path to -# 'Python.h'. It provides the $(PYTHON_CPPFLAGS) and $(PYTHON_LIBS) output -# variables. It also exports $(PYTHON_EXTRA_LIBS) and -# $(PYTHON_EXTRA_LDFLAGS) for embedding Python in your code. -# -# You can search for some particular version of Python by passing a -# parameter to this macro, for example ">= '2.3.1'", or "== '2.4'". Please -# note that you *have* to pass also an operator along with the version to -# match, and pay special attention to the single quotes surrounding the -# version number. Don't use "PYTHON_VERSION" for this: that environment -# variable is declared as precious and thus reserved for the end-user. -# -# This macro should work for all versions of Python >= 2.1.0. As an end -# user, you can disable the check for the python version by setting the -# PYTHON_NOVERSIONCHECK environment variable to something else than the -# empty string. -# -# If you need to use this macro for an older Python version, please -# contact the authors. We're always open for feedback. -# -# LICENSE -# -# Copyright (c) 2009 Sebastian Huber -# Copyright (c) 2009 Alan W. Irwin -# Copyright (c) 2009 Rafael Laboissiere -# Copyright (c) 2009 Andrew Collier -# Copyright (c) 2009 Matteo Settenvini -# Copyright (c) 2009 Horst Knorr -# Copyright (c) 2013 Daniel Mullner -# -# This program is free software: you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the -# Free Software Foundation, either version 3 of the License, or (at your -# option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General -# Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program. If not, see . -# -# As a special exception, the respective Autoconf Macro's copyright owner -# gives unlimited permission to copy, distribute and modify the configure -# scripts that are the output of Autoconf when processing the Macro. You -# need not follow the terms of the GNU General Public License when using -# or distributing such scripts, even though portions of the text of the -# Macro appear in them. The GNU General Public License (GPL) does govern -# all other use of the material that constitutes the Autoconf Macro. -# -# This special exception to the GPL applies to versions of the Autoconf -# Macro released by the Autoconf Archive. When you make and distribute a -# modified version of the Autoconf Macro, you may extend this special -# exception to the GPL to apply to your modified version as well. - -#serial 23 - -AU_ALIAS([AC_PYTHON_DEVEL], [AX_PYTHON_DEVEL]) -AC_DEFUN([AX_PYTHON_DEVEL],[ - # - # Allow the use of a (user set) custom python version - # - AC_ARG_VAR([PYTHON_VERSION],[The installed Python - version to use, for example '2.3'. This string - will be appended to the Python interpreter - canonical name.]) - - AC_PATH_PROG([PYTHON],[python[$PYTHON_VERSION]]) - if test -z "$PYTHON"; then - AC_MSG_ERROR([Cannot find python$PYTHON_VERSION in your system path]) - PYTHON_VERSION="" - fi - - # - # Check for a version of Python >= 2.1.0 - # - AC_MSG_CHECKING([for a version of Python >= '2.1.0']) - ac_supports_python_ver=`$PYTHON -c "import sys; \ - ver = sys.version.split ()[[0]]; \ - print (ver >= '2.1.0')"` - if test "$ac_supports_python_ver" != "True"; then - if test -z "$PYTHON_NOVERSIONCHECK"; then - AC_MSG_RESULT([no]) - AC_MSG_FAILURE([ -This version of the AC@&t@_PYTHON_DEVEL macro -doesn't work properly with versions of Python before -2.1.0. You may need to re-run configure, setting the -variables PYTHON_CPPFLAGS, PYTHON_LIBS, PYTHON_SITE_PKG, -PYTHON_EXTRA_LIBS and PYTHON_EXTRA_LDFLAGS by hand. -Moreover, to disable this check, set PYTHON_NOVERSIONCHECK -to something else than an empty string. -]) - else - AC_MSG_RESULT([skip at user request]) - fi - else - AC_MSG_RESULT([yes]) - fi - - # - # if the macro parameter ``version'' is set, honour it - # - if test -n "$1"; then - AC_MSG_CHECKING([for a version of Python $1]) - ac_supports_python_ver=`$PYTHON -c "import sys; \ - ver = sys.version.split ()[[0]]; \ - print (ver $1)"` - if test "$ac_supports_python_ver" = "True"; then - AC_MSG_RESULT([yes]) - else - AC_MSG_RESULT([no]) - AC_MSG_ERROR([this package requires Python $1. -If you have it installed, but it isn't the default Python -interpreter in your system path, please pass the PYTHON_VERSION -variable to configure. See ``configure --help'' for reference. -]) - PYTHON_VERSION="" - fi - fi - - # - # Check if you have distutils, else fail - # - AC_MSG_CHECKING([for the sysconfig Python package]) - ac_sysconfig_result=`$PYTHON -c "import sysconfig" 2>&1` - if test $? -eq 0; then - AC_MSG_RESULT([yes]) - IMPORT_SYSCONFIG="import sysconfig" - else - AC_MSG_RESULT([no]) - - AC_MSG_CHECKING([for the distutils Python package]) - ac_sysconfig_result=`$PYTHON -c "from distutils import sysconfig" 2>&1` - if test $? -eq 0; then - AC_MSG_RESULT([yes]) - IMPORT_SYSCONFIG="from distutils import sysconfig" - else - AC_MSG_ERROR([cannot import Python module "distutils". -Please check your Python installation. The error was: -$ac_sysconfig_result]) - PYTHON_VERSION="" - fi - fi - - # - # Check for Python include path - # - AC_MSG_CHECKING([for Python include path]) - if test -z "$PYTHON_CPPFLAGS"; then - if test "$IMPORT_SYSCONFIG" = "import sysconfig"; then - # sysconfig module has different functions - python_path=`$PYTHON -c "$IMPORT_SYSCONFIG; \ - print (sysconfig.get_path ('include'));"` - plat_python_path=`$PYTHON -c "$IMPORT_SYSCONFIG; \ - print (sysconfig.get_path ('platinclude'));"` - else - # old distutils way - python_path=`$PYTHON -c "$IMPORT_SYSCONFIG; \ - print (sysconfig.get_python_inc ());"` - plat_python_path=`$PYTHON -c "$IMPORT_SYSCONFIG; \ - print (sysconfig.get_python_inc (plat_specific=1));"` - fi - if test -n "${python_path}"; then - if test "${plat_python_path}" != "${python_path}"; then - python_path="-I$python_path -I$plat_python_path" - else - python_path="-I$python_path" - fi - fi - PYTHON_CPPFLAGS=$python_path - fi - AC_MSG_RESULT([$PYTHON_CPPFLAGS]) - AC_SUBST([PYTHON_CPPFLAGS]) - - # - # Check for Python library path - # - AC_MSG_CHECKING([for Python library path]) - if test -z "$PYTHON_LIBS"; then - # (makes two attempts to ensure we've got a version number - # from the interpreter) - ac_python_version=`cat<]], - [[Py_Initialize();]]) - ],[pythonexists=yes],[pythonexists=no]) - AC_LANG_POP([C]) - # turn back to default flags - CPPFLAGS="$ac_save_CPPFLAGS" - LIBS="$ac_save_LIBS" - LDFLAGS="$ac_save_LDFLAGS" - - AC_MSG_RESULT([$pythonexists]) - - if test ! "x$pythonexists" = "xyes"; then - AC_MSG_FAILURE([ - Could not link test program to Python. Maybe the main Python library has been - installed in some non-standard library path. If so, pass it to configure, - via the LIBS environment variable. - Example: ./configure LIBS="-L/usr/non-standard-path/python/lib" - ============================================================================ - ERROR! - You probably have to install the development version of the Python package - for your distribution. The exact name of this package varies among them. - ============================================================================ - ]) - PYTHON_VERSION="" - fi - - # - # all done! - # -]) diff --git a/python/.gitignore b/python/.gitignore deleted file mode 100644 index 9ebb0fbe..00000000 --- a/python/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# generated files -MANIFEST -dist/ -setup.py diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt deleted file mode 100644 index 8bdfbb3d..00000000 --- a/python/CMakeLists.txt +++ /dev/null @@ -1,39 +0,0 @@ -# EXTRA_DIST = cnghttp2.pxd nghttp2.pyx - -if(ENABLE_PYTHON_BINDINGS) - add_custom_target(python ALL - COMMAND "${PYTHON_EXECUTABLE}" setup.py build - VERBATIM - DEPENDS nghttp2.c nghttp2 - ) - - configure_file(install-python.cmake.in install-python.cmake ESCAPE_QUOTES @ONLY) - install(SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/install-python.cmake") - - add_custom_command(OUTPUT nghttp2.c - COMMAND "${CYTHON_EXECUTABLE}" -o nghttp2.c - "${CMAKE_CURRENT_SOURCE_DIR}/nghttp2.pyx" - VERBATIM - DEPENDS nghttp2.pyx - ) - - # Instead of calling "setup.py clean --all", this should do... - set_directory_properties(PROPERTIES - ADDITIONAL_MAKE_CLEAN_FILES "build;python_nghttp2.egg-info" - ) - -## This works also, except that the installation target is missing... -# include(UseCython) -# cython_add_module(python_nghttp2 nghttp2.pyx) -# set_target_properties(python_nghttp2 PROPERTIES -# OUTPUT_NAME nghttp2 -# ) -# target_include_directories(python_nghttp2 PRIVATE -# "${CMAKE_SOURCE_DIR}/lib" -# "${CMAKE_SOURCE_DIR}/lib/includes" -# "${CMAKE_BINARY_DIR}/lib/includes" -# ) -# target_link_libraries(python_nghttp2 -# nghttp2 -# ) -endif() diff --git a/python/Makefile.am b/python/Makefile.am deleted file mode 100644 index 75eb7a05..00000000 --- a/python/Makefile.am +++ /dev/null @@ -1,49 +0,0 @@ -# nghttp2 - HTTP/2 C Library - -# Copyright (c) 2013 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. - -# This will avoid that setup.py gets deleted before it is executed in -# clean-local in parallel build. -.NOTPARALLEL: - -EXTRA_DIST = cnghttp2.pxd nghttp2.pyx CMakeLists.txt install-python.cmake.in - -if ENABLE_PYTHON_BINDINGS - -all-local: nghttp2.c - $(PYTHON) setup.py build - -install-exec-local: - $(PYTHON) setup.py install --prefix=$(DESTDIR)$(prefix) - -uninstall-local: - rm -f $(DESTDIR)$(libdir)/python*/site-packages/nghttp2.so - rm -f $(DESTDIR)$(libdir)/python*/site-packages/python_nghttp2-*.egg - -clean-local: - $(PYTHON) setup.py clean --all - -rm -f $(builddir)/nghttp2.c - -.pyx.c: - $(CYTHON) -3 -o $@ $< - -endif # ENABLE_PYTHON_BINDINGS diff --git a/python/calcratio.py b/python/calcratio.py deleted file mode 100755 index 0c52a16f..00000000 --- a/python/calcratio.py +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env python -# -# This script takes directories which contain the hpack-test-case json -# files, and calculates the compression ratio in each file and outputs -# the result in table formatted in rst. -# -# The each directory contains the result of various HPACK compressor. -# -# The table is laid out so that we can see that how input header set -# in one json file is compressed in each compressor. -# -# For hpack-test-case, see https://github.com/Jxck/hpack-test-case -# -import sys, json, os, re - -class Stat: - - def __init__(self, complen, srclen): - self.complen = complen - self.srclen = srclen - -def compute_stat(jsdata): - complen = 0 - srclen = 0 - for item in jsdata['cases']: - complen += len(item['wire']) // 2 - srclen += \ - sum([len(list(x.keys())[0]) + len(list(x.values())[0]) \ - for x in item['headers']]) - return Stat(complen, srclen) - -def format_result(r): - return '{:.02f} ({}/{}) '.format(r.complen/r.srclen, r.complen, r.srclen) - -if __name__ == '__main__': - entries = [(os.path.basename(re.sub(r'/+$', '', p)), p) \ - for p in sys.argv[1:]] - maxnamelen = 0 - maxstorynamelen = 0 - res = {} - - stories = set() - for name, ent in entries: - files = [p for p in os.listdir(ent) if p.endswith('.json')] - res[name] = {} - maxnamelen = max(maxnamelen, len(name)) - for fn in files: - stories.add(fn) - maxstorynamelen = max(maxstorynamelen, len(fn)) - with open(os.path.join(ent, fn)) as f: - input = f.read() - rv = compute_stat(json.loads(input)) - res[name][fn] = rv - maxnamelen = max(maxnamelen, len(format_result(rv))) - stories = list(stories) - stories.sort() - - storynameformat = '{{:{}}} '.format(maxstorynamelen) - nameformat = '{{:{}}} '.format(maxnamelen) - - - sys.stdout.write('''\ -hpack-test-case compression ratio -================================= - -The each cell has ``X (Y/Z)`` format: - -X - Y / Z -Y - number of bytes after compression -Z - number of bytes before compression - -''') - - def write_border(): - sys.stdout.write('='*maxstorynamelen) - sys.stdout.write(' ') - for _ in entries: - sys.stdout.write('='*maxnamelen) - sys.stdout.write(' ') - sys.stdout.write('\n') - - write_border() - - sys.stdout.write(storynameformat.format('story')) - for name, _ in entries: - sys.stdout.write(nameformat.format(name)) - sys.stdout.write('\n') - - write_border() - - for story in stories: - sys.stdout.write(storynameformat.format(story)) - srclen = -1 - for name, _ in entries: - stats = res[name] - if story not in stats: - sys.stdout.write(nameformat.format('N/A')) - continue - if srclen == -1: - srclen = stats[story].srclen - elif srclen != stats[story].srclen: - raise Exception('Bad srclen') - sys.stdout.write(nameformat.format(format_result(stats[story]))) - sys.stdout.write('\n') - - write_border() diff --git a/python/cnghttp2.pxd b/python/cnghttp2.pxd deleted file mode 100644 index 0d25fdb3..00000000 --- a/python/cnghttp2.pxd +++ /dev/null @@ -1,340 +0,0 @@ -# nghttp2 - HTTP/2 C Library - -# Copyright (c) 2013 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. -from libc.stdint cimport uint8_t, uint16_t, uint32_t, int32_t - -cdef extern from 'nghttp2/nghttp2.h': - - const char NGHTTP2_PROTO_VERSION_ID[] - const char NGHTTP2_CLIENT_CONNECTION_PREFACE[] - const size_t NGHTTP2_INITIAL_WINDOW_SIZE - const size_t NGHTTP2_DEFAULT_HEADER_TABLE_SIZE - - ctypedef struct nghttp2_session: - pass - - ctypedef enum nghttp2_error: - NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE - NGHTTP2_ERR_DEFERRED - - ctypedef enum nghttp2_flag: - NGHTTP2_FLAG_NONE - NGHTTP2_FLAG_END_STREAM - NGHTTP2_FLAG_ACK - - ctypedef enum nghttp2_error_code: - NGHTTP2_NO_ERROR - NGHTTP2_PROTOCOL_ERROR - NGHTTP2_INTERNAL_ERROR - NGHTTP2_SETTINGS_TIMEOUT - - ctypedef enum nghttp2_frame_type: - NGHTTP2_DATA - NGHTTP2_HEADERS - NGHTTP2_RST_STREAM - NGHTTP2_SETTINGS - NGHTTP2_PUSH_PROMISE - NGHTTP2_GOAWAY - - ctypedef enum nghttp2_nv_flag: - NGHTTP2_NV_FLAG_NONE - NGHTTP2_NV_FLAG_NO_INDEX - - ctypedef struct nghttp2_nv: - uint8_t *name - uint8_t *value - uint16_t namelen - uint16_t valuelen - uint8_t flags - - ctypedef enum nghttp2_settings_id: - SETTINGS_HEADER_TABLE_SIZE - NGHTTP2_SETTINGS_HEADER_TABLE_SIZE - NGHTTP2_SETTINGS_ENABLE_PUSH - NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS - NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE - - ctypedef struct nghttp2_settings_entry: - int32_t settings_id - uint32_t value - - ctypedef struct nghttp2_frame_hd: - size_t length - int32_t stream_id - uint8_t type - uint8_t flags - - ctypedef struct nghttp2_data: - nghttp2_frame_hd hd - size_t padlen - - ctypedef enum nghttp2_headers_category: - NGHTTP2_HCAT_REQUEST - NGHTTP2_HCAT_RESPONSE - NGHTTP2_HCAT_PUSH_RESPONSE - NGHTTP2_HCAT_HEADERS - - ctypedef struct nghttp2_headers: - nghttp2_frame_hd hd - size_t padlen - nghttp2_nv *nva - size_t nvlen - nghttp2_headers_category cat - int32_t pri - - ctypedef struct nghttp2_rst_stream: - nghttp2_frame_hd hd - uint32_t error_code - - - ctypedef struct nghttp2_push_promise: - nghttp2_frame_hd hd - nghttp2_nv *nva - size_t nvlen - int32_t promised_stream_id - - ctypedef struct nghttp2_goaway: - nghttp2_frame_hd hd - int32_t last_stream_id - uint32_t error_code - uint8_t *opaque_data - size_t opaque_data_len - - ctypedef union nghttp2_frame: - nghttp2_frame_hd hd - nghttp2_data data - nghttp2_headers headers - nghttp2_rst_stream rst_stream - nghttp2_push_promise push_promise - nghttp2_goaway goaway - - ctypedef ssize_t (*nghttp2_send_callback)\ - (nghttp2_session *session, const uint8_t *data, size_t length, - int flags, void *user_data) - - ctypedef int (*nghttp2_on_frame_recv_callback)\ - (nghttp2_session *session, const nghttp2_frame *frame, void *user_data) - - ctypedef int (*nghttp2_on_data_chunk_recv_callback)\ - (nghttp2_session *session, uint8_t flags, int32_t stream_id, - const uint8_t *data, size_t length, void *user_data) - - ctypedef int (*nghttp2_before_frame_send_callback)\ - (nghttp2_session *session, const nghttp2_frame *frame, void *user_data) - - ctypedef int (*nghttp2_on_stream_close_callback)\ - (nghttp2_session *session, int32_t stream_id, - uint32_t error_code, void *user_data) - - ctypedef int (*nghttp2_on_begin_headers_callback)\ - (nghttp2_session *session, const nghttp2_frame *frame, void *user_data) - - ctypedef int (*nghttp2_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) - - ctypedef int (*nghttp2_on_frame_send_callback)\ - (nghttp2_session *session, const nghttp2_frame *frame, void *user_data) - - ctypedef int (*nghttp2_on_frame_not_send_callback)\ - (nghttp2_session *session, const nghttp2_frame *frame, - int lib_error_code, void *user_data) - - ctypedef struct nghttp2_session_callbacks: - pass - - int nghttp2_session_callbacks_new( - nghttp2_session_callbacks **callbacks_ptr) - - void nghttp2_session_callbacks_del(nghttp2_session_callbacks *callbacks) - - void nghttp2_session_callbacks_set_send_callback( - nghttp2_session_callbacks *cbs, nghttp2_send_callback send_callback) - - void nghttp2_session_callbacks_set_on_frame_recv_callback( - nghttp2_session_callbacks *cbs, - nghttp2_on_frame_recv_callback on_frame_recv_callback) - - void nghttp2_session_callbacks_set_on_data_chunk_recv_callback( - nghttp2_session_callbacks *cbs, - nghttp2_on_data_chunk_recv_callback on_data_chunk_recv_callback) - - void nghttp2_session_callbacks_set_before_frame_send_callback( - nghttp2_session_callbacks *cbs, - nghttp2_before_frame_send_callback before_frame_send_callback) - - void nghttp2_session_callbacks_set_on_frame_send_callback( - nghttp2_session_callbacks *cbs, - nghttp2_on_frame_send_callback on_frame_send_callback) - - void nghttp2_session_callbacks_set_on_frame_not_send_callback( - nghttp2_session_callbacks *cbs, - nghttp2_on_frame_not_send_callback on_frame_not_send_callback) - - void nghttp2_session_callbacks_set_on_stream_close_callback( - nghttp2_session_callbacks *cbs, - nghttp2_on_stream_close_callback on_stream_close_callback) - - void nghttp2_session_callbacks_set_on_begin_headers_callback( - nghttp2_session_callbacks *cbs, - nghttp2_on_begin_headers_callback on_begin_headers_callback) - - void nghttp2_session_callbacks_set_on_header_callback( - nghttp2_session_callbacks *cbs, - nghttp2_on_header_callback on_header_callback) - - int nghttp2_session_client_new(nghttp2_session **session_ptr, - const nghttp2_session_callbacks *callbacks, - void *user_data) - - int nghttp2_session_server_new(nghttp2_session **session_ptr, - const nghttp2_session_callbacks *callbacks, - void *user_data) - - void nghttp2_session_del(nghttp2_session *session) - - - ssize_t nghttp2_session_mem_recv(nghttp2_session *session, - const uint8_t *data, size_t datalen) - - ssize_t nghttp2_session_mem_send(nghttp2_session *session, - const uint8_t **data_ptr) - - int nghttp2_session_send(nghttp2_session *session) - - int nghttp2_session_want_read(nghttp2_session *session) - - int nghttp2_session_want_write(nghttp2_session *session) - - ctypedef union nghttp2_data_source: - int fd - void *ptr - - ctypedef enum nghttp2_data_flag: - NGHTTP2_DATA_FLAG_NONE - NGHTTP2_DATA_FLAG_EOF - - ctypedef ssize_t (*nghttp2_data_source_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) - - ctypedef struct nghttp2_data_provider: - nghttp2_data_source source - nghttp2_data_source_read_callback read_callback - - ctypedef struct nghttp2_priority_spec: - int32_t stream_id - int32_t weight - uint8_t exclusive - - int nghttp2_submit_request(nghttp2_session *session, const nghttp2_priority_spec *pri_spec, - const nghttp2_nv *nva, size_t nvlen, - const nghttp2_data_provider *data_prd, - void *stream_user_data) - - int nghttp2_submit_response(nghttp2_session *session, - int32_t stream_id, - const nghttp2_nv *nva, size_t nvlen, - const nghttp2_data_provider *data_prd) - - int nghttp2_submit_push_promise(nghttp2_session *session, uint8_t flags, - int32_t stream_id, - const nghttp2_nv *nva, size_t nvlen, - void *stream_user_data) - - int nghttp2_submit_settings(nghttp2_session *session, uint8_t flags, - const nghttp2_settings_entry *iv, size_t niv) - - int nghttp2_submit_rst_stream(nghttp2_session *session, uint8_t flags, - int32_t stream_id, - uint32_t error_code) - - void* nghttp2_session_get_stream_user_data(nghttp2_session *session, - uint32_t stream_id) - - int nghttp2_session_set_stream_user_data(nghttp2_session *session, - uint32_t stream_id, - void *stream_user_data) - - int nghttp2_session_terminate_session(nghttp2_session *session, - uint32_t error_code) - - int nghttp2_session_resume_data(nghttp2_session *session, - int32_t stream_id) - - const char* nghttp2_strerror(int lib_error_code) - - int nghttp2_session_check_server_session(nghttp2_session *session) - - int nghttp2_session_get_stream_remote_close(nghttp2_session *session, int32_t stream_id) - - int nghttp2_hd_deflate_new(nghttp2_hd_deflater **deflater_ptr, - size_t deflate_hd_table_bufsize_max) - - void nghttp2_hd_deflate_del(nghttp2_hd_deflater *deflater) - - int nghttp2_hd_deflate_change_table_size(nghttp2_hd_deflater *deflater, - size_t hd_table_bufsize_max) - - ssize_t nghttp2_hd_deflate_hd(nghttp2_hd_deflater *deflater, - uint8_t *buf, size_t buflen, - const nghttp2_nv *nva, size_t nvlen) - - size_t nghttp2_hd_deflate_bound(nghttp2_hd_deflater *deflater, - const nghttp2_nv *nva, size_t nvlen) - - int nghttp2_hd_inflate_new(nghttp2_hd_inflater **inflater_ptr) - - void nghttp2_hd_inflate_del(nghttp2_hd_inflater *inflater) - - int nghttp2_hd_inflate_change_table_size(nghttp2_hd_inflater *inflater, - size_t hd_table_bufsize_max) - - ssize_t nghttp2_hd_inflate_hd2(nghttp2_hd_inflater *inflater, - nghttp2_nv *nv_out, int *inflate_flags, - const uint8_t *input, size_t inlen, - int in_final) - - int nghttp2_hd_inflate_end_headers(nghttp2_hd_inflater *inflater) - - ctypedef enum nghttp2_hd_inflate_flag: - NGHTTP2_HD_INFLATE_EMIT - NGHTTP2_HD_INFLATE_FINAL - - ctypedef struct nghttp2_hd_deflater: - pass - - ctypedef struct nghttp2_hd_inflater: - pass - - size_t nghttp2_hd_deflate_get_num_table_entries(nghttp2_hd_deflater *deflater) - - const nghttp2_nv * nghttp2_hd_deflate_get_table_entry(nghttp2_hd_deflater *deflater, size_t idx) - - size_t nghttp2_hd_inflate_get_num_table_entries(nghttp2_hd_inflater *inflater) - - const nghttp2_nv *nghttp2_hd_inflate_get_table_entry(nghttp2_hd_inflater *inflater, size_t idx) diff --git a/python/hpackcheck.py b/python/hpackcheck.py deleted file mode 100755 index 41a8322e..00000000 --- a/python/hpackcheck.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env python3 -# -# This script reads json files given in the command-line (each file -# must be written in the format described in -# https://github.com/Jxck/hpack-test-case). And then it decompresses -# the sequence of encoded header blocks (which is the value of 'wire' -# key) and checks that decompressed header set is equal to the input -# header set (which is the value of 'headers' key). If there is -# mismatch, exception will be raised. -# -import sys, json -from binascii import a2b_hex -import nghttp2 - -def testsuite(testdata): - inflater = nghttp2.HDInflater() - - for casenum, item in enumerate(testdata['cases']): - if 'header_table_size' in item: - hd_table_size = int(item['header_table_size']) - inflater.change_table_size(hd_table_size) - compressed = a2b_hex(item['wire']) - # sys.stderr.write('#{} WIRE:\n{}\n'.format(casenum+1, item['wire'])) - # TODO decompressed headers are not necessarily UTF-8 strings - hdrs = [(k.decode('utf-8'), v.decode('utf-8')) \ - for k, v in inflater.inflate(compressed)] - - expected_hdrs = [(list(x.keys())[0], - list(x.values())[0]) for x in item['headers']] - if hdrs != expected_hdrs: - if 'seqno' in item: - seqno = item['seqno'] - else: - seqno = casenum - - sys.stderr.write('FAIL seqno#{}\n'.format(seqno)) - sys.stderr.write('expected:\n') - for k, v in expected_hdrs: - sys.stderr.write('{}: {}\n'.format(k, v)) - sys.stderr.write(', but got:\n') - for k, v in hdrs: - sys.stderr.write('{}: {}\n'.format(k, v)) - raise Exception('test failure') - sys.stderr.write('PASS\n') - -if __name__ == '__main__': - for filename in sys.argv[1:]: - sys.stderr.write('{}: '.format(filename)) - with open(filename) as f: - input = f.read() - - testdata = json.loads(input) - - testsuite(json.loads(input)) diff --git a/python/hpackmake.py b/python/hpackmake.py deleted file mode 100755 index a96bbe58..00000000 --- a/python/hpackmake.py +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/env python3 -# -# This script reads input headers from json file given in the -# command-line (each file must be written in the format described in -# https://github.com/Jxck/hpack-test-case but we require only -# 'headers' data). Then it encodes input header set and write the -# encoded header block in the same format. The output files are -# created under 'out' directory in the current directory. It must -# exist, otherwise the script will fail. The output filename is the -# same as the input filename. -# -import sys, base64, json, os.path, os, argparse, errno -from binascii import b2a_hex -import nghttp2 - -def testsuite(testdata, filename, outdir, table_size, deflate_table_size, - simulate_table_size_change): - res = { - 'description': '''\ -Encoded by nghttp2. The basic encoding strategy is described in \ -http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html \ -We use huffman encoding only if it produces strictly shorter byte string than \ -original. We make some headers not indexing at all, but this does not always \ -result in less bits on the wire.''' - } - cases = [] - deflater = nghttp2.HDDeflater(deflate_table_size) - - if table_size != nghttp2.DEFAULT_HEADER_TABLE_SIZE: - deflater.change_table_size(table_size) - - num_item = len(testdata['cases']) - - change_points = {} - if simulate_table_size_change and num_item > 1: - change_points[num_item * 2 // 3] = table_size * 2 // 3 - change_points[num_item // 3] = table_size // 3 - - for casenum, item in enumerate(testdata['cases']): - outitem = { - 'seqno': casenum, - 'headers': item['headers'] - } - - if casenum in change_points: - new_table_size = change_points[casenum] - deflater.change_table_size(new_table_size) - outitem['header_table_size'] = new_table_size - - casenum += 1 - hdrs = [(list(x.keys())[0].encode('utf-8'), - list(x.values())[0].encode('utf-8')) \ - for x in item['headers']] - outitem['wire'] = b2a_hex(deflater.deflate(hdrs)).decode('utf-8') - cases.append(outitem) - - if cases and table_size != nghttp2.DEFAULT_HEADER_TABLE_SIZE: - cases[0]['header_table_size'] = table_size - - res['cases'] = cases - jsonstr = json.dumps(res, indent=2) - with open(os.path.join(outdir, filename), 'w') as f: - f.write(jsonstr) - -if __name__ == '__main__': - ap = argparse.ArgumentParser(description='HPACK test case generator') - ap.add_argument('-d', '--dir', help='output directory', default='out') - ap.add_argument('-s', '--table-size', help='max header table size', - type=int, default=nghttp2.DEFAULT_HEADER_TABLE_SIZE) - ap.add_argument('-S', '--deflate-table-size', - help='max header table size for deflater', - type=int, default=nghttp2.DEFLATE_MAX_HEADER_TABLE_SIZE) - ap.add_argument('-c', '--simulate-table-size-change', - help='simulate table size change scenario', - action='store_true') - - ap.add_argument('file', nargs='*', help='input file') - args = ap.parse_args() - try: - os.mkdir(args.dir) - except OSError as e: - if e.errno != errno.EEXIST: - raise e - for filename in args.file: - sys.stderr.write('{}\n'.format(filename)) - with open(filename) as f: - input = f.read() - testsuite(json.loads(input), os.path.basename(filename), - args.dir, args.table_size, args.deflate_table_size, - args.simulate_table_size_change) diff --git a/python/install-python.cmake.in b/python/install-python.cmake.in deleted file mode 100644 index f3edbdb0..00000000 --- a/python/install-python.cmake.in +++ /dev/null @@ -1,10 +0,0 @@ -get_filename_component(rootdir "$ENV{DESTDIR}" ABSOLUTE) -if(rootdir STREQUAL "") - set(rootdir /) -endif() -execute_process( - COMMAND "@PYTHON_EXECUTABLE@" setup.py install - --skip-build - --root=${rootdir} --prefix=${CMAKE_INSTALL_PREFIX} - WORKING_DIRECTORY "@CMAKE_CURRENT_BINARY_DIR@" -) diff --git a/python/nghttp2.pyx b/python/nghttp2.pyx deleted file mode 100644 index 953a6481..00000000 --- a/python/nghttp2.pyx +++ /dev/null @@ -1,1655 +0,0 @@ -# nghttp2 - HTTP/2 C Library - -# Copyright (c) 2013 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. -cimport cnghttp2 - -from libc.stdlib cimport malloc, free -from libc.string cimport memcpy, memset -from libc.stdint cimport uint8_t, uint16_t, uint32_t, int32_t -import logging - - -DEFAULT_HEADER_TABLE_SIZE = cnghttp2.NGHTTP2_DEFAULT_HEADER_TABLE_SIZE -DEFLATE_MAX_HEADER_TABLE_SIZE = 4096 - -HD_ENTRY_OVERHEAD = 32 - -class HDTableEntry: - - def __init__(self, name, namelen, value, valuelen): - self.name = name - self.namelen = namelen - self.value = value - self.valuelen = valuelen - - def space(self): - return self.namelen + self.valuelen + HD_ENTRY_OVERHEAD - -cdef _get_pybytes(uint8_t *b, uint16_t blen): - return b[:blen] - -cdef class HDDeflater: - '''Performs header compression. The constructor takes - |hd_table_bufsize_max| parameter, which limits the usage of header - table in the given amount of bytes. This is necessary because the - header compressor and decompressor share the same amount of - header table and the decompressor decides that number. The - compressor may not want to use all header table size because of - limited memory availability. In that case, the - |hd_table_bufsize_max| can be used to cap the upper limit of table - size whatever the header table size is chosen by the decompressor. - The default value of |hd_table_bufsize_max| is 4096 bytes. - - The following example shows how to compress request header sets: - - import binascii, nghttp2 - - deflater = nghttp2.HDDeflater() - res = deflater.deflate([(b'foo', b'bar'), - (b'baz', b'buz')]) - print(binascii.b2a_hex(res)) - - ''' - - cdef cnghttp2.nghttp2_hd_deflater *_deflater - - def __cinit__(self, hd_table_bufsize_max = DEFLATE_MAX_HEADER_TABLE_SIZE): - rv = cnghttp2.nghttp2_hd_deflate_new(&self._deflater, - hd_table_bufsize_max) - if rv != 0: - raise Exception(_strerror(rv)) - - def __dealloc__(self): - cnghttp2.nghttp2_hd_deflate_del(self._deflater) - - def deflate(self, headers): - '''Compresses the |headers|. The |headers| must be sequence of tuple - of name/value pair, which are sequence of bytes (not unicode - string). - - This function returns the encoded header block in byte string. - An exception will be raised on error. - - ''' - cdef cnghttp2.nghttp2_nv *nva = \ - malloc(sizeof(cnghttp2.nghttp2_nv)*\ - len(headers)) - cdef cnghttp2.nghttp2_nv *nvap = nva - - for k, v in headers: - nvap[0].name = k - nvap[0].namelen = len(k) - nvap[0].value = v - nvap[0].valuelen = len(v) - nvap[0].flags = cnghttp2.NGHTTP2_NV_FLAG_NONE - nvap += 1 - - cdef size_t outcap = 0 - cdef ssize_t rv - cdef uint8_t *out - cdef size_t outlen - - outlen = cnghttp2.nghttp2_hd_deflate_bound(self._deflater, - nva, len(headers)) - - out = malloc(outlen) - - rv = cnghttp2.nghttp2_hd_deflate_hd(self._deflater, out, outlen, - nva, len(headers)) - free(nva) - - if rv < 0: - free(out) - - raise Exception(_strerror(rv)) - - cdef bytes res - - try: - res = out[:rv] - finally: - free(out) - - return res - - def change_table_size(self, hd_table_bufsize_max): - '''Changes header table size to |hd_table_bufsize_max| byte. - - An exception will be raised on error. - - ''' - cdef int rv - rv = cnghttp2.nghttp2_hd_deflate_change_table_size(self._deflater, - hd_table_bufsize_max) - if rv != 0: - raise Exception(_strerror(rv)) - - def get_hd_table(self): - '''Returns copy of current dynamic header table.''' - cdef size_t length = cnghttp2.nghttp2_hd_deflate_get_num_table_entries( - self._deflater) - cdef const cnghttp2.nghttp2_nv *nv - res = [] - for i in range(62, length + 1): - nv = cnghttp2.nghttp2_hd_deflate_get_table_entry(self._deflater, i) - k = _get_pybytes(nv.name, nv.namelen) - v = _get_pybytes(nv.value, nv.valuelen) - res.append(HDTableEntry(k, nv.namelen, v, nv.valuelen)) - return res - -cdef class HDInflater: - '''Performs header decompression. - - The following example shows how to compress request header sets: - - data = b'0082c5ad82bd0f000362617a0362757a' - inflater = nghttp2.HDInflater() - hdrs = inflater.inflate(data) - print(hdrs) - - ''' - - cdef cnghttp2.nghttp2_hd_inflater *_inflater - - def __cinit__(self): - rv = cnghttp2.nghttp2_hd_inflate_new(&self._inflater) - if rv != 0: - raise Exception(_strerror(rv)) - - def __dealloc__(self): - cnghttp2.nghttp2_hd_inflate_del(self._inflater) - - def inflate(self, data): - '''Decompresses the compressed header block |data|. The |data| must be - byte string (not unicode string). - - ''' - cdef cnghttp2.nghttp2_nv nv - cdef int inflate_flags - cdef ssize_t rv - cdef uint8_t *buf = data - cdef size_t buflen = len(data) - res = [] - while True: - inflate_flags = 0 - rv = cnghttp2.nghttp2_hd_inflate_hd2(self._inflater, &nv, - &inflate_flags, - buf, buflen, 1) - if rv < 0: - raise Exception(_strerror(rv)) - buf += rv - buflen -= rv - if inflate_flags & cnghttp2.NGHTTP2_HD_INFLATE_EMIT: - # may throw - res.append((nv.name[:nv.namelen], nv.value[:nv.valuelen])) - if inflate_flags & cnghttp2.NGHTTP2_HD_INFLATE_FINAL: - break - - cnghttp2.nghttp2_hd_inflate_end_headers(self._inflater) - return res - - def change_table_size(self, hd_table_bufsize_max): - '''Changes header table size to |hd_table_bufsize_max| byte. - - An exception will be raised on error. - - ''' - cdef int rv - rv = cnghttp2.nghttp2_hd_inflate_change_table_size(self._inflater, - hd_table_bufsize_max) - if rv != 0: - raise Exception(_strerror(rv)) - - def get_hd_table(self): - '''Returns copy of current dynamic header table.''' - cdef size_t length = cnghttp2.nghttp2_hd_inflate_get_num_table_entries( - self._inflater) - cdef const cnghttp2.nghttp2_nv *nv - res = [] - for i in range(62, length + 1): - nv = cnghttp2.nghttp2_hd_inflate_get_table_entry(self._inflater, i) - k = _get_pybytes(nv.name, nv.namelen) - v = _get_pybytes(nv.value, nv.valuelen) - res.append(HDTableEntry(k, nv.namelen, v, nv.valuelen)) - return res - -cdef _strerror(int liberror_code): - return cnghttp2.nghttp2_strerror(liberror_code).decode('utf-8') - -def print_hd_table(hdtable): - '''Convenient function to print |hdtable| to the standard output. This - function does not work if header name/value cannot be decoded using - UTF-8 encoding. - - s=N means the entry occupies N bytes in header table. - - ''' - idx = 0 - for entry in hdtable: - idx += 1 - print('[{}] (s={}) {}: {}'\ - .format(idx, entry.space(), - entry.name.decode('utf-8'), - entry.value.decode('utf-8'))) - -try: - import socket - import io - import asyncio - import traceback - import sys - import email.utils - import datetime - import time - import ssl as tls - from urllib.parse import urlparse -except ImportError: - asyncio = None - -# body generator flags -DATA_OK = 0 -DATA_EOF = 1 -DATA_DEFERRED = 2 - -class _ByteIOWrapper: - - def __init__(self, b): - self.b = b - - def generate(self, n): - data = self.b.read1(n) - if not data: - return None, DATA_EOF - return data, DATA_OK - -def wrap_body(body): - if body is None: - return body - elif isinstance(body, str): - return _ByteIOWrapper(io.BytesIO(body.encode('utf-8'))).generate - elif isinstance(body, bytes): - return _ByteIOWrapper(io.BytesIO(body)).generate - elif isinstance(body, io.IOBase): - return _ByteIOWrapper(body).generate - else: - # assume that callable in the form f(n) returning tuple byte - # string and flag. - return body - -def negotiated_protocol(ssl_obj): - protocol = ssl_obj.selected_alpn_protocol() - if protocol: - logging.info('alpn, protocol:%s', protocol) - return protocol - - protocol = ssl_obj.selected_npn_protocol() - if protocol: - logging.info('npn, protocol:%s', protocol) - return protocol - - return None - -def set_application_protocol(ssl_ctx): - app_protos = [cnghttp2.NGHTTP2_PROTO_VERSION_ID.decode('utf-8')] - ssl_ctx.set_npn_protocols(app_protos) - if tls.HAS_ALPN: - ssl_ctx.set_alpn_protocols(app_protos) - -cdef _get_stream_user_data(cnghttp2.nghttp2_session *session, - int32_t stream_id): - cdef void *stream_user_data - - stream_user_data = cnghttp2.nghttp2_session_get_stream_user_data\ - (session, stream_id) - if stream_user_data == NULL: - return None - - return stream_user_data - -cdef size_t _make_nva(cnghttp2.nghttp2_nv **nva_ptr, headers): - cdef cnghttp2.nghttp2_nv *nva - cdef size_t nvlen - - nvlen = len(headers) - nva = malloc(sizeof(cnghttp2.nghttp2_nv) * nvlen) - for i, (k, v) in enumerate(headers): - nva[i].name = k - nva[i].namelen = len(k) - nva[i].value = v - nva[i].valuelen = len(v) - nva[i].flags = cnghttp2.NGHTTP2_NV_FLAG_NONE - - nva_ptr[0] = nva - - return nvlen - -cdef int server_on_header(cnghttp2.nghttp2_session *session, - const cnghttp2.nghttp2_frame *frame, - const uint8_t *name, size_t namelen, - const uint8_t *value, size_t valuelen, - uint8_t flags, - void *user_data): - cdef http2 = <_HTTP2SessionCoreBase>user_data - logging.debug('server_on_header, type:%s, stream_id:%s', frame.hd.type, frame.hd.stream_id) - - handler = _get_stream_user_data(session, frame.hd.stream_id) - return on_header(name, namelen, value, valuelen, flags, handler) - -cdef int client_on_header(cnghttp2.nghttp2_session *session, - const cnghttp2.nghttp2_frame *frame, - const uint8_t *name, size_t namelen, - const uint8_t *value, size_t valuelen, - uint8_t flags, - void *user_data): - cdef http2 = <_HTTP2SessionCoreBase>user_data - logging.debug('client_on_header, type:%s, stream_id:%s', frame.hd.type, frame.hd.stream_id) - - if frame.hd.type == cnghttp2.NGHTTP2_HEADERS: - handler = _get_stream_user_data(session, frame.hd.stream_id) - elif frame.hd.type == cnghttp2.NGHTTP2_PUSH_PROMISE: - handler = _get_stream_user_data(session, frame.push_promise.promised_stream_id) - - return on_header(name, namelen, value, valuelen, flags, handler) - - -cdef int on_header(const uint8_t *name, size_t namelen, - const uint8_t *value, size_t valuelen, - uint8_t flags, - object handler): - if not handler: - return 0 - - key = name[:namelen] - values = value[:valuelen].split(b'\x00') - if key == b':scheme': - handler.scheme = values[0] - elif key == b':method': - handler.method = values[0] - elif key == b':authority' or key == b'host': - handler.host = values[0] - elif key == b':path': - handler.path = values[0] - elif key == b':status': - handler.status = values[0] - - if key == b'cookie': - handler.cookies.extend(values) - else: - for v in values: - handler.headers.append((key, v)) - - return 0 - -cdef int server_on_begin_request_headers(cnghttp2.nghttp2_session *session, - const cnghttp2.nghttp2_frame *frame, - void *user_data): - cdef http2 = <_HTTP2SessionCore>user_data - - handler = http2._make_handler(frame.hd.stream_id) - cnghttp2.nghttp2_session_set_stream_user_data(session, frame.hd.stream_id, - handler) - - return 0 - -cdef int server_on_begin_headers(cnghttp2.nghttp2_session *session, - const cnghttp2.nghttp2_frame *frame, - void *user_data): - if frame.hd.type == cnghttp2.NGHTTP2_HEADERS: - if frame.headers.cat == cnghttp2.NGHTTP2_HCAT_REQUEST: - return server_on_begin_request_headers(session, frame, user_data) - - return 0 - -cdef int server_on_frame_recv(cnghttp2.nghttp2_session *session, - const cnghttp2.nghttp2_frame *frame, - void *user_data): - cdef http2 = <_HTTP2SessionCore>user_data - logging.debug('server_on_frame_recv, type:%s, stream_id:%s', frame.hd.type, frame.hd.stream_id) - - if frame.hd.type == cnghttp2.NGHTTP2_DATA: - if frame.hd.flags & cnghttp2.NGHTTP2_FLAG_END_STREAM: - handler = _get_stream_user_data(session, frame.hd.stream_id) - if not handler: - return 0 - try: - handler.on_request_done() - except: - sys.stderr.write(traceback.format_exc()) - return http2._rst_stream(frame.hd.stream_id) - elif frame.hd.type == cnghttp2.NGHTTP2_HEADERS: - if frame.headers.cat == cnghttp2.NGHTTP2_HCAT_REQUEST: - handler = _get_stream_user_data(session, frame.hd.stream_id) - if not handler: - return 0 - if handler.cookies: - handler.headers.append((b'cookie', - b'; '.join(handler.cookies))) - handler.cookies = None - try: - handler.on_headers() - if frame.hd.flags & cnghttp2.NGHTTP2_FLAG_END_STREAM: - handler.on_request_done() - except: - sys.stderr.write(traceback.format_exc()) - return http2._rst_stream(frame.hd.stream_id) - elif frame.hd.type == cnghttp2.NGHTTP2_SETTINGS: - if (frame.hd.flags & cnghttp2.NGHTTP2_FLAG_ACK): - http2._stop_settings_timer() - - return 0 - -cdef int on_data_chunk_recv(cnghttp2.nghttp2_session *session, - uint8_t flags, - int32_t stream_id, const uint8_t *data, - size_t length, void *user_data): - cdef http2 = <_HTTP2SessionCoreBase>user_data - - handler = _get_stream_user_data(session, stream_id) - if not handler: - return 0 - - try: - handler.on_data(data[:length]) - except: - sys.stderr.write(traceback.format_exc()) - return http2._rst_stream(stream_id) - - return 0 - -cdef int server_on_frame_send(cnghttp2.nghttp2_session *session, - const cnghttp2.nghttp2_frame *frame, - void *user_data): - cdef http2 = <_HTTP2SessionCore>user_data - logging.debug('server_on_frame_send, type:%s, stream_id:%s', frame.hd.type, frame.hd.stream_id) - - if frame.hd.type == cnghttp2.NGHTTP2_PUSH_PROMISE: - # For PUSH_PROMISE, send push response immediately - handler = _get_stream_user_data\ - (session, frame.push_promise.promised_stream_id) - if not handler: - return 0 - - http2.send_response(handler) - elif frame.hd.type == cnghttp2.NGHTTP2_SETTINGS: - if (frame.hd.flags & cnghttp2.NGHTTP2_FLAG_ACK) != 0: - return 0 - http2._start_settings_timer() - elif frame.hd.type == cnghttp2.NGHTTP2_HEADERS: - if (frame.hd.flags & cnghttp2.NGHTTP2_FLAG_END_STREAM) and \ - cnghttp2.nghttp2_session_check_server_session(session): - # Send RST_STREAM if remote is not closed yet - if cnghttp2.nghttp2_session_get_stream_remote_close( - session, frame.hd.stream_id) == 0: - http2._rst_stream(frame.hd.stream_id, cnghttp2.NGHTTP2_NO_ERROR) - -cdef int server_on_frame_not_send(cnghttp2.nghttp2_session *session, - const cnghttp2.nghttp2_frame *frame, - int lib_error_code, - void *user_data): - cdef http2 = <_HTTP2SessionCore>user_data - logging.debug('server_on_frame_not_send, type:%s, stream_id:%s', frame.hd.type, frame.hd.stream_id) - - if frame.hd.type == cnghttp2.NGHTTP2_PUSH_PROMISE: - # We have to remove handler here. Without this, it is not - # removed until session is terminated. - handler = _get_stream_user_data\ - (session, frame.push_promise.promised_stream_id) - if not handler: - return 0 - http2._remove_handler(handler) - -cdef int on_stream_close(cnghttp2.nghttp2_session *session, - int32_t stream_id, - uint32_t error_code, - void *user_data): - cdef http2 = <_HTTP2SessionCoreBase>user_data - logging.debug('on_stream_close, stream_id:%s', stream_id) - - handler = _get_stream_user_data(session, stream_id) - if not handler: - return 0 - - try: - handler.on_close(error_code) - except: - sys.stderr.write(traceback.format_exc()) - - http2._remove_handler(handler) - - return 0 - -cdef ssize_t data_source_read(cnghttp2.nghttp2_session *session, - int32_t stream_id, - uint8_t *buf, size_t length, - uint32_t *data_flags, - cnghttp2.nghttp2_data_source *source, - void *user_data): - cdef http2 = <_HTTP2SessionCoreBase>user_data - generator = source.ptr - - http2.enter_callback() - try: - data, flag = generator(length) - except: - sys.stderr.write(traceback.format_exc()) - return cnghttp2.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; - finally: - http2.leave_callback() - - if flag == DATA_DEFERRED: - return cnghttp2.NGHTTP2_ERR_DEFERRED - - if data: - nread = len(data) - memcpy(buf, data, nread) - else: - nread = 0 - - if flag == DATA_EOF: - data_flags[0] = cnghttp2.NGHTTP2_DATA_FLAG_EOF - if cnghttp2.nghttp2_session_check_server_session(session): - # Send RST_STREAM if remote is not closed yet - if cnghttp2.nghttp2_session_get_stream_remote_close( - session, stream_id) == 0: - http2._rst_stream(stream_id, cnghttp2.NGHTTP2_NO_ERROR) - elif flag != DATA_OK: - return cnghttp2.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE - - return nread - -cdef int client_on_begin_headers(cnghttp2.nghttp2_session *session, - const cnghttp2.nghttp2_frame *frame, - void *user_data): - cdef http2 = <_HTTP2ClientSessionCore>user_data - - if frame.hd.type == cnghttp2.NGHTTP2_PUSH_PROMISE: - # Generate a temporary handler until the headers are all received - push_handler = BaseResponseHandler() - http2._add_handler(push_handler, frame.push_promise.promised_stream_id) - cnghttp2.nghttp2_session_set_stream_user_data(session, frame.push_promise.promised_stream_id, - push_handler) - - return 0 - -cdef int client_on_frame_recv(cnghttp2.nghttp2_session *session, - const cnghttp2.nghttp2_frame *frame, - void *user_data): - cdef http2 = <_HTTP2ClientSessionCore>user_data - logging.debug('client_on_frame_recv, type:%s, stream_id:%s', frame.hd.type, frame.hd.stream_id) - - if frame.hd.type == cnghttp2.NGHTTP2_DATA: - if frame.hd.flags & cnghttp2.NGHTTP2_FLAG_END_STREAM: - handler = _get_stream_user_data(session, frame.hd.stream_id) - if not handler: - return 0 - try: - handler.on_response_done() - except: - sys.stderr.write(traceback.format_exc()) - return http2._rst_stream(frame.hd.stream_id) - elif frame.hd.type == cnghttp2.NGHTTP2_HEADERS: - if frame.headers.cat == cnghttp2.NGHTTP2_HCAT_RESPONSE or frame.headers.cat == cnghttp2.NGHTTP2_HCAT_PUSH_RESPONSE: - handler = _get_stream_user_data(session, frame.hd.stream_id) - - if not handler: - return 0 - # TODO handle 1xx non-final response - if handler.cookies: - handler.headers.append((b'cookie', - b'; '.join(handler.cookies))) - handler.cookies = None - try: - handler.on_headers() - if frame.hd.flags & cnghttp2.NGHTTP2_FLAG_END_STREAM: - handler.on_response_done() - except: - sys.stderr.write(traceback.format_exc()) - return http2._rst_stream(frame.hd.stream_id) - elif frame.hd.type == cnghttp2.NGHTTP2_SETTINGS: - if (frame.hd.flags & cnghttp2.NGHTTP2_FLAG_ACK): - http2._stop_settings_timer() - elif frame.hd.type == cnghttp2.NGHTTP2_PUSH_PROMISE: - handler = _get_stream_user_data(session, frame.hd.stream_id) - if not handler: - return 0 - # Get the temporary push_handler which now should have all of the header data - push_handler = _get_stream_user_data(session, frame.push_promise.promised_stream_id) - if not push_handler: - return 0 - # Remove the temporary handler - http2._remove_handler(push_handler) - cnghttp2.nghttp2_session_set_stream_user_data(session, frame.push_promise.promised_stream_id, - NULL) - - try: - handler.on_push_promise(push_handler) - except: - sys.stderr.write(traceback.format_exc()) - return http2._rst_stream(frame.hd.stream_id) - - return 0 - -cdef int client_on_frame_send(cnghttp2.nghttp2_session *session, - const cnghttp2.nghttp2_frame *frame, - void *user_data): - cdef http2 = <_HTTP2ClientSessionCore>user_data - logging.debug('client_on_frame_send, type:%s, stream_id:%s', frame.hd.type, frame.hd.stream_id) - - if frame.hd.type == cnghttp2.NGHTTP2_SETTINGS: - if (frame.hd.flags & cnghttp2.NGHTTP2_FLAG_ACK) != 0: - return 0 - http2._start_settings_timer() - -cdef class _HTTP2SessionCoreBase: - cdef cnghttp2.nghttp2_session *session - cdef transport - cdef handler_class - cdef handlers - cdef settings_timer - cdef inside_callback - - def __cinit__(self, transport, handler_class=None): - self.session = NULL - self.transport = transport - self.handler_class = handler_class - self.handlers = set() - self.settings_timer = None - self.inside_callback = False - - def __dealloc__(self): - cnghttp2.nghttp2_session_del(self.session) - - def data_received(self, data): - cdef ssize_t rv - - rv = cnghttp2.nghttp2_session_mem_recv(self.session, data, len(data)) - if rv < 0: - raise Exception('nghttp2_session_mem_recv failed: {}'.format\ - (_strerror(rv))) - self.send_data() - - OUTBUF_MAX = 65535 - SETTINGS_TIMEOUT = 5.0 - - def send_data(self): - cdef ssize_t outbuflen - cdef const uint8_t *outbuf - - while True: - if self.transport.get_write_buffer_size() > self.OUTBUF_MAX: - break - outbuflen = cnghttp2.nghttp2_session_mem_send(self.session, &outbuf) - if outbuflen == 0: - break - if outbuflen < 0: - raise Exception('nghttp2_session_mem_send failed: {}'.format\ - (_strerror(outbuflen))) - self.transport.write(outbuf[:outbuflen]) - - if self.transport.get_write_buffer_size() == 0 and \ - cnghttp2.nghttp2_session_want_read(self.session) == 0 and \ - cnghttp2.nghttp2_session_want_write(self.session) == 0: - self.transport.close() - - def resume(self, stream_id): - cnghttp2.nghttp2_session_resume_data(self.session, stream_id) - if not self.inside_callback: - self.send_data() - - def enter_callback(self): - self.inside_callback = True - - def leave_callback(self): - self.inside_callback = False - - def _make_handler(self, stream_id): - logging.debug('_make_handler, stream_id:%s', stream_id) - handler = self.handler_class(self, stream_id) - self.handlers.add(handler) - return handler - - def _remove_handler(self, handler): - logging.debug('_remove_handler, stream_id:%s', handler.stream_id) - self.handlers.remove(handler) - - def _add_handler(self, handler, stream_id): - logging.debug('_add_handler, stream_id:%s', stream_id) - handler.stream_id = stream_id - handler.http2 = self - handler.remote_address = self._get_remote_address() - handler.client_certificate = self._get_client_certificate() - self.handlers.add(handler) - - def _rst_stream(self, stream_id, - error_code=cnghttp2.NGHTTP2_INTERNAL_ERROR): - cdef int rv - - rv = cnghttp2.nghttp2_submit_rst_stream\ - (self.session, cnghttp2.NGHTTP2_FLAG_NONE, - stream_id, error_code) - - return rv - - def _get_remote_address(self): - return self.transport.get_extra_info('peername') - - def _get_client_certificate(self): - sock = self.transport.get_extra_info('socket') - try: - return sock.getpeercert() - except AttributeError: - return None - - def _start_settings_timer(self): - loop = asyncio.get_event_loop() - self.settings_timer = loop.call_later(self.SETTINGS_TIMEOUT, - self._settings_timeout) - - def _stop_settings_timer(self): - if self.settings_timer: - self.settings_timer.cancel() - self.settings_timer = None - - def _settings_timeout(self): - cdef int rv - - logging.debug('_settings_timeout') - - self.settings_timer = None - - rv = cnghttp2.nghttp2_session_terminate_session\ - (self.session, cnghttp2.NGHTTP2_SETTINGS_TIMEOUT) - try: - self.send_data() - except Exception as err: - sys.stderr.write(traceback.format_exc()) - self.transport.close() - return - - def _log_request(self, handler): - now = datetime.datetime.now() - tv = time.mktime(now.timetuple()) - datestr = email.utils.formatdate(timeval=tv, localtime=False, - usegmt=True) - try: - method = handler.method.decode('utf-8') - except: - method = handler.method - try: - path = handler.path.decode('utf-8') - except: - path = handler.path - logging.info('%s - - [%s] "%s %s HTTP/2" %s - %s', handler.remote_address[0], - datestr, method, path, handler.status, - 'P' if handler.pushed else '-') - - def close(self): - rv = cnghttp2.nghttp2_session_terminate_session\ - (self.session, cnghttp2.NGHTTP2_NO_ERROR) - try: - self.send_data() - except Exception as err: - sys.stderr.write(traceback.format_exc()) - self.transport.close() - return - -cdef class _HTTP2SessionCore(_HTTP2SessionCoreBase): - def __cinit__(self, *args, **kwargs): - cdef cnghttp2.nghttp2_session_callbacks *callbacks - cdef cnghttp2.nghttp2_settings_entry iv[2] - cdef int rv - - super(_HTTP2SessionCore, self).__init__(*args, **kwargs) - - rv = cnghttp2.nghttp2_session_callbacks_new(&callbacks) - - if rv != 0: - raise Exception('nghttp2_session_callbacks_new failed: {}'.format\ - (_strerror(rv))) - - cnghttp2.nghttp2_session_callbacks_set_on_header_callback( - callbacks, server_on_header) - cnghttp2.nghttp2_session_callbacks_set_on_begin_headers_callback( - callbacks, server_on_begin_headers) - cnghttp2.nghttp2_session_callbacks_set_on_frame_recv_callback( - callbacks, server_on_frame_recv) - cnghttp2.nghttp2_session_callbacks_set_on_stream_close_callback( - callbacks, on_stream_close) - cnghttp2.nghttp2_session_callbacks_set_on_frame_send_callback( - callbacks, server_on_frame_send) - cnghttp2.nghttp2_session_callbacks_set_on_frame_not_send_callback( - callbacks, server_on_frame_not_send) - cnghttp2.nghttp2_session_callbacks_set_on_data_chunk_recv_callback( - callbacks, on_data_chunk_recv) - - rv = cnghttp2.nghttp2_session_server_new(&self.session, callbacks, - self) - - cnghttp2.nghttp2_session_callbacks_del(callbacks) - - if rv != 0: - raise Exception('nghttp2_session_server_new failed: {}'.format\ - (_strerror(rv))) - - iv[0].settings_id = cnghttp2.NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS - iv[0].value = 100 - iv[1].settings_id = cnghttp2.NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE - iv[1].value = cnghttp2.NGHTTP2_INITIAL_WINDOW_SIZE - - rv = cnghttp2.nghttp2_submit_settings(self.session, - cnghttp2.NGHTTP2_FLAG_NONE, - iv, sizeof(iv) // sizeof(iv[0])) - - if rv != 0: - raise Exception('nghttp2_submit_settings failed: {}'.format\ - (_strerror(rv))) - - def send_response(self, handler): - cdef cnghttp2.nghttp2_data_provider prd - cdef cnghttp2.nghttp2_data_provider *prd_ptr - cdef cnghttp2.nghttp2_nv *nva - cdef size_t nvlen - cdef int rv - - logging.debug('send_response, stream_id:%s', handler.stream_id) - - nva = NULL - nvlen = _make_nva(&nva, handler.response_headers) - - if handler.response_body: - prd.source.ptr = handler.response_body - prd.read_callback = data_source_read - prd_ptr = &prd - else: - prd_ptr = NULL - - rv = cnghttp2.nghttp2_submit_response(self.session, handler.stream_id, - nva, nvlen, prd_ptr) - - free(nva) - - if rv != 0: - # TODO Ignore return value - self._rst_stream(handler.stream_id) - raise Exception('nghttp2_submit_response failed: {}'.format\ - (_strerror(rv))) - - self._log_request(handler) - - def push(self, handler, promised_handler): - cdef cnghttp2.nghttp2_nv *nva - cdef size_t nvlen - cdef int32_t promised_stream_id - - self.handlers.add(promised_handler) - - nva = NULL - nvlen = _make_nva(&nva, promised_handler.headers) - - promised_stream_id = cnghttp2.nghttp2_submit_push_promise\ - (self.session, - cnghttp2.NGHTTP2_FLAG_NONE, - handler.stream_id, - nva, nvlen, - promised_handler) - if promised_stream_id < 0: - raise Exception('nghttp2_submit_push_promise failed: {}'.format\ - (_strerror(promised_stream_id))) - - promised_handler.stream_id = promised_stream_id - - logging.debug('push, stream_id:%s', promised_stream_id) - - return promised_handler - - def connection_lost(self): - self._stop_settings_timer() - - for handler in self.handlers: - handler.on_close(cnghttp2.NGHTTP2_INTERNAL_ERROR) - self.handlers = set() - -cdef class _HTTP2ClientSessionCore(_HTTP2SessionCoreBase): - def __cinit__(self, *args, **kwargs): - cdef cnghttp2.nghttp2_session_callbacks *callbacks - cdef cnghttp2.nghttp2_settings_entry iv[2] - cdef int rv - - super(_HTTP2ClientSessionCore, self).__init__(*args, **kwargs) - - rv = cnghttp2.nghttp2_session_callbacks_new(&callbacks) - - if rv != 0: - raise Exception('nghttp2_session_callbacks_new failed: {}'.format\ - (_strerror(rv))) - - cnghttp2.nghttp2_session_callbacks_set_on_header_callback( - callbacks, client_on_header) - cnghttp2.nghttp2_session_callbacks_set_on_begin_headers_callback( - callbacks, client_on_begin_headers) - cnghttp2.nghttp2_session_callbacks_set_on_frame_recv_callback( - callbacks, client_on_frame_recv) - cnghttp2.nghttp2_session_callbacks_set_on_stream_close_callback( - callbacks, on_stream_close) - cnghttp2.nghttp2_session_callbacks_set_on_frame_send_callback( - callbacks, client_on_frame_send) - cnghttp2.nghttp2_session_callbacks_set_on_data_chunk_recv_callback( - callbacks, on_data_chunk_recv) - - rv = cnghttp2.nghttp2_session_client_new(&self.session, callbacks, - self) - - cnghttp2.nghttp2_session_callbacks_del(callbacks) - - if rv != 0: - raise Exception('nghttp2_session_client_new failed: {}'.format\ - (_strerror(rv))) - - iv[0].settings_id = cnghttp2.NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS - iv[0].value = 100 - iv[1].settings_id = cnghttp2.NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE - iv[1].value = cnghttp2.NGHTTP2_INITIAL_WINDOW_SIZE - - rv = cnghttp2.nghttp2_submit_settings(self.session, - cnghttp2.NGHTTP2_FLAG_NONE, - iv, sizeof(iv) // sizeof(iv[0])) - - if rv != 0: - raise Exception('nghttp2_submit_settings failed: {}'.format\ - (_strerror(rv))) - - def send_request(self, method, scheme, host, path, headers, body, handler): - cdef cnghttp2.nghttp2_data_provider prd - cdef cnghttp2.nghttp2_data_provider *prd_ptr - cdef cnghttp2.nghttp2_priority_spec *pri_ptr - cdef cnghttp2.nghttp2_nv *nva - cdef size_t nvlen - cdef int32_t stream_id - - body = wrap_body(body) - - custom_headers = _encode_headers(headers) - headers = [ - (b':method', method.encode('utf-8')), - (b':scheme', scheme.encode('utf-8')), - (b':authority', host.encode('utf-8')), - (b':path', path.encode('utf-8')) - ] - headers.extend(custom_headers) - - nva = NULL - nvlen = _make_nva(&nva, headers) - - if body: - prd.source.ptr = body - prd.read_callback = data_source_read - prd_ptr = &prd - else: - prd_ptr = NULL - - # TODO: Enable priorities - pri_ptr = NULL - - stream_id = cnghttp2.nghttp2_submit_request\ - (self.session, pri_ptr, - nva, nvlen, prd_ptr, - handler) - free(nva) - - if stream_id < 0: - raise Exception('nghttp2_submit_request failed: {}'.format\ - (_strerror(stream_id))) - - logging.debug('request, stream_id:%s', stream_id) - - self._add_handler(handler, stream_id) - cnghttp2.nghttp2_session_set_stream_user_data(self.session, stream_id, - handler) - - return handler - - def push(self, push_promise, handler): - if handler: - # push_promise accepted, fill in the handler with the stored - # headers from the push_promise - handler.status = push_promise.status - handler.scheme = push_promise.scheme - handler.method = push_promise.method - handler.host = push_promise.host - handler.path = push_promise.path - handler.cookies = push_promise.cookies - handler.stream_id = push_promise.stream_id - handler.http2 = self - handler.pushed = True - - self._add_handler(handler, handler.stream_id) - - cnghttp2.nghttp2_session_set_stream_user_data(self.session, handler.stream_id, - handler) - else: - # push_promise rejected, reset the stream - self._rst_stream(push_promise.stream_id, - error_code=cnghttp2.NGHTTP2_NO_ERROR) - -if asyncio: - - class BaseRequestHandler: - - """HTTP/2 request (stream) handler base class. - - The class is used to handle the HTTP/2 stream. By default, it does - nothing. It must be subclassed to handle each event callback method. - - The first callback method invoked is on_headers(). It is called - when HEADERS frame, which includes request header fields, is - arrived. - - If request has request body, on_data(data) is invoked for each - chunk of received data. - - When whole request is received, on_request_done() is invoked. - - When stream is closed, on_close(error_code) is called. - - The application can send response using send_response() method. It - can be used in on_headers(), on_data() or on_request_done(). - - The application can push resource using push() method. It must be - used before send_response() call. - - The following instance variables are available: - - client_address - Contains a tuple of the form (host, port) referring to the client's - address. - - client_certificate - May contain the client certificate in its non-binary form - - stream_id - Stream ID of this stream - - scheme - Scheme of the request URI. This is a value of :scheme header field. - - method - Method of this stream. This is a value of :method header field. - - host - This is a value of :authority or host header field. - - path - This is a value of :path header field. - - headers - Request header fields - - """ - - def __init__(self, http2, stream_id): - self.headers = [] - self.cookies = [] - # Stream ID. For promised stream, it is initially -1. - self.stream_id = stream_id - self.http2 = http2 - # address of the client - self.remote_address = self.http2._get_remote_address() - # certificate of the client - self._client_certificate = self.http2._get_client_certificate() - # :scheme header field in request - self.scheme = None - # :method header field in request - self.method = None - # :authority or host header field in request - self.host = None - # :path header field in request - self.path = None - # HTTP status - self.status = None - # True if this is a handler for pushed resource - self.pushed = False - - @property - def client_address(self): - return self.remote_address - - @property - def client_certificate(self): - return self._client_certificate - - def on_headers(self): - - '''Called when request HEADERS is arrived. - - ''' - pass - - def on_data(self, data): - - '''Called when a chunk of request body is arrived. This method - will be called multiple times until all data are received. - - ''' - pass - - def on_request_done(self): - - '''Called when whole request was received - - ''' - pass - - def on_close(self, error_code): - - '''Called when stream is about to close. - - ''' - pass - - def send_response(self, status=200, headers=None, body=None): - - '''Send response. The status is HTTP status code. The headers is - additional response headers. The :status header field is - appended by the library. The body is the response body. It - could be None if response body is empty. Or it must be - instance of either str, bytes, io.IOBase or callable, - called body generator, which takes one parameter, - size. The body generator generates response body. It can - pause generation of response so that it can wait for slow - backend data generation. When invoked, it should return - tuple, byte string and flag. The flag is either DATA_OK, - DATA_EOF and DATA_DEFERRED. For non-empty byte string and - it is not the last chunk of response, DATA_OK is returned - as flag. If this is the last chunk of the response (byte - string is possibly None), DATA_EOF must be returned as - flag. If there is no data available right now, but - additional data are anticipated, return tuple (None, - DATA_DEFERRD). When data arrived, call resume() and - restart response body transmission. - - Only the body generator can pause response body - generation; instance of io.IOBase must not block. - - If instance of str is specified as body, it is encoded - using UTF-8. - - The headers is a list of tuple of the form (name, - value). The name and value can be either unicode string or - byte string. - - On error, exception will be thrown. - - ''' - if self.status is not None: - raise Exception('response has already been sent') - - if not status: - raise Exception('status must not be empty') - - body = wrap_body(body) - - self._set_response_prop(status, headers, body) - self.http2.send_response(self) - - def push(self, path, method='GET', request_headers=None, - status=200, headers=None, body=None): - - '''Push a resource. The path is a path portion of request URI - for this - resource. The method is a method to access this - resource. The request_headers is additional request - headers to access this resource. The :scheme, :method, - :authority and :path are appended by the library. The - :scheme and :authority are inherited from the request (not - request_headers parameter). - - The status is HTTP status code. The headers is additional - response headers. The :status header field is appended by - the library. The body is the response body. It has the - same semantics of body parameter of send_response(). - - The headers and request_headers are a list of tuple of the - form (name, value). The name and value can be either - unicode string or byte string. - - On error, exception will be thrown. - - ''' - if not status: - raise Exception('status must not be empty') - - if not method: - raise Exception('method must not be empty') - - if not path: - raise Exception('path must not be empty') - - body = wrap_body(body) - - promised_handler = self.http2._make_handler(-1) - promised_handler.pushed = True - promised_handler.scheme = self.scheme - promised_handler.method = method.encode('utf-8') - promised_handler.host = self.host - promised_handler.path = path.encode('utf-8') - promised_handler._set_response_prop(status, headers, body) - - headers = [ - (b':method', promised_handler.method), - (b':scheme', promised_handler.scheme), - (b':authority', promised_handler.host), - (b':path', promised_handler.path) - ] - headers.extend(_encode_headers(request_headers)) - - promised_handler.headers = headers - - return self.http2.push(self, promised_handler) - - def _set_response_prop(self, status, headers, body): - self.status = status - - if headers is None: - headers = [] - - self.response_headers = [(b':status', str(status).encode('utf-8'))] - self.response_headers.extend(_encode_headers(headers)) - - self.response_body = body - - def resume(self): - self.http2.resume(self.stream_id) - - def _encode_headers(headers): - if not headers: - return [] - return [(k if isinstance(k, bytes) else k.encode('utf-8'), - v if isinstance(v, bytes) else v.encode('utf-8')) \ - for k, v in headers] - - class _HTTP2Session(asyncio.Protocol): - - def __init__(self, RequestHandlerClass): - asyncio.Protocol.__init__(self) - self.RequestHandlerClass = RequestHandlerClass - self.http2 = None - - def connection_made(self, transport): - address = transport.get_extra_info('peername') - logging.info('connection_made, address:%s, port:%s', address[0], address[1]) - - self.transport = transport - sock = self.transport.get_extra_info('socket') - try: - sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - except OSError as e: - logging.info('failed to set tcp-nodelay: %s', str(e)) - ssl_ctx = self.transport.get_extra_info('sslcontext') - if ssl_ctx: - ssl_obj = self.transport.get_extra_info('ssl_object') - protocol = negotiated_protocol(ssl_obj) - if protocol is None or protocol.encode('utf-8') != \ - cnghttp2.NGHTTP2_PROTO_VERSION_ID: - self.transport.abort() - return - try: - self.http2 = _HTTP2SessionCore\ - (self.transport, - self.RequestHandlerClass) - except Exception as err: - sys.stderr.write(traceback.format_exc()) - self.transport.abort() - return - - - def connection_lost(self, exc): - logging.info('connection_lost') - if self.http2: - self.http2.connection_lost() - self.http2 = None - - def data_received(self, data): - try: - self.http2.data_received(data) - except Exception as err: - sys.stderr.write(traceback.format_exc()) - self.transport.close() - return - - def resume_writing(self): - try: - self.http2.send_data() - except Exception as err: - sys.stderr.write(traceback.format_exc()) - self.transport.close() - return - - class HTTP2Server: - - '''HTTP/2 server. - - This class builds on top of the asyncio event loop. On - construction, RequestHandlerClass must be given, which must be a - subclass of BaseRequestHandler class. - - ''' - def __init__(self, address, RequestHandlerClass, ssl=None): - - '''address is a tuple of the listening address and port (e.g., - ('127.0.0.1', 8080)). RequestHandlerClass must be a subclass - of BaseRequestHandler class to handle a HTTP/2 stream. The - ssl can be ssl.SSLContext instance. If it is not None, the - resulting server is SSL/TLS capable. - - ''' - def session_factory(): - return _HTTP2Session(RequestHandlerClass) - - self.loop = asyncio.get_event_loop() - - if ssl: - set_application_protocol(ssl) - - coro = self.loop.create_server(session_factory, - host=address[0], port=address[1], - ssl=ssl) - self.server = self.loop.run_until_complete(coro) - logging.info('listen, address:%s, port:%s', address[0], address[1]) - - def serve_forever(self): - try: - self.loop.run_forever() - finally: - self.server.close() - self.loop.close() - - - - class BaseResponseHandler: - - """HTTP/2 response (stream) handler base class. - - The class is used to handle the HTTP/2 stream. By default, it does - not nothing. It must be subclassed to handle each event callback - method. - - The first callback method invoked is on_headers(). It is called - when HEADERS frame, which includes response header fields, is - arrived. - - If response has a body, on_data(data) is invoked for each - chunk of received data. - - When whole response is received, on_response_done() is invoked. - - When stream is closed or underlying connection is lost, - on_close(error_code) is called. - - The application can send follow up requests using HTTP2Client.send_request() method. - - The application can handle push resource using on_push_promise() method. - - The following instance variables are available: - - server_address - Contains a tuple of the form (host, port) referring to the server's - address. - - stream_id - Stream ID of this stream - - scheme - Scheme of the request URI. This is a value of :scheme header field. - - method - Method of this stream. This is a value of :method header field. - - host - This is a value of :authority or host header field. - - path - This is a value of :path header field. - - headers - Response header fields. There is a special exception. If this - object is passed to push_promise(), this instance variable contains - pushed request header fields. - - """ - - def __init__(self, http2=None, stream_id=-1): - self.headers = [] - self.cookies = [] - # Stream ID. For promised stream, it is initially -1. - self.stream_id = stream_id - self.http2 = http2 - # address of the server - self.remote_address = None - # :scheme header field in request - self.scheme = None - # :method header field in request - self.method = None - # :authority or host header field in request - self.host = None - # :path header field in request - self.path = None - # HTTP status - self.status = None - # True if this is a handler for pushed resource - self.pushed = False - - @property - def server_address(self): - return self.remote_address - - def on_headers(self): - - '''Called when response HEADERS is arrived. - - ''' - pass - - def on_data(self, data): - - '''Called when a chunk of response body is arrived. This method - will be called multiple times until all data are received. - - ''' - pass - - def on_response_done(self): - - '''Called when whole response was received - - ''' - pass - - def on_close(self, error_code): - - '''Called when stream is about to close. - - ''' - pass - - def on_push_promise(self, push_promise): - - '''Called when a push is promised. Default behavior is to - cancel the push. If application overrides this method, - it should call either accept_push or reject_push. - - ''' - self.reject_push(push_promise) - - def reject_push(self, push_promise): - - '''Convenience method equivalent to calling accept_push - with a falsy value. - - ''' - self.http2.push(push_promise, None) - - def accept_push(self, push_promise, handler=None): - - '''Accept a push_promise and provider a handler for the - new stream. If a falsy value is supplied for the handler, - the push is rejected. - - ''' - self.http2.push(push_promise, handler) - - def resume(self): - self.http2.resume(self.stream_id) - - class _HTTP2ClientSession(asyncio.Protocol): - - def __init__(self, client): - asyncio.Protocol.__init__(self) - self.http2 = None - self.pending = [] - self.client = client - - def connection_made(self, transport): - address = transport.get_extra_info('peername') - logging.info('connection_made, address:%s, port:%s', address[0], address[1]) - - self.transport = transport - sock = self.transport.get_extra_info('socket') - sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - ssl_ctx = self.transport.get_extra_info('sslcontext') - if ssl_ctx: - ssl_obj = self.transport.get_extra_info('ssl_object') - protocol = negotiated_protocol(ssl_obj) - if protocol is None or protocol.encode('utf-8') != \ - cnghttp2.NGHTTP2_PROTO_VERSION_ID: - self.transport.abort() - - self.http2 = _HTTP2ClientSessionCore(self.transport) - - # Clear pending requests - send_pending = self.pending - self.pending = [] - for method,scheme,host,path,headers,body,handler in send_pending: - self.send_request(method=method, scheme=scheme, host=host, path=path,\ - headers=headers, body=body, handler=handler) - self.http2.send_data() - - def connection_lost(self, exc): - logging.info('connection_lost') - if self.http2: - self.http2 = None - self.client.close() - - def data_received(self, data): - try: - self.http2.data_received(data) - except Exception as err: - sys.stderr.write(traceback.format_exc()) - self.transport.close() - return - - def resume_writing(self): - try: - self.http2.send_data() - except Exception as err: - sys.stderr.write(traceback.format_exc()) - self.transport.close() - return - - def send_request(self, method, scheme, host, path, headers, body, handler): - try: - # Waiting until connection established - if not self.http2: - self.pending.append([method, scheme, host, path, headers, body, handler]) - return - - self.http2.send_request(method=method, scheme=scheme, host=host, path=path,\ - headers=headers, body=body, handler=handler) - self.http2.send_data() - except Exception as err: - sys.stderr.write(traceback.format_exc()) - self.transport.close() - return - - def close(self): - if self.http2: - self.http2.close() - - - class HTTP2Client: - - '''HTTP/2 client. - - This class builds on top of the asyncio event loop. - - ''' - def __init__(self, address, loop=None, ssl=None): - - '''address is a tuple of the connect address and port (e.g., - ('127.0.0.1', 8080)). The ssl can be ssl.SSLContext instance. - If it is not None, the resulting client is SSL/TLS capable. - ''' - - self.address = address - self.session = _HTTP2ClientSession(self) - def session_factory(): - return self.session - - if ssl: - set_application_protocol(ssl) - - self.loop = loop - if not self.loop: - self.loop = asyncio.get_event_loop() - - coro = self.loop.create_connection(session_factory, - host=address[0], port=address[1], - ssl=ssl) - - if ssl: - self.scheme = 'https' - else: - self.scheme = 'http' - - self.transport,_ = self.loop.run_until_complete(coro) - logging.info('connect, address:%s, port:%s', self.address[0], self.address[1]) - - @property - def io_loop(self): - return self.loop - - def close(self): - self.session.close() - - def send_request(self, method='GET', url='/', headers=None, body=None, handler=None): - url = urlparse(url) - scheme = url.scheme if url.scheme else self.scheme - host = url.netloc if url.netloc else self.address[0]+':'+str(self.address[1]) - path = url.path - if url.params: - path += ';'+url.params - if url.query: - path += '?'+url.query - if url.fragment: - path += '#'+url.fragment - - self.session.send_request(method=method, scheme=scheme, host=host, path=path,\ - headers=headers, body=body, handler=handler) diff --git a/python/setup.py.in b/python/setup.py.in deleted file mode 100644 index 93f89964..00000000 --- a/python/setup.py.in +++ /dev/null @@ -1,46 +0,0 @@ -# nghttp2 - HTTP/2 C Library - -# Copyright (c) 2013 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. - -from setuptools import setup, Extension - -LIBS = ['nghttp2'] - -setup( - name = 'python-nghttp2', - description = 'Python HTTP/2 library on top of nghttp2', - author = 'Tatsuhiro Tsujikawa', - version = '@PACKAGE_VERSION@', - author_email = 'tatsuhiro.t@gmail.com', - url = 'https://nghttp2.org/', - keywords = [], - ext_modules = [Extension("nghttp2", - ["nghttp2.c"], - include_dirs=['@top_srcdir@/lib', - '@top_srcdir@/lib/includes', - '@top_builddir@/lib/includes'], - library_dirs=['@top_builddir@/lib/.libs', - '@top_builddir@/lib', - '@top_builddir@'], - libraries=LIBS)], - long_description='TBD' - ) diff --git a/python/wsgi.py b/python/wsgi.py deleted file mode 100644 index 8e19bad6..00000000 --- a/python/wsgi.py +++ /dev/null @@ -1,119 +0,0 @@ -# nghttp2 - HTTP/2.0 C Library - -# Copyright (c) 2013 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. -import io -import sys -from urllib.parse import urlparse - -import nghttp2 - -def _dance_decode(b): - # TODO faster than looping through and mod-128'ing all unicode points? - return b.decode('utf-8').encode('latin1').decode('latin1') - -class WSGIContainer(nghttp2.BaseRequestHandler): - - _BASE_ENVIRON = { - 'wsgi.version': (1,0), - 'wsgi.url_scheme': 'http', # FIXME - 'wsgi.multithread': True, # TODO I think? - 'wsgi.multiprocess': False, # TODO no idea - 'wsgi.run_once': True, # TODO now I'm just guessing - 'wsgi.errors': sys.stderr, # TODO will work for testing - is this even used by any frameworks? - } - - def __init__(self, app, *args, **kwargs): - super(WSGIContainer, self).__init__(*args, **kwargs) - self.app = app - self.chunks = [] - - def on_data(self, chunk): - self.chunks.append(chunk) - - def on_request_done(self): - environ = WSGIContainer._BASE_ENVIRON.copy() - parsed = urlparse(self.path) - - environ['wsgi.input'] = io.BytesIO(b''.join(self.chunks)) - - for name, value in self.headers: - mangled_name = b'HTTP_' + name.replace(b'-', b'_').upper() - environ[_dance_decode(mangled_name)] = _dance_decode(value) - - environ.update(dict( - REQUEST_METHOD=_dance_decode(self.method), - # TODO SCRIPT_NAME? like APPLICATION_ROOT in Flask... - PATH_INFO=_dance_decode(parsed.path), - QUERY_STRING=_dance_decode(parsed.query), - CONTENT_TYPE=environ.get('HTTP_CONTENT_TYPE', ''), - CONTENT_LENGTH=environ.get('HTTP_CONTENT_LENGTH', ''), - SERVER_NAME=_dance_decode(self.host), - SERVER_PORT='', # FIXME probably requires changes in nghttp2 - SERVER_PROTOCOL='HTTP/2.0', - )) - - response_status = [None] - response_headers = [None] - response_chunks = [] - - def start_response(status, headers, exc_info=None): - if response_status[0] is not None: - raise AssertionError('Response already started') - exc_info = None # avoid dangling circular ref - TODO is this necessary? borrowed from snippet in WSGI spec - - response_status[0] = status - response_headers[0] = headers - # TODO handle exc_info - - return lambda chunk: response_chunks.append(chunk) - - # TODO technically, this breaks the WSGI spec by buffering the status, - # headers, and body until all are completely output from the app before - # writing the response, but it looks like nghttp2 doesn't support any - # other way for now - - # TODO disallow yielding/returning before start_response is called - response_chunks.extend(self.app(environ, start_response)) - response_body = b''.join(response_chunks) - - # TODO automatically set content-length if not provided - self.send_response( - status=response_status[0], - headers=response_headers[0], - body=response_body, - ) - -def wsgi_app(app): - return lambda *args, **kwargs: WSGIContainer(app, *args, **kwargs) - - -if __name__ == '__main__': - import ssl - from werkzeug.testapp import test_app - - ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23) - ssl_ctx.options = ssl.OP_ALL | ssl.OP_NO_SSLv2 - ssl_ctx.load_cert_chain('server.crt', 'server.key') - - server = nghttp2.HTTP2Server(('127.0.0.1', 8443), wsgi_app(test_app), - ssl=ssl_ctx) - server.serve_forever()