/*
* Copyright 2010-2013 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 System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Net;
using Amazon.Runtime;
using Amazon.Runtime.Internal.Util;
using Amazon.Runtime.Internal.Auth;
using Amazon.Util;
namespace Amazon.Runtime.Internal
{
///
/// Default implementation of the IRequest interface.
///
/// This class is only intended for internal use inside the AWS client libraries.
/// Callers shouldn't ever interact directly with objects of this class.
///
///
public class DefaultRequest : IRequest
{
readonly ParameterCollection parametersCollection;
readonly IDictionary parametersFacade;
readonly IDictionary headers = new Dictionary(StringComparer.OrdinalIgnoreCase);
readonly IDictionary subResources = new Dictionary(StringComparer.OrdinalIgnoreCase);
readonly IDictionary pathResources = new Dictionary(StringComparer.Ordinal);
Uri endpoint;
string resourcePath;
string serviceName;
readonly AmazonWebServiceRequest originalRequest;
byte[] content;
Stream contentStream;
string contentStreamHash;
string httpMethod = "POST";
bool useQueryString = false;
string requestName;
string canonicalResource;
RegionEndpoint alternateRegion;
long originalStreamLength;
int marshallerVersion = 1; //1 is the default version and must be used whenever a version is not specified in the marshaller.
///
/// Constructs a new DefaultRequest with the specified service name and the
/// original, user facing request object.
///
/// The orignal request that is being wrapped
/// The service name
public DefaultRequest(AmazonWebServiceRequest request, String serviceName)
{
if (request == null) throw new ArgumentNullException("request");
if (string.IsNullOrEmpty(serviceName)) throw new ArgumentNullException("serviceName");
this.serviceName = serviceName;
this.originalRequest = request;
this.requestName = this.originalRequest.GetType().Name;
this.UseSigV4 = ((Amazon.Runtime.Internal.IAmazonWebServiceRequest)this.originalRequest).UseSigV4;
this.HostPrefix = string.Empty;
parametersCollection = new ParameterCollection();
parametersFacade = new ParametersDictionaryFacade(parametersCollection);
}
///
/// The name of the request
///
public string RequestName
{
get { return this.requestName; }
}
///
/// Gets and sets the type of http request to make, whether it should be POST,GET or DELETE
///
public string HttpMethod
{
get
{
return this.httpMethod;
}
set
{
this.httpMethod = value;
}
}
///
/// Gets and sets a flag that indicates whether the request is sent as a query string instead of the request body.
///
public bool UseQueryString
{
get
{
if (this.HttpMethod == "GET")
return true;
return this.useQueryString;
}
set
{
this.useQueryString = value;
}
}
///
/// Returns the original, user facing request object which this internal
/// request object is representing.
///
public AmazonWebServiceRequest OriginalRequest
{
get
{
return originalRequest;
}
}
///
/// Returns a dictionary of the headers included in this request.
///
public IDictionary Headers
{
get
{
return this.headers;
}
}
///
/// Returns a dictionary of the parameters included in this request.
///
public IDictionary Parameters
{
get
{
return this.parametersFacade;
}
}
///
/// Collection of parameters included in this request.
///
public ParameterCollection ParameterCollection
{
get
{
return this.parametersCollection;
}
}
///
/// Returns the subresources that should be appended to the resource path.
/// This is used primarily for Amazon S3, where object keys can contain '?'
/// characters, making string-splitting of a resource path potentially
/// hazardous.
///
public IDictionary SubResources
{
get
{
return this.subResources;
}
}
///
/// Adds a new null entry to the SubResources collection for the request
///
/// The name of the subresource
public void AddSubResource(string subResource)
{
AddSubResource(subResource, null);
}
///
/// Adds a new entry to the SubResources collection for the request
///
/// The name of the subresource
/// Value of the entry
public void AddSubResource(string subResource, string value)
{
SubResources.Add(subResource, value);
}
///
/// Gets and Sets the endpoint for this request.
///
public Uri Endpoint
{
get
{
return this.endpoint;
}
set
{
this.endpoint = value;
}
}
///
/// Gets and Sets the resource path added on to the endpoint.
///
public string ResourcePath
{
get
{
return this.resourcePath;
}
set
{
this.resourcePath = value;
}
}
///
/// Returns the path resources that should be used within the resource path.
/// This is used for services where path keys can contain '/'
/// characters, making string-splitting of a resource path potentially
/// hazardous.
///
public IDictionary PathResources
{
get
{
return this.pathResources;
}
}
///
/// Adds a new entry to the PathResources collection for the request
///
/// The name of the pathresource with potential greedy syntax: {key+}
/// Value of the entry
public void AddPathResource(string key, string value)
{
PathResources.Add(key, value);
}
///
/// Gets and Sets the version number for the marshaller used to create this request. The version number
/// is used to support backward compatible changes that would otherwise be breaking changes when a
/// newer core is used with an older service assembly.
/// Versions:
/// 1 - Default version
/// 2 - Support for path segments
///
public int MarshallerVersion
{
get
{
return this.marshallerVersion;
}
set
{
this.marshallerVersion = value;
}
}
public string CanonicalResource
{
get
{
return this.canonicalResource;
}
set
{
this.canonicalResource = value;
}
}
///
/// Gets and Sets the content for this request.
///
public byte[] Content
{
get
{
return this.content;
}
set
{
this.content = value;
}
}
///
/// Flag that signals that Content was and should be set
/// from the Parameters collection.
///
public bool SetContentFromParameters { get; set; }
///
/// Gets and sets the content stream.
///
public Stream ContentStream
{
get { return this.contentStream; }
set
{
this.contentStream = value;
OriginalStreamPosition = -1;
if (this.contentStream != null)
{
Stream baseStream = HashStream.GetNonWrapperBaseStream(this.contentStream);
if (baseStream.CanSeek)
OriginalStreamPosition = baseStream.Position;
}
}
}
///
/// Gets and sets the original stream position.
/// If ContentStream is null or does not support seek, this propery
/// should be equal to -1.
///
public long OriginalStreamPosition
{
get { return this.originalStreamLength; }
set { this.originalStreamLength = value; }
}
///
/// Computes the SHA 256 hash of the content stream. If the stream is not
/// seekable, it searches the parent stream hierarchy to find a seekable
/// stream prior to computation. Once computed, the hash is cached for future
/// use. If a suitable stream cannot be found to use, null is returned.
///
public string ComputeContentStreamHash()
{
if (this.contentStream == null)
return null;
if (this.contentStreamHash == null)
{
var seekableStream = WrapperStream.SearchWrappedStream(this.contentStream, s => s.CanSeek);
if (seekableStream != null)
{
var position = seekableStream.Position;
byte[] payloadHashBytes = CryptoUtilFactory.CryptoInstance.ComputeSHA256Hash(seekableStream);
this.contentStreamHash = AWSSDKUtils.ToHex(payloadHashBytes, true);
seekableStream.Seek(position, SeekOrigin.Begin);
}
}
return this.contentStreamHash;
}
///
/// The name of the service to which this request is being sent.
///
public string ServiceName
{
get
{
return this.serviceName;
}
}
///
/// Alternate endpoint to use for this request, if any.
///
public RegionEndpoint AlternateEndpoint
{
get
{
return this.alternateRegion;
}
set
{
this.alternateRegion = value;
}
}
///
/// Host prefix value to prepend to the endpoint for this request, if any.
///
public string HostPrefix { get; set; }
///
/// Gets and sets the Suppress404Exceptions property. If true then 404s return back from AWS will not cause an exception and
/// an empty response object will be returned.
///
public bool Suppress404Exceptions
{
get;
set;
}
///
/// If using AWS4 signing protocol, contains the resultant parts of the
/// signature that we may need to make use of if we elect to do a chunked
/// encoding upload.
///
public AWS4SigningResult AWS4SignerResult { get; set; }
///
/// Determine whether to use a chunked encoding upload for the request
/// (applies to Amazon S3 PutObject and UploadPart requests only).
///
///
public bool UseChunkEncoding { get; set; }
///
/// Used for Amazon S3 requests where the bucket name is removed from
/// the marshalled resource path into the host header. To comply with
/// AWS2 signature calculation, we need to recover the bucket name
/// and include it in the resource canonicalization, which we do using
/// this field.
///
public string CanonicalResourcePrefix
{
get;
set;
}
///
/// This flag specifies if SigV4 is required for the current request.
///
public bool UseSigV4 { get; set; }
///
/// The authentication region to use for the request.
/// Set from Config.AuthenticationRegion.
///
public string AuthenticationRegion { get; set; }
///
/// The region in which the service request was signed.
///
public string DeterminedSigningRegion { get ; set; }
///
/// Checks if the request stream can be rewinded.
///
/// Returns true if the request stream can be rewinded ,
/// else false.
public bool IsRequestStreamRewindable()
{
var stream = this.ContentStream;
// Retries may not be possible with a stream
if (stream != null)
{
// Pull out the underlying non-wrapper stream
stream = WrapperStream.GetNonWrapperBaseStream(stream);
// Retry is possible if stream is seekable
return stream.CanSeek;
}
return true;
}
///
/// Returns true if the request can contain a request body, else false.
///
/// Returns true if the currect request can contain a request body, else false.
public bool MayContainRequestBody()
{
return
(this.HttpMethod == "POST" ||
this.HttpMethod == "PUT" ||
this.HttpMethod == "PATCH");
}
///
/// Returns true if the request has a body, else false.
///
/// Returns true if the request has a body, else false.
public bool HasRequestBody()
{
var isPutPost = (this.HttpMethod == "POST" || this.HttpMethod == "PUT" || this.HttpMethod == "PATCH");
var hasContent = this.HasRequestData();
return (isPutPost && hasContent);
}
public string GetHeaderValue(string headerName)
{
string headerValue;
if (headers.TryGetValue(headerName, out headerValue))
return headerValue;
return string.Empty;
}
}
}