using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.Threading;
namespace Duplicati.Library.Utility
{
///
/// This class wraps a HttpWebRequest and performs GetRequestStream and GetResponseStream
/// with async methods while maintaining a synchronous interface
///
public class AsyncHttpRequest
{
///
/// The method being wrapped
///
private readonly WebRequest m_request;
///
/// The current internal state of the object
///
private RequestStates m_state = RequestStates.Created;
///
/// The request async wrapper
///
private AsyncWrapper m_asyncRequest = null;
///
/// The response async wrapper
///
private AsyncWrapper m_asyncResponse = null;
///
/// The request/response timeout value
///
private int m_timeout = 100000;
///
/// The activity timeout value
///
private readonly int m_activity_timeout = (int)TimeSpan.FromSeconds(30).TotalMilliseconds;
///
/// List of valid states
///
private enum RequestStates
{
///
/// The request has been created
///
Created,
///
/// The request stream has been requested
///
GetRequest,
///
/// The response has been requested
///
GetResponse,
///
///
///
Done
}
///
/// Constructs a new request from a url
///
/// The url to create the request from
public AsyncHttpRequest(string url)
: this(System.Net.WebRequest.Create(url))
{
}
///
/// Creates a async request wrapper for an existing url
///
/// The request to wrap
public AsyncHttpRequest(WebRequest request)
{
if (request == null)
throw new ArgumentNullException(nameof(request));
m_request = request;
m_timeout = m_request.Timeout;
if (m_timeout != System.Threading.Timeout.Infinite)
{
var tmp = (int)HttpContextSettings.OperationTimeout.TotalMilliseconds;
if (tmp <= 0)
m_timeout = System.Threading.Timeout.Infinite;
else
m_timeout = Math.Max(m_timeout, tmp);
}
m_activity_timeout = (int)HttpContextSettings.ReadWriteTimeout.TotalMilliseconds;
if (m_activity_timeout <= 0)
m_activity_timeout = System.Threading.Timeout.Infinite;
//We set this to prevent timeout related stuff from happening outside this module
m_request.Timeout = System.Threading.Timeout.Infinite;
//Then we register custom settings
if (m_request is HttpWebRequest)
{
if (((HttpWebRequest)m_request).ReadWriteTimeout != System.Threading.Timeout.Infinite)
m_activity_timeout = ((HttpWebRequest)m_request).ReadWriteTimeout;
((HttpWebRequest)m_request).ReadWriteTimeout = System.Threading.Timeout.Infinite;
// Prevent in-memory buffering causing out-of-memory issues
((HttpWebRequest)m_request).AllowReadStreamBuffering = HttpContextSettings.BufferRequests;
}
}
///
/// Gets the request that is wrapped
///
public WebRequest Request { get { return m_request; } }
///
/// Gets or sets the timeout used to guard the and calls
///
public int Timeout { get { return m_timeout; } set { m_timeout = value; } }
///
/// Gets the request stream
///
/// The request stream
/// The content length to use
public Stream GetRequestStream(long contentlength = -1)
{
// Prevent in-memory buffering causing out-of-memory issues
if (m_request is HttpWebRequest)
{
if (contentlength >= 0)
((HttpWebRequest)m_request).ContentLength = contentlength;
if (m_request.ContentLength >= 0)
((HttpWebRequest)m_request).AllowWriteStreamBuffering = false;
}
if (m_state == RequestStates.GetRequest)
return (Stream)m_asyncRequest.GetResponseOrStream();
if (m_state != RequestStates.Created)
throw new InvalidOperationException();
m_asyncRequest = new AsyncWrapper(this, true);
m_state = RequestStates.GetRequest;
return TrySetTimeout((Stream)m_asyncRequest.GetResponseOrStream(), m_activity_timeout);
}
///
/// Gets the response object
///
/// The web response
public WebResponse GetResponse()
{
if (m_state == RequestStates.GetResponse)
return (WebResponse)m_asyncResponse.GetResponseOrStream();
if (m_state == RequestStates.Done)
throw new InvalidOperationException();
m_asyncRequest = null;
m_asyncResponse = new AsyncWrapper(this, false);
m_state = RequestStates.GetResponse;
return (WebResponse)m_asyncResponse.GetResponseOrStream();
}
public Stream GetResponseStream()
{
return TrySetTimeout(GetResponse().GetResponseStream(), m_activity_timeout);
}
public static Stream TrySetTimeout(Stream str, int timeoutmilliseconds = 30000)
{
try { str.ReadTimeout = timeoutmilliseconds; }
catch { }
return str;
}
///
/// Wrapper class for getting request and respone objects in a async manner
///
private class AsyncWrapper
{
private readonly IAsyncResult m_async = null;
private Stream m_stream = null;
private WebResponse m_response = null;
private readonly AsyncHttpRequest m_owner;
private Exception m_exception = null;
private readonly ManualResetEvent m_event = new ManualResetEvent(false);
private readonly bool m_isRequest;
private bool m_timedout = false;
public AsyncWrapper(AsyncHttpRequest owner, bool isRequest)
{
m_owner = owner;
m_isRequest = isRequest;
if (m_isRequest)
m_async = m_owner.m_request.BeginGetRequestStream(new AsyncCallback(this.OnAsync), null);
else
m_async = m_owner.m_request.BeginGetResponse(new AsyncCallback(this.OnAsync), null);
if ( m_owner.m_timeout != System.Threading.Timeout.Infinite)
ThreadPool.RegisterWaitForSingleObject(m_async.AsyncWaitHandle, new WaitOrTimerCallback(this.OnTimeout), null, TimeSpan.FromMilliseconds( m_owner.m_timeout), true);
}
private void OnAsync(IAsyncResult r)
{
try
{
if (m_isRequest)
m_stream = m_owner.m_request.EndGetRequestStream(r);
else
m_response = m_owner.m_request.EndGetResponse(r);
}
catch (Exception ex)
{
if (m_timedout)
m_exception = new WebException(string.Format("{0} timed out", m_isRequest ? "GetRequestStream" : "GetResponse"), ex, WebExceptionStatus.Timeout, ex is WebException ? ((WebException)ex).Response : null);
else
{
// Workaround for: https://bugzilla.xamarin.com/show_bug.cgi?id=28287
var wex = ex;
if (ex is WebException && ((WebException)ex).Response == null)
{
WebResponse resp = null;
try { resp = (WebResponse)r.GetType().GetProperty("Response", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(r); }
catch {}
if (resp == null)
try { resp = (WebResponse)m_owner.m_request.GetType().GetField("webResponse", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(m_owner.m_request); }
catch { }
if (resp != null)
wex = new WebException(ex.Message, ex.InnerException, ((WebException)ex).Status, resp);
}
m_exception = wex;
}
}
finally
{
m_event.Set();
}
}
private void OnTimeout(object state, bool timedout)
{
if (timedout)
{
if (!m_event.WaitOne(0, false))
{
m_timedout = true;
m_owner.m_request.Abort();
}
}
}
public object GetResponseOrStream()
{
try
{
m_event.WaitOne();
}
catch (ThreadAbortException)
{
m_owner.m_request.Abort();
//Grant a little time for cleanups
m_event.WaitOne((int)TimeSpan.FromSeconds(5).TotalMilliseconds, false);
//The abort exception will automatically be rethrown
}
if (m_exception != null)
throw m_exception;
if (m_isRequest)
return m_stream;
else
return m_response;
}
}
}
}