2009-06-25 Gonzalo Paniagua Javier <gonzalo@novell.com>
authorGonzalo Paniagua Javier <gonzalo.mono@gmail.com>
Thu, 25 Jun 2009 23:23:24 +0000 (23:23 -0000)
committerGonzalo Paniagua Javier <gonzalo.mono@gmail.com>
Thu, 25 Jun 2009 23:23:24 +0000 (23:23 -0000)
* WebAsyncResult.cs: new field that tells us if the request is being
written automatically because we reached the content length.
* WebConnection.cs:
* WebConnectionStream.cs: make sure we only access the NetworkStream
while we own it. Automatically write the request when we reach
ContentLength number of bytes. Kill the write buffer as soon as
possible. Don't use the buffer at all when chunked encoding is
enabled.

Fixes bugs #515931 and #510642.

svn path=/trunk/mcs/; revision=136900

mcs/class/System/System.Net/ChangeLog
mcs/class/System/System.Net/WebAsyncResult.cs
mcs/class/System/System.Net/WebConnection.cs
mcs/class/System/System.Net/WebConnectionStream.cs

index 2a6221984c1b6a6dfa835efb66ad9aca8d67ef36..08ae1c41a2c92363e44b6f66b87dc3a5721efcb8 100644 (file)
@@ -1,3 +1,16 @@
+2009-06-25 Gonzalo Paniagua Javier <gonzalo@novell.com>
+
+       * WebAsyncResult.cs: new field that tells us if the request is being
+       written automatically because we reached the content length.
+       * WebConnection.cs:
+       * WebConnectionStream.cs: make sure we only access the NetworkStream
+       while we own it. Automatically write the request when we reach
+       ContentLength number of bytes. Kill the write buffer as soon as
+       possible. Don't use the buffer at all when chunked encoding is
+       enabled.
+
+       Fixes bugs #515931 and #510642.
+
 2009-06-24 Gonzalo Paniagua Javier <gonzalo@novell.com>
 
        * WebConnection.cs: 'socket' and 'Data' where being changed by 2
index 6d0a3f431ee357ef6d6b2d8b4e389889d9610066..3c0a3154d476462e8235c66cb4d80e07db3cb1ec 100644 (file)
@@ -51,6 +51,7 @@ namespace System.Net
                int size;
                object locker = new object ();
                public bool EndCalled;
+               public bool AsyncWriteAll;
 
                public WebAsyncResult (AsyncCallback cb, object state)
                {
index c0b4ac9f42bdbf46b982f30c0dd4ef2e5246ed2d..d33c9ba3f34c11217831472d65cef3a7e34f8f18 100644 (file)
@@ -740,10 +740,14 @@ namespace System.Net
                }
 
 
