2010-05-31 13 views
8

Ich verwende Socket Klasse für meine Web-Client. Ich kann HttpWebRequest nicht verwenden, da Socks-Proxys nicht unterstützt werden. Also muss ich die Header analysieren und die Chunked-Codierung selbst handhaben. Die schwierigste Sache für mich ist, die Länge des Inhalts zu bestimmen, also muss ich es Byte für Byte lesen. Zuerst muss ich ReadByte() verwenden, um die letzte Kopfzeile ("\ r \ n \ r \ n" -Kombination) zu finden, und dann prüfen, ob der Körper eine Übertragungscodierung hat oder nicht. Wenn es tut, muss ich Chunk Größe etc lesen:Wie bekomme ich HTTP-Nachrichten mit Socket

public void ParseHeaders(Stream stream) 
{ 
    while (true) 
    { 
     var lineBuffer = new List<byte>(); 
     while (true) 
     { 
      int b = stream.ReadByte(); 
      if (b == -1) return; 
      if (b == 10) break; 
      if (b != 13) lineBuffer.Add((byte)b); 
     } 
     string line = Encoding.ASCII.GetString(lineBuffer.ToArray()); 
     if (line.Length == 0) break; 
     int pos = line.IndexOf(": "); 
     if (pos == -1) throw new VkException("Incorrect header format"); 
     string key = line.Substring(0, pos); 
     string value = line.Substring(pos + 2); 
     Headers[key] = value; 
    } 
} 

Aber dieser Ansatz hat sehr schlechte Leistung. Können Sie eine bessere Lösung vorschlagen? Vielleicht einige Open-Source-Beispiele oder Bibliotheken, die HTTP-Anfrage über Sockets behandeln (nicht sehr groß und kompliziert, aber ich bin ein Noob). Am besten wäre es, einen Link zum Beispiel zu senden, der den Nachrichtentext liest und die Fälle korrekt behandelt, wenn: content chunked-encoding, gzip- oder deflate-encoded ist, Content-Length-Header fehlt (Nachricht endet, wenn die Verbindung geschlossen wird). So etwas wie der Quellcode der HttpWebRequest-Klasse.

Upd:

int bytesRead = 0; 
byte[] buffer = new byte[0x8000]; 
do 
{ 
    try 
    { 
     bytesRead = this.socket.Receive(buffer); 
     if (bytesRead <= 0) break; 
     else 
     { 
      this.m_responseData.Write(buffer, 0, bytesRead); 
      if (this.m_inHeaders == null) this.GetHeaders(); 
     } 
    } 
    catch (Exception exception) 
    { 
     throw new Exception("Read response failed", exception); 
    } 
} 
while ((this.m_inHeaders == null) || !this.isResponseBodyComplete()); 

Wo GetHeaders() und isResponseBodyComplete() Verwendung m_responseData (MemoryStream) mit bereits empfangenen Daten: Meine neue Funktion sieht wie folgt aus.

+0

Wenn Sie gerade diese Frage bearbeiten, mit Sachen, die Sie lästig finden, werde ich einfachere Antworten auf meine ursprüngliche Antwort hinzufügen. –

+0

Können Sie WinInet nicht verwenden? http://msdn.microsoft.com/en-us/library/aa383996(VS.85).aspx –

+0

Zugehöriges, mögliches Duplikat: http://stackoverflow.com/questions/11862890/c-how-to-execute- a-http-request-using-sockets – vapcguy

Antwort

9

Ich schlage vor, dass Sie dies nicht selbst implementieren - das HTTP 1.1-Protokoll ist ausreichend komplex, um dies ein Projekt von mehreren Mann-Monaten zu machen.

Die Frage ist, gibt es einen HTTP-Request-Protokoll-Parser für .NET? Diese Frage wurde in SO gestellt, und in den Antworten finden Sie verschiedene Vorschläge, einschließlich Quellcode für die Verarbeitung von HTTP-Streams.

Converting Raw HTTP Request into HTTPWebRequest Object

EDIT: Der Code Rotor ist relativ komplex und schwierig, wie Web-Seiten zu lesen/navigieren. Der Implementierungsaufwand für das Hinzufügen von SOCKS-Support ist jedoch viel geringer als das Implementieren des gesamten HTTP-Protokolls selbst. Sie werden in höchstens ein paar Tagen etwas haben, auf das Sie sich verlassen können, das auf einer bewährten Implementierung basiert.

Die Anforderung und die Antwort werden von einer NetworkStream, m_Transport, in der Connection Klasse gelesen/geschrieben.Dies wird in diesem Verfahren verwendet:

internal int Read(byte[] buffer, int offset, int size) 
//and 
private static void ReadCallback(IAsyncResult asyncResult) 

beide in http://www.123aspx.com/Rotor/RotorSrc.aspx?rot=42903

Die Buchse werden erstellt, in

private void StartConnectionCallback(object state, bool wasSignalled) 

So Sie diese Methode ändern könnten einen Sockel auf dem Socken-Server zu erstellen, und zu tun der notwendige Handshake, um die externe Verbindung zu erhalten. Der Rest des Codes kann gleich bleiben.

Ich gammerte diese Informationen in etwa 30 Minuten auf den Seiten im Internet suchen. Dies sollte viel schneller gehen, wenn Sie diese Dateien in eine IDE laden. Es scheint eine Last zu sein, diesen Code lesen zu müssen - schließlich ist das Lesen von Code viel schwieriger als das Schreiben, aber Sie machen nur kleine Änderungen an einem bereits etablierten, funktionierenden System.

Um sicherzustellen, dass die Änderungen in allen Fällen funktionieren, ist es ratsam, auch zu testen, wenn die Verbindung unterbrochen ist, um sicherzustellen, dass der Client die gleiche Methode erneut verbindet und die SOCKS-Verbindung erneut herstellt und SOCKS sendet anfordern.

+4

+1 für tun dies nicht tun. –

+1

Ich stimme dir zu, aber Quellcode von HttpWebRequest (Rotor) ist viel zu kompliziert. Ich kann nicht einmal eine Funktion finden, die tatsächlich Daten vom Netzwerk empfängt. – Poma

+0

Ich verstehe - es ist nicht der einfachste Code zu lesen, vor allem als Webseiten. Ich habe einige Hinweise zu meiner Antwort hinzugefügt. – mdma

1

In den meisten HTTP-Anforderungen (sollte alle sein) sollte ein Header mit dem Namen content-length vorhanden sein, der angibt, wie viele Bytes im Hauptteil der Anforderung enthalten sind. Dann ist es nur eine Frage der Zuweisung der entsprechenden Menge an Bytes und Lesen dieser Bytes auf einmal.

+2

Einige Übertragungsmethoden in HTTP 1.1 senden Ihnen keine gültige Inhaltslänge, da HTML manchmal in Blöcken gesendet wird. Es ist kein zuverlässiges Feld für HTML-Inhalte. – Aren

+0

Wie auch immer, ich muss Header Byte für Byte lesen, um zum Header "Content-Length" zu gelangen. – Poma

+2

Anstatt byteweise zu lesen, sollte es einen readLine-Methodenaufruf geben, mit dem Sie jeweils eine Zeile lesen können. HTTP-Protokoll ist .. \ r \ n \ r \ n \ r \ n <...> \ r \ n \ r \ n So werden Sie lesen müssen Zeile für Zeile, bis Sie den Content-Length-Header finden. Und dann können Sie diese Zeile auf ":" aufteilen, um den Header-Namen und Header-Wert (die Länge) zu erhalten.Sobald Sie die Länge haben, lesen Sie Zeile für Zeile weiter, bis Sie die leere Zeile erreichen. Dann lese die Länge, die du aus der Kopfzeile bekommen hast, in Bytes ein. Können Sie diese Kommentare formatieren ??? lol –

-1

Sie können sich die TcpClient Klasse in System.Net ansehen, es ist ein Wrapper für einen Socket, der die grundlegenden Operationen vereinfacht.

