I’m very black and white when it comes to buying things and doing personal shopping. I almost take it to the extreme: a £20 t-shirt should be twice as good as a £10 t-shirt with regards to build quality. If it is not twice as good I’m paying for the brand. The trouble with the brand is that it is only perceived value. Perceived value is not the same as actual value and in reality bares no relationship to build quality.
I had a girlfriend a while back who spent an awful lot of money on shoes, bags and clothes. It was a constant bone of contention that a Gucci hand bag that cost £500 cost that much because it was made from quality components. Whereas a similar looking bag made by an independent was cheaper because it was made with sub-standard materials. “You’re paying for the quality” she kept saying. Amazing, such is the power of marketing!
While there may be other things that effect the price, such as after sales support, the majority of the cost comes from the perception that if it is expensive then only a select few will be able to afford them and so with that comes exclusivity. There is no way that a pair of Emporio Armani jeans are 5 times as good as a pair of Levi jeans even though their price tag is five times more. So what am I paying for? It costs a lot of money to advertise in exclusive magazines and even more to advertise on television. I expect it costs quite a bit to push their wears on celebrities and sponsorship. But none of these expenses have anything to do with the actual product. I don’t expect these companies are pouring money into R & D to design the latest bags; market research isn’t that expensive and because it’s fashion, what ever they say the latest fashion is - then that’s what it is. So no money spent there either.
Maybe I’m taking a simplistic view on this, but is that just it. Is everyone being duped in to paying twice the price for nothing?
One of the most difficult things in the world is to convince a woman that even a bargain costs money.I ended up with a house full of things I didn’t need or want that my girlfriend had bought because they were cheap. For some reason I couldn’t convince her that just because they had knocked 50% off the price didn’t mean that it was a bargain. It only became a bargain if we needed it and more often than not it was just used as ammo for her argument about buying a bigger house!
API call fail with message:System.BadImageFormatException: An attempt was made to load a program with an incorrect format. (Exception from HRESULT: 0x8007000B) at SentinelRMSCore.Interface.LSAPI.VLSinitialize() at SentinelRMSCore.RMSDKCore.VLSinitialize():VLSinitialize API call fail with error-code:501While the manual for Sentinel RMS was pretty good it was all about the API’s and how to call them. The documentation was missing a How to get started section that would describe how to set up Visual Studio. There is a brief few lines saying which version of Visual Studio are supported but that’s about it.
- Download SDK RMS 8.5.0 Windows 32 and 64-bit GA release (you can get this from your support representative)
- Download the Sentinel RMS 8.4.1 .Net Interface (you can get this from your support representative)
- Unpack and install the SDK.
- Add the following DLL folder to your PATH:
C:\Program Files (x86)\SafeNet Sentinel\Sentinel RMS Development Kit\8.5\English\MsvcDev\Lib\MSVS2008\Win32\DLL\
This will allow the C# assemblies to find the required DLLs. - Unpack the .Net Interface.
- Navigate to
RMS 8.4.1 .NET Interface\Examples
- Create a folder called
Libraries
- Copy the contents of
RMS 8.4.1 .NET Interface\Library
into theRMS 8.4.1 .NET Interface\Examples\Libraries
folder - Navigate to
RMS 8.4.1 .NET Interface\Examples\VisualC#\AddRemoveLicense
- Double click the solution file (.sln) to launch Visual Studio. The documentation says that only Microsoft Visual Studio 2003/2005/2008 is supported so make sure its one of those.
- When the solution loads in, right-click on the project name and select Properties.
- Under the Application tab change the Target Framework to 3.5.
- Under the Build tab change the Platform Target to x86.
- Then save, clean solution, rebuild solution and run.
PATH
then I got the following error message, and in order to fix it I had to manually copy the lsapiw32.dll into the Release/Debug folder. There didn’t seem to be anywhere in visual studio that let you add extra DLL search folders because it is rubbish.
API call fail with message:System.DllNotFoundException: Unable to load DLL ‘lsapiw32.dll’: The specified module could not be found. (Exception from HRESULT: 0x8007007E) at SentinelRMSCore.Interface.LSAPI.VLSinitialize() at SentinelRMSCore.RMSDKCore.VLSinitialize():VLSinitialize API call fail with error-code:501The other problem, which is probably more generally useful, was that of the System.BadImageFormatException exception. This occurs when the Common Language Runtime (CLR) tries to load an assembly that contains unmanaged code built targeting a different platform (thanks Dave). In my case with Sentinal, the
lsapiw32.dll
was compiled with the platform x86 for the 32-bit version of the DLL. Visual Studio defaults to building for a target platform of Any and this discrepancy is what causes the error. Equally if I had chosen to fill the Libraries
folder with DLLs from the Library(x64)
folder (and the corresponding C:\Program Files (x86)\SafeNet Sentinel\Sentinel RMS Development Kit\8.5\English\MsvcDev\Lib\MSVS2008\Win64\DLL
) instead then I would have had exactly the same problem.web.config
. For each request that comes in the module will remember the remote host, remote port and how many requests have been serviced by that combination. When the request is in its final stage we’ll check to see if the number of serviced requests is bigger than MaxKeepAliveRequests. If it is then we can inject a Connection: close into the response. This will make its way through IIS, safely closing the connection on it’s way out.
Surprisingly there was a great deal of confusion on MSDN documentation, blogs and forums surrounding how to force a close after a request. I found that HttpResponse.Close()
can chop the end off the reply, HttpApplication.CompleteRequest()
didn’t work because the request’s run line was already inside the EndRequest
section of the pipeline. So I went back to the specification and in RFC2616: Section 8 - Connections it talks about injecting Connection: close into the response header so that after the response is sent out the server closes the connection. The closure forces the client to reconnect. I tried this using a telnet client (and not a web browser) and can reveal that it is the server that closes the connection and not the client deciding.
I had thought about using the Session to store the request count but I didn’t think it would help. If a proxy server is talking to your cluster then it may be interleaving requests from several sources with different session identifiers. We are interested in the transport layer, and not the session layer. We must use values from the transport layer to differentiate the clients in order to spread the load.
Simply compile up this C# and add it to your IIS integrated process pipe line.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Diagnostics;
using System.Collections.Specialized;
using System.IO;
namespace WebApplication1
{
public class MaxKeepAliveRequestsModule : IHttpModule
{
int maxRequests = 0;
Dictionary<string, KeepAliveClient> record = new Dictionary<string, KeepAliveClient>();
public MaxKeepAliveRequestsModule()
{
Debug.WriteLine("Debug : MaxKeepAliveRequestsModule.con");
}
public int MaxRequests
{
get { return maxRequests; }
set { maxRequests = value; }
}
public void Init(HttpApplication context)
{
Debug.WriteLine("Debug : creating MaxKeepAliveRequestsModule");
string mrStr = System.Web.Configuration.WebConfigurationManager.AppSettings["MaxKeepAliveRequests"];
maxRequests = validateMaxKeepAliveRequestsValue(mrStr);
context.EndRequest += new EventHandler(OnEndRequest);
}
private int validateMaxKeepAliveRequestsValue(string val)
{
if (val == null || val.Length == 0)
throw new ArgumentException("appSettings.MaxKeepAliveRequests is empty");
int mr = Convert.ToInt32(val);
if (mr < 1)
throw new ArgumentException("appSettings.MaxKeepAliveRequests must be greater than zero: " + mr);
return mr;
}
public void Dispose()
{
Debug.WriteLine("Debug : MaxKeepAliveRequestsModule.Dispose");
}
public void OnEndRequest(Object source, EventArgs e)
{
Debug.WriteLine("Debug : MaxKeepAliveRequestsModule.OnEndRequest");
HttpApplication app = (HttpApplication) source;
HttpRequest request = app.Context.Request;
HttpResponse response = app.Context.Response;
// Tried to use socket as the key, but don't seem to back access to it from here
// Stream k = response.OutputStream;
NameValueCollection serverVariables = request.ServerVariables;
string k = serverVariables["REMOTE_HOST"] + ":" + serverVariables["REMOTE_PORT"];
if (record.ContainsKey(k))
{
KeepAliveClient c = record[k];
Debug.WriteLine("Debug : MaxKeepAliveRequestsModule.OnEndRequest: hit");
if (c.Hits > maxRequests)
{
Debug.WriteLine("Debug : MaxKeepAliveRequestsModule.OnEndRequest:max requests reached for " + k + "(" + c.Hits + "), force close connection to client");
// works, but may chop the end of the response
// response.Close();
// doesn't appear to work
// app.CompleteRequest();
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html
response.Headers["Connection"] = "close";
record.Remove(k);
return;
}
c.touch();
}
else
{
Debug.WriteLine("Debug : MaxKeepAliveRequestsModule.OnEndRequest: miss");
cleanOldKeepAliveRecords();
record.Add(k, new KeepAliveClient(k));
}
}
private void cleanOldKeepAliveRecords()
{
foreach (KeepAliveClient cc in record.Values.ToList())
{
if (cc.isExpired())
{
Debug.WriteLine("Debug : MaxKeepAliveRequestsModule.cleanOldKeepAliveRecords: key=" + cc.Key);
record.Remove(cc.Key);
}
}
}
}
class KeepAliveClient
{
private static TimeSpan TIMEOUT = new TimeSpan(1, 0, 0); // hour
private DateTime now;
private int hits;
private string key;
public KeepAliveClient(string key)
{
this.key = key;
now = DateTime.Now;
hits = 1;
}
public int Hits
{
get { return hits; }
}
public string Key
{
get { return key; }
}
public void touch()
{
hits++;
now = DateTime.Now;
}
public bool isExpired()
{
return now + TIMEOUT < DateTime.Now;
}
}
}
You’ll need to add the configuration option to the web.config
<configuration>
<appSettings>
<add key="MaxKeepAliveRequests" value="100"/>
</appSettings>
</configuration>