HTTPS Request mit "unendlicher" Antwort

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
McChaos
User
Beiträge: 7
Registriert: Freitag 8. März 2019, 17:42

Guten Abend,

ich versuche seit einigen Tagen, das Fernsteuerinterface einer Behnke Türstation via Python zu programmieren.

Code: Alles auswählen

import requests
import time

URL = "https://tuerstation.ct1n.de:8000/remote.cgi"

session = requests.session()

req = session.get(URL, verify=False, auth=('xx', 'yy'), stream=True)

if req.status_code != requests.codes.ok:
    print("HTTPS Statuscode : ", r.status_code, "; Exit.")
    exit(1)

try:
    while True:
        print(req.text)
        time.sleep(1)

except KeyboardInterrupt:
    pass

except requests.exceptions.StreamConsumedError:
    pass
Als Ausgabe bekomme ich allerdings nur:

Code: Alles auswählen

--specialBehnkeBoun
--specialBehnkeBoun
--specialBehnkeBoun
....
Im Wireshark kann ich allerdings sehen, dass deutlich mehr gesendet wird:

Code: Alles auswählen

GET /remote.cgi HTTP/1.1
Host: tuerstation.ct1n.de:8000
User-Agent: python-requests/2.21.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Authorization: Basic xx

HTTP/1.1 200 OK
Server: Behnke/1.0
Content-Length: 19
Connection: close
Cache-Control: no-cache,no-store
Content-Type: multipart/x-mixed-replace;boundary=specialBehnkeBoundary

--specialBehnkeBoundary
Content-type: text/plain

Welcome

--specialBehnkeBoundary
Content-type: text/plain

call ended 1

--specialBehnkeBoundary
Content-type: text/plain

call ended 1

--specialBehnkeBoundary
Content-type: text/plain

call ended 1

--specialBehnkeBoundary
Content-type: text/plain

call ended 1

--specialBehnkeBoundary
und danach wird dieser Block alle paar Sekunden wiederholt:

Code: Alles auswählen

Content-type: text/plain

call ended 1

--specialBehnkeBoundary
Wie kann ich die Ausgabe auslesen?

Vielen Dank schon mal!

Gruß, Thomas
__deets__
User
Beiträge: 14525
Registriert: Mittwoch 14. Oktober 2015, 14:29

McChaos
User
Beiträge: 7
Registriert: Freitag 8. März 2019, 17:42

Hatte ich auch schon versucht:

Code: Alles auswählen

...
try:
    while True:
        for line in req.iter_lines():
            print(line)
        time.sleep(1)

except KeyboardInterrupt:
    pass

Code: Alles auswählen

b'--specialBehnkeBoun'
Traceback (most recent call last):
  File "/Users/thomas/Ablage/Projekte/Python/Behnke_remote_control/Behnke_SSL_1.py", line 16, in <module>
    for line in req.iter_lines():
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/requests/models.py", line 794, in iter_lines
    for chunk in self.iter_content(chunk_size=chunk_size, decode_unicode=decode_unicode):
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/requests/models.py", line 769, in iter_content
    raise StreamConsumedError()
requests.exceptions.StreamConsumedError

Process finished with exit code 1
Im http header wird eine Content-Length: 19 gesendet und offenbar werden auch nur diese 19 Bytes gelesen..
Benutzeravatar
sls
User
Beiträge: 480
Registriert: Mittwoch 13. Mai 2015, 23:52
Wohnort: Country country = new Zealand();

Lass die while-Schleife weg.
When we say computer, we mean the electronic computer.
__deets__
User
Beiträge: 14525
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das mit den 19 Bytes finde ich auch ziemlich verwunderlich. Mein HTTP Protokollwissen ist etwas rostig, aber das sieht falsch aus.

Gibt es denn andere Beispiele ggf in anderen sprachen die zeigen, wie mit dem Gerät zu reden ist?
McChaos
User
Beiträge: 7
Registriert: Freitag 8. März 2019, 17:42

Guten Morgen,

es gibt ein Beispiel in Delphi. Darin wird das alles zu Fuß gemacht - ich dachte ich könnte die requests Bibliothek dafür nutzen, ums eben nicht zu Fuß machen zu müssen ;-)

Abgesehen davon, dass content-length offenbar falsch gesetzt ist, bin ich mir auch nicht sicher, ob requests mit "multipart/x-mixed-replace;boundary=specialBehnkeBoundary" korrekt umgehen kann.

Anyway, ich habs jetzt eine Ebene tiefer mit socket & ssl zu Fuß gelöst und es spricht erst mal korrekt mit mir :-)

Code: Alles auswählen

import socket, ssl
from time import sleep
from base64 import b64encode

