Skip to content

Instantly share code, notes, and snippets.

@dotMorten
Last active May 8, 2018 19:38
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save dotMorten/4981198 to your computer and use it in GitHub Desktop.
Save dotMorten/4981198 to your computer and use it in GitHub Desktop.
GZip support for PCL HttpClient. Create HttpClient using: HttpClient client = new HttpClient(new HttpGZipClientHandler());
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
namespace SharpGIS.Http
{
public class HttpGZipClientHandler : HttpClientHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip"));
var response = await base.SendAsync(request, cancellationToken);
if (response.Content.Headers.ContentEncoding.Contains("gzip"))
{
await response.Content.LoadIntoBufferAsync();
response.Content = new HttpGZipContent(await response.Content.ReadAsStreamAsync());
}
return response;
}
public override bool SupportsAutomaticDecompression
{
get
{
return true;
}
}
}
internal sealed class HttpGZipContent : HttpContent
{
private readonly GZipInflateStream m_stream;
public HttpGZipContent(Stream stream)
{
m_stream = new GZipInflateStream(stream);
}
protected override Task SerializeToStreamAsync(System.IO.Stream stream, System.Net.TransportContext context)
{
return m_stream.CopyToAsync(stream);
}
protected override bool TryComputeLength(out long length)
{
length = m_stream.Length;
return true;
}
protected override void Dispose(bool disposing)
{
m_stream.Dispose();
base.Dispose(disposing);
}
}
}
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Windows;
using System.Windows.Resources;
namespace SharpGIS.Http
{
internal sealed class GZipInflateStream : Stream
{
private readonly Stream _deflatedStream;
private Stream _inflatedStream;
public GZipInflateStream(System.IO.Stream deflatedStream)
{
_deflatedStream = deflatedStream;
ProcessStream();
}
private void ProcessStream()
{
if ((0x1f != _deflatedStream.ReadByte()) || // ID1
(0x8b != _deflatedStream.ReadByte()) || // ID2
(8 != _deflatedStream.ReadByte())) // CM (8 == deflate)
{
throw new NotSupportedException("Compressed data not in the expected format.");
}
// Read flags
var flg = _deflatedStream.ReadByte(); // FLG
var fhcrc = 0 != (0x2 & flg); // CRC16 present before compressed data
var fextra = 0 != (0x4 & flg); // extra fields present
var fname = 0 != (0x8 & flg); // original file name present
var fcomment = 0 != (0x10 & flg); // file comment present
// Skip unsupported fields
if (_deflatedStream.CanSeek)
_deflatedStream.Seek(6, SeekOrigin.Current);
else
{
_deflatedStream.ReadByte();
_deflatedStream.ReadByte();
_deflatedStream.ReadByte();
_deflatedStream.ReadByte(); // MTIME
_deflatedStream.ReadByte(); // XFL
_deflatedStream.ReadByte(); // OS
}
if (fextra)
{
// Skip XLEN bytes of data
var xlen = _deflatedStream.ReadByte() | (_deflatedStream.ReadByte() << 8);
while (0 < xlen)
{
_deflatedStream.ReadByte();
xlen--;
}
}
if (fname)
{
// Skip 0-terminated file name
while (0 != _deflatedStream.ReadByte())
{
}
}
if (fcomment)
{
// Skip 0-terminated file comment
while (0 != _deflatedStream.ReadByte())
{
}
}
if (fhcrc)
{
_deflatedStream.ReadByte(); _deflatedStream.ReadByte(); // CRC16
}
// Read compressed data
const int zipHeaderSize = 30 + 1; // 30 bytes + 1 character for file name
const int zipFooterSize = 68 + 1; // 68 bytes + 1 character for file name
// Download unknown amount of compressed data efficiently (note: Content-Length header is not always reliable)
var buffers = new List<byte[]>();
var buffer = new byte[4096];
var bytesInBuffer = 0;
var totalBytes = 0;
var bytesRead = 0;
do
{
if (buffer.Length == bytesInBuffer)
{
// Full, allocate another
buffers.Add(buffer);
buffer = new byte[buffer.Length];
bytesInBuffer = 0;
}
Debug.Assert(bytesInBuffer < buffer.Length);
bytesRead = _deflatedStream.Read(buffer, bytesInBuffer, buffer.Length - bytesInBuffer);
bytesInBuffer += bytesRead;
totalBytes += bytesRead;
} while (0 < bytesRead);
buffers.Add(buffer);
// "Trim" crc32 and isize fields off the end
var compressedSize = totalBytes - 4 - 4;
if (compressedSize < 0)
{
throw new NotSupportedException("Compressed data not in the expected format.");
}
// Create contiguous buffer
var compressedBytes = new byte[zipHeaderSize + compressedSize + zipFooterSize];
var offset = zipHeaderSize;
var remainingBytes = totalBytes;
foreach (var b in buffers)
{
var length = Math.Min(b.Length, remainingBytes);
Array.Copy(b, 0, compressedBytes, offset, length);
offset += length;
remainingBytes -= length;
}
Debug.Assert(0 == remainingBytes);
// Read footer from end of compressed bytes (note: footer is within zipFooterSize; will be overwritten below)
Debug.Assert(totalBytes <= compressedSize + zipFooterSize);
offset = zipHeaderSize + compressedSize;
var crc32 = compressedBytes[offset + 0] | (compressedBytes[offset + 1] << 8) | (compressedBytes[offset + 2] << 16) | (compressedBytes[offset + 3] << 24);
var isize = compressedBytes[offset + 4] | (compressedBytes[offset + 5] << 8) | (compressedBytes[offset + 6] << 16) | (compressedBytes[offset + 7] << 24);
if (0 == isize) // HACK to handle compressed 0-byte streams without figuring out what's really going wrong
{
_inflatedStream = new MemoryStream();
return;
}
// Create ZIP file stream
const string fileName = "f"; // MUST be 1 character (offsets below assume this)
Debug.Assert(1 == fileName.Length);
var zipFileMemoryStream = new MemoryStream(compressedBytes);
var writer = new BinaryWriter(zipFileMemoryStream);
// Local file header
writer.Write((uint)0x04034b50); // local file header signature
writer.Write((ushort)20); // version needed to extract (2.0 == compressed using deflate)
writer.Write((ushort)0); // general purpose bit flag
writer.Write((ushort)8); // compression method (8: deflate)
writer.Write((ushort)0); // last mod file time
writer.Write((ushort)0); // last mod file date
writer.Write(crc32); // crc-32
writer.Write(compressedSize); // compressed size
writer.Write(isize); // uncompressed size
writer.Write((ushort)1); // file name length
writer.Write((ushort)0); // extra field length
writer.Write((byte)fileName[0]); // file name
// File data (already present)
zipFileMemoryStream.Seek(compressedSize, SeekOrigin.Current);
// Central directory structure
writer.Write((uint)0x02014b50); // central file header signature
writer.Write((ushort)20); // version made by
writer.Write((ushort)20); // version needed to extract (2.0 == compressed using deflate)
writer.Write((ushort)0); // general purpose bit flag
writer.Write((ushort)8); // compression method
writer.Write((ushort)0); // last mod file time
writer.Write((ushort)0); // last mod file date
writer.Write(crc32); // crc-32
writer.Write(compressedSize); // compressed size
writer.Write(isize); // uncompressed size
writer.Write((ushort)1); // file name length
writer.Write((ushort)0); // extra field length
writer.Write((ushort)0); // file comment length
writer.Write((ushort)0); // disk number start
writer.Write((ushort)0); // internal file attributes
writer.Write((uint)0); // external file attributes
writer.Write((uint)0); // relative offset of local header
writer.Write((byte)fileName[0]); // file name
// End of central directory record
writer.Write((uint)0x06054b50); // end of central dir signature
writer.Write((ushort)0); // number of this disk
writer.Write((ushort)0); // number of the disk with the start of the central directory
writer.Write((ushort)1); // total number of entries in the central directory on this disk
writer.Write((ushort)1); // total number of entries in the central directory
writer.Write((uint)(46 + 1)); // size of the central directory (46 bytes + 1 character for file name)
writer.Write((uint)(zipHeaderSize + compressedSize)); // offset of start of central directory with respect to the starting disk number
writer.Write((ushort)0); // .ZIP file comment length
// Reset ZIP file stream to beginning
zipFileMemoryStream.Seek(0, SeekOrigin.Begin);
// Return the decompressed stream
_inflatedStream = Application.GetResourceStream(
new StreamResourceInfo(zipFileMemoryStream, null),
new Uri(fileName, UriKind.Relative))
.Stream;
}
public override bool CanRead
{
get { return _inflatedStream.CanRead; }
}
public override bool CanSeek
{
get { return _inflatedStream.CanSeek; }
}
public override bool CanWrite
{
get { return _inflatedStream.CanWrite; }
}
public override void Flush()
{
_inflatedStream.Flush();
}
public override long Length
{
get { return _inflatedStream.Length; }
}
public override long Position
{
get { return _inflatedStream.Position; }
set { _inflatedStream.Position = value; }
}
public override int Read(byte[] buffer, int offset, int count)
{
return _inflatedStream.Read(buffer, offset, count);
}
public override long Seek(long offset, SeekOrigin origin)
{
return _inflatedStream.Seek(offset, origin);
}
public override void SetLength(long value)
{
_inflatedStream.SetLength(value);
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
public override void Close()
{
_deflatedStream.Close();
_inflatedStream.Close();
}
protected override void Dispose(bool disposing)
{
_deflatedStream.Dispose();
_inflatedStream.Dispose();
}
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
return _inflatedStream.BeginRead(buffer, offset, count, callback, state);
}
public override int ReadByte()
{
return _inflatedStream.ReadByte();
}
public override int EndRead(IAsyncResult asyncResult)
{
return _inflatedStream.EndRead(asyncResult);
}
public override int ReadTimeout
{
get { return _inflatedStream.ReadTimeout; }
set { _inflatedStream.ReadTimeout = value; }
}
public override bool CanTimeout
{
get { return _inflatedStream.CanTimeout; }
}
}
}
@robertmclaws
Copy link

Line 193 relies on classes not available in PCLs... can you please provide a version that will compile to portable-net403+sl4+wp71+win8? Thanks!

@robertmclaws
Copy link

This works in Silverlight & Windows Phone, but if you are building a PCL that needs compression, this will do the job: https://github.com/advancedrei/HttpClient.Compression (check the readme for details on how to use from NuGet).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment