Hi@all.
Erstmal gleich vorne weg...
Mir ist durchaus bewusst, dass ich für Http-Request, Klassen wie "HttpWebRequest" oder "WebClient" nutzen sollte.
Mir geht es hier mehr um ein besseres Verständnis des HTTP-Protokolls und wie man dieses verarbeitet. Daher spiele ich z.Z. ein bisschen mit der TcpClient-Klasse herum. Allerdings bin ich jetzt auf ein Problem gestoßen...
Und zwar versuche ich z.Z. Internetseiten auszulesen, welche in Chunks aufgeteilt sind. Der aufbau ist mir soweit klar. Zuerst wird der Header der Seite gesendet. danach kommt die Chunk-Size als Hexadezimal-Zahl gefolgt vom eigentlichen Chunk (natürlich getrennt durch Zeilenumbrüche).
Jetzt habe ich mir mal etwas Code zusammen geschustert, um den ersten Chunk einer internetseite auszulesen (ich bitte darum über die Code-Qualität hinweg zu sehen, der ist nur zu testzwecken und daher absolut quick&dirty):
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace HTTPChunkReader
{
class Program
{
static void Main(string[] args)
{
// ein bisschen Initialisierungs-Gedöhns
TcpClient client = new TcpClient("blog.cowchimp.com", 80);
NetworkStream stream = client.GetStream();
string request = "GET /chunk-scatter-http-chunked-response-analysis-tool/ HTTP/1.1\n";
request += @"Host: blog.cowchimp.com\n";
request += @"User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:61.0) Gecko/20100101 Firefox/61.0\n";
request += @"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\n";
request += @"Accept-Language: en-GB,en;q=0.5\n";
request += @"Accept-Encoding: gzip, deflate\n";
request += @"Connection: keep-alive\n";
request += @"Upgrade-Insecure-Requests: 1\n\n";
stream.Write(Encoding.ASCII.GetBytes(request), 0, Encoding.ASCII.GetBytes(request).Length);
StreamReader reader = new StreamReader(stream);
// einfach nur alle Response-Headerzeilen abzählen und ausgeben
Console.WriteLine(reader.ReadLine());
Console.WriteLine(reader.ReadLine());
Console.WriteLine(reader.ReadLine());
Console.WriteLine(reader.ReadLine());
Console.WriteLine(reader.ReadLine());
Console.WriteLine(reader.ReadLine());
Console.WriteLine(reader.ReadLine());
Console.WriteLine(reader.ReadLine());
Console.WriteLine(reader.ReadLine());
Console.WriteLine(reader.ReadLine());
Console.WriteLine(reader.ReadLine());
Console.WriteLine(reader.ReadLine());
// Chunksize in Dezimalzahl umwandeln und ausgeben
string hexChunkSize = reader.ReadLine();
int decChunkSizeInt = Convert.ToInt32(hexChunkSize, 16);
Console.WriteLine("ChunkSize (HEX): " + hexChunkSize);
Console.WriteLine("ChunkSize (DEC): " + decChunkSizeInt);
// x bytes auslesen
// (wobei "x" für die Chunksize steht)
for (int i = 0; i < decChunkSizeInt; i++)
{
Console.Write(i + ", ");
stream.ReadByte();
}
// Hier müssten jetzt eigentlich die Werte 13, 10, ?, ?, ? ausgegeben werden,
// wobei "?" für die nächsten Hex-Chunksize-Characters (natürlich als Bytes) bzw. einem weitern Zeilenumbruch (13, 10) stehen.
Console.WriteLine("\n");
Console.WriteLine(stream.ReadByte());
Console.WriteLine(stream.ReadByte());
Console.WriteLine(stream.ReadByte());
Console.WriteLine(stream.ReadByte());
Console.WriteLine(stream.ReadByte());
// Fertig
Console.WriteLine("\n\n--- END ---");
Console.ReadKey();
}
}
}
Die letzten 5 Bytes die ich mit diesem Code von der Internetseite erhalte und ausgebe, sollten eigentlich (wie schon im Kommentar beschrieben) "13, 10, ?, ?, ?" sein, wobei "?" für die nächsten Hex-Chunksize-Characters (natürlich als Bytes) bzw. einem weitern Zeilenumbruch (13, 10) stehen.
Allerdings ist das leider nicht der Fall.
Ich hoffe, dass jemand etwas Ahnung davon hat, versteht was ich meine und mir erklären kann, wo genau das Problem liegt?
Hallo,
das liegt daran, dass Du zuerst mit dem StreamReader
auf den Stream losgehst, und anschließend versuchst, direkt einzelne Bytes aus dem Stream zu lesen.
Der StreamReader verwendet einen internen Lesepuffer, den er erst mal füllt. Dadurch liest er Dir dann schon einige Bytes unterm Hintern weg, und wenn Du die einzelnen Bytes lesen willst, ist der NetworkStream
schon ein Stück weiter, als Du es erwartest.
Du kannst das mit dem StreamReader machen, dann musst Du aber alles damit lesen.
Dann brauchst Du aber auch noch einen StreamWriter
, der dir das wieder sauber wegschreibt in einen MemoryStream
.
Achtung: Du brauchst dann ein 8-Bit Encoding im StreamReader und das gleiche nochmal im StreamWriter, mit einem Multibyte-Encoding wird das nix (und mit ASCII auch nicht), vor allem weil Du mit Accept-Encoding: gzip, deflate
mit Sicherheit Binärdaten erhältst. Du kannst z.B. Encoding.Default
verwenden (nicht zu verwechseln mit dem standardmäßigen Encoding des StreamReaders, das ist nämlich UTF8).
Also sowas würde funktioieren:
StreamReader reader = new StreamReader(stream, Encoding.Default);
//... deine ganze Header-Ausgabe
string hexChunkSize = reader.ReadLine();
int decChunkSizeInt = Convert.ToInt32(hexChunkSize, 16);
Console.WriteLine("ChunkSize (HEX): " + hexChunkSize);
Console.WriteLine("ChunkSize (DEC): " + decChunkSizeInt);
MemoryStream ms = new MemoryStream();
StreamWriter writer = new StreamWriter(ms, Encoding.Default);
while (decChunkSizeInt > 0)
{
var content = new char[decChunkSizeInt];
reader.ReadBlock(content, 0, decChunkSizeInt);
writer.Write(content);
// am Ende des Chunks das CRLF lesen
reader.ReadLine();
// und ab in die nächste Runde
hexChunkSize = reader.ReadLine();
decChunkSizeInt = Convert.ToInt32(hexChunkSize, 16);
Console.WriteLine("ChunkSize (HEX): " + hexChunkSize);
Console.WriteLine("ChunkSize (DEC): " + decChunkSizeInt);
}
writer.Flush();
// jezt stehen im MemoryStream genau die Nutzdaten, allerdings binär wegen gzip/deflate,
// die musst Du dann noch auspacken
Die bessere Lösung:
Besser ist es meiner Ansicht nach aber, die Daten gar nicht als Text zu verhackstücken, sondern direkt alles binär aus dem Stream zu lesen - weil es sich ja auch um Binärdaten handelt.
Dazu brauchst Du erst mal eine Hilfsmethode, die ein Zeilenende erkennt. Nach HTTP-Spezifikation kannst Du Dich dabei auf CRLF verlassen:
static byte[] ReadLine(NetworkStream stream)
{
MemoryStream ms = new MemoryStream();
int b;
while((b = stream.ReadByte()) != -1)
{
if (b == 13)
{
b = stream.ReadByte();
if (b == 10 || b == -1)
break;
}
ms.WriteByte((byte)b);
}
return ms.ToArray();
}
Diese kannst Du dann nutzen:
// die Ausgabe der Header erfolgt dann mit
Console.WriteLine(Encoding.ASCII.GetString(ReadLine(stream)));
//... und die anderen Zeilen dazu
// Chunksize in Dezimalzahl umwandeln und ausgeben
string hexChunkSize = Encoding.ASCII.GetString(ReadLine(stream));
int decChunkSizeInt = Convert.ToInt32(hexChunkSize, 16);
Console.WriteLine("ChunkSize (HEX): " + hexChunkSize);
Console.WriteLine("ChunkSize (DEC): " + decChunkSizeInt);
MemoryStream ms = new MemoryStream();
while(decChunkSizeInt > 0)
{
byte[] buffer = new byte[decChunkSizeInt];
stream.Read(buffer, 0, buffer.Length);
ms.Write(buffer, 0, buffer.Length);
// am Ende des Chunks das CRLF lesen
ReadLine(stream);
// und ab in die nächste Runde
hexChunkSize = Encoding.ASCII.GetString(ReadLine(stream));
decChunkSizeInt = Convert.ToInt32(hexChunkSize, 16);
Console.WriteLine("ChunkSize (HEX): " + hexChunkSize);
Console.WriteLine("ChunkSize (DEC): " + decChunkSizeInt);
}
// jezt stehen im MemoryStream genau die Nutzdaten, allerdings binär wegen gzip/deflate,
// die musst Du dann noch auspacken
Gruß, MarsStein
Non quia difficilia sunt, non audemus, sed quia non audemus, difficilia sunt! - Seneca