Archive for May, 2007

C# HTTP Client

May 14, 2007

This is just a quick class I threw together for SQL Server 2005 – we needed a way to download XML from a web site within a stored procedure. On the surface, this sounds like a gross over-use of the CLR, but it was an elegant solution to an otherwise difficult problem for the specific case we were working on. The code is linked to this post.

HTTP Client code (download ZIP)

   1: using System;
   2: using System.Collections.Generic;
   3: using System.IO;
   4: using System.Net;
   5: using System.Net.Sockets;
   6: using System.Text;
   7: using System.Threading;
   8: using System.Xml;
   9:  
  10: namespace Synapseware.Web
  11: {
  12:     /// <summary>
  13:     /// HttpClient class
  14:     /// </summary>
  15:     public static class HttpClient
  16:     {
  17:         /// <summary>
  18:         /// Calls the target site using an HTTP/1.0 GET.
  19:         /// </summary>
  20:         /// <param name="url">Request URL</param>
  21:         /// <param name="timeOut">Timeout to wait for response, in milliseconds.</param>
  22:         /// <returns></returns>
  23:         public static string GetString (string url, int timeOut)
  24:         {
  25:             Socket        sock            = null;
  26:             string        host            = null;                // host name of request
  27:             string        path            = null;                // path portion of request
  28:             string        request            = null;
  29:             string        response        = null;                // body of HTTP response
  30:             string []    data            = null;                // header block converted to string []
  31:             string []    parts            = null;
  32:             int            pos                = 0;
  33:             int            status            = 0;                // HTTP response code
  34:             int            hdrLen            = 0;                // length of header block
  35:             SocketState    state            = null;
  36:             Dictionary<string, string>
  37:                         headers            = null;                // HTTP headers
  38:  
  39:             try
  40:             {
  41:                 // we don't support SSL!
  42:                 if (url.StartsWith ("https://"))
  43:                     return null;
  44:  
  45:                 // parse up the request string
  46:                 if (url.StartsWith ("http://"))
  47:                     host = url.Substring (7);
  48:                 else
  49:                     host = url;
  50:  
  51:                 // split at the first path delimiter
  52:                 if (-1 != (pos = host.IndexOf ('/')))
  53:                 {
  54:                     path = host.Substring (pos);
  55:                     host = host.Substring (0, pos);
  56:                 }
  57:  
  58:                 // make sure path is correct
  59:                 if (String.IsNullOrEmpty (path) || !path.StartsWith ("/"))
  60:                     path = String.Concat ("/", (path ?? ""));
  61:  
  62:                 // connect to server
  63:                 sock = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  64:                 sock.Blocking = true;
  65:                 sock.Connect (host, 80);
  66:  
  67:                 // create state object
  68:                 state = new SocketState (sock);
  69:  
  70:                 // build request
  71:                 request = String.Format (
  72: @"GET {0} HTTP/1.0
  73: HOST: {1}
  74: Accept: text/\*
  75: Accept-Language: en-us
  76: Cache-Control: no-cache
  77: Content-Length: 0
  78:  
  79: ", path, host);
  80:  
  81:                 // send request and wait for it to complete
  82:                 Send (state, request);
  83:                 state.Done.WaitOne (5000, false);
  84:                 state.Done.Reset ();
  85:  
  86:                 // get the response
  87:                 StartReceive (state);
  88:                 state.Done.WaitOne ();
  89:  
  90:                 // convert the response bytes
  91:                 response = state.Data.ToString ();
  92:  
  93:                 // check for HTTP header end
  94:                 if (-1 == (hdrLen = response.IndexOf ("\r\n\r\n")))
  95:                     return null;
  96:  
  97:                 // parse out header data
  98:                 data = response.Substring (0, hdrLen).Replace ("\r\n", "\n").Split ('\n');
  99:                 if (null == data || 0 == data.Length)
 100:                     return null;
 101:  
 102:                 // get response status code
 103:                 parts = data [0].Split (' ');
 104:                 status = Int32.Parse (parts [1]);
 105:                 if (200 != status)
 106:                     return null;
 107:  
 108:                 // turn header array collection into a collection
 109:                 headers = new Dictionary<string, string> ();
 110:                 foreach (string hdr in data)
 111:                 {
 112:                     if (-1 == (pos = hdr.IndexOf (' ')))
 113:                         continue;
 114:                     if (hdr.StartsWith ("HTTP"))
 115:                         continue;
 116:                     headers.Add (hdr.Substring (0, pos), hdr.Substring (pos + 1));
 117:                 }
 118:  
 119:                 return response.Substring (hdrLen + 4);
 120:             }
 121:             catch
 122:             {
 123:                 return null;
 124:             }
 125:             finally
 126:             {
 127:                 // close the connection
 128:                 if (null != sock && sock.Connected)
 129:                 {
 130:                     sock.Disconnect (false);
 131:                     sock.Close ();
 132:                 }
 133:             }
 134:         }
 135:  
 136:         /// <summary>
 137:         /// Sends string data over the socket.
 138:         /// </summary>
 139:         /// <param name="client"></param>
 140:         /// <param name="data"></param>
 141:         private static void Send (SocketState state, string data)
 142:         {
 143:             // Convert the string data to byte data using ASCII encoding.
 144:             byte [] byteData = Encoding.ASCII.GetBytes (data);
 145:  
 146:             // Begin sending the data to the remote device.
 147:             state.Socket.BeginSend (byteData, 0, byteData.Length, 0,
 148:                 new AsyncCallback (SendCallback), state);
 149:         }
 150:  
 151:         /// <summary>
 152:         /// Send data callback.
 153:         /// </summary>
 154:         /// <param name="ar"></param>
 155:         private static void SendCallback (IAsyncResult ar)
 156:         {
 157:             SocketState    state        = null;
 158:             Socket        client        = null;
 159:  
 160:             try
 161:             {
 162:                 // Retrieve the socket from the state object.
 163:                 state = ar.AsyncState as SocketState;
 164:                 client = state.Socket;
 165:  
 166:                 // Complete sending the data to the remote device.
 167:                 client.EndSend (ar);
 168:  
 169:                 // Signal that all bytes have been sent.
 170:                 state.Done.Set ();
 171:             }
 172:             catch
 173:             { }
 174:         }
 175:  
 176:         /// <summary>
 177:         /// Main receive thread entry point
 178:         /// </summary>
 179:         /// <param name="stateInfo"></param>
 180:         private static void StartReceive (SocketState state)
 181:         {
 182:             if (null == state)
 183:                 return;
 184:  
 185:             state.Socket.BeginReceive (
 186:                   state.ReadBuffer,
 187:                   0,
 188:                   state.ReadBuffer.Length,
 189:                   SocketFlags.None,
 190:                   new AsyncCallback (OnReceiveData),
 191:                   state
 192:             );
 193:         }
 194:  
 195:         /// <summary>
 196:         /// Receive data callback.
 197:         /// </summary>
 198:         /// <param name="stateInfo"></param>
 199:         private static void OnReceiveData (IAsyncResult result)
 200:         {
 201:             SocketState        state        = null;
 202:             Socket            sock        = null;
 203:             int                read        = 0;
 204:  
 205:             if (null == result)
 206:                 return;
 207:  
 208:             if (null == (state = result.AsyncState as SocketState))
 209:                 return;
 210:  
 211:             // get socket
 212:             sock = state.Socket;
 213:  
 214:             // end the read
 215:             if (0 != (read = sock.EndReceive (result)))
 216:             {
 217:                 state.Data.Append (Encoding.ASCII.GetString (state.ReadBuffer, 0, read));
 218:  
 219:                 // start another read
 220:                 sock.BeginReceive (
 221:                        state.ReadBuffer,
 222:                        0,
 223:                        state.ReadBuffer.Length,
 224:                        SocketFlags.None,
 225:                        new AsyncCallback (OnReceiveData),
 226:                        state
 227:                 );
 228:             }
 229:             else
 230:             {
 231:                 if (state.Data.Length > 0)
 232:                 {
 233:                     state.Socket.Close ();
 234:                     state.Done.Set ();
 235:                 }
 236:             }
 237:         }
 238:     }
 239:  
 240:     /// <summary>
 241:     /// SocketState class
 242:     /// 
 243:     /// Used to track an async TCP/IP data, and maintain all data received over the async operation.
 244:     /// </summary>
 245:     internal class SocketState
 246:     {
 247:         /// <summary>
 248:         /// Constructor
 249:         /// </summary>
 250:         public SocketState ()
 251:         {
 252:             Socket        = null;
 253:             ReadBuffer    = new byte [BUFFER_SIZE];
 254:             Data        = new StringBuilder ();
 255:             Done        = new ManualResetEvent (false);
 256:         }
 257:  
 258:         /// <summary>
 259:         /// Constructor
 260:         /// </summary>
 261:         /// <param name="socket"></param>
 262:         public SocketState (Socket socket)
 263:             : this ()
 264:         {
 265:             Socket = socket;
 266:         }
 267:  
 268:         /// <summary>
 269:         /// TCP/IP socket.
 270:         /// </summary>
 271:         public Socket Socket = null;
 272:  
 273:         /// <summary>
 274:         /// Size of download buffer.
 275:         /// </summary>
 276:         public const int BUFFER_SIZE = 1024;
 277:  
 278:         /// <summary>
 279:         /// Download buffer.
 280:         /// </summary>
 281:         public byte [] ReadBuffer;
 282:  
 283:         /// <summary>
 284:         /// Downloaded data converted to a string builder.
 285:         /// </summary>
 286:         public StringBuilder Data;
 287:  
 288:         /// <summary>
 289:         /// Reset event for synchronization.
 290:         /// </summary>
 291:         public ManualResetEvent Done;
 292:     }
 293: }