-               internal IAsyncResult BeginRead (byte [] buffer, int offset, int size, AsyncCallback cb, object state)
+               internal IAsyncResult BeginRead (HttpWebRequest request, byte [] buffer, int offset, int size, AsyncCallback cb, object state)
                {
-                       if (nstream == null)
-                               return null;
+                       lock (this) {
+                               if (Data.request != request)
+                                       throw new ObjectDisposedException (typeof (NetworkStream).FullName);
+                               if (nstream == null)
+                                       return null;
+                       }
 
                        IAsyncResult result = null;
                        if (!chunkedRead || chunkStream.WantMore) {
@@ -770,10 +774,14 @@ namespace System.Net
                        return result;
                }
                
-               internal int EndRead (IAsyncResult result)
+               internal int EndRead (HttpWebRequest request, IAsyncResult result)
                {
-                       if (nstream == null)
-                               return 0;
+                       lock (this) {
+                               if (Data.request != request)
+                                       throw new ObjectDisposedException (typeof (NetworkStream).FullName);
+                               if (nstream == null)
+                                       throw new ObjectDisposedException (typeof (NetworkStream).FullName);
+                       }
 
                        int nbytes = 0;
                        WebAsyncResult wr = null;
@@ -852,12 +860,17 @@ namespace System.Net
 
                        return true;
                }
-               internal IAsyncResult BeginWrite (byte [] buffer, int offset, int size, AsyncCallback cb, object state)
+
+               internal IAsyncResult BeginWrite (HttpWebRequest request, byte [] buffer, int offset, int size, AsyncCallback cb, object state)
                {
-                       IAsyncResult result = null;
-                       if (nstream == null)
-                               return null;
+                       lock (this) {
+                               if (Data.request != request)
+                                       throw new ObjectDisposedException (typeof (NetworkStream).FullName);
+                               if (nstream == null)
+                                       return null;
+                       }
 
+                       IAsyncResult result = null;
                        try {
                                result = nstream.BeginWrite (buffer, offset, size, cb, state);
                        } catch (Exception) {
@@ -868,10 +881,33 @@ namespace System.Net
                        return result;
                }
 
-               internal bool EndWrite (IAsyncResult result)
+               internal void EndWrite2 (HttpWebRequest request, IAsyncResult result)
                {
-                       if (nstream == null)
-                               return false;
+                       lock (this) {
+                               if (Data.request != request)
+                                       throw new ObjectDisposedException (typeof (NetworkStream).FullName);
+                               if (nstream == null)
+                                       throw new ObjectDisposedException (typeof (NetworkStream).FullName);
+                       }
+
+                       try {
+                               nstream.EndWrite (result);
+                       } catch (Exception exc) {
+                               status = WebExceptionStatus.SendFailure;
+                               if (exc.InnerException != null)
+                                       throw exc.InnerException;
+                               throw;
+                       }
+               }
+
+               internal bool EndWrite (HttpWebRequest request, IAsyncResult result)
+               {
+                       lock (this) {
+                               if (Data.request != request)
+                                       throw new ObjectDisposedException (typeof (NetworkStream).FullName);
+                               if (nstream == null)
+                                       throw new ObjectDisposedException (typeof (NetworkStream).FullName);
+                       }
 
                        try {
                                nstream.EndWrite (result);
@@ -882,10 +918,14 @@ namespace System.Net
                        }
                }
 
-               internal int Read (byte [] buffer, int offset, int size)
+               internal int Read (HttpWebRequest request, byte [] buffer, int offset, int size)
                {
-                       if (nstream == null)
-                               return 0;
+                       lock (this) {
+                               if (Data.request != request)
+                                       throw new ObjectDisposedException (typeof (NetworkStream).FullName);
+                               if (nstream == null)
+                                       return 0;
+                       }
 
                        int result = 0;
                        try {
@@ -917,10 +957,14 @@ namespace System.Net
                        return result;
                }
 
-               internal bool Write (byte [] buffer, int offset, int size)
+               internal bool Write (HttpWebRequest request, byte [] buffer, int offset, int size)
                {
-                       if (nstream == null)
-                               return false;
+                       lock (this) {
+                               if (Data.request != request)
+                                       throw new ObjectDisposedException (typeof (NetworkStream).FullName);
+                               if (nstream == null)
+                                       return false;
+                       }
 
                        try {
                                nstream.Write (buffer, offset, size);
@@ -965,6 +1009,7 @@ namespace System.Net
                                }
 
                                busy = false;
+                               Data = new WebConnectionData ();
                                if (sendNext)
                                        SendNext ();
                        }
@@ -979,7 +1024,6 @@ namespace System.Net
                                                if (!req.FinishedReading) {
                                                        status = WebExceptionStatus.RequestCanceled;
                                                        Close (false);
-                                                       Data = new WebConnectionData ();
                                                        if (queue.Count > 0) {
                                                                Data.request = (HttpWebRequest) queue.Dequeue ();
                                                                SendRequest (Data.request);
index d74ebbabfc08740819e7b3c0db8036cd5f6e23ff..5693e277ae9b02d032446d4efe87af5dc17a3995 100644 (file)
@@ -122,6 +122,10 @@ namespace System.Net
                        }
                }
 
+               internal HttpWebRequest Request {
+                       get { return request; }
+               }
+
                internal WebConnection Connection {
                        get { return cnc; }
                }
@@ -240,7 +244,7 @@ namespace System.Net
                                                buffer = new byte [8192];
 
                                        int read;
-                                       while ((read = cnc.Read (buffer, 0, buffer.Length)) != 0)
+                                       while ((read = cnc.Read (request, buffer, 0, buffer.Length)) != 0)
                                                ms.Write (buffer, 0, read);
 
                                        b = ms.GetBuffer ();
@@ -259,7 +263,7 @@ namespace System.Net
                                        int remaining = new_size - diff;
                                        int r = -1;
                                        while (remaining > 0 && r != 0) {
-                                               r = cnc.Read (b, diff, remaining);
+                                               r = cnc.Read (request, b, diff, remaining);
                                                remaining -= r;
                                                diff += r;
                                        }
@@ -366,7 +370,7 @@ namespace System.Net
                                size = contentLength - totalRead;
 
                        if (!read_eof) {
-                               result.InnerAsyncResult = cnc.BeginRead (buffer, offset, size, cb, result);
+                               result.InnerAsyncResult = cnc.BeginRead (request, buffer, offset, size, cb, result);
                        } else {
                                result.SetCompleted (true, result.NBytes);
                                result.DoCallback ();
@@ -387,7 +391,7 @@ namespace System.Net
                        if (!result.IsCompleted) {
                                int nbytes = -1;
                                try {
-                                       nbytes = cnc.EndRead (result);
+                                       nbytes = cnc.EndRead (request, result);
                                } catch (Exception exc) {
                                        lock (locker) {
                                                pendingReads--;
@@ -425,7 +429,25 @@ namespace System.Net
                        int nb = result.NBytes;
                        return (nb >= 0) ? nb : 0;
                }
-               
+
+               void WriteRequestAsyncCB (IAsyncResult r)
+               {
+                       WebAsyncResult result = (WebAsyncResult) r.AsyncState;
+                       try {
+                               cnc.EndWrite2 (request, r);
+                               result.SetCompleted (false, 0);
+                               if (!initRead) {
+                                       initRead = true;
+                                       WebConnection.InitRead (cnc);
+                               }
+                       } catch (Exception e) {
+                               result.SetCompleted (false, e);
+                       }
+                       headersSent = true;
+                       complete_request_written = true;
+                       result.DoCallback ();
+               }
+
                public override IAsyncResult BeginWrite (byte [] buffer, int offset, int size,
                                                        AsyncCallback cb, object state)
                {
@@ -449,14 +471,26 @@ namespace System.Net
                        WebAsyncResult result = new WebAsyncResult (cb, state);
                        if (!sendChunked)
                                CheckWriteOverflow (request.ContentLength, totalWritten, size);
-                       if (allowBuffering) {
+                       if (allowBuffering && !sendChunked) {
                                writeBuffer.Write (buffer, offset, size);
                                totalWritten += size;
-                               if (!sendChunked) {
+                               if (request.ContentLength > 0 && totalWritten == request.ContentLength) {
+                                       try {
+                                               result.AsyncWriteAll = true;
+                                               result.InnerAsyncResult = WriteRequestAsync (new AsyncCallback (WriteRequestAsyncCB), result);
+                                               if (result.InnerAsyncResult == null) {
+                                                       result.SetCompleted (true, 0);
+                                                       result.DoCallback ();
+                                               }
+                                       } catch (Exception exc) {
+                                               result.SetCompleted (true, exc);
+                                               result.DoCallback ();
+                                       }
+                               } else {
                                        result.SetCompleted (true, 0);
                                        result.DoCallback ();
-                                       return result;
                                }
+                               return result;
                        }
 
                        AsyncCallback callback = null;
@@ -479,21 +513,25 @@ namespace System.Net
                                size = chunkSize;
                        }
 
-                       result.InnerAsyncResult = cnc.BeginWrite (buffer, offset, size, callback, result);
+                       result.InnerAsyncResult = cnc.BeginWrite (request, buffer, offset, size, callback, result);
                        totalWritten += size;
                        return result;
                }
 
-               static void CheckWriteOverflow (long contentLength, long totalWritten, long size)
+               void CheckWriteOverflow (long contentLength, long totalWritten, long size)
                {
                        if (contentLength == -1)
                                return;
 
                        long avail = contentLength - totalWritten;
-                       if (size > avail)
+                       if (size > avail) {
+                               KillBuffer ();
+                               nextReadCalled = true;
+                               cnc.Close (true);
                                throw new ProtocolViolationException (
                                        "The number of bytes to be written is greater than " +
                                        "the specified ContentLength.");
+                       }
                }
 
                public override void EndWrite (IAsyncResult r)
@@ -509,6 +547,12 @@ namespace System.Net
                                return;
 
                        result.EndCalled = true;
+                       if (result.AsyncWriteAll) {
+                               result.WaitUntilComplete ();
+                               if (result.GotException)
+                                       throw result.Exception;
+                               return;
+                       }
 
                        if (allowBuffering && !sendChunked)
                                return;
@@ -516,18 +560,19 @@ namespace System.Net
                        if (result.GotException)
                                throw result.Exception;
 
-                       try { 
-                               cnc.EndWrite (result.InnerAsyncResult);
+                       try {
+                               cnc.EndWrite (request, result.InnerAsyncResult);
                                result.SetCompleted (false, 0);
                        } catch (Exception e) {
                                result.SetCompleted (false, e);
-                       }
-
-                       if (sendChunked) {
-                               lock (locker) {
-                                       pendingWrites--;
-                                       if (pendingWrites == 0)
-                                               pending.Set ();
+                               throw;
+                       } finally {
+                               if (sendChunked) {
+                                       lock (locker) {
+                                               pendingWrites--;
+                                               if (pendingWrites == 0)
+                                                       pending.Set ();
+                                       }
                                }
                        }
                }
@@ -540,6 +585,7 @@ namespace System.Net
                        AsyncCallback cb = new AsyncCallback (WriteCallbackWrapper);
                        WebAsyncResult res = (WebAsyncResult) BeginWrite (buffer, offset, size, cb, null);
                        if (!res.IsCompleted && !res.WaitUntilComplete (WriteTimeout, false)) {
+                               KillBuffer ();
                                nextReadCalled = true;
                                cnc.Close (true);
                                throw new IOException ("Write timed out.");
@@ -559,12 +605,16 @@ namespace System.Net
 
                        if (!allowBuffering || sendChunked) {
                                headersSent = true;
-                               if (!cnc.Connected)
+                               if (!cnc.Connected) {
+                                       KillBuffer ();
                                        throw new WebException ("Not connected", null, WebExceptionStatus.SendFailure, null);
+                               }
 
                                
-                               if (!cnc.Write (buffer, offset, size))
+                               if (!cnc.Write (request, buffer, offset, size)) {
+                                       KillBuffer ();
                                        throw new WebException ("Error writing request.", null, WebExceptionStatus.SendFailure, null);
+                               }
 
                                if (!initRead) {
                                        initRead = true;
@@ -580,6 +630,38 @@ namespace System.Net
                        get { return requestWritten; }
                }
 
+               IAsyncResult WriteRequestAsync (AsyncCallback cb, object state)
+               {
+                       requestWritten = true;
+                       byte [] bytes = writeBuffer.GetBuffer ();
+                       int length = (int) writeBuffer.Length;
+                       writeBuffer = null;
+                       request.InternalContentLength = length;
+                       request.SendRequestHeaders ();
+
+                       //
+                       // For small requests, make a copy, it will reduce the traffic, for large
+                       // requests, the NoDelay bit on the socket should take effect (set in WebConnection).
+                       //
+                       if (headers.Length + length < 8192){
+                               byte[] b = new byte [headers.Length + length];
+
+                               Buffer.BlockCopy (headers, 0, b, 0, headers.Length);
+                               Buffer.BlockCopy (bytes, 0, b, headers.Length, length);
+
+                               return cnc.BeginWrite (request, b, 0, b.Length, cb, state);
+                       } else {
+                               if (!cnc.Write (request, headers, 0, headers.Length))
+                                       throw new WebException ("Error writing request.", null, WebExceptionStatus.SendFailure, null);
+                               
+                               headersSent = true;
+                               if (cnc.Data.StatusCode != 0 && cnc.Data.StatusCode != 100)
+                                       return null;
+                               
+                               return (length > 0) ? cnc.BeginWrite (request, bytes, 0, length, cb, state) : null;
+                       }
+               }
+
                internal void WriteRequest ()
                {
                        if (requestWritten)
@@ -596,7 +678,10 @@ namespace System.Net
 
                        byte [] bytes = writeBuffer.GetBuffer ();
                        int length = (int) writeBuffer.Length;
+                       writeBuffer = null;
                        if (request.ContentLength != -1 && request.ContentLength < length) {
+                               nextReadCalled = true;
+                               cnc.Close (true);
                                throw new WebException ("Specified Content-Length is less than the number of bytes to write", null,
                                                        WebExceptionStatus.ServerProtocolViolation, null);
                        }
@@ -615,7 +700,7 @@ namespace System.Net
                                Buffer.BlockCopy (headers, 0, b, 0, headers.Length);
                                Buffer.BlockCopy (bytes, 0, b, headers.Length, length);
                                
-                               if (!cnc.Write (b, 0, b.Length))
+                               if (!cnc.Write (request, b, 0, b.Length))
                                        throw new WebException ("Error writing request.", null, WebExceptionStatus.SendFailure, null);
                                
                                headersSent = true;
@@ -626,7 +711,7 @@ namespace System.Net
                                        WebConnection.InitRead (cnc);
                                }                               
                        } else {
-                               if (!cnc.Write (headers, 0, headers.Length))
+                               if (!cnc.Write (request, headers, 0, headers.Length))
                                        throw new WebException ("Error writing request.", null, WebExceptionStatus.SendFailure, null);
                                
                                headersSent = true;
@@ -635,15 +720,15 @@ namespace System.Net
                                
                                IAsyncResult result = null;
                                if (length > 0)
-                                       result = cnc.BeginWrite (bytes, 0, length, null, null);
+                                       result = cnc.BeginWrite (request, bytes, 0, length, null, null);
                                
                                if (!initRead) {
                                        initRead = true;
                                        WebConnection.InitRead (cnc);
                                }
-                               
+
                                if (length > 0) 
-                                       complete_request_written = cnc.EndWrite (result);
+                                       complete_request_written = cnc.EndWrite (request, result);
                                else
                                        complete_request_written = true;
                        }
@@ -654,20 +739,12 @@ namespace System.Net
                        disposed = true;
                }
 
-               internal void ForceCloseConnection ()
-               {
-                       if (!disposed) {
-                               disposed = true;
-                               cnc.Close (true);
-                       }
-               }
-
                public override void Close ()
                {
                        if (sendChunked) {
                                pending.WaitOne ();
                                byte [] chunk = Encoding.ASCII.GetBytes ("0\r\n\r\n");
-                               cnc.Write (chunk, 0, chunk.Length);
+                               cnc.Write (request, chunk, 0, chunk.Length);
                                return;
                        }
 
@@ -690,7 +767,7 @@ namespace System.Net
                                return;
                        }
 
-                       if (disposed)
+                       if (disposed || requestWritten)
                                return;
 
                        long length = request.ContentLength;