Copy the tests for the lower level API.

This commit is contained in:
Chris Ross 2014-05-01 17:28:32 -07:00
parent 9c5253a415
commit 72e14ebd6f
18 changed files with 2308 additions and 3 deletions

View File

@ -25,6 +25,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Server.Web
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Server.WebListener", "src\Microsoft.AspNet.Server.WebListener\Microsoft.AspNet.Server.WebListener.kproj", "{B9F45F9D-D206-47F0-8E5F-54CE2F0BDF92}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Net.Server.FunctionalTests", "test\Microsoft.Net.Server.FunctionalTests\Microsoft.Net.Server.FunctionalTests.kproj", "{DCB6E0B1-223D-44E6-8696-4767E5B6E6A1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -115,6 +117,16 @@ Global
{B9F45F9D-D206-47F0-8E5F-54CE2F0BDF92}.Release|Mixed Platforms.Build.0 = Release|x86
{B9F45F9D-D206-47F0-8E5F-54CE2F0BDF92}.Release|x86.ActiveCfg = Release|x86
{B9F45F9D-D206-47F0-8E5F-54CE2F0BDF92}.Release|x86.Build.0 = Release|x86
{DCB6E0B1-223D-44E6-8696-4767E5B6E6A1}.Debug|Any CPU.ActiveCfg = Debug|x86
{DCB6E0B1-223D-44E6-8696-4767E5B6E6A1}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
{DCB6E0B1-223D-44E6-8696-4767E5B6E6A1}.Debug|Mixed Platforms.Build.0 = Debug|x86
{DCB6E0B1-223D-44E6-8696-4767E5B6E6A1}.Debug|x86.ActiveCfg = Debug|x86
{DCB6E0B1-223D-44E6-8696-4767E5B6E6A1}.Debug|x86.Build.0 = Debug|x86
{DCB6E0B1-223D-44E6-8696-4767E5B6E6A1}.Release|Any CPU.ActiveCfg = Release|x86
{DCB6E0B1-223D-44E6-8696-4767E5B6E6A1}.Release|Mixed Platforms.ActiveCfg = Release|x86
{DCB6E0B1-223D-44E6-8696-4767E5B6E6A1}.Release|Mixed Platforms.Build.0 = Release|x86
{DCB6E0B1-223D-44E6-8696-4767E5B6E6A1}.Release|x86.ActiveCfg = Release|x86
{DCB6E0B1-223D-44E6-8696-4767E5B6E6A1}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -128,5 +140,6 @@ Global
{E788AEAE-2CB4-4BFA-8746-D0BB7E93A1BB} = {99D5E5F3-88F5-4CCF-8D8C-717C8925DF09}
{4492FF4C-9032-411D-853F-46B01755E504} = {E183C826-1360-4DFF-9994-F33CED5C8525}
{B9F45F9D-D206-47F0-8E5F-54CE2F0BDF92} = {99D5E5F3-88F5-4CCF-8D8C-717C8925DF09}
{DCB6E0B1-223D-44E6-8696-4767E5B6E6A1} = {E183C826-1360-4DFF-9994-F33CED5C8525}
EndGlobalSection
EndGlobal

View File

@ -56,7 +56,7 @@ namespace Microsoft.Net.Server {
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.AspNet.Server.WebListener.Resources", System.Reflection.IntrospectionExtensions.GetTypeInfo(typeof(Resources)).Assembly);
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Net.Server.Resources", System.Reflection.IntrospectionExtensions.GetTypeInfo(typeof(Resources)).Assembly);
resourceMan = temp;
}
return resourceMan;

View File

@ -120,7 +120,7 @@ namespace Microsoft.AspNet.Server.WebListener
}
*/
[Fact]
public void ResponseBody_WriteContentLengthNoneWritten_Throws()
public async Task ResponseBody_WriteContentLengthNoneWritten_Throws()
{
using (Utilities.CreateHttpServer(env =>
{
@ -129,7 +129,7 @@ namespace Microsoft.AspNet.Server.WebListener
return Task.FromResult(0);
}))
{
Assert.Throws<AggregateException>(() => SendRequestAsync(Address).Result);
await Assert.ThrowsAsync<HttpRequestException>(() => SendRequestAsync(Address));
}
}

View File

@ -0,0 +1,119 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.Net.Server
{
public class AuthenticationTests
{
private const string Address = "http://localhost:8080/";
[Theory]
[InlineData(AuthenticationType.Kerberos)]
[InlineData(AuthenticationType.Negotiate)]
[InlineData(AuthenticationType.Ntlm)]
[InlineData(AuthenticationType.Digest)]
[InlineData(AuthenticationType.Basic)]
[InlineData(AuthenticationType.Kerberos | AuthenticationType.Negotiate | AuthenticationType.Ntlm | AuthenticationType.Digest | AuthenticationType.Basic)]
public async Task AuthTypes_EnabledButNotChalleneged_PassThrough(AuthenticationType authType)
{
using (var server = Utilities.CreateAuthServer(authType))
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
context.Dispose();
var response = await responseTask;
response.EnsureSuccessStatusCode();
}
}
[Theory]
[InlineData(AuthenticationType.Kerberos)]
[InlineData(AuthenticationType.Negotiate)]
[InlineData(AuthenticationType.Ntlm)]
// [InlineData(AuthenticationType.Digest)] // TODO: Not implemented
[InlineData(AuthenticationType.Basic)]
public async Task AuthType_Specify401_ChallengesAdded(AuthenticationType authType)
{
using (var server = Utilities.CreateAuthServer(authType))
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
context.Response.StatusCode = 401;
context.Dispose();
var response = await responseTask;
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
Assert.Equal(authType.ToString(), response.Headers.WwwAuthenticate.ToString(), StringComparer.OrdinalIgnoreCase);
}
}
[Fact]
public async Task MultipleAuthTypes_Specify401_ChallengesAdded()
{
using (var server = Utilities.CreateAuthServer(
AuthenticationType.Kerberos
| AuthenticationType.Negotiate
| AuthenticationType.Ntlm
/* | AuthenticationType.Digest TODO: Not implemented */
| AuthenticationType.Basic))
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
context.Response.StatusCode = 401;
context.Dispose();
var response = await responseTask;
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
Assert.Equal("Kerberos, Negotiate, NTLM, basic", response.Headers.WwwAuthenticate.ToString(), StringComparer.OrdinalIgnoreCase);
}
}
/* TODO: User
[Theory]
[InlineData(AuthenticationType.Kerberos)]
[InlineData(AuthenticationType.Negotiate)]
[InlineData(AuthenticationType.Ntlm)]
// [InlineData(AuthenticationType.Digest)] // TODO: Not implemented
// [InlineData(AuthenticationType.Basic)] // Doesn't work with default creds
[InlineData(AuthenticationType.Kerberos | AuthenticationType.Negotiate | AuthenticationType.Ntlm | / *AuthenticationType.Digest |* / AuthenticationType.Basic)]
public async Task AuthTypes_Login_Success(AuthenticationType authType)
{
int requestCount = 0;
using (Utilities.CreateAuthServer(authType, env =>
{
requestCount++;
/ * // TODO: Expose user as feature.
object obj;
if (env.TryGetValue("server.User", out obj) && obj != null)
{
return Task.FromResult(0);
}* /
new DefaultHttpContext((IFeatureCollection)env).Response.StatusCode = 401;
return Task.FromResult(0);
}))
{
var response = await SendRequestAsync(Address, useDefaultCredentials: true);
response.EnsureSuccessStatusCode();
}
}
*/
private async Task<HttpResponseMessage> SendRequestAsync(string uri, bool useDefaultCredentials = false)
{
HttpClientHandler handler = new HttpClientHandler();
handler.UseDefaultCredentials = useDefaultCredentials;
using (HttpClient client = new HttpClient(handler))
{
return await client.GetAsync(uri);
}
}
}
}

View File

@ -0,0 +1,170 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.IO;
using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.Net.Server
{
public class HttpsTests
{
private const string Address = "https://localhost:9090/";
[Fact(Skip = "TODO: Add trait filtering support so these SSL tests don't get run on teamcity or the command line."), Trait("scheme", "https")]
public async Task Https_200OK_Success()
{
using (var server = Utilities.CreateHttpsServer())
{
Task<string> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
context.Dispose();
string response = await responseTask;
Assert.Equal(string.Empty, response);
}
}
[Fact(Skip = "TODO: Add trait filtering support so these SSL tests don't get run on teamcity or the command line."), Trait("scheme", "https")]
public async Task Https_SendHelloWorld_Success()
{
using (var server = Utilities.CreateHttpsServer())
{
Task<string> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
byte[] body = Encoding.UTF8.GetBytes("Hello World");
context.Response.ContentLength = body.Length;
await context.Response.Body.WriteAsync(body, 0, body.Length);
context.Dispose();
string response = await responseTask;
Assert.Equal("Hello World", response);
}
}
[Fact(Skip = "TODO: Add trait filtering support so these SSL tests don't get run on teamcity or the command line."), Trait("scheme", "https")]
public async Task Https_EchoHelloWorld_Success()
{
using (var server = Utilities.CreateHttpsServer())
{
Task<string> responseTask = SendRequestAsync(Address, "Hello World");
var context = await server.GetContextAsync();
string input = new StreamReader(context.Request.Body).ReadToEnd();
Assert.Equal("Hello World", input);
context.Response.ContentLength = 11;
using (var writer = new StreamWriter(context.Response.Body))
{
writer.Write("Hello World");
}
string response = await responseTask;
Assert.Equal("Hello World", response);
}
}
[Fact(Skip = "TODO: Add trait filtering support so these SSL tests don't get run on teamcity or the command line."), Trait("scheme", "https")]
public async Task Https_ClientCertNotSent_ClientCertNotPresent()
{
using (var server = Utilities.CreateHttpsServer())
{
Task<string> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
var cert = await context.Request.GetClientCertificateAsync();
Assert.Null(cert);
context.Dispose();
string response = await responseTask;
Assert.Equal(string.Empty, response);
}
}
[Fact(Skip = "TODO: Add trait filtering support so these SSL tests don't get run on teamcity or the command line."), Trait("scheme", "https")]
public async Task Https_ClientCertRequested_ClientCertPresent()
{
using (var server = Utilities.CreateHttpsServer())
{
Task<string> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
var cert = await context.Request.GetClientCertificateAsync();
Assert.NotNull(cert);
context.Dispose();
X509Certificate2 clientCert = FindClientCert();
Assert.NotNull(clientCert);
string response = await SendRequestAsync(Address, clientCert);
Assert.Equal(string.Empty, response);
}
}
private async Task<string> SendRequestAsync(string uri,
X509Certificate cert = null)
{
WebRequestHandler handler = new WebRequestHandler();
handler.ServerCertificateValidationCallback = (a, b, c, d) => true;
if (cert != null)
{
handler.ClientCertificates.Add(cert);
}
using (HttpClient client = new HttpClient(handler))
{
return await client.GetStringAsync(uri);
}
}
private async Task<string> SendRequestAsync(string uri, string upload)
{
WebRequestHandler handler = new WebRequestHandler();
handler.ServerCertificateValidationCallback = (a, b, c, d) => true;
using (HttpClient client = new HttpClient(handler))
{
HttpResponseMessage response = await client.PostAsync(uri, new StringContent(upload));
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
private X509Certificate2 FindClientCert()
{
var store = new X509Store();
store.Open(OpenFlags.ReadOnly);
foreach (var cert in store.Certificates)
{
bool isClientAuth = false;
bool isSmartCard = false;
foreach (var extension in cert.Extensions)
{
var eku = extension as X509EnhancedKeyUsageExtension;
if (eku != null)
{
foreach (var oid in eku.EnhancedKeyUsages)
{
if (oid.FriendlyName == "Client Authentication")
{
isClientAuth = true;
}
else if (oid.FriendlyName == "Smart Card Logon")
{
isSmartCard = true;
break;
}
}
}
}
if (isClientAuth && !isSmartCard)
{
return cert;
}
}
return null;
}
}
}

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">12.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\ProjectK\Microsoft.Web.ProjectK.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>dcb6e0b1-223d-44e6-8696-4767e5b6e6a1</ProjectGuid>
<OutputType>Library</OutputType>
<ActiveTargetFramework>net45</ActiveTargetFramework>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'" Label="Configuration">
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'" Label="Configuration">
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<ItemGroup>
<Compile Include="AuthenticationTests.cs" />
<Compile Include="HttpsTests.cs" />
<Compile Include="OpaqueUpgradeTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RequestBodyTests.cs" />
<Compile Include="RequestHeaderTests.cs" />
<Compile Include="RequestTests.cs" />
<Compile Include="ResponseBodyTests.cs" />
<Compile Include="ResponseHeaderTests.cs" />
<Compile Include="ResponseSendFileTests.cs" />
<Compile Include="ResponseTests.cs" />
<Compile Include="ServerTests.cs" />
<Compile Include="Utilities.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="Project.json" />
</ItemGroup>
<Import Project="$(VSToolsPath)\ProjectK\Microsoft.Web.ProjectK.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1,322 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
/* TODO: Opaque
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
using Xunit.Extensions;
namespace Microsoft.Net.Server
{
using AppFunc = Func<object, Task>;
using OpaqueUpgrade = Action<IDictionary<string, object>, Func<IDictionary<string, object>, Task>>;
public class OpaqueUpgradeTests
{
private const string Address = "http://localhost:8080/";
[Fact]
public async Task OpaqueUpgrade_SupportKeys_Present()
{
using (CreateServer(env =>
{
try
{
IDictionary<string, object> capabilities = env.Get<IDictionary<string, object>>("server.Capabilities");
Assert.NotNull(capabilities);
Assert.Equal("1.0", capabilities.Get<string>("opaque.Version"));
OpaqueUpgrade opaqueUpgrade = env.Get<OpaqueUpgrade>("opaque.Upgrade");
Assert.NotNull(opaqueUpgrade);
}
catch (Exception ex)
{
byte[] body = Encoding.UTF8.GetBytes(ex.ToString());
env.Get<Stream>("owin.ResponseBody").Write(body, 0, body.Length);
}
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 OpaqueUpgrade_NullCallback_Throws()
{
using (CreateServer(env =>
{
try
{
OpaqueUpgrade opaqueUpgrade = env.Get<OpaqueUpgrade>("opaque.Upgrade");
opaqueUpgrade(new Dictionary<string, object>(), null);
}
catch (Exception ex)
{
byte[] body = Encoding.UTF8.GetBytes(ex.ToString());
env.Get<Stream>("owin.ResponseBody").Write(body, 0, body.Length);
}
return Task.FromResult(0);
}))
{
HttpResponseMessage response = await SendRequestAsync(Address);
Assert.Equal(200, (int)response.StatusCode);
Assert.True(response.Headers.TransferEncodingChunked.Value, "Chunked");
Assert.Contains("callback", response.Content.ReadAsStringAsync().Result);
}
}
[Fact]
public async Task OpaqueUpgrade_AfterHeadersSent_Throws()
{
bool? upgradeThrew = null;
using (CreateServer(env =>
{
byte[] body = Encoding.UTF8.GetBytes("Hello World");
env.Get<Stream>("owin.ResponseBody").Write(body, 0, body.Length);
OpaqueUpgrade opaqueUpgrade = env.Get<OpaqueUpgrade>("opaque.Upgrade");
try
{
opaqueUpgrade(null, _ => Task.FromResult(0));
upgradeThrew = false;
}
catch (InvalidOperationException)
{
upgradeThrew = true;
}
return Task.FromResult(0);
}))
{
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 OpaqueUpgrade_GetUpgrade_Success()
{
ManualResetEvent waitHandle = new ManualResetEvent(false);
bool? callbackInvoked = null;
using (CreateServer(env =>
{
var responseHeaders = env.Get<IDictionary<string, string[]>>("owin.ResponseHeaders");
responseHeaders["Upgrade"] = new string[] { "websocket" }; // Win8.1 blocks anything but WebSockets
OpaqueUpgrade opaqueUpgrade = env.Get<OpaqueUpgrade>("opaque.Upgrade");
opaqueUpgrade(null, opqEnv =>
{
callbackInvoked = true;
waitHandle.Set();
return Task.FromResult(0);
});
return Task.FromResult(0);
}))
{
using (Stream stream = await SendOpaqueRequestAsync("GET", Address))
{
Assert.True(waitHandle.WaitOne(TimeSpan.FromSeconds(1)), "Timed out");
Assert.True(callbackInvoked.HasValue, "CallbackInvoked not set");
Assert.True(callbackInvoked.Value, "Callback not invoked");
}
}
}
[Theory]
// See HTTP_VERB for known verbs
[InlineData("UNKNOWN", null)]
[InlineData("INVALID", null)]
[InlineData("OPTIONS", null)]
[InlineData("GET", null)]
[InlineData("HEAD", null)]
[InlineData("DELETE", null)]
[InlineData("TRACE", null)]
[InlineData("CONNECT", null)]
[InlineData("TRACK", null)]
[InlineData("MOVE", null)]
[InlineData("COPY", null)]
[InlineData("PROPFIND", null)]
[InlineData("PROPPATCH", null)]
[InlineData("MKCOL", null)]
[InlineData("LOCK", null)]
[InlineData("UNLOCK", null)]
[InlineData("SEARCH", null)]
[InlineData("CUSTOMVERB", null)]
[InlineData("PATCH", null)]
[InlineData("POST", "Content-Length: 0")]
[InlineData("PUT", "Content-Length: 0")]
public async Task OpaqueUpgrade_VariousMethodsUpgradeSendAndReceive_Success(string method, string extraHeader)
{
using (CreateServer(env =>
{
var responseHeaders = env.Get<IDictionary<string, string[]>>("owin.ResponseHeaders");
responseHeaders["Upgrade"] = new string[] { "WebSocket" }; // Win8.1 blocks anything but WebSockets
OpaqueUpgrade opaqueUpgrade = env.Get<OpaqueUpgrade>("opaque.Upgrade");
opaqueUpgrade(null, async opqEnv =>
{
Stream opaqueStream = opqEnv.Get<Stream>("opaque.Stream");
byte[] buffer = new byte[100];
int read = await opaqueStream.ReadAsync(buffer, 0, buffer.Length);
await opaqueStream.WriteAsync(buffer, 0, read);
});
return Task.FromResult(0);
}))
{
using (Stream stream = await SendOpaqueRequestAsync(method, Address, extraHeader))
{
byte[] data = new byte[100];
stream.WriteAsync(data, 0, 49).Wait();
int read = stream.ReadAsync(data, 0, data.Length).Result;
Assert.Equal(49, read);
}
}
}
[Theory]
// Http.Sys returns a 411 Length Required if PUT or POST does not specify content-length or chunked.
[InlineData("POST", "Content-Length: 10")]
[InlineData("POST", "Transfer-Encoding: chunked")]
[InlineData("PUT", "Content-Length: 10")]
[InlineData("PUT", "Transfer-Encoding: chunked")]
[InlineData("CUSTOMVERB", "Content-Length: 10")]
[InlineData("CUSTOMVERB", "Transfer-Encoding: chunked")]
public void OpaqueUpgrade_InvalidMethodUpgrade_Disconnected(string method, string extraHeader)
{
OpaqueUpgrade opaqueUpgrade = null;
using (CreateServer(env =>
{
opaqueUpgrade = env.Get<OpaqueUpgrade>("opaque.Upgrade");
if (opaqueUpgrade == null)
{
throw new NotImplementedException();
}
opaqueUpgrade(null, opqEnv => Task.FromResult(0));
return Task.FromResult(0);
}))
{
Assert.Throws<InvalidOperationException>(() =>
{
try
{
return SendOpaqueRequestAsync(method, Address, extraHeader).Result;
}
catch (AggregateException ag)
{
throw ag.GetBaseException();
}
});
Assert.Null(opaqueUpgrade);
}
}
private IDisposable CreateServer(AppFunc app)
{
IDictionary<string, object> properties = new Dictionary<string, object>();
IList<IDictionary<string, object>> addresses = new List<IDictionary<string, object>>();
properties["host.Addresses"] = addresses;
IDictionary<string, object> address = new Dictionary<string, object>();
addresses.Add(address);
address["scheme"] = "http";
address["host"] = "localhost";
address["port"] = "8080";
address["path"] = string.Empty;
OwinServerFactory.Initialize(properties);
return OwinServerFactory.Create(app, properties);
}
private async Task<HttpResponseMessage> SendRequestAsync(string uri)
{
using (HttpClient client = new HttpClient())
{
return await client.GetAsync(uri);
}
}
// Returns a bidirectional opaque stream or throws if the upgrade fails
private async Task<Stream> SendOpaqueRequestAsync(string method, string address, string extraHeader = null)
{
// Connect with a socket
Uri uri = new Uri(address);
TcpClient client = new TcpClient();
try
{
await client.ConnectAsync(uri.Host, uri.Port);
NetworkStream stream = client.GetStream();
// Send an HTTP GET request
byte[] requestBytes = BuildGetRequest(method, uri, extraHeader);
await stream.WriteAsync(requestBytes, 0, requestBytes.Length);
// Read the response headers, fail if it's not a 101
await ParseResponseAsync(stream);
// Return the opaque network stream
return stream;
}
catch (Exception)
{
client.Close();
throw;
}
}
private byte[] BuildGetRequest(string method, Uri uri, string extraHeader)
{
StringBuilder builder = new StringBuilder();
builder.Append(method);
builder.Append(" ");
builder.Append(uri.PathAndQuery);
builder.Append(" HTTP/1.1");
builder.AppendLine();
builder.Append("Host: ");
builder.Append(uri.Host);
builder.Append(':');
builder.Append(uri.Port);
builder.AppendLine();
if (!string.IsNullOrEmpty(extraHeader))
{
builder.AppendLine(extraHeader);
}
builder.AppendLine();
return Encoding.ASCII.GetBytes(builder.ToString());
}
// Read the response headers, fail if it's not a 101
private async Task ParseResponseAsync(NetworkStream stream)
{
StreamReader reader = new StreamReader(stream);
string statusLine = await reader.ReadLineAsync();
string[] parts = statusLine.Split(' ');
if (int.Parse(parts[1]) != 101)
{
throw new InvalidOperationException("The response status code was incorrect: " + statusLine);
}
// Scan to the end of the headers
while (!string.IsNullOrEmpty(reader.ReadLine()))
{
}
}
}
}
*/

View File

@ -0,0 +1,24 @@
{
"version" : "0.1-alpha-*",
"commands": {
"test": "Xunit.KRunner"
},
"dependencies": {
"Xunit.KRunner": "0.1-alpha-*",
"xunit.abstractions": "2.0.0-aspnet-*",
"xunit.assert": "2.0.0-aspnet-*",
"xunit.core": "2.0.0-aspnet-*",
"xunit.execution": "2.0.0-aspnet-*",
"Microsoft.Net.Server" : "",
"Microsoft.AspNet.Logging": "0.1-alpha-*"
},
"configurations": {
"net45": {
"dependencies": {
"System.Runtime": "",
"System.Net.Http": "",
"System.Net.Http.WebRequest": ""
}
}
}
}

View File

@ -0,0 +1,8 @@
// -----------------------------------------------------------------------
// <copyright file="AssemblyInfo.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------
// These tests can't run in parallel because they all use the same port.
[assembly: Xunit.CollectionBehaviorAttribute(Xunit.CollectionBehavior.CollectionPerAssembly, DisableTestParallelization = true)]

View File

@ -0,0 +1,249 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.Net.Server
{
public class RequestBodyTests
{
private const string Address = "http://localhost:8080/";
[Fact]
public async Task RequestBody_ReadSync_Success()
{
using (var server = Utilities.CreateHttpServer())
{
Task<string> responseTask = SendRequestAsync(Address, "Hello World");
var context = await server.GetContextAsync();
byte[] input = new byte[100];
int read = context.Request.Body.Read(input, 0, input.Length);
context.Response.ContentLength = read;
context.Response.Body.Write(input, 0, read);
string response = await responseTask;
Assert.Equal("Hello World", response);
}
}
[Fact]
public async Task RequestBody_ReadAync_Success()
{
using (var server = Utilities.CreateHttpServer())
{
Task<string> responseTask = SendRequestAsync(Address, "Hello World");
var context = await server.GetContextAsync();
byte[] input = new byte[100];
int read = await context.Request.Body.ReadAsync(input, 0, input.Length);
context.Response.ContentLength = read;
await context.Response.Body.WriteAsync(input, 0, read);
string response = await responseTask;
Assert.Equal("Hello World", response);
}
}
#if NET45
[Fact]
public async Task RequestBody_ReadBeginEnd_Success()
{
using (var server = Utilities.CreateHttpServer())
{
Task<string> responseTask = SendRequestAsync(Address, "Hello World");
var context = await server.GetContextAsync();
byte[] input = new byte[100];
int read = context.Request.Body.EndRead(context.Request.Body.BeginRead(input, 0, input.Length, null, null));
context.Response.ContentLength = read;
context.Response.Body.EndWrite(context.Response.Body.BeginWrite(input, 0, read, null, null));
string response = await responseTask;
Assert.Equal("Hello World", response);
}
}
#endif
[Fact]
public async Task RequestBody_InvalidBuffer_ArgumentException()
{
using (var server = Utilities.CreateHttpServer())
{
Task<string> responseTask = SendRequestAsync(Address, "Hello World");
var context = await server.GetContextAsync();
byte[] input = new byte[100];
Assert.Throws<ArgumentNullException>("buffer", () => context.Request.Body.Read(null, 0, 1));
Assert.Throws<ArgumentOutOfRangeException>("offset", () => context.Request.Body.Read(input, -1, 1));
Assert.Throws<ArgumentOutOfRangeException>("offset", () => context.Request.Body.Read(input, input.Length + 1, 1));
Assert.Throws<ArgumentOutOfRangeException>("size", () => context.Request.Body.Read(input, 10, -1));
Assert.Throws<ArgumentOutOfRangeException>("size", () => context.Request.Body.Read(input, 0, 0));
Assert.Throws<ArgumentOutOfRangeException>("size", () => context.Request.Body.Read(input, 1, input.Length));
Assert.Throws<ArgumentOutOfRangeException>("size", () => context.Request.Body.Read(input, 0, input.Length + 1));
context.Dispose();
string response = await responseTask;
Assert.Equal(string.Empty, response);
}
}
[Fact]
public async Task RequestBody_ReadSyncPartialBody_Success()
{
StaggardContent content = new StaggardContent();
using (var server = Utilities.CreateHttpServer())
{
Task<string> responseTask = SendRequestAsync(Address, content);
var context = await server.GetContextAsync();
byte[] input = new byte[10];
int read = context.Request.Body.Read(input, 0, input.Length);
Assert.Equal(5, read);
content.Block.Release();
read = context.Request.Body.Read(input, 0, input.Length);
Assert.Equal(5, read);
context.Dispose();
string response = await responseTask;
Assert.Equal(string.Empty, response);
}
}
[Fact]
public async Task RequestBody_ReadAsyncPartialBody_Success()
{
StaggardContent content = new StaggardContent();
using (var server = Utilities.CreateHttpServer())
{
Task<string> responseTask = SendRequestAsync(Address, content);
var context = await server.GetContextAsync();
byte[] input = new byte[10];
int read = await context.Request.Body.ReadAsync(input, 0, input.Length);
Assert.Equal(5, read);
content.Block.Release();
read = await context.Request.Body.ReadAsync(input, 0, input.Length);
Assert.Equal(5, read);
context.Dispose();
string response = await responseTask;
Assert.Equal(string.Empty, response);
}
}
[Fact]
public async Task RequestBody_PostWithImidateBody_Success()
{
using (var server = Utilities.CreateHttpServer())
{
Task<string> responseTask = SendSocketRequestAsync(Address);
var context = await server.GetContextAsync();
byte[] input = new byte[11];
int read = await context.Request.Body.ReadAsync(input, 0, input.Length);
Assert.Equal(10, read);
read = await context.Request.Body.ReadAsync(input, 0, input.Length);
Assert.Equal(0, read);
context.Response.ContentLength = 10;
await context.Response.Body.WriteAsync(input, 0, 10);
context.Dispose();
string response = await responseTask;
string[] lines = response.Split('\r', '\n');
Assert.Equal(13, lines.Length);
Assert.Equal("HTTP/1.1 200 OK", lines[0]);
Assert.Equal("0123456789", lines[12]);
}
}
private Task<string> SendRequestAsync(string uri, string upload)
{
return SendRequestAsync(uri, new StringContent(upload));
}
private async Task<string> SendRequestAsync(string uri, HttpContent content)
{
using (HttpClient client = new HttpClient())
{
HttpResponseMessage response = await client.PostAsync(uri, content);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
private async Task<string> SendSocketRequestAsync(string address)
{
// Connect with a socket
Uri uri = new Uri(address);
TcpClient client = new TcpClient();
try
{
await client.ConnectAsync(uri.Host, uri.Port);
NetworkStream stream = client.GetStream();
// Send an HTTP GET request
byte[] requestBytes = BuildPostRequest(uri);
await stream.WriteAsync(requestBytes, 0, requestBytes.Length);
StreamReader reader = new StreamReader(stream);
return await reader.ReadToEndAsync();
}
catch (Exception)
{
client.Close();
throw;
}
}
private byte[] BuildPostRequest(Uri uri)
{
StringBuilder builder = new StringBuilder();
builder.Append("POST");
builder.Append(" ");
builder.Append(uri.PathAndQuery);
builder.Append(" HTTP/1.1");
builder.AppendLine();
builder.Append("Host: ");
builder.Append(uri.Host);
builder.Append(':');
builder.Append(uri.Port);
builder.AppendLine();
builder.AppendLine("Connection: close");
builder.AppendLine("Content-Length: 10");
builder.AppendLine();
builder.Append("0123456789");
return Encoding.ASCII.GetBytes(builder.ToString());
}
private class StaggardContent : HttpContent
{
public StaggardContent()
{
Block = new SemaphoreSlim(0, 1);
}
public SemaphoreSlim Block { get; private set; }
protected async override Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
await stream.WriteAsync(new byte[5], 0, 5);
await Block.WaitAsync();
await stream.WriteAsync(new byte[5], 0, 5);
}
protected override bool TryComputeLength(out long length)
{
length = 10;
return true;
}
}
}
}

View File

@ -0,0 +1,100 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Linq;
using System.Net.Http;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.Net.Server
{
public class RequestHeaderTests
{
private const string Address = "http://localhost:8080/";
[Fact]
public async Task RequestHeaders_ClientSendsDefaultHeaders_Success()
{
using (var server = Utilities.CreateHttpServer())
{
Task<string> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
var requestHeaders = context.Request.Headers;
// 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());
string[] values;
Assert.False(requestHeaders.TryGetValue("Accept", out values));
context.Dispose();
string response = await responseTask;
Assert.Equal(string.Empty, response);
}
}
[Fact]
public async Task RequestHeaders_ClientSendsCustomHeaders_Success()
{
using (var server = Utilities.CreateHttpServer())
{
string[] customValues = new string[] { "custom1, and custom2", "custom3" };
Task responseTask = SendRequestAsync("localhost", 8080, "Custom-Header", customValues);
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);
// 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());
context.Dispose();
await responseTask;
}
}
private async Task<string> SendRequestAsync(string uri)
{
using (HttpClient client = new HttpClient())
{
return await client.GetStringAsync(uri);
}
}
private async Task SendRequestAsync(string host, int port, string customHeader, string[] customValues)
{
StringBuilder builder = new StringBuilder();
builder.AppendLine("GET / HTTP/1.1");
builder.AppendLine("Connection: close");
builder.Append("HOST: ");
builder.Append(host);
builder.Append(':');
builder.AppendLine(port.ToString());
foreach (string value in customValues)
{
builder.Append(customHeader);
builder.Append(": ");
builder.AppendLine(value);
builder.AppendLine("Spacer-Header: spacervalue");
}
builder.AppendLine();
byte[] request = Encoding.ASCII.GetBytes(builder.ToString());
Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
socket.Connect(host, port);
socket.Send(request);
byte[] response = new byte[1024 * 5];
await Task.Run(() => socket.Receive(response));
socket.Close();
}
}
}

View File

@ -0,0 +1,130 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.Net.Server
{
public class RequestTests
{
private const string Address = "http://localhost:8080";
[Fact]
public async Task Request_SimpleGet_Success()
{
using (var server = Utilities.CreateServer("http", "localhost", "8080", "/basepath"))
{
Task<string> responseTask = SendRequestAsync(Address + "/basepath/SomePath?SomeQuery");
var context = await server.GetContextAsync();
// General fields
var request = context.Request;
// Request Keys
Assert.Equal("GET", request.Method);
Assert.Equal(Stream.Null, request.Body);
Assert.NotNull(request.Headers);
Assert.Equal("http", request.Scheme);
Assert.Equal("/basepath", request.PathBase);
Assert.Equal("/SomePath", request.Path);
Assert.Equal("?SomeQuery", request.QueryString);
Assert.Equal(new Version(1, 1), request.ProtocolVersion);
Assert.Equal("::1", request.RemoteIpAddress.ToString());
Assert.NotEqual(0, request.RemotePort);
Assert.Equal("::1", request.LocalIpAddress.ToString());
Assert.NotEqual(0, request.LocalPort);
Assert.True(request.IsLocal);
// Note: Response keys are validated in the ResponseTests
context.Dispose();
string response = await responseTask;
Assert.Equal(string.Empty, response);
}
}
[Theory]
[InlineData("/", "http://localhost:8080/", "", "/")]
[InlineData("/basepath/", "http://localhost:8080/basepath", "/basepath", "")]
[InlineData("/basepath/", "http://localhost:8080/basepath/", "/basepath", "/")]
[InlineData("/basepath/", "http://localhost:8080/basepath/subpath", "/basepath", "/subpath")]
[InlineData("/base path/", "http://localhost:8080/base%20path/sub path", "/base path", "/sub path")]
[InlineData("/base葉path/", "http://localhost:8080/base%E8%91%89path/sub%E8%91%89path", "/base葉path", "/sub葉path")]
public async Task Request_PathSplitting(string pathBase, string requestUri, string expectedPathBase, string expectedPath)
{
using (var server = Utilities.CreateServer("http", "localhost", "8080", pathBase))
{
Task<string> responseTask = SendRequestAsync(requestUri);
var context = await server.GetContextAsync();
// General fields
var request = context.Request;
// Request Keys
Assert.Equal("http", request.Scheme);
Assert.Equal(expectedPath, request.Path);
Assert.Equal(expectedPathBase, request.PathBase);
Assert.Equal(string.Empty, request.QueryString);
Assert.Equal(8080, request.LocalPort);
context.Dispose();
string response = await responseTask;
Assert.Equal(string.Empty, response);
}
}
[Theory]
// The test server defines these prefixes: "/", "/11", "/2/3", "/2", "/11/2"
[InlineData("/", "", "/")]
[InlineData("/random", "", "/random")]
[InlineData("/11", "/11", "")]
[InlineData("/11/", "/11", "/")]
[InlineData("/11/random", "/11", "/random")]
[InlineData("/2", "/2", "")]
[InlineData("/2/", "/2", "/")]
[InlineData("/2/random", "/2", "/random")]
[InlineData("/2/3", "/2/3", "")]
[InlineData("/2/3/", "/2/3", "/")]
[InlineData("/2/3/random", "/2/3", "/random")]
public async Task Request_MultiplePrefixes(string requestUri, string expectedPathBase, string expectedPath)
{
using (var server = new WebListener())
{
foreach (string path in new[] { "/", "/11", "/2/3", "/2", "/11/2" })
{
server.UrlPrefixes.Add(UrlPrefix.Create("http", "localhost", "8080", path));
}
server.Start();
Task<string> responseTask = SendRequestAsync(Address + requestUri);
var context = await server.GetContextAsync();
var request = context.Request;
Assert.Equal(expectedPath, request.Path);
Assert.Equal(expectedPathBase, request.PathBase);
context.Dispose();
string response = await responseTask;
Assert.Equal(string.Empty, response);
}
}
private async Task<string> SendRequestAsync(string uri)
{
using (HttpClient client = new HttpClient())
{
return await client.GetStringAsync(uri);
}
}
}
}

View File

@ -0,0 +1,192 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.Net.Server
{
public class ResponseBodyTests
{
private const string Address = "http://localhost:8080/";
[Fact]
public async Task ResponseBody_WriteNoHeaders_DefaultsToChunked()
{
using (var server = Utilities.CreateHttpServer())
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
context.Response.Body.Write(new byte[10], 0, 10);
await context.Response.Body.WriteAsync(new byte[10], 0, 10);
context.Dispose();
HttpResponseMessage response = await responseTask;
Assert.Equal(200, (int)response.StatusCode);
Assert.Equal(new Version(1, 1), response.Version);
IEnumerable<string> ignored;
Assert.False(response.Content.Headers.TryGetValues("content-length", out ignored), "Content-Length");
Assert.True(response.Headers.TransferEncodingChunked.Value, "Chunked");
Assert.Equal(new byte[20], await response.Content.ReadAsByteArrayAsync());
}
}
[Fact]
public async Task ResponseBody_WriteChunked_Chunked()
{
using (var server = Utilities.CreateHttpServer())
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
context.Request.Headers["transfeR-Encoding"] = new[] { " CHunked " };
Stream stream = context.Response.Body;
stream.EndWrite(stream.BeginWrite(new byte[10], 0, 10, null, null));
stream.Write(new byte[10], 0, 10);
await stream.WriteAsync(new byte[10], 0, 10);
context.Dispose();
HttpResponseMessage response = await responseTask;
Assert.Equal(200, (int)response.StatusCode);
Assert.Equal(new Version(1, 1), response.Version);
IEnumerable<string> ignored;
Assert.False(response.Content.Headers.TryGetValues("content-length", out ignored), "Content-Length");
Assert.True(response.Headers.TransferEncodingChunked.Value, "Chunked");
Assert.Equal(new byte[30], await response.Content.ReadAsByteArrayAsync());
}
}
[Fact]
public async Task ResponseBody_WriteContentLength_PassedThrough()
{
using (var server = Utilities.CreateHttpServer())
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
context.Response.Headers["Content-lenGth"] = new[] { " 30 " };
Stream stream = context.Response.Body;
stream.EndWrite(stream.BeginWrite(new byte[10], 0, 10, null, null));
stream.Write(new byte[10], 0, 10);
await stream.WriteAsync(new byte[10], 0, 10);
context.Dispose();
HttpResponseMessage response = await responseTask;
Assert.Equal(200, (int)response.StatusCode);
Assert.Equal(new Version(1, 1), response.Version);
IEnumerable<string> contentLength;
Assert.True(response.Content.Headers.TryGetValues("content-length", out contentLength), "Content-Length");
Assert.Equal("30", contentLength.First());
Assert.Null(response.Headers.TransferEncodingChunked);
Assert.Equal(new byte[30], await response.Content.ReadAsByteArrayAsync());
}
}
/* TODO: response protocol
[Fact]
public async Task ResponseBody_Http10WriteNoHeaders_DefaultsConnectionClose()
{
using (Utilities.CreateHttpServer(env =>
{
env["owin.ResponseProtocol"] = "HTTP/1.0";
env.Get<Stream>("owin.ResponseBody").Write(new byte[10], 0, 10);
return env.Get<Stream>("owin.ResponseBody").WriteAsync(new byte[10], 0, 10);
}))
{
HttpResponseMessage response = await SendRequestAsync(Address);
Assert.Equal(200, (int)response.StatusCode);
Assert.Equal(new Version(1, 1), response.Version); // Http.Sys won't transmit 1.0
IEnumerable<string> ignored;
Assert.False(response.Content.Headers.TryGetValues("content-length", out ignored), "Content-Length");
Assert.Null(response.Headers.TransferEncodingChunked);
Assert.Equal(new byte[20], await response.Content.ReadAsByteArrayAsync());
}
}
*/
/* TODO: Why does this test time out?
[Fact]
public async Task ResponseBody_WriteContentLengthNoneWritten_Throws()
{
using (var server = Utilities.CreateHttpServer())
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
context.Response.Headers["Content-lenGth"] = new[] { " 20 " };
context.Dispose();
await Assert.ThrowsAsync<HttpRequestException>(() => responseTask);
}
}
*/
[Fact]
public async Task ResponseBody_WriteContentLengthNotEnoughWritten_Throws()
{
using (var server = Utilities.CreateHttpServer())
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
context.Response.Headers["Content-lenGth"] = new[] { " 20 " };
context.Response.Body.Write(new byte[5], 0, 5);
context.Dispose();
await Assert.ThrowsAsync<HttpRequestException>(() => responseTask);
}
}
[Fact]
public async Task ResponseBody_WriteContentLengthTooMuchWritten_Throws()
{
using (var server = Utilities.CreateHttpServer())
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
context.Response.Headers["Content-lenGth"] = new[] { " 10 " };
context.Response.Body.Write(new byte[5], 0, 5);
Assert.Throws<InvalidOperationException>(() => context.Response.Body.Write(new byte[6], 0, 6));
context.Dispose();
await Assert.ThrowsAsync<HttpRequestException>(() => responseTask);
}
}
[Fact]
public async Task ResponseBody_WriteContentLengthExtraWritten_Throws()
{
using (var server = Utilities.CreateHttpServer())
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
context.Response.Headers["Content-lenGth"] = new[] { " 10 " };
context.Response.Body.Write(new byte[10], 0, 10);
Assert.Throws<ObjectDisposedException>(() => context.Response.Body.Write(new byte[6], 0, 6));
context.Dispose();
var response = await responseTask;
Assert.Equal(200, (int)response.StatusCode);
Assert.Equal(new Version(1, 1), response.Version);
IEnumerable<string> contentLength;
Assert.True(response.Content.Headers.TryGetValues("content-length", out contentLength), "Content-Length");
Assert.Equal("10", contentLength.First());
Assert.Null(response.Headers.TransferEncodingChunked);
Assert.Equal(new byte[10], await response.Content.ReadAsByteArrayAsync());
}
}
private async Task<HttpResponseMessage> SendRequestAsync(string uri)
{
using (HttpClient client = new HttpClient())
{
return await client.GetAsync(uri);
}
}
}
}

View File

@ -0,0 +1,286 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.Net.Server
{
public class ResponseHeaderTests
{
private const string Address = "http://localhost:8080/";
[Fact]
public async Task ResponseHeaders_ServerSendsDefaultHeaders_Success()
{
using (var server = Utilities.CreateHttpServer())
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
context.Dispose();
HttpResponseMessage response = await responseTask;
response.EnsureSuccessStatusCode();
Assert.Equal(2, response.Headers.Count());
Assert.False(response.Headers.TransferEncodingChunked.HasValue);
Assert.True(response.Headers.Date.HasValue);
Assert.Equal("Microsoft-HTTPAPI/2.0", response.Headers.Server.ToString());
Assert.Equal(1, response.Content.Headers.Count());
Assert.Equal(0, response.Content.Headers.ContentLength);
}
}
[Fact]
public async Task ResponseHeaders_ServerSendsSingleValueKnownHeaders_Success()
{
using (var server = Utilities.CreateHttpServer())
{
WebRequest request = WebRequest.Create(Address);
Task<WebResponse> responseTask = request.GetResponseAsync();
var context = await server.GetContextAsync();
var responseHeaders = context.Response.Headers;
responseHeaders["WWW-Authenticate"] = new string[] { "custom1" };
context.Dispose();
// HttpClient would merge the headers no matter what
HttpWebResponse response = (HttpWebResponse)await responseTask;
Assert.Equal(4, response.Headers.Count);
Assert.Null(response.Headers["Transfer-Encoding"]);
Assert.Equal(0, response.ContentLength);
Assert.NotNull(response.Headers["Date"]);
Assert.Equal("Microsoft-HTTPAPI/2.0", response.Headers["Server"]);
Assert.Equal(new string[] { "custom1" }, response.Headers.GetValues("WWW-Authenticate"));
}
}
[Fact]
public async Task ResponseHeaders_ServerSendsMultiValueKnownHeaders_Success()
{
using (var server = Utilities.CreateHttpServer())
{
WebRequest request = WebRequest.Create(Address);
Task<WebResponse> responseTask = request.GetResponseAsync();
var context = await server.GetContextAsync();
var responseHeaders = context.Response.Headers;
responseHeaders["WWW-Authenticate"] = new string[] { "custom1, and custom2", "custom3" };
context.Dispose();
// HttpClient would merge the headers no matter what
HttpWebResponse response = (HttpWebResponse)await responseTask;
Assert.Equal(4, response.Headers.Count);
Assert.Null(response.Headers["Transfer-Encoding"]);
Assert.Equal(0, response.ContentLength);
Assert.NotNull(response.Headers["Date"]);
Assert.Equal("Microsoft-HTTPAPI/2.0", response.Headers["Server"]);
Assert.Equal(new string[] { "custom1, and custom2", "custom3" }, response.Headers.GetValues("WWW-Authenticate"));
}
}
[Fact]
public async Task ResponseHeaders_ServerSendsCustomHeaders_Success()
{
using (var server = Utilities.CreateHttpServer())
{
WebRequest request = WebRequest.Create(Address);
Task<WebResponse> responseTask = request.GetResponseAsync();
var context = await server.GetContextAsync();
var responseHeaders = context.Response.Headers;
responseHeaders["Custom-Header1"] = new string[] { "custom1, and custom2", "custom3" };
context.Dispose();
// HttpClient would merge the headers no matter what
HttpWebResponse response = (HttpWebResponse)await responseTask;
Assert.Equal(4, response.Headers.Count);
Assert.Null(response.Headers["Transfer-Encoding"]);
Assert.Equal(0, response.ContentLength);
Assert.NotNull(response.Headers["Date"]);
Assert.Equal("Microsoft-HTTPAPI/2.0", response.Headers["Server"]);
Assert.Equal(new string[] { "custom1, and custom2", "custom3" }, response.Headers.GetValues("Custom-Header1"));
}
}
[Fact]
public async Task ResponseHeaders_ServerSendsConnectionClose_Closed()
{
using (var server = Utilities.CreateHttpServer())
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
var responseHeaders = context.Response.Headers;
responseHeaders["Connection"] = new string[] { "Close" };
context.Dispose();
HttpResponseMessage response = await responseTask;
response.EnsureSuccessStatusCode();
Assert.True(response.Headers.ConnectionClose.Value);
Assert.Equal(new string[] { "close" }, response.Headers.GetValues("Connection"));
}
}
/* TODO:
[Fact]
public async Task ResponseHeaders_SendsHttp10_Gets11Close()
{
using (Utilities.CreateHttpServer(env =>
{
env["owin.ResponseProtocol"] = "HTTP/1.0";
return Task.FromResult(0);
}))
{
HttpResponseMessage response = await SendRequestAsync(Address);
response.EnsureSuccessStatusCode();
Assert.Equal(new Version(1, 1), response.Version);
Assert.True(response.Headers.ConnectionClose.Value);
Assert.Equal(new string[] { "close" }, response.Headers.GetValues("Connection"));
}
}
[Fact]
public async Task ResponseHeaders_SendsHttp10WithBody_Gets11Close()
{
using (Utilities.CreateHttpServer(env =>
{
env["owin.ResponseProtocol"] = "HTTP/1.0";
return env.Get<Stream>("owin.ResponseBody").WriteAsync(new byte[10], 0, 10);
}))
{
HttpResponseMessage response = await SendRequestAsync(Address);
response.EnsureSuccessStatusCode();
Assert.Equal(new Version(1, 1), response.Version);
Assert.False(response.Headers.TransferEncodingChunked.HasValue);
Assert.False(response.Content.Headers.Contains("Content-Length"));
Assert.True(response.Headers.ConnectionClose.Value);
Assert.Equal(new string[] { "close" }, response.Headers.GetValues("Connection"));
}
}
*/
[Fact]
public async Task ResponseHeaders_HTTP10Request_Gets11Close()
{
using (var server = Utilities.CreateHttpServer())
{
using (HttpClient client = new HttpClient())
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, Address);
request.Version = new Version(1, 0);
Task<HttpResponseMessage> responseTask = client.SendAsync(request);
var context = await server.GetContextAsync();
context.Dispose();
HttpResponseMessage response = await responseTask;
response.EnsureSuccessStatusCode();
Assert.Equal(new Version(1, 1), response.Version);
Assert.True(response.Headers.ConnectionClose.Value);
Assert.Equal(new string[] { "close" }, response.Headers.GetValues("Connection"));
}
}
}
[Fact]
public async Task ResponseHeaders_HTTP10Request_RemovesChunkedHeader()
{
using (var server = Utilities.CreateHttpServer())
{
using (HttpClient client = new HttpClient())
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, Address);
request.Version = new Version(1, 0);
Task<HttpResponseMessage> responseTask = client.SendAsync(request);
var context = await server.GetContextAsync();
var responseHeaders = context.Response.Headers;
responseHeaders["Transfer-Encoding"] = new string[] { "chunked" };
await context.Response.Body.WriteAsync(new byte[10], 0, 10);
context.Dispose();
HttpResponseMessage response = await responseTask;
response.EnsureSuccessStatusCode();
Assert.Equal(new Version(1, 1), response.Version);
Assert.False(response.Headers.TransferEncodingChunked.HasValue);
Assert.False(response.Content.Headers.Contains("Content-Length"));
Assert.True(response.Headers.ConnectionClose.Value);
Assert.Equal(new string[] { "close" }, response.Headers.GetValues("Connection"));
}
}
}
[Fact]
public async Task Headers_FlushSendsHeaders_Success()
{
using (var server = Utilities.CreateHttpServer())
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
var responseHeaders = context.Response.Headers;
responseHeaders.Add("Custom1", new string[] { "value1a", "value1b" });
responseHeaders.Add("Custom2", new string[] { "value2a, value2b" });
var body = context.Response.Body;
body.Flush();
Assert.Throws<InvalidOperationException>(() => context.Response.StatusCode = 404);
responseHeaders.Add("Custom3", new string[] { "value3a, value3b", "value3c" }); // Ignored
context.Dispose();
HttpResponseMessage response = await responseTask;
response.EnsureSuccessStatusCode();
Assert.Equal(5, response.Headers.Count()); // Date, Server, Chunked
Assert.Equal(2, response.Headers.GetValues("Custom1").Count());
Assert.Equal("value1a", response.Headers.GetValues("Custom1").First());
Assert.Equal("value1b", response.Headers.GetValues("Custom1").Skip(1).First());
Assert.Equal(1, response.Headers.GetValues("Custom2").Count());
Assert.Equal("value2a, value2b", response.Headers.GetValues("Custom2").First());
}
}
[Fact]
public async Task Headers_FlushAsyncSendsHeaders_Success()
{
using (var server = Utilities.CreateHttpServer())
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
var responseHeaders = context.Response.Headers;
responseHeaders.Add("Custom1", new string[] { "value1a", "value1b" });
responseHeaders.Add("Custom2", new string[] { "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
context.Dispose();
HttpResponseMessage response = await responseTask;
response.EnsureSuccessStatusCode();
Assert.Equal(5, response.Headers.Count()); // Date, Server, Chunked
Assert.Equal(2, response.Headers.GetValues("Custom1").Count());
Assert.Equal("value1a", response.Headers.GetValues("Custom1").First());
Assert.Equal("value1b", response.Headers.GetValues("Custom1").Skip(1).First());
Assert.Equal(1, response.Headers.GetValues("Custom2").Count());
Assert.Equal("value2a, value2b", response.Headers.GetValues("Custom2").First());
}
}
private async Task<HttpResponseMessage> SendRequestAsync(string uri)
{
using (HttpClient client = new HttpClient())
{
return await client.GetAsync(uri);
}
}
}
}

