From ca89d85ab5ad84aef1c673f3a0e6b53e8b0c0df0 Mon Sep 17 00:00:00 2001 From: Alex Blount Date: Fri, 20 May 2016 10:43:49 +0100 Subject: [PATCH] [Mono.Android, Xamarin.Android.Net.AndroidClientHandler] Updated to work with requests which contains content. Also fixed a race condition which causes the Output stream to not be ready. Fixed issues with calling on the MainThread which is not alowed with the Java HttpURLConnection updating logger incorrect calls adding fixes --- .../AndroidClientHandler.cs | 82 +++++++++++-------- 1 file changed, 50 insertions(+), 32 deletions(-) diff --git a/src/Mono.Android/Xamarin.Android.Net/AndroidClientHandler.cs b/src/Mono.Android/Xamarin.Android.Net/AndroidClientHandler.cs index e1d6a138b10..6d8dfcdb551 100644 --- a/src/Mono.Android/Xamarin.Android.Net/AndroidClientHandler.cs +++ b/src/Mono.Android/Xamarin.Android.Net/AndroidClientHandler.cs @@ -91,7 +91,7 @@ public class AndroidClientHandler : HttpClientHandler bool decompress_here; URL java_url; - URLConnection java_connection; + HttpURLConnection java_connection; /// /// @@ -189,33 +189,30 @@ protected void AssertSelf () if (!request.RequestUri.IsAbsoluteUri) throw new ArgumentException ("Must represent an absolute URI", "request"); - /*using (*/java_url = new URL (request.RequestUri.ToString ());/*) {*/ - /*using (*/java_connection = java_url.OpenConnection ();/*) {*/ - HttpURLConnection httpConnection = SetupRequestInternal (request, java_connection); + java_url = new URL (request.RequestUri.ToString ()); + java_connection = java_url.OpenConnection () as HttpURLConnection; + HttpURLConnection httpConnection = await SetupRequestInternal (request, java_connection).ConfigureAwait (false); return await ProcessRequest (request, httpConnection, cancellationToken); - /*} - }*/ } - async Task ProcessRequest (HttpRequestMessage request, HttpURLConnection httpConnection, CancellationToken cancellationToken) + Task ProcessRequest (HttpRequestMessage request, HttpURLConnection httpConnection, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested (); httpConnection.InstanceFollowRedirects = AllowAutoRedirect; RequestedAuthentication = null; ProxyAuthenticationRequested = false; - return await Task.Factory.StartNew (() => DoProcessRequest (request, httpConnection, cancellationToken), cancellationToken).ConfigureAwait (false); + return DoProcessRequest (request, httpConnection, cancellationToken); } - HttpResponseMessage DoProcessRequest (HttpRequestMessage request, HttpURLConnection httpConnection, CancellationToken cancellationToken) + async Task DoProcessRequest (HttpRequestMessage request, HttpURLConnection httpConnection, CancellationToken cancellationToken) { if (Logger.LogNet) Logger.Log (LogLevel.Info, LOG_APP, $"{this}.DoProcessRequest ()"); - httpConnection.RequestMethod = request.Method.ToString (); try { if (Logger.LogNet) Logger.Log (LogLevel.Info, LOG_APP, $" connecting"); - httpConnection.Connect (); + await httpConnection.ConnectAsync ().ConfigureAwait (false); if (Logger.LogNet) Logger.Log (LogLevel.Info, LOG_APP, $" connected"); } catch (Java.Net.ConnectException ex) { @@ -372,9 +369,9 @@ void CopyHeaders (HttpURLConnection httpConnection, HttpResponseMessage response /// /// Request data /// Pre-configured connection instance - protected virtual void SetupRequest (HttpRequestMessage request, HttpURLConnection conn) + protected virtual Task SetupRequest (HttpRequestMessage request, HttpURLConnection conn) { - AssertSelf (); + return Task.Factory.StartNew (AssertSelf); } /// @@ -433,20 +430,20 @@ void AppendEncoding (string encoding, ref List list) list.Add (encoding); } - HttpURLConnection SetupRequestInternal (HttpRequestMessage request, URLConnection conn) + async Task SetupRequestInternal (HttpRequestMessage request, HttpURLConnection conn) { if (conn == null) throw new ArgumentNullException (nameof (conn)); - var httpConnection = conn.JavaCast (); + var httpConnection = conn; if (httpConnection == null) throw new InvalidOperationException ($"Unsupported URL scheme {conn.URL.Protocol}"); - + httpConnection.RequestMethod = request.Method.ToString (); // SSL context must be set up as soon as possible, before adding any content or // headers. Otherwise Java won't use the socket factory - SetupSSL (httpConnection as HttpsURLConnection); + await SetupSSL (httpConnection as HttpsURLConnection); if (request.Content != null) - AddHeaders (httpConnection, request.Content.Headers); - AddHeaders (httpConnection, request.Headers); + await AddHeaders (httpConnection, request.Content.Headers); + await AddHeaders (httpConnection, request.Headers); List accept_encoding = null; @@ -475,15 +472,16 @@ HttpURLConnection SetupRequestInternal (HttpRequestMessage request, URLConnectio httpConnection.SetRequestProperty ("Cookie", cookieHeaderValue); } - HandlePreAuthentication (httpConnection); - SetupRequest (request, httpConnection); - SetupRequestBody (httpConnection, request); + await HandlePreAuthentication (httpConnection); + await SetupRequest (request, httpConnection); + await SetupRequestBody (httpConnection, request); return httpConnection; } - void SetupSSL (HttpsURLConnection httpsConnection) + Task SetupSSL (HttpsURLConnection httpsConnection) { + return Task.Factory.StartNew (() => { if (httpsConnection == null) return; @@ -516,10 +514,12 @@ void SetupSSL (HttpsURLConnection httpsConnection) SSLContext context = SSLContext.GetInstance ("TLS"); context.Init (kmf?.GetKeyManagers (), tmf.GetTrustManagers (), null); httpsConnection.SSLSocketFactory = context.SocketFactory; + }); } - void HandlePreAuthentication (HttpURLConnection httpConnection) + Task HandlePreAuthentication (HttpURLConnection httpConnection) { + return Task.Factory.StartNew (() => { AuthenticationData data = PreAuthenticationData; if (!PreAuthenticate || data == null) return; @@ -548,37 +548,55 @@ void HandlePreAuthentication (HttpURLConnection httpConnection) if (Logger.LogNet) Logger.Log (LogLevel.Info, LOG_APP, $"Authentication header '{data.UseProxyAuthentication ? "Proxy-Authorization" : "Authorization"}' will be set to '{authorization.Message}'"); httpConnection.SetRequestProperty (data.UseProxyAuthentication ? "Proxy-Authorization" : "Authorization", authorization.Message); + }); } - void AddHeaders (HttpURLConnection conn, HttpHeaders headers) + Task AddHeaders (HttpURLConnection conn, HttpHeaders headers) { + return Task.Factory.StartNew (() => { if (headers == null) return; foreach (KeyValuePair> header in headers) { conn.SetRequestProperty (header.Key, header.Value != null ? String.Join (",", header.Value) : String.Empty); } + }); } - void SetupRequestBody (HttpURLConnection conn, HttpRequestMessage request) + async Task SetupRequestBody (HttpURLConnection httpConnection, HttpRequestMessage request) { if (request.Content == null) { + httpConnection.SetChunkedStreamingMode (0); // Pilfered from System.Net.Http.HttpClientHandler:SendAync if (HttpMethod.Post.Equals (request.Method) || HttpMethod.Put.Equals (request.Method) || HttpMethod.Delete.Equals (request.Method)) { // Explicitly set this to make sure we're sending a "Content-Length: 0" header. // This fixes the issue that's been reported on the forums: // http://forums.xamarin.com/discussion/17770/length-required-error-in-http-post-since-latest-release - conn.SetRequestProperty ("Content-Length", "0"); + httpConnection.SetRequestProperty ("Content-Length", "0"); } return; } - conn.DoOutput = true; - long? contentLength = request.Content.Headers.ContentLength; - if (contentLength != null) - conn.SetFixedLengthStreamingMode ((int)contentLength); + httpConnection.DoOutput = true; + + var bytes = await request.Content.ReadAsByteArrayAsync ().ConfigureAwait (false); + + int contentLength = bytes.Length; + httpConnection.SetRequestProperty ("Content-Length", contentLength.ToString()); + httpConnection.SetFixedLengthStreamingMode (contentLength); + + string contentType; + if (request.Content.Headers.ContentType != null) + contentType = String.Join (" ", request.Content.Headers.GetValues ("Content-Type")); else - conn.SetChunkedStreamingMode (0); + contentType = "text/plain"; + httpConnection.SetRequestProperty ("Content-Type", contentType); + + await Task.Factory.StartNew (() => { + httpConnection.OutputStream.Write (bytes, 0, contentLength); + httpConnection.OutputStream.Flush (); + httpConnection.OutputStream.Close (); + }); } } }