Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 35 additions & 62 deletions src/Mono.Android/Xamarin.Android.Net/AndroidClientHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,6 @@ public class AndroidClientHandler : HttpClientHandler
// uncompress it any longer, doh. And they don't support 'deflate' so we need to handle it ourselves.
bool decompress_here;

URL java_url;
HttpURLConnection java_connection;

/// <summary>
/// <para>
/// Gets or sets the pre authentication data for the request. This property must be set by the application
Expand Down Expand Up @@ -154,15 +151,6 @@ public bool RequestNeedsAuthorization {
protected override void Dispose (bool disposing)
{
disposed = true;
if (java_connection != null) {
java_connection.Dispose ();
java_connection = null;
}

if (java_url != null) {
java_url.Dispose ();
java_url = null;
}

base.Dispose (disposing);
}
Expand All @@ -180,7 +168,7 @@ protected void AssertSelf ()
/// <returns>Task in which the request is executed</returns>
/// <param name="request">Request provided by <see cref="System.Net.Http.HttpClient"/></param>
/// <param name="cancellationToken">Cancellation token.</param>
protected override async Task <HttpResponseMessage> SendAsync (HttpRequestMessage request, CancellationToken cancellationToken)
protected override Task <HttpResponseMessage> SendAsync (HttpRequestMessage request, CancellationToken cancellationToken)
{
AssertSelf ();
if (request == null)
Expand All @@ -189,23 +177,23 @@ protected void AssertSelf ()
if (!request.RequestUri.IsAbsoluteUri)
throw new ArgumentException ("Must represent an absolute URI", "request");

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);
URL java_url = new URL (request.RequestUri.ToString ());
URLConnection java_connection = java_url.OpenConnection ();
HttpURLConnection httpConnection = SetupRequestInternal (request, java_connection);
return ProcessRequest (request, java_url, httpConnection, cancellationToken);
}

Task <HttpResponseMessage> ProcessRequest (HttpRequestMessage request, HttpURLConnection httpConnection, CancellationToken cancellationToken)
Task <HttpResponseMessage> ProcessRequest (HttpRequestMessage request, URL javaUrl, HttpURLConnection httpConnection, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested ();
httpConnection.InstanceFollowRedirects = AllowAutoRedirect;
RequestedAuthentication = null;
ProxyAuthenticationRequested = false;
return DoProcessRequest (request, httpConnection, cancellationToken);

return DoProcessRequest (request, javaUrl, httpConnection, cancellationToken);
}

async Task <HttpResponseMessage> DoProcessRequest (HttpRequestMessage request, HttpURLConnection httpConnection, CancellationToken cancellationToken)
async Task <HttpResponseMessage> DoProcessRequest (HttpRequestMessage request, URL javaUrl, HttpURLConnection httpConnection, CancellationToken cancellationToken)
{
if (Logger.LogNet)
Logger.Log (LogLevel.Info, LOG_APP, $"{this}.DoProcessRequest ()");
Expand All @@ -222,12 +210,16 @@ protected void AssertSelf ()
throw new WebException (ex.Message, ex, WebExceptionStatus.ConnectFailure, null);
}

if (httpConnection.DoOutput) {
await request.Content.CopyToAsync (httpConnection.OutputStream).ConfigureAwait (false);
}

var statusCode = (HttpStatusCode)httpConnection.ResponseCode;
var connectionUri = new Uri (httpConnection.URL.ToString ());

