#45 Add friendly header collection APIs for people using WebListener directly.
This commit is contained in:
parent
09267cca2f
commit
53e38e2a23
|
|
@ -48,7 +48,7 @@ namespace HelloWorld
|
|||
// Request
|
||||
// context.Request.ProtocolVersion
|
||||
// context.Request.IsLocal
|
||||
// context.Request.Headers // TODO: Header helpers?
|
||||
// context.Request.Headers
|
||||
// context.Request.Method
|
||||
// context.Request.Body
|
||||
// Content-Length - long?
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Principal;
|
||||
|
|
@ -152,22 +153,7 @@ namespace Microsoft.Net.Server
|
|||
|
||||
if (challenges.Count > 0)
|
||||
{
|
||||
// TODO: We need a better header API that just lets us append values.
|
||||
// Append to the existing header, if any. Some clients (IE, Chrome) require each challenges to be sent on their own line/header.
|
||||
string[] oldValues;
|
||||
string[] newValues;
|
||||
if (context.Response.Headers.TryGetValue(HttpKnownHeaderNames.WWWAuthenticate, out oldValues))
|
||||
{
|
||||
newValues = new string[oldValues.Length + challenges.Count];
|
||||
Array.Copy(oldValues, newValues, oldValues.Length);
|
||||
challenges.CopyTo(newValues, oldValues.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
newValues = new string[challenges.Count];
|
||||
challenges.CopyTo(newValues, 0);
|
||||
}
|
||||
context.Response.Headers[HttpKnownHeaderNames.WWWAuthenticate] = newValues;
|
||||
context.Response.Headers.AppendValues(HttpKnownHeaderNames.WWWAuthenticate, challenges.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,68 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc.
|
||||
// All Rights Reserved
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR
|
||||
// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
|
||||
// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF
|
||||
// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR
|
||||
// NON-INFRINGEMENT.
|
||||
// See the Apache 2 License for the specific language governing
|
||||
// permissions and limitations under the License.
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// <copyright file="DictionaryExtensions.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace System.Collections.Generic
|
||||
{
|
||||
internal static class DictionaryExtensions
|
||||
{
|
||||
internal static void Append(this IDictionary<string, string[]> dictionary, string key, string value)
|
||||
{
|
||||
string[] orriginalValues;
|
||||
if (dictionary.TryGetValue(key, out orriginalValues))
|
||||
{
|
||||
string[] newValues = new string[orriginalValues.Length + 1];
|
||||
orriginalValues.CopyTo(newValues, 0);
|
||||
newValues[newValues.Length - 1] = value;
|
||||
dictionary[key] = newValues;
|
||||
}
|
||||
else
|
||||
{
|
||||
dictionary[key] = new string[] { value };
|
||||
}
|
||||
}
|
||||
|
||||
internal static string Get(this IDictionary<string, string[]> dictionary, string key)
|
||||
{
|
||||
string[] values;
|
||||
if (dictionary.TryGetValue(key, out values))
|
||||
{
|
||||
return string.Join(", ", values);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
internal static T Get<T>(this IDictionary<string, object> dictionary, string key, T fallback = default(T))
|
||||
{
|
||||
object values;
|
||||
if (dictionary.TryGetValue(key, out values))
|
||||
{
|
||||
return (T)values;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -22,7 +22,6 @@
|
|||
<Compile Include="AuthenticationManager.cs" />
|
||||
<Compile Include="AuthenticationTypes.cs" />
|
||||
<Compile Include="Constants.cs" />
|
||||
<Compile Include="DictionaryExtensions.cs" />
|
||||
<Compile Include="fx\Microsoft\Win32\SafeHandles\CriticalHandleZeroOrMinusOneIsInvalid.cs" />
|
||||
<Compile Include="fx\Microsoft\Win32\SafeHandles\SafeHandleZeroOrMinusOneIsInvalid.cs" />
|
||||
<Compile Include="fx\System\Diagnostics\TraceEventType.cs" />
|
||||
|
|
@ -52,6 +51,8 @@
|
|||
<Compile Include="NativeInterop\SocketAddress.cs" />
|
||||
<Compile Include="NativeInterop\SSPIHandle.cs" />
|
||||
<Compile Include="NativeInterop\UnsafeNativeMethods.cs" />
|
||||
<Compile Include="RequestProcessing\HeaderCollection.cs" />
|
||||
<Compile Include="RequestProcessing\HeaderParser.cs" />
|
||||
<Compile Include="WebListener.cs" />
|
||||
<Compile Include="RequestProcessing\BoundaryType.cs" />
|
||||
<Compile Include="RequestProcessing\ClientCertLoader.cs" />
|
||||
|
|
|
|||
|
|
@ -961,9 +961,9 @@ namespace Microsoft.Net.Server
|
|||
{
|
||||
headerValue = string.Empty;
|
||||
}
|
||||
// Note that Http.Sys currently collapses all headers of the same name to a single string, so
|
||||
// append will just set.
|
||||
unknownHeaders.Append(headerName, headerValue);
|
||||
// Note that Http.Sys currently collapses all headers of the same name to a single coma seperated string,
|
||||
// so we can just call Set.
|
||||
unknownHeaders[headerName] = new[] { headerValue };
|
||||
}
|
||||
pUnknownHeader++;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,183 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Net.Server
|
||||
{
|
||||
public class HeaderCollection : IDictionary<string, string[]>
|
||||
{
|
||||
public HeaderCollection()
|
||||
: this(new Dictionary<string, string[]>(4, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
}
|
||||
|
||||
public HeaderCollection(IDictionary<string, string[]> store)
|
||||
{
|
||||
Store = store;
|
||||
}
|
||||
|
||||
private IDictionary<string, string[]> Store { get; set; }
|
||||
|
||||
public string this[string key]
|
||||
{
|
||||
get { return Get(key); }
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
Remove(key);
|
||||
}
|
||||
else
|
||||
{
|
||||
Set(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string[] IDictionary<string, string[]>.this[string key]
|
||||
{
|
||||
get { return Store[key]; }
|
||||
set { Store[key] = value; }
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get { return Store.Count; }
|
||||
}
|
||||
|
||||
public bool IsReadOnly
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public ICollection<string> Keys
|
||||
{
|
||||
get { return Store.Keys; }
|
||||
}
|
||||
|
||||
public ICollection<string[]> Values
|
||||
{
|
||||
get { return Store.Values; }
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<string, string[]> item)
|
||||
{
|
||||
Store.Add(item);
|
||||
}
|
||||
|
||||
public void Add(string key, string[] value)
|
||||
{
|
||||
Store.Add(key, value);
|
||||
}
|
||||
|
||||
public void Append(string key, string value)
|
||||
{
|
||||
string[] values;
|
||||
if (Store.TryGetValue(key, out values))
|
||||
{
|
||||
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)
|
||||
{
|
||||
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()
|
||||
{
|
||||
Store.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(KeyValuePair<string, string[]> item)
|
||||
{
|
||||
return Store.Contains(item);
|
||||
}
|
||||
|
||||
public bool ContainsKey(string key)
|
||||
{
|
||||
return Store.ContainsKey(key);
|
||||
}
|
||||
|
||||
public void CopyTo(KeyValuePair<string, string[]>[] array, int arrayIndex)
|
||||
{
|
||||
Store.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public string Get(string key)
|
||||
{
|
||||
string[] values;
|
||||
if (Store.TryGetValue(key, out values))
|
||||
{
|
||||
return string.Join(", ", values);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<string, string[]>> GetEnumerator()
|
||||
{
|
||||
return Store.GetEnumerator();
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetValues(string key)
|
||||
{
|
||||
string[] values;
|
||||
if (Store.TryGetValue(key, out values))
|
||||
{
|
||||
return HeaderParser.SplitValues(values);
|
||||
}
|
||||
return HeaderParser.Empty;
|
||||
}
|
||||
|
||||
public bool Remove(KeyValuePair<string, string[]> item)
|
||||
{
|
||||
return Store.Remove(item);
|
||||
}
|
||||
|
||||
public bool Remove(string key)
|
||||
{
|
||||
return Store.Remove(key);
|
||||
}
|
||||
|
||||
public void Set(string key, string value)
|
||||
{
|
||||
Store[key] = new[] { value };
|
||||
}
|
||||
|
||||
public void SetValues(string key, params string[] values)
|
||||
{
|
||||
Store[key] = values;
|
||||
}
|
||||
|
||||
public bool TryGetValue(string key, out string[] value)
|
||||
{
|
||||
return Store.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Net.Server
|
||||
{
|
||||
internal static class HeaderParser
|
||||
{
|
||||
internal static IEnumerable<string> Empty = new string[0];
|
||||
|
||||
// Split on commas, except in quotes
|
||||
internal static IEnumerable<string> SplitValues(string[] values)
|
||||
{
|
||||
foreach (var value in values)
|
||||
{
|
||||
int start = 0;
|
||||
bool inQuotes = false;
|
||||
int current = 0;
|
||||
for ( ; current < value.Length; current++)
|
||||
{
|
||||
char ch = value[current];
|
||||
if (inQuotes)
|
||||
{
|
||||
if (ch == '"')
|
||||
{
|
||||
inQuotes = false;
|
||||
}
|
||||
}
|
||||
else if (ch == '"')
|
||||
{
|
||||
inQuotes = true;
|
||||
}
|
||||
else if (ch == ',')
|
||||
{
|
||||
var subValue = value.Substring(start, current - start);
|
||||
if (!string.IsNullOrWhiteSpace(subValue))
|
||||
{
|
||||
yield return subValue.Trim();
|
||||
start = current + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (start < current)
|
||||
{
|
||||
var subValue = value.Substring(start, current - start);
|
||||
if (!string.IsNullOrWhiteSpace(subValue))
|
||||
{
|
||||
yield return subValue.Trim();
|
||||
start = current + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -61,7 +61,7 @@ namespace Microsoft.Net.Server
|
|||
|
||||
private X509Certificate _clientCert;
|
||||
|
||||
private IDictionary<string, string[]> _headers;
|
||||
private HeaderCollection _headers;
|
||||
private BoundaryType _contentBoundaryType;
|
||||
private long? _contentLength;
|
||||
private Stream _nativeStream;
|
||||
|
|
@ -140,7 +140,7 @@ namespace Microsoft.Net.Server
|
|||
}
|
||||
|
||||
_httpMethod = UnsafeNclNativeMethods.HttpApi.GetVerb(RequestBuffer, OriginalBlobAddress);
|
||||
_headers = new RequestHeaders(_nativeRequestContext);
|
||||
_headers = new HeaderCollection(new RequestHeaders(_nativeRequestContext));
|
||||
|
||||
UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_V2* requestV2 = (UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_V2*)memoryBlob.RequestBlob;
|
||||
_user = AuthenticationManager.GetUser(requestV2->pRequestInfo);
|
||||
|
|
@ -250,7 +250,7 @@ namespace Microsoft.Net.Server
|
|||
}
|
||||
}
|
||||
|
||||
public IDictionary<string, string[]> Headers
|
||||
public HeaderCollection Headers
|
||||
{
|
||||
get { return _headers; }
|
||||
}
|
||||
|
|
@ -424,17 +424,6 @@ namespace Microsoft.Net.Server
|
|||
return UnsafeNclNativeMethods.HttpApi.GetKnownVerb(RequestBuffer, OriginalBlobAddress);
|
||||
}
|
||||
|
||||
// TODO: We need an easier to user header collection that has this built in
|
||||
internal string GetHeader(string headerName)
|
||||
{
|
||||
string[] values;
|
||||
if (Headers.TryGetValue(headerName, out values))
|
||||
{
|
||||
return string.Join(", ", values);
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// Populates the client certificate. The result may be null if there is no client cert.
|
||||
// TODO: Does it make sense for this to be invoked multiple times (e.g. renegotiate)? Client and server code appear to
|
||||
// enable this, but it's unclear what Http.Sys would do.
|
||||
|
|
|
|||
|
|
@ -179,28 +179,28 @@ namespace Microsoft.Net.Server
|
|||
}
|
||||
|
||||
// Connection: Upgrade (some odd clients send Upgrade,KeepAlive)
|
||||
string connection = Request.GetHeader(HttpKnownHeaderNames.Connection);
|
||||
string connection = Request.Headers[HttpKnownHeaderNames.Connection] ?? string.Empty;
|
||||
if (connection.IndexOf(HttpKnownHeaderNames.Upgrade, StringComparison.OrdinalIgnoreCase) < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Upgrade: websocket
|
||||
string upgrade = Request.GetHeader(HttpKnownHeaderNames.Upgrade);
|
||||
string upgrade = Request.Headers[HttpKnownHeaderNames.Upgrade];
|
||||
if (!string.Equals(WebSocketHelpers.WebSocketUpgradeToken, upgrade, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sec-WebSocket-Version: 13
|
||||
string version = Request.GetHeader(HttpKnownHeaderNames.SecWebSocketVersion);
|
||||
string version = Request.Headers[HttpKnownHeaderNames.SecWebSocketVersion];
|
||||
if (!string.Equals(WebSocketConstants.SupportedProtocolVersion, version, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sec-WebSocket-Key: {base64string}
|
||||
string key = Request.GetHeader(HttpKnownHeaderNames.SecWebSocketKey);
|
||||
string key = Request.Headers[HttpKnownHeaderNames.SecWebSocketKey];
|
||||
if (!WebSocketHelpers.IsValidWebSocketKey(key))
|
||||
{
|
||||
return false;
|
||||
|
|
@ -229,28 +229,28 @@ namespace Microsoft.Net.Server
|
|||
}
|
||||
|
||||
// Connection: Upgrade (some odd clients send Upgrade,KeepAlive)
|
||||
string connection = Request.GetHeader(HttpKnownHeaderNames.Connection);
|
||||
string connection = Request.Headers[HttpKnownHeaderNames.Connection] ?? string.Empty;
|
||||
if (connection.IndexOf(HttpKnownHeaderNames.Upgrade, StringComparison.OrdinalIgnoreCase) < 0)
|
||||
{
|
||||
throw new InvalidOperationException("The Connection header is invalid: " + connection);
|
||||
}
|
||||
|
||||
// Upgrade: websocket
|
||||
string upgrade = Request.GetHeader(HttpKnownHeaderNames.Upgrade);
|
||||
string upgrade = Request.Headers[HttpKnownHeaderNames.Upgrade];
|
||||
if (!string.Equals(WebSocketHelpers.WebSocketUpgradeToken, upgrade, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException("The Upgrade header is invalid: " + upgrade);
|
||||
}
|
||||
|
||||
// Sec-WebSocket-Version: 13
|
||||
string version = Request.GetHeader(HttpKnownHeaderNames.SecWebSocketVersion);
|
||||
string version = Request.Headers[HttpKnownHeaderNames.SecWebSocketVersion];
|
||||
if (!string.Equals(WebSocketConstants.SupportedProtocolVersion, version, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException("The Sec-WebSocket-Version header is invalid or not supported: " + version);
|
||||
}
|
||||
|
||||
// Sec-WebSocket-Key: {base64string}
|
||||
string key = Request.GetHeader(HttpKnownHeaderNames.SecWebSocketKey);
|
||||
string key = Request.Headers[HttpKnownHeaderNames.SecWebSocketKey];
|
||||
if (!WebSocketHelpers.IsValidWebSocketKey(key))
|
||||
{
|
||||
throw new InvalidOperationException("The Sec-WebSocket-Key header is invalid: " + upgrade);
|
||||
|
|
@ -317,29 +317,22 @@ namespace Microsoft.Net.Server
|
|||
{
|
||||
try
|
||||
{
|
||||
// TODO: We need a better header collection API.
|
||||
ValidateWebSocketRequest();
|
||||
|
||||
string subProtocols = string.Empty;
|
||||
string[] values;
|
||||
if (Request.Headers.TryGetValue(HttpKnownHeaderNames.SecWebSocketProtocol, out values))
|
||||
{
|
||||
subProtocols = string.Join(", ", values);
|
||||
}
|
||||
|
||||
var subProtocols = Request.Headers.GetValues(HttpKnownHeaderNames.SecWebSocketProtocol);
|
||||
bool shouldSendSecWebSocketProtocolHeader = WebSocketHelpers.ProcessWebSocketProtocolHeader(subProtocols, subProtocol);
|
||||
if (shouldSendSecWebSocketProtocolHeader)
|
||||
{
|
||||
Response.Headers[HttpKnownHeaderNames.SecWebSocketProtocol] = new[] { subProtocol };
|
||||
Response.Headers[HttpKnownHeaderNames.SecWebSocketProtocol] = subProtocol;
|
||||
}
|
||||
|
||||
// negotiate the websocket key return value
|
||||
string secWebSocketKey = Request.Headers[HttpKnownHeaderNames.SecWebSocketKey].First();
|
||||
string secWebSocketKey = Request.Headers[HttpKnownHeaderNames.SecWebSocketKey];
|
||||
string secWebSocketAccept = WebSocketHelpers.GetSecWebSocketAcceptString(secWebSocketKey);
|
||||
|
||||
Response.Headers.Add(HttpKnownHeaderNames.Connection, new[] { HttpKnownHeaderNames.Upgrade });
|
||||
Response.Headers.Add(HttpKnownHeaderNames.Upgrade, new[] { WebSocketHelpers.WebSocketUpgradeToken });
|
||||
Response.Headers.Add(HttpKnownHeaderNames.SecWebSocketAccept, new[] { secWebSocketAccept });
|
||||
Response.Headers.AppendValues(HttpKnownHeaderNames.Connection, HttpKnownHeaderNames.Upgrade);
|
||||
Response.Headers.AppendValues(HttpKnownHeaderNames.Upgrade, WebSocketHelpers.WebSocketUpgradeToken);
|
||||
Response.Headers.AppendValues(HttpKnownHeaderNames.SecWebSocketAccept, secWebSocketAccept);
|
||||
|
||||
Stream opaqueStream = await UpgradeAsync();
|
||||
|
||||
|
|
|
|||
|
|
@ -102,17 +102,27 @@ namespace Microsoft.Net.Server
|
|||
}
|
||||
}
|
||||
|
||||
bool IDictionary<string, string[]>.ContainsKey(string key)
|
||||
public bool ContainsKey(string key)
|
||||
{
|
||||
return PropertiesContainsKey(key) || Extra.ContainsKey(key);
|
||||
}
|
||||
|
||||
ICollection<string> IDictionary<string, string[]>.Keys
|
||||
public ICollection<string> Keys
|
||||
{
|
||||
get { return PropertiesKeys().Concat(Extra.Keys).ToArray(); }
|
||||
}
|
||||
|
||||
bool IDictionary<string, string[]>.Remove(string key)
|
||||
ICollection<string[]> IDictionary<string, string[]>.Values
|
||||
{
|
||||
get { return PropertiesValues().Concat(Extra.Values).ToArray(); }
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get { return PropertiesKeys().Count() + Extra.Count; }
|
||||
}
|
||||
|
||||
public bool Remove(string key)
|
||||
{
|
||||
// Although this is a mutating operation, Extra is used instead of StrongExtra,
|
||||
// because if a real dictionary has not been allocated the default behavior of the
|
||||
|
|
@ -120,16 +130,11 @@ namespace Microsoft.Net.Server
|
|||
return PropertiesTryRemove(key) || Extra.Remove(key);
|
||||
}
|
||||
|
||||
bool IDictionary<string, string[]>.TryGetValue(string key, out string[] value)
|
||||
public bool TryGetValue(string key, out string[] value)
|
||||
{
|
||||
return PropertiesTryGetValue(key, out value) || Extra.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
ICollection<string[]> IDictionary<string, string[]>.Values
|
||||
{
|
||||
get { return PropertiesValues().Concat(Extra.Values).ToArray(); }
|
||||
}
|
||||
|
||||
void ICollection<KeyValuePair<string, string[]>>.Add(KeyValuePair<string, string[]> item)
|
||||
{
|
||||
((IDictionary<string, object>)this).Add(item.Key, item.Value);
|
||||
|
|
@ -155,11 +160,6 @@ namespace Microsoft.Net.Server
|
|||
PropertiesEnumerable().Concat(Extra).ToArray().CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
int ICollection<KeyValuePair<string, string[]>>.Count
|
||||
{
|
||||
get { return PropertiesKeys().Count() + Extra.Count; }
|
||||
}
|
||||
|
||||
bool ICollection<KeyValuePair<string, string[]>>.IsReadOnly
|
||||
{
|
||||
get { return false; }
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ namespace Microsoft.Net.Server
|
|||
private static readonly string[] ZeroContentLength = new[] { "0" };
|
||||
|
||||
private ResponseState _responseState;
|
||||
private IDictionary<string, string[]> _headers;
|
||||
private HeaderCollection _headers;
|
||||
private string _reasonPhrase;
|
||||
private ResponseStream _nativeStream;
|
||||
private long _contentLength;
|
||||
|
|
@ -53,7 +53,7 @@ namespace Microsoft.Net.Server
|
|||
// TODO: Verbose log
|
||||
_requestContext = httpContext;
|
||||
_nativeResponse = new UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_V2();
|
||||
_headers = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
|
||||
_headers = new HeaderCollection();
|
||||
_boundaryType = BoundaryType.None;
|
||||
_nativeResponse.Response_V1.StatusCode = (ushort)HttpStatusCode.OK;
|
||||
_nativeResponse.Response_V1.Version.MajorVersion = 1;
|
||||
|
|
@ -156,17 +156,9 @@ namespace Microsoft.Net.Server
|
|||
return true;
|
||||
}
|
||||
|
||||
public IDictionary<string, string[]> Headers
|
||||
public HeaderCollection Headers
|
||||
{
|
||||
get { return _headers; }
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException("value");
|
||||
}
|
||||
_headers = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal long CalculatedLength
|
||||
|
|
@ -214,11 +206,11 @@ namespace Microsoft.Net.Server
|
|||
|
||||
if (value.Value == 0)
|
||||
{
|
||||
Headers[HttpKnownHeaderNames.ContentLength] = ZeroContentLength;
|
||||
((IDictionary<string, string[]>)Headers)[HttpKnownHeaderNames.ContentLength] = ZeroContentLength;
|
||||
}
|
||||
else
|
||||
{
|
||||
Headers[HttpKnownHeaderNames.ContentLength] = new[] { value.Value.ToString(CultureInfo.InvariantCulture) };
|
||||
Headers[HttpKnownHeaderNames.ContentLength] = value.Value.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -239,7 +231,7 @@ namespace Microsoft.Net.Server
|
|||
}
|
||||
else
|
||||
{
|
||||
Headers[HttpKnownHeaderNames.ContentType] = new[] { value };
|
||||
Headers[HttpKnownHeaderNames.ContentType] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -535,7 +527,7 @@ namespace Microsoft.Net.Server
|
|||
else if (endOfRequest)
|
||||
{
|
||||
// The request is ending without a body, add a Content-Length: 0 header.
|
||||
Headers[HttpKnownHeaderNames.ContentLength] = new string[] { "0" };
|
||||
Headers[HttpKnownHeaderNames.ContentLength] = "0";
|
||||
_boundaryType = BoundaryType.ContentLength;
|
||||
_contentLength = 0;
|
||||
flags = UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.NONE;
|
||||
|
|
@ -551,7 +543,7 @@ namespace Microsoft.Net.Server
|
|||
}
|
||||
else
|
||||
{
|
||||
Headers[HttpKnownHeaderNames.TransferEncoding] = new string[] { "chunked" };
|
||||
Headers[HttpKnownHeaderNames.TransferEncoding] = "chunked";
|
||||
_boundaryType = BoundaryType.Chunked;
|
||||
}
|
||||
|
||||
|
|
@ -561,7 +553,7 @@ namespace Microsoft.Net.Server
|
|||
}
|
||||
else
|
||||
{
|
||||
Headers[HttpKnownHeaderNames.ContentLength] = new string[] { "0" };
|
||||
Headers[HttpKnownHeaderNames.ContentLength] = "0";
|
||||
_contentLength = 0;
|
||||
_boundaryType = BoundaryType.ContentLength;
|
||||
}
|
||||
|
|
@ -583,7 +575,7 @@ namespace Microsoft.Net.Server
|
|||
{
|
||||
if (Request.ProtocolVersion.Minor == 0 && !keepAliveSet)
|
||||
{
|
||||
Headers[HttpKnownHeaderNames.KeepAlive] = new string[] { "true" };
|
||||
Headers[HttpKnownHeaderNames.KeepAlive] = "true";
|
||||
}
|
||||
}
|
||||
return flags;
|
||||
|
|
|
|||
|
|
@ -22,10 +22,12 @@
|
|||
//------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.WebSockets;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security.Cryptography;
|
||||
|
|
@ -132,9 +134,9 @@ namespace Microsoft.Net.WebSockets
|
|||
}
|
||||
|
||||
// return value here signifies if a Sec-WebSocket-Protocol header should be returned by the server.
|
||||
public static bool ProcessWebSocketProtocolHeader(string clientSecWebSocketProtocol, string subProtocol)
|
||||
public static bool ProcessWebSocketProtocolHeader(IEnumerable<string> clientSecWebSocketProtocols, string subProtocol)
|
||||
{
|
||||
if (string.IsNullOrEmpty(clientSecWebSocketProtocol))
|
||||
if (clientSecWebSocketProtocols == null || !clientSecWebSocketProtocols.Any())
|
||||
{
|
||||
// client hasn't specified any Sec-WebSocket-Protocol header
|
||||
if (subProtocol != null)
|
||||
|
|
@ -158,14 +160,10 @@ namespace Microsoft.Net.WebSockets
|
|||
// here, we know that the client has specified something, it's not empty
|
||||
// and the server has specified exactly one protocol
|
||||
|
||||
string[] requestProtocols = clientSecWebSocketProtocol.Split(new char[] { ',' },
|
||||
StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
// client specified protocols, serverOptions has exactly 1 non-empty entry. Check that
|
||||
// this exists in the list the client specified.
|
||||
for (int i = 0; i < requestProtocols.Length; i++)
|
||||
foreach (var currentRequestProtocol in clientSecWebSocketProtocols)
|
||||
{
|
||||
string currentRequestProtocol = requestProtocols[i].Trim();
|
||||
if (string.Compare(subProtocol, currentRequestProtocol, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
return true;
|
||||
|
|
@ -174,7 +172,7 @@ namespace Microsoft.Net.WebSockets
|
|||
|
||||
throw new WebSocketException(WebSocketError.UnsupportedProtocol,
|
||||
SR.GetString(SR.net_WebSockets_AcceptUnsupportedProtocol,
|
||||
clientSecWebSocketProtocol,
|
||||
string.Join(", ", clientSecWebSocketProtocols),
|
||||
subProtocol));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ namespace Microsoft.Net.Server
|
|||
byte[] body = Encoding.UTF8.GetBytes("Hello World");
|
||||
context.Response.Body.Write(body, 0, body.Length);
|
||||
|
||||
context.Response.Headers["Upgrade"] = new[] { "WebSocket" }; // Win8.1 blocks anything but WebSocket
|
||||
context.Response.Headers["Upgrade"] = "WebSocket"; // Win8.1 blocks anything but WebSocket
|
||||
Assert.ThrowsAsync<InvalidOperationException>(async () => await context.UpgradeAsync());
|
||||
context.Dispose();
|
||||
HttpResponseMessage response = await clientTask;
|
||||
|
|
@ -44,7 +44,7 @@ namespace Microsoft.Net.Server
|
|||
|
||||
var context = await server.GetContextAsync();
|
||||
Assert.True(context.IsUpgradableRequest);
|
||||
context.Response.Headers["Upgrade"] = new[] { "WebSocket" }; // Win8.1 blocks anything but WebSocket
|
||||
context.Response.Headers["Upgrade"] = "WebSocket"; // Win8.1 blocks anything but WebSocket
|
||||
Stream serverStream = await context.UpgradeAsync();
|
||||
Assert.True(serverStream.CanRead);
|
||||
Assert.True(serverStream.CanWrite);
|
||||
|
|
@ -86,7 +86,7 @@ namespace Microsoft.Net.Server
|
|||
|
||||
var context = await server.GetContextAsync();
|
||||
Assert.True(context.IsUpgradableRequest);
|
||||
context.Response.Headers["Upgrade"] = new[] { "WebSocket" }; // Win8.1 blocks anything but WebSocket
|
||||
context.Response.Headers["Upgrade"] = "WebSocket"; // Win8.1 blocks anything but WebSocket
|
||||
Stream serverStream = await context.UpgradeAsync();
|
||||
Stream clientStream = await clientTask;
|
||||
|
||||
|
|
|
|||
|
|
@ -25,9 +25,11 @@ namespace Microsoft.Net.Server
|
|||
// 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("Keep-Alive", requestHeaders.Get("Connection"));
|
||||
Assert.Equal("localhost:8080", requestHeaders["Host"].First());
|
||||
Assert.Equal("localhost:8080", requestHeaders["Host"]);
|
||||
string[] values;
|
||||
Assert.False(requestHeaders.TryGetValue("Accept", out values));
|
||||
Assert.False(requestHeaders.ContainsKey("Accept"));
|
||||
Assert.Null(requestHeaders["Accept"]);
|
||||
context.Dispose();
|
||||
|
||||
string response = await responseTask;
|
||||
|
|
@ -46,13 +48,15 @@ namespace Microsoft.Net.Server
|
|||
var context = await server.GetContextAsync();
|
||||
var requestHeaders = context.Request.Headers;
|
||||
Assert.Equal(4, requestHeaders.Count);
|
||||
Assert.Equal("localhost:8080", requestHeaders["Host"].First());
|
||||
Assert.Equal("close", requestHeaders["Connection"].First());
|
||||
Assert.Equal(1, requestHeaders["Custom-Header"].Length);
|
||||
Assert.Equal("localhost:8080", requestHeaders["Host"]);
|
||||
Assert.Equal(new[] { "localhost:8080" }, requestHeaders.GetValues("Host"));
|
||||
Assert.Equal("close", requestHeaders["Connection"]);
|
||||
Assert.Equal(new[] { "close" }, requestHeaders.GetValues("Connection"));
|
||||
// Apparently Http.Sys squashes request headers together.
|
||||
Assert.Equal("custom1, and custom2, custom3", requestHeaders["Custom-Header"].First());
|
||||
Assert.Equal(1, requestHeaders["Spacer-Header"].Length);
|
||||
Assert.Equal("spacervalue, spacervalue", requestHeaders["Spacer-Header"].First());
|
||||
Assert.Equal("custom1, and custom2, custom3", requestHeaders["Custom-Header"]);
|
||||
Assert.Equal(new[] { "custom1", "and custom2", "custom3" }, requestHeaders.GetValues("Custom-Header"));
|
||||
Assert.Equal("spacervalue, spacervalue", requestHeaders["Spacer-Header"]);
|
||||
Assert.Equal(new[] { "spacervalue", "spacervalue" }, requestHeaders.GetValues("Spacer-Header"));
|
||||
context.Dispose();
|
||||
|
||||
await responseTask;
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ namespace Microsoft.Net.Server
|
|||
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
|
||||
|
||||
var context = await server.GetContextAsync();
|
||||
context.Request.Headers["transfeR-Encoding"] = new[] { " CHunked " };
|
||||
context.Request.Headers["transfeR-Encoding"] = " CHunked ";
|
||||
Stream stream = context.Response.Body;
|
||||
stream.EndWrite(stream.BeginWrite(new byte[10], 0, 10, null, null));
|
||||
stream.Write(new byte[10], 0, 10);
|
||||
|
|
@ -70,7 +70,7 @@ namespace Microsoft.Net.Server
|
|||
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
|
||||
|
||||
var context = await server.GetContextAsync();
|
||||
context.Response.Headers["Content-lenGth"] = new[] { " 30 " };
|
||||
context.Response.Headers["Content-lenGth"] = " 30 ";
|
||||
Stream stream = context.Response.Body;
|
||||
stream.EndWrite(stream.BeginWrite(new byte[10], 0, 10, null, null));
|
||||
stream.Write(new byte[10], 0, 10);
|
||||
|
|
@ -132,7 +132,7 @@ namespace Microsoft.Net.Server
|
|||
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
|
||||
|
||||
var context = await server.GetContextAsync();
|
||||
context.Response.Headers["Content-lenGth"] = new[] { " 20 " };
|
||||
context.Response.Headers["Content-lenGth"] = " 20 ";
|
||||
context.Response.Body.Write(new byte[5], 0, 5);
|
||||
context.Dispose();
|
||||
|
||||
|
|
@ -148,7 +148,7 @@ namespace Microsoft.Net.Server
|
|||
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
|
||||
|
||||
var context = await server.GetContextAsync();
|
||||
context.Response.Headers["Content-lenGth"] = new[] { " 10 " };
|
||||
context.Response.Headers["Content-lenGth"] = " 10 ";
|
||||
context.Response.Body.Write(new byte[5], 0, 5);
|
||||
Assert.Throws<InvalidOperationException>(() => context.Response.Body.Write(new byte[6], 0, 6));
|
||||
context.Dispose();
|
||||
|
|
@ -165,7 +165,7 @@ namespace Microsoft.Net.Server
|
|||
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
|
||||
|
||||
var context = await server.GetContextAsync();
|
||||
context.Response.Headers["Content-lenGth"] = new[] { " 10 " };
|
||||
context.Response.Headers["Content-lenGth"] = " 10 ";
|
||||
context.Response.Body.Write(new byte[10], 0, 10);
|
||||
Assert.Throws<ObjectDisposedException>(() => context.Response.Body.Write(new byte[6], 0, 6));
|
||||
context.Dispose();
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ namespace Microsoft.Net.Server
|
|||
|
||||
var context = await server.GetContextAsync();
|
||||
var responseHeaders = context.Response.Headers;
|
||||
responseHeaders["WWW-Authenticate"] = new string[] { "custom1" };
|
||||
responseHeaders["WWW-Authenticate"] = "custom1";
|
||||
context.Dispose();
|
||||
|
||||
// HttpClient would merge the headers no matter what
|
||||
|
|
@ -68,7 +68,7 @@ namespace Microsoft.Net.Server
|
|||
|
||||
var context = await server.GetContextAsync();
|
||||
var responseHeaders = context.Response.Headers;
|
||||
responseHeaders["WWW-Authenticate"] = new string[] { "custom1, and custom2", "custom3" };
|
||||
responseHeaders.SetValues("WWW-Authenticate", "custom1, and custom2", "custom3");
|
||||
context.Dispose();
|
||||
|
||||
// HttpClient would merge the headers no matter what
|
||||
|
|
@ -92,7 +92,7 @@ namespace Microsoft.Net.Server
|
|||
|
||||
var context = await server.GetContextAsync();
|
||||
var responseHeaders = context.Response.Headers;
|
||||
responseHeaders["Custom-Header1"] = new string[] { "custom1, and custom2", "custom3" };
|
||||
responseHeaders.SetValues("Custom-Header1", "custom1, and custom2", "custom3");
|
||||
context.Dispose();
|
||||
|
||||
// HttpClient would merge the headers no matter what
|
||||
|
|
@ -115,7 +115,7 @@ namespace Microsoft.Net.Server
|
|||
|
||||
var context = await server.GetContextAsync();
|
||||
var responseHeaders = context.Response.Headers;
|
||||
responseHeaders["Connection"] = new string[] { "Close" };
|
||||
responseHeaders["Connection"] = "Close";
|
||||
context.Dispose();
|
||||
|
||||
HttpResponseMessage response = await responseTask;
|
||||
|
|
@ -198,7 +198,7 @@ namespace Microsoft.Net.Server
|
|||
|
||||
var context = await server.GetContextAsync();
|
||||
var responseHeaders = context.Response.Headers;
|
||||
responseHeaders["Transfer-Encoding"] = new string[] { "chunked" };
|
||||
responseHeaders["Transfer-Encoding"] = "chunked";
|
||||
await context.Response.Body.WriteAsync(new byte[10], 0, 10);
|
||||
context.Dispose();
|
||||
|
||||
|
|
@ -223,8 +223,8 @@ namespace Microsoft.Net.Server
|
|||
var context = await server.GetContextAsync();
|
||||
var responseHeaders = context.Response.Headers;
|
||||
|
||||
responseHeaders.Add("Custom1", new string[] { "value1a", "value1b" });
|
||||
responseHeaders.Add("Custom2", new string[] { "value2a, value2b" });
|
||||
responseHeaders.SetValues("Custom1", "value1a", "value1b");
|
||||
responseHeaders.SetValues("Custom2", "value2a, value2b");
|
||||
var body = context.Response.Body;
|
||||
body.Flush();
|
||||
Assert.Throws<InvalidOperationException>(() => context.Response.StatusCode = 404);
|
||||
|
|
@ -254,12 +254,12 @@ namespace Microsoft.Net.Server
|
|||
var context = await server.GetContextAsync();
|
||||
var responseHeaders = context.Response.Headers;
|
||||
|
||||
responseHeaders.Add("Custom1", new string[] { "value1a", "value1b" });
|
||||
responseHeaders.Add("Custom2", new string[] { "value2a, value2b" });
|
||||
responseHeaders.SetValues("Custom1", "value1a", "value1b");
|
||||
responseHeaders.SetValues("Custom2", "value2a, value2b");
|
||||
var body = context.Response.Body;
|
||||
await body.FlushAsync();
|
||||
Assert.Throws<InvalidOperationException>(() => context.Response.StatusCode = 404);
|
||||
responseHeaders.Add("Custom3", new string[] { "value3a, value3b", "value3c" }); // Ignored
|
||||
responseHeaders.SetValues("Custom3", "value3a, value3b", "value3c"); // Ignored
|
||||
|
||||
context.Dispose();
|
||||
|
||||
|
|
|
|||
|
|
@ -4,9 +4,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
|
@ -91,7 +89,7 @@ namespace Microsoft.Net.Server
|
|||
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
|
||||
|
||||
var context = await server.GetContextAsync();
|
||||
context.Response.Headers["Transfer-EncodinG"] = new[] { "CHUNKED" };
|
||||
context.Response.Headers["Transfer-EncodinG"] = "CHUNKED";
|
||||
await context.Response.SendFileAsync(AbsoluteFilePath, 0, null, CancellationToken.None);
|
||||
context.Dispose();
|
||||
|
||||
|
|
@ -112,7 +110,7 @@ namespace Microsoft.Net.Server
|
|||
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
|
||||
|
||||
var context = await server.GetContextAsync();
|
||||
context.Response.Headers["Transfer-EncodinG"] = new[] { "CHUNKED" };
|
||||
context.Response.Headers["Transfer-EncodinG"] = "CHUNKED";
|
||||
await context.Response.SendFileAsync(AbsoluteFilePath, 0, null, CancellationToken.None);
|
||||
await context.Response.SendFileAsync(AbsoluteFilePath, 0, null, CancellationToken.None);
|
||||
context.Dispose();
|
||||
|
|
@ -206,7 +204,7 @@ namespace Microsoft.Net.Server
|
|||
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
|
||||
|
||||
var context = await server.GetContextAsync();
|
||||
context.Response.Headers["Content-lenGth"] = new[] { FileLength.ToString() };
|
||||
context.Response.Headers["Content-lenGth"] = FileLength.ToString();
|
||||
await context.Response.SendFileAsync(AbsoluteFilePath, 0, null, CancellationToken.None);
|
||||
|
||||
HttpResponseMessage response = await responseTask;
|
||||
|
|
@ -227,7 +225,7 @@ namespace Microsoft.Net.Server
|
|||
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
|
||||
|
||||
var context = await server.GetContextAsync();
|
||||
context.Response.Headers["Content-lenGth"] = new[] { "10" };
|
||||
context.Response.Headers["Content-lenGth"] = "10";
|
||||
await context.Response.SendFileAsync(AbsoluteFilePath, 0, 10, CancellationToken.None);
|
||||
context.Dispose();
|
||||
|
||||
|
|
@ -249,7 +247,7 @@ namespace Microsoft.Net.Server
|
|||
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
|
||||
|
||||
var context = await server.GetContextAsync();
|
||||
context.Response.Headers["Content-lenGth"] = new[] { "0" };
|
||||
context.Response.Headers["Content-lenGth"] = "0";
|
||||
await context.Response.SendFileAsync(AbsoluteFilePath, 0, 0, CancellationToken.None);
|
||||
context.Dispose();
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue