cth35 / tydom_python

Example of Python Code to manage Tydom (Delta Dore) devices - Need Tydom Gateway
Apache License 2.0
31 stars 5 forks source link

websockets.exceptions.InvalidHandshake: Malformed HTTP message #1

Closed makinapower closed 5 years ago

makinapower commented 5 years ago

Hello and thanks for your work. I can not get the script working I am under debian 9 and I installed the package python3-websockets and uses python 3.5. I checked the credential, everything seems ok...

Thanks.

makina@JEEDOM:~/tydom_python$` python3.5 main.py

send: b'GET /mediation/client?mac=001122334455&appli=1 HTTP/1.1\r\nAccept-Encoding: identity\r\nSec-WebSocket-Version: 13\r\nUpgrade: websocket\r\nSec-WebSocket-Key: szgD00Pl4jejePGM8N4DAA==\r\nHost: mediation.tydom.com:443\r\nConnection: Upgrade\r\nAccept: /\r\n\r\n' reply: 'HTTP/1.1 401 \r\n' header: X-Content-Type-Options header: X-XSS-Protection header: Cache-Control header: Pragma header: Expires header: X-Frame-Options header: Set-Cookie header: WWW-Authenticate header: Content-Length header: Date Traceback (most recent call last): File "/usr/lib/python3/dist-packages/websockets/client.py", line 79, in handshake status_code, headers = yield from read_response(self.reader) File "/usr/lib/python3/dist-packages/websockets/http.py", line 66, in read_response version, status, reason = status_line[:-2].decode().split(None, 2) ValueError: not enough values to unpack (expected 3, got 2)

The above exception was the direct cause of the following exception:

Traceback (most recent call last): File "main.py", line 172, in asyncio.get_event_loop().run_until_complete(hello()) File "/usr/lib/python3.5/asyncio/base_events.py", line 466, in run_until_complete return future.result() File "/usr/lib/python3.5/asyncio/futures.py", line 293, in result raise self._exception File "/usr/lib/python3.5/asyncio/tasks.py", line 239, in _step result = coro.send(None) File "main.py", line 164, in hello async with websockets.client.connect('wss://mediation.tydom.com:443/mediation/client?mac={}&appli=1'.format(mac), extra_headers=websocketHeaders) as websocket: File "/usr/lib/python3/dist-packages/websockets/py35/client.py", line 12, in aenter self.websocket = await self File "/usr/lib/python3/dist-packages/websockets/py35/client.py", line 19, in await return (yield from self.client) File "/usr/lib/python3/dist-packages/websockets/client.py", line 165, in connect extra_headers=extra_headers) File "/usr/lib/python3/dist-packages/websockets/client.py", line 81, in handshake raise InvalidHandshake("Malformed HTTP message") from exc websockets.exceptions.InvalidHandshake: Malformed HTTP message

cth35 commented 5 years ago

It seems that you have an incorrect response from the server and HTTP parser of websockets fails on it. Can you put a print in the http.py code of websockets :

print(status_line)
version, status_code, reason = status_line[:-2].split(b' ', 2)

around line 125 And give me the output if there is not sensitive data.

makinapower commented 5 years ago

I do not think we have the same http.py My http.py file is located in /usr/lib/python3/dist-packages/websockets/

I do not see where to place the print ` """ The :mod:websockets.http` module provides HTTP parsing functions. They're merely adequate for the WebSocket handshake messages.

These functions cannot be imported from :mod:websockets; they must be imported from :mod:websockets.http.

"""

import asyncio import email.parser import io import sys

from .version import version as websockets_version

all = ['read_request', 'read_response', 'USER_AGENT']

MAX_HEADERS = 256 MAX_LINE = 4096

USER_AGENT = ' '.join(( 'Python/{}'.format(sys.version[:3]), 'websockets/{}'.format(websockets_version), ))

@asyncio.coroutine def read_request(stream): """ Read an HTTP/1.1 request from stream.

Return ``(path, headers)`` where ``path`` is a :class:`str` and
``headers`` is a :class:`~email.message.Message`. ``path`` isn't
URL-decoded.

Raise an exception if the request isn't well formatted.

The request is assumed not to contain a body.

"""
request_line, headers = yield from read_message(stream)
method, path, version = request_line[:-2].decode().split(None, 2)
if method != 'GET':
    raise ValueError("Unsupported method")
if version != 'HTTP/1.1':
    raise ValueError("Unsupported HTTP version")
return path, headers

@asyncio.coroutine def read_response(stream): """ Read an HTTP/1.1 response from stream.

Return ``(status, headers)`` where ``status`` is a :class:`int` and
``headers`` is a :class:`~email.message.Message`.

Raise an exception if the request isn't well formatted.

The response is assumed not to contain a body.

"""
status_line, headers = yield from read_message(stream)
version, status, reason = status_line[:-2].decode().split(None, 2)
if version != 'HTTP/1.1':
    raise ValueError("Unsupported HTTP version")
return int(status), headers

@asyncio.coroutine def read_message(stream): """ Read an HTTP message from stream.

Return ``(start_line, headers)`` where ``start_line`` is :class:`bytes`
and ``headers`` is a :class:`~email.message.Message`.

The message is assumed not to contain a body.

"""
start_line = yield from read_line(stream)
header_lines = io.BytesIO()
for num in range(MAX_HEADERS):
    header_line = yield from read_line(stream)
    header_lines.write(header_line)
    if header_line == b'\r\n':
        break
