React to string[] -> StringValues changes.

This commit is contained in:
Chris R 2015-08-28 12:43:12 -07:00
parent 42343bedee
commit 1f596abfe5
15 changed files with 331 additions and 364 deletions

View File

@ -26,6 +26,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Http.Features; using Microsoft.AspNet.Http.Features;
using Microsoft.AspNet.Http.Features.Authentication; using Microsoft.AspNet.Http.Features.Authentication;
using Microsoft.Framework.Primitives;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
using Microsoft.Net.Http.Server; using Microsoft.Net.Http.Server;
using Microsoft.Net.WebSockets; using Microsoft.Net.WebSockets;
@ -53,7 +54,7 @@ namespace Microsoft.AspNet.Server.WebListener
private bool _enableResponseCaching; private bool _enableResponseCaching;
private Stream _requestBody; private Stream _requestBody;
private IDictionary<string, string[]> _requestHeaders; private IDictionary<string, StringValues> _requestHeaders;
private string _scheme; private string _scheme;
private string _httpMethod; private string _httpMethod;
private string _httpProtocolVersion; private string _httpProtocolVersion;
@ -71,7 +72,7 @@ namespace Microsoft.AspNet.Server.WebListener
private IAuthenticationHandler _authHandler; private IAuthenticationHandler _authHandler;
private CancellationToken? _disconnectToken; private CancellationToken? _disconnectToken;
private Stream _responseStream; private Stream _responseStream;
private IDictionary<string, string[]> _responseHeaders; private IDictionary<string, StringValues> _responseHeaders;
internal FeatureContext(RequestContext requestContext, bool enableResponseCaching) internal FeatureContext(RequestContext requestContext, bool enableResponseCaching)
{ {
@ -115,7 +116,7 @@ namespace Microsoft.AspNet.Server.WebListener
set { _requestBody = value; } set { _requestBody = value; }
} }
IDictionary<string, string[]> IHttpRequestFeature.Headers IDictionary<string, StringValues> IHttpRequestFeature.Headers
{ {
get get
{ {
@ -348,7 +349,7 @@ namespace Microsoft.AspNet.Server.WebListener
set { _responseStream = value; } set { _responseStream = value; }
} }
IDictionary<string, string[]> IHttpResponseFeature.Headers IDictionary<string, StringValues> IHttpResponseFeature.Headers
{ {
get get
{ {

View File

@ -27,6 +27,7 @@ using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security.Claims; using System.Security.Claims;
using System.Security.Principal; using System.Security.Principal;
using Microsoft.Framework.Primitives;
namespace Microsoft.Net.Http.Server namespace Microsoft.Net.Http.Server
{ {
@ -147,7 +148,8 @@ namespace Microsoft.Net.Http.Server
if (challenges.Count > 0) if (challenges.Count > 0)
{ {
context.Response.Headers.AppendValues(HttpKnownHeaderNames.WWWAuthenticate, challenges.ToArray()); context.Response.Headers[HttpKnownHeaderNames.WWWAuthenticate]
= StringValues.Concat(context.Response.Headers[HttpKnownHeaderNames.WWWAuthenticate], challenges.ToArray());
} }
} }

View File

@ -25,6 +25,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Microsoft.Framework.Primitives;
namespace Microsoft.Net.Http.Server namespace Microsoft.Net.Http.Server
{ {
@ -1048,7 +1049,7 @@ namespace Microsoft.Net.Http.Server
// Server API // Server API
internal static void GetUnknownHeaders(IDictionary<string, string[]> unknownHeaders, byte[] memoryBlob, IntPtr originalAddress) internal static void GetUnknownHeaders(IDictionary<string, StringValues> unknownHeaders, byte[] memoryBlob, IntPtr originalAddress)
{ {
// Return value. // Return value.
fixed (byte* pMemoryBlob = memoryBlob) fixed (byte* pMemoryBlob = memoryBlob)
@ -1079,7 +1080,7 @@ namespace Microsoft.Net.Http.Server
} }
// Note that Http.Sys currently collapses all headers of the same name to a single coma seperated string, // Note that Http.Sys currently collapses all headers of the same name to a single coma seperated string,
// so we can just call Set. // so we can just call Set.
unknownHeaders[headerName] = new[] { headerValue }; unknownHeaders[headerName] = headerValue;
} }
pUnknownHeader++; pUnknownHeader++;
} }

View File

@ -4,44 +4,49 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.Framework.Primitives;
namespace Microsoft.Net.Http.Server namespace Microsoft.Net.Http.Server
{ {
public class HeaderCollection : IDictionary<string, string[]> public class HeaderCollection : IDictionary<string, StringValues>
{ {
public HeaderCollection() public HeaderCollection()
: this(new Dictionary<string, string[]>(4, StringComparer.OrdinalIgnoreCase)) : this(new Dictionary<string, StringValues>(4, StringComparer.OrdinalIgnoreCase))
{ {
} }
public HeaderCollection(IDictionary<string, string[]> store) public HeaderCollection(IDictionary<string, StringValues> store)
{ {
Store = store; Store = store;
} }
private IDictionary<string, string[]> Store { get; set; } private IDictionary<string, StringValues> Store { get; set; }
// Readonly after the response has been started. // Readonly after the response has been started.
public bool IsReadOnly { get; internal set; } public bool IsReadOnly { get; internal set; }
public string this[string key] public StringValues this[string key]
{ {
get { return Get(key); } get
{
StringValues values;
return TryGetValue(key, out values) ? values : StringValues.Empty;
}
set set
{ {
ThrowIfReadOnly(); ThrowIfReadOnly();
if (string.IsNullOrEmpty(value)) if (StringValues.IsNullOrEmpty(value))
{ {
Remove(key); Remove(key);
} }
else else
{ {
Set(key, value); Store[key] = value;
} }
} }
} }
string[] IDictionary<string, string[]>.this[string key] StringValues IDictionary<string, StringValues>.this[string key]
{ {
get { return Store[key]; } get { return Store[key]; }
set set
@ -61,18 +66,18 @@ namespace Microsoft.Net.Http.Server
get { return Store.Keys; } get { return Store.Keys; }
} }
public ICollection<string[]> Values public ICollection<StringValues> Values
{ {
get { return Store.Values; } get { return Store.Values; }
} }
public void Add(KeyValuePair<string, string[]> item) public void Add(KeyValuePair<string, StringValues> item)
{ {
ThrowIfReadOnly(); ThrowIfReadOnly();
Store.Add(item); Store.Add(item);
} }
public void Add(string key, string[] value) public void Add(string key, StringValues value)
{ {
ThrowIfReadOnly(); ThrowIfReadOnly();
Store.Add(key, value); Store.Add(key, value);
@ -81,35 +86,9 @@ namespace Microsoft.Net.Http.Server
public void Append(string key, string value) public void Append(string key, string value)
{ {
ThrowIfReadOnly(); ThrowIfReadOnly();
string[] values; StringValues values;
if (Store.TryGetValue(key, out values)) Store.TryGetValue(key, out values);
{ Store[key] = StringValues.Concat(values, value);
var newValues = new string[values.Length + 1];
Array.Copy(values, newValues, values.Length);
newValues[values.Length] = value;
Store[key] = newValues;
}
else
{
Set(key, value);
}
}
public void AppendValues(string key, params string[] values)
{
ThrowIfReadOnly();
string[] oldValues;
if (Store.TryGetValue(key, out oldValues))
{
var newValues = new string[oldValues.Length + values.Length];
Array.Copy(oldValues, newValues, oldValues.Length);
Array.Copy(values, 0, newValues, oldValues.Length, values.Length);
Store[key] = newValues;
}
else
{
SetValues(key, values);
}
} }
public void Clear() public void Clear()
@ -118,7 +97,7 @@ namespace Microsoft.Net.Http.Server
Store.Clear(); Store.Clear();
} }
public bool Contains(KeyValuePair<string, string[]> item) public bool Contains(KeyValuePair<string, StringValues> item)
{ {
return Store.Contains(item); return Store.Contains(item);
} }
@ -128,29 +107,19 @@ namespace Microsoft.Net.Http.Server
return Store.ContainsKey(key); return Store.ContainsKey(key);
} }
public void CopyTo(KeyValuePair<string, string[]>[] array, int arrayIndex) public void CopyTo(KeyValuePair<string, StringValues>[] array, int arrayIndex)
{ {
Store.CopyTo(array, arrayIndex); Store.CopyTo(array, arrayIndex);
} }
public string Get(string key) public IEnumerator<KeyValuePair<string, StringValues>> GetEnumerator()
{
string[] values;
if (Store.TryGetValue(key, out values))
{
return string.Join(", ", values);
}
return null;
}
public IEnumerator<KeyValuePair<string, string[]>> GetEnumerator()
{ {
return Store.GetEnumerator(); return Store.GetEnumerator();
} }
public IEnumerable<string> GetValues(string key) public IEnumerable<string> GetValues(string key)
{ {
string[] values; StringValues values;
if (Store.TryGetValue(key, out values)) if (Store.TryGetValue(key, out values))
{ {
return HeaderParser.SplitValues(values); return HeaderParser.SplitValues(values);
@ -158,7 +127,7 @@ namespace Microsoft.Net.Http.Server
return HeaderParser.Empty; return HeaderParser.Empty;
} }
public bool Remove(KeyValuePair<string, string[]> item) public bool Remove(KeyValuePair<string, StringValues> item)
{ {
ThrowIfReadOnly(); ThrowIfReadOnly();
return Store.Remove(item); return Store.Remove(item);
@ -170,19 +139,7 @@ namespace Microsoft.Net.Http.Server
return Store.Remove(key); return Store.Remove(key);
} }
public void Set(string key, string value) public bool TryGetValue(string key, out StringValues value)
{
ThrowIfReadOnly();
Store[key] = new[] { value };
}
public void SetValues(string key, params string[] values)
{
ThrowIfReadOnly();
Store[key] = values;
}
public bool TryGetValue(string key, out string[] value)
{ {
return Store.TryGetValue(key, out value); return Store.TryGetValue(key, out value);
} }

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.Framework.Primitives;
namespace Microsoft.Net.Http.Server namespace Microsoft.Net.Http.Server
{ {
@ -11,7 +12,7 @@ namespace Microsoft.Net.Http.Server
internal static IEnumerable<string> Empty = new string[0]; internal static IEnumerable<string> Empty = new string[0];
// Split on commas, except in quotes // Split on commas, except in quotes
internal static IEnumerable<string> SplitValues(string[] values) internal static IEnumerable<string> SplitValues(StringValues values)
{ {
foreach (var value in values) foreach (var value in values)
{ {

View File

@ -231,16 +231,16 @@ namespace Microsoft.Net.Http.Server
{ {
if (_contentBoundaryType == BoundaryType.None) if (_contentBoundaryType == BoundaryType.None)
{ {
string transferEncoding = Headers.Get(HttpKnownHeaderNames.TransferEncoding) ?? string.Empty; string transferEncoding = Headers[HttpKnownHeaderNames.TransferEncoding];
if (string.Equals("chunked", transferEncoding.Trim(), StringComparison.OrdinalIgnoreCase)) if (string.Equals("chunked", transferEncoding?.Trim(), StringComparison.OrdinalIgnoreCase))
{ {
_contentBoundaryType = BoundaryType.Chunked; _contentBoundaryType = BoundaryType.Chunked;
} }
else else
{ {
string length = Headers.Get(HttpKnownHeaderNames.ContentLength) ?? string.Empty; string length = Headers[HttpKnownHeaderNames.ContentLength];
long value; long value;
if (long.TryParse(length.Trim(), NumberStyles.None, if (length != null && long.TryParse(length.Trim(), NumberStyles.None,
CultureInfo.InvariantCulture.NumberFormat, out value)) CultureInfo.InvariantCulture.NumberFormat, out value))
{ {
_contentBoundaryType = BoundaryType.ContentLength; _contentBoundaryType = BoundaryType.ContentLength;
@ -422,7 +422,7 @@ namespace Microsoft.Net.Http.Server
{ {
get get
{ {
return Headers.Get(HttpKnownHeaderNames.ContentType); return Headers[HttpKnownHeaderNames.ContentType];
} }
} }

View File

@ -194,8 +194,8 @@ namespace Microsoft.Net.Http.Server
} }
// Connection: Upgrade (some odd clients send Upgrade,KeepAlive) // Connection: Upgrade (some odd clients send Upgrade,KeepAlive)
string connection = Request.Headers[HttpKnownHeaderNames.Connection] ?? string.Empty; string connection = Request.Headers[HttpKnownHeaderNames.Connection];
if (connection.IndexOf(HttpKnownHeaderNames.Upgrade, StringComparison.OrdinalIgnoreCase) < 0) if (connection == null || connection.IndexOf(HttpKnownHeaderNames.Upgrade, StringComparison.OrdinalIgnoreCase) < 0)
{ {
return false; return false;
} }
@ -244,8 +244,8 @@ namespace Microsoft.Net.Http.Server
} }
// Connection: Upgrade (some odd clients send Upgrade,KeepAlive) // Connection: Upgrade (some odd clients send Upgrade,KeepAlive)
string connection = Request.Headers[HttpKnownHeaderNames.Connection] ?? string.Empty; string connection = Request.Headers[HttpKnownHeaderNames.Connection];
if (connection.IndexOf(HttpKnownHeaderNames.Upgrade, StringComparison.OrdinalIgnoreCase) < 0) if (connection == null || connection.IndexOf(HttpKnownHeaderNames.Upgrade, StringComparison.OrdinalIgnoreCase) < 0)
{ {
throw new InvalidOperationException("The Connection header is invalid: " + connection); throw new InvalidOperationException("The Connection header is invalid: " + connection);
} }
@ -345,9 +345,9 @@ namespace Microsoft.Net.Http.Server
string secWebSocketKey = Request.Headers[HttpKnownHeaderNames.SecWebSocketKey]; string secWebSocketKey = Request.Headers[HttpKnownHeaderNames.SecWebSocketKey];
string secWebSocketAccept = WebSocketHelpers.GetSecWebSocketAcceptString(secWebSocketKey); string secWebSocketAccept = WebSocketHelpers.GetSecWebSocketAcceptString(secWebSocketKey);
Response.Headers.AppendValues(HttpKnownHeaderNames.Connection, HttpKnownHeaderNames.Upgrade); Response.Headers.Append(HttpKnownHeaderNames.Connection, HttpKnownHeaderNames.Upgrade);
Response.Headers.AppendValues(HttpKnownHeaderNames.Upgrade, WebSocketHelpers.WebSocketUpgradeToken); Response.Headers.Append(HttpKnownHeaderNames.Upgrade, WebSocketHelpers.WebSocketUpgradeToken);
Response.Headers.AppendValues(HttpKnownHeaderNames.SecWebSocketAccept, secWebSocketAccept); Response.Headers.Append(HttpKnownHeaderNames.SecWebSocketAccept, secWebSocketAccept);
Stream opaqueStream = await UpgradeAsync(); Stream opaqueStream = await UpgradeAsync();

View File

@ -68,6 +68,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Framework.Primitives;
namespace Microsoft.Net.Http.Server namespace Microsoft.Net.Http.Server
{ {
@ -75,15 +76,15 @@ namespace Microsoft.Net.Http.Server
internal partial class RequestHeaders internal partial class RequestHeaders
{ {
// Tracks if individual fields have been read from native or set directly. // Tracks if individual fields have been read from native or set directly.
// Once read or set, their presence in the collection is marked by if their string[] is null or not. // Once read or set, their presence in the collection is marked by if their StringValues is null or not.
private UInt32 _flag0, _flag1; private UInt32 _flag0, _flag1;
<# foreach(var prop in props) { #> <# foreach(var prop in props) { #>
private string[] _<#=prop.Name#>; private StringValues _<#=prop.Name#>;
<# } #> <# } #>
<# foreach(var prop in props) { #> <# foreach(var prop in props) { #>
internal string[] <#=prop.Name#> internal StringValues <#=prop.Name#>
{ {
get get
{ {
@ -92,7 +93,7 @@ namespace Microsoft.Net.Http.Server
string nativeValue = GetKnownHeader(<#=prop.ID#>); string nativeValue = GetKnownHeader(<#=prop.ID#>);
if (nativeValue != null) if (nativeValue != null)
{ {
_<#=prop.Name#> = new string[] { nativeValue }; _<#=prop.Name#> = nativeValue;
} }
<#=MarkRead(prop.Index)#>; <#=MarkRead(prop.Index)#>;
} }
@ -124,7 +125,7 @@ namespace Microsoft.Net.Http.Server
return false; return false;
} }
private bool PropertiesTryGetValue(string key, out string[] value) private bool PropertiesTryGetValue(string key, out StringValues value)
{ {
switch (key.Length) switch (key.Length)
{ {
@ -144,7 +145,7 @@ namespace Microsoft.Net.Http.Server
return false; return false;
} }
private bool PropertiesTrySetValue(string key, string[] value) private bool PropertiesTrySetValue(string key, StringValues value)
{ {
switch (key.Length) switch (key.Length)
{ {
@ -195,7 +196,7 @@ namespace Microsoft.Net.Http.Server
<# } #> <# } #>
} }
private IEnumerable<string[]> PropertiesValues() private IEnumerable<StringValues> PropertiesValues()
{ {
<# foreach(var prop in props) { #> <# foreach(var prop in props) { #>
if (<#=prop.Name#> != null) if (<#=prop.Name#> != null)
@ -205,12 +206,12 @@ namespace Microsoft.Net.Http.Server
<# } #> <# } #>
} }
private IEnumerable<KeyValuePair<string, string[]>> PropertiesEnumerable() private IEnumerable<KeyValuePair<string, StringValues>> PropertiesEnumerable()
{ {
<# foreach(var prop in props) { #> <# foreach(var prop in props) { #>
if (<#=prop.Name#> != null) if (<#=prop.Name#> != null)
{ {
yield return new KeyValuePair<string, string[]>("<#=prop.Key#>", <#=prop.Name#>); yield return new KeyValuePair<string, StringValues>("<#=prop.Key#>", <#=prop.Name#>);
} }
<# } #> <# } #>
} }

View File

@ -39,12 +39,13 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using Microsoft.Framework.Primitives;
namespace Microsoft.Net.Http.Server namespace Microsoft.Net.Http.Server
{ {
internal partial class RequestHeaders : IDictionary<string, string[]> internal partial class RequestHeaders : IDictionary<string, StringValues>
{ {
private IDictionary<string, string[]> _extra; private IDictionary<string, StringValues> _extra;
private NativeRequestContext _requestMemoryBlob; private NativeRequestContext _requestMemoryBlob;
internal RequestHeaders(NativeRequestContext requestMemoryBlob) internal RequestHeaders(NativeRequestContext requestMemoryBlob)
@ -52,13 +53,13 @@ namespace Microsoft.Net.Http.Server
_requestMemoryBlob = requestMemoryBlob; _requestMemoryBlob = requestMemoryBlob;
} }
private IDictionary<string, string[]> Extra private IDictionary<string, StringValues> Extra
{ {
get get
{ {
if (_extra == null) if (_extra == null)
{ {
var newDict = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase); var newDict = new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase);
GetUnknownHeaders(newDict); GetUnknownHeaders(newDict);
Interlocked.CompareExchange(ref _extra, newDict, null); Interlocked.CompareExchange(ref _extra, newDict, null);
} }
@ -66,11 +67,11 @@ namespace Microsoft.Net.Http.Server
} }
} }
string[] IDictionary<string, string[]>.this[string key] StringValues IDictionary<string, StringValues>.this[string key]
{ {
get get
{ {
string[] value; StringValues value;
return PropertiesTryGetValue(key, out value) ? value : Extra[key]; return PropertiesTryGetValue(key, out value) ? value : Extra[key];
} }
set set
@ -88,13 +89,13 @@ namespace Microsoft.Net.Http.Server
_requestMemoryBlob.OriginalBlobAddress, (int)header); _requestMemoryBlob.OriginalBlobAddress, (int)header);
} }
private void GetUnknownHeaders(IDictionary<string, string[]> extra) private void GetUnknownHeaders(IDictionary<string, StringValues> extra)
{ {
UnsafeNclNativeMethods.HttpApi.GetUnknownHeaders(extra, _requestMemoryBlob.RequestBuffer, UnsafeNclNativeMethods.HttpApi.GetUnknownHeaders(extra, _requestMemoryBlob.RequestBuffer,
_requestMemoryBlob.OriginalBlobAddress); _requestMemoryBlob.OriginalBlobAddress);
} }
void IDictionary<string, string[]>.Add(string key, string[] value) void IDictionary<string, StringValues>.Add(string key, StringValues value)
{ {
if (!PropertiesTrySetValue(key, value)) if (!PropertiesTrySetValue(key, value))
{ {
@ -112,7 +113,7 @@ namespace Microsoft.Net.Http.Server
get { return PropertiesKeys().Concat(Extra.Keys).ToArray(); } get { return PropertiesKeys().Concat(Extra.Keys).ToArray(); }
} }
ICollection<string[]> IDictionary<string, string[]>.Values ICollection<StringValues> IDictionary<string, StringValues>.Values
{ {
get { return PropertiesValues().Concat(Extra.Values).ToArray(); } get { return PropertiesValues().Concat(Extra.Values).ToArray(); }
} }
@ -130,17 +131,17 @@ namespace Microsoft.Net.Http.Server
return PropertiesTryRemove(key) || Extra.Remove(key); return PropertiesTryRemove(key) || Extra.Remove(key);
} }
public bool TryGetValue(string key, out string[] value) public bool TryGetValue(string key, out StringValues value)
{ {
return PropertiesTryGetValue(key, out value) || Extra.TryGetValue(key, out value); return PropertiesTryGetValue(key, out value) || Extra.TryGetValue(key, out value);
} }
void ICollection<KeyValuePair<string, string[]>>.Add(KeyValuePair<string, string[]> item) void ICollection<KeyValuePair<string, StringValues>>.Add(KeyValuePair<string, StringValues> item)
{ {
((IDictionary<string, object>)this).Add(item.Key, item.Value); ((IDictionary<string, object>)this).Add(item.Key, item.Value);
} }
void ICollection<KeyValuePair<string, string[]>>.Clear() void ICollection<KeyValuePair<string, StringValues>>.Clear()
{ {
foreach (var key in PropertiesKeys()) foreach (var key in PropertiesKeys())
{ {
@ -149,36 +150,36 @@ namespace Microsoft.Net.Http.Server
Extra.Clear(); Extra.Clear();
} }
bool ICollection<KeyValuePair<string, string[]>>.Contains(KeyValuePair<string, string[]> item) bool ICollection<KeyValuePair<string, StringValues>>.Contains(KeyValuePair<string, StringValues> item)
{ {
object value; object value;
return ((IDictionary<string, object>)this).TryGetValue(item.Key, out value) && Object.Equals(value, item.Value); return ((IDictionary<string, object>)this).TryGetValue(item.Key, out value) && Object.Equals(value, item.Value);
} }
void ICollection<KeyValuePair<string, string[]>>.CopyTo(KeyValuePair<string, string[]>[] array, int arrayIndex) void ICollection<KeyValuePair<string, StringValues>>.CopyTo(KeyValuePair<string, StringValues>[] array, int arrayIndex)
{ {
PropertiesEnumerable().Concat(Extra).ToArray().CopyTo(array, arrayIndex); PropertiesEnumerable().Concat(Extra).ToArray().CopyTo(array, arrayIndex);
} }
bool ICollection<KeyValuePair<string, string[]>>.IsReadOnly bool ICollection<KeyValuePair<string, StringValues>>.IsReadOnly
{ {
get { return false; } get { return false; }
} }
bool ICollection<KeyValuePair<string, string[]>>.Remove(KeyValuePair<string, string[]> item) bool ICollection<KeyValuePair<string, StringValues>>.Remove(KeyValuePair<string, StringValues> item)
{ {
return ((IDictionary<string, string[]>)this).Contains(item) && return ((IDictionary<string, StringValues>)this).Contains(item) &&
((IDictionary<string, string[]>)this).Remove(item.Key); ((IDictionary<string, StringValues>)this).Remove(item.Key);
} }
IEnumerator<KeyValuePair<string, string[]>> IEnumerable<KeyValuePair<string, string[]>>.GetEnumerator() IEnumerator<KeyValuePair<string, StringValues>> IEnumerable<KeyValuePair<string, StringValues>>.GetEnumerator()
{ {
return PropertiesEnumerable().Concat(Extra).GetEnumerator(); return PropertiesEnumerable().Concat(Extra).GetEnumerator();
} }
IEnumerator IEnumerable.GetEnumerator() IEnumerator IEnumerable.GetEnumerator()
{ {
return ((IDictionary<string, string[]>)this).GetEnumerator(); return ((IDictionary<string, StringValues>)this).GetEnumerator();
} }
} }
} }

View File

@ -33,14 +33,13 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Framework.Logging; using Microsoft.Framework.Logging;
using Microsoft.Framework.Primitives;
using static Microsoft.Net.Http.Server.UnsafeNclNativeMethods; using static Microsoft.Net.Http.Server.UnsafeNclNativeMethods;
namespace Microsoft.Net.Http.Server namespace Microsoft.Net.Http.Server
{ {
public sealed unsafe class Response public sealed unsafe class Response
{ {
private static readonly string[] ZeroContentLength = new[] { Constants.Zero };
private ResponseState _responseState; private ResponseState _responseState;
private HeaderCollection _headers; private HeaderCollection _headers;
private string _reasonPhrase; private string _reasonPhrase;
@ -200,7 +199,7 @@ namespace Microsoft.Net.Http.Server
{ {
get get
{ {
string contentLengthString = Headers.Get(HttpKnownHeaderNames.ContentLength); string contentLengthString = Headers[HttpKnownHeaderNames.ContentLength];
long contentLength; long contentLength;
if (!string.IsNullOrWhiteSpace(contentLengthString)) if (!string.IsNullOrWhiteSpace(contentLengthString))
{ {
@ -232,7 +231,7 @@ namespace Microsoft.Net.Http.Server
if (value.Value == 0) if (value.Value == 0)
{ {
((IDictionary<string, string[]>)Headers)[HttpKnownHeaderNames.ContentLength] = ZeroContentLength; Headers[HttpKnownHeaderNames.ContentLength] = Constants.Zero;
} }
else else
{ {
@ -246,7 +245,7 @@ namespace Microsoft.Net.Http.Server
{ {
get get
{ {
return Headers.Get(HttpKnownHeaderNames.ContentType); return Headers[HttpKnownHeaderNames.ContentType];
} }
set set
{ {
@ -467,14 +466,14 @@ namespace Microsoft.Net.Http.Server
// Gather everything from the request that affects the response: // Gather everything from the request that affects the response:
var requestVersion = Request.ProtocolVersion; var requestVersion = Request.ProtocolVersion;
var requestConnectionString = Request.Headers.Get(HttpKnownHeaderNames.Connection); var requestConnectionString = Request.Headers[HttpKnownHeaderNames.Connection];
var isHeadRequest = Request.IsHeadMethod; var isHeadRequest = Request.IsHeadMethod;
var requestCloseSet = Matches(Constants.Close, requestConnectionString); var requestCloseSet = Matches(Constants.Close, requestConnectionString);
// Gather everything the app may have set on the response: // Gather everything the app may have set on the response:
// Http.Sys does not allow us to specify the response protocol version, assume this is a HTTP/1.1 response when making decisions. // Http.Sys does not allow us to specify the response protocol version, assume this is a HTTP/1.1 response when making decisions.
var responseConnectionString = Headers.Get(HttpKnownHeaderNames.Connection); var responseConnectionString = Headers[HttpKnownHeaderNames.Connection];
var transferEncodingString = Headers.Get(HttpKnownHeaderNames.TransferEncoding); var transferEncodingString = Headers[HttpKnownHeaderNames.TransferEncoding];
var responseContentLength = ContentLength; var responseContentLength = ContentLength;
var responseCloseSet = Matches(Constants.Close, responseConnectionString); var responseCloseSet = Matches(Constants.Close, responseConnectionString);
var responseChunkedSet = Matches(Constants.Chunked, transferEncodingString); var responseChunkedSet = Matches(Constants.Chunked, transferEncodingString);
@ -572,9 +571,9 @@ namespace Microsoft.Net.Http.Server
int numUnknownHeaders = 0; int numUnknownHeaders = 0;
int numKnownMultiHeaders = 0; int numKnownMultiHeaders = 0;
foreach (KeyValuePair<string, string[]> headerPair in Headers) foreach (var headerPair in Headers)
{ {
if (headerPair.Value.Length == 0) if (headerPair.Value.Count == 0)
{ {
// TODO: Have the collection exclude empty headers. // TODO: Have the collection exclude empty headers.
continue; continue;
@ -586,9 +585,9 @@ namespace Microsoft.Net.Http.Server
if (lookup == -1 || if (lookup == -1 ||
(isOpaqueUpgrade && lookup == (int)HttpApi.HTTP_RESPONSE_HEADER_ID.Enum.HttpHeaderConnection)) (isOpaqueUpgrade && lookup == (int)HttpApi.HTTP_RESPONSE_HEADER_ID.Enum.HttpHeaderConnection))
{ {
numUnknownHeaders += headerPair.Value.Length; numUnknownHeaders += headerPair.Value.Count;
} }
else if (headerPair.Value.Length > 1) else if (headerPair.Value.Count > 1)
{ {
numKnownMultiHeaders++; numKnownMultiHeaders++;
} }
@ -599,15 +598,15 @@ namespace Microsoft.Net.Http.Server
{ {
fixed (HttpApi.HTTP_KNOWN_HEADER* pKnownHeaders = &_nativeResponse.Response_V1.Headers.KnownHeaders) fixed (HttpApi.HTTP_KNOWN_HEADER* pKnownHeaders = &_nativeResponse.Response_V1.Headers.KnownHeaders)
{ {
foreach (KeyValuePair<string, string[]> headerPair in Headers) foreach (var headerPair in Headers)
{ {
if (headerPair.Value.Length == 0) if (headerPair.Value.Count == 0)
{ {
// TODO: Have the collection exclude empty headers. // TODO: Have the collection exclude empty headers.
continue; continue;
} }
headerName = headerPair.Key; headerName = headerPair.Key;
string[] headerValues = headerPair.Value; StringValues headerValues = headerPair.Value;
lookup = HttpApi.HTTP_RESPONSE_HEADER_ID.IndexOfKnownHeader(headerName); lookup = HttpApi.HTTP_RESPONSE_HEADER_ID.IndexOfKnownHeader(headerName);
// Http.Sys doesn't let us send the Connection: Upgrade header as a Known header. // Http.Sys doesn't let us send the Connection: Upgrade header as a Known header.
@ -622,7 +621,7 @@ namespace Microsoft.Net.Http.Server
_nativeResponse.Response_V1.Headers.pUnknownHeaders = (HttpApi.HTTP_UNKNOWN_HEADER*)gcHandle.AddrOfPinnedObject(); _nativeResponse.Response_V1.Headers.pUnknownHeaders = (HttpApi.HTTP_UNKNOWN_HEADER*)gcHandle.AddrOfPinnedObject();
} }
for (int headerValueIndex = 0; headerValueIndex < headerValues.Length; headerValueIndex++) for (int headerValueIndex = 0; headerValueIndex < headerValues.Count; headerValueIndex++)
{ {
// Add Name // Add Name
bytes = new byte[HeaderEncoding.GetByteCount(headerName)]; bytes = new byte[HeaderEncoding.GetByteCount(headerName)];
@ -643,7 +642,7 @@ namespace Microsoft.Net.Http.Server
_nativeResponse.Response_V1.Headers.UnknownHeaderCount++; _nativeResponse.Response_V1.Headers.UnknownHeaderCount++;
} }
} }
else if (headerPair.Value.Length == 1) else if (headerPair.Value.Count == 1)
{ {
headerValue = headerValues[0]; headerValue = headerValues[0];
if (headerValue != null) if (headerValue != null)
@ -674,12 +673,12 @@ namespace Microsoft.Net.Http.Server
header.HeaderId = (HttpApi.HTTP_RESPONSE_HEADER_ID.Enum)lookup; header.HeaderId = (HttpApi.HTTP_RESPONSE_HEADER_ID.Enum)lookup;
header.Flags = HttpApi.HTTP_RESPONSE_INFO_FLAGS.PreserveOrder; // TODO: The docs say this is for www-auth only. header.Flags = HttpApi.HTTP_RESPONSE_INFO_FLAGS.PreserveOrder; // TODO: The docs say this is for www-auth only.
HttpApi.HTTP_KNOWN_HEADER[] nativeHeaderValues = new HttpApi.HTTP_KNOWN_HEADER[headerValues.Length]; HttpApi.HTTP_KNOWN_HEADER[] nativeHeaderValues = new HttpApi.HTTP_KNOWN_HEADER[headerValues.Count];
gcHandle = GCHandle.Alloc(nativeHeaderValues, GCHandleType.Pinned); gcHandle = GCHandle.Alloc(nativeHeaderValues, GCHandleType.Pinned);
pinnedHeaders.Add(gcHandle); pinnedHeaders.Add(gcHandle);
header.KnownHeaders = (HttpApi.HTTP_KNOWN_HEADER*)gcHandle.AddrOfPinnedObject(); header.KnownHeaders = (HttpApi.HTTP_KNOWN_HEADER*)gcHandle.AddrOfPinnedObject();
for (int headerValueIndex = 0; headerValueIndex < headerValues.Length; headerValueIndex++) for (int headerValueIndex = 0; headerValueIndex < headerValues.Count; headerValueIndex++)
{ {
// Add Value // Add Value
headerValue = headerValues[headerValueIndex]; headerValue = headerValues[headerValueIndex];

View File

@ -3,6 +3,7 @@
"description": "Implementation of WebListener, a successor to HttpListener. It is used in the WebListener server package.", "description": "Implementation of WebListener, a successor to HttpListener. It is used in the WebListener server package.",
"dependencies": { "dependencies": {
"Microsoft.Framework.Logging.Abstractions": "1.0.0-*", "Microsoft.Framework.Logging.Abstractions": "1.0.0-*",
"Microsoft.Framework.Primitives": "1.0.0-*",
"Microsoft.Net.WebSockets": "1.0.0-*" "Microsoft.Net.WebSockets": "1.0.0-*"
}, },
"compilationOptions": { "compilationOptions": {

View File

@ -22,6 +22,7 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Http.Features; using Microsoft.AspNet.Http.Features;
using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Http.Internal;
using Microsoft.Framework.Primitives;
using Xunit; using Xunit;
namespace Microsoft.AspNet.Server.WebListener namespace Microsoft.AspNet.Server.WebListener
@ -38,8 +39,8 @@ namespace Microsoft.AspNet.Server.WebListener
// NOTE: The System.Net client only sends the Connection: keep-alive header on the first connection per service-point. // NOTE: The System.Net client only sends the Connection: keep-alive header on the first connection per service-point.
// Assert.Equal(2, requestHeaders.Count); // Assert.Equal(2, requestHeaders.Count);
// Assert.Equal("Keep-Alive", requestHeaders.Get("Connection")); // Assert.Equal("Keep-Alive", requestHeaders.Get("Connection"));
Assert.NotNull(requestHeaders.Get("Host")); Assert.False(StringValues.IsNullOrEmpty(requestHeaders["Host"]));
Assert.Equal(null, requestHeaders.Get("Accept")); Assert.True(StringValues.IsNullOrEmpty(requestHeaders["Accept"]));
return Task.FromResult(0); return Task.FromResult(0);
})) }))
{ {
@ -56,13 +57,13 @@ namespace Microsoft.AspNet.Server.WebListener
{ {
var requestHeaders = new DefaultHttpContext((IFeatureCollection)env).Request.Headers; var requestHeaders = new DefaultHttpContext((IFeatureCollection)env).Request.Headers;
Assert.Equal(4, requestHeaders.Count); Assert.Equal(4, requestHeaders.Count);
Assert.NotNull(requestHeaders.Get("Host")); Assert.False(StringValues.IsNullOrEmpty(requestHeaders["Host"]));
Assert.Equal("close", requestHeaders.Get("Connection")); Assert.Equal("close", requestHeaders["Connection"]);
// Apparently Http.Sys squashes request headers together. // Apparently Http.Sys squashes request headers together.
Assert.Equal(1, requestHeaders.GetValues("Custom-Header").Count); Assert.Equal(1, requestHeaders["Custom-Header"].Count);
Assert.Equal("custom1, and custom2, custom3", requestHeaders.Get("Custom-Header")); Assert.Equal("custom1, and custom2, custom3", requestHeaders["Custom-Header"]);
Assert.Equal(1, requestHeaders.GetValues("Spacer-Header").Count); Assert.Equal(1, requestHeaders["Spacer-Header"].Count);
Assert.Equal("spacervalue, spacervalue", requestHeaders.Get("Spacer-Header")); Assert.Equal("spacervalue, spacervalue", requestHeaders["Spacer-Header"]);
return Task.FromResult(0); return Task.FromResult(0);
})) }))
{ {

View File

@ -6,6 +6,7 @@ using System.Net.Http;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Framework.Primitives;
using Xunit; using Xunit;
namespace Microsoft.Net.Http.Server namespace Microsoft.Net.Http.Server
@ -26,10 +27,10 @@ namespace Microsoft.Net.Http.Server
// Assert.Equal(2, requestHeaders.Count); // Assert.Equal(2, requestHeaders.Count);
// Assert.Equal("Keep-Alive", requestHeaders.Get("Connection")); // Assert.Equal("Keep-Alive", requestHeaders.Get("Connection"));
Assert.Equal(new Uri(address).Authority, requestHeaders["Host"]); Assert.Equal(new Uri(address).Authority, requestHeaders["Host"]);
string[] values; StringValues values;
Assert.False(requestHeaders.TryGetValue("Accept", out values)); Assert.False(requestHeaders.TryGetValue("Accept", out values));
Assert.False(requestHeaders.ContainsKey("Accept")); Assert.False(requestHeaders.ContainsKey("Accept"));
Assert.Null(requestHeaders["Accept"]); Assert.True(StringValues.IsNullOrEmpty(requestHeaders["Accept"]));
context.Dispose(); context.Dispose();
string response = await responseTask; string response = await responseTask;

View File

@ -207,7 +207,7 @@ namespace Microsoft.Net.Http.Server
var context = await server.GetContextAsync(); var context = await server.GetContextAsync();
var responseHeaders = context.Response.Headers; var responseHeaders = context.Response.Headers;
responseHeaders.SetValues("WWW-Authenticate", "custom1, and custom2", "custom3"); responseHeaders["WWW-Authenticate"] = new[] { "custom1, and custom2", "custom3" };
context.Dispose(); context.Dispose();
// HttpClient would merge the headers no matter what // HttpClient would merge the headers no matter what
@ -232,7 +232,7 @@ namespace Microsoft.Net.Http.Server
var context = await server.GetContextAsync(); var context = await server.GetContextAsync();
var responseHeaders = context.Response.Headers; var responseHeaders = context.Response.Headers;
responseHeaders.SetValues("Custom-Header1", "custom1, and custom2", "custom3"); responseHeaders["Custom-Header1"] = new[] { "custom1, and custom2", "custom3" };
context.Dispose(); context.Dispose();
// HttpClient would merge the headers no matter what // HttpClient would merge the headers no matter what
@ -346,8 +346,8 @@ namespace Microsoft.Net.Http.Server
var context = await server.GetContextAsync(); var context = await server.GetContextAsync();
var responseHeaders = context.Response.Headers; var responseHeaders = context.Response.Headers;
responseHeaders.SetValues("Custom1", "value1a", "value1b"); responseHeaders["Custom1"] = new[] { "value1a", "value1b" };
responseHeaders.SetValues("Custom2", "value2a, value2b"); responseHeaders["Custom2"] = "value2a, value2b";
var body = context.Response.Body; var body = context.Response.Body;
Assert.False(context.Response.HasStarted); Assert.False(context.Response.HasStarted);
body.Flush(); body.Flush();
@ -382,8 +382,8 @@ namespace Microsoft.Net.Http.Server
var context = await server.GetContextAsync(); var context = await server.GetContextAsync();
var responseHeaders = context.Response.Headers; var responseHeaders = context.Response.Headers;
responseHeaders.SetValues("Custom1", "value1a", "value1b"); responseHeaders["Custom1"] = new[] { "value1a", "value1b" };
responseHeaders.SetValues("Custom2", "value2a, value2b"); responseHeaders["Custom2"] = "value2a, value2b";
var body = context.Response.Body; var body = context.Response.Body;
Assert.False(context.Response.HasStarted); Assert.False(context.Response.HasStarted);
await body.FlushAsync(); await body.FlushAsync();