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)
73 socket = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
75 status = sPoint.Connect (socket);
80 bool CreateStream (HttpWebRequest request)
82 //TODO: create stream for https
84 nstream = new NetworkStream (socket, false);
85 } catch (Exception e) {
86 status = WebExceptionStatus.ConnectFailure;
93 void HandleError (WebExceptionStatus st, Exception e)
97 if (e == null) { // At least we now where it comes from
99 throw new Exception ();
100 } catch (Exception e2) {
105 if (Data != null && Data.request != null)
106 Data.request.SetResponseError (st, e);
109 internal bool WaitForContinue (byte [] headers, int offset, int size)
112 waitingForContinue = sPoint.SendContinue;
113 if (waitingForContinue && waitForContinue == null)
114 waitForContinue = new AutoResetEvent (false);
116 Write (headers, offset, size);
117 if (!waitingForContinue)
120 bool result = waitForContinue.WaitOne (2000, false);
121 waitingForContinue = false;
123 sPoint.SendContinue = true;
124 if (Data.request.ExpectContinue)
125 Data.request.DoContinueDelegate (Data.StatusCode, Data.Headers);
127 sPoint.SendContinue = false;
133 static void ReadDone (IAsyncResult result)
135 WebConnection cnc = (WebConnection) result.AsyncState;
136 WebConnectionData data = cnc.Data;
137 NetworkStream ns = cnc.nstream;
142 cnc.dataAvailable.Reset ();
144 nread = ns.EndRead (result);
145 } catch (Exception e) {
146 cnc.status = WebExceptionStatus.ReceiveFailure;
147 cnc.HandleError (cnc.status, e);
148 cnc.dataAvailable.Set ();
153 Console.WriteLine ("nread == 0: may be the connection was closed?");
154 cnc.dataAvailable.Set ();
159 cnc.HandleError (WebExceptionStatus.ServerProtocolViolation, null);
160 cnc.dataAvailable.Set ();
164 //Console.WriteLine (System.Text.Encoding.Default.GetString (cnc.buffer, 0, nread));
166 if (cnc.readState == ReadState.None) {
167 Exception exc = null;
169 pos = cnc.GetResponse (cnc.buffer, nread);
170 if (data.StatusCode == 100) {
171 cnc.readState = ReadState.None;
173 cnc.sPoint.SendContinue = true;
174 if (cnc.waitingForContinue) {
175 cnc.waitForContinue.Set ();
176 } else if (data.request.ExpectContinue) { // We get a 100 after waiting for it.
177 data.request.DoContinueDelegate (data.StatusCode, data.Headers);
182 } catch (Exception e) {
186 if (pos == -1 || exc != null) {
187 cnc.HandleError (WebExceptionStatus.ServerProtocolViolation, exc);
188 cnc.dataAvailable.Set ();
193 if (cnc.readState != ReadState.Content) {
194 cnc.HandleError (WebExceptionStatus.ServerProtocolViolation, null);
195 cnc.dataAvailable.Set ();
199 WebConnectionStream stream = new WebConnectionStream (cnc);
201 string contentType = data.Headers ["Transfer-Encoding"];
202 cnc.chunkedRead = (contentType != null && contentType.ToLower ().IndexOf ("chunked") != -1);
203 if (!cnc.chunkedRead) {
204 stream.ReadBuffer = cnc.buffer;
205 stream.ReadBufferOffset = pos;
206 stream.ReadBufferSize = nread;
207 } else if (cnc.chunkStream == null) {
208 cnc.chunkStream = new ChunkStream (cnc.buffer, pos, nread, data.Headers);
210 cnc.chunkStream.ResetBuffer ();
211 cnc.chunkStream.Write (cnc.buffer, pos, nread);
214 cnc.prevStream = stream;
215 data.stream = stream;
216 data.request.SetResponseData (data);
217 stream.CheckComplete ();
220 static void InitRead (object state)
222 WebConnection cnc = (WebConnection) state;
223 NetworkStream ns = cnc.nstream;
225 ns.BeginRead (cnc.buffer, 0, cnc.buffer.Length, readDoneDelegate, cnc);
226 } catch (Exception e) {
227 cnc.HandleError (WebExceptionStatus.ReceiveFailure, e);
228 cnc.dataAvailable.Set ();
232 int GetResponse (byte [] buffer, int max)
238 if (readState == ReadState.None) {
239 lineok = ReadLine (buffer, ref pos, max, ref line);
243 readState = ReadState.Status;
245 string [] parts = line.Split (' ');
246 if (parts.Length < 3)
249 if (String.Compare (parts [0], "HTTP/1.1", true) == 0) {
250 Data.Version = HttpVersion.Version11;
252 Data.Version = HttpVersion.Version10;
255 Data.StatusCode = (int) UInt32.Parse (parts [1]);
256 Data.StatusDescription = String.Join (" ", parts, 2, parts.Length - 2);
261 if (readState == ReadState.Status) {
262 readState = ReadState.Headers;
263 Data.Headers = new WebHeaderCollection ();
264 ArrayList headers = new ArrayList ();
265 bool finished = false;
267 if (ReadLine (buffer, ref pos, max, ref line) == false)
271 // Empty line: end of headers
276 if (line.Length > 0 && (line [0] == ' ' || line [0] == '\t')) {
277 int count = headers.Count - 1;
281 string prev = (string) headers [count] + line;
282 headers [count] = prev;
289 // handle the error...
291 foreach (string s in headers)
292 Data.Headers.Add (s);
294 readState = ReadState.Content;
302 void InitConnection (object state, bool notUsed)
304 HttpWebRequest request = (HttpWebRequest) state;
306 status = WebExceptionStatus.RequestCanceled;
307 request.SetWriteStreamError (status);
312 if (status != WebExceptionStatus.Success) {
313 request.SetWriteStreamError (status);
318 if (!CreateStream (request)) {
319 request.SetWriteStreamError (status);
324 readState = ReadState.None;
325 request.SetWriteStream (new WebConnectionStream (this, request));
329 void BeginRequest (HttpWebRequest request)
332 keepAlive = request.KeepAlive;
334 Data.request = request;
337 ThreadPool.RegisterWaitForSingleObject (dataAvailable, initConn, request, -1, true);
340 internal EventHandler SendRequest (HttpWebRequest request)
342 Monitor.Enter (this);
344 if (prevStream != null && socket != null && socket.Connected) {
345 prevStream.ReadAll ();
352 BeginRequest (request);
361 internal void NextRead ()
363 Monitor.Enter (this);
364 string cncHeader = (Data.Headers != null) ? Data.Headers ["Connection"] : null;
365 // Bug in xsp...? It does not send Connection: close. Well. it's 1.0
366 if (Data.Version == HttpVersion.Version10 || (socket != null && !socket.Connected) || (!keepAlive ||
367 (cncHeader != null && cncHeader.IndexOf ("close") != -1))) {
372 dataAvailable.Set ();
374 if (queue.Count > 0) {
375 HttpWebRequest request = (HttpWebRequest) queue [0];
378 SendRequest (request);
384 static bool ReadLine (byte [] buffer, ref int start, int max, ref string output)
386 bool foundCR = false;
387 StringBuilder text = new StringBuilder ();
390 while (start < max) {
391 c = (int) buffer [start++];
393 if (c == '\n') { // newline
394 if ((text.Length > 0) && (text [text.Length - 1] == '\r'))
399 } else if (foundCR) {
408 text.Append ((char) c);
411 if (c != '\n' && c != '\r')
414 if (text.Length == 0) {
416 return (c == '\n' || c == '\r');
422 output = text.ToString ();
426 internal IAsyncResult BeginRead (byte [] buffer, int offset, int size, AsyncCallback cb, object state)
431 IAsyncResult result = null;
432 if (!chunkedRead || chunkStream.WantMore) {
434 result = nstream.BeginRead (buffer, offset, size, cb, state);
435 } catch (Exception e) {
436 status = WebExceptionStatus.ReceiveFailure;
442 WebAsyncResult wr = new WebAsyncResult (null, null, buffer, offset, size);
443 wr.InnerAsyncResult = result;
450 internal int EndRead (IAsyncResult result)
456 WebAsyncResult wr = (WebAsyncResult) result;
458 if (wr.InnerAsyncResult != null)
459 nbytes = nstream.EndRead (wr.InnerAsyncResult);
461 chunkStream.WriteAndReadBack (wr.Buffer, wr.Offset, wr.Size, ref nbytes);
465 return nstream.EndRead (result);
468 internal IAsyncResult BeginWrite (byte [] buffer, int offset, int size, AsyncCallback cb, object state)
470 IAsyncResult result = null;
475 result = nstream.BeginWrite (buffer, offset, size, cb, state);
476 } catch (Exception e) {
477 status = WebExceptionStatus.SendFailure;
484 internal void EndWrite (IAsyncResult result)
487 nstream.EndWrite (result);
490 internal int Read (byte [] buffer, int offset, int size)
497 if (!chunkedRead || chunkStream.WantMore)
498 result = nstream.Read (buffer, offset, size);
501 chunkStream.WriteAndReadBack (buffer, offset, size, ref result);
502 } catch (Exception e) {
503 status = WebExceptionStatus.ReceiveFailure;
504 HandleError (status, e);
510 internal void Write (byte [] buffer, int offset, int size)
516 nstream.Write (buffer, offset, size);
517 } catch (Exception e) {
518 status = WebExceptionStatus.SendFailure;
519 HandleError (status, e);
525 if (nstream != null) {
532 if (socket != null) {
540 void Abort (object sender, EventArgs args)
542 HandleError (WebExceptionStatus.RequestCanceled, null);