I ended up updating the code to use WCF since that's what it is in the current dev version I've been working on. Then I used some code that was posted on the Amazon forums, but made it a little easier to use.
UPDATE: new easier to use code that lets you still use the config settings for everything
In the previous code I posted, and what I've seen elsewhere, when the service object is created one of the constructor overrides is used to tell it to use HTTPS, give it the HTTPS url and to manually attach the message inspector that will do the signing. The downfall to not using the default constructor is you lose the ability to configure the service via the config file.
I've since redone this code so you can continue to use the default, parameterless, constructor and configure the service via the config file. The benifit of this is you don't have to recompile your code to use this, or make changes once deployed such as to maxStringContentLength (which is what caused this change to take place as well as discover the downfalls to doing it all in code). I also updated the signing part a bit so that way you can tell it what hashing algorithm to use as well as the regex for extracting the Action.
These two changes are because not all web services from Amazon use the same hashing algorithm and the Action might need to be extracted differently. This means you can reuse the same code for each service type just by changing what’s in the config file.
public class SigningExtension : BehaviorExtensionElement
{
public override Type BehaviorType
{
get { return typeof(SigningBehavior); }
}
[ConfigurationProperty("actionPattern", IsRequired = true)]
public string ActionPattern
{
get { return this["actionPattern"] as string; }
set { this["actionPattern"] = value; }
}
[ConfigurationProperty("algorithm", IsRequired = true)]
public string Algorithm
{
get { return this["algorithm"] as string; }
set { this["algorithm"] = value; }
}
[ConfigurationProperty("algorithmKey", IsRequired = true)]
public string AlgorithmKey
{
get { return this["algorithmKey"] as string; }
set { this["algorithmKey"] = value; }
}
protected override object CreateBehavior()
{
var hmac = HMAC.Create(Algorithm);
if (hmac == null)
{
throw new ArgumentException(string.Format("Algorithm of type ({0}) is not supported.", Algorithm));
}
if (string.IsNullOrEmpty(AlgorithmKey))
{
throw new ArgumentException("AlgorithmKey cannot be null or empty.");
}
hmac.Key = Encoding.UTF8.GetBytes(AlgorithmKey);
return new SigningBehavior(hmac, ActionPattern);
}
}
public class SigningBehavior : IEndpointBehavior
{
private HMAC algorithm;
private string actionPattern;
public SigningBehavior(HMAC algorithm, string actionPattern)
{
this.algorithm = algorithm;
this.actionPattern = actionPattern;
}
public void Validate(ServiceEndpoint endpoint)
{
}
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.MessageInspectors.Add(new SigningMessageInspector(algorithm, actionPattern));
}
}
public class SigningMessageInspector : IClientMessageInspector
{
private readonly HMAC Signer;
private readonly Regex ActionRegex;
public SigningMessageInspector(HMAC algorithm, string actionPattern)
{
Signer = algorithm;
ActionRegex = new Regex(actionPattern);
}
public void AfterReceiveReply(ref Message reply, object correlationState)
{
}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
var operation = GetOperation(request.Headers.Action);
var timeStamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ");
var toSignBytes = Encoding.UTF8.GetBytes(operation + timeStamp);
var sigBytes = Signer.ComputeHash(toSignBytes);
var signature = Convert.ToBase64String(sigBytes);
request.Headers.Add(MessageHeader.CreateHeader("AWSAccessKeyId", Helpers.NameSpace, Helpers.AWSAccessKeyId));
request.Headers.Add(MessageHeader.CreateHeader("Timestamp", Helpers.NameSpace, timeStamp));
request.Headers.Add(MessageHeader.CreateHeader("Signature", Helpers.NameSpace, signature));
return null;
}
private string GetOperation(string request)
{
var match = ActionRegex.Match(request);
var val = match.Groups["action"];
return val.Value;
}
}
To use this you don't need to make any changes to your existing code, you can even put the signing code in a whole other assembly if need be. You just need to set up the config section as so (note: the version number is important, without it matching the code will not load or run)
<system.serviceModel>
<extensions>
<behaviorExtensions>
<add name="signer" type="WebServices.Amazon.SigningExtension, AmazonExtensions, Version=1.3.11.7, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
<behaviors>
<endpointBehaviors>
<behavior name="AWSECommerceBehaviors">
<signer algorithm="HMACSHA256" algorithmKey="..." actionPattern="\w:\/\/.+/(?<action>.+)" />
</behavior>
</endpointBehaviors>
</behaviors>
<bindings>
<basicHttpBinding>
<binding name="AWSECommerceServiceBinding" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered" useDefaultWebProxy="true" maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536">
<readerQuotas maxDepth="32" maxStringContentLength="16384" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<security mode="Transport">
<transport clientCredentialType="None" proxyCredentialType="None" realm="" />
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="https://ecs.amazonaws.com/onca/soap?Service=AWSECommerceService" behaviorConfiguration="AWSECommerceBehaviors" binding="basicHttpBinding" bindingConfiguration="AWSECommerceServiceBinding" contract="WebServices.Amazon.AWSECommerceServicePortType" name="AWSECommerceServicePort" />
</client>
</system.serviceModel>
git log --pretty=format:"%h%x09%an%x09%ad%x09%s"
did the job. This outputs:
fbc3503 mads Thu Dec 4 07:43:27 2008 +0000 show mobile if phone is null...
ec36490 jesper Wed Nov 26 05:41:37 2008 +0000 Cleanup after [942]: Using timezon
ae62afd tobias Tue Nov 25 21:42:55 2008 +0000 Fixed #67 by adding time zone supp
164be7e mads Tue Nov 25 19:56:43 2008 +0000 fixed tests, and a 'unending appoi
93f1526 jesper Tue Nov 25 09:45:56 2008 +0000 adding time.ZONE.now as time zone
2f0f8c1 tobias Tue Nov 25 03:07:02 2008 +0000 Timezone configured in environment
a33c1dc jesper Tue Nov 25 01:26:18 2008 +0000 updated to most recent will_pagina
Inspired by stackoverflow question: "git log output like svn ls -v", i found out that I could add the exact params I needed.
To shorten the date (not showing the time) use --date=short
In case you were curious what the different options were:
%h
= abbreviated commit hash
%x09
= tab (character for code 9)
%an
= author name
%ad
= author date (format respects --date= option)
%s
= subject
From kernel.org/pub/software/scm/git/docs/git-log.html (PRETTY FORMATS section) by comment of Vivek.
Best Answer
You could use SOAP extensions to get the SOAP content as a string and then log it wherever.