// If the request was redirected we need to put the new URL in the request
request.RequestUri = connectionUri;
var ret = new AndroidHttpResponseMessage {
var ret = new AndroidHttpResponseMessage (javaUrl, httpConnection) {
RequestMessage = request,
ReasonPhrase = httpConnection.ResponseMessage,
StatusCode = statusCode,
Expand Down Expand Up @@ -369,9 +361,9 @@ void CopyHeaders (HttpURLConnection httpConnection, HttpResponseMessage response
/// </summary>
/// <param name="request">Request data</param>
/// <param name="conn">Pre-configured connection instance</param>
protected virtual Task SetupRequest (HttpRequestMessage request, HttpURLConnection conn)
protected virtual void SetupRequest (HttpRequestMessage request, HttpURLConnection conn)
{
return Task.Factory.StartNew (AssertSelf);
AssertSelf ();
}

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

async Task <HttpURLConnection> SetupRequestInternal (HttpRequestMessage request, HttpURLConnection conn)
HttpURLConnection SetupRequestInternal (HttpRequestMessage request, URLConnection conn)
{
if (conn == null)
throw new ArgumentNullException (nameof (conn));
var httpConnection = conn;
var httpConnection = conn.JavaCast <HttpURLConnection> ();
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
await SetupSSL (httpConnection as HttpsURLConnection);
SetupSSL (httpConnection as HttpsURLConnection);
if (request.Content != null)
await AddHeaders (httpConnection, request.Content.Headers);
await AddHeaders (httpConnection, request.Headers);
AddHeaders (httpConnection, request.Content.Headers);
AddHeaders (httpConnection, request.Headers);

List <string> accept_encoding = null;

Expand Down Expand Up @@ -472,16 +466,15 @@ void AppendEncoding (string encoding, ref List <string> list)
httpConnection.SetRequestProperty ("Cookie", cookieHeaderValue);
}

await HandlePreAuthentication (httpConnection);
await SetupRequest (request, httpConnection);
await SetupRequestBody (httpConnection, request);
HandlePreAuthentication (httpConnection);
SetupRequest (request, httpConnection);
SetupRequestBody (httpConnection, request);

return httpConnection;
}

Task SetupSSL (HttpsURLConnection httpsConnection)
void SetupSSL (HttpsURLConnection httpsConnection)
{
return Task.Factory.StartNew (() => {
if (httpsConnection == null)
return;

Expand Down Expand Up @@ -514,12 +507,10 @@ Task SetupSSL (HttpsURLConnection httpsConnection)
SSLContext context = SSLContext.GetInstance ("TLS");
context.Init (kmf?.GetKeyManagers (), tmf.GetTrustManagers (), null);
httpsConnection.SSLSocketFactory = context.SocketFactory;
});
}

Task HandlePreAuthentication (HttpURLConnection httpConnection)
void HandlePreAuthentication (HttpURLConnection httpConnection)
{
return Task.Factory.StartNew (() => {
AuthenticationData data = PreAuthenticationData;
if (!PreAuthenticate || data == null)
return;
Expand Down Expand Up @@ -548,25 +539,21 @@ Task 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);
});
}

Task AddHeaders (HttpURLConnection conn, HttpHeaders headers)
void 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);
}
});
}

async Task SetupRequestBody (HttpURLConnection httpConnection, HttpRequestMessage request)
void 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.
Expand All @@ -576,27 +563,13 @@ async Task SetupRequestBody (HttpURLConnection httpConnection, HttpRequestMessag
}
return;
}

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"));
httpConnection.DoOutput = true;
long? contentLength = request.Content.Headers.ContentLength;
if (contentLength != null)
httpConnection.SetFixedLengthStreamingMode ((int)contentLength);
else
contentType = "text/plain";
httpConnection.SetRequestProperty ("Content-Type", contentType);

await Task.Factory.StartNew (() => {
httpConnection.OutputStream.Write (bytes, 0, contentLength);
httpConnection.OutputStream.Flush ();
httpConnection.OutputStream.Close ();
});
httpConnection.SetChunkedStreamingMode (0);
}
}
}
20 changes: 20 additions & 0 deletions src/Mono.Android/Xamarin.Android.Net/AndroidHttpResponseMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,38 @@ namespace Xamarin.Android.Net
/// </summary>
public class AndroidHttpResponseMessage : HttpResponseMessage
{
URL javaUrl;
HttpURLConnection httpConnection;

/// <summary>
/// Set to the same value as <see cref="AndroidClientHandler.RequestedAuthentication"/>.
/// </summary>
/// <value>The requested authentication.</value>
public IList <AuthenticationData> RequestedAuthentication { get; internal set; }

public AndroidHttpResponseMessage (URL javaUrl, HttpURLConnection httpConnection) {
javaUrl = javaUrl;
httpConnection = httpConnection;
}

/// <summary>
/// Set to the same value as <see cref="AndroidClientHandler.RequestNeedsAuthorization"/>
/// </summary>
/// <value>The request needs authorization.</value>
public bool RequestNeedsAuthorization {
get { return RequestedAuthentication?.Count > 0; }
}

protected override void Dispose(bool disposing) {
base.Dispose(disposing);

if (javaUrl != null) {
javaUrl.Dispose ();
}

if (httpConnection != null) {
httpConnection.Dispose ();
}
}
}
}