2 // System.Net.WebConnection
5 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 // (C) 2003 Ximian, Inc (http://www.ximian.com)
10 using System.Collections;
11 using System.Net.Sockets;
13 using System.Threading;
28 NetworkStream nstream;
30 WebExceptionStatus status;
31 WebConnectionGroup group;
34 WaitOrTimerCallback initConn;
35 internal ManualResetEvent dataAvailable;
39 internal static AsyncCallback readDoneDelegate = new AsyncCallback (ReadDone);
40 EventHandler abortHandler;
42 internal WebConnectionData Data;
43 WebConnectionStream prevStream;
45 ChunkStream chunkStream;
46 AutoResetEvent waitForContinue;
47 bool waitingForContinue;
49 public WebConnection (WebConnectionGroup group, ServicePoint sPoint)
53 queue = new ArrayList (1);
54 dataAvailable = new ManualResetEvent (true);
55 buffer = new byte [4096];
56 readState = ReadState.None;
57 Data = new WebConnectionData ();
58 initConn = new WaitOrTimerCallback (InitConnection);
59 abortHandler = new EventHandler (Abort);
62 public void Connect ()
64 if (socket != null && socket.Connected && status == WebExceptionStatus.Success)
68 if (socket != null && socket.Connected && status == WebExceptionStatus.Success)
76 IPHostEntry hostEntry = sPoint.HostEntry;
78 if(hostEntry == null) {
79 status = sPoint.UsesProxy ? WebExceptionStatus.ProxyNameResolutionFailure :
\r
80 WebExceptionStatus.NameResolutionFailure;
\r
83 foreach(IPAddress address in hostEntry.AddressList) {
\r
84 socket = new Socket (address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
87 socket.Connect (new IPEndPoint(address, sPoint.Address.Port));
\r
88 status = WebExceptionStatus.Success;
\r
91 catch (SocketException e2) {
\r
93 status = WebExceptionStatus.ConnectFailure;
\r
102 bool CreateStream (HttpWebRequest request)
104 //TODO: create stream for https
106 nstream = new NetworkStream (socket, false);
107 } catch (Exception e) {
108 status = WebExceptionStatus.ConnectFailure;
115 void HandleError (WebExceptionStatus st, Exception e)
119 if (e == null) { // At least we now where it comes from
121 throw new Exception ();
122 } catch (Exception e2) {
127 if (Data != null && Data.request != null)
128 Data.request.SetResponseError (st, e);
131 internal bool WaitForContinue (byte [] headers, int offset, int size)
134 waitingForContinue = sPoint.SendContinue;
135 if (waitingForContinue && waitForContinue == null)
136 waitForContinue = new AutoResetEvent (false);
138 Write (headers, offset, size);
139 if (!waitingForContinue)
142 bool result = waitForContinue.WaitOne (2000, false);
143 waitingForContinue = false;
145 sPoint.SendContinue = true;
146 if (Data.request.ExpectContinue)
147 Data.request.DoContinueDelegate (Data.StatusCode, Data.Headers);
149 sPoint.SendContinue = false;
155 static void ReadDone (IAsyncResult result)
157 WebConnection cnc = (WebConnection) result.AsyncState;
158 WebConnectionData data = cnc.Data;
159 NetworkStream ns = cnc.nstream;
164 cnc.dataAvailable.Reset ();
166 nread = ns.EndRead (result);
167 } catch (Exception e) {
168 cnc.status = WebExceptionStatus.ReceiveFailure;
169 cnc.HandleError (cnc.status, e);
170 cnc.dataAvailable.Set ();
175 Console.WriteLine ("nread == 0: may be the connection was closed?");
176 cnc.dataAvailable.Set ();
181 cnc.HandleError (WebExceptionStatus.ServerProtocolViolation, null);
182 cnc.dataAvailable.Set ();
186 //Console.WriteLine (System.Text.Encoding.Default.GetString (cnc.buffer, 0, nread));
188 if (cnc.readState == ReadState.None) {
189 Exception exc = null;
191 pos = cnc.GetResponse (cnc.buffer, nread);
192 if (data.StatusCode == 100) {
193 cnc.readState = ReadState.None;
195 cnc.sPoint.SendContinue = true;
196 if (cnc.waitingForContinue) {
197 cnc.waitForContinue.Set ();
198 } else if (data.request.ExpectContinue) { // We get a 100 after waiting for it.
199 data.request.DoContinueDelegate (data.StatusCode, data.Headers);
204 } catch (Exception e) {
208 if (pos == -1 || exc != null) {
209 cnc.HandleError (WebExceptionStatus.ServerProtocolViolation, exc);
210 cnc.dataAvailable.Set ();
215 if (cnc.readState != ReadState.Content) {
216 cnc.HandleError (WebExceptionStatus.ServerProtocolViolation, null);
217 cnc.dataAvailable.Set ();
221 WebConnectionStream stream = new WebConnectionStream (cnc);
223 string contentType = data.Headers ["Transfer-Encoding"];
224 cnc.chunkedRead = (contentType != null && contentType.ToLower ().IndexOf ("chunked") != -1);
225 if (!cnc.chunkedRead) {
226 stream.ReadBuffer = cnc.buffer;
227 stream.ReadBufferOffset = pos;
228 stream.ReadBufferSize = nread;
229 } else if (cnc.chunkStream == null) {
230 cnc.chunkStream = new ChunkStream (cnc.buffer, pos, nread, data.Headers);
232 cnc.chunkStream.ResetBuffer ();
233 cnc.chunkStream.Write (cnc.buffer, pos, nread);
236 cnc.prevStream = stream;
237 data.stream = stream;
238 data.request.SetResponseData (data);
239 stream.CheckComplete ();
242 static void InitRead (object state)
244 WebConnection cnc = (WebConnection) state;
245 NetworkStream ns = cnc.nstream;
248 ns.BeginRead (cnc.buffer, 0, cnc.buffer.Length, readDoneDelegate, cnc);
249 } catch (Exception e) {
250 cnc.HandleError (WebExceptionStatus.ReceiveFailure, e);
251 cnc.dataAvailable.Set ();
255 int GetResponse (byte [] buffer, int max)
261 if (readState == ReadState.None) {
262 lineok = ReadLine (buffer, ref pos, max, ref line);
266 readState = ReadState.Status;
268 string [] parts = line.Split (' ');
269 if (parts.Length < 3)
272 if (String.Compare (parts [0], "HTTP/1.1", true) == 0) {
273 Data.Version = HttpVersion.Version11;
275 Data.Version = HttpVersion.Version10;
278 Data.StatusCode = (int) UInt32.Parse (parts [1]);
279 Data.StatusDescription = String.Join (" ", parts, 2, parts.Length - 2);
284 if (readState == ReadState.Status) {
285 readState = ReadState.Headers;
286 Data.Headers = new WebHeaderCollection ();
287 ArrayList headers = new ArrayList ();
288 bool finished = false;
290 if (ReadLine (buffer, ref pos, max, ref line) == false)
294 // Empty line: end of headers
299 if (line.Length > 0 && (line [0] == ' ' || line [0] == '\t')) {
300 int count = headers.Count - 1;
304 string prev = (string) headers [count] + line;
305 headers [count] = prev;
312 // handle the error...
314 foreach (string s in headers)
315 Data.Headers.Add (s);
317 readState = ReadState.Content;
325 void InitConnection (object state, bool notUsed)
327 HttpWebRequest request = (HttpWebRequest) state;
329 status = WebExceptionStatus.RequestCanceled;
330 request.SetWriteStreamError (status);
335 if (status != WebExceptionStatus.Success) {
336 request.SetWriteStreamError (status);
341 if (!CreateStream (request)) {
342 request.SetWriteStreamError (status);
347 readState = ReadState.None;
348 request.SetWriteStream (new WebConnectionStream (this, request));
352 void BeginRequest (HttpWebRequest request)
355 keepAlive = request.KeepAlive;
357 Data.request = request;
360 ThreadPool.RegisterWaitForSingleObject (dataAvailable, initConn, request, -1, true);
363 internal EventHandler SendRequest (HttpWebRequest request)
365 Monitor.Enter (this);
367 if (prevStream != null && socket != null && socket.Connected) {
368 prevStream.ReadAll ();
375 BeginRequest (request);
384 internal void NextRead ()
386 Monitor.Enter (this);
387 string header = (sPoint.UsesProxy) ? "Proxy-Connection" : "Connection";
388 string cncHeader = (Data.Headers != null) ? Data.Headers [header] : null;
389 bool keepAlive = this.keepAlive;
390 if (cncHeader != null) {
391 cncHeader = cncHeader.ToLower ();
392 keepAlive = (keepAlive && cncHeader.IndexOf ("keep-alive") != -1);
395 if ((socket != null && !socket.Connected) ||
396 (!keepAlive || (cncHeader != null && cncHeader.IndexOf ("close") != -1))) {
401 dataAvailable.Set ();
403 if (queue.Count > 0) {
404 HttpWebRequest request = (HttpWebRequest) queue [0];
407 SendRequest (request);
413 static bool ReadLine (byte [] buffer, ref int start, int max, ref string output)
415 bool foundCR = false;
416 StringBuilder text = new StringBuilder ();
419 while (start < max) {
420 c = (int) buffer [start++];
422 if (c == '\n') { // newline
423 if ((text.Length > 0) && (text [text.Length - 1] == '\r'))
428 } else if (foundCR) {
437 text.Append ((char) c);
440 if (c != '\n' && c != '\r')
443 if (text.Length == 0) {
445 return (c == '\n' || c == '\r');
451 output = text.ToString ();
455 internal IAsyncResult BeginRead (byte [] buffer, int offset, int size, AsyncCallback cb, object state)
460 IAsyncResult result = null;
461 if (!chunkedRead || chunkStream.WantMore) {
463 result = nstream.BeginRead (buffer, offset, size, cb, state);
464 } catch (Exception e) {
465 status = WebExceptionStatus.ReceiveFailure;
471 WebAsyncResult wr = new WebAsyncResult (null, null, buffer, offset, size);
472 wr.InnerAsyncResult = result;
479 internal int EndRead (IAsyncResult result)
485 WebAsyncResult wr = (WebAsyncResult) result;
487 if (wr.InnerAsyncResult != null)
488 nbytes = nstream.EndRead (wr.InnerAsyncResult);
490 chunkStream.WriteAndReadBack (wr.Buffer, wr.Offset, wr.Size, ref nbytes);
494 return nstream.EndRead (result);
497 internal IAsyncResult BeginWrite (byte [] buffer, int offset, int size, AsyncCallback cb, object state)
499 IAsyncResult result = null;
504 result = nstream.BeginWrite (buffer, offset, size, cb, state);
505 } catch (Exception e) {
506 status = WebExceptionStatus.SendFailure;
513 internal void EndWrite (IAsyncResult result)
516 nstream.EndWrite (result);
519 internal int Read (byte [] buffer, int offset, int size)
526 if (!chunkedRead || chunkStream.WantMore)
527 result = nstream.Read (buffer, offset, size);
530 chunkStream.WriteAndReadBack (buffer, offset, size, ref result);
531 } catch (Exception e) {
532 status = WebExceptionStatus.ReceiveFailure;
533 HandleError (status, e);
539 internal void Write (byte [] buffer, int offset, int size)
545 nstream.Write (buffer, offset, size);
546 } catch (Exception e) {
547 status = WebExceptionStatus.SendFailure;
548 HandleError (status, e);
555 if (nstream != null) {
562 if (socket != null) {
571 void Abort (object sender, EventArgs args)
573 HandleError (WebExceptionStatus.RequestCanceled, null);