/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
using Amazon.Runtime.Internal.Auth;
using Amazon.Runtime.Internal.Transform;
using Amazon.Runtime.Internal.Util;
using Amazon.Util;
using Amazon.Util.Internal;
using System;
using System.Globalization;
using System.Net;
using System.Reflection;
using System.Text;
namespace Amazon.Runtime.Internal
{
///
/// The HTTP handler contains common logic for issuing an HTTP request that is
/// independent of the underlying HTTP infrastructure.
///
///
public class HttpHandler : PipelineHandler, IDisposable
{
private bool _disposed;
private IHttpRequestFactory _requestFactory;
///
/// The sender parameter used in any events raised by this handler.
///
public object CallbackSender { get; private set; }
///
/// The constructor for HttpHandler.
///
/// The request factory used to create HTTP Requests.
/// The sender parameter used in any events raised by this handler.
public HttpHandler(IHttpRequestFactory requestFactory, object callbackSender)
{
_requestFactory = requestFactory;
this.CallbackSender = callbackSender;
}
///
/// Issues an HTTP request for the current request context.
///
/// The execution context which contains both the
/// requests and response context.
public override void InvokeSync(IExecutionContext executionContext)
{
IHttpRequest httpRequest = null;
try
{
SetMetrics(executionContext.RequestContext);
IRequest wrappedRequest = executionContext.RequestContext.Request;
httpRequest = CreateWebRequest(executionContext.RequestContext);
httpRequest.SetRequestHeaders(wrappedRequest.Headers);
using (executionContext.RequestContext.Metrics.StartEvent(Metric.HttpRequestTime))
{
// Send request body if present.
if (wrappedRequest.HasRequestBody())
{
try
{
var requestContent = httpRequest.GetRequestContent();
WriteContentToRequestBody(requestContent, httpRequest, executionContext.RequestContext);
}
catch
{
CompleteFailedRequest(httpRequest);
throw;
}
}
executionContext.ResponseContext.HttpResponse = httpRequest.GetResponse();
}
}
finally
{
if (httpRequest != null)
httpRequest.Dispose();
}
}
private static void CompleteFailedRequest(IHttpRequest httpRequest)
{
try
{
// In some cases where writing the request body fails, HttpWebRequest.Abort
// may not dispose of the underlying Socket, so we need to retrieve and dispose
// the web response to close the socket
IWebResponseData response = null;
try
{
response = httpRequest.GetResponse();
}
catch (WebException webException)
{
if (webException.Response != null)
{
#if BCL35
webException.Response.Close();
#else
webException.Response.Dispose();
#endif
}
}
catch (HttpErrorResponseException httpErrorResponse)
{
if (httpErrorResponse.Response != null && httpErrorResponse.Response.ResponseBody != null)
httpErrorResponse.Response.ResponseBody.Dispose();
}
finally
{
if (response != null && response.ResponseBody != null)
response.ResponseBody.Dispose();
}
}
catch { }
}
#if AWS_ASYNC_API
///
/// Issues an HTTP request for the current request context.
///
/// The response type for the current request.
/// The execution context, it contains the
/// request and response context.
/// A task that represents the asynchronous operation.
public override async System.Threading.Tasks.Task InvokeAsync(IExecutionContext executionContext)
{
IHttpRequest httpRequest = null;
try
{
SetMetrics(executionContext.RequestContext);
IRequest wrappedRequest = executionContext.RequestContext.Request;
httpRequest = CreateWebRequest(executionContext.RequestContext);
httpRequest.SetRequestHeaders(wrappedRequest.Headers);
using(executionContext.RequestContext.Metrics.StartEvent(Metric.HttpRequestTime))
{
// Send request body if present.
if (wrappedRequest.HasRequestBody())
{
System.Runtime.ExceptionServices.ExceptionDispatchInfo edi = null;
try
{
// In .NET Framework, there needs to be a cancellation token in this method since GetRequestStreamAsync
// does not accept a cancellation token. A workaround is used. This isn't necessary in .NET Standard
// where the stream is a property of the request.
#if BCL45
var requestContent = await httpRequest.GetRequestContentAsync(executionContext.RequestContext.CancellationToken).ConfigureAwait(false);
await WriteContentToRequestBodyAsync(requestContent, httpRequest, executionContext.RequestContext).ConfigureAwait(false);
#else
var requestContent = await httpRequest.GetRequestContentAsync().ConfigureAwait(false);
WriteContentToRequestBody(requestContent, httpRequest, executionContext.RequestContext);
#endif
}
catch(Exception e)
{
edi = System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(e);
}
if (edi != null)
{
await CompleteFailedRequest(executionContext, httpRequest).ConfigureAwait(false);
edi.Throw();
}
}
var response = await httpRequest.GetResponseAsync(executionContext.RequestContext.CancellationToken).
ConfigureAwait(false);
executionContext.ResponseContext.HttpResponse = response;
}
// The response is not unmarshalled yet.
return null;
}
finally
{
if (httpRequest != null)
httpRequest.Dispose();
}
}
private static async System.Threading.Tasks.Task CompleteFailedRequest(IExecutionContext executionContext, IHttpRequest httpRequest)
{
// In some cases where writing the request body fails, HttpWebRequest.Abort
// may not dispose of the underlying Socket, so we need to retrieve and dispose
// the web response to close the socket
IWebResponseData iwrd = null;
try
{
iwrd = await httpRequest.GetResponseAsync(executionContext.RequestContext.CancellationToken).ConfigureAwait(false);
}
catch { }
finally
{
if (iwrd != null && iwrd.ResponseBody != null)
iwrd.ResponseBody.Dispose();
}
}
#elif AWS_APM_API
///
/// Issues an HTTP request for the current request context.
///
/// The execution context which contains both the
/// requests and response context.
/// IAsyncResult which represent an async operation.
public override IAsyncResult InvokeAsync(IAsyncExecutionContext executionContext)
{
IHttpRequest httpRequest = null;
try
{
SetMetrics(executionContext.RequestContext);
httpRequest = CreateWebRequest(executionContext.RequestContext);
executionContext.RuntimeState = httpRequest;
IRequest wrappedRequest = executionContext.RequestContext.Request;
if (executionContext.RequestContext.Retries == 0)
{
// First call, initialize an async result.
executionContext.ResponseContext.AsyncResult =
new RuntimeAsyncResult(executionContext.RequestContext.Callback,
executionContext.RequestContext.State);
}
// Set request headers
httpRequest.SetRequestHeaders(executionContext.RequestContext.Request.Headers);
executionContext.RequestContext.Metrics.StartEvent(Metric.HttpRequestTime);
if (wrappedRequest.HasRequestBody())
{
// Send request body if present.
httpRequest.BeginGetRequestContent(new AsyncCallback(GetRequestStreamCallback), executionContext);
}
else
{
// Get response if there is no response body to send.
httpRequest.BeginGetResponse(new AsyncCallback(GetResponseCallback), executionContext);
}
return executionContext.ResponseContext.AsyncResult;
}
catch (Exception exception)
{
if (executionContext.ResponseContext.AsyncResult != null)
{
// An exception will be thrown back to the calling code.
// Dispose AsyncResult as it will not be used further.
executionContext.ResponseContext.AsyncResult.Dispose();
executionContext.ResponseContext.AsyncResult = null;
}
if (httpRequest != null)
{
httpRequest.Dispose();
}
// Log this exception as it will not be caught by ErrorHandler.
this.Logger.Error(exception, "An exception occured while initiating an asynchronous HTTP request.");
throw;
}
}
private void GetRequestStreamCallback(IAsyncResult result)
{
if (result.CompletedSynchronously)
{
System.Threading.ThreadPool.QueueUserWorkItem(GetRequestStreamCallbackHelper, result);
}
else
{
GetRequestStreamCallbackHelper(result);
}
}
private void GetRequestStreamCallbackHelper(object state)
{
IAsyncResult result = state as IAsyncResult;
IAsyncExecutionContext executionContext = null;
IHttpRequest httpRequest = null;
try
{
executionContext = result.AsyncState as IAsyncExecutionContext;
httpRequest = executionContext.RuntimeState as IHttpRequest;
var requestContent = httpRequest.EndGetRequestContent(result);
WriteContentToRequestBody(requestContent, httpRequest, executionContext.RequestContext);
//var requestStream = httpRequest.EndSetRequestBody(result);
httpRequest.BeginGetResponse(new AsyncCallback(GetResponseCallback), executionContext);
}
catch (Exception exception)
{
httpRequest.Dispose();
// Capture the exception and invoke outer handlers to
// process the exception.
executionContext.ResponseContext.AsyncResult.Exception = exception;
base.InvokeAsyncCallback(executionContext);
}
}
private void GetResponseCallback(IAsyncResult result)
{
if (result.CompletedSynchronously)
{
System.Threading.ThreadPool.QueueUserWorkItem(GetResponseCallbackHelper, result);
}
else
{
GetResponseCallbackHelper(result);
}
}
private void GetResponseCallbackHelper(object state)
{
IAsyncResult result = state as IAsyncResult;
IAsyncExecutionContext executionContext = null;
IHttpRequest httpRequest = null;
try
{
executionContext = result.AsyncState as IAsyncExecutionContext;
httpRequest = executionContext.RuntimeState as IHttpRequest;
var httpResponse = httpRequest.EndGetResponse(result);
executionContext.ResponseContext.HttpResponse = httpResponse;
}
catch (Exception exception)
{
// Capture the exception and invoke outer handlers to
// process the exception.
executionContext.ResponseContext.AsyncResult.Exception = exception;
}
finally
{
executionContext.RequestContext.Metrics.StopEvent(Metric.HttpRequestTime);
httpRequest.Dispose();
base.InvokeAsyncCallback(executionContext);
}
}
#endif
private static void SetMetrics(IRequestContext requestContext)
{
requestContext.Metrics.AddProperty(Metric.ServiceName, requestContext.Request.ServiceName);
requestContext.Metrics.AddProperty(Metric.ServiceEndpoint, requestContext.Request.Endpoint);
requestContext.Metrics.AddProperty(Metric.MethodName, requestContext.Request.RequestName);
}
///
/// Determines the content for request body and uses the HTTP request
/// to write the content to the HTTP request body.
///
/// Content to be written.
/// The HTTP request.
/// The request context.
private void WriteContentToRequestBody(TRequestContent requestContent,
IHttpRequest httpRequest,
IRequestContext requestContext)
{
IRequest wrappedRequest = requestContext.Request;
// This code path ends up using a ByteArrayContent for System.Net.HttpClient used by .NET Core.
// HttpClient can't seem to handle ByteArrayContent with 0 length so in that case use
// the StreamContent code path.
if (wrappedRequest.Content != null && wrappedRequest.Content.Length > 0)
{
byte[] requestData = wrappedRequest.Content;
requestContext.Metrics.AddProperty(Metric.RequestSize, requestData.Length);
httpRequest.WriteToRequestBody(requestContent, requestData, requestContext.Request.Headers);
}
else
{
System.IO.Stream originalStream;
if (wrappedRequest.ContentStream == null)
{
originalStream = new System.IO.MemoryStream();
originalStream.Write(wrappedRequest.Content, 0, wrappedRequest.Content.Length);
originalStream.Position = 0;
}
else
{
originalStream = wrappedRequest.ContentStream;
}
var callback = ((Amazon.Runtime.Internal.IAmazonWebServiceRequest)wrappedRequest.OriginalRequest).StreamUploadProgressCallback;
if (callback != null)
originalStream = httpRequest.SetupProgressListeners(originalStream, requestContext.ClientConfig.ProgressUpdateInterval, this.CallbackSender, callback);
var inputStream = GetInputStream(requestContext, originalStream, wrappedRequest);
httpRequest.WriteToRequestBody(requestContent, inputStream,
requestContext.Request.Headers, requestContext);
}
}
#if BCL45
///
/// Determines the content for request body and uses the HTTP request
/// to write the content to the HTTP request body.
///
/// Content to be written.
/// The HTTP request.
/// The request context.
private async System.Threading.Tasks.Task WriteContentToRequestBodyAsync(TRequestContent requestContent,
IHttpRequest httpRequest,
IRequestContext requestContext)
{
IRequest wrappedRequest = requestContext.Request;
// This code path ends up using a ByteArrayContent for System.Net.HttpClient used by .NET Core.
// HttpClient can't seem to handle ByteArrayContent with 0 length so in that case use
// the StreamContent code path.
if (wrappedRequest.Content != null && wrappedRequest.Content.Length > 0)
{
byte[] requestData = wrappedRequest.Content;
requestContext.Metrics.AddProperty(Metric.RequestSize, requestData.Length);
await httpRequest.WriteToRequestBodyAsync(requestContent, requestData, requestContext.Request.Headers, requestContext.CancellationToken).ConfigureAwait(false);
}
else
{
System.IO.Stream originalStream;
if (wrappedRequest.ContentStream == null)
{
originalStream = new System.IO.MemoryStream();
await originalStream.WriteAsync(wrappedRequest.Content, 0, wrappedRequest.Content.Length, requestContext.CancellationToken).ConfigureAwait(false);
originalStream.Position = 0;
}
else
{
originalStream = wrappedRequest.ContentStream;
}
var callback = ((Amazon.Runtime.Internal.IAmazonWebServiceRequest)wrappedRequest.OriginalRequest).StreamUploadProgressCallback;
if (callback != null)
originalStream = httpRequest.SetupProgressListeners(originalStream, requestContext.ClientConfig.ProgressUpdateInterval, this.CallbackSender, callback);
var inputStream = GetInputStream(requestContext, originalStream, wrappedRequest);
await httpRequest.WriteToRequestBodyAsync(requestContent, inputStream,
requestContext.Request.Headers, requestContext).ConfigureAwait(false);
}
}
#endif
///
/// Creates the HttpWebRequest and configures the end point, content, user agent and proxy settings.
///
/// The async request.
/// The web request that actually makes the call.
protected virtual IHttpRequest CreateWebRequest(IRequestContext requestContext)
{
IRequest request = requestContext.Request;
Uri url = AmazonServiceClient.ComposeUrl(request);
var httpRequest = _requestFactory.CreateHttpRequest(url);
httpRequest.ConfigureRequest(requestContext);
httpRequest.Method = request.HttpMethod;
if (request.MayContainRequestBody())
{
var content = request.Content;
if (request.SetContentFromParameters || (content == null && request.ContentStream == null))
{
// Mapping parameters to query string or body are mutually exclusive.
if (!request.UseQueryString)
{
string queryString = AWSSDKUtils.GetParametersAsString(request);
content = Encoding.UTF8.GetBytes(queryString);
request.Content = content;
request.SetContentFromParameters = true;
}
else
{
request.Content = ArrayEx.Empty();
}
}
if (content != null)
{
request.Headers[HeaderKeys.ContentLengthHeader] =
content.Length.ToString(CultureInfo.InvariantCulture);
}
else if (request.ContentStream != null && request.ContentStream.CanSeek && !request.Headers.ContainsKey(HeaderKeys.ContentLengthHeader))
{
request.Headers[HeaderKeys.ContentLengthHeader] =
request.ContentStream.Length.ToString(CultureInfo.InvariantCulture);
}
}
return httpRequest;
}
///
/// Disposes the HTTP handler.
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
if (_requestFactory != null)
_requestFactory.Dispose();
_disposed = true;
}
}
private static System.IO.Stream GetInputStream(IRequestContext requestContext, System.IO.Stream originalStream, IRequest wrappedRequest)
{
var requestHasConfigForChunkStream = wrappedRequest.UseChunkEncoding && (wrappedRequest.AWS4SignerResult != null || wrappedRequest.AWS4aSignerResult != null);
var hasTransferEncodingHeader = wrappedRequest.Headers.ContainsKey(HeaderKeys.TransferEncodingHeader);
var isTransferEncodingHeaderChunked = hasTransferEncodingHeader && wrappedRequest.Headers[HeaderKeys.TransferEncodingHeader] == "chunked";
var hasTrailingHeaders = wrappedRequest.TrailingHeaders?.Count > 0;
if (requestHasConfigForChunkStream || isTransferEncodingHeaderChunked)
{
AWSSigningResultBase signingResult;
if (wrappedRequest.AWS4aSignerResult != null)
{
signingResult = wrappedRequest.AWS4aSignerResult;
}
else
{
signingResult = wrappedRequest.AWS4SignerResult;
}
if (hasTrailingHeaders)
{
return new ChunkedUploadWrapperStream(originalStream,
requestContext.ClientConfig.BufferSize,
signingResult,
wrappedRequest.SelectedChecksum,
wrappedRequest.TrailingHeaders);
}
else // no trailing headers
{
return new ChunkedUploadWrapperStream(originalStream,
requestContext.ClientConfig.BufferSize,
signingResult);
}
}
else if (hasTrailingHeaders) // and is unsigned/unchunked
{
if (wrappedRequest.SelectedChecksum != CoreChecksumAlgorithm.NONE)
{
return new TrailingHeadersWrapperStream(originalStream, wrappedRequest.TrailingHeaders, wrappedRequest.SelectedChecksum);
}
else
{
return new TrailingHeadersWrapperStream(originalStream, wrappedRequest.TrailingHeaders);
}
}
else
{
return originalStream;
}
}
}
}