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;
33 WaitOrTimerCallback initConn;
37 static AsyncCallback readDoneDelegate = new AsyncCallback (ReadDone);
38 EventHandler abortHandler;
40 internal WebConnectionData Data;
41 WebConnectionStream prevStream;
43 ChunkStream chunkStream;
44 AutoResetEvent waitForContinue;
45 AutoResetEvent goAhead;
46 bool waitingForContinue;
50 public WebConnection (WebConnectionGroup group, ServicePoint sPoint)
54 buffer = new byte [4096];
55 readState = ReadState.None;
56 Data = new WebConnectionData ();
57 initConn = new WaitOrTimerCallback (InitConnection);
58 abortHandler = new EventHandler (Abort);
59 goAhead = new AutoResetEvent (true);
60 queue = new Queue (1);
63 public void Connect ()
66 if (socket != null && socket.Connected && status == WebExceptionStatus.Success) {
78 IPHostEntry hostEntry = sPoint.HostEntry;
80 if (hostEntry == null) {
81 status = sPoint.UsesProxy ? WebExceptionStatus.ProxyNameResolutionFailure :
82 WebExceptionStatus.NameResolutionFailure;
86 foreach (IPAddress address in hostEntry.AddressList) {
87 socket = new Socket (address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
89 socket.Connect (new IPEndPoint(address, sPoint.Address.Port));
90 status = WebExceptionStatus.Success;
92 } catch (SocketException) {
95 status = WebExceptionStatus.ConnectFailure;
101 bool CreateStream (HttpWebRequest request)
103 //TODO: create stream for https
105 nstream = new NetworkStream (socket, false);
106 } catch (Exception) {
107 status = WebExceptionStatus.ConnectFailure;
114 void HandleError (WebExceptionStatus st, Exception e)
119 if (st == WebExceptionStatus.RequestCanceled)
120 Data = new WebConnectionData ();
125 if (e == null) { // At least we now where it comes from
127 throw new Exception (new System.Diagnostics.StackTrace ().ToString ());
128 } catch (Exception e2) {
133 if (Data != null && Data.request != null)
134 Data.request.SetResponseError (st, e);
139 internal bool WaitForContinue (byte [] headers, int offset, int size)
141 waitingForContinue = sPoint.SendContinue;
142 if (waitingForContinue && waitForContinue == null)
143 waitForContinue = new AutoResetEvent (false);
145 Write (headers, offset, size);
146 if (!waitingForContinue)
149 bool result = waitForContinue.WaitOne (2000, false);
150 waitingForContinue = false;
152 sPoint.SendContinue = true;
153 if (Data.request.ExpectContinue)
154 Data.request.DoContinueDelegate (Data.StatusCode, Data.Headers);
156 sPoint.SendContinue = false;
162 static void ReadDone (IAsyncResult result)
164 WebConnection cnc = (WebConnection) result.AsyncState;
165 WebConnectionData data = cnc.Data;
166 NetworkStream ns = cnc.nstream;
174 nread = ns.EndRead (result);
175 } catch (Exception e) {
176 cnc.status = WebExceptionStatus.ReceiveFailure;
177 cnc.HandleError (cnc.status, e);
182 cnc.status = WebExceptionStatus.ReceiveFailure;
183 cnc.HandleError (cnc.status, null);
188 cnc.HandleError (WebExceptionStatus.ServerProtocolViolation, null);
192 //Console.WriteLine (System.Text.Encoding.Default.GetString (cnc.buffer, 0, nread));
194 if (cnc.readState == ReadState.None) {
195 Exception exc = null;
197 pos = cnc.GetResponse (cnc.buffer, nread);
198 if (data.StatusCode == 100) {
199 cnc.readState = ReadState.None;
201 cnc.sPoint.SendContinue = true;
202 if (cnc.waitingForContinue) {
203 cnc.waitForContinue.Set ();
204 } else if (data.request.ExpectContinue) { // We get a 100 after waiting for it.
205 data.request.DoContinueDelegate (data.StatusCode, data.Headers);
210 } catch (Exception e) {
214 if (pos == -1 || exc != null) {
215 cnc.HandleError (WebExceptionStatus.ServerProtocolViolation, exc);
220 if (cnc.readState != ReadState.Content) {
221 cnc.HandleError (WebExceptionStatus.ServerProtocolViolation, null);
225 WebConnectionStream stream = new WebConnectionStream (cnc);
227 string contentType = data.Headers ["Transfer-Encoding"];
228 cnc.chunkedRead = (contentType != null && contentType.ToLower ().IndexOf ("chunked") != -1);
229 if (!cnc.chunkedRead) {
230 stream.ReadBuffer = cnc.buffer;
231 stream.ReadBufferOffset = pos;
232 stream.ReadBufferSize = nread;
233 } else if (cnc.chunkStream == null) {
234 cnc.chunkStream = new ChunkStream (cnc.buffer, pos, nread, data.Headers);
236 cnc.chunkStream.ResetBuffer ();
237 cnc.chunkStream.Write (cnc.buffer, pos, nread);
242 more = (cnc.queue.Count > 0);
243 cnc.prevStream = stream;
246 data.stream = stream;
250 stream.CheckComplete ();
252 data.request.SetResponseData (data);
255 static void InitRead (object state)
257 WebConnection cnc = (WebConnection) state;
258 NetworkStream ns = cnc.nstream;
261 ns.BeginRead (cnc.buffer, 0, cnc.buffer.Length, readDoneDelegate, cnc);
262 } catch (Exception e) {
263 cnc.HandleError (WebExceptionStatus.ReceiveFailure, e);
267 int GetResponse (byte [] buffer, int max)
273 if (readState == ReadState.None) {
274 lineok = ReadLine (buffer, ref pos, max, ref line);
278 readState = ReadState.Status;
280 string [] parts = line.Split (' ');
281 if (parts.Length < 3)
284 if (String.Compare (parts [0], "HTTP/1.1", true) == 0) {
285 Data.Version = HttpVersion.Version11;
287 Data.Version = HttpVersion.Version10;
290 Data.StatusCode = (int) UInt32.Parse (parts [1]);
291 Data.StatusDescription = String.Join (" ", parts, 2, parts.Length - 2);
296 if (readState == ReadState.Status) {
297 readState = ReadState.Headers;
298 Data.Headers = new WebHeaderCollection ();
299 ArrayList headers = new ArrayList ();
300 bool finished = false;
302 if (ReadLine (buffer, ref pos, max, ref line) == false)
306 // Empty line: end of headers
311 if (line.Length > 0 && (line [0] == ' ' || line [0] == '\t')) {
312 int count = headers.Count - 1;
316 string prev = (string) headers [count] + line;
317 headers [count] = prev;
324 // handle the error...
326 foreach (string s in headers)
327 Data.Headers.Add (s);
329 readState = ReadState.Content;
337 void InitConnection (object state, bool notUsed)
339 HttpWebRequest request = (HttpWebRequest) state;
341 if (status == WebExceptionStatus.RequestCanceled) {
343 Data = new WebConnectionData ();
350 keepAlive = request.KeepAlive;
351 Data = new WebConnectionData ();
352 Data.request = request;
355 if (status != WebExceptionStatus.Success) {
356 request.SetWriteStreamError (status);
361 if (!CreateStream (request)) {
362 request.SetWriteStreamError (status);
367 readState = ReadState.None;
368 request.SetWriteStream (new WebConnectionStream (this, request));
372 internal EventHandler SendRequest (HttpWebRequest request)
375 if (prevStream != null && socket != null && socket.Connected) {
376 prevStream.ReadAll ();
382 ThreadPool.RegisterWaitForSingleObject (goAhead, initConn,
385 queue.Enqueue (request);
395 if (queue.Count > 0) {
397 SendRequest ((HttpWebRequest) queue.Dequeue ());
402 internal void NextRead ()
406 string header = (sPoint.UsesProxy) ? "Proxy-Connection" : "Connection";
407 string cncHeader = (Data.Headers != null) ? Data.Headers [header] : null;
408 bool keepAlive = (Data.Version == HttpVersion.Version11);
409 if (cncHeader != null) {
410 cncHeader = cncHeader.ToLower ();
411 keepAlive = (this.keepAlive && cncHeader.IndexOf ("keep-alive") != -1);
414 if ((socket != null && !socket.Connected) ||
415 (!keepAlive || (cncHeader != null && cncHeader.IndexOf ("close") != -1))) {
420 if (queue.Count > 0) {
422 SendRequest ((HttpWebRequest) queue.Dequeue ());
427 static bool ReadLine (byte [] buffer, ref int start, int max, ref string output)
429 bool foundCR = false;
430 StringBuilder text = new StringBuilder ();
433 while (start < max) {
434 c = (int) buffer [start++];
436 if (c == '\n') { // newline
437 if ((text.Length > 0) && (text [text.Length - 1] == '\r'))
442 } else if (foundCR) {
451 text.Append ((char) c);
454 if (c != '\n' && c != '\r')
457 if (text.Length == 0) {
459 return (c == '\n' || c == '\r');
465 output = text.ToString ();
469 internal IAsyncResult BeginRead (byte [] buffer, int offset, int size, AsyncCallback cb, object state)
474 IAsyncResult result = null;
475 if (!chunkedRead || chunkStream.WantMore) {
477 result = nstream.BeginRead (buffer, offset, size, cb, state);
478 } catch (Exception) {
479 status = WebExceptionStatus.ReceiveFailure;
485 WebAsyncResult wr = new WebAsyncResult (null, null, buffer, offset, size);
486 wr.InnerAsyncResult = result;
493 internal int EndRead (IAsyncResult result)
499 WebAsyncResult wr = (WebAsyncResult) result;
501 if (wr.InnerAsyncResult != null)
502 nbytes = nstream.EndRead (wr.InnerAsyncResult);
504 chunkStream.WriteAndReadBack (wr.Buffer, wr.Offset, wr.Size, ref nbytes);
508 return nstream.EndRead (result);
511 internal IAsyncResult BeginWrite (byte [] buffer, int offset, int size, AsyncCallback cb, object state)
513 IAsyncResult result = null;
518 result = nstream.BeginWrite (buffer, offset, size, cb, state);
519 } catch (Exception) {
520 status = WebExceptionStatus.SendFailure;
527 internal void EndWrite (IAsyncResult result)
530 nstream.EndWrite (result);
533 internal int Read (byte [] buffer, int offset, int size)
540 if (!chunkedRead || chunkStream.WantMore)
541 result = nstream.Read (buffer, offset, size);
544 chunkStream.WriteAndReadBack (buffer, offset, size, ref result);
545 } catch (Exception e) {
546 status = WebExceptionStatus.ReceiveFailure;
547 HandleError (status, e);
553 internal void Write (byte [] buffer, int offset, int size)
559 nstream.Write (buffer, offset, size);
560 } catch (Exception) {
564 internal bool TryReconnect ()
568 HandleError (WebExceptionStatus.SendFailure, null);
575 if (status != WebExceptionStatus.Success) {
576 HandleError (WebExceptionStatus.SendFailure, null);
580 if (!CreateStream (Data.request)) {
581 HandleError (WebExceptionStatus.SendFailure, null);
588 void Close (bool sendNext)
592 if (nstream != null) {
599 if (socket != null) {
613 void Abort (object sender, EventArgs args)
615 HandleError (WebExceptionStatus.RequestCanceled, null);
619 get { lock (this) return busy; }
622 internal bool Connected {
625 return (socket != null && socket.Connected);