Get the Body of a JsonServiceClient request before sending

I need to calculate a HMAC signature of a request and then set a request header value of that signature, before sending the request of a JsonServiceClient.

I started down the track of handling the JsonSericeClient.RequestFilter, but quickly discovered that I cannot read the body form the request.GetRequestStream() in that context. (Exception: when I try to copy it stream.CopyTo()).

How can I get the body of the request from a JonServiceClient in time to set a header value?

It’s not possible to re-read a Request Stream as it’s a forward only Stream. I’ve made SerializeRequestToStream protected in this commit so you can override it in a sub class. You’ll need to write it to a MemoryStream so you can read it back to get a copy of the bytes then use it write to the Request Stream and calculate the hash.

This change is available from v4.5.7 that’s now on MyGet.

Thanks @mythz,

Before I go there. Isn’t it also possible to override Send() and calculate my hash and set the headers there too?
(I think I saw you do something similar in the EncryptedServiceClient)

EncryptedServiceClient works differently, it’s not overriding Send, it’s converting the Request DTO into another EncryptedMessage Request DTO and sending that using an internal client instead.

Now looking at the call to SerializeRequestToStream from SendRequest the IRequest is going to be null, which means once I override and calculate my hash, I wont be able to set the value of the hash to a headers of the request.

How am I going to do this?

var hash = calculateHash(stream);

request.Headers.Add("X-Hub-Signature", hash);

Note I removed the unused IRequest param in a later commit. But I should’ve made SendRequest protected instead as you don’t have access to the HttpWebRequest in SerializeRequestToStream.

Basically you should now be able to do something like:

protected override WebRequest SendRequest(string httpMethod, string requestUri, object request)
{
    return PrepareWebRequest(httpMethod, requestUri, request, client =>
    {
        using (var ms = MemoryStreamFactory.GetStream())
        using (var requestStream = PclExport.Instance.GetRequestStream(client))
        {
            SerializeRequestToStream(request, ms);
            ms.Position = 0;
            var bytes = ms.ToArray();
            client.Headers["X-Hub-Signature"] = calculateHash(bytes);
            requestStream.Write(bytes, 0, bytes.Length);
        }
    });
}

Change to make SendRequest() protected is now on MyGet.

PrepareWebRequest needs to be made protected too

Why? If you’re going to override PrepareWebRequest you may as well avoid the Service Client and just use HttpWebRequest directly.

I cant use your example code below unless PrepareWebRequest is changed from private to something I can call from my subclass.

I have no intention of overriding it, just need to be able to call it from my subclass.

ok cool, PrepareWebRequest is protected from this commit which is now available on MyGet.

Thanks @mythz,

OK we now have a problem in the example you gave me.

SerializeRequestStream, line: 912 closes the passed in [MemoryStream] stream, so the example throws because the stream is closed before we can use it to calculate the hash, and then write it to the requestStream again.

I could override SerializeRequestToStream and not close the stream, but first I’d need to know why you were closing it in the first place.

Can you remember why the stream is closed in this context? I see a few lines before that new streams are used in compression. Its a little hard to decipher what the intention was here.

Perhaps an optional parameter on the method to keep the stream open (by default not)?

It’s required for Request Compression.

How should I work around this then?
Presumably by overriding SerializeRequestToStream?
But what to do?

Just don’t close it? means you can’t enable Request Compression which is a new feature added in the last release.

OK any chance you could change the signature to include the optional param: keepOpen

        protected virtual void SerializeRequestToStream(object request, Stream requestStream, bool keepOpen = false)
        {
            var str = request as string;
            var bytes = request as byte[];
            var stream = request as Stream;
            if (str != null)
            {
                requestStream.Write(str);
            }
            else if (bytes != null)
            {
                requestStream.Write(bytes, 0, bytes.Length);
            }
            else if (stream != null)
            {
                stream.WriteTo(requestStream);
            }
            else
            {
#if !SL5
                if (RequestCompressionType == CompressionTypes.Deflate)
                {
                    requestStream = new DeflateStream(requestStream, CompressionMode.Compress);
                }
                else if (RequestCompressionType == CompressionTypes.GZip)
                {
                    requestStream = new GZipStream(requestStream, CompressionMode.Compress);
                }
#endif
                SerializeToStream(null, request, requestStream);

                if (!keepOpen)
                {
                    requestStream.Close();
                }
            }
        }        

Its not going to break anything, since you only just made the method protected.
AND I would not have to worry about COMPILER variables like SL5 in our library.

I could then call it with KeepOpen = true and close the stream after I’ve done my thing with it.

This change is now on MyGet.

Thank you very much. That is great.

Here is the example reworked a little with the recent changes:

        protected override WebRequest SendRequest(string httpMethod, string requestUri, object request)
        {
            return PrepareWebRequest(httpMethod, requestUri, request, client =>
            {
                using (var ms = MemoryStreamFactory.GetStream())
                using (var requestStream = PclExport.Instance.GetRequestStream(client))
                {
                    SerializeRequestToStream(request, ms, true);
                    var bytes = ms.ToArray();
                    client.Headers["X-Hub-Signature"] = calculateHash(bytes);
                    requestStream.Write(bytes, 0, bytes.Length);
                }
            });
        }