1

I am writing a proxy for some site using ASP.NET Core 2.0. Proxy works fine if all it does is just re-translating HttpResponseMessage to the browser. My proxy based on this example. But I need to make some changes site content, for instance, some of the href contains an absolute reference. So when I click them from my proxy, I get to the original site, and it is a problem.

I get access to target page content, using way I find here. But when I try to copy changed content to HttpResponse.Body I run into NotSupportedException with message GZipStream does not support reading. My code is bellow:

public static async Task CopyProxyHttpResponse(this HttpContext context, HttpResponseMessage responseMessage)
{
    if (responseMessage == null)
    {
        throw new ArgumentNullException(nameof(responseMessage));
    }

    var response = context.Response;
    response.StatusCode = (int)responseMessage.StatusCode;

    //work with headers

    using (var responseStream = await responseMessage.Content.ReadAsStreamAsync())
    {
        string str;
        using (var gZipStream = new GZipStream(responseStream, CompressionMode.Decompress))
        using (var streamReader = new StreamReader(gZipStream))
        {
            str = await streamReader.ReadToEndAsync();
            //some stings changes...
        }
        var bytes = Encoding.UTF8.GetBytes(str);
        using (var msi = new MemoryStream(bytes))
        using (var mso = new MemoryStream())
        {
            using (var gZipStream = new GZipStream(mso, CompressionMode.Compress))
            {
                await msi.CopyToAsync(gZipStream);
                await gZipStream.CopyToAsync(response.Body, StreamCopyBufferSize, context.RequestAborted);
            }
        }
        //next string works, but I don't change content this way
        //await responseStream.CopyToAsync(response.Body, StreamCopyBufferSize, context.RequestAborted);
    }
}

After some search, I find out, after compression into GZipStream gZipStream.CanRead is false, it seems to be false always if CompressionMode is Compressed. I also tried to copy msi into response.Body, it doesn't throw exceptions, but in the browser I get an empty page (document Response in Network in browser console is also empty).

Is it possible to copy compressed GZipStream to another Stream or my way is entirely wrong?

QuarK
  • 1,162
  • 2
  • 12
  • 27

1 Answers1

1

GZipStream is not meant to be copied from directly. Your mso Stream is holding the compressed data.

But you can drop the mso stream entirely and copy from your msi stream to the response.Body:

using (var msi = new MemoryStream(bytes))
{
    using (var gZipStream = new GZipStream(response.Body, CompressionMode.Compress)) //<-- declare your response.Body as the target for the compressed data 
    {
        await msi.CopyToAsync(gZipStream, StreamCopyBufferSize, context.RequestAborted); //copy the msi stream to the response.Body through the gZipStream
    }
}
Hyarus
  • 922
  • 6
  • 14
  • So far it works great, I have to test it in the evening, but hope it will be enough to solve it (I was hardly hoping for it). Interesting way.. I actually copy data to `gZipStream`, and they get to `Body` some way. – QuarK Jul 30 '18 at 11:51
  • @QuarK Streams never hold any data. They only help you reading, writing and/or processing data from one source to another. Like FileStream is specialized in file access, is the GZipStream specialized in (de-)compression between sources. In your example is the GZipStream compressing the data from the msi Stream 'on the fly', while writing to your response.Body. – Hyarus Jul 30 '18 at 12:16