View File

@ -0,0 +1,274 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
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;
namespace Microsoft.Net.Server
{
public class ResponseSendFileTests
{
private const string Address = "http://localhost:8080/";
private readonly string AbsoluteFilePath;
private readonly string RelativeFilePath;
private readonly long FileLength;
public ResponseSendFileTests()
{
AbsoluteFilePath = Directory.GetFiles(Environment.CurrentDirectory).First();
RelativeFilePath = Path.GetFileName(AbsoluteFilePath);
FileLength = new FileInfo(AbsoluteFilePath).Length;
}
[Fact]
public async Task ResponseSendFile_MissingFile_Throws()
{
using (var server = Utilities.CreateHttpServer())
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
await Assert.ThrowsAsync<FileNotFoundException>(() =>
context.Response.SendFileAsync("Missing.txt", 0, null, CancellationToken.None));
context.Dispose();
HttpResponseMessage response = await responseTask;
}
}
[Fact]
public async Task ResponseSendFile_NoHeaders_DefaultsToChunked()
{
using (var server = Utilities.CreateHttpServer())
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
await context.Response.SendFileAsync(AbsoluteFilePath, 0, null, CancellationToken.None);
context.Dispose();
HttpResponseMessage response = await responseTask;
Assert.Equal(200, (int)response.StatusCode);
IEnumerable<string> ignored;
Assert.False(response.Content.Headers.TryGetValues("content-length", out ignored), "Content-Length");
Assert.True(response.Headers.TransferEncodingChunked.Value, "Chunked");
Assert.Equal(FileLength, (await response.Content.ReadAsByteArrayAsync()).Length);
}
}
[Fact]
public async Task ResponseSendFile_RelativeFile_Success()
{
using (var server = Utilities.CreateHttpServer())
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
await context.Response.SendFileAsync(RelativeFilePath, 0, null, CancellationToken.None);
context.Dispose();
HttpResponseMessage response = await responseTask;
Assert.Equal(200, (int)response.StatusCode);
IEnumerable<string> ignored;
Assert.False(response.Content.Headers.TryGetValues("content-length", out ignored), "Content-Length");
Assert.True(response.Headers.TransferEncodingChunked.Value, "Chunked");
Assert.Equal(FileLength, (await response.Content.ReadAsByteArrayAsync()).Length);
}
}
[Fact]
public async Task ResponseSendFile_Chunked_Chunked()
{
using (var server = Utilities.CreateHttpServer())
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
context.Response.Headers["Transfer-EncodinG"] = new[] { "CHUNKED" };
await context.Response.SendFileAsync(AbsoluteFilePath, 0, null, CancellationToken.None);
context.Dispose();
HttpResponseMessage response = await responseTask;
Assert.Equal(200, (int)response.StatusCode);
IEnumerable<string> contentLength;
Assert.False(response.Content.Headers.TryGetValues("content-length", out contentLength), "Content-Length");
Assert.True(response.Headers.TransferEncodingChunked.Value);
Assert.Equal(FileLength, (await response.Content.ReadAsByteArrayAsync()).Length);
}
}
[Fact]
public async Task ResponseSendFile_MultipleChunks_Chunked()
{
using (var server = Utilities.CreateHttpServer())
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
context.Response.Headers["Transfer-EncodinG"] = new[] { "CHUNKED" };
await context.Response.SendFileAsync(AbsoluteFilePath, 0, null, CancellationToken.None);
await context.Response.SendFileAsync(AbsoluteFilePath, 0, null, CancellationToken.None);
context.Dispose();
HttpResponseMessage response = await responseTask;
Assert.Equal(200, (int)response.StatusCode);
IEnumerable<string> contentLength;
Assert.False(response.Content.Headers.TryGetValues("content-length", out contentLength), "Content-Length");
Assert.True(response.Headers.TransferEncodingChunked.Value);
Assert.Equal(FileLength * 2, (await response.Content.ReadAsByteArrayAsync()).Length);
}
}
[Fact]
public async Task ResponseSendFile_ChunkedHalfOfFile_Chunked()
{
using (var server = Utilities.CreateHttpServer())
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
await context.Response.SendFileAsync(AbsoluteFilePath, 0, FileLength / 2, CancellationToken.None);
context.Dispose();
HttpResponseMessage response = await responseTask;
Assert.Equal(200, (int)response.StatusCode);
IEnumerable<string> contentLength;
Assert.False(response.Content.Headers.TryGetValues("content-length", out contentLength), "Content-Length");
Assert.True(response.Headers.TransferEncodingChunked.Value);
Assert.Equal(FileLength / 2, (await response.Content.ReadAsByteArrayAsync()).Length);
}
}
[Fact]
public async Task ResponseSendFile_ChunkedOffsetOutOfRange_Throws()
{
using (var server = Utilities.CreateHttpServer())
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(
() => context.Response.SendFileAsync(AbsoluteFilePath, 1234567, null, CancellationToken.None));
context.Dispose();
HttpResponseMessage response = await responseTask;
}
}
[Fact]
public async Task ResponseSendFile_ChunkedCountOutOfRange_Throws()
{
using (var server = Utilities.CreateHttpServer())
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(
() => context.Response.SendFileAsync(AbsoluteFilePath, 0, 1234567, CancellationToken.None));
context.Dispose();
HttpResponseMessage response = await responseTask;
}
}
[Fact]
public async Task ResponseSendFile_ChunkedCount0_Chunked()
{
using (var server = Utilities.CreateHttpServer())
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
await context.Response.SendFileAsync(AbsoluteFilePath, 0, 0, CancellationToken.None);
context.Dispose();
HttpResponseMessage response = await responseTask;
Assert.Equal(200, (int)response.StatusCode);
IEnumerable<string> contentLength;
Assert.False(response.Content.Headers.TryGetValues("content-length", out contentLength), "Content-Length");
Assert.True(response.Headers.TransferEncodingChunked.Value);
Assert.Equal(0, (await response.Content.ReadAsByteArrayAsync()).Length);
}
}
[Fact]
public async Task ResponseSendFile_ContentLength_PassedThrough()
{
using (var server = Utilities.CreateHttpServer())
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
context.Response.Headers["Content-lenGth"] = new[] { FileLength.ToString() };
await context.Response.SendFileAsync(AbsoluteFilePath, 0, null, CancellationToken.None);
HttpResponseMessage response = await responseTask;
Assert.Equal(200, (int)response.StatusCode);
IEnumerable<string> contentLength;
Assert.True(response.Content.Headers.TryGetValues("content-length", out contentLength), "Content-Length");
Assert.Equal(FileLength.ToString(), contentLength.First());
Assert.Null(response.Headers.TransferEncodingChunked);
Assert.Equal(FileLength, response.Content.ReadAsByteArrayAsync().Result.Length);
}
}
[Fact]
public async Task ResponseSendFile_ContentLengthSpecific_PassedThrough()
{
using (var server = Utilities.CreateHttpServer())
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
context.Response.Headers["Content-lenGth"] = new[] { "10" };
await context.Response.SendFileAsync(AbsoluteFilePath, 0, 10, CancellationToken.None);
context.Dispose();
HttpResponseMessage response = await responseTask;
Assert.Equal(200, (int)response.StatusCode);
IEnumerable<string> contentLength;
Assert.True(response.Content.Headers.TryGetValues("content-length", out contentLength), "Content-Length");
Assert.Equal("10", contentLength.First());
Assert.Null(response.Headers.TransferEncodingChunked);
Assert.Equal(10, (await response.Content.ReadAsByteArrayAsync()).Length);
}
}
[Fact]
public async Task ResponseSendFile_ContentLength0_PassedThrough()
{
using (var server = Utilities.CreateHttpServer())
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
context.Response.Headers["Content-lenGth"] = new[] { "0" };
await context.Response.SendFileAsync(AbsoluteFilePath, 0, 0, CancellationToken.None);
context.Dispose();
HttpResponseMessage response = await responseTask;
Assert.Equal(200, (int)response.StatusCode);
IEnumerable<string> contentLength;
Assert.True(response.Content.Headers.TryGetValues("content-length", out contentLength), "Content-Length");
Assert.Equal("0", contentLength.First());
Assert.Null(response.Headers.TransferEncodingChunked);
Assert.Equal(0, (await response.Content.ReadAsByteArrayAsync()).Length);
}
}
private async Task<HttpResponseMessage> SendRequestAsync(string uri)
{
using (HttpClient client = new HttpClient())
{
return await client.GetAsync(uri);
}
}
}
}

