Prototypeing a fast header dictionary
Conflicts: src/Microsoft.AspNet.Server.Kestrel/project.json
This commit is contained in:
parent
83b2c95385
commit
963f086eb0
|
|
@ -26,6 +26,7 @@ EndProject
|
|||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{8A3D00B8-1CCF-4BE6-A060-11104CE2D9CE}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "LargeResponseApp", "samples\LargeResponseApp\LargeResponseApp.xproj", "{B35D4D31-E74C-4646-8A11-7A7A40F0021E}"
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Server.Kestrel.GeneratedCode", "src\Microsoft.AspNet.Server.Kestrel.GeneratedCode\Microsoft.AspNet.Server.Kestrel.GeneratedCode.xproj", "{BD2D4D29-1BD9-40D0-BB31-337D5416B63C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
|
@ -53,6 +54,10 @@ Global
|
|||
{B35D4D31-E74C-4646-8A11-7A7A40F0021E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B35D4D31-E74C-4646-8A11-7A7A40F0021E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B35D4D31-E74C-4646-8A11-7A7A40F0021E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BD2D4D29-1BD9-40D0-BB31-337D5416B63C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BD2D4D29-1BD9-40D0-BB31-337D5416B63C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BD2D4D29-1BD9-40D0-BB31-337D5416B63C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BD2D4D29-1BD9-40D0-BB31-337D5416B63C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -63,5 +68,6 @@ Global
|
|||
{2C3CB3DC-EEBF-4F52-9E1C-4F2F972E76C3} = {8A3D00B8-1CCF-4BE6-A060-11104CE2D9CE}
|
||||
{30B7617E-58EF-4382-B3EA-5B2E718CF1A6} = {2D5D5227-4DBD-499A-96B1-76A36B03B750}
|
||||
{B35D4D31-E74C-4646-8A11-7A7A40F0021E} = {8A3D00B8-1CCF-4BE6-A060-11104CE2D9CE}
|
||||
{BD2D4D29-1BD9-40D0-BB31-337D5416B63C} = {2D5D5227-4DBD-499A-96B1-76A36B03B750}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
|
|
@ -0,0 +1,305 @@
|
|||
using Microsoft.Framework.Runtime.Roslyn;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNet.Server.Kestrel.GeneratedCode
|
||||
{
|
||||
// This project can output the Class library as a NuGet Package.
|
||||
// To enable this option, right-click on the project and select the Properties menu item. In the Build tab select "Produce outputs on build".
|
||||
public class KnownHeaders : ICompileModule
|
||||
{
|
||||
string Each<T>(IEnumerable<T> values, Func<T, string> formatter)
|
||||
{
|
||||
return values.Select(formatter).Aggregate((a, b) => a + b + "\r\n");
|
||||
}
|
||||
|
||||
class KnownHeader
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public int Index { get; set; }
|
||||
public string Identifier => Name.Replace("-", "");
|
||||
public string TestBit() => $"((_bits & ({1L << Index}L)) != 0)";
|
||||
public string SetBit() => $"_bits |= {1L << Index}L";
|
||||
public string ClearBit() => $"_bits &= ~{(1L << Index)}L";
|
||||
}
|
||||
|
||||
public virtual void BeforeCompile(BeforeCompileContext context)
|
||||
{
|
||||
Console.WriteLine("I like pie");
|
||||
|
||||
var commonHeaders = new[]
|
||||
{
|
||||
"Cache-Control",
|
||||
"Connection",
|
||||
"Date",
|
||||
"Keep-Alive",
|
||||
"Pragma",
|
||||
"Trailer",
|
||||
"Transfer-Encoding",
|
||||
"Upgrade",
|
||||
"Via",
|
||||
"Warning",
|
||||
"Allow",
|
||||
"Content-Length",
|
||||
"Content-Type",
|
||||
"Content-Encoding",
|
||||
"Content-Language",
|
||||
"Content-Location",
|
||||
"Content-MD5",
|
||||
"Content-Range",
|
||||
"Expires",
|
||||
"Last-Modified"
|
||||
};
|
||||
var requestHeaders = commonHeaders.Concat(new[]
|
||||
{
|
||||
"Accept",
|
||||
"Accept-Charset",
|
||||
"Accept-Encoding",
|
||||
"Accept-Language",
|
||||
"Authorization",
|
||||
"Cookie",
|
||||
"Expect",
|
||||
"From",
|
||||
"Host",
|
||||
"If-Match",
|
||||
"If-Modified-Since",
|
||||
"If-None-Match",
|
||||
"If-Range",
|
||||
"If-Unmodified-Since",
|
||||
"Max-Forwards",
|
||||
"Proxy-Authorization",
|
||||
"Referer",
|
||||
"Range",
|
||||
"TE",
|
||||
"Translage",
|
||||
"User-Agent",
|
||||
}).Select((header, index) => new KnownHeader
|
||||
{
|
||||
Name = header,
|
||||
Index = index
|
||||
});
|
||||
|
||||
var responseHeaders = commonHeaders.Concat(new[]
|
||||
{
|
||||
"Accept-Ranges",
|
||||
"Age",
|
||||
"ETag",
|
||||
"Location",
|
||||
"Proxy-Autheticate",
|
||||
"Retry-After",
|
||||
"Server",
|
||||
"Set-Cookie",
|
||||
"Vary",
|
||||
"WWW-Authenticate",
|
||||
}).Select((header, index) => new KnownHeader
|
||||
{
|
||||
Name = header,
|
||||
Index = index
|
||||
});
|
||||
|
||||
var loops = new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
Headers = requestHeaders,
|
||||
HeadersByLength = requestHeaders.GroupBy(x => x.Name.Length),
|
||||
ClassName = "FrameRequestHeaders"
|
||||
},
|
||||
new
|
||||
{
|
||||
Headers = responseHeaders,
|
||||
HeadersByLength = responseHeaders.GroupBy(x => x.Name.Length),
|
||||
ClassName = "FrameResponseHeaders"
|
||||
}
|
||||
};
|
||||
|
||||
var syntaxTree = Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree.ParseText($@"
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||
{{{Each(loops, loop => $@"
|
||||
public partial class {loop.ClassName}
|
||||
{{
|
||||
long _bits = 0;
|
||||
{Each(loop.Headers, header => "string[] _" + header.Identifier + ";")}
|
||||
|
||||
protected override int GetCountFast()
|
||||
{{
|
||||
var count = Unknown.Count;
|
||||
{Each(loop.Headers, header => $@"
|
||||
if ({header.TestBit()})
|
||||
{{
|
||||
++count;
|
||||
}}
|
||||
")}
|
||||
return count;
|
||||
}}
|
||||
|
||||
protected override string[] GetValueFast(string key)
|
||||
{{
|
||||
switch(key.Length)
|
||||
{{{Each(loop.HeadersByLength, byLength => $@"
|
||||
case {byLength.Key}:
|
||||
{{{Each(byLength, header => $@"
|
||||
if (0 == StringComparer.OrdinalIgnoreCase.Compare(key, ""{header.Name}""))
|
||||
{{
|
||||
if ({header.TestBit()})
|
||||
{{
|
||||
return _{header.Identifier};
|
||||
}}
|
||||
else
|
||||
{{
|
||||
throw new System.Collections.Generic.KeyNotFoundException();
|
||||
}}
|
||||
}}
|
||||
")}}}
|
||||
break;
|
||||
")}}}
|
||||
return Unknown[key];
|
||||
}}
|
||||
|
||||
protected override bool TryGetValueFast(string key, out string[] value)
|
||||
{{
|
||||
switch(key.Length)
|
||||
{{{Each(loop.HeadersByLength, byLength => $@"
|
||||
case {byLength.Key}:
|
||||
{{{Each(byLength, header => $@"
|
||||
if (0 == StringComparer.OrdinalIgnoreCase.Compare(key, ""{header.Name}""))
|
||||
{{
|
||||
if ({header.TestBit()})
|
||||
{{
|
||||
value = _{header.Identifier};
|
||||
return true;
|
||||
}}
|
||||
else
|
||||
{{
|
||||
value = null;
|
||||
return false;
|
||||
}}
|
||||
}}
|
||||
")}}}
|
||||
break;
|
||||
")}}}
|
||||
return Unknown.TryGetValue(key, out value);
|
||||
}}
|
||||
|
||||
protected override void SetValueFast(string key, string[] value)
|
||||
{{
|
||||
switch(key.Length)
|
||||
{{{Each(loop.HeadersByLength, byLength => $@"
|
||||
case {byLength.Key}:
|
||||
{{{Each(byLength, header => $@"
|
||||
if (0 == StringComparer.OrdinalIgnoreCase.Compare(key, ""{header.Name}""))
|
||||
{{
|
||||
{header.SetBit()};
|
||||
_{header.Identifier} = value;
|
||||
return;
|
||||
}}
|
||||
")}}}
|
||||
break;
|
||||
")}}}
|
||||
Unknown[key] = value;
|
||||
}}
|
||||
|
||||
protected override void AddValueFast(string key, string[] value)
|
||||
{{
|
||||
switch(key.Length)
|
||||
{{{Each(loop.HeadersByLength, byLength => $@"
|
||||
case {byLength.Key}:
|
||||
{{{Each(byLength, header => $@"
|
||||
if (0 == StringComparer.OrdinalIgnoreCase.Compare(key, ""{header.Name}""))
|
||||
{{
|
||||
if ({header.TestBit()})
|
||||
{{
|
||||
throw new ArgumentException(""An item with the same key has already been added."");
|
||||
}}
|
||||
{header.SetBit()};
|
||||
_{header.Identifier} = value;
|
||||
return;
|
||||
}}
|
||||
")}}}
|
||||
break;
|
||||
")}}}
|
||||
Unknown.Add(key, value);
|
||||
}}
|
||||
|
||||
protected override bool RemoveFast(string key)
|
||||
{{
|
||||
switch(key.Length)
|
||||
{{{Each(loop.HeadersByLength, byLength => $@"
|
||||
case {byLength.Key}:
|
||||
{{{Each(byLength, header => $@"
|
||||
if (0 == StringComparer.OrdinalIgnoreCase.Compare(key, ""{header.Name}""))
|
||||
{{
|
||||
if ({header.TestBit()})
|
||||
{{
|
||||
{header.ClearBit()};
|
||||
return true;
|
||||
}}
|
||||
else
|
||||
{{
|
||||
return false;
|
||||
}}
|
||||
}}
|
||||
")}}}
|
||||
break;
|
||||
")}}}
|
||||
return Unknown.Remove(key);
|
||||
}}
|
||||
|
||||
protected override void ClearFast()
|
||||
{{
|
||||
_bits = 0;
|
||||
Unknown.Clear();
|
||||
}}
|
||||
|
||||
protected override void CopyToFast(KeyValuePair<string, string[]>[] array, int arrayIndex)
|
||||
{{
|
||||
if (arrayIndex < 0)
|
||||
{{
|
||||
throw new ArgumentException();
|
||||
}}
|
||||
|
||||
{Each(loop.Headers, header => $@"
|
||||
if ({header.TestBit()})
|
||||
{{
|
||||
if (arrayIndex == array.Length)
|
||||
{{
|
||||
throw new ArgumentException();
|
||||
}}
|
||||
|
||||
array[arrayIndex] = new KeyValuePair<string, string[]>(""{header.Name}"", _{header.Identifier});
|
||||
++arrayIndex;
|
||||
}}
|
||||
")}
|
||||
((ICollection<KeyValuePair<string, string[]>>)Unknown).CopyTo(array, arrayIndex);
|
||||
}}
|
||||
|
||||
protected override IEnumerable<KeyValuePair<string, string[]>> EnumerateFast()
|
||||
{{
|
||||
{Each(loop.Headers, header => $@"
|
||||
if ({header.TestBit()})
|
||||
{{
|
||||
yield return new KeyValuePair<string, string[]>(""{header.Name}"", _{header.Identifier});
|
||||
}}
|
||||
")}
|
||||
foreach(var kv in Unknown)
|
||||
{{
|
||||
yield return kv;
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
")}}}
|
||||
");
|
||||
|
||||
context.Compilation = context.Compilation.AddSyntaxTrees(syntaxTree);
|
||||
}
|
||||
|
||||
public virtual void AfterCompile(AfterCompileContext context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>bd2d4d29-1bd9-40d0-bb31-337d5416b63c</ProjectGuid>
|
||||
<RootNamespace>Microsoft.AspNet.Server.Kestrel.GeneratedCode</RootNamespace>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"version": "1.0.0-*",
|
||||
"description": "",
|
||||
"authors": [ "" ],
|
||||
"tags": [ "" ],
|
||||
"projectUrl": "",
|
||||
"licenseUrl": "",
|
||||
|
||||
"dependencies": {
|
||||
"Microsoft.Framework.Runtime.Roslyn": "1.0.0-beta6-*"
|
||||
},
|
||||
|
||||
"frameworks": {
|
||||
"dnx451": {
|
||||
"frameworkAssemblies": {
|
||||
"System.Runtime": "4.0.10.0"
|
||||
}
|
||||
},
|
||||
"dnxcore50": {
|
||||
"dependencies": {
|
||||
"System.Collections": "4.0.10-beta-*",
|
||||
"System.Linq": "4.0.0-beta-*",
|
||||
"System.Threading": "4.0.10-beta-*",
|
||||
"Microsoft.CSharp": "4.0.0-beta-*"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNet.Server.Kestrel.Http
|
||||
{
|
||||
public partial class FrameRequestHeaders : FrameHeaders
|
||||
{
|
||||
}
|
||||
|
||||
public partial class FrameResponseHeaders : FrameHeaders
|
||||
{
|
||||
}
|
||||
|
||||
public abstract class FrameHeaders : IDictionary<string, string[]>
|
||||
{
|
||||
protected Dictionary<string, string[]> Unknown = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
protected virtual int GetCountFast()
|
||||
{ throw new NotImplementedException(); }
|
||||
|
||||
protected virtual string[] GetValueFast(string key)
|
||||
{ throw new NotImplementedException(); }
|
||||
|
||||
protected virtual bool TryGetValueFast(string key, out string[] value)
|
||||
{ throw new NotImplementedException(); }
|
||||
|
||||
protected virtual void SetValueFast(string key, string[] value)
|
||||
{ throw new NotImplementedException(); }
|
||||
|
||||
protected virtual void AddValueFast(string key, string[] value)
|
||||
{ throw new NotImplementedException(); }
|
||||
|
||||
protected virtual bool RemoveFast(string key)
|
||||
{ throw new NotImplementedException(); }
|
||||
|
||||
protected virtual void ClearFast()
|
||||
{ throw new NotImplementedException(); }
|
||||
|
||||
protected virtual void CopyToFast(KeyValuePair<string, string[]>[] array, int arrayIndex)
|
||||
{ throw new NotImplementedException(); }
|
||||
|
||||
protected virtual IEnumerable<KeyValuePair<string, string[]>> EnumerateFast()
|
||||
{ throw new NotImplementedException(); }
|
||||
|
||||
|
||||
string[] IDictionary<string, string[]>.this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetValueFast(key);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
SetValueFast(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
int ICollection<KeyValuePair<string, string[]>>.Count => GetCountFast();
|
||||
|
||||
bool ICollection<KeyValuePair<string, string[]>>.IsReadOnly => false;
|
||||
|
||||
ICollection<string> IDictionary<string, string[]>.Keys => EnumerateFast().Select(x => x.Key).ToList();
|
||||
|
||||
ICollection<string[]> IDictionary<string, string[]>.Values => EnumerateFast().Select(x => x.Value).ToList();
|
||||
|
||||
void ICollection<KeyValuePair<string, string[]>>.Add(KeyValuePair<string, string[]> item)
|
||||
{
|
||||
AddValueFast(item.Key, item.Value);
|
||||
}
|
||||
|
||||
void IDictionary<string, string[]>.Add(string key, string[] value)
|
||||
{
|
||||
AddValueFast(key, value);
|
||||
}
|
||||
|
||||
void ICollection<KeyValuePair<string, string[]>>.Clear()
|
||||
{
|
||||
ClearFast();
|
||||
}
|
||||
|
||||
bool ICollection<KeyValuePair<string, string[]>>.Contains(KeyValuePair<string, string[]> item)
|
||||
{
|
||||
string[] value;
|
||||
return
|
||||
TryGetValueFast(item.Key, out value) &&
|
||||
object.Equals(value, item.Value);
|
||||
}
|
||||
|
||||
bool IDictionary<string, string[]>.ContainsKey(string key)
|
||||
{
|
||||
string[] value;
|
||||
return TryGetValueFast(key, out value);
|
||||
}
|
||||
|
||||
void ICollection<KeyValuePair<string, string[]>>.CopyTo(KeyValuePair<string, string[]>[] array, int arrayIndex)
|
||||
{
|
||||
CopyToFast(array, arrayIndex);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return EnumerateFast().GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator<KeyValuePair<string, string[]>> IEnumerable<KeyValuePair<string, string[]>>.GetEnumerator()
|
||||
{
|
||||
return EnumerateFast().GetEnumerator();
|
||||
}
|
||||
|
||||
bool ICollection<KeyValuePair<string, string[]>>.Remove(KeyValuePair<string, string[]> item)
|
||||
{
|
||||
string[] value;
|
||||
return
|
||||
TryGetValueFast(item.Key, out value) &&
|
||||
object.Equals(value, item.Value) &&
|
||||
RemoveFast(item.Key);
|
||||
}
|
||||
|
||||
bool IDictionary<string, string[]>.Remove(string key)
|
||||
{
|
||||
return RemoveFast(key);
|
||||
}
|
||||
|
||||
bool IDictionary<string, string[]>.TryGetValue(string key, out string[] value)
|
||||
{
|
||||
return TryGetValueFast(key, out value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNet.Server.Kestrel
|
||||
{
|
||||
public class KnownHeaders : Microsoft.AspNet.Server.Kestrel.GeneratedCode.KnownHeaders
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,8 @@
|
|||
"url": "git://github.com/aspnet/kestrelhttpserver"
|
||||
},
|
||||
"dependencies": {
|
||||
"Microsoft.Dnx.Runtime.Abstractions": "1.0.0-beta7-*"
|
||||
"Microsoft.Dnx.Runtime.Abstractions": "1.0.0-beta7-*",
|
||||
"Microsoft.AspNet.Server.Kestrel.GeneratedCode": { "version": "1.0.0-*", "type": "build" }
|
||||
},
|
||||
"frameworks": {
|
||||
"dnx451": { },
|
||||
|
|
|
|||
|
|
@ -0,0 +1,238 @@
|
|||
using Microsoft.AspNet.Server.Kestrel.Http;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Server.KestrelTests
|
||||
{
|
||||
public class FrameRequestHeadersTests
|
||||
{
|
||||
[Fact]
|
||||
public void InitialDictionaryIsEmpty()
|
||||
{
|
||||
IDictionary<string, string[]> headers = new FrameRequestHeaders();
|
||||
|
||||
Assert.Equal(0, headers.Count);
|
||||
Assert.False(headers.IsReadOnly);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SettingUnknownHeadersWorks()
|
||||
{
|
||||
IDictionary<string, string[]> headers = new FrameRequestHeaders();
|
||||
|
||||
headers["custom"] = new[] { "value" };
|
||||
|
||||
Assert.NotNull(headers["custom"]);
|
||||
Assert.Equal(1, headers["custom"].Length);
|
||||
Assert.Equal("value", headers["custom"][0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SettingKnownHeadersWorks()
|
||||
{
|
||||
IDictionary<string, string[]> headers = new FrameRequestHeaders();
|
||||
|
||||
headers["host"] = new[] { "value" };
|
||||
|
||||
Assert.NotNull(headers["host"]);
|
||||
Assert.Equal(1, headers["host"].Length);
|
||||
Assert.Equal("value", headers["host"][0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KnownAndCustomHeaderCountAddedTogether()
|
||||
{
|
||||
IDictionary<string, string[]> headers = new FrameRequestHeaders();
|
||||
|
||||
headers["host"] = new[] { "value" };
|
||||
headers["custom"] = new[] { "value" };
|
||||
|
||||
Assert.Equal(2, headers.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGetValueWorksForKnownAndUnknownHeaders()
|
||||
{
|
||||
IDictionary<string, string[]> headers = new FrameRequestHeaders();
|
||||
|
||||
string[] value;
|
||||
Assert.False(headers.TryGetValue("host", out value));
|
||||
Assert.False(headers.TryGetValue("custom", out value));
|
||||
|
||||
headers["host"] = new[] { "value" };
|
||||
Assert.True(headers.TryGetValue("host", out value));
|
||||
Assert.False(headers.TryGetValue("custom", out value));
|
||||
|
||||
headers["custom"] = new[] { "value" };
|
||||
Assert.True(headers.TryGetValue("host", out value));
|
||||
Assert.True(headers.TryGetValue("custom", out value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SameExceptionThrownForMissingKey()
|
||||
{
|
||||
IDictionary<string, string[]> headers = new FrameRequestHeaders();
|
||||
|
||||
Assert.Throws<KeyNotFoundException>(() => headers["custom"]);
|
||||
Assert.Throws<KeyNotFoundException>(() => headers["host"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EntriesCanBeEnumerated()
|
||||
{
|
||||
IDictionary<string, string[]> headers = new FrameRequestHeaders();
|
||||
var v1 = new[] { "localhost" };
|
||||
var v2 = new[] { "value" };
|
||||
headers["host"] = v1;
|
||||
headers["custom"] = v2;
|
||||
|
||||
Assert.Equal(
|
||||
new[] {
|
||||
new KeyValuePair<string, string[]>("Host", v1),
|
||||
new KeyValuePair<string, string[]>("custom", v2),
|
||||
},
|
||||
headers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeysAndValuesCanBeEnumerated()
|
||||
{
|
||||
IDictionary<string, string[]> headers = new FrameRequestHeaders();
|
||||
var v1 = new[] { "localhost" };
|
||||
var v2 = new[] { "value" };
|
||||
headers["host"] = v1;
|
||||
headers["custom"] = v2;
|
||||
|
||||
Assert.Equal<string>(
|
||||
new[] { "Host", "custom" },
|
||||
headers.Keys);
|
||||
|
||||
Assert.Equal<string[]>(
|
||||
new[] { v1, v2 },
|
||||
headers.Values);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void ContainsAndContainsKeyWork()
|
||||
{
|
||||
IDictionary<string, string[]> headers = new FrameRequestHeaders();
|
||||
var kv1 = new KeyValuePair<string, string[]>("host", new[] { "localhost" });
|
||||
var kv2 = new KeyValuePair<string, string[]>("custom", new[] { "value" });
|
||||
var kv1b = new KeyValuePair<string, string[]>("host", new[] { "localhost" });
|
||||
var kv2b = new KeyValuePair<string, string[]>("custom", new[] { "value" });
|
||||
|
||||
Assert.False(headers.ContainsKey("host"));
|
||||
Assert.False(headers.ContainsKey("custom"));
|
||||
Assert.False(headers.Contains(kv1));
|
||||
Assert.False(headers.Contains(kv2));
|
||||
|
||||
headers["host"] = kv1.Value;
|
||||
Assert.True(headers.ContainsKey("host"));
|
||||
Assert.False(headers.ContainsKey("custom"));
|
||||
Assert.True(headers.Contains(kv1));
|
||||
Assert.False(headers.Contains(kv2));
|
||||
Assert.False(headers.Contains(kv1b));
|
||||
Assert.False(headers.Contains(kv2b));
|
||||
|
||||
headers["custom"] = kv2.Value;
|
||||
Assert.True(headers.ContainsKey("host"));
|
||||
Assert.True(headers.ContainsKey("custom"));
|
||||
Assert.True(headers.Contains(kv1));
|
||||
Assert.True(headers.Contains(kv2));
|
||||
Assert.False(headers.Contains(kv1b));
|
||||
Assert.False(headers.Contains(kv2b));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddWorksLikeSetAndThrowsIfKeyExists()
|
||||
{
|
||||
IDictionary<string, string[]> headers = new FrameRequestHeaders();
|
||||
|
||||
string[] value;
|
||||
Assert.False(headers.TryGetValue("host", out value));
|
||||
Assert.False(headers.TryGetValue("custom", out value));
|
||||
|
||||
headers.Add("host", new[] { "localhost" });
|
||||
headers.Add("custom", new[] { "value" });
|
||||
Assert.True(headers.TryGetValue("host", out value));
|
||||
Assert.True(headers.TryGetValue("custom", out value));
|
||||
|
||||
Assert.Throws<ArgumentException>(() => headers.Add("host", new[] { "localhost" }));
|
||||
Assert.Throws<ArgumentException>(() => headers.Add("custom", new[] { "value" }));
|
||||
Assert.True(headers.TryGetValue("host", out value));
|
||||
Assert.True(headers.TryGetValue("custom", out value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClearRemovesAllHeaders()
|
||||
{
|
||||
IDictionary<string, string[]> headers = new FrameRequestHeaders();
|
||||
headers.Add("host", new[] { "localhost" });
|
||||
headers.Add("custom", new[] { "value" });
|
||||
|
||||
string[] value;
|
||||
Assert.Equal(2, headers.Count);
|
||||
Assert.True(headers.TryGetValue("host", out value));
|
||||
Assert.True(headers.TryGetValue("custom", out value));
|
||||
|
||||
headers.Clear();
|
||||
|
||||
Assert.Equal(0, headers.Count);
|
||||
Assert.False(headers.TryGetValue("host", out value));
|
||||
Assert.False(headers.TryGetValue("custom", out value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveTakesHeadersOutOfDictionary()
|
||||
{
|
||||
IDictionary<string, string[]> headers = new FrameRequestHeaders();
|
||||
headers.Add("host", new[] { "localhost" });
|
||||
headers.Add("custom", new[] { "value" });
|
||||
|
||||
string[] value;
|
||||
Assert.Equal(2, headers.Count);
|
||||
Assert.True(headers.TryGetValue("host", out value));
|
||||
Assert.True(headers.TryGetValue("custom", out value));
|
||||
|
||||
Assert.True(headers.Remove("host"));
|
||||
Assert.False(headers.Remove("host"));
|
||||
|
||||
Assert.Equal(1, headers.Count);
|
||||
Assert.False(headers.TryGetValue("host", out value));
|
||||
Assert.True(headers.TryGetValue("custom", out value));
|
||||
|
||||
Assert.True(headers.Remove("custom"));
|
||||
Assert.False(headers.Remove("custom"));
|
||||
|
||||
Assert.Equal(0, headers.Count);
|
||||
Assert.False(headers.TryGetValue("host", out value));
|
||||
Assert.False(headers.TryGetValue("custom", out value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CopyToMovesDataIntoArray()
|
||||
{
|
||||
IDictionary<string, string[]> headers = new FrameRequestHeaders();
|
||||
headers.Add("host", new[] { "localhost" });
|
||||
headers.Add("custom", new[] { "value" });
|
||||
|
||||
var entries = new KeyValuePair<string, string[]>[4];
|
||||
headers.CopyTo(entries, 1);
|
||||
|
||||
Assert.Null(entries[0].Key);
|
||||
Assert.Null(entries[0].Value);
|
||||
|
||||
Assert.Equal("Host", entries[1].Key);
|
||||
Assert.NotNull(entries[1].Value);
|
||||
|
||||
Assert.Equal("custom", entries[2].Key);
|
||||
Assert.NotNull(entries[2].Value);
|
||||
|
||||
Assert.Null(entries[3].Key);
|
||||
Assert.Null(entries[3].Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue