[TCP / IP] Versuchen Sie nach dem Studium, mit Python einen HTTP-Client zu erstellen

Vor kurzem habe ich einen etwas ungewöhnlichen Mechanismus bei der Arbeit entwickelt, bei dem mithilfe der Socket-Kommunikation Daten von einem PC mithilfe eines Android-Terminals als Server gesendet werden.

Bisher war mir "HTTP (S)" nur bekannt, wenn ich über "Kommunikation" sprach, aber ich wollte diese Gelegenheit nutzen, um mehr über Kommunikation zu erfahren, also vorerst "TCP / IP" Ich lerne an einem Ort wie "ha".

Im Rahmen dieser Studie habe ich versucht, einen HTTP-Client mithilfe der Socket-Kommunikation zu implementieren. Daher möchte ich dessen Inhalt vorstellen. Die Sprache ist Python.

davor

Bitte lesen Sie vorerst die folgende Seite, wenn Sie überhaupt an Socket-Kommunikation interessiert sind.

Socket-Programmierung HOWTO | Python

Wie auf dieser Seite beschrieben, wird in diesem Artikel davon ausgegangen, dass "Socket-Kommunikation vorerst TCP ist".

Außerdem werde ich kurz zusammenfassen, was ich aus dem Studium gelernt habe.

Eine kurze Beschreibung der TCP- und HTTP-Protokolle

Protokoll

Vor der Erläuterung von TCP und HTTP werde ich kurz auf das Wort "Protokoll" eingehen. (Weil ich es nicht gut verstanden habe)

Das Protokoll lautet laut Dr. Eijiro "Regeln zum Senden und Empfangen von Daten zwischen Computern".

Auf der ganzen Welt existierende Kommunikationsgeräte und die darin laufende Software (einschließlich des Betriebssystems) werden natürlich von verschiedenen Unternehmen und Personen hergestellt und entwickelt. Und jedes Gerät und jede Software wird hergestellt und entwickelt, ohne dass die Spezifikationen miteinander übereinstimmen.

Selbst wenn in einer solchen Situation "dann lassen Sie uns Daten zwischen Maschinen auf der ganzen Welt austauschen", wenn es keine gemeinsame Spezifikation gibt, "welche Art von Maschine" "wie Funkwellen gesendet werden" " Ich kann meine Hand nicht bewegen, ohne die für die Implementierung erforderlichen Informationen zu erhalten, z. B. "Welche Daten repräsentiert die Funkwelle?"

Hier wurde die "Regel" namens "TCP / IP" geboren. Solange es gemäß den in TCP / IP beschriebenen Regeln entwickelt wurde, ist es möglich, Daten zu senden und zu empfangen, ohne mit jedem Unternehmen Besprechungen abhalten zu müssen.

Und diese "Regel" wird in IT-Begriffen als "Protokoll" bezeichnet.

Wie haben Sie übrigens ein solches universelles Protokoll erstellt und verbreitet? !! Ich werde die Frage weglassen, weil sie lang sein wird. Es war leicht zu verstehen, als ich es zusammen mit dem Begriff "OSI-Referenzmodell" nachgeschlagen habe.

TCP und HTTP

TCP / IP ist eine Sammlung individueller Protokolle, die für Geräte auf der ganzen Welt zur Kommunikation erforderlich sind.

Abhängig von der Kommunikationsmethode und der Verwendung gibt es verschiedene Arten von Protokollen, die je nach physischer und Softwareschicht in vier Schichten unterteilt werden. Die erste Schicht unten befindet sich nicht mehr auf der Ebene "Welche Art von Maschine sendet Funkwellen?".

"TCP" befindet sich in der dritten Schicht unter ihnen und ist ein Protokoll, das "Regeln zum zuverlässigen Senden und Empfangen von Kommunikationsinhalten zwischen zwei Maschinen unabhängig vom Dateninhalt" definiert.

Wenn man nur die Buchstaben betrachtet, ist es dasselbe, aber die Position des Wortes selbst unterscheidet sich zwischen "TCP / IP" und "TCP", und "eine der Protokolllisten mit dem Namen TCP / IP" ist TCP.

