diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 330c692..c89c9bc 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -38,6 +38,7 @@ + @@ -53,5 +54,5 @@ false - + diff --git a/src/FluentRest/FluentDispatcher.cs b/src/FluentRest/FluentDispatcher.cs index b290b82..337084b 100644 --- a/src/FluentRest/FluentDispatcher.cs +++ b/src/FluentRest/FluentDispatcher.cs @@ -52,7 +52,7 @@ private static async Task PrepareRequest(HttpRequestMessage if (requestMessage.Headers.UserAgent.Count == 0) { // user-agent header required - var headerValue = new ProductInfoHeaderValue("FluentRest", "5.0.0.0"); + var headerValue = new ProductInfoHeaderValue("FluentRest", ThisAssembly.FileVersion); requestMessage.Headers.UserAgent.Add(headerValue); } diff --git a/src/FluentRest/StringBuilderCache.cs b/src/FluentRest/StringBuilderCache.cs new file mode 100644 index 0000000..27c2724 --- /dev/null +++ b/src/FluentRest/StringBuilderCache.cs @@ -0,0 +1,54 @@ +using System.Text; + +namespace FluentRest; + +/// Provide a cached reusable instance of StringBuilder per thread. +internal static class StringBuilderCache +{ + // The value 360 was chosen in discussion with performance experts as a compromise between using + // as little memory per thread as possible and still covering a large part of short-lived + // StringBuilder creations on the startup path of VS designers. + internal const int MaxBuilderSize = 360; + private const int DefaultCapacity = 16; // == StringBuilder.DefaultCapacity + + [ThreadStatic] + private static StringBuilder t_cachedInstance; + + /// Get a StringBuilder for the specified capacity. + /// If a StringBuilder of an appropriate size is cached, it will be returned and the cache emptied. + public static StringBuilder Acquire(int capacity = DefaultCapacity) + { + if (capacity > MaxBuilderSize) + return new StringBuilder(capacity); + + var sb = t_cachedInstance; + if (sb == null) + return new StringBuilder(capacity); + + // Avoid StringBuilder block fragmentation by getting a new StringBuilder + // when the requested size is larger than the current capacity + if (capacity > sb.Capacity) + return new StringBuilder(capacity); + + t_cachedInstance = null; + sb.Clear(); + + return sb; + + } + + /// Place the specified builder in the cache if it is not too big. + public static void Release(StringBuilder sb) + { + if (sb.Capacity <= MaxBuilderSize) + t_cachedInstance = sb; + } + + /// Release StringBuilder to the cache, and return the resulting string. + public static string ToString(StringBuilder sb) + { + string result = sb.ToString(); + Release(sb); + return result; + } +} diff --git a/src/FluentRest/UrlBuilder.cs b/src/FluentRest/UrlBuilder.cs index 793f257..4279366 100644 --- a/src/FluentRest/UrlBuilder.cs +++ b/src/FluentRest/UrlBuilder.cs @@ -544,7 +544,7 @@ public Uri ToUri() /// public override string ToString() { - var builder = new StringBuilder(); + var builder = StringBuilderCache.Acquire(150); if (!string.IsNullOrWhiteSpace(_scheme)) builder.Append(_scheme).Append(_schemeDelimiter); @@ -553,16 +553,16 @@ public override string ToString() { builder.Append(_username); if (!string.IsNullOrWhiteSpace(_password)) - builder.Append(":").Append(_password); + builder.Append(':').Append(_password); - builder.Append("@"); + builder.Append('@'); } if (!string.IsNullOrWhiteSpace(_host)) { builder.Append(_host); if (_port.HasValue && !IsStandardPort()) - builder.Append(":").Append(_port); + builder.Append(':').Append(_port); } WritePath(builder); @@ -571,7 +571,7 @@ public override string ToString() if (!string.IsNullOrWhiteSpace(_fragment)) builder.Append(_fragment); - return builder.ToString(); + return StringBuilderCache.ToString(builder); } @@ -586,7 +586,7 @@ private bool IsStandardPort() private void WritePath(StringBuilder builder) { - builder.Append("/"); + builder.Append('/'); if (Path == null || Path.Count == 0) return; @@ -594,12 +594,11 @@ private void WritePath(StringBuilder builder) foreach (var p in Path) { if (builder.Length > start) - builder.Append("/"); + builder.Append('/'); - var s = p.Replace(" ", "+"); - s = Uri.EscapeUriString(s); + var v = Uri.EscapeDataString(p); - builder.Append(s); + builder.Append(v); } } @@ -608,29 +607,27 @@ private void WriteQueryString(StringBuilder builder) if (Query == null || Query.Count == 0) return; - builder.Append("?"); + builder.Append('?'); int start = builder.Length; foreach (var pair in Query) { var key = pair.Key; key = Uri.EscapeDataString(key); - key = key.Replace("%20", "+"); var values = pair.Value.ToList(); foreach (var value in values) { if (builder.Length > start) - builder.Append("&"); + builder.Append('&'); var v = value; v = Uri.EscapeDataString(v); - v = v.Replace("%20", "+"); builder .Append(key) - .Append("=") + .Append('=') .Append(v); } } @@ -763,6 +760,3 @@ private void ParsePath(string s) } } - - - diff --git a/test/FluentRest.Tests/UrlBuilderTests.cs b/test/FluentRest.Tests/UrlBuilderTests.cs index db769c3..6e767c1 100644 --- a/test/FluentRest.Tests/UrlBuilderTests.cs +++ b/test/FluentRest.Tests/UrlBuilderTests.cs @@ -21,10 +21,10 @@ public UrlBuilderTests(ITestOutputHelper output) [Theory] [InlineData("http://foo/bar/baz", "date", "today", "http://foo/bar/baz?date=today")] - [InlineData("http://foo/bar/baz", "date", "sunday afternoon", "http://foo/bar/baz?date=sunday+afternoon")] + [InlineData("http://foo/bar/baz", "date", "sunday afternoon", "http://foo/bar/baz?date=sunday%20afternoon")] [InlineData("http://foo/bar/baz?date=today", "key1", "value1", "http://foo/bar/baz?date=today&key1=value1")] - [InlineData("http://foo/bar/baz?date=today", "key1", "value 1&", "http://foo/bar/baz?date=today&key1=value+1%26")] - [InlineData("foo/bar/baz?date=today", "key1", "value 1&", "http://foo/bar/baz?date=today&key1=value+1%26")] + [InlineData("http://foo/bar/baz?date=today", "key1", "value 1&", "http://foo/bar/baz?date=today&key1=value%201%26")] + [InlineData("foo/bar/baz?date=today", "key1", "value 1&", "http://foo/bar/baz?date=today&key1=value%201%26")] public void AppendQuery(string url, string key, string value, string expected) { var builder = new UrlBuilder(url);