Update BodyParser logic (#212)

* Update BodyParser logic

* update logic for byte[]

* small update

* MyGetKey

* myget

* dotnet nuget push

* dotnet build

* Release

* .

* StringContent

* 1.0.4.18-preview-02

* Debug

* 1.0.4.18-preview-02

* disable some proxy tests

* myget

* packagesToPack

* fix

* <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>     <GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>

* Release

* <VersionPrefix>1.0.4.18</VersionPrefix>

* fix

* BodyParserTests

* ResponseBodyData (#216)

* ResponseBodyData

* refactor tests

* LogEntryMapperTests
This commit is contained in:
Stef Heyenrath
2018-10-25 14:08:24 +02:00
committed by GitHub
parent d9ed1bf812
commit 1af512fc72
66 changed files with 1186 additions and 823 deletions

View File

@@ -26,5 +26,30 @@ namespace WireMock.Util
/// The body (as bytearray).
/// </summary>
public byte[] BodyAsBytes { get; set; }
/// <summary>
/// Gets or sets a value indicating whether child objects to be indented according to the Newtonsoft.Json.JsonTextWriter.Indentation and Newtonsoft.Json.JsonTextWriter.IndentChar settings.
/// </summary>
public bool? BodyAsJsonIndented { get; set; }
/// <summary>
/// Gets or sets the body as a file.
/// </summary>
public string BodyAsFile { get; set; }
/// <summary>
/// Is the body as file cached?
/// </summary>
public bool? BodyAsFileIsCached { get; set; }
/// <summary>
/// The detected body type (detection based on body content).
/// </summary>
public BodyType DetectedBodyType { get; set; }
/// <summary>
/// The detected body type (detection based on Content-Type).
/// </summary>
public BodyType DetectedBodyTypeFromContentType { get; set; }
}
}

View File