HOST, PORT = 'tuerstation.ct1n.de', 8000
USER = 'xx'
PASSWORD = 'xx'

def main():
    sock = socket.socket(socket.AF_INET)
    context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
    context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1  # optional
    context.check_hostname = False
    context.verify_mode = ssl.CERT_NONE

    conn = context.wrap_socket(sock, server_hostname=HOST)

    try:
        conn.connect((HOST, PORT))
        conn.write(b'GET /remote.cgi HTTP/1.1\n')

        user_pass_b64 = b64encode(str.encode(USER + ':' + PASSWORD))
        conn.write(str.encode('Authorization: Basic %s \n' % user_pass_b64.decode('ISO-8859-1')))
        conn.write(b'\n')

        while True:
            data = conn.recv().decode()
            for line in data.splitlines():
                if line:
                    print(line)
            sleep(1)
    finally:
        conn.close()

if __name__ == '__main__':
    main()
    
Vielen Dank!

Gruß, Thomas
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

@McChaos: Wie requests mit kaputten Multipart-Antworten umgeht, kann ich gerade nicht testen, da müßte ich erst einen entsprechenden Server schreiben.

requests ist nicht umsonst ein recht umfangreiches Paket. da jetzt händisch was zu schreiben, ist nicht gut, weil es viele Stolperstellen gibt. Wenn überhaupt, funktioniert Dein Code nur mit viel Glück. `conn.write` sollte man nicht mehr benutzen. `conn.sendall` wäre die Methode. String-Objekte haben eine encode-Methode, die man nicht über die Klasse aufrufen sollte. Wenn Du schon einen Byte-String hast, ist es auch umständlich, den erst hin und wieder her zu encoden.
HTTP erwartet \r\n als Zeilenabschluß. Die while-Schleife mit recv ist kaputt, da ersten recv eine beliebige Anzahl Bytes liefert, die zum einen unvollständige Zeilen sein können und zum Zweiten auch unvollständige UTF8-Squenzen, so dass Dein decode mit einem Fehler aussteigt. Zudem ignorierst Du das Encoding, das der Server vielleicht angibt.
Das sleep ist dann auch unsinnig, weil recv wartet.
Besser wäre es mit conn.makefile Dir File-Objekte geben zu lassen, mit denen es sich bequem arbeiten läßt, noch besser, gar nicht erst versuchen, HTTP per Hand zu schreiben und zu lesen.
Benutzeravatar
sls
User
Beiträge: 480
Registriert: Mittwoch 13. Mai 2015, 23:52
Wohnort: Country country = new Zealand();

@MrChaos: es macht keinen Sinn den StreamConsumedError abzufangen und damit dann genau nichts zu machen in der Hoffnung es geht schon irgendwie weiter. Wenn du das flag stream=True setzt und über die Ausgabe iterierst macht es immer noch kein Sinn das mit einer while-Loop zu wrappen. Die genannte Exception konnte ich so nachstellen. Möglicherweise genau dein Problem.
When we say computer, we mean the electronic computer.
McChaos
User
Beiträge: 7
Registriert: Freitag 8. März 2019, 17:42

Hallo,

erst mal vielen Dank für Eure Hinweise.
requests ist nicht umsonst ein recht umfangreiches Paket. da jetzt händisch was zu schreiben, ist nicht gut, weil es viele Stolperstellen gibt.
Das ist mir schon klar. Leider konnte ich requests nicht zur Zusammenarbeit bewegen und den Server kann ich auch nicht anpassen... :?
String-Objekte haben eine encode-Methode, die man nicht über die Klasse aufrufen sollte. Wenn Du schon einen Byte-String hast, ist es auch umständlich, den erst hin und wieder her zu encoden.
Ich kann Dir leider nicht folgen... Könntest Du mir bitte genauer erklären, was ich verbessern sollte?

Vielen Dank!
Gruß, Thomas
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

Gemeint ist dass `user_pass_b64` ein Bytes-Objekt ist und das erst nach Unicode zu decodieren um es gleich wieder in Bytes zu encodieren, umständlich ist.
Hier:

Code: Alles auswählen

        user_pass_b64 = b64encode(f"{USER}:{PASSWORD}".encode())
        conn.sendall(b'Authorization: Basic %s \n' % user_pass_b64)
Aber bevor man auf dieser Ebene arbeitet, gibt es auch noch urllib

Code: Alles auswählen

request = urllib.request.Request(URL)
request.add_header("Authorization", b"Basic %s" % b64encode(f"{USER}:{PASSWORD}".encode()))
with urllib.request.urlopen(request) as response:
    for line in response:
        print(line)
Antworten