Von dort müssen Sie nach dem HTTP-Protokoll lesen. Seien Sie auch bereit, einige Zip-Operationen durchzuführen. Http 1.1 unterstützt GZip von seinen Inhalten und Teilblöcken. Du wirst einiges lernen müssen, um sie von Hand zu analysieren.

Basic Http 1.0 ist einfach, das Protokoll ist online gut dokumentiert, unsere freundliche Nachbarschaft Google kann Ihnen dabei helfen.

+0

Ich kann 'GZipStream' und' DeflateStream' für diese – Poma

0

Während ich tendieren würde, mit mdma zu versuchen, so hart wie möglich zu versuchen, die Implementierung Ihres eigenen HTTP-Stacks zu vermeiden, könnte ein Trick, den Sie in Betracht ziehen, das Lesen von mässig großen Streams sein. Wenn Sie einen Lesevorgang ausführen und ihm einen Puffer geben, der größer ist als der verfügbare Puffer, sollte er Ihnen die Anzahl der gelesenen Bytes zurückgeben. Dies sollte die Anzahl der Systemaufrufe reduzieren und Ihre Leistung erheblich beschleunigen. Sie müssen die Puffer immer noch so scannen, wie Sie es jetzt tun.

0

einen Blick auf anderer Client-Code zu nehmen ist hilfreich (wenn nicht verwirrend): http://src.chromium.org/viewvc/chrome/trunk/src/net/http/

ich zur Zeit so etwas wie dies zu tun bin. Ich finde den besten Weg, um die Effizienz des Clients zu erhöhen, ist die Verwendung der asynchronen Socket-Funktionen zur Verfügung gestellt. Sie sind ziemlich low-level und loswerden beschäftigt warten und selbst mit Threads umzugehen. Alle diese haben Begin und End in ihren Methodennamen. Aber zuerst würde ich es mit Blockierung versuchen, nur damit Sie die Semantik von HTTP aus dem Weg bekommen. Dann können Sie an Effizienz arbeiten. Denken Sie daran: Vorzeitige Optimierung ist schlecht - also machen Sie es sich zunutze, dann optimieren Sie all das Zeug!

Auch: Einige Ihrer Effizienz könnte in Ihrer Verwendung von ToArray() gebunden sein. Es ist bekannt, dass es ein bisschen teuer ist. Eine bessere Lösung könnte sein, Ihre Zwischenergebnisse in einem byte[] Puffer zu speichern und sie an eine StringBuilder mit der richtigen Codierung anzufügen.

Für gezippte oder deflationierte Daten, lesen Sie alle Daten ein (denken Sie daran, dass Sie möglicherweise nicht alle Daten erhalten, wenn Sie das erste Mal fragen. Verfolgen Sie, wie viele Daten Sie eingelesen haben, und hängen Sie weiter an in den gleichen Puffer). Dann können Sie die Daten mit GZipStream(..., CompressionMode.Decompress) dekodieren.

Ich würde sagen, dass dies nicht so schwierig ist, wie manche vielleicht meinen, du musst nur ein bisschen abenteuerlich sein!

-1

Ich würde einen SOCKS-Proxy erstellen, der HTTP tunneln kann und dann die Anforderungen von HttpWebRequest akzeptieren und weiterleiten kann. Ich denke, das wäre viel einfacher, als alles neu zu erstellen, was HttpWebRequest tut. Sie könnten mit Privoxy beginnen oder einfach Ihre eigenen Rollen machen.Das Protokoll ist einfach und hier dokumentiert:

http://en.wikipedia.org/wiki/SOCKS

Und auf der RFC ist, dass sie zu verknüpfen.

Sie haben erwähnt, dass Sie viele verschiedene Proxys haben müssen - Sie könnten für jeden einen lokalen Port einrichten.

2

Wenn das Problem ein Engpass in Bezug auf ReadByte zu langsam ist, schlage ich vor, dass Sie Ihren Eingangsstrom mit einem StreamBuffer umhüllen. Wenn das Leistungsproblem, das Sie beanspruchen, teuer ist, weil kleine Lesevorgänge das Problem für Sie lösen.

