Skip to content

Commit

Permalink
[Mono.Android, Xamarin.Android.Net.AndroidClientHandler] Updated to w…
Browse files Browse the repository at this point in the history
…ork 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 (#44)

updating logger incorrect calls

adding fixes
  • Loading branch information
blounty authored and grendello committed Jun 20, 2016
1 parent d22044f commit 5db477d
Showing 1 changed file with 50 additions and 32 deletions.
82 changes: 50 additions & 32 deletions src/Mono.Android/Xamarin.Android.Net/AndroidClientHandler.cs
Expand Up @@ -91,7 +91,7 @@ public class AndroidClientHandler : HttpClientHandler
bool decompress_here;

URL java_url;
URLConnection java_connection;
HttpURLConnection java_connection;

/// <summary>
/// <para>
Expand Down Expand Up @@ -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 <HttpResponseMessage> ProcessRequest (HttpRequestMessage request, HttpURLConnection httpConnection, CancellationToken cancellationToken)
Task <HttpResponseMessage> ProcessRequest (HttpRequestMessage request, HttpURLConnection httpConnection, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested ();
httpConnection.InstanceFollowRedirects = AllowAutoRedirect;
RequestedAuthentication = null;
ProxyAuthenticationRequested = false;

return await Task<HttpResponseMessage>.Factory.StartNew (() => DoProcessRequest (request, httpConnection, cancellationToken), cancellationToken).ConfigureAwait (false);
return DoProcessRequest (request, httpConnection, cancellationToken);
}

HttpResponseMessage DoProcessRequest (HttpRequestMessage request, HttpURLConnection httpConnection, CancellationToken cancellationToken)
async Task <HttpResponseMessage> 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) {
Expand Down Expand Up @@ -372,9 +369,9 @@ void CopyHeaders (HttpURLConnection httpConnection, HttpResponseMessage response
/// </summary>
/// <param name="request">Request data</param>
/// <param name="conn">Pre-configured connection instance</param>
protected virtual void SetupRequest (HttpRequestMessage request, HttpURLConnection conn)
protected virtual Task SetupRequest (HttpRequestMessage request, HttpURLConnection conn)
{
AssertSelf ();
return Task.Factory.StartNew (AssertSelf);
}

/// <summary>
Expand Down Expand Up @@ -433,20 +430,20 @@ void AppendEncoding (string encoding, ref List <string> list)
list.Add (encoding);
}

HttpURLConnection SetupRequestInternal (HttpRequestMessage request, URLConnection conn)
async Task <HttpURLConnection> SetupRequestInternal (HttpRequestMessage request, HttpURLConnection conn)
{
if (conn == null)
throw new ArgumentNullException (nameof (conn));
var httpConnection = conn.JavaCast <HttpURLConnection> ();
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 <string> accept_encoding = null;

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<string, IEnumerable<string>> 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 ();
});
}
}
}

0 comments on commit 5db477d

Please sign in to comment.