View File

@ -0,0 +1,131 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.Net.Server
{
public class ResponseTests
{
private const string Address = "http://localhost:8080/";
[Fact]
public async Task Response_ServerSendsDefaultResponse_ServerProvidesStatusCodeAndReasonPhrase()
{
using (var server = Utilities.CreateHttpServer())
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
Assert.Equal(200, context.Response.StatusCode);
context.Dispose();
HttpResponseMessage response = await responseTask;
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("OK", response.ReasonPhrase);
Assert.Equal(new Version(1, 1), response.Version);
Assert.Equal(string.Empty, await response.Content.ReadAsStringAsync());
}
}
[Fact]
public async Task Response_ServerSendsSpecificStatus_ServerProvidesReasonPhrase()
{
using (var server = Utilities.CreateHttpServer())
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
context.Response.StatusCode = 201;
// TODO: env["owin.ResponseProtocol"] = "HTTP/1.0"; // Http.Sys ignores this value
context.Dispose();
HttpResponseMessage response = await responseTask;
Assert.Equal(201, (int)response.StatusCode);
Assert.Equal("Created", response.ReasonPhrase);
Assert.Equal(new Version(1, 1), response.Version);
Assert.Equal(string.Empty, await response.Content.ReadAsStringAsync());
}
}
[Fact]
public async Task Response_ServerSendsSpecificStatusAndReasonPhrase_PassedThrough()
{
using (var server = Utilities.CreateHttpServer())
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
context.Response.StatusCode = 201;
context.Response.ReasonPhrase = "CustomReasonPhrase";
// TODO: env["owin.ResponseProtocol"] = "HTTP/1.0"; // Http.Sys ignores this value
context.Dispose();
HttpResponseMessage response = await responseTask;
Assert.Equal(201, (int)response.StatusCode);
Assert.Equal("CustomReasonPhrase", response.ReasonPhrase);
Assert.Equal(new Version(1, 1), response.Version);
Assert.Equal(string.Empty, await response.Content.ReadAsStringAsync());
}
}
[Fact]
public async Task Response_ServerSendsCustomStatus_NoReasonPhrase()
{
using (var server = Utilities.CreateHttpServer())
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
context.Response.StatusCode = 901;
context.Dispose();
HttpResponseMessage response = await responseTask;
Assert.Equal(901, (int)response.StatusCode);
Assert.Equal(string.Empty, response.ReasonPhrase);
Assert.Equal(string.Empty, await response.Content.ReadAsStringAsync());
}
}
[Fact]
public async Task Response_100_Throws()
{
using (var server = Utilities.CreateHttpServer())
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
Assert.Throws<ArgumentOutOfRangeException>(() => { context.Response.StatusCode = 100; });
context.Dispose();
HttpResponseMessage response = await responseTask;
}
}
[Fact]
public async Task Response_0_Throws()
{
using (var server = Utilities.CreateHttpServer())
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
Assert.Throws<ArgumentOutOfRangeException>(() => { context.Response.StatusCode = 0; });
context.Dispose();
HttpResponseMessage response = await responseTask;
}
}
private async Task<HttpResponseMessage> SendRequestAsync(string uri)
{
using (HttpClient client = new HttpClient())
{
return await client.GetAsync(uri);
}
}
}
}

View File

@ -0,0 +1,210 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.Net.Server
{
public class ServerTests
{
private const string Address = "http://localhost:8080/";
[Fact]
public async Task Server_200OK_Success()
{
using (var server = Utilities.CreateHttpServer())
{
Task<string> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
context.Dispose();
string response = await responseTask;
Assert.Equal(string.Empty, response);
}
}
[Fact]
public async Task Server_SendHelloWorld_Success()
{
using (var server = Utilities.CreateHttpServer())
{
Task<string> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
context.Response.ContentLength = 11;
using (var writer = new StreamWriter(context.Response.Body))
{
writer.Write("Hello World");
}
string response = await responseTask;
Assert.Equal("Hello World", response);
}
}
[Fact]
public async Task Server_EchoHelloWorld_Success()
{
using (var server = Utilities.CreateHttpServer())
{
Task<string> responseTask = SendRequestAsync(Address, "Hello World");
var context = await server.GetContextAsync();
string input = new StreamReader(context.Request.Body).ReadToEnd();
Assert.Equal("Hello World", input);
context.Response.ContentLength = 11;
using (var writer = new StreamWriter(context.Response.Body))
{
writer.Write("Hello World");
}
string response = await responseTask;
Assert.Equal("Hello World", response);
}
}
[Fact]
public async Task Server_ClientDisconnects_CallCancelled()
{
TimeSpan interval = TimeSpan.FromSeconds(1);
ManualResetEvent canceled = new ManualResetEvent(false);
using (var server = Utilities.CreateHttpServer())
{
// Note: System.Net.Sockets does not RST the connection by default, it just FINs.
// Http.Sys's disconnect notice requires a RST.
Task<Socket> responseTask = SendHungRequestAsync("GET", Address);
var context = await server.GetContextAsync();
CancellationToken ct = context.DisconnectToken;
Assert.True(ct.CanBeCanceled, "CanBeCanceled");
Assert.False(ct.IsCancellationRequested, "IsCancellationRequested");
ct.Register(() => canceled.Set());
using (Socket socket = await responseTask)
{
socket.Close(0); // Force a RST
}
Assert.True(canceled.WaitOne(interval), "canceled");
Assert.True(ct.IsCancellationRequested, "IsCancellationRequested");
context.Dispose();
}
}
[Fact]
public async Task Server_Abort_CallCancelled()
{
TimeSpan interval = TimeSpan.FromSeconds(1);
ManualResetEvent canceled = new ManualResetEvent(false);
using (var server = Utilities.CreateHttpServer())
{
// Note: System.Net.Sockets does not RST the connection by default, it just FINs.
// Http.Sys's disconnect notice requires a RST.
Task<Socket> responseTask = SendHungRequestAsync("GET", Address);
var context = await server.GetContextAsync();
CancellationToken ct = context.DisconnectToken;
Assert.True(ct.CanBeCanceled, "CanBeCanceled");
Assert.False(ct.IsCancellationRequested, "IsCancellationRequested");
ct.Register(() => canceled.Set());
context.Abort();
Assert.True(canceled.WaitOne(interval), "Aborted");
Assert.True(ct.IsCancellationRequested, "IsCancellationRequested");
using (Socket socket = await responseTask)
{
Assert.Throws<SocketException>(() => socket.Receive(new byte[10]));
}
}
}
[Fact]
public async Task Server_SetQueueLimit_Success()
{
using (var server = Utilities.CreateHttpServer())
{
server.SetRequestQueueLimit(1001);
Task<string> responseTask = SendRequestAsync(Address);
var context = await server.GetContextAsync();
context.Dispose();
string response = await responseTask;
Assert.Equal(string.Empty, response);
}
}
private async Task<string> SendRequestAsync(string uri)
{
ServicePointManager.DefaultConnectionLimit = 100;
using (HttpClient client = new HttpClient())
{
return await client.GetStringAsync(uri);
}
}
private async Task<string> SendRequestAsync(string uri, string upload)
{
using (HttpClient client = new HttpClient())
{
HttpResponseMessage response = await client.PostAsync(uri, new StringContent(upload));
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
private async Task<Socket> SendHungRequestAsync(string method, string address)
{
// Connect with a socket
Uri uri = new Uri(address);
TcpClient client = new TcpClient();
try
{
await client.ConnectAsync(uri.Host, uri.Port);
NetworkStream stream = client.GetStream();
// Send an HTTP GET request
byte[] requestBytes = BuildGetRequest(method, uri);
await stream.WriteAsync(requestBytes, 0, requestBytes.Length);
// Return the opaque network stream
return client.Client;
}
catch (Exception)
{
client.Close();
throw;
}
}
private byte[] BuildGetRequest(string method, Uri uri)
{
StringBuilder builder = new StringBuilder();
builder.Append(method);
builder.Append(" ");
builder.Append(uri.PathAndQuery);
builder.Append(" HTTP/1.1");
builder.AppendLine();
builder.Append("Host: ");
builder.Append(uri.Host);
builder.Append(':');
builder.Append(uri.Port);
builder.AppendLine();
builder.AppendLine();
return Encoding.ASCII.GetBytes(builder.ToString());
}
}
}

View File

@ -0,0 +1,38 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
namespace Microsoft.Net.Server
{
internal static class Utilities
{
internal static WebListener CreateHttpServer()
{
return CreateServer("http", "localhost", "8080", string.Empty);
}
internal static WebListener CreateHttpsServer()
{
return CreateServer("https", "localhost", "9090", string.Empty);
}
internal static WebListener CreateAuthServer(AuthenticationType authType)
{
return CreateServer("http", "localhost", "8080", string.Empty, authType);
}
internal static WebListener CreateServer(string scheme, string host, string port, string path)
{
return CreateServer(scheme, host, port, path, AuthenticationType.None);
}
internal static WebListener CreateServer(string scheme, string host, string port, string path, AuthenticationType authType)
{
WebListener listener = new WebListener();
listener.UrlPrefixes.Add(UrlPrefix.Create(scheme, host, port, path));
listener.AuthenticationManager.AuthenticationTypes = authType;
listener.Start();
return listener;
}
}
}