Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-httptools for 
openSUSE:Factory checked in at 2022-01-10 23:54:00
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-httptools (Old)
 and      /work/SRC/openSUSE:Factory/.python-httptools.new.1892 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-httptools"

Mon Jan 10 23:54:00 2022 rev:2 rq:945406 version:0.3.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-httptools/python-httptools.changes        
2020-10-13 15:46:53.389481929 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-httptools.new.1892/python-httptools.changes  
    2022-01-10 23:54:37.676858224 +0100
@@ -1,0 +2,18 @@
+Sun Jan  9 08:53:15 UTC 2022 - Torsten Gruner <simmpho...@opensuse.org>
+
+- update to version 0.3.0
+  * Use cibuildwheel to build release wheels (by @elprans in 2f57b6b)
+- version 0.2.0
+  * Swap http-parse to llhttp
+    (by @victoraugustolls and @fantix in 63b5de2 for #56)
+  * Fix httptools.__all__
+    (by @elprans in 9340d32 for #52)
+  * Add Python 3.9 in the build/test matrix
+    (by @b0g3r in e2d1a46 for #62)
+- version 0.1.2
+  * Fix httptools.__all__
+    (by @elprans in 9340d32 for #52)
+  * Add Python 3.9 in the build/test matrix
+    (by @b0g3r in e2d1a46 for #62)
+
+-------------------------------------------------------------------

Old:
----
  httptools-0.1.1.tar.gz

New:
----
  http-parser-2.9.4.tar.gz
  httptools-0.3.0.tar.gz
  llhttp-release-v6.0.6.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-httptools.spec ++++++
--- /var/tmp/diff_new_pack.3Bu9pG/_old  2022-01-10 23:54:38.064858564 +0100
+++ /var/tmp/diff_new_pack.3Bu9pG/_new  2022-01-10 23:54:38.068858568 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package python-httptools
 #
-# Copyright (c) 2020 SUSE LLC
+# Copyright (c) 2022 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -19,18 +19,19 @@
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 %define skip_python2 1
 Name:           python-httptools
-Version:        0.1.1
+Version:        0.3.0
 Release:        0
 Summary:        Python framework independent HTTP protocol utils
 License:        MIT
 Group:          Development/Languages/Python
 URL:            https://github.com/MagicStack/httptools
-Source:         
https://github.com/MagicStack/httptools/archive/v%{version}.tar.gz#/httptools-%{version}.tar.gz
-BuildRequires:  %{python_module Cython}
+Source0:        
https://github.com/MagicStack/httptools/archive/v%{version}.tar.gz#/httptools-%{version}.tar.gz
+Source1:        
https://github.com/nodejs/llhttp/archive/refs/tags/release/v6.0.6.tar.gz#/llhttp-release-v6.0.6.tar.gz
+Source2:        
https://github.com/nodejs/http-parser/archive/refs/tags/v2.9.4.tar.gz#/http-parser-2.9.4.tar.gz
+BuildRequires:  %{python_module Cython >= 0.29.24}
 BuildRequires:  %{python_module pytest}
 BuildRequires:  %{python_module setuptools}
 BuildRequires:  fdupes
-BuildRequires:  http-parser-devel
 BuildRequires:  python-rpm-macros
 %python_subpackages
 
@@ -39,23 +40,26 @@
 
 %prep
 %setup -q -n httptools-%{version}
-# unpin Cython
-sed -i 's/Cython==/Cython>=/' setup.py
-cp %{_includedir}/http_parser.h vendor/http-parser/
+rm -df vendor/llhttp/
+tar -xzf '%{SOURCE1}' -C vendor
+mv vendor/llhttp-release*/ vendor/llhttp/
+rm -df vendor/http-parser/
+tar -xzf '%{SOURCE2}' -C vendor
+mv vendor/http-parser*/ vendor/http-parser/
 
 %build
-%python_build build_ext --use-system-http-parser
+%python_build
 
 %install
 %python_install
-%{python_expand rm %{buildroot}%{$python_sitearch}/httptools/parser/parser.c
+%{python_expand rm %{buildroot}%{$python_sitearch}/httptools/parser/*parser.c;
 %fdupes %{buildroot}%{$python_sitearch}
 }
 
+%if 0%{?python_version_nodots} > 36
 %check
-mv httptools .httptools
-%pytest_arch -k 'not (test_parser_response_1 or test_parser_url_2)'
-mv .httptools httptools
+%pytest_arch -k 'not test_parser_response_1'
+%endif
 
 %files %{python_files}
 %doc README.md

++++++ httptools-0.1.1.tar.gz -> httptools-0.3.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/httptools-0.1.1/.github/workflows/build-manylinux-wheels.sh 
new/httptools-0.3.0/.github/workflows/build-manylinux-wheels.sh
--- old/httptools-0.1.1/.github/workflows/build-manylinux-wheels.sh     
2020-02-08 00:00:33.000000000 +0100
+++ new/httptools-0.3.0/.github/workflows/build-manylinux-wheels.sh     
1970-01-01 01:00:00.000000000 +0100
@@ -1,25 +0,0 @@
-#!/bin/bash
-
-set -e -x
-
-PY_MAJOR=${PYTHON_VERSION%%.*}
-PY_MINOR=${PYTHON_VERSION#*.}
-
-ML_PYTHON_VERSION="cp${PY_MAJOR}${PY_MINOR}-cp${PY_MAJOR}${PY_MINOR}"
-if [ "${PY_MAJOR}" -lt "4" -a "${PY_MINOR}" -lt "8" ]; then
-    ML_PYTHON_VERSION+="m"
-fi
-
-# Compile wheels
-PYTHON="/opt/python/${ML_PYTHON_VERSION}/bin/python"
-PIP="/opt/python/${ML_PYTHON_VERSION}/bin/pip"
-"${PIP}" install --upgrade setuptools pip wheel~=0.31.1
-cd "${GITHUB_WORKSPACE}"
-make clean
-"${PYTHON}" setup.py bdist_wheel
-
-# Bundle external shared libraries into the wheels.
-for whl in "${GITHUB_WORKSPACE}"/dist/*.whl; do
-    auditwheel repair $whl -w "${GITHUB_WORKSPACE}"/dist/
-    rm "${GITHUB_WORKSPACE}"/dist/*-linux_*.whl
-done
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/httptools-0.1.1/.github/workflows/release-trigger.yml 
new/httptools-0.3.0/.github/workflows/release-trigger.yml
--- old/httptools-0.1.1/.github/workflows/release-trigger.yml   2020-02-08 
00:00:33.000000000 +0100
+++ new/httptools-0.3.0/.github/workflows/release-trigger.yml   1970-01-01 
01:00:00.000000000 +0100
@@ -1,25 +0,0 @@
-name: Trigger Release
-
-on:
-  pull_request_review:
-    types: [submitted]
-
-jobs:
-  check-review:
-    runs-on: ubuntu-latest
-    steps:
-    - name: Validate release PR
-      uses: edgedb/action-release/validate-pr@master
-      id: release
-      continue-on-error: true
-      with:
-        github_token: ${{ secrets.RELEASE_BOT_GITHUB_TOKEN }}
-        version_file: httptools/_version.py
-        version_line_pattern: |
-          __version__\s*=\s*(?:['"])([[:PEP440:]])(?:['"])
-    - name: Trigger release
-      uses: edgedb/action-release/trigger@master
-      if: steps.release.outputs.version != 0
-      with:
-        github_token: ${{ secrets.RELEASE_BOT_GITHUB_TOKEN }}
-        release_validation_check: "validate-release-request"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/httptools-0.1.1/.github/workflows/release.yml 
new/httptools-0.3.0/.github/workflows/release.yml
--- old/httptools-0.1.1/.github/workflows/release.yml   2020-02-08 
00:00:33.000000000 +0100
+++ new/httptools-0.3.0/.github/workflows/release.yml   2021-08-10 
20:09:32.000000000 +0200
@@ -17,6 +17,8 @@
       uses: edgedb/action-release/validate-pr@master
       id: checkver
       with:
+        require_team: Release Managers
+        require_approval: no
         github_token: ${{ secrets.RELEASE_BOT_GITHUB_TOKEN }}
         version_file: httptools/_version.py
         version_line_pattern: |
@@ -35,7 +37,7 @@
         mkdir -p dist/
         echo "${VERSION}" > dist/VERSION
 
-    - uses: actions/upload-artifact@v1
+    - uses: actions/upload-artifact@v2
       with:
         name: dist
         path: dist/
@@ -45,93 +47,98 @@
     runs-on: ubuntu-latest
 
     steps:
-    - uses: actions/checkout@v1
+    - uses: actions/checkout@v2
       with:
         fetch-depth: 50
         submodules: true
 
-    - name: Set up Python 3.7
-      uses: actions/setup-python@v1
-      with:
-        python-version: 3.7
+    - name: Set up Python
+      uses: actions/setup-python@v2
 
     - name: Build source distribution
       run: |
-        pip install -U setuptools wheel pip
+        python -m pip install -U setuptools wheel pip
         python setup.py sdist
 
-    - uses: actions/upload-artifact@v1
+    - uses: actions/upload-artifact@v2
       with:
         name: dist
-        path: dist/
+        path: dist/*.tar.*
 
   build-wheels:
     needs: validate-release-request
     runs-on: ${{ matrix.os }}
     strategy:
       matrix:
-        python-version: [3.5, 3.6, 3.7, 3.8]
-        os: [ubuntu-16.04, macos-latest, windows-latest]
+        os: [ubuntu-latest, macos-latest, windows-latest]
+        cibw_arch: ["auto64", "aarch64", "universal2"]
+        cibw_python:
+          - "cp36-*"
+          - "cp37-*"
+          - "cp38-*"
+          - "cp39-*"
+          - "cp310-*"
         exclude:
-          # Python 3.5 is unable to properly
-          # find the recent VS tooling
-          # https://bugs.python.org/issue30389
+          - os: ubuntu-latest
+            cibw_arch: universal2
+          - os: macos-latest
+            cibw_arch: aarch64
+          - os: macos-latest
+            cibw_python: "cp36-*"
+            cibw_arch: universal2
+          - os: macos-latest
+            cibw_python: "cp37-*"
+            cibw_arch: universal2
           - os: windows-latest
-            python-version: 3.5
+            cibw_arch: universal2
+          - os: windows-latest
+            cibw_arch: aarch64
+
+    defaults:
+      run:
+        shell: bash
+
+    env:
+      PIP_DISABLE_PIP_VERSION_CHECK: 1
 
     steps:
-    - uses: actions/checkout@v1
+    - uses: actions/checkout@v2
       with:
         fetch-depth: 50
         submodules: true
 
-    - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v1
+    - name: Set up QEMU
+      if: matrix.os == 'ubuntu-latest' && matrix.cibw_arch == 'aarch64'
+      uses: docker/setup-qemu-action@v1
       with:
-        python-version: ${{ matrix.python-version }}
-
-    - name: Install Python Deps
-      run: |
-        python -m pip install --upgrade setuptools pip wheel
+        platforms: arm64
 
-    - name: Build Wheels (linux)
-      if: startsWith(matrix.os, 'ubuntu')
-      uses: docker://quay.io/pypa/manylinux1_x86_64
+    - uses: pypa/cibuildwheel@v2.1.1
       env:
-        PYTHON_VERSION: ${{ matrix.python-version }}
-      with:
-        entrypoint: 
/github/workspace/.github/workflows/build-manylinux-wheels.sh
-
-    - name: Build Wheels (non-linux)
-      if: "!startsWith(matrix.os, 'ubuntu')"
-      run: |
-        make clean
-        python setup.py bdist_wheel
+        CIBW_BUILD_VERBOSITY: 1
+        CIBW_BUILD: ${{ matrix.cibw_python }}
+        CIBW_ARCHS: ${{ matrix.cibw_arch }}
+        CIBW_TEST_EXTRAS: "test"
+        CIBW_TEST_COMMAND: "python {project}/tests/__init__.py"
+        CIBW_TEST_COMMAND_WINDOWS: "python {project}\\tests\\__init__.py"
+        CIBW_TEST_SKIP: "*universal2:arm64"
 
-    - name: Test Wheels
-      if: |
-        !startsWith(matrix.os, 'windows')
-        && !contains(github.event.pull_request.labels.*.name, 'skip wheel 
tests')
-      run: |
-        pip install --pre httptools -f "file:///${GITHUB_WORKSPACE}/dist"
-        make -C "${GITHUB_WORKSPACE}" testinstalled
-
-    - uses: actions/upload-artifact@v1
+    - uses: actions/upload-artifact@v2
       with:
         name: dist
-        path: dist/
+        path: wheelhouse/*.whl
 
   publish:
     needs: [build-sdist, build-wheels]
     runs-on: ubuntu-latest
 
     steps:
-    - uses: actions/checkout@v1
+    - uses: actions/checkout@v2
       with:
         fetch-depth: 5
         submodules: false
 
-    - uses: actions/download-artifact@v1
+    - uses: actions/download-artifact@v2
       with:
         name: dist
         path: dist/
@@ -161,7 +168,7 @@
         release_name: v${{ steps.relver.outputs.version }}
         target: ${{ github.event.pull_request.base.ref }}
         body: ${{ github.event.pull_request.body }}
-        draft: true
+        draft: false
 
     - run: |
         ls -al dist/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/httptools-0.1.1/.github/workflows/tests.yml 
new/httptools-0.3.0/.github/workflows/tests.yml
--- old/httptools-0.1.1/.github/workflows/tests.yml     2020-02-08 
00:00:33.000000000 +0100
+++ new/httptools-0.3.0/.github/workflows/tests.yml     2021-08-10 
20:09:32.000000000 +0200
@@ -13,10 +13,9 @@
   build:
     runs-on: ${{ matrix.os }}
     strategy:
-      max-parallel: 4
       matrix:
-        python-version: [3.5, 3.6, 3.7, 3.8]
-        os: [windows-latest, ubuntu-18.04, macos-latest]
+        python-version: [3.5, 3.6, 3.7, 3.8, 3.9, 3.10.0-rc.1]
+        os: [windows-latest, ubuntu-latest, macos-latest]
         exclude:
           # Python 3.5 is unable to properly
           # find the recent VS tooling
@@ -24,24 +23,27 @@
           - os: windows-latest
             python-version: 3.5
 
+    env:
+      PIP_DISABLE_PIP_VERSION_CHECK: 1
+
     steps:
-    - uses: actions/checkout@v1
+    - uses: actions/checkout@v2
       with:
         fetch-depth: 50
         submodules: true
 
     - name: Check if release PR.
       uses: edgedb/action-release/validate-pr@master
-      continue-on-error: true
       id: release
       with:
         github_token: ${{ secrets.RELEASE_BOT_GITHUB_TOKEN }}
+        missing_version_ok: yes
         version_file: httptools/_version.py
         version_line_pattern: |
           __version__\s*=\s*(?:['"])([[:PEP440:]])(?:['"])
 
     - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v1
+      uses: actions/setup-python@v2
       if: steps.release.outputs.version == 0
       with:
         python-version: ${{ matrix.python-version }}
@@ -49,5 +51,6 @@
     - name: Test
       if: steps.release.outputs.version == 0
       run: |
-        pip install -e .[test]
-        python setup.py test
+        python -m pip install -U pip setuptools wheel
+        python -m pip install -e .[test]
+        python -m unittest -v tests.suite
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/httptools-0.1.1/.gitignore 
new/httptools-0.3.0/.gitignore
--- old/httptools-0.1.1/.gitignore      2020-02-08 00:00:33.000000000 +0100
+++ new/httptools-0.3.0/.gitignore      2021-08-10 20:09:32.000000000 +0200
@@ -29,3 +29,5 @@
 /.pytest_cache
 /.mypy_cache
 /.vscode
+.eggs
+.venv
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/httptools-0.1.1/.gitmodules 
new/httptools-0.3.0/.gitmodules
--- old/httptools-0.1.1/.gitmodules     2020-02-08 00:00:33.000000000 +0100
+++ new/httptools-0.3.0/.gitmodules     2021-08-10 20:09:32.000000000 +0200
@@ -1,3 +1,6 @@
 [submodule "vendor/http-parser"]
        path = vendor/http-parser
        url = https://github.com/nodejs/http-parser.git
+[submodule "vendor/llhttp"]
+       path = vendor/llhttp
+       url = https://github.com/nodejs/llhttp.git
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/httptools-0.1.1/Makefile new/httptools-0.3.0/Makefile
--- old/httptools-0.1.1/Makefile        2020-02-08 00:00:33.000000000 +0100
+++ new/httptools-0.3.0/Makefile        2021-08-10 20:09:32.000000000 +0200
@@ -13,17 +13,16 @@
        python3 setup.py sdist upload
 
 
-test:
-       python3 setup.py test
+test: compile
+       python3 -m unittest -v
 
 clean:
        find $(ROOT)/httptools/parser -name '*.c' | xargs rm -f
        find $(ROOT)/httptools/parser -name '*.html' | xargs rm -f
 
-distclean:
+distclean: clean
        git --git-dir="$(ROOT)/vendor/http-parser/.git" clean -dfx
-       find $(ROOT)/httptools/parser -name '*.c' | xargs rm -f
-       find $(ROOT)/httptools/parser -name '*.html' | xargs rm -f
+       git --git-dir="$(ROOT)/vendor/llhttp/.git" clean -dfx
 
 
 testinstalled:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/httptools-0.1.1/README.md 
new/httptools-0.3.0/README.md
--- old/httptools-0.1.1/README.md       2020-02-08 00:00:33.000000000 +0100
+++ new/httptools-0.3.0/README.md       2021-08-10 20:09:32.000000000 +0200
@@ -8,8 +8,11 @@
 # APIs
 
 httptools contains two classes `httptools.HttpRequestParser`,
-`httptools.HttpResponseParser` and a function for parsing URLs
-`httptools.parse_url`.  See unittests for examples.
+`httptools.HttpResponseParser` (fulfilled through
+[llhttp](https://github.com/nodejs/llhttp)) and a function for
+parsing URLs `httptools.parse_url` (through
+[http-parse](https://github.com/nodejs/http-parser) for now).
+See unittests for examples.
 
 
 ```python
@@ -91,12 +94,12 @@
 1. Clone this repository with
    `git clone --recursive g...@github.com:MagicStack/httptools.git`
 
-2. Create a virtual environment with Python 3.5:
-   `python3.5 -m venv envname`
+2. Create a virtual environment with Python 3:
+   `python3 -m venv envname`
 
 3. Activate the environment with `source envname/bin/activate`
 
-4. Install Cython with `pip install cython`
+4. Install development requirements with `pip install -e .[test]`
 
 5. Run `make` and `make test`.
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/httptools-0.1.1/httptools/__init__.py 
new/httptools-0.3.0/httptools/__init__.py
--- old/httptools-0.1.1/httptools/__init__.py   2020-02-08 00:00:33.000000000 
+0100
+++ new/httptools-0.3.0/httptools/__init__.py   2021-08-10 20:09:32.000000000 
+0200
@@ -1,6 +1,6 @@
-from .parser import parser
+from . import parser
 from .parser import *  # NOQA
 
 from ._version import __version__  # NOQA
 
-__all__ = parser.__all__  # NOQA
+__all__ = parser.__all__ + ('__version__',)  # NOQA
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/httptools-0.1.1/httptools/_version.py 
new/httptools-0.3.0/httptools/_version.py
--- old/httptools-0.1.1/httptools/_version.py   2020-02-08 00:00:33.000000000 
+0100
+++ new/httptools-0.3.0/httptools/_version.py   2021-08-10 20:09:32.000000000 
+0200
@@ -10,4 +10,4 @@
 # supported platforms, publish the packages on PyPI, merge the PR
 # to the target branch, create a Git tag pointing to the commit.
 
-__version__ = '0.1.1'
+__version__ = '0.3.0'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/httptools-0.1.1/httptools/parser/__init__.py 
new/httptools-0.3.0/httptools/parser/__init__.py
--- old/httptools-0.1.1/httptools/parser/__init__.py    2020-02-08 
00:00:33.000000000 +0100
+++ new/httptools-0.3.0/httptools/parser/__init__.py    2021-08-10 
20:09:32.000000000 +0200
@@ -1,5 +1,5 @@
-from .parser import *
-from .errors import *
+from .parser import *  # NoQA
+from .errors import *  # NoQA
+from .url_parser import *  # NoQA
 
-
-__all__ = parser.__all__ + errors.__all__
+__all__ = parser.__all__ + errors.__all__ + url_parser.__all__  # NoQA
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/httptools-0.1.1/httptools/parser/cparser.pxd 
new/httptools-0.3.0/httptools/parser/cparser.pxd
--- old/httptools-0.1.1/httptools/parser/cparser.pxd    2020-02-08 
00:00:33.000000000 +0100
+++ new/httptools-0.3.0/httptools/parser/cparser.pxd    2021-08-10 
20:09:32.000000000 +0200
@@ -1,139 +1,156 @@
-from libc.stdint cimport uint16_t, uint32_t, uint64_t
+from libc.stdint cimport int32_t, uint8_t, uint16_t, uint64_t
 
 
-cdef extern from "../../vendor/http-parser/http_parser.h":
-    ctypedef int (*http_data_cb) (http_parser*,
-                                  const char *at,
-                                  size_t length) except -1
-
-    ctypedef int (*http_cb) (http_parser*) except -1
-
-    struct http_parser:
-        unsigned int type
-        unsigned int flags
-        unsigned int state
-        unsigned int header_state
-        unsigned int index
-
-        uint32_t nread
+cdef extern from "llhttp.h":
+    struct llhttp__internal_s:
+        int32_t _index
+        void *_span_pos0
+        void *_span_cb0
+        int32_t error
+        const char *reason
+        const char *error_pos
+        void *data
+        void *_current
         uint64_t content_length
+        uint8_t type
+        uint8_t method
+        uint8_t http_major
+        uint8_t http_minor
+        uint8_t header_state
+        uint16_t flags
+        uint8_t upgrade
+        uint16_t status_code
+        uint8_t finish
+        void *settings
+    ctypedef llhttp__internal_s llhttp__internal_t
+    ctypedef llhttp__internal_t llhttp_t
 
-        unsigned short http_major
-        unsigned short http_minor
-        unsigned int status_code
-        unsigned int method
-        unsigned int http_errno
-
-        unsigned int upgrade
+    ctypedef int (*llhttp_data_cb) (llhttp_t*,
+                                  const char *at,
+                                  size_t length) except -1
 
-        void *data
+    ctypedef int (*llhttp_cb) (llhttp_t*) except -1
 
-    struct http_parser_settings:
-        http_cb      on_message_begin
-        http_data_cb on_url
-        http_data_cb on_status
-        http_data_cb on_header_field
-        http_data_cb on_header_value
-        http_cb      on_headers_complete
-        http_data_cb on_body
-        http_cb      on_message_complete
-        http_cb      on_chunk_header
-        http_cb      on_chunk_complete
+    struct llhttp_settings_s:
+        llhttp_cb      on_message_begin
+        llhttp_data_cb on_url
+        llhttp_data_cb on_status
+        llhttp_data_cb on_header_field
+        llhttp_data_cb on_header_value
+        llhttp_cb      on_headers_complete
+        llhttp_data_cb on_body
+        llhttp_cb      on_message_complete
+        llhttp_cb      on_chunk_header
+        llhttp_cb      on_chunk_complete
+    ctypedef llhttp_settings_s llhttp_settings_t
 
-    enum http_parser_type:
+    enum llhttp_type:
+        HTTP_BOTH,
         HTTP_REQUEST,
-        HTTP_RESPONSE,
-        HTTP_BOTH
+        HTTP_RESPONSE
+    ctypedef llhttp_type llhttp_type_t
 
-    enum http_errno:
+    enum llhttp_errno:
         HPE_OK,
-        HPE_CB_message_begin,
-        HPE_CB_url,
-        HPE_CB_header_field,
-        HPE_CB_header_value,
-        HPE_CB_headers_complete,
-        HPE_CB_body,
-        HPE_CB_message_complete,
-        HPE_CB_status,
-        HPE_CB_chunk_header,
-        HPE_CB_chunk_complete,
-        HPE_INVALID_EOF_STATE,
-        HPE_HEADER_OVERFLOW,
+        HPE_INTERNAL,
+        HPE_STRICT,
+        HPE_LF_EXPECTED,
+        HPE_UNEXPECTED_CONTENT_LENGTH,
         HPE_CLOSED_CONNECTION,
-        HPE_INVALID_VERSION,
-        HPE_INVALID_STATUS,
         HPE_INVALID_METHOD,
         HPE_INVALID_URL,
-        HPE_INVALID_HOST,
-        HPE_INVALID_PORT,
-        HPE_INVALID_PATH,
-        HPE_INVALID_QUERY_STRING,
-        HPE_INVALID_FRAGMENT,
-        HPE_LF_EXPECTED,
+        HPE_INVALID_CONSTANT,
+        HPE_INVALID_VERSION,
         HPE_INVALID_HEADER_TOKEN,
         HPE_INVALID_CONTENT_LENGTH,
         HPE_INVALID_CHUNK_SIZE,
-        HPE_INVALID_CONSTANT,
-        HPE_INVALID_INTERNAL_STATE,
-        HPE_STRICT,
+        HPE_INVALID_STATUS,
+        HPE_INVALID_EOF_STATE,
+        HPE_INVALID_TRANSFER_ENCODING,
+        HPE_CB_MESSAGE_BEGIN,
+        HPE_CB_HEADERS_COMPLETE,
+        HPE_CB_MESSAGE_COMPLETE,
+        HPE_CB_CHUNK_HEADER,
+        HPE_CB_CHUNK_COMPLETE,
         HPE_PAUSED,
-        HPE_UNKNOWN
+        HPE_PAUSED_UPGRADE,
+        HPE_USER
+    ctypedef llhttp_errno llhttp_errno_t
 
-    enum flags:
-        F_CHUNKED,
+    enum llhttp_flags:
         F_CONNECTION_KEEP_ALIVE,
         F_CONNECTION_CLOSE,
         F_CONNECTION_UPGRADE,
-        F_TRAILING,
+        F_CHUNKED,
         F_UPGRADE,
-        F_SKIPBODY
+        F_CONTENT_LENGTH,
+        F_SKIPBODY,
+        F_TRAILING,
+        F_LENIENT,
+        F_TRANSFER_ENCODING
+    ctypedef llhttp_flags llhttp_flags_t
+
+    enum llhttp_method:
+        HTTP_DELETE,
+        HTTP_GET,
+        HTTP_HEAD,
+        HTTP_POST,
+        HTTP_PUT,
+        HTTP_CONNECT,
+        HTTP_OPTIONS,
+        HTTP_TRACE,
+        HTTP_COPY,
+        HTTP_LOCK,
+        HTTP_MKCOL,
+        HTTP_MOVE,
+        HTTP_PROPFIND,
+        HTTP_PROPPATCH,
+        HTTP_SEARCH,
+        HTTP_UNLOCK,
+        HTTP_BIND,
+        HTTP_REBIND,
+        HTTP_UNBIND,
+        HTTP_ACL,
+        HTTP_REPORT,
+        HTTP_MKACTIVITY,
+        HTTP_CHECKOUT,
+        HTTP_MERGE,
+        HTTP_MSEARCH,
+        HTTP_NOTIFY,
+        HTTP_SUBSCRIBE,
+        HTTP_UNSUBSCRIBE,
+        HTTP_PATCH,
+        HTTP_PURGE,
+        HTTP_MKCALENDAR,
+        HTTP_LINK,
+        HTTP_UNLINK,
+        HTTP_SOURCE,
+        HTTP_PRI,
+        HTTP_DESCRIBE,
+        HTTP_ANNOUNCE,
+        HTTP_SETUP,
+        HTTP_PLAY,
+        HTTP_PAUSE,
+        HTTP_TEARDOWN,
+        HTTP_GET_PARAMETER,
+        HTTP_SET_PARAMETER,
+        HTTP_REDIRECT,
+        HTTP_RECORD,
+        HTTP_FLUSH
+    ctypedef llhttp_method llhttp_method_t
+
+    void llhttp_init(llhttp_t* parser, llhttp_type_t type, const 
llhttp_settings_t* settings)
+
+    void llhttp_settings_init(llhttp_settings_t* settings)
+
+    llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t 
len)
+
+    void llhttp_resume_after_upgrade(llhttp_t* parser)
+
+    int llhttp_should_keep_alive(const llhttp_t* parser)
+
+    const char* llhttp_get_error_pos(const llhttp_t* parser)
+    const char* llhttp_get_error_reason(const llhttp_t* parser)
+    const char* llhttp_method_name(llhttp_method_t method)
 
-    enum http_method:
-        DELETE, GET, HEAD, POST, PUT, CONNECT, OPTIONS, TRACE, COPY,
-        LOCK, MKCOL, MOVE, PROPFIND, PROPPATCH, SEARCH, UNLOCK, BIND,
-        REBIND, UNBIND, ACL, REPORT, MKACTIVITY, CHECKOUT, MERGE,
-        MSEARCH, NOTIFY, SUBSCRIBE, UNSUBSCRIBE, PATCH, PURGE, MKCALENDAR,
-        LINK, UNLINK
-
-    void http_parser_init(http_parser *parser, http_parser_type type)
-
-    size_t http_parser_execute(http_parser *parser,
-                               const http_parser_settings *settings,
-                               const char *data,
-                               size_t len)
-
-    int http_should_keep_alive(const http_parser *parser)
-
-    void http_parser_settings_init(http_parser_settings *settings)
-
-    const char *http_errno_name(http_errno err)
-    const char *http_errno_description(http_errno err)
-    const char *http_method_str(http_method m)
-
-    # URL Parser
-
-    enum http_parser_url_fields:
-        UF_SCHEMA   = 0,
-        UF_HOST     = 1,
-        UF_PORT     = 2,
-        UF_PATH     = 3,
-        UF_QUERY    = 4,
-        UF_FRAGMENT = 5,
-        UF_USERINFO = 6,
-        UF_MAX      = 7
-
-    struct http_parser_url_field_data:
-        uint16_t off
-        uint16_t len
-
-    struct http_parser_url:
-        uint16_t field_set
-        uint16_t port
-        http_parser_url_field_data[<int>UF_MAX] field_data
-
-    void http_parser_url_init(http_parser_url *u)
-
-    int http_parser_parse_url(const char *buf,
-                              size_t buflen,
-                              int is_connect,
-                              http_parser_url *u)
+    void llhttp_set_error_reason(llhttp_t* parser, const char* reason);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/httptools-0.1.1/httptools/parser/parser.pyx 
new/httptools-0.3.0/httptools/parser/parser.pyx
--- old/httptools-0.1.1/httptools/parser/parser.pyx     2020-02-08 
00:00:33.000000000 +0100
+++ new/httptools-0.3.0/httptools/parser/parser.pyx     2021-08-10 
20:09:32.000000000 +0200
@@ -19,15 +19,15 @@
 from . cimport cparser
 
 
-__all__ = ('HttpRequestParser', 'HttpResponseParser', 'parse_url')
+__all__ = ('HttpRequestParser', 'HttpResponseParser')
 
 
 @cython.internal
 cdef class HttpParser:
 
     cdef:
-        cparser.http_parser* _cparser
-        cparser.http_parser_settings* _csettings
+        cparser.llhttp_t* _cparser
+        cparser.llhttp_settings_t* _csettings
 
         bytes _current_header_name
         bytes _current_header_value
@@ -42,13 +42,13 @@
         Py_buffer py_buf
 
     def __cinit__(self):
-        self._cparser = <cparser.http_parser*> \
-                                PyMem_Malloc(sizeof(cparser.http_parser))
+        self._cparser = <cparser.llhttp_t*> \
+                                PyMem_Malloc(sizeof(cparser.llhttp_t))
         if self._cparser is NULL:
             raise MemoryError()
 
-        self._csettings = <cparser.http_parser_settings*> \
-                                
PyMem_Malloc(sizeof(cparser.http_parser_settings))
+        self._csettings = <cparser.llhttp_settings_t*> \
+                                PyMem_Malloc(sizeof(cparser.llhttp_settings_t))
         if self._csettings is NULL:
             raise MemoryError()
 
@@ -56,11 +56,11 @@
         PyMem_Free(self._cparser)
         PyMem_Free(self._csettings)
 
-    cdef _init(self, protocol, cparser.http_parser_type mode):
-        cparser.http_parser_init(self._cparser, mode)
-        self._cparser.data = <void*>self
+    cdef _init(self, protocol, cparser.llhttp_type_t mode):
+        cparser.llhttp_settings_init(self._csettings)
 
-        cparser.http_parser_settings_init(self._csettings)
+        cparser.llhttp_init(self._cparser, mode, self._csettings)
+        self._cparser.data = <void*>self
 
         self._current_header_name = None
         self._current_header_value = None
@@ -145,59 +145,72 @@
     ### Public API ###
 
     def get_http_version(self):
-        cdef cparser.http_parser* parser = self._cparser
+        cdef cparser.llhttp_t* parser = self._cparser
         return '{}.{}'.format(parser.http_major, parser.http_minor)
 
     def should_keep_alive(self):
-        return bool(cparser.http_should_keep_alive(self._cparser))
+        return bool(cparser.llhttp_should_keep_alive(self._cparser))
 
     def should_upgrade(self):
-        cdef cparser.http_parser* parser = self._cparser
+        cdef cparser.llhttp_t* parser = self._cparser
         return bool(parser.upgrade)
 
     def feed_data(self, data):
         cdef:
             size_t data_len
-            size_t nb
+            cparser.llhttp_errno_t err
             Py_buffer *buf
+            bint owning_buf = False
+            char* err_pos
 
         if PyMemoryView_Check(data):
             buf = PyMemoryView_GET_BUFFER(data)
             data_len = <size_t>buf.len
-            nb = cparser.http_parser_execute(
+            err = cparser.llhttp_execute(
                 self._cparser,
-                self._csettings,
                 <char*>buf.buf,
                 data_len)
 
         else:
             buf = &self.py_buf
             PyObject_GetBuffer(data, buf, PyBUF_SIMPLE)
+            owning_buf = True
             data_len = <size_t>buf.len
 
-            nb = cparser.http_parser_execute(
+            err = cparser.llhttp_execute(
                 self._cparser,
-                self._csettings,
                 <char*>buf.buf,
                 data_len)
 
-            PyBuffer_Release(buf)
+        try:
+            if self._cparser.upgrade == 1 and err == 
cparser.HPE_PAUSED_UPGRADE:
+                err_pos = cparser.llhttp_get_error_pos(self._cparser)
+
+                # Immediately free the parser from "error" state, simulating
+                # http-parser behavior here because 1) we never had the API to
+                # allow users manually "resume after upgrade", and 2) the use
+                # case for resuming parsing is very rare.
+                cparser.llhttp_resume_after_upgrade(self._cparser)
+
+                # The err_pos here is specific for the input buf. So if we ever
+                # switch to the llhttp behavior (re-raise HttpParserUpgrade for
+                # successive calls to feed_data() until resume_after_upgrade is
+                # called), we have to store the result and keep our own state.
+                raise HttpParserUpgrade(err_pos - <char*>buf.buf)
+        finally:
+            if owning_buf:
+                PyBuffer_Release(buf)
 
-        if self._cparser.http_errno != cparser.HPE_OK:
-            ex =  parser_error_from_errno(
-                <cparser.http_errno> self._cparser.http_errno)
+        if err != cparser.HPE_OK:
+            ex = parser_error_from_errno(
+                self._cparser,
+                <cparser.llhttp_errno_t> self._cparser.error)
             if isinstance(ex, HttpParserCallbackError):
                 if self._last_error is not None:
                     ex.__context__ = self._last_error
                     self._last_error = None
             raise ex
 
-        if self._cparser.upgrade:
-            raise HttpParserUpgrade(nb)
-
-        if nb != data_len:
-            raise HttpParserError('not all of the data was parsed')
-
 
 cdef class HttpRequestParser(HttpParser):
 
@@ -209,8 +222,8 @@
             self._csettings.on_url = cb_on_url
 
     def get_method(self):
-        cdef cparser.http_parser* parser = self._cparser
-        return cparser.http_method_str(<cparser.http_method> parser.method)
+        cdef cparser.llhttp_t* parser = self._cparser
+        return cparser.llhttp_method_name(<cparser.llhttp_method_t> 
parser.method)
 
 
 cdef class HttpResponseParser(HttpParser):
@@ -223,11 +236,11 @@
             self._csettings.on_status = cb_on_status
 
     def get_status_code(self):
-        cdef cparser.http_parser* parser = self._cparser
+        cdef cparser.llhttp_t* parser = self._cparser
         return parser.status_code
 
 
-cdef int cb_on_message_begin(cparser.http_parser* parser) except -1:
+cdef int cb_on_message_begin(cparser.llhttp_t* parser) except -1:
     cdef HttpParser pyparser = <HttpParser>parser.data
     try:
         pyparser._proto_on_message_begin()
@@ -238,55 +251,59 @@
         return 0
 
 
-cdef int cb_on_url(cparser.http_parser* parser,
+cdef int cb_on_url(cparser.llhttp_t* parser,
                    const char *at, size_t length) except -1:
     cdef HttpParser pyparser = <HttpParser>parser.data
     try:
         pyparser._proto_on_url(at[:length])
     except BaseException as ex:
+        cparser.llhttp_set_error_reason(parser, "`on_url` callback error")
         pyparser._last_error = ex
-        return -1
+        return cparser.HPE_USER
     else:
         return 0
 
 
-cdef int cb_on_status(cparser.http_parser* parser,
+cdef int cb_on_status(cparser.llhttp_t* parser,
                       const char *at, size_t length) except -1:
     cdef HttpParser pyparser = <HttpParser>parser.data
     try:
         pyparser._proto_on_status(at[:length])
     except BaseException as ex:
+        cparser.llhttp_set_error_reason(parser, "`on_status` callback error")
         pyparser._last_error = ex
-        return -1
+        return cparser.HPE_USER
     else:
         return 0
 
 
-cdef int cb_on_header_field(cparser.http_parser* parser,
+cdef int cb_on_header_field(cparser.llhttp_t* parser,
                             const char *at, size_t length) except -1:
     cdef HttpParser pyparser = <HttpParser>parser.data
     try:
         pyparser._on_header_field(at[:length])
     except BaseException as ex:
+        cparser.llhttp_set_error_reason(parser, "`on_header_field` callback 
error")
         pyparser._last_error = ex
-        return -1
+        return cparser.HPE_USER
     else:
         return 0
 
 
-cdef int cb_on_header_value(cparser.http_parser* parser,
+cdef int cb_on_header_value(cparser.llhttp_t* parser,
                             const char *at, size_t length) except -1:
     cdef HttpParser pyparser = <HttpParser>parser.data
     try:
         pyparser._on_header_value(at[:length])
     except BaseException as ex:
+        cparser.llhttp_set_error_reason(parser, "`on_header_value` callback 
error")
         pyparser._last_error = ex
-        return -1
+        return cparser.HPE_USER
     else:
         return 0
 
 
-cdef int cb_on_headers_complete(cparser.http_parser* parser) except -1:
+cdef int cb_on_headers_complete(cparser.llhttp_t* parser) except -1:
     cdef HttpParser pyparser = <HttpParser>parser.data
     try:
         pyparser._on_headers_complete()
@@ -300,19 +317,20 @@
             return 0
 
 
-cdef int cb_on_body(cparser.http_parser* parser,
+cdef int cb_on_body(cparser.llhttp_t* parser,
                     const char *at, size_t length) except -1:
     cdef HttpParser pyparser = <HttpParser>parser.data
     try:
         pyparser._proto_on_body(at[:length])
     except BaseException as ex:
+        cparser.llhttp_set_error_reason(parser, "`on_body` callback error")
         pyparser._last_error = ex
-        return -1
+        return cparser.HPE_USER
     else:
         return 0
 
 
-cdef int cb_on_message_complete(cparser.http_parser* parser) except -1:
+cdef int cb_on_message_complete(cparser.llhttp_t* parser) except -1:
     cdef HttpParser pyparser = <HttpParser>parser.data
     try:
         pyparser._proto_on_message_complete()
@@ -323,7 +341,7 @@
         return 0
 
 
-cdef int cb_on_chunk_header(cparser.http_parser* parser) except -1:
+cdef int cb_on_chunk_header(cparser.llhttp_t* parser) except -1:
     cdef HttpParser pyparser = <HttpParser>parser.data
     try:
         pyparser._on_chunk_header()
@@ -334,7 +352,7 @@
         return 0
 
 
-cdef int cb_on_chunk_complete(cparser.http_parser* parser) except -1:
+cdef int cb_on_chunk_complete(cparser.llhttp_t* parser) except -1:
     cdef HttpParser pyparser = <HttpParser>parser.data
     try:
         pyparser._on_chunk_complete()
@@ -345,19 +363,15 @@
         return 0
 
 
-cdef parser_error_from_errno(cparser.http_errno errno):
-    cdef bytes desc = cparser.http_errno_description(errno)
+cdef parser_error_from_errno(cparser.llhttp_t* parser, cparser.llhttp_errno_t 
errno):
+    cdef bytes reason = cparser.llhttp_get_error_reason(parser)
 
-    if errno in (cparser.HPE_CB_message_begin,
-                 cparser.HPE_CB_url,
-                 cparser.HPE_CB_header_field,
-                 cparser.HPE_CB_header_value,
-                 cparser.HPE_CB_headers_complete,
-                 cparser.HPE_CB_body,
-                 cparser.HPE_CB_message_complete,
-                 cparser.HPE_CB_status,
-                 cparser.HPE_CB_chunk_header,
-                 cparser.HPE_CB_chunk_complete):
+    if errno in (cparser.HPE_CB_MESSAGE_BEGIN,
+                 cparser.HPE_CB_HEADERS_COMPLETE,
+                 cparser.HPE_CB_MESSAGE_COMPLETE,
+                 cparser.HPE_CB_CHUNK_HEADER,
+                 cparser.HPE_CB_CHUNK_COMPLETE,
+                 cparser.HPE_USER):
         cls = HttpParserCallbackError
 
     elif errno == cparser.HPE_INVALID_STATUS:
@@ -372,100 +386,4 @@
     else:
         cls = HttpParserError
 
-    return cls(desc.decode('latin-1'))
-
-
-@cython.freelist(250)
-cdef class URL:
-    cdef readonly bytes schema
-    cdef readonly bytes host
-    cdef readonly object port
-    cdef readonly bytes path
-    cdef readonly bytes query
-    cdef readonly bytes fragment
-    cdef readonly bytes userinfo
-
-    def __cinit__(self, bytes schema, bytes host, object port, bytes path,
-                  bytes query, bytes fragment, bytes userinfo):
-
-        self.schema = schema
-        self.host = host
-        self.port = port
-        self.path = path
-        self.query = query
-        self.fragment = fragment
-        self.userinfo = userinfo
-
-    def __repr__(self):
-        return ('<URL schema: {!r}, host: {!r}, port: {!r}, path: {!r}, '
-                'query: {!r}, fragment: {!r}, userinfo: {!r}>'
-                .format(self.schema, self.host, self.port, self.path,
-                    self.query, self.fragment, self.userinfo))
-
-
-def parse_url(url):
-    cdef:
-        Py_buffer py_buf
-        char* buf_data
-        cparser.http_parser_url* parsed
-        int res
-        bytes schema = None
-        bytes host = None
-        object port = None
-        bytes path = None
-        bytes query = None
-        bytes fragment = None
-        bytes userinfo = None
-        object result = None
-        int off
-        int ln
-
-    parsed = <cparser.http_parser_url*> \
-                        PyMem_Malloc(sizeof(cparser.http_parser_url))
-    cparser.http_parser_url_init(parsed)
-
-    PyObject_GetBuffer(url, &py_buf, PyBUF_SIMPLE)
-    try:
-        buf_data = <char*>py_buf.buf
-        res = cparser.http_parser_parse_url(buf_data, py_buf.len, 0, parsed)
-
-        if res == 0:
-            if parsed.field_set & (1 << cparser.UF_SCHEMA):
-                off = parsed.field_data[<int>cparser.UF_SCHEMA].off
-                ln = parsed.field_data[<int>cparser.UF_SCHEMA].len
-                schema = buf_data[off:off+ln]
-
-            if parsed.field_set & (1 << cparser.UF_HOST):
-                off = parsed.field_data[<int>cparser.UF_HOST].off
-                ln = parsed.field_data[<int>cparser.UF_HOST].len
-                host = buf_data[off:off+ln]
-
-            if parsed.field_set & (1 << cparser.UF_PORT):
-                port = parsed.port
-
-            if parsed.field_set & (1 << cparser.UF_PATH):
-                off = parsed.field_data[<int>cparser.UF_PATH].off
-                ln = parsed.field_data[<int>cparser.UF_PATH].len
-                path = buf_data[off:off+ln]
-
-            if parsed.field_set & (1 << cparser.UF_QUERY):
-                off = parsed.field_data[<int>cparser.UF_QUERY].off
-                ln = parsed.field_data[<int>cparser.UF_QUERY].len
-                query = buf_data[off:off+ln]
-
-            if parsed.field_set & (1 << cparser.UF_FRAGMENT):
-                off = parsed.field_data[<int>cparser.UF_FRAGMENT].off
-                ln = parsed.field_data[<int>cparser.UF_FRAGMENT].len
-                fragment = buf_data[off:off+ln]
-
-            if parsed.field_set & (1 << cparser.UF_USERINFO):
-                off = parsed.field_data[<int>cparser.UF_USERINFO].off
-                ln = parsed.field_data[<int>cparser.UF_USERINFO].len
-                userinfo = buf_data[off:off+ln]
-
-            return URL(schema, host, port, path, query, fragment, userinfo)
-        else:
-            raise HttpParserInvalidURLError("invalid url {!r}".format(url))
-    finally:
-        PyBuffer_Release(&py_buf)
-        PyMem_Free(parsed)
+    return cls(reason.decode('latin-1'))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/httptools-0.1.1/httptools/parser/url_cparser.pxd 
new/httptools-0.3.0/httptools/parser/url_cparser.pxd
--- old/httptools-0.1.1/httptools/parser/url_cparser.pxd        1970-01-01 
01:00:00.000000000 +0100
+++ new/httptools-0.3.0/httptools/parser/url_cparser.pxd        2021-08-10 
20:09:32.000000000 +0200
@@ -0,0 +1,31 @@
+from libc.stdint cimport uint16_t
+
+
+cdef extern from "http_parser.h":
+    # URL Parser
+
+    enum http_parser_url_fields:
+        UF_SCHEMA   = 0,
+        UF_HOST     = 1,
+        UF_PORT     = 2,
+        UF_PATH     = 3,
+        UF_QUERY    = 4,
+        UF_FRAGMENT = 5,
+        UF_USERINFO = 6,
+        UF_MAX      = 7
+
+    struct http_parser_url_field_data:
+        uint16_t off
+        uint16_t len
+
+    struct http_parser_url:
+        uint16_t field_set
+        uint16_t port
+        http_parser_url_field_data[<int>UF_MAX] field_data
+
+    void http_parser_url_init(http_parser_url *u)
+
+    int http_parser_parse_url(const char *buf,
+                              size_t buflen,
+                              int is_connect,
+                              http_parser_url *u)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/httptools-0.1.1/httptools/parser/url_parser.pyx 
new/httptools-0.3.0/httptools/parser/url_parser.pyx
--- old/httptools-0.1.1/httptools/parser/url_parser.pyx 1970-01-01 
01:00:00.000000000 +0100
+++ new/httptools-0.3.0/httptools/parser/url_parser.pyx 2021-08-10 
20:09:32.000000000 +0200
@@ -0,0 +1,108 @@
+#cython: language_level=3
+
+from __future__ import print_function
+from cpython.mem cimport PyMem_Malloc, PyMem_Free
+from cpython cimport PyObject_GetBuffer, PyBuffer_Release, PyBUF_SIMPLE, \
+                     Py_buffer
+
+from .errors import HttpParserInvalidURLError
+
+cimport cython
+from . cimport url_cparser as uparser
+
+__all__ = ('parse_url',)
+
+@cython.freelist(250)
+cdef class URL:
+    cdef readonly bytes schema
+    cdef readonly bytes host
+    cdef readonly object port
+    cdef readonly bytes path
+    cdef readonly bytes query
+    cdef readonly bytes fragment
+    cdef readonly bytes userinfo
+
+    def __cinit__(self, bytes schema, bytes host, object port, bytes path,
+                  bytes query, bytes fragment, bytes userinfo):
+
+        self.schema = schema
+        self.host = host
+        self.port = port
+        self.path = path
+        self.query = query
+        self.fragment = fragment
+        self.userinfo = userinfo
+
+    def __repr__(self):
+        return ('<URL schema: {!r}, host: {!r}, port: {!r}, path: {!r}, '
+                'query: {!r}, fragment: {!r}, userinfo: {!r}>'
+                .format(self.schema, self.host, self.port, self.path,
+                    self.query, self.fragment, self.userinfo))
+
+
+def parse_url(url):
+    cdef:
+        Py_buffer py_buf
+        char* buf_data
+        uparser.http_parser_url* parsed
+        int res
+        bytes schema = None
+        bytes host = None
+        object port = None
+        bytes path = None
+        bytes query = None
+        bytes fragment = None
+        bytes userinfo = None
+        object result = None
+        int off
+        int ln
+
+    parsed = <uparser.http_parser_url*> \
+                        PyMem_Malloc(sizeof(uparser.http_parser_url))
+    uparser.http_parser_url_init(parsed)
+
+    PyObject_GetBuffer(url, &py_buf, PyBUF_SIMPLE)
+    try:
+        buf_data = <char*>py_buf.buf
+        res = uparser.http_parser_parse_url(buf_data, py_buf.len, 0, parsed)
+
+        if res == 0:
+            if parsed.field_set & (1 << uparser.UF_SCHEMA):
+                off = parsed.field_data[<int>uparser.UF_SCHEMA].off
+                ln = parsed.field_data[<int>uparser.UF_SCHEMA].len
+                schema = buf_data[off:off+ln]
+
+            if parsed.field_set & (1 << uparser.UF_HOST):
+                off = parsed.field_data[<int>uparser.UF_HOST].off
+                ln = parsed.field_data[<int>uparser.UF_HOST].len
+                host = buf_data[off:off+ln]
+
+            if parsed.field_set & (1 << uparser.UF_PORT):
+                port = parsed.port
+
+            if parsed.field_set & (1 << uparser.UF_PATH):
+                off = parsed.field_data[<int>uparser.UF_PATH].off
+                ln = parsed.field_data[<int>uparser.UF_PATH].len
+                path = buf_data[off:off+ln]
+
+            if parsed.field_set & (1 << uparser.UF_QUERY):
+                off = parsed.field_data[<int>uparser.UF_QUERY].off
+                ln = parsed.field_data[<int>uparser.UF_QUERY].len
+                query = buf_data[off:off+ln]
+
+            if parsed.field_set & (1 << uparser.UF_FRAGMENT):
+                off = parsed.field_data[<int>uparser.UF_FRAGMENT].off
+                ln = parsed.field_data[<int>uparser.UF_FRAGMENT].len
+                fragment = buf_data[off:off+ln]
+
+            if parsed.field_set & (1 << uparser.UF_USERINFO):
+                off = parsed.field_data[<int>uparser.UF_USERINFO].off
+                ln = parsed.field_data[<int>uparser.UF_USERINFO].len
+                userinfo = buf_data[off:off+ln]
+
+            return URL(schema, host, port, path, query, fragment, userinfo)
+        else:
+            raise HttpParserInvalidURLError("invalid url {!r}".format(url))
+    finally:
+        PyBuffer_Release(&py_buf)
+        PyMem_Free(parsed)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/httptools-0.1.1/pytest.ini 
new/httptools-0.3.0/pytest.ini
--- old/httptools-0.1.1/pytest.ini      2020-02-08 00:00:33.000000000 +0100
+++ new/httptools-0.3.0/pytest.ini      2021-08-10 20:09:32.000000000 +0200
@@ -1,3 +1,3 @@
 [pytest]
-addopts = --capture=no --assert=plain --strict --tb native
+addopts = --capture=no --assert=plain --strict-markers --tb=native 
--import-mode=importlib
 testpaths = tests
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/httptools-0.1.1/setup.py new/httptools-0.3.0/setup.py
--- old/httptools-0.1.1/setup.py        2020-02-08 00:00:33.000000000 +0100
+++ new/httptools-0.3.0/setup.py        2021-08-10 20:09:32.000000000 +0200
@@ -15,7 +15,7 @@
 
 ROOT = pathlib.Path(__file__).parent
 
-CYTHON_DEPENDENCY = 'Cython==0.29.14'
+CYTHON_DEPENDENCY = 'Cython(>=0.29.24,<0.30.0)'
 
 
 class httptools_build_ext(build_ext):
@@ -26,6 +26,8 @@
             'Produce a colorized HTML version of the Cython source.'),
         ('cython-directives=', None,
             'Cythion compiler directives'),
+        ('use-system-llhttp', None,
+            'Use the system provided llhttp, instead of the bundled one'),
         ('use-system-http-parser', None,
             'Use the system provided http-parser, instead of the bundled one'),
     ]
@@ -33,6 +35,7 @@
     boolean_options = build_ext.boolean_options + [
         'cython-always',
         'cython-annotate',
+        'use-system-llhttp',
         'use-system-http-parser',
     ]
 
@@ -44,6 +47,7 @@
             return
 
         super().initialize_options()
+        self.use_system_llhttp = False
         self.use_system_http_parser = False
         self.cython_always = False
         self.cython_annotate = None
@@ -108,16 +112,34 @@
         self._initialized = True
 
     def build_extensions(self):
+        mod_parser, mod_url_parser = self.distribution.ext_modules
+        if self.use_system_llhttp:
+            mod_parser.libraries.append('llhttp')
+
+            if sys.platform == 'darwin' and \
+                    os.path.exists('/opt/local/include'):
+                # Support macports on Mac OS X.
+                mod_parser.include_dirs.append('/opt/local/include')
+        else:
+            mod_parser.include_dirs.append(
+                str(ROOT / 'vendor' / 'llhttp' / 'include'))
+            mod_parser.include_dirs.append(
+                str(ROOT / 'vendor' / 'llhttp' / 'src'))
+            mod_parser.sources.append('vendor/llhttp/src/api.c')
+            mod_parser.sources.append('vendor/llhttp/src/http.c')
+            mod_parser.sources.append('vendor/llhttp/src/llhttp.c')
+
         if self.use_system_http_parser:
-            self.compiler.add_library('http_parser')
+            mod_url_parser.libraries.append('http_parser')
 
             if sys.platform == 'darwin' and \
                     os.path.exists('/opt/local/include'):
                 # Support macports on Mac OS X.
-                self.compiler.add_include_dir('/opt/local/include')
+                mod_url_parser.include_dirs.append('/opt/local/include')
         else:
-            self.compiler.add_include_dir(str(ROOT / 'vendor' / 'http-parser'))
-            self.distribution.ext_modules[0].sources.append(
+            mod_url_parser.include_dirs.append(
+                str(ROOT / 'vendor' / 'http-parser'))
+            mod_url_parser.sources.append(
                 'vendor/http-parser/http_parser.c')
 
         super().build_extensions()
@@ -163,6 +185,7 @@
         'Development Status :: 5 - Production/Stable',
     ],
     platforms=['macOS', 'POSIX', 'Windows'],
+    python_requires='>=3.5.0',
     zip_safe=False,
     author='Yury Selivanov',
     author_email='y...@magic.io',
@@ -179,6 +202,13 @@
             ],
             extra_compile_args=CFLAGS,
         ),
+        Extension(
+            "httptools.parser.url_parser",
+            sources=[
+                "httptools/parser/url_parser.pyx",
+            ],
+            extra_compile_args=CFLAGS,
+        ),
     ],
     include_package_data=True,
     test_suite='tests.suite',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/httptools-0.1.1/tests/test_parser.py 
new/httptools-0.3.0/tests/test_parser.py
--- old/httptools-0.1.1/tests/test_parser.py    2020-02-08 00:00:33.000000000 
+0100
+++ new/httptools-0.3.0/tests/test_parser.py    2021-08-10 20:09:32.000000000 
+0200
@@ -103,7 +103,7 @@
 
         with self.assertRaisesRegex(
                 httptools.HttpParserError,
-                'data received after completed connection'):
+                'Expected HTTP/'):
             p.feed_data(b'12123123')
 
     def test_parser_response_2(self):
@@ -117,7 +117,7 @@
         for cbname in callbacks:
             with self.subTest('{} callback fails correctly'.format(cbname)):
                 with self.assertRaisesRegex(httptools.HttpParserCallbackError,
-                                            'callback failed'):
+                                            'callback error'):
 
                     m = mock.Mock()
                     getattr(m, cbname).side_effect = Exception()
@@ -225,7 +225,6 @@
         p = httptools.HttpRequestParser(m)
 
         p.feed_data(CHUNKED_REQUEST1_1)
-
         self.assertEqual(p.get_method(), b'POST')
 
         m.on_message_begin.assert_called_once_with()
@@ -348,6 +347,11 @@
             b'Host': b'example.com',
             b'Upgrade': b'WebSocket'})
 
+        # The parser can be used again for further parsing - this is a legacy
+        # behavior from the time we were still using http-parser.
+        p.feed_data(CHUNKED_REQUEST1_1)
+        self.assertEqual(p.get_method(), b'POST')
+
     def test_parser_request_upgrade_flag(self):
 
         class Protocol:

Reply via email to