Add support for GZip and Deflate (#439)

* gzip - wip

* wip

* tests

* fix gzip and deflate

* CheckIfShouldKillVBCSCompiler

* DisableRequestBodyDecompressing
This commit is contained in:
Stef Heyenrath
2020-04-10 19:05:09 +02:00
committed by GitHub
parent a9974a4874
commit e91be0a4d1
22 changed files with 504 additions and 366 deletions

View File

@@ -52,5 +52,10 @@ namespace WireMock.Util
/// The detected body type (detection based on Content-Type).
/// </summary>
public BodyType DetectedBodyTypeFromContentType { get; set; }
/// <summary>
/// The detected compression.
/// </summary>
public string DetectedCompression { get; set; }
}
}

View File

@@ -1,11 +1,12 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Newtonsoft.Json;
using WireMock.Http;
using WireMock.Matchers;
using WireMock.Types;
@@ -108,15 +109,17 @@ namespace WireMock.Util
return BodyType.Bytes;
}
public static async Task<BodyData> Parse([NotNull] Stream stream, [CanBeNull] string contentType = null, bool deserializeJson = true)
public static async Task<BodyData> Parse([NotNull] BodyParserSettings settings)
{
Check.NotNull(stream, nameof(stream));
Check.NotNull(settings, nameof(settings));
var bodyWithContentEncoding = await ReadBytesAsync(settings.Stream, settings.ContentEncoding, settings.DecompressGZipAndDeflate);
var data = new BodyData
{
BodyAsBytes = await ReadBytesAsync(stream),
BodyAsBytes = bodyWithContentEncoding.Value,
DetectedCompression = bodyWithContentEncoding.Key,
DetectedBodyType = BodyType.Bytes,
DetectedBodyTypeFromContentType = DetectBodyTypeFromContentType(contentType)
DetectedBodyTypeFromContentType = DetectBodyTypeFromContentType(settings.ContentType)
};
// In case of MultiPart: check if the BodyAsBytes is a valid UTF8 or ASCII string, in that case read as String else keep as-is
@@ -141,7 +144,7 @@ namespace WireMock.Util
data.DetectedBodyType = BodyType.String;
// If string is not null or empty, try to deserialize the string to a JObject
if (deserializeJson && !string.IsNullOrEmpty(data.BodyAsString))
if (settings.DeserializeJson && !string.IsNullOrEmpty(data.BodyAsString))
{
try
{
@@ -161,12 +164,21 @@ namespace WireMock.Util
return data;
}
private static async Task<byte[]> ReadBytesAsync(Stream stream)
private static async Task<KeyValuePair<string, byte[]>> ReadBytesAsync(Stream stream, string contentEncoding = null, bool decompressGZipAndDeflate = true)
{
using (var memoryStream = new MemoryStream())
{
await stream.CopyToAsync(memoryStream);
return memoryStream.ToArray();
byte[] data = memoryStream.ToArray();
string type = contentEncoding?.ToLowerInvariant();
if (decompressGZipAndDeflate && (type == "gzip" || type == "deflate"))
{
return new KeyValuePair<string, byte[]>(type, CompressionUtils.Decompress(type, data));
}
return new KeyValuePair<string, byte[]>(null, data);
}
}
}

View File

@@ -0,0 +1,17 @@
using System.IO;
namespace WireMock.Util
{
internal class BodyParserSettings
{
public Stream Stream { get; set; }
public string ContentType { get; set; }
public string ContentEncoding { get; set; }
public bool DecompressGZipAndDeflate { get; set; } = true;
public bool DeserializeJson { get; set; } = true;
}
}

View File

@@ -1,29 +0,0 @@
using FastDeepCloner;
using System;
using System.Runtime.Serialization;
namespace WireMock.Util
{
internal static class CloneUtils
{
private static FastDeepClonerSettings settings = new FastDeepCloner.FastDeepClonerSettings()
{
FieldType = FastDeepCloner.FieldType.FieldInfo,
OnCreateInstance = new FastDeepCloner.Extensions.CreateInstance((Type type) =>
{
#if !NETSTANDARD1_3
return FormatterServices.GetUninitializedObject(type);
#else
#endif
})
};
public static T DeepClone<T>(T objectToBeCloned) where T : class
{
//return CloneExtensionsEx.CloneFactory.GetClone(objectToBeCloned);
// Expression.Lambda<Func<object>>(Expression.New(type)).Compile()
return FastDeepCloner.DeepCloner.Clone(objectToBeCloned, settings);
}
}
}

View File

@@ -0,0 +1,49 @@
using System;
using System.IO;
using System.IO.Compression;
namespace WireMock.Util
{
internal static class CompressionUtils
{
public static byte[] Compress(string contentEncoding, byte[] data)
{
using (var compressedStream = new MemoryStream())
using (var zipStream = Create(contentEncoding, compressedStream, CompressionMode.Compress))
{
zipStream.Write(data, 0, data.Length);
#if !NETSTANDARD1_3
zipStream.Close();
#endif
return compressedStream.ToArray();
}
}
public static byte[] Decompress(string contentEncoding, byte[] data)
{
using (var compressedStream = new MemoryStream(data))
using (var zipStream = Create(contentEncoding, compressedStream, CompressionMode.Decompress))
using (var resultStream = new MemoryStream())
{
zipStream.CopyTo(resultStream);
return resultStream.ToArray();
}
}
private static Stream Create(string contentEncoding, Stream stream, CompressionMode mode)
{
switch (contentEncoding)
{
case "gzip":
return new GZipStream(stream, mode);
case "deflate":
return new DeflateStream(stream, mode);
default:
throw new NotSupportedException($"ContentEncoding '{contentEncoding}' is not supported.");
}
}
}
}

View File

@@ -1,26 +0,0 @@
using System.Collections.Generic;
using System.Linq;
namespace WireMock.Util
{
public class IndexableDictionary<TKey, TValue> : Dictionary<TKey, TValue>
{
/// <summary>
/// Gets the value associated with the specified index.
/// </summary>
/// <param name="index"> The index of the value to get.</param>
/// <returns>The value associated with the specified index.</returns>
public TValue this[int index]
{
get
{
// get the item for that index.
if (index < 0 || index > Count)
{
throw new KeyNotFoundException();
}
return Values.Cast<TValue>().ToArray()[index];
}
}
}
}

View File

@@ -1,80 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Threading;
namespace WireMock.Util
{
/// <summary>
/// http://johnculviner.com/achieving-named-lock-locker-functionality-in-c-4-0/
/// </summary>
internal class NamedReaderWriterLocker
{
private readonly ConcurrentDictionary<string, ReaderWriterLockSlim> _lockDict = new ConcurrentDictionary<string, ReaderWriterLockSlim>();
public ReaderWriterLockSlim GetLock(string name)
{
return _lockDict.GetOrAdd(name, s => new ReaderWriterLockSlim());
}
public TResult RunWithReadLock<TResult>(string name, Func<TResult> body)
{
var rwLock = GetLock(name);
try
{
rwLock.EnterReadLock();
return body();
}
finally
{
rwLock.ExitReadLock();
}
}
public void RunWithReadLock(string name, Action body)
{
var rwLock = GetLock(name);
try
{
rwLock.EnterReadLock();
body();
}
finally
{
rwLock.ExitReadLock();
}
}
public TResult RunWithWriteLock<TResult>(string name, Func<TResult> body)
{
var rwLock = GetLock(name);
try
{
rwLock.EnterWriteLock();
return body();
}
finally
{
rwLock.ExitWriteLock();
}
}
public void RunWithWriteLock(string name, Action body)
{
var rwLock = GetLock(name);
try
{
rwLock.EnterWriteLock();
body();
}
finally
{
rwLock.ExitWriteLock();
}
}
public void RemoveLock(string name)
{
_lockDict.TryRemove(name, out _);
}
}
}