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: }