else:
    raise ValueError("Too many headers")
header_lines.seek(0)
headers = email.parser.BytesHeaderParser().parse(header_lines)
return start_line, headers

@asyncio.coroutine def read_line(stream): """ Read a single line from stream.

"""
line = yield from stream.readline()
if len(line) > MAX_LINE:
    raise ValueError("Line too long")
if not line.endswith(b'\r\n'):
    raise ValueError("Line without CRLF")
return line

`

cth35 commented 5 years ago

yes it's the same. Put the print(status_line) in function read_response before version, status, reason = status_line[:-2].decode().split(None, 2)

makinapower commented 5 years ago

Ok Thanks, here is the return: send: b'GET /mediation/client?mac=001133445566&appli=1 HTTP/1.1\r\nAccept-Encoding: identity\r\nHost: mediation.tydom.com:443\r\nSec-WebSocket-Version: 13\r\nUpgrade: websocket\r\nAccept: /\r\nConnection: Upgrade\r\nSec-WebSocket-Key: NAnGaovSf7qCLcGM/GL48A==\r\n\r\n' reply: 'HTTP/1.1 401 \r\n' header: X-Content-Type-Options header: X-XSS-Protection header: Cache-Control header: Pragma header: Expires header: X-Frame-Options header: Set-Cookie header: WWW-Authenticate header: Content-Length header: Date b'HTTP/1.1 101 \r\n' Traceback (most recent call last): File "/usr/lib/python3/dist-packages/websockets/client.py", line 79, in handshake status_code, headers = yield from read_response(self.reader) File "/usr/lib/python3/dist-packages/websockets/http.py", line 67, in read_response version, status, reason = status_line[:-2].decode().split(None, 2) ValueError: not enough values to unpack (expected 3, got 2)

The above exception was the direct cause of the following exception:

Traceback (most recent call last): File "main.py", line 172, in asyncio.get_event_loop().run_until_complete(hello()) File "/usr/lib/python3.5/asyncio/base_events.py", line 466, in run_until_complete return future.result() File "/usr/lib/python3.5/asyncio/futures.py", line 293, in result raise self._exception File "/usr/lib/python3.5/asyncio/tasks.py", line 239, in _step result = coro.send(None) File "main.py", line 164, in hello async with websockets.client.connect('wss://mediation.tydom.com:443/mediation/client?mac={}&appli=1'.format(mac), extra_headers=websocketHeaders) as websocket: File "/usr/lib/python3/dist-packages/websockets/py35/client.py", line 12, in aenter self.websocket = await self File "/usr/lib/python3/dist-packages/websockets/py35/client.py", line 19, in await return (yield from self.client) File "/usr/lib/python3/dist-packages/websockets/client.py", line 165, in connect extra_headers=extra_headers) File "/usr/lib/python3/dist-packages/websockets/client.py", line 81, in handshake raise InvalidHandshake("Malformed HTTP message") from exc websockets.exceptions.InvalidHandshake: Malformed HTTP message

cth35 commented 5 years ago

it's stange, it seems status_line[:-2].decode().split(None, 2) returns 2 elements instead of 3 But you have the correct printed value b'HTTP/1.1 101 \r\n' Which version of websockets do you have ? I've got 7.0

cth35 commented 5 years ago

I got it : in http.py change status_line[:-2].decode().split(None, 2) with status_line[:-2].split(b' ', 2) Seems that your websockets version is buggy. See : https://github.com/aaugustin/websockets/commit/2e7fd48e19731aa1fe8e5af57825b5a7f43a972c So try to update you websockets module to last version if possible.

makinapower commented 5 years ago

Thanks cth35 My version of websockets is 3.2 (installed directly from Debian package) and I have another error with status_line[:-2].split(b' ', 2) I will try the update the websockets module for python 3 and will keep you informed.

send: b'GET /mediation/client?Mac=001122334455&appli=1 HTTP/1.1\r\nAccept-Encoding: identity\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: UuLApq3Ah2yAp09Sv7Pu+A==\r\nHost: mediation.tydom.com:443\r\nConnection: Upgrade\r\nAccept: /\r\nUpgrade: websocket\r\n\r\n' reply: 'HTTP/1.1 401 \r\n' header: X-Content-Type-Options header: X-XSS-Protection header: Cache-Control header: Pragma header: Expires header: X-Frame-Options header: Set-Cookie header: WWW-Authenticate header: Content-Length header: Date b'HTTP/1.1 101 \r\n' Traceback (most recent call last): File "/usr/lib/python3/dist-packages/websockets/client.py", line 79, in handshake status_code, headers = yield from read_response(self.reader) File "/usr/lib/python3/dist-packages/websockets/http.py", line 70, in read_response raise ValueError("Unsupported HTTP version") ValueError: Unsupported HTTP version

makinapower commented 5 years ago

Well done ! I have remove the debian package installed the websockets module via pip3:

pip3 install websockets. and It works ! Good Jobs and thanks again !

cth35 commented 5 years ago

Cool ! You're welcome. If you develop on this code feel free to commit or to share what you did.