Sockets in C#: How to get the response stream?
Asked Answered
M

3

11

I'm trying to replace this:

void ProcessRequest(object listenerContext)
{
    var context = (HttpListenerContext)listenerContext;
    Uri URL = new Uri(context.Request.RawUrl);
    HttpWebRequest.DefaultWebProxy = null;
    HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(URL);
    httpWebRequest.Method = context.Request.HttpMethod;
    httpWebRequest.Headers.Clear();
    if (context.Request.UserAgent != null) httpWebRequest.UserAgent = context.Request.UserAgent;
    foreach (string headerKey in context.Request.Headers.AllKeys)
    {
        try { httpWebRequest.Headers.Set(headerKey, context.Request.Headers[headerKey]); }
            catch (Exception) { }
    }

    using (HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse())
    {
        Stream responseStream = httpWebResponse.GetResponseStream();
        if (httpWebResponse.ContentEncoding.ToLower().Contains("gzip"))
            responseStream = new GZipStream(responseStream, CompressionMode.Decompress);
        else if (httpWebResponse.ContentEncoding.ToLower().Contains("deflate"))
                responseStream = new DeflateStream(responseStream, CompressionMode.Decompress);

        MemoryStream memStream = new MemoryStream();

        byte[] respBuffer = new byte[4096];
        try
        {
            int bytesRead = responseStream.Read(respBuffer, 0, respBuffer.Length);
            while (bytesRead > 0)
            {
                memStream.Write(respBuffer, 0, bytesRead);
                bytesRead = responseStream.Read(respBuffer, 0, respBuffer.Length);
            }
        }
        finally
        {
            responseStream.Close();
        }

        byte[] msg = memStream.ToArray();

        context.Response.ContentLength64 = msg.Length;
        using (Stream strOut = context.Response.OutputStream)
        {
            strOut.Write(msg, 0, msg.Length);
        }
    }
    catch (Exception ex)
    {
        // Some error handling
    }
}

with sockets. This is what I have so far:

void ProcessRequest(object listenerContext)
{
    HttpListenerContext context = (HttpListenerContext)listenerContext;
    Uri URL = new Uri(context.Request.RawUrl);
    string getString = string.Format("GET {0} HTTP/1.1\r\nHost: {1}\r\nAccept-Encoding: gzip\r\n\r\n",
                context.Request.Url.PathAndQuery,
                context.Request.UserHostName);

    Socket socket = null;

    string[] hostAndPort;
    if (context.Request.UserHostName.Contains(":"))
    {
        hostAndPort = context.Request.UserHostName.Split(':');
    }
    else
    {
        hostAndPort = new string[] { context.Request.UserHostName, "80" };
    }

    IPHostEntry ipAddress = Dns.GetHostEntry(hostAndPort[0]);
    IPEndPoint ip = new IPEndPoint(IPAddress.Parse(ipAddress.AddressList[0].ToString()), int.Parse(hostAndPort[1]));
    socket = new Socket(ip.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
    socket.Connect(ip);

BEGIN NEW CODE

Encoding ASCII = Encoding.ASCII;
Byte[] byteGetString = ASCII.GetBytes(getString);
Byte[] receiveByte = new Byte[256];
string response = string.Empty;
socket.Send(byteGetString, byteGetString.Length, 0);
Int32 bytes = socket.Receive(receiveByte, receiveByte.Length, 0);
response += ASCII.GetString(receiveByte, 0, bytes);
while (bytes > 0)
{
bytes = socket.Receive(receiveByte, receiveByte.Length, 0);
strPage = strPage + ASCII.GetString(receiveByte, 0, bytes);
}
socket.Close();

string separator = "\r\n\r\n";
string header = strPage.Substring(0,strPage.IndexOf(separator));
string content = strPage.Remove(0, strPage.IndexOf(separator) + 4);

byte[] byteResponse = ASCII.GetBytes(content);
context.Response.ContentLength64 = byteResponse .Length;
context.Response.OutputStream.Write(byteResponse , 0, byteResponse .Length);
context.Response.OutputStream.Close();

END NEW CODE

After connecting to the socket I don't know how to get the Stream response to decompress, and send back to context.Response.OutputStream

Any help will be appreciated. Thanks. Cheers.

EDIT 2: With this edit now seems to be working fine (same as HttpWebRequest at least). Do you find any error here?

EDIT 3: False alarm... Still can't get this working

EDIT 4: I needed to add the following lines to Scott's code ... because not always the first to bytes of reponseStream are the gzip magic number. The sequence seems to be: 0x0a (10), 0x1f (31), 0x8b (139). The last two are the gzip magic number. The first number was always before in my tests.

if (contentEncoding.Equals("gzip"))
{
    int magicNumber = 0;
    while (magicNumber != 10)
        magicNumber = responseStream.ReadByte();
    responseStream = new GZipStream(responseStream, CompressionMode.Decompress);
}
Miriam answered 7/2, 2009 at 15:16 Comment(2)
@Matias: I have to ask, why do you want to do this? You are going to spend so much overhead duplicating what HttpWebRequest and HttpWebResponse already do for you.Pathic
The answer for that is here: #522477Accident
H
11

Here's some code that works for me.

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.IO.Compression;

namespace HttpUsingSockets {
    public class Program {
        private static readonly Encoding DefaultEncoding = Encoding.ASCII;
        private static readonly byte[] LineTerminator = new byte[] { 13, 10 };

        public static void Main(string[] args) {
            var host = "stackoverflow.com";
            var url = "/questions/523930/sockets-in-c-how-to-get-the-response-stream";

            IPHostEntry ipAddress = Dns.GetHostEntry(host);
            var ip = new IPEndPoint(ipAddress.AddressList[0], 80);
            using (var socket = new Socket(ip.AddressFamily, SocketType.Stream, ProtocolType.Tcp)) {
                socket.Connect(ip);
                using (var n = new NetworkStream(socket)) {
                    SendRequest(n, new[] {"GET " + url + " HTTP/1.1", "Host: " + host, "Connection: Close", "Accept-Encoding: gzip"});

                    var headers = new Dictionary<string, string>();
                    while (true) {
                        var line = ReadLine(n);
                        if (line.Length == 0) {
                            break;
                        }
                        int index = line.IndexOf(':');
                        headers.Add(line.Substring(0, index), line.Substring(index + 2));
                    }

                    string contentEncoding;
                    if (headers.TryGetValue("Content-Encoding", out contentEncoding)) {
                        Stream responseStream = n;
                        if (contentEncoding.Equals("gzip")) {
                            responseStream = new GZipStream(responseStream, CompressionMode.Decompress);
                        }
                        else if (contentEncoding.Equals("deflate")) {
                            responseStream = new DeflateStream(responseStream, CompressionMode.Decompress);
                        }

                        var memStream = new MemoryStream();

                        var respBuffer = new byte[4096];
                        try {
                            int bytesRead = responseStream.Read(respBuffer, 0, respBuffer.Length);
                            while (bytesRead > 0) {
                                memStream.Write(respBuffer, 0, bytesRead);
                                bytesRead = responseStream.Read(respBuffer, 0, respBuffer.Length);
                            }
                        }
                        finally {
                            responseStream.Close();
                        }

                        var body = DefaultEncoding.GetString(memStream.ToArray());
                        Console.WriteLine(body);
                    }
                    else {
                        while (true) {
                            var line = ReadLine(n);
                            if (line == null) {
                                break;
                            }
                            Console.WriteLine(line);
                        }
                    }
                }
            }
        }

        static void SendRequest(Stream stream, IEnumerable<string> request) {
            foreach (var r in request) {
                var data = DefaultEncoding.GetBytes(r);
                stream.Write(data, 0, data.Length);
                stream.Write(LineTerminator, 0, 2);
            }
            stream.Write(LineTerminator, 0, 2);
            // Eat response
            var response = ReadLine(stream);
        }

        static string ReadLine(Stream stream) {
            var lineBuffer = new List<byte>();
            while (true) {
                int b = stream.ReadByte();
                if (b == -1) {
                    return null;
                }
                if (b == 10) {
                    break;
                }
                if (b != 13) {
                    lineBuffer.Add((byte)b);
                }
            }
            return DefaultEncoding.GetString(lineBuffer.ToArray());
        }
    }
}

You could substitute this for the Socket/NetworkStream and save a bit of work.

using (var client = new TcpClient(host, 80)) {
      using (var n = client.GetStream()) {
     }
} 
Hutt answered 7/2, 2009 at 15:16 Comment(3)
hey!, thanks for your answer!!!! but I'm getting an Exception: InvalidDataException: The magic number in GZip header is not correct. Make sure you are passing in a GZip stream. The problem seems to be in this line: int bytesRead = responseStream.Read(respBuffer, 0, respBuffer.Length); Any ideas?Accident
Run it in debug and check that the first couple of bytes are 0x1f 0x8b (the gzip magic number), maybe there's an extra line or prefix or something.Hutt
Thanks for answer. I've added a few lines after "if (contentEncoding.Equals("gzip")) {" to seek the beggining of the gzip magic number. It's on my first post. Best regards!Accident
T
4

Socket, by definition, is the low level to access the network. You can even use datagram protocols with a socket. In that case a stream does not make sense at all.

While I'm not sure why are you doing what HttpWebRequest easily accomplishes, to read/write data to a socket, you use the Send/Receive methods. If you want to have a stream like access to a TCP socket, you should use the TcpClient/TcpListener classes which wrap a socket and provide a network stream for it.

Thick answered 7/2, 2009 at 15:16 Comment(0)
C
3

There's also the NetworkStream class that takes a Socket as a parameter. ;)

Crooked answered 7/2, 2009 at 15:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.