@@ -4,36 +4,112 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using JetBrains.Annotations;
using MimeKit;
using Newtonsoft.Json;
using WireMock.Matchers;
using WireMock.Validation;
namespace WireMock.Util
{
internal static class BodyParser
{
private static readonly string[] JsonContentTypes =
{
"application/json",
"application/vnd.api+json"
private static readonly Encoding DefaultEncoding = Encoding.UTF8;
/*
HEAD - No defined body semantics.
GET - No defined body semantics.
PUT - Body supported.
POST - Body supported.
DELETE - No defined body semantics.
TRACE - Body not supported.
OPTIONS - Body supported but no semantics on usage (maybe in the future).
CONNECT - No defined body semantics
PATCH - Body supported.
*/
private static readonly string[] AllowedBodyParseMethods = { "PUT", "POST", "OPTIONS", "PATCH" };
private static readonly IStringMatcher[] JsonContentTypesMatchers = {
new WildcardMatcher("application/json", true),
new WildcardMatcher("application/vnd.*+json", true)
};
private static readonly string[] TextContentTypes =
private static readonly IStringMatcher[] TextContentTypeMatchers =
{
"text/",
"application/javascript", "application/typescript",
"application/xml", "application/xhtml+xml",
"application/x-www-form-urlencoded"
new WildcardMatcher("text/*", true),
new RegexMatcher("^application\\/(java|type)script$", true),
new WildcardMatcher("application/*xml", true),
new WildcardMatcher("application/x-www-form-urlencoded", true)
};
private static async Task<Tuple<string, Encoding>> ReadStringAsync(Stream stream)
public static bool ParseBodyAsIsValid([CanBeNull] string parseBodyAs)
{
using (var streamReader = new StreamReader(stream))
{
string content = await streamReader.ReadToEndAsync();
return new Tuple<string, Encoding>(content, streamReader.CurrentEncoding);
}
return Enum.TryParse(parseBodyAs, out BodyType _);
}
public static bool ShouldParseBody([CanBeNull] string method)
{
return AllowedBodyParseMethods.Contains(method, StringComparer.OrdinalIgnoreCase);
}
public static BodyType DetectBodyTypeFromContentType([CanBeNull] string contentTypeValue)
{
if (string.IsNullOrEmpty(contentTypeValue) || !ContentType.TryParse(contentTypeValue, out ContentType contentType))
{
return BodyType.Bytes;
}
if (TextContentTypeMatchers.Any(matcher => MatchScores.IsPerfect(matcher.IsMatch(contentType.MimeType))))
{
return BodyType.String;
}
if (JsonContentTypesMatchers.Any(matcher => MatchScores.IsPerfect(matcher.IsMatch(contentType.MimeType))))
{
return BodyType.Json;
}
return BodyType.Bytes;
}
public static async Task<BodyData> Parse([NotNull] Stream stream, [CanBeNull] string contentType)
{
Check.NotNull(stream, nameof(stream));
var data = new BodyData
{
BodyAsBytes = await ReadBytesAsync(stream),
DetectedBodyType = BodyType.Bytes,
DetectedBodyTypeFromContentType = DetectBodyTypeFromContentType(contentType)
};
// Try to get the body as String
try
{
data.BodyAsString = DefaultEncoding.GetString(data.BodyAsBytes);
data.Encoding = DefaultEncoding;
data.DetectedBodyType = BodyType.String;
// If string is not null or empty, try to get as Json
if (!string.IsNullOrEmpty(data.BodyAsString))
{
try
{
data.BodyAsJson = JsonConvert.DeserializeObject(data.BodyAsString, new JsonSerializerSettings { Formatting = Formatting.Indented });
data.DetectedBodyType = BodyType.Json;
}
catch
{
// JsonConvert failed, just ignore.
}
}
}
catch
{
// Reading as string failed, just ignore
}
return data;
}
private static async Task<byte[]> ReadBytesAsync(Stream stream)
{
using (var memoryStream = new MemoryStream())
@@ -42,47 +118,5 @@ namespace WireMock.Util
return memoryStream.ToArray();
}
}
public static async Task<BodyData> Parse([NotNull] Stream stream, [CanBeNull] string contentTypeHeaderValue)
{
var data = new BodyData();
if (contentTypeHeaderValue != null && TextContentTypes.Any(text => contentTypeHeaderValue.StartsWith(text, StringComparison.OrdinalIgnoreCase)))
{
try
{
var stringData = await ReadStringAsync(stream);
data.BodyAsString = stringData.Item1;
data.Encoding = stringData.Item2;
}
catch
{
// Reading as string failed, just get the ByteArray.
data.BodyAsBytes = await ReadBytesAsync(stream);
}
}
else if (contentTypeHeaderValue != null && JsonContentTypes.Any(json => contentTypeHeaderValue.StartsWith(json, StringComparison.OrdinalIgnoreCase)))
{
var stringData = await ReadStringAsync(stream);
data.BodyAsString = stringData.Item1;
data.Encoding = stringData.Item2;
try
{
data.BodyAsJson = JsonConvert.DeserializeObject(stringData.Item1, new JsonSerializerSettings { Formatting = Formatting.Indented });
}
catch
{
// JsonConvert failed, just set the Body as string.
data.BodyAsString = stringData.Item1;
}
}
else
{
data.BodyAsBytes = await ReadBytesAsync(stream);
}
return data;
}
}
}

View File

@@ -0,0 +1,33 @@
namespace WireMock.Util
{
/// <summary>
/// The BodyType
/// </summary>
public enum BodyType
{
/// <summary>
/// No body present
/// </summary>
None,
/// <summary>
/// Body is a String
/// </summary>
String,
/// <summary>
/// Body is a Json object
/// </summary>
Json,
/// <summary>
/// Body is a Byte array
/// </summary>
Bytes,
/// <summary>
/// Body is a File
/// </summary>
File
}
}

View File

