Convert blocking operations in HttpWebRequest and SslClientStream to non-blocking...
[mono.git] / mcs / class / System / System.Net / HttpWebRequest.cs
index 8b96e1e816913f4b5bdba99866a91f5182ada9bd..6aee5af52bd3bda083667b07cc29d699d178f597 100644 (file)
@@ -46,12 +46,8 @@ using System.Threading;
 
 namespace System.Net 
 {
-#if MOONLIGHT
-       internal class HttpWebRequest : WebRequest, ISerializable {
-#else
        [Serializable]
        public class HttpWebRequest : WebRequest, ISerializable {
-#endif
                Uri requestUri;
                Uri actualUri;
                bool hostChanged;
@@ -140,7 +136,8 @@ namespace System.Net
                        this.actualUri = uri;
                        this.proxy = GlobalProxySelection.Select;
                        this.webHeaders = new WebHeaderCollection (WebHeaderCollection.HeaderInfo.Request);
-               }               
+                       ThrowOnError = true;
+               }
                
                [Obsolete ("Serialization is obsoleted for this type", false)]
                protected HttpWebRequest (SerializationInfo serializationInfo, StreamingContext streamingContext) 
@@ -193,6 +190,13 @@ namespace System.Net
                        get { return allowBuffering; }
                        set { allowBuffering = value; }
                }
+               
+#if NET_4_5
+               public virtual bool AllowReadStreamBuffering {
+                       get { return allowBuffering; }
+                       set { allowBuffering = value; }
+               }
+#endif
 
                static Exception GetMustImplement ()
                {
@@ -271,6 +275,8 @@ namespace System.Net
                internal long InternalContentLength {
                        set { contentLength = value; }
                }
+                       
+               internal bool ThrowOnError { get; set; }
                
                public override string ContentType { 
                        get { return webHeaders ["Content-Type"]; }
@@ -288,6 +294,9 @@ namespace System.Net
                        set { continueDelegate = value; }
                }
                
+#if NET_4_5
+               virtual
+#endif
                public CookieContainer CookieContainer {
                        get { return cookieContainer; }
                        set { cookieContainer = value; }
@@ -306,14 +315,15 @@ namespace System.Net
                                return DateTime.ParseExact (date, "r", CultureInfo.InvariantCulture).ToLocalTime ();
                        }
                        set {
-                               if (value == null) {
+                               if (value.Equals (DateTime.MinValue))
                                        webHeaders.RemoveInternal ("Date");
-                               } else {
+                               else
                                        webHeaders.RemoveAndAdd ("Date", value.ToUniversalTime ().ToString ("r", CultureInfo.InvariantCulture));
-                               }
                        }
                }
 #endif
+
+#if !NET_2_1
                [MonoTODO]
                public static new RequestCachePolicy DefaultCachePolicy
                {
@@ -324,6 +334,7 @@ namespace System.Net
                                throw GetMustImplement ();
                        }
                }
+#endif
                
                [MonoTODO]
                public static int DefaultMaximumErrorResponseLength
@@ -356,6 +367,9 @@ namespace System.Net
                        }
                }
                
+#if NET_4_5
+               virtual
+#endif
                public bool HaveResponse {
                        get { return haveResponse; }
                }
@@ -397,9 +411,6 @@ namespace System.Net
 
                static bool CheckValidHost (string scheme, string val)
                {
-                       if (val == null)
-                               throw new ArgumentNullException ("value");
-
                        if (val.Length == 0)
                                return false;
 
@@ -410,6 +421,10 @@ namespace System.Net
                        if (idx >= 0)
                                return false;
 
+                       IPAddress ipaddr;
+                       if (IPAddress.TryParse (val, out ipaddr))
+                               return true;
+
                        string u = scheme + "://" + val + "/";
                        return Uri.IsWellFormedUriString (u, UriKind.Absolute);
                }
@@ -478,6 +493,14 @@ namespace System.Net
                        }
                }
                
+#if NET_4_5
+               [MonoTODO]
+               public int ContinueTimeout {
+                       get { throw new NotImplementedException (); }
+                       set { throw new NotImplementedException (); }
+               }
+#endif
+               
                public string MediaType {
                        get { return mediaType; }
                        set { 
@@ -557,7 +580,18 @@ namespace System.Net
                public ServicePoint ServicePoint {
                        get { return GetServicePoint (); }
                }
-               
+
+               internal ServicePoint ServicePointNoLock {
+                       get { return servicePoint; }
+               }
+#if NET_4_0
+               [MonoTODO ("for portable library support")]
+               public virtual bool SupportsCookieContainer { 
+                       get {
+                               throw new NotImplementedException ();
+                       }
+               }
+#endif
                public override int Timeout { 
                        get { return timeout; }
                        set {
@@ -755,6 +789,9 @@ namespace System.Net
 
                        lock (locker)
                        {
+                               if (getResponseCalled)
+                                       throw new InvalidOperationException ("The operation cannot be performed once the request has been submitted.");
+
                                if (asyncWrite != null) {
                                        throw new InvalidOperationException ("Cannot re-call start of asynchronous " +
                                                                "method while a previous call is still in progress.");
@@ -819,9 +856,19 @@ namespace System.Net
 
                void CheckIfForceWrite ()
                {
-                       if (writeStream == null || writeStream.RequestWritten || (contentLength < 0 && writeStream.CanWrite == true) || !InternalAllowBuffering)
+                       if (writeStream == null || writeStream.RequestWritten || !InternalAllowBuffering)
+                               return;
+#if NET_4_0
+                       if (contentLength < 0 && writeStream.CanWrite == true && writeStream.WriteBufferLength < 0)
                                return;
 
+                       if (contentLength < 0 && writeStream.WriteBufferLength >= 0)
+                               InternalContentLength = writeStream.WriteBufferLength;
+#else
+                       if (contentLength < 0 && writeStream.CanWrite == true)
+                               return;
+#endif
+
                        // This will write the POST/PUT if the write stream already has the expected
                        // amount of bytes in it (ContentLength) (bug #77753) or if the write stream
                        // contains data and it has been closed already (xamarin bug #1512).
@@ -1116,7 +1163,9 @@ namespace System.Net
                        bool spoint10 = (proto_version == null || proto_version == HttpVersion.Version10);
 
                        if (keepAlive && (version == HttpVersion.Version10 || spoint10)) {
-                               webHeaders.RemoveAndAdd (connectionHeader, "keep-alive");
+                               if (webHeaders[connectionHeader] == null
+                                   || webHeaders[connectionHeader].IndexOf ("keep-alive", StringComparison.OrdinalIgnoreCase) == -1)
+                                       webHeaders.RemoveAndAdd (connectionHeader, "keep-alive");
                        } else if (!keepAlive && version == HttpVersion.Version11) {
                                webHeaders.RemoveAndAdd (connectionHeader, "close");
                        }
@@ -1125,7 +1174,9 @@ namespace System.Net
                        if (cookieContainer != null) {
                                string cookieHeader = cookieContainer.GetCookieHeader (actualUri);
                                if (cookieHeader != "")
-                                       webHeaders.SetInternal ("Cookie", cookieHeader);
+                                       webHeaders.RemoveAndAdd ("Cookie", cookieHeader);
+                               else
+                                       webHeaders.RemoveInternal ("Cookie");
                        }
 
                        string accept_encoding = null;
@@ -1181,7 +1232,7 @@ namespace System.Net
                        }
                }
 
-               internal void SendRequestHeaders (bool propagate_error)
+               internal byte[] GetRequestHeaders ()
                {
                        StringBuilder req = new StringBuilder ();
                        string query;
@@ -1203,18 +1254,7 @@ namespace System.Net
                                                                actualVersion.Major, actualVersion.Minor);
                        req.Append (GetHeaders ());
                        string reqstr = req.ToString ();
-                       byte [] bytes = Encoding.UTF8.GetBytes (reqstr);
-                       try {
-                               writeStream.SetHeaders (bytes);
-                       } catch (WebException wexc) {
-                               SetWriteStreamError (wexc.Status, wexc);
-                               if (propagate_error)
-                                       throw;
-                       } catch (Exception exc) {
-                               SetWriteStreamError (WebExceptionStatus.SendFailure, exc);
-                               if (propagate_error)
-                                       throw;
-                       }
+                       return Encoding.UTF8.GetBytes (reqstr);
                }
 
                internal void SetWriteStream (WebConnectionStream stream)
@@ -1229,14 +1269,32 @@ namespace System.Net
                                writeStream.SendChunked = false;
                        }
 
-                       SendRequestHeaders (false);
+                       byte[] requestHeaders = GetRequestHeaders ();
+                       WebAsyncResult result = new WebAsyncResult (new AsyncCallback (SetWriteStreamCB), null);
+                       writeStream.SetHeadersAsync (requestHeaders, result);
+               }
 
+               void SetWriteStreamCB(IAsyncResult ar)
+               {
+                       WebAsyncResult result = ar as WebAsyncResult;
+
+                       if (result.Exception != null) {
+                               WebException wexc = result.Exception as WebException;
+                               if (wexc != null) {
+                                       SetWriteStreamError (wexc.Status, wexc);
+                                       return;
+                               }
+                               SetWriteStreamError (WebExceptionStatus.SendFailure, result.Exception);
+                               return;
+                       }
+               
                        haveRequest = true;
-                       
+
                        if (bodyBuffer != null) {
                                // The body has been written and buffered. The request "user"
                                // won't write it again, so we must do it.
                                if (ntlm_auth_state != NtlmAuthState.Challenge) {
+                                       // FIXME: this is a blocking call on the thread pool that could lead to thread pool exhaustion
                                        writeStream.Write (bodyBuffer, 0, bodyBufferLength);
                                        bodyBuffer = null;
                                        writeStream.Close ();
@@ -1244,11 +1302,12 @@ namespace System.Net
                        } else if (method != "HEAD" && method != "GET" && method != "MKCOL" && method != "CONNECT" &&
                                        method != "TRACE") {
                                if (getResponseCalled && !writeStream.RequestWritten)
+                                       // FIXME: this is a blocking call on the thread pool that could lead to thread pool exhaustion
                                        writeStream.WriteRequest ();
                        }
 
                        if (asyncWrite != null) {
-                               asyncWrite.SetCompleted (false, stream);
+                               asyncWrite.SetCompleted (false, writeStream);
                                asyncWrite.DoCallback ();
                                asyncWrite = null;
                        }
@@ -1437,7 +1496,7 @@ namespace System.Net
                        if (isProxy && (proxy == null || proxy.Credentials == null))
                                return false;
 
-                       string [] authHeaders = response.Headers.GetValues ( (isProxy) ? "Proxy-Authenticate" : "WWW-Authenticate");
+                       string [] authHeaders = response.Headers.GetValues_internal ( (isProxy) ? "Proxy-Authenticate" : "WWW-Authenticate", false);
                        if (authHeaders == null || authHeaders.Length == 0)
                                return false;
 
@@ -1491,13 +1550,16 @@ namespace System.Net
                                                        bodyBuffer = null;
                                                        return true;
                                                }
-                                               
+
+                                               if (!ThrowOnError)
+                                                       return false;
+                                                       
                                                writeStream.InternalClose ();
                                                writeStream = null;
                                                webResponse.Close ();
                                                webResponse = null;
                                                bodyBuffer = null;
-
+                                                       
                                                throw new WebException ("This request requires buffering " +
                                                                        "of data for authentication or " +
                                                                        "redirection to be sucessful.");
@@ -1531,6 +1593,8 @@ namespace System.Net
                                                bodyBufferLength = writeStream.WriteBufferLength;
                                        }
                                        b = Redirect (result, code);
+                                       if (b && ntlm_auth_state != 0)
+                                               ntlm_auth_state = 0;
                                }
 
                                if (resp != null && c >= 300 && c != 304)
@@ -1538,6 +1602,9 @@ namespace System.Net
 
                                return b;
                        }
+                               
+                       if (!ThrowOnError)
+                               return false;
 
                        if (writeStream != null) {
                                writeStream.InternalClose ();
@@ -1548,6 +1615,13 @@ namespace System.Net
 
                        throw throwMe;
                }
+
+               internal bool ReuseConnection {
+                       get;
+                       set;
+               }
+
+               internal WebConnection StoredConnection;
        }
 }