python-hyper / hyper

HTTP/2 for Python.
http://hyper.rtfd.org/en/latest/
MIT License
1.05k stars 191 forks source link

No POST data send with HTTP/2 client (no Content-Length header) #320

Closed michal-niklas closed 7 years ago

michal-niklas commented 7 years ago

I have build simple HTTP, HTTPS and HTTP/2 client based on Python hyper library. When I test it on my Jetty based WebService I see that hyper HTTP/2 client do not send POST data like HTTP and HTTPS.

My code looks like:

#!/usr/bin/env python3
# -*- coding: utf8 -*-

PY3 = 0
try:
    import urllib.parse
    PY3 = 1
except:
    import urlparse
import os.path
import ssl
import sys
import traceback

import hyper

DEFAULT_CA_FILE = '/etc/heuthes/ca.crt'

TEST_URLS = """
http://127.0.0.1:8765/isof/echo.hdb
https://stresstest.heuthesd:18443/isof/echo.hdb
"""

def http2_simply_get(url, ssl_context=None):
    try:
        parsed = urllib.parse.urlparse(url)
    except:
        parsed = urlparse.urlparse(url)
    secure = url.startswith('https')
    if secure:
        if not ssl_context:
            if os.path.isfile(DEFAULT_CA_FILE):
                ssl_context = ssl.create_default_context(cafile=DEFAULT_CA_FILE)
            else:
                ssl_context = ssl.create_default_context()
        # comment next line if you want to test only HTTPS
        # unocmment next line if you want to test HTTP/2
        ssl_context.set_alpn_protocols(["h2", "http/1.1"])
    conn = hyper.HTTPConnection(parsed.netloc, secure=secure, ssl_context=ssl_context)
    path_and_qry = parsed.path
    p = url.find(parsed.path)
    if p >= 0:
        path_and_qry = url[p:]
    param_value = 'zorro'
    if PY3:
        body = bytes('param=%s' % (param_value), 'UTF-8')
    else:
        body = 'param=%s' % (param_value)
    conn.request('POST', path_and_qry, body=body)
    result = conn.get_response().read().decode('UTF-8')
    if '<param>%s</param>' % (param_value) in result:
        print('test ok')
    else:
        print('\n\ntest ERROR!!! no param value in response!!!\n\n')
    return result

def test():
    for url in TEST_URLS.split('\n'):
        url = url.strip()
        if url and not url.startswith('#'):
            print('url: [%s]' % (url))
            try:
                http2_response = http2_simply_get(url)
                print(http2_response)
            except Exception as e:
                s = traceback.format_exc()
                print('\n!!!\n!!! Exception: [%s]\n%s\n' % (e, s))
            print('\n--------\n\n\n')

if __name__ == '__main__':
    if '--test' in sys.argv:
        test()

Of course you have to add your own WebService url and CA file.

To test it with HTTP/2 you must uncomment line:

ssl_context.set_alpn_protocols(["h2", "http/1.1"])

To test it with HTTPS you must comment it.

My results:

url: [http://127.0.0.1:8765/isof/echo.hdb]
test ok
<?xml version="1.0" encoding="UTF-8"?>
<info>
<param>zorro</param>
</info>
--------

url: [https://stresstest.heuthesd:18443/isof/echo.hdb]

test ERROR!!! no param value in response!!!

<?xml version="1.0" encoding="UTF-8"?>
<info>
<param></param>
</info>
--------

Of course my WebService works well with Chrome and Firefox using HTTP/2.

I have found similar problem: https://github.com/Lukasa/hyper/issues/206

When I use HTTP/2 client it probably do not send Content-Length header. With other clients I receive: http_content_length=11

I test it with hyper 0.7 on Fedora Linux with Python 2.7.13 and Python 3.5.3.

Lukasa commented 7 years ago

Can you confirm that Jetty requires a content-length by manually setting the content-length header yourself in the request to see if that works?

michal-niklas commented 7 years ago

Yes, when I add that header:

conn.request('POST', path_and_qry, body=body, headers={'content-length': '11'})

I got response with data read from the query body.

Lukasa commented 7 years ago

Ok, so this is a duplicate of #206. If you'd like to pick up the patch from that issue and resolve my concerns with it from that bug report, I'll happily review that fix. =)