@@ -8,26 +8,26 @@ namespace WireMock.Util
/// </summary>
/// <typeparam name="T">The type of elements in the collection.</typeparam>
/// <inheritdoc cref="ObservableCollection{T}" />
public class ConcurentObservableCollection<T> : ObservableCollection<T>
public class ConcurrentObservableCollection<T> : ObservableCollection<T>
{
private readonly object _lockObject = new object();
/// <summary>
/// Initializes a new instance of the <see cref="T:WireMock.Util.ConcurentObservableCollection`1" /> class.
/// Initializes a new instance of the <see cref="T:WireMock.Util.ConcurrentObservableCollection`1" /> class.
/// </summary>
public ConcurentObservableCollection() { }
public ConcurrentObservableCollection() { }
/// <summary>
/// Initializes a new instance of the <see cref="ConcurentObservableCollection{T}"/> class that contains elements copied from the specified list.
/// Initializes a new instance of the <see cref="ConcurrentObservableCollection{T}"/> class that contains elements copied from the specified list.
/// </summary>
/// <param name="list">The list from which the elements are copied.</param>
public ConcurentObservableCollection(List<T> list) : base(list) { }
public ConcurrentObservableCollection(List<T> list) : base(list) { }
/// <summary>
/// Initializes a new instance of the <see cref="ConcurentObservableCollection{T}"/> class that contains elements copied from the specified collection.
/// Initializes a new instance of the <see cref="ConcurrentObservableCollection{T}"/> class that contains elements copied from the specified collection.
/// </summary>
/// <param name="collection">The collection from which the elements are copied.</param>
public ConcurentObservableCollection(IEnumerable<T> collection) : base(collection) { }
public ConcurrentObservableCollection(IEnumerable<T> collection) : base(collection) { }
/// <inheritdoc cref="ObservableCollection{T}.ClearItems"/>
protected override void ClearItems()

View File

@@ -11,16 +11,16 @@ namespace WireMock.Util
private const int NumberOfRetries = 3;
private const int DelayOnRetry = 500;
public static string ReadAllTextWithRetryAndDelay([NotNull] IFileSystemHandler filehandler, [NotNull] string path)
public static string ReadAllTextWithRetryAndDelay([NotNull] IFileSystemHandler handler, [NotNull] string path)
{
Check.NotNull(filehandler, nameof(filehandler));
Check.NotNull(handler, nameof(handler));
Check.NotNullOrEmpty(path, nameof(path));
for (int i = 1; i <= NumberOfRetries; ++i)
{
try
{
return filehandler.ReadMappingFile(path);
return handler.ReadMappingFile(path);
}
catch
{

View File

@@ -92,7 +92,7 @@ namespace WireMock.Util
private static void ProcessItem(JToken node, string path, string propertyName, List<string> lines)
{
string castText = string.Empty;
string castText;
switch (node.Type)
{
case JTokenType.Boolean:
@@ -132,8 +132,7 @@ namespace WireMock.Util
break;
default:
throw new NotSupportedException(
$"JTokenType '{node.Type}' cannot be converted to a Dynamic Linq cast operator.");
throw new NotSupportedException($"JTokenType '{node.Type}' cannot be converted to a Dynamic Linq cast operator.");
}
if (!string.IsNullOrEmpty(propertyName))

View File

@@ -1,6 +1,7 @@
using System;
using JetBrains.Annotations;
using WireMock.Models;
using WireMock.Validation;
#if !USE_ASPNETCORE
using Microsoft.Owin;
#else
@@ -13,6 +14,8 @@ namespace WireMock.Util
{
public static UrlDetails Parse([NotNull] Uri uri, PathString pathBase)
{
Check.NotNull(uri, nameof(uri));
if (!pathBase.HasValue)
{
return new UrlDetails(uri, uri);
@@ -26,7 +29,7 @@ namespace WireMock.Util
private static string RemoveFirst(string text, string search)
{
int pos = text.IndexOf(search);
int pos = text.IndexOf(search, StringComparison.Ordinal);
if (pos < 0)
{
return text;