using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Net.Sockets;
using System.Collections;
namespace LPT_5.HTTPSocket
{
class Socket
{
private System.Net.Sockets.Socket msSock = null;
private String msHost = null;
private int miPort = 80;
private Hashtable mhHeaders=null;
public Socket(String sHost, int iPort)
{
msHost = sHost;
miPort = iPort;
//Initialise Standard Headers
mhHeaders = new Hashtable();
AddHeader("Connection","Close"); //We wont need to do any pipelining
AddHeader("Host", sHost);
}
public void AddHeader(String sKey, String sValue)
{
if(!mhHeaders.ContainsKey(sKey))
mhHeaders.Add(sKey,sValue);
}
private void RemoveHeader(String sKey)
{
if(mhHeaders.ContainsKey(sKey))
mhHeaders.Remove(sKey);
}
public String GetURL(String sURL)
{
msSock = new System.Net.Sockets.Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
msSock.Connect(msHost, miPort);
String sReq = "GET " + sURL + " HTTP/1.1";
sReq = TagHeaders(sReq);
msSock.Send(System.Text.Encoding.ASCII.GetBytes(sReq));
Hashtable htHeadParams = new Hashtable();
int iResult = ReceiveHeader(ref htHeadParams);
String sBody = null;
if(htHeadParams.ContainsKey("Content-Length"))
{
int iLen = Convert.ToInt32((String)htHeadParams["Content-Length"],10);
int iOffs = 0;
byte[] bBuff = new byte[iLen];
while (iOffs < iLen)
{
iOffs += msSock.Receive(bBuff,iOffs,iLen-iOffs,SocketFlags.None);
}
sBody += System.Text.Encoding.ASCII.GetString(bBuff);
}
msSock.Close();
return sBody;
}
public byte[] GetRaw(String sURL)
{
msSock = new System.Net.Sockets.Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
msSock.Connect(msHost, miPort);
String sReq = "GET " + sURL + " HTTP/1.1";
sReq = TagHeaders(sReq);
msSock.Send(System.Text.Encoding.ASCII.GetBytes(sReq));
Hashtable htHeadParams = new Hashtable();
if(ReceiveHeader(ref htHeadParams) == 200) //If we got the data with no problems
{
int iLen = Convert.ToInt32((String)htHeadParams["Content-Length"], 10);
byte[] bBuff = new byte[iLen];
if (htHeadParams.ContainsKey("Content-Length"))
{
int iOffs = 0;
while (iOffs < iLen)
{
iOffs += msSock.Receive(bBuff, iOffs, iLen - iOffs, SocketFlags.None);
}
}
msSock.Close();
return bBuff;
}
return null;
}
private String TagHeaders(String sReq)
{
IDictionaryEnumerator deEnumer = mhHeaders.GetEnumerator();
while(deEnumer.MoveNext())
{
sReq += "\r\n" + deEnumer.Key + ": " + deEnumer.Value;
}
return sReq + "\r\n\r\n";
}
private int ReceiveHeader(ref Hashtable htHash)
{
String sHead = null;
byte[] buff = new byte[1];
/*
We dont know the size of the header, but we do know that header and
body are separated by a double newline, so read a byte at a time until
we receive double newline.
*/
do
{
msSock.Receive(buff, 0, 1, SocketFlags.None);
sHead += (char)buff[0];
}
while (!sHead.Contains("\r\n\r\n"));
int iResult = ProcessHeaders(sHead,ref htHash);
return iResult;
}
private int ProcessHeaders(String sHead, ref Hashtable htHash)
{
//Split wont work with \r\n, so just regex away the carriage return
String sToSplit = Regex.Replace(sHead,"\r","");
String[] sParams = sToSplit.Split('\n');
foreach (String sItem in sParams)
{
String[] sKeyVal = sItem.Split(':');
if(sKeyVal.Length >= 2)
{
String sKey = sKeyVal[0].Trim();
String sVal = sKeyVal[1].Trim();
if (htHash.ContainsKey(sKey))
htHash[sKey] += ',' + sVal; //Group multiple headers
else
htHash.Add(sKey, sVal);
}
}
String[] sResultParams = sParams[0].Split(' ');
return Convert.ToInt32(sResultParams[1]); //Return the HTTP Response Code
}
}
}
Refactorings
No refactoring yet !
Josh N
April 5, 2009, April 05, 2009 22:56, permalink
is there a reason for not using the built in HttpWebRequest/HttpWebResponse classes? (maybe im missing something..)
using System;
using System.Net;
public class App {
static void Main() {
HttpWebRequest request = WebRequest.Create("http://www.contoso.com/") as HttpWebRequest;
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
Console.WriteLine(response.StatusCode);
foreach (var key in response.Headers.AllKeys)
Console.WriteLine("{0} = {1}", key, response.Headers[key]);
response.Close();
}
}
Josh N
April 5, 2009, April 05, 2009 22:58, permalink
ooops.. sorry missed your comment about WebRequest my bad..
teariok.myopenid.com
April 5, 2009, April 05, 2009 23:18, permalink
It was simply a learning exercise. I haven't used C# a great deal and I enjoy this kind of programming. Plus for another project I will likely be using sockets for TCP communication using a custom protocol, so working on improving my socket code would help with that.
Jackson Savitraz
June 3, 2009, June 03, 2009 17:57, permalink
Yes, it has!
Because HttpWebRequest/HttpWebResponse classes doesn´t permit to mask the Host header.
eBuster
August 14, 2009, August 14, 2009 14:24, permalink
Sockets also allow you to deal with pages that have a 404 in the headder like some eBay pages and a webrequest with throw an error even when the responce contains the page you are look for but a browser just thinks it's a custom error page and not an advert ! Good try eBay.
i'm looking for a faster way to read the sockets stream without doing it one byte at a time.
This is a HTTP socket that I use in one of my projects. It can't handle anything other than 200 OK, but for the purpose it was intended anything other than that should fail anyway, so I don't need proper HTTP response code handling. I'm just hoping really that if there is a way it could be tidied up, sped up or otherwise improved that someone can show how and then learning can happen. My particular problem is reading the header a byte at a time, I am sure there is a better way but such a method escapes me.
Oh and before you say it, I know I can just use WebRequest. If that is your only criticism, please keep it to yourself.