Auch Sie diese nicht benötigen:

string line = Encoding.ASCII.GetString(lineBuffer.ToArray()); 

HTTP nach Design erfordert, dass der Header nur aus ASCII-Zeichen besteht. Sie wollen oder wollen es nicht wirklich in tatsächliche .NET-Zeichenfolgen umwandeln (die Unicode sind).

Wenn Sie den EOF des HTTP-Headers finden möchten, können Sie dies für eine gute Leistung tun.

int k = 0; 
while (k != 0x0d0a0d0a) 
{ 
    var ch = stream.ReadByte(); 
    k = (k << 8) | ch; 
} 

Wenn die Zeichenfolge \r\n\r\nk encoutered ist 0x0d0a0d0a

+0

Während dies mit diesem speziellen Problem behilflich sein kann, geben Sie dem Poster keinen Hinweis auf die Größe der Probleme, denen er gegenübersteht, wenn er weiterhin einen HTTP-Client implementiert. Permanente Verbindungen sind nicht einfach zu implementieren, und wenn sie nicht vorhanden sind, wird die Leistung beeinträchtigt. – mdma

+0

Ich glaube wir haben das in den Kommentaren zu deiner Antwort besprochen. –

0

alle Antworten hier über erstreckenden Sockel und/oder TCPClient gleich scheinen etwas wirklich offensichtlich zu verpassen - das HttpWebRequest ist auch ein Klasse und kann daher erweitert.

Sie müssen keine eigene HTTP/Socket-Klasse schreiben. Sie müssen HttpWebRequest einfach um eine benutzerdefinierte Verbindungsmethode erweitern. Nach dem Verbindungsaufbau sind alle Daten Standard-HTTP und können von der Basisklasse wie gewohnt behandelt werden.

public class SocksHttpWebRequest : HttpWebRequest 

    public static Create(string url, string proxy_url) { 
    ... setup socks connection ... 

    // call base HttpWebRequest class Create() with proxy url 
    base.Create(proxy_url); 
    } 

Das Handshake-SOCKS ist nicht besonders komplex, so, wenn Sie ein grundlegendes Verständnis der Programmierung Steckdosen sollte es nicht sehr lange dauern, um die Verbindung zu implementieren. Danach kann HttpWebRequest das HTTP-Heavy-Lifting durchführen.

+0

Es wäre sicherlich schön, wenn sich das einfach lösen lässt.Wie erhält die Basis WebHttpRequest.Create die gleiche Socket-Verbindung, wie sie auf dem SOCKS-Server in der SocketHttpWebRequest.Create erstellt wurde? – mdma

+1

Theorie ist toll, aber ich glaube nicht, dass du das kannst. Könnten Sie ein funktionierendes Codebeispiel senden? Wie geben Sie eine TCP-Verbindung zu HTTPRequest? AFAIK das kannst du nicht machen. –

+0

Können Sie den Contructor für 'SocksHttpWebRequest' anzeigen? – Jaanus

0

Warum lesen Sie nicht bis 2 Zeilenumbrüche und dann einfach von der Zeichenfolge? Leistung könnte schlechter sein, aber es sollte immer noch vernünftig sein:

Dim Headers As String = GetHeadersFromRawRequest(ResponseBinary) 
    If Headers.IndexOf("Content-Encoding: gzip") > 0 Then 

    Dim GzSream As New GZipStream(New MemoryStream(ResponseBinary, Headers.Length + (vbNewLine & vbNewLine).Length, ReadByteSize - Headers.Length), CompressionMode.Decompress) 
ClearTextHtml = New StreamReader(GzSream).ReadToEnd() 
End If       

Private Function GetHeadersFromRawRequest(ByVal request() As Byte) As String 

     Dim Req As String = Text.Encoding.ASCII.GetString(request) 
     Dim ContentPos As Integer = Req.IndexOf(vbNewLine & vbNewLine) 

     If ContentPos = -1 Then Return String.Empty 

     Return Req.Substring(0, ContentPos) 
    End Function 
Verwandte Themen