Darüber hinaus befindet sich "HTTP" in der 4. Schicht und ist eine "Regel, die durch Hinzufügen weiterer Regeln zu TCP definiert wird, um das Format und den Zeitpunkt des Sendens und Empfangens von Daten zum Surfen auf Websites zu optimieren."

Wie ich bereits erwähnt habe, ist TCP ein Protokoll für den Datenaustausch zwischen zwei Computern ohne Überschuss oder Mangel. Es spielt also keine Rolle, in welchem Format die dort gesendeten und empfangenen Daten in welcher Anwendung verwendet werden. Da ist gar nichts. Es ist HTTP, das die Regeln bestimmt, und andere Protokolle, die sich in der vierten Schicht befinden, wie "SSH" und "FTP". Das Konzept von "Client" und "Server" unterscheidet sich in der TCP-Kommunikation nicht so stark. Die Person, die die Möglichkeit geschaffen hat, zuerst eine Verbindung herzustellen, ist der "Client", und die Person, die verbunden ist, ist der "Server". Sobald die Verbindung hergestellt ist, können beide Daten auf dieselbe Weise senden und empfangen.

Allein damit können Sie sehen, dass HTTP nicht alles ist, wenn Sie "Kommunikation" sagen. Es ist möglicherweise einfacher zu verstehen, wenn Sie die folgenden Inhalte in diesem Sinne lesen.

Wie geht es weiter?

Wie geht es dann mit der konkreten Umsetzung weiter?

Wenn es wahr ist, denke ich, dass es richtig ist, den RFC richtig zu lesen oder über das Design nachzudenken, während man sich die Implementierung anderer HTTP-Clients ansieht, aber ich denke, es durch Ausprobieren, ohne etwas zu betrachten. Ich frage mich, ob es besser ist, beim Nachdenken fortzufahren. Diesmal gehen wir also folgendermaßen vor.

  1. Implementieren Sie einen Client, der wie HTTP verwendet werden kann, und beachten Sie dabei das bekannte HTTP.
  2. Gehen Sie näher an einen richtigen HTTP-Client heran, während Sie sich die Implementierung von RFC und vorhandenen HTTP-Client-Bibliotheken ansehen

In diesem Artikel schreibe ich über den ersten Teil, den ich versuchen werde, während ich für mich selbst denke.

Versuchen Sie zu implementieren

Beginnen wir also mit der Implementierung. Der Code ist auch auf GitHub zu finden.

ChooyanHttp - GitHub

Nutzungsbild

http_client.py


if __name__ == '__main__':
    resp = ChooyanHttpClient.request('127.0.0.1', 8010)
    if resp.responce_code == 200:
        print(resp.body)

Zunächst sehen Sie, wie Sie Ihren eigenen HTTP-Client verwenden. Nach dem Übergeben von Host und Port möchten wir das Objekt abrufen, das die Antwortdaten enthält.

Erstellen Sie eine Klasse, die nichts tut

http_client.py


class ChooyanHttpClient:

    def request(host, port=80):
        response = ChooyanResponse()
        return response

class ChooyanResponse:
    def __init__(self):
        self.responce_code = None
        self.body = None

if __name__ == '__main__':

...Folgendes wird weggelassen

Klicken Sie hier für Diff

Fügen Sie als Nächstes die Klassen "ChooyanHttpClient" und "ChooyanResponse" gemäß dem obigen Verwendungsbild hinzu.

Ich habe es hinzugefügt, aber noch nichts getan.

Dieses Mal versuchen wir, den Antwortcode und den Text, der das Anforderungsergebnis sein wird, in dieses "Antwort" -Objekt zu bekommen.

Verwenden Sie das Sockelmodul

Fügen Sie als Nächstes ein Socket-Modul für die Kommunikation hinzu.

http_client.py


import socket

class ChooyanHttpClient:

    def request(host, port=80):
        response = ChooyanResponse()

        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect((host, port))

        return response

class ChooyanResponse:

...Folgendes wird weggelassen

Klicken Sie hier für Diff

Wie ich zuvor erklärt habe, wird das HTTP-Protokoll der 4. Schicht erstellt, indem das TCP-Protokoll der 3. Schicht verwendet und weitere Regeln hinzugefügt werden.

Dieses Mal besteht der Zweck darin, eine Bibliothek zu implementieren, die gemäß dem "HTTP" -Protokoll kommuniziert. Daher importieren wir das "Socket" -Modul, das mit dem "TCP" kommuniziert, das die Basis ist.

Informationen zur Verwendung des Socket-Moduls finden Sie unter [Socket-Programmieranleitung] ](Https://docs.python.jp/3/howto/sockets.html) Es wird auf der Seite kurz beschrieben. Auch in diesem Artikel werden wir mit der Implementierung fortfahren, während wir uns darauf beziehen.

Was wir hier hinzugefügt haben, ist die Verwendung des Socket-Moduls, um eine Verbindung mit dem Computer herzustellen, die dem angegebenen Host und Port entspricht.

Wenn Sie dies tatsächlich tun, beginnt die Kommunikation mit dem angegebenen Server. (Ich bin nicht sicher, weil nichts auf dem Bildschirm erscheint)

Versuchen Sie anzufordern

Nun, von hier aus ist es schwer.

Ich konnte zuvor über das Socket-Modul eine Verbindung zum Computer des angegebenen Hosts und Ports herstellen.

Es werden jedoch noch keine Daten zurückgegeben. Es ist in Ordnung, eine Verbindung herzustellen, aber es ist natürlich, da wir die "Anforderungs" -Daten noch nicht gesendet haben.

Jetzt schreibe ich den Code, um die Anfrage an den Server zu senden.

http_client.py


import socket

class ChooyanHttpClient:

    def request(host, port=80):
        response = ChooyanResponse()

        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect((request.host, request.port))
        request_str = 'GET / HTTP/1.1\nHost: %s\r\n\r\n' % (host)
        s.send(request_str.encode('utf-8'))

        return response

class ChooyanResponse:

...Folgendes wird weggelassen

Klicken Sie hier für Diff

Es wurde eine Zeile zum Ausführen der Funktion "send ()" und eine Zeile zum Zusammenstellen der Zeichenfolge hinzugefügt, die einmal an sie übergeben werden soll.

Jetzt können Sie (feste) Daten an den Server senden.

Bei der Ausführung wird diese Anforderung meiner Meinung nach im Zugriffsprotokoll auf der Serverseite angezeigt.

Erhalten Sie eine Antwort

Jetzt können Sie eine GET-Anforderung (Datenrepräsentation) an den angegebenen Host senden, dies allein reicht jedoch noch nicht aus, um mit dem Server zu kommunizieren. Dies liegt daran, dass es keinen Code gibt, der "empfangen" entspricht.

Wenn Sie sich fragen: "Nun, Sie haben eine Anfrage gesendet, damit Sie eine Antwort erhalten, oder?", Liegt dies daran, dass eine im Handel erhältliche HTTP-Client-Bibliothek ordnungsgemäß erstellt wurde. Ich habe es noch nicht richtig gemacht, daher kann ich keine Antwort erhalten.

In TCP gibt es keine besonderen Regeln bezüglich des Zeitpunkts der Datenübertragung für jeden Server und Client. Mit anderen Worten, jeder kann "seine Lieblingsdaten senden, wann er will".

Wenn jedoch nur dies entschieden wird, ist es nicht möglich zu wissen, welche Art von Daten zu welchem Zeitpunkt von Servern und Clients mit unterschiedlichen Erstellern aneinander gesendet werden, so dass die Freiheit in gewissem Umfang eingeschränkt wird und die allgemeine Anerkennung in der Regel erfolgt Haben müssen. Eine der gängigen Wahrnehmungen ist das HTTP-Protokoll.

Mit anderen Worten, in HTTP lautet die Regel "Wenn Sie eine Anfrage senden, wird eine Antwort zurückgegeben", sodass der Client "Wenn Sie eine Anfrage senden, warten Sie auf den Empfang der Antwort" implementieren muss.

Der Code sieht so aus.

http_client.py


import socket

class ChooyanHttpClient:

    def request(request):

...Kürzung

        s.connect((request.host, request.port))
        request_str = 'GET / HTTP/1.1\nHost: %s\r\n\r\n' % (host)
        s.send(request_str.encode('utf-8'))
        response = s.recv(4096)

...Folgendes wird weggelassen

Klicken Sie hier für Diff

Der Funktion recv () wurde eine Zeile hinzugefügt. Diese Zeile blockiert die Verarbeitung dieses Programms, bis der Server die Daten sendet.

Dies hat jedoch immer noch Probleme.

Ich werde die Details weglassen (weil ich sie nicht richtig verstehe), aber bei der Socket-Kommunikation können nicht alle Daten gleichzeitig empfangen werden. Wie oben erwähnt, können Sie mit der Socket-Kommunikation jederzeit Ihre Lieblingsdaten senden, sodass nicht entschieden wird, wie viel Sie einmal tun können.

Daher weiß das Programm nicht, wie viele Daten sich in einer Masse befinden, und weiß nicht, wann die Verbindung getrennt werden muss. [^ 1]

Die zuvor erwähnte Funktion "recv ()" fährt auch mit dem nächsten Prozess fort, sobald sie einige Daten bis zu einem guten Punkt (oder bis zu der im Argument angegebenen Anzahl von Bytes) anstelle von "all" empfängt.

Mit anderen Worten, dieser Code kann nur Antworten bis zu 4096 Bytes akzeptieren. Ändern Sie den Code so, dass Sie genügend Daten empfangen können.

http_client.py


import socket

class ChooyanHttpClient:

    def request(request):
...Kürzung

        s.send(request_str.encode('utf-8'))

        data = []
        while True:
            chunk = s.recv(4096)
            data.append(chunk)

        response.body = b''.join(data)
        return response

...Folgendes wird weggelassen

Klicken Sie hier für Diff

Es empfängt bis zu 4096 Bytes in einer Endlosschleife und fügt dem Array immer mehr hinzu. Wenn Sie es verketten, können Sie die Daten vom Server empfangen, ohne sie zu verpassen.

Dies ist jedoch noch unvollständig. Wenn ich diesen Code ausführe, verlässt er die Endlosschleife nicht und kann das Ergebnis nicht an den Aufrufer zurückgeben.

Wie ich bereits geschrieben habe, hat die Socket-Kommunikation nicht das Konzept "einmal" und die Kommunikation hat kein Ende. Dies sagt dem Programm nicht, wo die Endlosschleife beendet werden soll.

Damit kann das Merkmal von HTTP "Wenn Sie es einmal senden, wird es einmal zurückgegeben" nicht realisiert werden. In HTTP wird daher entschieden, die Datengröße (im Hauptteil) mithilfe des Headers "Content-Length" anzugeben. Der folgende Code erstellt einen Mechanismus zum Lesen.

http_client.py


import socket

class ChooyanHttpClient:

    def request(request):

...Kürzung
        s.send(request_str.encode('utf-8'))

        headerbuffer = ResponseBuffer()
        allbuffer = ResponseBuffer()
        while True:
            chunk = s.recv(4096)
            allbuffer.append(chunk)

            if response.content_length == -1:
                headerbuffer.append(chunk)
                response.content_length = ChooyanHttpClient.parse_contentlength(headerbuffer)

            else:
                if len(allbuffer.get_body()) >= response.content_length:
                    break

        response.body = allbuffer.get_body()
        response.responce_code = 200

        s.close()
        return response

    def parse_contentlength(buffer):
        while True:
            line = buffer.read_line()
            if line.startswith('Content-Length'):
                return int(line.replace('Content-Length: ', ''))
            if line == None:
                return -1

class ChooyanResponse:
    def __init__(self):
        self.responce_code = None
        self.body = None
        self.content_length = -1

class ResponseBuffer:
    def __init__(self):
        self.data = b''

    def append(self, data):
        self.data += data

    def read_line(self):
        if self.data == b'':
            return None

        end_index = self.data.find(b'\r\n')
        if end_index == -1:
            ret = self.data
            self.data = b''
        else:
            ret = self.data[:end_index]
            self.data = self.data[end_index + len(b'\r\n'):]
        return ret.decode('utf-8')

    def get_body(self):
        body_index = self.data.find(b'\r\n\r\n')
        if body_index == -1:
            return None
        else:
            return self.data[body_index + len(b'\r\n\r\n'):]

...Folgendes wird weggelassen

Klicken Sie hier für Diff

Es ist lange her, aber ich werde erklären, was ich versuche, um zu tun.

Suchen Sie die Zeile "Content-Length"

Als HTTP-Antwortformat

Es wurde entschieden.

Daher erfolgt die Bestellung jedes Mal, wenn Daten empfangen werden, von vorne.

Ich mache das Jetzt können Sie die Content-Length abrufen.

"Inhaltslänge" beschreibt jedoch nur die Größe des Körperteils. Der Header und der Antwortcode in der ersten Zeile sind nicht enthalten.

Daher versuche ich unter Verwendung aller empfangenen Daten, die Größe der Daten nach dem zweiten Zeilenumbruch in Folge (dh dem Körperteil nach der ersten Leerzeile) mit "Inhaltslänge" zu vergleichen. ..

Wenn die Größe der Inhaltslänge und die Größe des Körperteils übereinstimmen (im Code nur für den Fall, dass sie größer oder gleich der Länge des Inhalts ist), können Sie die Schleife verlassen und die Daten an den Aufrufer zurückgeben. Ich kann es schaffen

Verbessern

Jetzt, da wir endlich Anfragen senden und Antworten empfangen können, ist es als HTTP-Client immer noch unbrauchbar.

Die Anforderung ist schrecklich, auf die GET-Methode beschränkt, auf den Stammpfad und keinen Anforderungsheader beschränkt, und die Antwort gibt nur alle Daten einschließlich des Antwortcodes und des Headers als Bytezeichenfolge zurück.

Es gibt noch viele Dinge zu tun, wie die Formatierung der Daten hier, das Ändern des Verhaltens entsprechend dem Header, die Feinabstimmung des Sende- und Empfangszeitpunkts, die Timeout-Verarbeitung usw., aber dieser Artikel ist ziemlich lang geworden. Ich habe es getan, also würde ich das gerne das nächste Mal tun.

Einmal zusammengefasst

Vorerst habe ich versucht, eine HTTP-Client-ähnliche Verarbeitung zu implementieren, aber ich habe das Gefühl, dass ich mein Verständnis von TCP und HTTP vertiefen konnte. Es ist schwierig, eine HTTP-Client-Bibliothek zu erstellen ... Welche Art von Implementierung ist "Anfragen" oder "Urllib"?

Deshalb werde ich das nächste Mal weitermachen.

Referenz

Nachdem ich diesen Artikel gelesen hatte, entschied ich mich, auf ähnliche Weise zu studieren. In diesem Artikel habe ich den HTTP "Server" erstellt, aber es gab viele Inhalte, die beim Erstellen des Clients sehr hilfreich waren.

Er erklärte die Socket-Kommunikation auf sehr leichte und leicht verständliche Weise, was für mich beim Studium der Socket-Kommunikation sehr hilfreich war. Obwohl es sich um ein Python-Dokument handelt, ist es unabhängig von der Sprache hilfreich.


[^ 1]: Ich dachte, das liegt an dem in HTTP 1.1 hinzugefügten KeepAlive-Header. Wenn Sie dies deaktivieren, wird die Verbindung zum Server getrennt, wenn die Daten an das Ende gesendet werden, und die clientseitige Funktion recv () gibt 0 zurück, sodass Sie dies erkennen und aus der Schleife ausbrechen können.

Recommended Posts

[TCP / IP] Versuchen Sie nach dem Studium, mit Python einen HTTP-Client zu erstellen
Versuchen Sie, in Python einen "Entschlüsselungs" -Code zu erstellen
Versuchen Sie, mit Python eine Diedergruppe zu bilden
Lassen Sie uns ein Befehls-Standby-Tool mit Python erstellen
Fortsetzung ・ Ich habe versucht, Slackbot zu erstellen, nachdem ich Python3 studiert habe
Versuchen Sie, Facebook mit Python zu betreiben
Versuchen Sie, mit Python3 eine Zeichenfolge aus einem Bild zu extrahieren
Versuchen Sie, mit Python (1) eine Erfassungssoftware zu erstellen, die so genau wie möglich ist.
Ich habe versucht, mit Python + OpenCV eine Bildähnlichkeitsfunktion zu erstellen
Versuchen Sie, Farbfilme mit Python zu reproduzieren
Versuchen Sie, sich mit Python bei qiita anzumelden
Fraktal zum Erstellen und Spielen mit Python
Versuchen Sie, ein Bild mit Entfremdung zu erzeugen
Versuchen Sie, mit Python (2) eine Erfassungssoftware zu erstellen, die so genau wie möglich ist.
Versuchen Sie, Foldl und Foldr mit Python: Lambda zu machen. Auch Zeitmessung
Versuchen Sie, Ihr eigenes AWS-SDK mit bash zu erstellen
Senden Sie eine E-Mail mit Python an Spushis Adresse
Versuchen Sie, das Mensch-Maschine-Diagramm mit Python zu lösen
Versuchen Sie, mit Python eine Lebenskurve zu zeichnen
Ich möchte ein Spiel mit Python machen
So beschneiden Sie ein Bild mit Python + OpenCV
Versuchen Sie, Python-Dokumente automatisch mit Sphinx zu generieren
WEB Scraping mit Python und versuchen, aus Bewertungen eine Wortwolke zu machen
Versuchen Sie, mit Node.js einen HTTP-Server zu erstellen
Versuchen Sie, Client-FTP mit Pythonista am schnellsten zu machen
Versuchen Sie, Fische mit Python + OpenCV2.4 (unvollendet) zu erkennen.
[Mac] Ich möchte einen einfachen HTTP-Server erstellen, auf dem CGI mit Python ausgeführt wird
Anfänger versuchen mit Django + React + Bootstrap (1) eine Online-Webanwendung für Othello zu erstellen.
[Cloud 9] Versuchen Sie, eine Umgebung mit Django 1.11 von Python 3.4 zu erstellen, ohne auch nur 1 mm zu verstehen
Versuchen Sie, das Programmier-Herausforderungsbuch mit Python3 zu lösen
Versuchen Sie, ein Python-Modul in C-Sprache zu erstellen
Erklären Sie ausführlich, wie Sie mit Python einen Sound erzeugen
Versuchen Sie, das Problem der Zuweisung von Schulungsärzten mit Python zu lösen
Ändern Sie die IP-Einstellungen mit Python in ACL von conoha
Probieren Sie die DB-Operation mit Python aus und visualisieren Sie sie mit d3
Einführung in die verteilte Parallelverarbeitung von Python durch Ray
Lesehinweis: Einführung in die Datenanalyse mit Python
Versuchen Sie, mit MVC eine RESTful-API mit Flask 1.0.2 zu erstellen
Ich habe versucht, künstliches Perzeptron mit Python zu implementieren
So erstellen Sie einen HTTPS-Server mit Go / Gin
Ich habe versucht, eine OCR-App mit PySimpleGUI zu erstellen
Versuchen Sie es mit Python.
HTTP-Kommunikation mit Python
[Python] Ich habe versucht, mit tkinter eine Anwendung zu erstellen, die das Gehalt anhand der Arbeitszeit berechnet
Vorsichtsmaßnahmen bei der Eingabe von CSV mit Python und der Ausgabe an json, um exe zu erstellen
Versuchen Sie es mit GUI, PyQt in Python
Immerhin ist es falsch, mit Python-Subprozess zu katzen.
Ich habe versucht, mit Python faker verschiedene "Dummy-Daten" zu erstellen
Versuchen Sie, verschiedene Informationen anzuzeigen, die für das Debuggen mit Python nützlich sind
[Python] Erstellen einer Adjazenzmatrix / Adjazenzliste [Graphentheorie]
So konvertieren Sie mit Python [Anwendung] von einem Array in ein Wörterbuch
Die erste API, die mit dem Python Djnago REST-Framework erstellt wurde
Versuchen Sie, eine Excel-Datei mit Python (Pandas / XlsxWriter) zu betreiben