TCPClient GET Request

  • VB.NET

Es gibt 25 Antworten in diesem Thema. Der letzte Beitrag () ist von Kraizy.

    TCPClient GET Request

    Hi,

    ich würde gerne GET/POST Requests über einen TCPClient an eine Webseite senden und dessen Antwort auswerten. Ich weiß, dass hierfür WebRequests bzw ein WebClient besser geeignet wäre, da es den Großteil der Arbeit abnimmt, aber in diesem Fall muss es eben über einen TCPClient laufen.

    Code:

    VB.NET-Quellcode

    1. Dim tcpClient As New TcpClient("www.google.de", 80)
    2. Dim stream As NetworkStream = tcpClient.GetStream()
    3. If stream.CanWrite Then
    4. Dim sGet As String = "GET / HTTP/1.1\r\nHost: google.de\r\n\r\n"
    5. Dim data() As Byte = Encoding.UTF8.GetBytes(sGet)
    6. stream.Write(data, 0, data.Length)
    7. stream.Flush()
    8. Else
    9. '...
    10. End If
    11. If stream.CanRead Then
    12. Dim data(tcpClient.ReceiveBufferSize) As Byte
    13. stream.Read(data, 0, data.Length)
    14. Dim sResponse As String = Encoding.UTF8.GetString(data)
    15. Debug.Print(sResponse)
    16. Else
    17. '...
    18. End If


    Problem hierbei ist jedoch, dass nach "stream.Read(data...." nichts mehr passiert und ich weiß einfach nicht warum.
    Hi @Kraizy

    Kraizy schrieb:

    aber in diesem Fall muss es eben über einen TCPClient laufen.
    Kann mir beim besten Willen ned vorstellen, welcher Service das sein, soll, der keinen HTTPWebRequest wills, sondern einen Tcp-Client, der da was schickt und dafür was zurücksendet.

    Kraizy schrieb:

    dass nach "stream.Read(data...." nichts mehr passiert
    Hast schon mal Breakpoint gestzt und dann Rechtsklick->Überwachung hinzufügen gemacht? Dann kannst dir im Einzelschritt ansehen, was in der Variable data gespeichert ist.
    Bzw mach mal dein stream.Read(data, 0, data.Length) weg und schreib Dim len As Integer = stream.Read(data, 0, data.Length), dann mit Einzelschritt draufgehen und dir den Inhalt von len anschauen. Wenn der 0 ist, dann hast du nix empfangen.

    Lg Radinator
    In general (across programming languages), a pointer is a number that represents a physical location in memory. A nullpointer is (almost always) one that points to 0, and is widely recognized as "not pointing to anything". Since systems have different amounts of supported memory, it doesn't always take the same number of bytes to hold that number, so we call a "native size integer" one that can hold a pointer on any particular system. - Sam Harwell
    Hat nichts damit zu tun, dass der Service keine WebRequests annehmen möchte, sondern dass ich die GET/POST Anfragen gerne über einen Socks5 Proxy absenden möchte, und so viel ich weiß, ist das nicht, oder nur ziemlich umständlich, in WebRequests umzusetzen. Für den TCPClient habe ich jedoch mehrere Libraries zur Verfügung, bei denen ich einfach IP & Port übergeben kann um Anfragen über den Proxy zu senden.

    Jedoch klappt nicht mal das normale Requesten ohne Proxy. Auch mit deiner Zeile "Dim len As Integer = ..." passiert einfach nichts mehr, nachdem diese aufgerufen wurde. Es sieht einfach so aus, als würde die ganze Zeit darauf gewartet werden, etwas auszulesen zu können, aber es kommt einfach nichts.
    Hab noch 2 Vorschläge, die du versuchen könntest:
    1.) Nach der ersten IF-Verzweigung ein stream.Close(). Musst halt VOR der zweiten IF-Verzweigung noch mal erstellen:
    What is the correct way to read from NetworkStream in .NET erste Antwort nach Fragestellung:
    [...]
    The code in your attempt (and the answers) do
    not close client or stream, which causes a resource leak with big
    consequences if called repeatedly.
    [...]
    Bzw das erstellen des Streams einfach in einen Using-Block packen.
    Denn es kann sein - jedenfalls kann ich mir das grad nur so erklären - dass durch dein vorheriges REINschreiben, der Stream denkt, er sein ein reiner Input-Stream/Schreibe-Stream.
    Wenn du natürlich dann darauffolgend was raus-lesen willst, dann isser iwie zu blöd zu checken "Hey ich sollte mich evl umstellen!"

    2.) Mach mal, bevor du 1.) ausprobierst, einmal VOR deinem Lesen ein If (stream.DataAvailable) Then ... End If .
    Das DataAvailable prüft - wie der Name sagt - OB überhaupt Daten zu lesen da sind.

    Lg Radinator
    In general (across programming languages), a pointer is a number that represents a physical location in memory. A nullpointer is (almost always) one that points to 0, and is widely recognized as "not pointing to anything". Since systems have different amounts of supported memory, it doesn't always take the same number of bytes to hold that number, so we call a "native size integer" one that can hold a pointer on any particular system. - Sam Harwell
    Bzgl 2.) kommt immer False bei DataAvailable. Zumindest wenn ich es so mache:

    VB.NET-Quellcode

    1. tcpClient.Connect(...)
    2. stream = tcpClient.GetStream()
    3. If stream.CanWrite
    4. ...
    5. stream.Write(...)
    6. stream.Flush()
    7. End If
    8. If stream.DataAvailable
    9. If stream.CanRead
    10. ...
    11. End If
    12. End If


    Aber aus welchem Grund? Das ist mir nicht ganz klar.

    Kraizy schrieb:

    False bei DataAvailable
    Dachte ich es mir fast

    Kraizy schrieb:

    Aber aus welchem Grund? Das ist mir nicht ganz klar
    Wie bereits gesagt: Ich geh mal davon aus, dass durch dein vorheriges Schreiben in den Stream eine interne Property gesetzt wurde, die den Stream für sich selber als Schreibe-Stream definiert. Sprich: Du kannst nur schreiben.
    Probier es einfach mal aus, um den Schreibe-Block ein Using NetworkStream ns = client.GetStream() und um den Lese-Block ein Using NetworkStream ns = client.GetStream() zu schreiben. Dann sollten jedesmal einzelne Stream erstellt werden, die sich nicht blokieren.
    In general (across programming languages), a pointer is a number that represents a physical location in memory. A nullpointer is (almost always) one that points to 0, and is widely recognized as "not pointing to anything". Since systems have different amounts of supported memory, it doesn't always take the same number of bytes to hold that number, so we call a "native size integer" one that can hold a pointer on any particular system. - Sam Harwell
    Leider nicht...

    VB.NET-Quellcode

    1. Using ns As NetworkStream = tcpClient.GetStream()
    2. If ns.CanWrite Then
    3. Dim getRequest As String = "GET / HTTP/1.1\r\nHost: www.google.de\r\n\r\n"
    4. Dim data() As Byte = Encoding.UTF8.GetBytes(getRequest)
    5. ns.Write(data, 0, data.Length)
    6. ns.Flush()
    7. End If
    8. End Using
    9. Using ns As NetworkStream = tcpClient.GetStream()
    10. If ns.DataAvailable Then
    11. If ns.CanRead Then
    12. Dim data(tcpClient.ReceiveBufferSize) As Byte
    13. ns.Read(data, 0, data.Length)
    14. Dim returndata As String = Encoding.UTF8.GetString(data)
    15. Debug.Print(returndata)
    16. End If
    17. End If
    18. End Using


    Beim 2. Using kommt "Der Vorgang ist für nicht verbundene Sockets unzulässig."
    Das erste Using schmeißt deine Verbindung bereits weg. Das Aufräumen der NetworkStreams solltest du dem TcpClient überlassen, indem du Dispose() aufrufst, nachdem du mit sämtlichem Datenaustausch fertig bist.

    Radinator schrieb:

    Kann mir beim besten Willen ned vorstellen, welcher Service das sein, soll, der keinen HTTPWebRequest wills, sondern einen Tcp-Client, der da was schickt und dafür was zurücksendet.
    Hausaufgaben, nehme ich an. @Kraizy stimmts?
    Mit freundlichen Grüßen,
    Thunderbolt

    Thunderbolt schrieb:

    Hausaufgaben, nehme ich an. @Kraizy stimmts?

    Nein, wie bereits gesagt, würde ich die Anfragen gerne über einen Socks5 laufen lassen und das geht über HttpRequests nicht. Bzw ich wüsste nicht wie. Deswegen TCPClient, da ich hierfür diverse Libraries habe, bei denen ich einfach IP & Port vom Socks eintrage und fertig. Aber wie man sieht, klappt nicht mal das normale requesten ohne Proxy.

    Kraizy schrieb:

    Beim 2. Using kommt "Der Vorgang ist für nicht verbundene Sockets unzulässig."

    Thunderbolt schrieb:

    Das erste Using schmeißt deine Verbindung bereits weg.
    Stufu!...Hätt auch selber drauf kommen können :/
    Stimmt...das Dispose auf dem NetworkStream schließt auch den darunterliegenden Socket(MSDN - ReferenceSource) Sry.

    Mach am Besten statt des einen einzigen TCPClients lieber 2 und arbeite mit den zweien oder du rufst nach den schließen ein client.Connect auf und erzeugst ein neues Stream-Objekt

    Lg Radinator
    In general (across programming languages), a pointer is a number that represents a physical location in memory. A nullpointer is (almost always) one that points to 0, and is widely recognized as "not pointing to anything". Since systems have different amounts of supported memory, it doesn't always take the same number of bytes to hold that number, so we call a "native size integer" one that can hold a pointer on any particular system. - Sam Harwell
    Hab's nun endlich...lag an meinem GET String. Aus:

    VB.NET-Quellcode

    1. Dim getRequest As String = "GET / HTTP/1.1\r\nHost: google.de\r\n\r\n"


    Habe ich nun:

    VB.NET-Quellcode

    1. Dim getRequest As String = String.Format("GET / HTTP/1.1{0}Host: {1}{0}{0}", vbNewLine, "google.de")


    gemacht und bekomme die erwartet Antwort vom Server. Lag also an \r\n

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „Kraizy“ ()

    Ein Problem habe ich jedoch noch, undzwar bekomme ich nicht den vollständigen Html Source geliefert:

    VB.NET-Quellcode

    1. Using writer As New StreamWriter(stream)
    2. writer.Write(String.Format("GET / HTTP/1.1{0}Host: {1}{0}{0}", vbNewLine, url))
    3. writer.Flush()
    4. Dim byteList As New List(Of Byte)
    5. Dim bufferSize As Integer = tcpClient.ReceiveBufferSize
    6. Dim buffer(bufferSize - 1) As Byte
    7. Do
    8. Dim bytesRead As Integer = stream.Read(buffer, 0, bufferSize)
    9. byteList.AddRange(buffer.Take(bytesRead))
    10. Loop While stream.DataAvailable
    11. Dim sResponse As String = Encoding.UTF8.GetString(byteList.ToArray())
    12. Debug.Print(sResponse)
    13. End Using


    Habe es nun auf verschiedene Arten probiert, aber nirgends bekomme ich den kompletten Source zurück.

    Edit: Habs nun ganz banal gelöst mit Loop Until sResponse.Contains("</html>")
    Da gibts doch aber sicher ne schönere Lösung..?

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Kraizy“ ()

    Keiner ne Idee? Musste nämlich feststellen, dass die Abfrage, ob der Content mit "</html>" endet, nicht immer zutrifft - z.B. wenn die Antwort vom Server nicht der eigentliche Seitenquelltext ist, sondern der Hinweis auf 301 Moved Permanently o.ä. da hört die Antwort nämlich nicht mit </html> auf.
    @3daycliff
    Bekomme leider kein Content-Length in der Response zurück. Und wenn doch, dann nur wenn kein Quellcode vorhanden ist, sondern nur der Header - z.B. bei einem 301 Moved Permanently - wobei da dann Content-Length: 0 steht, was mir logischerweise nichts bringt.

    Habs nun aber folgendermaßen hinbekommen:

    VB.NET-Quellcode

    1. Using client = New TcpClient("www.host.com", 80)
    2. Using stream = client.GetStream()
    3. Using writer = New StreamWriter(stream)
    4. Using reader = New StreamReader(stream)
    5. writer.AutoFlush = True
    6. writer.WriteLine("GET / HTTP/1.1")
    7. writer.WriteLine("Host: www.host.com")
    8. writer.WriteLine("User-Agent: ...")
    9. ...
    10. writer.WriteLine("Connection: close")
    11. Dim response As String = reader.ReadToEnd()
    12. End Using
    13. End Using
    14. End Using
    15. End Using


    Klappt nun alles wie es soll, Problem hierbei ist jedoch, dass ich Connection: close erzwinge. Somit muss ich bei jedem weiteren Request (z.B. 1. Request: per GET Hauptseite aufrufen, Cookies parsen, 2. Request: per POST Login Anfrage absenden) einen neuen TcpClient erstellen und eine erneute Verbindung mit dem Host aufbauen.

    Wenn ich Connection: keep-alive benutze, bleibt mein Tool bei reader.ReadToEnd() ewig lang hängen und gibt mit Glück entweder die komplette Antwort (vollständigen HTML Quellcode) zurück, oder es kommt die Exception: "Von der Übertragungsverbindung können keine Daten gelesen werden: Eine vorhandene Verbindung wurde vom Remotehost geschlossen."

    Wenn ich nun also bei jedem Request Connection: close benutze und bei jedem weiteren Request einen neuen Client erstelle + Verbindung aufbaue, hat dies dann irgendwelche negativen Auswirkungen? Kann es in diesem Fall z.B. als Spam empfunden werden? Oder wäre es sogar eher eine normale Vorgehensweise?
    Deswegen schrieb ich ja auch "im Normalfall". Wobei das heute auch nicht mehr der Normalfall ist.
    Also, hier mal eine kurze Zusammenfassung wie du laut der RFC vorgehen musst, um festzustellen, wie Lang die Nachricht ist:
    (siehe auch Änderungen in RFC7231)

    1) Wenn der Statuscode keinen Message-Body definiert, endet die Nachricht mit einer Leerzeile nach den Headern. Das trifft mind. auf die Statuscodes 1xx, 204 und 304 zu.

    2) Andernfalls schaust du, ob der Header Transfer-Encoding gesetzt und ungleich identity ist. Falls ja, ist der Body in chunks aufgeteilt. Der letzte chunk hat die Größe 0 und wenn du den gelesen hast, ist die Antwort komplett.

    3) Andernfalls, wenn der Content-Length-Header gesetzt ist, gibt dieser die Anzahl an Bytes des Bodies an (also ohne Header etc.)

    4) Andernfalls, wenn der Media type (im Content-Type-Header) multipart/byteranges ist, kannst du anhand des boundary-Parameter feststellen, wann du die Nachricht zuende gelesen hast.

    5) Andernfalls, sollte der Server die Verbindung schließen
    Spoiler anzeigen

    Hab nun folgendes versucht:

    VB.NET-Quellcode

    1. 'Header lesen
    2. Dim sbHeaders As New StringBuilder
    3. Do
    4. Dim line As String = reader.ReadLine()
    5. If line = ""
    6. Exit Do 'Header Ende
    7. End If
    8. sbHeaders.AppendLine(line)
    9. Loop
    10. 'HTML Body lesen
    11. Dim sbHtml As New StringBuilder
    12. 'Prüfen, ob Content-Length angegeben ist
    13. If sbHeaders.ToString().Contains("Content-Length:")
    14. Dim contentLength As Integer = 'mit Split/RegEx wie auch immer aus dem Header parsen
    15. 'Testweise Byte für Byte auslesen ... geht sicherlich auch mit .Read() + Buffer aber macht nun auch kein Unterschied
    16. For i As Integer = 0 To contentLength - 1
    17. sbHtml.Append(CChar(ChrW(reader.Read())))
    18. Next
    19. 'HTML Body wird vollständig ausgegeben und es gibt auch kein Hänger
    20. 'trotz Connection: keep-alive - alles klappt wunderbar
    21. Debug.Print(sbHtml.ToString())
    22. Else
    23. 'Content-Length nicht vorhanden,
    24. 'HTML Body wird in mehreren Teilen gesendet
    25. 'Problem hierbei ist jedoch, es wird mehr gelesen,
    26. 'als es eigentlich sein sollte
    27. Dim sSize As String = reader.ReadLine() 'hier wird die Größe des Body Abschnitts als Hex empfangen
    28. Dim size As Integer = CInt("&H" & sSize)
    29. 'Hier nun wieder auf die gleiche Weise auslesen,
    30. 'wie als hätte der Server einen Content-Length ausgegeben
    31. For i As Integer = 0 to size - 1
    32. sbHtml.Append(CChar(ChrW(Reader.Read())))
    33. Next
    34. 'Normalerweise müsste hier nach der For-Schleife nun wieder ein Hexwert kommen,
    35. 'welcher die Größe des 2. Body Abschnitts bestimmt
    36. 'somit könnte ich einfach wieder sSize = reader.ReadLine() benutzen.
    37. 'Das ist jedoch nicht der Fall, die Ausgabe sieht nämlich so aus:
    38. Debug.Print(sbHtml.ToString())
    39. End If


    Debug.Print Ausgabe:


    11ef0 <- 1. Hex Wert der die Größe des 1. Abschnitts bestimmt
    <!DOCTYPE html>
    <html dir="ltr" lang="de">
    <head>
    <title>VB-Paradise 2.0 — Die große Visual–Basic– und .NET–Community</title>

    <base href="https://www.vb-paradise.de/" />
    ...
    ...
    ... der Übersicht halber mal gekürzt
    ...
    ...
    <footer class="messageOptions">
    <nav class="jsMo
    11e48
    bileNavigation buttonGroupNavigation">
    <ul class="smallButtons buttonGroup">
    <li><a href="https://www.vb-paradise.de/index.php/Thread/116262-01-01-16-Dim-y-As-New-Year-2016-—-VB-Paradise-wünscht-ein-erfolgreiches-Programm/?s


    Wie man sieht, steht die Größe des 2. Body Abschnitts bereits im 1. ausgelesenen Abschnitt. Es wird, und ich kann mir einfach nicht erklären wieso, zu viel gelesen. Und das obwohl ja nur so viel mit der For-Schleife gelesen wird, wie der 1. Hexwert auch angibt. Wo genau liegt da der Fehler?


    Edit: Hab den Fehler gefunden. Der Code von oben funktioniert anscheinend doch ohne Probleme. Egal ob der Server Content-Length sendet, oder ich den Body teilweise lesen muss. Habe es nun mit einigen anderen Seiten probiert und rausgefunden woran es lag. Nämlich, da ich kein Accept-Encoding: gzip, deflate benutzt habe, hat der Server als Content-Length irgendwas mit ~98.000 geantwortet. Mit gzip sinds nur noch um die 20.000.

    Nun ist mein Problem aber ein anderes. Normalerweise benutze ich kein gzip, da nun alles auch ohne perfekt klappt. Jedoch muss ich bei dieser einen Seite gzip benutzen, da ich andernfalls einen viel zu großen Content-Length bekomme - und somit wieder zu dem Problem komme, dass zu viel gelesen wird als eigentlich sollte.

    Wenn ich nun also Accept-Encoding: gzip, deflate bei meinem Request benutze, bekomme ich beim Lesen des HTML Bodys komische Zeichen:


    HTTP/1.1 200 OK
    Cache-Control: no-cache, no-store, must-revalidate
    Content-Encoding: gzip
    Content-Type: text/html
    Date: Sat, 06 Feb 2016 22:25:49 GMT
    Expires: 0
    Pragma: no-cache
    ...
    ...
    ...
    Strict-Transport-Security: max-age=0; includeSubDomains
    Vary: Accept-Encoding
    Content-Length: 20588
    Connection: keep-alive

    ‹§±âùÓøïÌvaô‰Ù­<Sæ2þö7åÃGå%¡%ô%B©q¨DòÞ÷o£x ñGo@áƒNèÏO0ªüßÿ×\&äÀ.u>©¼ò‚»ŽpW9õ½‡,üý—‡‰pÉYê5|Ré
    ¼@ ̱ç*l <
    ¦ÌULôÇ=û7±ûòo•ç(ñ°_Ÿ+SßSŽ°šäu¼«ñIåwmþéBÏñ ´íL8Ý÷?'¿Œò–[ÜÇÇ<)óÓ߁—1ÇÁæ
    Î{à-\‹+Gïžó1wƒ÷C:Œ¾ê$ Ž?À[š„¦¹mú'•Oÿ)ZV


    Hab mir also gedacht, gut dann benutze ich eben New System.IO.Compression.GZipStream(Stream, Compression.CompressionMode.Decompress)
    Beim Lesen mit dem StreamReader bekomme ich jedoch die Exception: Die "Magic Number" im GZip-Header ist nicht richtig. Stellen Sie sicher, dass Sie einen GZip-Stream übergeben.

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Kraizy“ ()

    'Normalerweise müsste hier nach der For-Schleife nun wieder ein Hexwert kommen,

    Falsch. Danach kommt zunächst ein CRLF.

    Und das obwohl ja nur so viel mit der For-Schleife gelesen wird, wie der 1. Hexwert auch angibt.

    Hast du das mal im Debugger genau verfolgt?

    Übrigens würde ich vorsichtig mit dem StringBuilder sein, die chunks bestehen aus bytes, du behandelst sie aber wie Zeichen.

    Edit: Zu langsam. Wenn es ohne gzip nicht klappt, hat du noch einen Fehler. Du hältst dich in keinster Weise an die in der RFC vorgeschriebenen Reihenfolge. Versuch das zunächst gerade zu bügeln, dann kannst du Extras wie gzip einarbeiten.
    Ja hab ich auch gemerkt und deshalb jedesmal noch ein reader.ReadLine() reingeklatscht bevor ich den Body wieder mit der For-Schleife lese.

    Weiß nicht ob du mein Editierten Beitrag bereits gelesen hast, wie gesagt, das Problem habe ich nun gefunden, jedoch besteht jetzt das Problem mit der GZip MagicNumber...

    3daycliff schrieb:


    Edit: Zu langsam. Wenn es ohne gzip nicht klappt, hat du noch einen Fehler. Du hältst dich in keinster Weise an die in der RFC vorgeschriebenen Reihenfolge. Versuch das zunächst gerade zu bügeln, dann kannst du Extras wie gzip einarbeiten.


    Naja, bei allen anderen Seiten klappt es ja. Ich kann den Header und Body auslesen. Und wieso halte ich mich in keinster Weise daran? Ich prüf doch, ob vom Server ein Content-Length gesendet wird, wenn ja, dann benutze ich diese. Andernfalls, wenn kein Content-Length gegeben ist, lese ich den Body Teil für Teil aus, da vor jedem Teil ja die Größe als Hexwert angegeben wird.

    Das Problem ist jedoch, wenn ich bei der Seite kein GZip benutze, spuckt es mir über 90.000 als Content-Length aus, was meiner Meinung nach falsch ist - und mein Tool dann irgendwie durcheinander kommt mit dem "Teil für Teil des Bodys lesen". Mit GZip sinds dann nur noch um die 20.000 was auch in Chrome/FireFox angezeigt wird. Jedoch bekomme ich hierbei dann den Fehler bzgl Magic Number.

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Kraizy“ ()