From a3dfa41372f135117d8c02fce452f35920d78b5d Mon Sep 17 00:00:00 2001 From: Chris Ross Date: Tue, 17 Jun 2014 16:11:44 -0700 Subject: [PATCH] Add WebSocket tests. Fix Connection header. --- .../RequestProcessing/Response.cs | 12 +- ...t.Server.WebListener.FunctionalTests.kproj | 4 +- .../WebSocketTests.cs | 160 ++++++++++++++++++ ...Microsoft.Net.Server.FunctionalTests.kproj | 4 +- .../WebSocketTests.cs | 99 +++++++++++ 5 files changed, 271 insertions(+), 8 deletions(-) create mode 100644 test/Microsoft.AspNet.Server.WebListener.FunctionalTests/WebSocketTests.cs create mode 100644 test/Microsoft.Net.Server.FunctionalTests/WebSocketTests.cs diff --git a/src/Microsoft.Net.Server/RequestProcessing/Response.cs b/src/Microsoft.Net.Server/RequestProcessing/Response.cs index 119cec3..22d3d19 100644 --- a/src/Microsoft.Net.Server/RequestProcessing/Response.cs +++ b/src/Microsoft.Net.Server/RequestProcessing/Response.cs @@ -377,7 +377,7 @@ namespace Microsoft.Net.Server */ uint statusCode; uint bytesSent; - List pinnedHeaders = SerializeHeaders(); + List pinnedHeaders = SerializeHeaders(isOpaqueUpgrade); try { if (pDataChunk != null) @@ -589,7 +589,7 @@ namespace Microsoft.Net.Server return flags; } - private List SerializeHeaders() + private List SerializeHeaders(bool isOpaqueUpgrade) { UnsafeNclNativeMethods.HttpApi.HTTP_UNKNOWN_HEADER[] unknownHeaders = null; UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_INFO[] knownHeaderInfo = null; @@ -623,7 +623,9 @@ namespace Microsoft.Net.Server // See if this is an unknown header lookup = UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.IndexOfKnownHeader(headerPair.Key); - if (lookup == -1) + // Http.Sys doesn't let us send the Connection: Upgrade header as a Known header. + if (lookup == -1 || + (isOpaqueUpgrade && lookup == (int)UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.Enum.HttpHeaderConnection)) { numUnknownHeaders += headerPair.Value.Length; } @@ -649,7 +651,9 @@ namespace Microsoft.Net.Server string[] headerValues = headerPair.Value; lookup = UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.IndexOfKnownHeader(headerName); - if (lookup == -1) + // Http.Sys doesn't let us send the Connection: Upgrade header as a Known header. + if (lookup == -1 || + (isOpaqueUpgrade && lookup == (int)UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.Enum.HttpHeaderConnection)) { if (unknownHeaders == null) { diff --git a/test/Microsoft.AspNet.Server.WebListener.FunctionalTests/Microsoft.AspNet.Server.WebListener.FunctionalTests.kproj b/test/Microsoft.AspNet.Server.WebListener.FunctionalTests/Microsoft.AspNet.Server.WebListener.FunctionalTests.kproj index 6f6987b..afd9e54 100644 --- a/test/Microsoft.AspNet.Server.WebListener.FunctionalTests/Microsoft.AspNet.Server.WebListener.FunctionalTests.kproj +++ b/test/Microsoft.AspNet.Server.WebListener.FunctionalTests/Microsoft.AspNet.Server.WebListener.FunctionalTests.kproj @@ -20,6 +20,7 @@ + @@ -36,5 +37,4 @@ - - + \ No newline at end of file diff --git a/test/Microsoft.AspNet.Server.WebListener.FunctionalTests/WebSocketTests.cs b/test/Microsoft.AspNet.Server.WebListener.FunctionalTests/WebSocketTests.cs new file mode 100644 index 0000000..1720bdd --- /dev/null +++ b/test/Microsoft.AspNet.Server.WebListener.FunctionalTests/WebSocketTests.cs @@ -0,0 +1,160 @@ +// 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. + +using System; +using System.Net.Http; +using System.Net.WebSockets; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNet.FeatureModel; +using Microsoft.AspNet.HttpFeature; +using Microsoft.AspNet.PipelineCore; +using Xunit; + +namespace Microsoft.AspNet.Server.WebListener +{ + public class WebSocketTests + { + private const string Address = "http://localhost:8080/"; + private const string WsAddress = "ws://localhost:8080/"; + + [Fact] + public async Task WebSocketTests_SupportKeys_Present() + { + using (Utilities.CreateHttpServer(env => + { + var httpContext = new DefaultHttpContext((IFeatureCollection)env); + try + { + var webSocketFeature = httpContext.GetFeature(); + Assert.NotNull(webSocketFeature); + } + catch (Exception ex) + { + return httpContext.Response.WriteAsync(ex.ToString()); + } + return Task.FromResult(0); + })) + { + HttpResponseMessage response = await SendRequestAsync(Address); + Assert.Equal(200, (int)response.StatusCode); + Assert.False(response.Headers.TransferEncodingChunked.HasValue, "Chunked"); + Assert.Equal(0, response.Content.Headers.ContentLength); + Assert.Equal(string.Empty, response.Content.ReadAsStringAsync().Result); + } + } + + [Fact] + public async Task WebSocketTests_AfterHeadersSent_Throws() + { + bool? upgradeThrew = null; + using (Utilities.CreateHttpServer(async env => + { + var httpContext = new DefaultHttpContext((IFeatureCollection)env); + await httpContext.Response.WriteAsync("Hello World"); + try + { + var webSocketFeature = httpContext.GetFeature(); + Assert.NotNull(webSocketFeature); + await webSocketFeature.AcceptAsync(null); + upgradeThrew = false; + } + catch (InvalidOperationException) + { + upgradeThrew = true; + } + })) + { + HttpResponseMessage response = await SendRequestAsync(Address); + Assert.Equal(200, (int)response.StatusCode); + Assert.True(response.Headers.TransferEncodingChunked.Value, "Chunked"); + Assert.True(upgradeThrew.Value); + } + } + + [Fact] + public async Task WebSocketAccept_Success() + { + ManualResetEvent waitHandle = new ManualResetEvent(false); + bool? upgraded = null; + using (Utilities.CreateHttpServer(async env => + { + var httpContext = new DefaultHttpContext((IFeatureCollection)env); + var webSocketFeature = httpContext.GetFeature(); + Assert.NotNull(webSocketFeature); + Assert.True(webSocketFeature.IsWebSocketRequest); + await webSocketFeature.AcceptAsync(null); + upgraded = true; + waitHandle.Set(); + })) + { + using (WebSocket clientWebSocket = await SendWebSocketRequestAsync(WsAddress)) + { + Assert.True(waitHandle.WaitOne(TimeSpan.FromSeconds(1)), "Timed out"); + Assert.True(upgraded.HasValue, "Upgraded not set"); + Assert.True(upgraded.Value, "Upgrade failed"); + } + } + } + + [Fact] + public async Task WebSocketAccept_SendAndReceive_Success() + { + byte[] clientBuffer = new byte[] { 0x00, 0x01, 0xFF, 0x00, 0x00 }; + using (Utilities.CreateHttpServer(async env => + { + var httpContext = new DefaultHttpContext((IFeatureCollection)env); + var webSocketFeature = httpContext.GetFeature(); + Assert.NotNull(webSocketFeature); + Assert.True(webSocketFeature.IsWebSocketRequest); + var serverWebSocket = await webSocketFeature.AcceptAsync(null); + + byte[] serverBuffer = new byte[clientBuffer.Length]; + var result = await serverWebSocket.ReceiveAsync(new ArraySegment(serverBuffer, 0, serverBuffer.Length), CancellationToken.None); + Assert.Equal(clientBuffer, serverBuffer); + + await serverWebSocket.SendAsync(new ArraySegment(serverBuffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None); + + })) + { + using (WebSocket clientWebSocket = await SendWebSocketRequestAsync(WsAddress)) + { + await clientWebSocket.SendAsync(new ArraySegment(clientBuffer, 0, 3), WebSocketMessageType.Binary, true, CancellationToken.None); + + byte[] clientEchoBuffer = new byte[clientBuffer.Length]; + var result = await clientWebSocket.ReceiveAsync(new ArraySegment(clientEchoBuffer), CancellationToken.None); + Assert.Equal(clientBuffer, clientEchoBuffer); + } + } + } + + private async Task SendRequestAsync(string uri) + { + using (HttpClient client = new HttpClient()) + { + return await client.GetAsync(uri); + } + } + + private async Task SendWebSocketRequestAsync(string address) + { + ClientWebSocket client = new ClientWebSocket(); + await client.ConnectAsync(new Uri(address), CancellationToken.None); + return client; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.Net.Server.FunctionalTests/Microsoft.Net.Server.FunctionalTests.kproj b/test/Microsoft.Net.Server.FunctionalTests/Microsoft.Net.Server.FunctionalTests.kproj index 3de1aac..228bd0a 100644 --- a/test/Microsoft.Net.Server.FunctionalTests/Microsoft.Net.Server.FunctionalTests.kproj +++ b/test/Microsoft.Net.Server.FunctionalTests/Microsoft.Net.Server.FunctionalTests.kproj @@ -20,6 +20,7 @@ + @@ -36,5 +37,4 @@ - - + \ No newline at end of file diff --git a/test/Microsoft.Net.Server.FunctionalTests/WebSocketTests.cs b/test/Microsoft.Net.Server.FunctionalTests/WebSocketTests.cs new file mode 100644 index 0000000..946aabe --- /dev/null +++ b/test/Microsoft.Net.Server.FunctionalTests/WebSocketTests.cs @@ -0,0 +1,99 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Net.Http; +using System.Net.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.Net.Server +{ + public class WebSocketTests + { + private const string Address = "http://localhost:8080/"; + private const string WsAddress = "ws://localhost:8080/"; + + [Fact] + public async Task WebSocketAccept_AfterHeadersSent_Throws() + { + using (var server = Utilities.CreateHttpServer()) + { + Task clientTask = SendRequestAsync(Address); + + var context = await server.GetContextAsync(); + byte[] body = Encoding.UTF8.GetBytes("Hello World"); + context.Response.Body.Write(body, 0, body.Length); + + Assert.ThrowsAsync(async () => await context.AcceptWebSocketAsync()); + context.Dispose(); + HttpResponseMessage response = await clientTask; + Assert.Equal(200, (int)response.StatusCode); + Assert.True(response.Headers.TransferEncodingChunked.Value, "Chunked"); + Assert.Equal("Hello World", await response.Content.ReadAsStringAsync()); + } + } + + [Fact] + public async Task WebSocketAccept_Success() + { + using (var server = Utilities.CreateHttpServer()) + { + Task clientTask = SendWebSocketRequestAsync(WsAddress); + + var context = await server.GetContextAsync(); + Assert.True(context.IsUpgradableRequest); + WebSocket serverWebSocket = await context.AcceptWebSocketAsync(); + WebSocket clientWebSocket = await clientTask; + serverWebSocket.Dispose(); + clientWebSocket.Dispose(); + } + } + + [Fact] + public async Task WebSocketAccept_SendAndReceive_Success() + { + using (var server = Utilities.CreateHttpServer()) + { + Task clientTask = SendWebSocketRequestAsync(WsAddress); + + var context = await server.GetContextAsync(); + Assert.True(context.IsWebSocketRequest); + WebSocket serverWebSocket = await context.AcceptWebSocketAsync(); + WebSocket clientWebSocket = await clientTask; + + byte[] clientBuffer = new byte[] { 0x00, 0x01, 0xFF, 0x00, 0x00 }; + await clientWebSocket.SendAsync(new ArraySegment(clientBuffer, 0, 3), WebSocketMessageType.Binary, true, CancellationToken.None); + + byte[] serverBuffer = new byte[clientBuffer.Length]; + var result = await serverWebSocket.ReceiveAsync(new ArraySegment(serverBuffer, 0, serverBuffer.Length), CancellationToken.None); + Assert.Equal(clientBuffer, serverBuffer); + + await serverWebSocket.SendAsync(new ArraySegment(serverBuffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None); + + byte[] clientEchoBuffer = new byte[clientBuffer.Length]; + result = await clientWebSocket.ReceiveAsync(new ArraySegment(clientEchoBuffer), CancellationToken.None); + Assert.Equal(clientBuffer, clientEchoBuffer); + + serverWebSocket.Dispose(); + clientWebSocket.Dispose(); + } + } + + private async Task SendRequestAsync(string uri) + { + using (HttpClient client = new HttpClient()) + { + return await client.GetAsync(uri); + } + } + + private async Task SendWebSocketRequestAsync(string address) + { + ClientWebSocket client = new ClientWebSocket(); + await client.ConnectAsync(new Uri(address), CancellationToken.None); + return client; + } + } +} \ No newline at end of file