#65 - Port more TestHost funcationality.
This commit is contained in:
parent
0385438ed0
commit
ed38d28db4
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio 14
|
# Visual Studio 14
|
||||||
VisualStudioVersion = 14.0.21730.1
|
VisualStudioVersion = 14.0.21916.0
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E0497F39-AFFB-4819-A116-E39E361915AB}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E0497F39-AFFB-4819-A116-E39E361915AB}"
|
||||||
EndProject
|
EndProject
|
||||||
|
|
@ -17,6 +17,11 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Hosting.Te
|
||||||
EndProject
|
EndProject
|
||||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.RequestContainer", "src\Microsoft.AspNet.RequestContainer\Microsoft.AspNet.RequestContainer.kproj", "{374A5B0C-3E93-4A23-A4A0-EE2AB6DF7814}"
|
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.RequestContainer", "src\Microsoft.AspNet.RequestContainer\Microsoft.AspNet.RequestContainer.kproj", "{374A5B0C-3E93-4A23-A4A0-EE2AB6DF7814}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A66E3673-3976-4152-B902-2D0EC1428EA2}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
global.json = global.json
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,207 @@
|
||||||
|
// 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;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Diagnostics.Contracts;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.FeatureModel;
|
||||||
|
using Microsoft.AspNet.Http;
|
||||||
|
using Microsoft.AspNet.HttpFeature;
|
||||||
|
using Microsoft.AspNet.PipelineCore;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.TestHost
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This adapts HttpRequestMessages to ASP.NET requests, dispatches them through the pipeline, and returns the
|
||||||
|
/// associated HttpResponseMessage.
|
||||||
|
/// </summary>
|
||||||
|
public class ClientHandler : HttpMessageHandler
|
||||||
|
{
|
||||||
|
private readonly Func<object, Task> _next;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new handler.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="next">The pipeline entry point.</param>
|
||||||
|
public ClientHandler(Func<object, Task> next)
|
||||||
|
{
|
||||||
|
if (next == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("next");
|
||||||
|
}
|
||||||
|
|
||||||
|
_next = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This adapts HttpRequestMessages to ASP.NET requests, dispatches them through the pipeline, and returns the
|
||||||
|
/// associated HttpResponseMessage.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request"></param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected override async Task<HttpResponseMessage> SendAsync(
|
||||||
|
HttpRequestMessage request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (request == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("request");
|
||||||
|
}
|
||||||
|
|
||||||
|
var state = new RequestState(request, cancellationToken);
|
||||||
|
HttpContent requestContent = request.Content ?? new StreamContent(Stream.Null);
|
||||||
|
Stream body = await requestContent.ReadAsStreamAsync();
|
||||||
|
if (body.CanSeek)
|
||||||
|
{
|
||||||
|
// This body may have been consumed before, rewind it.
|
||||||
|
body.Seek(0, SeekOrigin.Begin);
|
||||||
|
}
|
||||||
|
state.HttpContext.Request.Body = body;
|
||||||
|
CancellationTokenRegistration registration = cancellationToken.Register(state.Abort);
|
||||||
|
|
||||||
|
// Async offload, don't let the test code block the caller.
|
||||||
|
Task offload = Task.Factory.StartNew(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _next(state.FeatureCollection);
|
||||||
|
state.CompleteResponse();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
state.Abort(ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
registration.Dispose();
|
||||||
|
state.Dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return await state.ResponseTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RequestState : IDisposable
|
||||||
|
{
|
||||||
|
private readonly HttpRequestMessage _request;
|
||||||
|
private TaskCompletionSource<HttpResponseMessage> _responseTcs;
|
||||||
|
private ResponseStream _responseStream;
|
||||||
|
private ResponseFeature _responseFeature;
|
||||||
|
|
||||||
|
internal RequestState(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_request = request;
|
||||||
|
_responseTcs = new TaskCompletionSource<HttpResponseMessage>();
|
||||||
|
|
||||||
|
if (request.RequestUri.IsDefaultPort)
|
||||||
|
{
|
||||||
|
request.Headers.Host = request.RequestUri.Host;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
request.Headers.Host = request.RequestUri.GetComponents(UriComponents.HostAndPort, UriFormat.UriEscaped);
|
||||||
|
}
|
||||||
|
|
||||||
|
FeatureCollection = new FeatureCollection();
|
||||||
|
HttpContext = new DefaultHttpContext(FeatureCollection);
|
||||||
|
HttpContext.SetFeature<IHttpRequestFeature>(new RequestFeature());
|
||||||
|
_responseFeature = new ResponseFeature();
|
||||||
|
HttpContext.SetFeature<IHttpResponseFeature>(_responseFeature);
|
||||||
|
var serverRequest = HttpContext.Request;
|
||||||
|
serverRequest.Protocol = "HTTP/" + request.Version.ToString(2);
|
||||||
|
serverRequest.Scheme = request.RequestUri.Scheme;
|
||||||
|
serverRequest.Method = request.Method.ToString();
|
||||||
|
serverRequest.Path = PathString.FromUriComponent(request.RequestUri);
|
||||||
|
serverRequest.PathBase = PathString.Empty;
|
||||||
|
serverRequest.QueryString = QueryString.FromUriComponent(request.RequestUri);
|
||||||
|
// TODO: serverRequest.CallCancelled = cancellationToken;
|
||||||
|
|
||||||
|
foreach (var header in request.Headers)
|
||||||
|
{
|
||||||
|
serverRequest.Headers.AppendValues(header.Key, header.Value.ToArray());
|
||||||
|
}
|
||||||
|
HttpContent requestContent = request.Content;
|
||||||
|
if (requestContent != null)
|
||||||
|
{
|
||||||
|
foreach (var header in request.Content.Headers)
|
||||||
|
{
|
||||||
|
serverRequest.Headers.AppendValues(header.Key, header.Value.ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_responseStream = new ResponseStream(CompleteResponse);
|
||||||
|
HttpContext.Response.Body = _responseStream;
|
||||||
|
HttpContext.Response.StatusCode = 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpContext HttpContext { get; private set; }
|
||||||
|
|
||||||
|
public IFeatureCollection FeatureCollection { get; private set; }
|
||||||
|
|
||||||
|
public Task<HttpResponseMessage> ResponseTask
|
||||||
|
{
|
||||||
|
get { return _responseTcs.Task; }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void CompleteResponse()
|
||||||
|
{
|
||||||
|
if (!_responseTcs.Task.IsCompleted)
|
||||||
|
{
|
||||||
|
HttpResponseMessage response = GenerateResponse();
|
||||||
|
// Dispatch, as TrySetResult will synchronously execute the waiters callback and block our Write.
|
||||||
|
Task.Factory.StartNew(() => _responseTcs.TrySetResult(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope",
|
||||||
|
Justification = "HttpResposneMessage must be returned to the caller.")]
|
||||||
|
internal HttpResponseMessage GenerateResponse()
|
||||||
|
{
|
||||||
|
_responseFeature.FireOnSendingHeaders();
|
||||||
|
|
||||||
|
var response = new HttpResponseMessage();
|
||||||
|
response.StatusCode = (HttpStatusCode)HttpContext.Response.StatusCode;
|
||||||
|
response.ReasonPhrase = HttpContext.GetFeature<IHttpResponseFeature>().ReasonPhrase;
|
||||||
|
response.RequestMessage = _request;
|
||||||
|
// response.Version = owinResponse.Protocol;
|
||||||
|
|
||||||
|
response.Content = new StreamContent(_responseStream);
|
||||||
|
|
||||||
|
foreach (var header in HttpContext.Response.Headers)
|
||||||
|
{
|
||||||
|
if (!response.Headers.TryAddWithoutValidation(header.Key, header.Value))
|
||||||
|
{
|
||||||
|
bool success = response.Content.Headers.TryAddWithoutValidation(header.Key, header.Value);
|
||||||
|
Contract.Assert(success, "Bad header");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Abort()
|
||||||
|
{
|
||||||
|
Abort(new OperationCanceledException());
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Abort(Exception exception)
|
||||||
|
{
|
||||||
|
_responseStream.Abort(exception);
|
||||||
|
_responseTcs.TrySetException(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_responseStream.Dispose();
|
||||||
|
// Do not dispose the request, that will be disposed by the caller.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -20,11 +20,14 @@
|
||||||
<Content Include="project.json" />
|
<Content Include="project.json" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="ClientHandler.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="RequestInformation.cs" />
|
<Compile Include="RequestBuilder.cs" />
|
||||||
<Compile Include="ResponseInformation.cs" />
|
<Compile Include="RequestFeature.cs" />
|
||||||
|
<Compile Include="ResponseFeature.cs" />
|
||||||
|
<Compile Include="ResponseStream.cs" />
|
||||||
<Compile Include="TestClient.cs" />
|
<Compile Include="TestClient.cs" />
|
||||||
<Compile Include="TestServer.cs" />
|
<Compile Include="TestServer.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||||
</Project>
|
</Project>
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
// 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.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.TestHost
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used to construct a HttpRequestMessage object.
|
||||||
|
/// </summary>
|
||||||
|
[SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable",
|
||||||
|
Justification = "HttpRequestMessage is disposed by HttpClient in SendAsync")]
|
||||||
|
public class RequestBuilder
|
||||||
|
{
|
||||||
|
private readonly TestServer _server;
|
||||||
|
private readonly HttpRequestMessage _req;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Construct a new HttpRequestMessage with the given path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="server"></param>
|
||||||
|
/// <param name="path"></param>
|
||||||
|
[SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Not a full URI")]
|
||||||
|
public RequestBuilder(TestServer server, string path)
|
||||||
|
{
|
||||||
|
if (server == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("server");
|
||||||
|
}
|
||||||
|
|
||||||
|
_server = server;
|
||||||
|
_req = new HttpRequestMessage(HttpMethod.Get, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configure any HttpRequestMessage properties.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="configure"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public RequestBuilder And(Action<HttpRequestMessage> configure)
|
||||||
|
{
|
||||||
|
if (configure == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("configure");
|
||||||
|
}
|
||||||
|
|
||||||
|
configure(_req);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add the given header and value to the request or request content.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public RequestBuilder AddHeader(string name, string value)
|
||||||
|
{
|
||||||
|
if (!_req.Headers.TryAddWithoutValidation(name, value))
|
||||||
|
{
|
||||||
|
if (_req.Content == null)
|
||||||
|
{
|
||||||
|
_req.Content = new StreamContent(Stream.Null);
|
||||||
|
}
|
||||||
|
if (!_req.Content.Headers.TryAddWithoutValidation(name, value))
|
||||||
|
{
|
||||||
|
// TODO: throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.InvalidHeaderName, name), "name");
|
||||||
|
throw new ArgumentException("Invalid header name: " + name, "name");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the request method and start processing the request.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="method"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<HttpResponseMessage> SendAsync(string method)
|
||||||
|
{
|
||||||
|
_req.Method = new HttpMethod(method);
|
||||||
|
return _server.CreateClient().SendAsync(_req);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the request method to GET and start processing the request.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "GET is an HTTP verb.")]
|
||||||
|
public Task<HttpResponseMessage> GetAsync()
|
||||||
|
{
|
||||||
|
_req.Method = HttpMethod.Get;
|
||||||
|
return _server.CreateClient().SendAsync(_req);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the request method to POST and start processing the request.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<HttpResponseMessage> PostAsync()
|
||||||
|
{
|
||||||
|
_req.Method = HttpMethod.Post;
|
||||||
|
return _server.CreateClient().SendAsync(_req);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
// 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;
|
||||||
|
using System.IO;
|
||||||
|
using Microsoft.AspNet.HttpFeature;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.TestHost
|
||||||
|
{
|
||||||
|
internal class RequestFeature : IHttpRequestFeature
|
||||||
|
{
|
||||||
|
public RequestFeature()
|
||||||
|
{
|
||||||
|
Body = Stream.Null;
|
||||||
|
Headers = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
Method = "GET";
|
||||||
|
Path = "";
|
||||||
|
PathBase = "";
|
||||||
|
Protocol = "HTTP/1.1";
|
||||||
|
QueryString = "";
|
||||||
|
Scheme = "http";
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream Body { get; set; }
|
||||||
|
|
||||||
|
public IDictionary<string, string[]> Headers { get; set; }
|
||||||
|
|
||||||
|
public string Method { get; set; }
|
||||||
|
|
||||||
|
public string Path { get; set; }
|
||||||
|
|
||||||
|
public string PathBase { get; set; }
|
||||||
|
|
||||||
|
public string Protocol { get; set; }
|
||||||
|
|
||||||
|
public string QueryString { get; set; }
|
||||||
|
|
||||||
|
public string Scheme { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,51 +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.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using Microsoft.AspNet.HttpFeature;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.TestHost
|
|
||||||
{
|
|
||||||
internal class RequestInformation : IHttpRequestFeature
|
|
||||||
{
|
|
||||||
public RequestInformation()
|
|
||||||
{
|
|
||||||
Headers = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
PathBase = "";
|
|
||||||
Body = Stream.Null;
|
|
||||||
Protocol = "HTTP/1.1";
|
|
||||||
}
|
|
||||||
|
|
||||||
public Stream Body { get; set; }
|
|
||||||
|
|
||||||
public IDictionary<string, string[]> Headers { get; set; }
|
|
||||||
|
|
||||||
public string Method { get; set; }
|
|
||||||
|
|
||||||
public string Path { get; set; }
|
|
||||||
|
|
||||||
public string PathBase { get; set; }
|
|
||||||
|
|
||||||
public string Protocol { get; set; }
|
|
||||||
|
|
||||||
public string QueryString { get; set; }
|
|
||||||
|
|
||||||
public string Scheme { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
// 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;
|
||||||
|
using System.IO;
|
||||||
|
using Microsoft.AspNet.HttpFeature;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.TestHost
|
||||||
|
{
|
||||||
|
internal class ResponseFeature : IHttpResponseFeature
|
||||||
|
{
|
||||||
|
private Action _sendingHeaders = () => { };
|
||||||
|
|
||||||
|
public ResponseFeature()
|
||||||
|
{
|
||||||
|
Headers = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
Body = new MemoryStream();
|
||||||
|
|
||||||
|
// 200 is the default status code all the way down to the host, so we set it
|
||||||
|
// here to be consistent with the rest of the hosts when writing tests.
|
||||||
|
StatusCode = 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int StatusCode { get; set; }
|
||||||
|
|
||||||
|
public string ReasonPhrase { get; set; }
|
||||||
|
|
||||||
|
public IDictionary<string, string[]> Headers { get; set; }
|
||||||
|
|
||||||
|
public Stream Body { get; set; }
|
||||||
|
|
||||||
|
public void OnSendingHeaders(Action<object> callback, object state)
|
||||||
|
{
|
||||||
|
var prior = _sendingHeaders;
|
||||||
|
_sendingHeaders = () =>
|
||||||
|
{
|
||||||
|
callback(state);
|
||||||
|
prior();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FireOnSendingHeaders()
|
||||||
|
{
|
||||||
|
_sendingHeaders();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,50 +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.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using Microsoft.AspNet.HttpFeature;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.TestHost
|
|
||||||
{
|
|
||||||
internal class ResponseInformation : IHttpResponseFeature
|
|
||||||
{
|
|
||||||
public ResponseInformation()
|
|
||||||
{
|
|
||||||
Headers = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
Body = new MemoryStream();
|
|
||||||
|
|
||||||
// 200 is the default status code all the way down to the host, so we set it
|
|
||||||
// here to be consistent with the rest of the hosts when writing tests.
|
|
||||||
StatusCode = 200;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int StatusCode { get; set; }
|
|
||||||
|
|
||||||
public string ReasonPhrase { get; set; }
|
|
||||||
|
|
||||||
public IDictionary<string, string[]> Headers { get; set; }
|
|
||||||
|
|
||||||
public Stream Body { get; set; }
|
|
||||||
|
|
||||||
public void OnSendingHeaders(Action<object> callback, object state)
|
|
||||||
{
|
|
||||||
// TODO: Figure out how to implement this thing.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,380 @@
|
||||||
|
// 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.Concurrent;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Diagnostics.Contracts;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.TestHost
|
||||||
|
{
|
||||||
|
// This steam accepts writes from the server/app, buffers them internally, and returns the data via Reads
|
||||||
|
// when requested by the client.
|
||||||
|
internal class ResponseStream : Stream
|
||||||
|
{
|
||||||
|
private bool _disposed;
|
||||||
|
private bool _aborted;
|
||||||
|
private Exception _abortException;
|
||||||
|
private ConcurrentQueue<byte[]> _bufferedData;
|
||||||
|
private ArraySegment<byte> _topBuffer;
|
||||||
|
private SemaphoreSlim _readLock;
|
||||||
|
private SemaphoreSlim _writeLock;
|
||||||
|
private TaskCompletionSource<object> _readWaitingForData;
|
||||||
|
private object _signalReadLock;
|
||||||
|
|
||||||
|
private Action _onFirstWrite;
|
||||||
|
private bool _firstWrite;
|
||||||
|
|
||||||
|
internal ResponseStream(Action onFirstWrite)
|
||||||
|
{
|
||||||
|
if (onFirstWrite == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("onFirstWrite");
|
||||||
|
}
|
||||||
|
_onFirstWrite = onFirstWrite;
|
||||||
|
_firstWrite = true;
|
||||||
|
|
||||||
|
_readLock = new SemaphoreSlim(1, 1);
|
||||||
|
_writeLock = new SemaphoreSlim(1, 1);
|
||||||
|
_bufferedData = new ConcurrentQueue<byte[]>();
|
||||||
|
_readWaitingForData = new TaskCompletionSource<object>();
|
||||||
|
_signalReadLock = new object();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanRead
|
||||||
|
{
|
||||||
|
get { return true; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanSeek
|
||||||
|
{
|
||||||
|
get { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanWrite
|
||||||
|
{
|
||||||
|
get { return true; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#region NotSupported
|
||||||
|
|
||||||
|
public override long Length
|
||||||
|
{
|
||||||
|
get { throw new NotSupportedException(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override long Position
|
||||||
|
{
|
||||||
|
get { throw new NotSupportedException(); }
|
||||||
|
set { throw new NotSupportedException(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override long Seek(long offset, SeekOrigin origin)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetLength(long value)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion NotSupported
|
||||||
|
|
||||||
|
public override void Flush()
|
||||||
|
{
|
||||||
|
CheckDisposed();
|
||||||
|
|
||||||
|
_writeLock.Wait();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
FirstWrite();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_writeLock.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Wait for data to drain?
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task FlushAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
|
||||||
|
tcs.TrySetCanceled();
|
||||||
|
return tcs.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
Flush();
|
||||||
|
|
||||||
|
// TODO: Wait for data to drain?
|
||||||
|
|
||||||
|
return Task.FromResult<object>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
VerifyBuffer(buffer, offset, count, allowEmpty: false);
|
||||||
|
_readLock.Wait();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int totalRead = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
// Don't drain buffered data when signaling an abort.
|
||||||
|
CheckAborted();
|
||||||
|
if (_topBuffer.Count <= 0)
|
||||||
|
{
|
||||||
|
byte[] topBuffer = null;
|
||||||
|
while (!_bufferedData.TryDequeue(out topBuffer))
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
CheckAborted();
|
||||||
|
// Graceful close
|
||||||
|
return totalRead;
|
||||||
|
}
|
||||||
|
WaitForDataAsync().Wait();
|
||||||
|
}
|
||||||
|
_topBuffer = new ArraySegment<byte>(topBuffer);
|
||||||
|
}
|
||||||
|
int actualCount = Math.Min(count, _topBuffer.Count);
|
||||||
|
Buffer.BlockCopy(_topBuffer.Array, _topBuffer.Offset, buffer, offset, actualCount);
|
||||||
|
_topBuffer = new ArraySegment<byte>(_topBuffer.Array,
|
||||||
|
_topBuffer.Offset + actualCount,
|
||||||
|
_topBuffer.Count - actualCount);
|
||||||
|
totalRead += actualCount;
|
||||||
|
offset += actualCount;
|
||||||
|
count -= actualCount;
|
||||||
|
}
|
||||||
|
while (count > 0 && (_topBuffer.Count > 0 || _bufferedData.Count > 0));
|
||||||
|
// Keep reading while there is more data available and we have more space to put it in.
|
||||||
|
return totalRead;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_readLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#if NET45
|
||||||
|
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||||||
|
{
|
||||||
|
// TODO: This option doesn't preserve the state object.
|
||||||
|
// return ReadAsync(buffer, offset, count);
|
||||||
|
return base.BeginRead(buffer, offset, count, callback, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int EndRead(IAsyncResult asyncResult)
|
||||||
|
{
|
||||||
|
// return ((Task<int>)asyncResult).Result;
|
||||||
|
return base.EndRead(asyncResult);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
public async override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
VerifyBuffer(buffer, offset, count, allowEmpty: false);
|
||||||
|
CancellationTokenRegistration registration = cancellationToken.Register(Abort);
|
||||||
|
await _readLock.WaitAsync(cancellationToken);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int totalRead = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
// Don't drained buffered data on abort.
|
||||||
|
CheckAborted();
|
||||||
|
if (_topBuffer.Count <= 0)
|
||||||
|
{
|
||||||
|
byte[] topBuffer = null;
|
||||||
|
while (!_bufferedData.TryDequeue(out topBuffer))
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
CheckAborted();
|
||||||
|
// Graceful close
|
||||||
|
return totalRead;
|
||||||
|
}
|
||||||
|
await WaitForDataAsync();
|
||||||
|
}
|
||||||
|
_topBuffer = new ArraySegment<byte>(topBuffer);
|
||||||
|
}
|
||||||
|
int actualCount = Math.Min(count, _topBuffer.Count);
|
||||||
|
Buffer.BlockCopy(_topBuffer.Array, _topBuffer.Offset, buffer, offset, actualCount);
|
||||||
|
_topBuffer = new ArraySegment<byte>(_topBuffer.Array,
|
||||||
|
_topBuffer.Offset + actualCount,
|
||||||
|
_topBuffer.Count - actualCount);
|
||||||
|
totalRead += actualCount;
|
||||||
|
offset += actualCount;
|
||||||
|
count -= actualCount;
|
||||||
|
}
|
||||||
|
while (count > 0 && (_topBuffer.Count > 0 || _bufferedData.Count > 0));
|
||||||
|
// Keep reading while there is more data available and we have more space to put it in.
|
||||||
|
return totalRead;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
registration.Dispose();
|
||||||
|
_readLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called under write-lock.
|
||||||
|
private void FirstWrite()
|
||||||
|
{
|
||||||
|
if (_firstWrite)
|
||||||
|
{
|
||||||
|
_firstWrite = false;
|
||||||
|
_onFirstWrite();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write with count 0 will still trigger OnFirstWrite
|
||||||
|
public override void Write(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
VerifyBuffer(buffer, offset, count, allowEmpty: true);
|
||||||
|
CheckDisposed();
|
||||||
|
|
||||||
|
_writeLock.Wait();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
FirstWrite();
|
||||||
|
if (count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Copies are necessary because we don't know what the caller is going to do with the buffer afterwards.
|
||||||
|
byte[] internalBuffer = new byte[count];
|
||||||
|
Buffer.BlockCopy(buffer, offset, internalBuffer, 0, count);
|
||||||
|
_bufferedData.Enqueue(internalBuffer);
|
||||||
|
|
||||||
|
SignalDataAvailable();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_writeLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#if NET45
|
||||||
|
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||||||
|
{
|
||||||
|
Write(buffer, offset, count);
|
||||||
|
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>(state);
|
||||||
|
tcs.TrySetResult(null);
|
||||||
|
IAsyncResult result = tcs.Task;
|
||||||
|
if (callback != null)
|
||||||
|
{
|
||||||
|
callback(result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void EndWrite(IAsyncResult asyncResult)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
VerifyBuffer(buffer, offset, count, allowEmpty: true);
|
||||||
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
|
||||||
|
tcs.TrySetCanceled();
|
||||||
|
return tcs.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
Write(buffer, offset, count);
|
||||||
|
return Task.FromResult<object>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void VerifyBuffer(byte[] buffer, int offset, int count, bool allowEmpty)
|
||||||
|
{
|
||||||
|
if (buffer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("buffer");
|
||||||
|
}
|
||||||
|
if (offset < 0 || offset > buffer.Length)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException("offset", offset, string.Empty);
|
||||||
|
}
|
||||||
|
if (count < 0 || count > buffer.Length - offset
|
||||||
|
|| (!allowEmpty && count == 0))
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException("count", count, string.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SignalDataAvailable()
|
||||||
|
{
|
||||||
|
// Dispatch, as TrySetResult will synchronously execute the waiters callback and block our Write.
|
||||||
|
Task.Factory.StartNew(() => _readWaitingForData.TrySetResult(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task WaitForDataAsync()
|
||||||
|
{
|
||||||
|
// Prevent race with Dispose
|
||||||
|
lock (_signalReadLock)
|
||||||
|
{
|
||||||
|
_readWaitingForData = new TaskCompletionSource<object>();
|
||||||
|
|
||||||
|
if (!_bufferedData.IsEmpty || _disposed)
|
||||||
|
{
|
||||||
|
// Race, data could have arrived before we created the TCS.
|
||||||
|
_readWaitingForData.TrySetResult(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _readWaitingForData.Task;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Abort()
|
||||||
|
{
|
||||||
|
Abort(new OperationCanceledException());
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Abort(Exception innerException)
|
||||||
|
{
|
||||||
|
Contract.Requires(innerException != null);
|
||||||
|
_aborted = true;
|
||||||
|
_abortException = innerException;
|
||||||
|
Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckAborted()
|
||||||
|
{
|
||||||
|
if (_aborted)
|
||||||
|
{
|
||||||
|
throw new IOException(string.Empty, _abortException);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "_writeLock", Justification = "ODEs from the locks would mask IOEs from abort.")]
|
||||||
|
[SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "_readLock", Justification = "Data can still be read unless we get aborted.")]
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
// Prevent race with WaitForDataAsync
|
||||||
|
lock (_signalReadLock)
|
||||||
|
{
|
||||||
|
// Throw for further writes, but not reads. Allow reads to drain the buffered data and then return 0 for further reads.
|
||||||
|
_disposed = true;
|
||||||
|
_readWaitingForData.TrySetResult(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckDisposed()
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException(GetType().FullName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,19 +1,5 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc.
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
// All Rights Reserved
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
//
|
|
||||||
// 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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
@ -53,7 +39,7 @@ namespace Microsoft.AspNet.TestHost
|
||||||
Action<HttpRequest> onSendingRequest = null)
|
Action<HttpRequest> onSendingRequest = null)
|
||||||
{
|
{
|
||||||
var request = CreateRequest(method, uri, headers, body);
|
var request = CreateRequest(method, uri, headers, body);
|
||||||
var response = new ResponseInformation();
|
var response = new ResponseFeature();
|
||||||
|
|
||||||
var features = new FeatureCollection();
|
var features = new FeatureCollection();
|
||||||
features.Add(typeof(IHttpRequestFeature), request);
|
features.Add(typeof(IHttpRequestFeature), request);
|
||||||
|
|
@ -76,7 +62,7 @@ namespace Microsoft.AspNet.TestHost
|
||||||
IDictionary<string, string[]> headers,
|
IDictionary<string, string[]> headers,
|
||||||
Stream body)
|
Stream body)
|
||||||
{
|
{
|
||||||
var request = new RequestInformation();
|
var request = new RequestFeature();
|
||||||
request.Method = method;
|
request.Method = method;
|
||||||
request.Scheme = uri.Scheme;
|
request.Scheme = uri.Scheme;
|
||||||
request.Path = PathString.FromUriComponent(uri).Value;
|
request.Path = PathString.FromUriComponent(uri).Value;
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,17 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc.
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
// All Rights Reserved
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
//
|
|
||||||
// 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;
|
||||||
using System.Collections.Generic;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Builder;
|
using Microsoft.AspNet.Builder;
|
||||||
using Microsoft.AspNet.Hosting;
|
using Microsoft.AspNet.Hosting;
|
||||||
using Microsoft.AspNet.Hosting.Server;
|
using Microsoft.AspNet.Hosting.Server;
|
||||||
using Microsoft.AspNet.Hosting.Startup;
|
|
||||||
using Microsoft.Framework.ConfigurationModel;
|
using Microsoft.Framework.ConfigurationModel;
|
||||||
using Microsoft.Framework.DependencyInjection;
|
using Microsoft.Framework.DependencyInjection;
|
||||||
using Microsoft.Framework.DependencyInjection.Fallback;
|
using Microsoft.Framework.DependencyInjection.Fallback;
|
||||||
using Microsoft.Framework.Runtime;
|
using Microsoft.Framework.Runtime;
|
||||||
|
using Microsoft.Framework.Runtime.Infrastructure;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.TestHost
|
namespace Microsoft.AspNet.TestHost
|
||||||
{
|
{
|
||||||
|
|
@ -35,6 +21,8 @@ namespace Microsoft.AspNet.TestHost
|
||||||
private static readonly ServerInformation ServerInfo = new ServerInformation();
|
private static readonly ServerInformation ServerInfo = new ServerInformation();
|
||||||
private Func<object, Task> _appDelegate;
|
private Func<object, Task> _appDelegate;
|
||||||
private TestClient _handler;
|
private TestClient _handler;
|
||||||
|
private IDisposable _appInstance;
|
||||||
|
private bool _disposed = false;
|
||||||
|
|
||||||
public TestServer(IConfiguration config, IServiceProvider serviceProvider, Action<IBuilder> appStartup)
|
public TestServer(IConfiguration config, IServiceProvider serviceProvider, Action<IBuilder> appStartup)
|
||||||
{
|
{
|
||||||
|
|
@ -54,16 +42,26 @@ namespace Microsoft.AspNet.TestHost
|
||||||
};
|
};
|
||||||
|
|
||||||
var engine = serviceProvider.GetService<IHostingEngine>();
|
var engine = serviceProvider.GetService<IHostingEngine>();
|
||||||
var disposable = engine.Start(hostContext);
|
_appInstance = engine.Start(hostContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
//public static TestServer Create<TStartup>(IServiceProvider provider)
|
public TestClient Handler
|
||||||
//{
|
{
|
||||||
// var startupLoader = new StartupLoader(provider, new NullStartupLoader());
|
get
|
||||||
// var name = typeof(TStartup).AssemblyQualifiedName;
|
{
|
||||||
// var diagnosticMessages = new List<string>();
|
if (_handler == null)
|
||||||
// return Create(provider, startupLoader.LoadStartup(name, "Test", diagnosticMessages));
|
{
|
||||||
//}
|
_handler = new TestClient(Invoke);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _handler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TestServer Create(Action<IBuilder> app)
|
||||||
|
{
|
||||||
|
return Create(provider: CallContextServiceLocator.Locator.ServiceProvider, app: app);
|
||||||
|
}
|
||||||
|
|
||||||
public static TestServer Create(IServiceProvider provider, Action<IBuilder> app)
|
public static TestServer Create(IServiceProvider provider, Action<IBuilder> app)
|
||||||
{
|
{
|
||||||
|
|
@ -77,17 +75,24 @@ namespace Microsoft.AspNet.TestHost
|
||||||
return new TestServer(config, serviceProvider, app);
|
return new TestServer(config, serviceProvider, app);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TestClient Handler
|
public HttpMessageHandler CreateHandler()
|
||||||
{
|
{
|
||||||
get
|
return new ClientHandler(Invoke);
|
||||||
{
|
}
|
||||||
if (_handler == null)
|
|
||||||
{
|
|
||||||
_handler = new TestClient(_appDelegate);
|
|
||||||
}
|
|
||||||
|
|
||||||
return _handler;
|
public HttpClient CreateClient()
|
||||||
}
|
{
|
||||||
|
return new HttpClient(CreateHandler()) { BaseAddress = new Uri("http://localhost/") };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Begins constructing a request message for submission.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path"></param>
|
||||||
|
/// <returns><see cref="RequestBuilder"/> to use in constructing additional request details.</returns>
|
||||||
|
public RequestBuilder CreateRequest(string path)
|
||||||
|
{
|
||||||
|
return new RequestBuilder(this, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IServerInformation Initialize(IConfiguration configuration)
|
public IServerInformation Initialize(IConfiguration configuration)
|
||||||
|
|
@ -107,11 +112,19 @@ namespace Microsoft.AspNet.TestHost
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task Invoke(object env)
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException(GetType().FullName);
|
||||||
|
}
|
||||||
|
return _appDelegate(env);
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
// IServerFactory.Start needs to return an IDisposable. Typically this IDisposable instance is used to
|
_disposed = true;
|
||||||
// clear any server resources when tearing down the host. In our case we don't have anything to clear
|
_appInstance.Dispose();
|
||||||
// so we just implement IDisposable and do nothing.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ServerInformation : IServerInformation
|
private class ServerInformation : IServerInformation
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
{
|
{
|
||||||
"version" : "1.0.0-*",
|
"version" : "1.0.0-*",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.AspNet.Hosting": ""
|
"Microsoft.AspNet.Hosting": "",
|
||||||
|
"System.Net.Http": "4.0.0.0"
|
||||||
},
|
},
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
"net45": { },
|
"net45": { },
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,231 @@
|
||||||
|
// 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.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.FeatureModel;
|
||||||
|
using Microsoft.AspNet.Http;
|
||||||
|
using Microsoft.AspNet.HttpFeature;
|
||||||
|
using Microsoft.AspNet.PipelineCore;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.TestHost
|
||||||
|
{
|
||||||
|
public class ClientHandlerTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public Task ExpectedKeysAreAvailable()
|
||||||
|
{
|
||||||
|
var handler = new ClientHandler(env =>
|
||||||
|
{
|
||||||
|
var context = new DefaultHttpContext((IFeatureCollection)env);
|
||||||
|
|
||||||
|
// TODO: Assert.True(context.RequestAborted.CanBeCanceled);
|
||||||
|
Assert.Equal("HTTP/1.1", context.Request.Protocol);
|
||||||
|
Assert.Equal("GET", context.Request.Method);
|
||||||
|
Assert.Equal("https", context.Request.Scheme);
|
||||||
|
Assert.Equal(string.Empty, context.Request.PathBase.Value);
|
||||||
|
Assert.Equal("/A/Path/and/file.txt", context.Request.Path.Value);
|
||||||
|
Assert.Equal("?and=query", context.Request.QueryString.Value);
|
||||||
|
Assert.NotNull(context.Request.Body);
|
||||||
|
Assert.NotNull(context.Request.Headers);
|
||||||
|
Assert.NotNull(context.Response.Headers);
|
||||||
|
Assert.NotNull(context.Response.Body);
|
||||||
|
Assert.Equal(200, context.Response.StatusCode);
|
||||||
|
Assert.Null(context.GetFeature<IHttpResponseFeature>().ReasonPhrase);
|
||||||
|
Assert.Equal("example.com", context.Request.Host.Value);
|
||||||
|
|
||||||
|
return Task.FromResult(0);
|
||||||
|
});
|
||||||
|
var httpClient = new HttpClient(handler);
|
||||||
|
return httpClient.GetAsync("https://example.com/A/Path/and/file.txt?and=query");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ResubmitRequestWorks()
|
||||||
|
{
|
||||||
|
int requestCount = 1;
|
||||||
|
var handler = new ClientHandler(env =>
|
||||||
|
{
|
||||||
|
var context = new DefaultHttpContext((IFeatureCollection)env);
|
||||||
|
int read = context.Request.Body.Read(new byte[100], 0, 100);
|
||||||
|
Assert.Equal(11, read);
|
||||||
|
|
||||||
|
context.Response.Headers["TestHeader"] = "TestValue:" + requestCount++;
|
||||||
|
return Task.FromResult(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
HttpMessageInvoker invoker = new HttpMessageInvoker(handler);
|
||||||
|
HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Post, "https://example.com/");
|
||||||
|
message.Content = new StringContent("Hello World");
|
||||||
|
|
||||||
|
HttpResponseMessage response = await invoker.SendAsync(message, CancellationToken.None);
|
||||||
|
Assert.Equal("TestValue:1", response.Headers.GetValues("TestHeader").First());
|
||||||
|
|
||||||
|
response = await invoker.SendAsync(message, CancellationToken.None);
|
||||||
|
Assert.Equal("TestValue:2", response.Headers.GetValues("TestHeader").First());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task MiddlewareOnlySetsHeaders()
|
||||||
|
{
|
||||||
|
var handler = new ClientHandler(env =>
|
||||||
|
{
|
||||||
|
var context = new DefaultHttpContext((IFeatureCollection)env);
|
||||||
|
|
||||||
|
context.Response.Headers["TestHeader"] = "TestValue";
|
||||||
|
return Task.FromResult(0);
|
||||||
|
});
|
||||||
|
var httpClient = new HttpClient(handler);
|
||||||
|
HttpResponseMessage response = await httpClient.GetAsync("https://example.com/");
|
||||||
|
Assert.Equal("TestValue", response.Headers.GetValues("TestHeader").First());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task BlockingMiddlewareShouldNotBlockClient()
|
||||||
|
{
|
||||||
|
ManualResetEvent block = new ManualResetEvent(false);
|
||||||
|
var handler = new ClientHandler(env =>
|
||||||
|
{
|
||||||
|
block.WaitOne();
|
||||||
|
return Task.FromResult(0);
|
||||||
|
});
|
||||||
|
var httpClient = new HttpClient(handler);
|
||||||
|
Task<HttpResponseMessage> task = httpClient.GetAsync("https://example.com/");
|
||||||
|
Assert.False(task.IsCompleted);
|
||||||
|
Assert.False(task.Wait(50));
|
||||||
|
block.Set();
|
||||||
|
HttpResponseMessage response = await task;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task HeadersAvailableBeforeBodyFinished()
|
||||||
|
{
|
||||||
|
ManualResetEvent block = new ManualResetEvent(false);
|
||||||
|
var handler = new ClientHandler(async env =>
|
||||||
|
{
|
||||||
|
var context = new DefaultHttpContext((IFeatureCollection)env);
|
||||||
|
context.Response.Headers["TestHeader"] = "TestValue";
|
||||||
|
await context.Response.WriteAsync("BodyStarted,");
|
||||||
|
block.WaitOne();
|
||||||
|
await context.Response.WriteAsync("BodyFinished");
|
||||||
|
});
|
||||||
|
var httpClient = new HttpClient(handler);
|
||||||
|
HttpResponseMessage response = await httpClient.GetAsync("https://example.com/",
|
||||||
|
HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
Assert.Equal("TestValue", response.Headers.GetValues("TestHeader").First());
|
||||||
|
block.Set();
|
||||||
|
Assert.Equal("BodyStarted,BodyFinished", await response.Content.ReadAsStringAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task FlushSendsHeaders()
|
||||||
|
{
|
||||||
|
ManualResetEvent block = new ManualResetEvent(false);
|
||||||
|
var handler = new ClientHandler(async env =>
|
||||||
|
{
|
||||||
|
var context = new DefaultHttpContext((IFeatureCollection)env);
|
||||||
|
context.Response.Headers["TestHeader"] = "TestValue";
|
||||||
|
context.Response.Body.Flush();
|
||||||
|
block.WaitOne();
|
||||||
|
await context.Response.WriteAsync("BodyFinished");
|
||||||
|
});
|
||||||
|
var httpClient = new HttpClient(handler);
|
||||||
|
HttpResponseMessage response = await httpClient.GetAsync("https://example.com/",
|
||||||
|
HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
Assert.Equal("TestValue", response.Headers.GetValues("TestHeader").First());
|
||||||
|
block.Set();
|
||||||
|
Assert.Equal("BodyFinished", await response.Content.ReadAsStringAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ClientDisposalCloses()
|
||||||
|
{
|
||||||
|
ManualResetEvent block = new ManualResetEvent(false);
|
||||||
|
var handler = new ClientHandler(env =>
|
||||||
|
{
|
||||||
|
var context = new DefaultHttpContext((IFeatureCollection)env);
|
||||||
|
context.Response.Headers["TestHeader"] = "TestValue";
|
||||||
|
context.Response.Body.Flush();
|
||||||
|
block.WaitOne();
|
||||||
|
return Task.FromResult(0);
|
||||||
|
});
|
||||||
|
var httpClient = new HttpClient(handler);
|
||||||
|
HttpResponseMessage response = await httpClient.GetAsync("https://example.com/",
|
||||||
|
HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
Assert.Equal("TestValue", response.Headers.GetValues("TestHeader").First());
|
||||||
|
Stream responseStream = await response.Content.ReadAsStreamAsync();
|
||||||
|
Task<int> readTask = responseStream.ReadAsync(new byte[100], 0, 100);
|
||||||
|
Assert.False(readTask.IsCompleted);
|
||||||
|
responseStream.Dispose();
|
||||||
|
Thread.Sleep(50);
|
||||||
|
Assert.True(readTask.IsCompleted);
|
||||||
|
Assert.Equal(0, readTask.Result);
|
||||||
|
block.Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ClientCancellationAborts()
|
||||||
|
{
|
||||||
|
ManualResetEvent block = new ManualResetEvent(false);
|
||||||
|
var handler = new ClientHandler(env =>
|
||||||
|
{
|
||||||
|
var context = new DefaultHttpContext((IFeatureCollection)env);
|
||||||
|
context.Response.Headers["TestHeader"] = "TestValue";
|
||||||
|
context.Response.Body.Flush();
|
||||||
|
block.WaitOne();
|
||||||
|
return Task.FromResult(0);
|
||||||
|
});
|
||||||
|
var httpClient = new HttpClient(handler);
|
||||||
|
HttpResponseMessage response = await httpClient.GetAsync("https://example.com/",
|
||||||
|
HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
Assert.Equal("TestValue", response.Headers.GetValues("TestHeader").First());
|
||||||
|
Stream responseStream = await response.Content.ReadAsStreamAsync();
|
||||||
|
CancellationTokenSource cts = new CancellationTokenSource();
|
||||||
|
Task<int> readTask = responseStream.ReadAsync(new byte[100], 0, 100, cts.Token);
|
||||||
|
Assert.False(readTask.IsCompleted);
|
||||||
|
cts.Cancel();
|
||||||
|
Thread.Sleep(50);
|
||||||
|
Assert.True(readTask.IsCompleted);
|
||||||
|
Assert.True(readTask.IsFaulted);
|
||||||
|
block.Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public Task ExceptionBeforeFirstWriteIsReported()
|
||||||
|
{
|
||||||
|
var handler = new ClientHandler(env =>
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Test Exception");
|
||||||
|
});
|
||||||
|
var httpClient = new HttpClient(handler);
|
||||||
|
return Assert.ThrowsAsync<InvalidOperationException>(() => httpClient.GetAsync("https://example.com/",
|
||||||
|
HttpCompletionOption.ResponseHeadersRead));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExceptionAfterFirstWriteIsReported()
|
||||||
|
{
|
||||||
|
ManualResetEvent block = new ManualResetEvent(false);
|
||||||
|
var handler = new ClientHandler(async env =>
|
||||||
|
{
|
||||||
|
var context = new DefaultHttpContext((IFeatureCollection)env);
|
||||||
|
context.Response.Headers["TestHeader"] = "TestValue";
|
||||||
|
await context.Response.WriteAsync("BodyStarted");
|
||||||
|
block.WaitOne();
|
||||||
|
throw new InvalidOperationException("Test Exception");
|
||||||
|
});
|
||||||
|
var httpClient = new HttpClient(handler);
|
||||||
|
HttpResponseMessage response = await httpClient.GetAsync("https://example.com/",
|
||||||
|
HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
Assert.Equal("TestValue", response.Headers.GetValues("TestHeader").First());
|
||||||
|
block.Set();
|
||||||
|
var ex = await Assert.ThrowsAsync<HttpRequestException>(() => response.Content.ReadAsStringAsync());
|
||||||
|
Assert.IsType<InvalidOperationException>(ex.GetBaseException());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,11 +21,13 @@
|
||||||
<Content Include="project.json" />
|
<Content Include="project.json" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="ClientHandlerTests.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="ResponseInformationTests.cs" />
|
<Compile Include="RequestBuilderTests.cs" />
|
||||||
|
<Compile Include="ResponseFeatureTests.cs" />
|
||||||
<Compile Include="TestApplicationEnvironment.cs" />
|
<Compile Include="TestApplicationEnvironment.cs" />
|
||||||
<Compile Include="TestClientTests.cs" />
|
<Compile Include="TestClientTests.cs" />
|
||||||
<Compile Include="TestServerTests.cs" />
|
<Compile Include="TestServerTests.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||||
</Project>
|
</Project>
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
// 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 Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.TestHost
|
||||||
|
{
|
||||||
|
public class RequestBuilderTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void AddRequestHeader()
|
||||||
|
{
|
||||||
|
TestServer server = TestServer.Create(app => { });
|
||||||
|
server.CreateRequest("/")
|
||||||
|
.AddHeader("Host", "MyHost:90")
|
||||||
|
.And(request =>
|
||||||
|
{
|
||||||
|
Assert.Equal("MyHost:90", request.Headers.Host.ToString());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddContentHeaders()
|
||||||
|
{
|
||||||
|
TestServer server = TestServer.Create(app => { });
|
||||||
|
server.CreateRequest("/")
|
||||||
|
.AddHeader("Content-Type", "Test/Value")
|
||||||
|
.And(request =>
|
||||||
|
{
|
||||||
|
Assert.NotNull(request.Content);
|
||||||
|
Assert.Equal("Test/Value", request.Content.Headers.ContentType.ToString());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
// 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 Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.TestHost
|
||||||
|
{
|
||||||
|
public class ResponseFeatureTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void StatusCode_DefaultsTo200()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var responseInformation = new ResponseFeature();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(200, responseInformation.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,34 +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.
|
|
||||||
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.TestHost.Tests
|
|
||||||
{
|
|
||||||
public class ResponseInformationTests
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void StatusCode_DefaultsTo200()
|
|
||||||
{
|
|
||||||
// Arrange & Act
|
|
||||||
var responseInformation = new ResponseInformation();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(200, responseInformation.StatusCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,25 +1,11 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc.
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
// All Rights Reserved
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
//
|
|
||||||
// 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;
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
using Microsoft.Framework.Runtime;
|
using Microsoft.Framework.Runtime;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.TestHost.Tests
|
namespace Microsoft.AspNet.TestHost
|
||||||
{
|
{
|
||||||
public class TestApplicationEnvironment : IApplicationEnvironment
|
public class TestApplicationEnvironment : IApplicationEnvironment
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,5 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc.
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
// All Rights Reserved
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
//
|
|
||||||
// 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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
@ -26,7 +12,7 @@ using Microsoft.Framework.DependencyInjection.Fallback;
|
||||||
using Microsoft.Framework.Runtime;
|
using Microsoft.Framework.Runtime;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.TestHost.Tests
|
namespace Microsoft.AspNet.TestHost
|
||||||
{
|
{
|
||||||
public class TestClientTests
|
public class TestClientTests
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,9 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc.
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
// All Rights Reserved
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
//
|
|
||||||
// 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;
|
||||||
using System.IO;
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Builder;
|
using Microsoft.AspNet.Builder;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
|
|
@ -25,7 +12,7 @@ using Microsoft.Framework.DependencyInjection.Fallback;
|
||||||
using Microsoft.Framework.Runtime;
|
using Microsoft.Framework.Runtime;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.TestHost.Tests
|
namespace Microsoft.AspNet.TestHost
|
||||||
{
|
{
|
||||||
public class TestServerTests
|
public class TestServerTests
|
||||||
{
|
{
|
||||||
|
|
@ -41,24 +28,6 @@ namespace Microsoft.AspNet.TestHost.Tests
|
||||||
Assert.DoesNotThrow(() => TestServer.Create(services, app => { }));
|
Assert.DoesNotThrow(() => TestServer.Create(services, app => { }));
|
||||||
}
|
}
|
||||||
|
|
||||||
//[Fact]
|
|
||||||
//public async Task CreateWithGeneric()
|
|
||||||
//{
|
|
||||||
// // Arrange
|
|
||||||
// var services = new ServiceCollection()
|
|
||||||
// .AddSingleton<IApplicationEnvironment, TestApplicationEnvironment>()
|
|
||||||
// .BuildServiceProvider();
|
|
||||||
|
|
||||||
// var server = TestServer.Create<Startup>(services);
|
|
||||||
// var client = server.Handler;
|
|
||||||
|
|
||||||
// // Act
|
|
||||||
// var response = await client.GetAsync("http://any");
|
|
||||||
|
|
||||||
// // Assert
|
|
||||||
// Assert.Equal("Startup", new StreamReader(response.Body).ReadToEnd());
|
|
||||||
//}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ThrowsIfNoApplicationEnvironmentIsRegisteredWithTheProvider()
|
public void ThrowsIfNoApplicationEnvironmentIsRegisteredWithTheProvider()
|
||||||
{
|
{
|
||||||
|
|
@ -70,6 +39,72 @@ namespace Microsoft.AspNet.TestHost.Tests
|
||||||
Assert.Throws<Exception>(() => TestServer.Create(services, new Startup().Configuration));
|
Assert.Throws<Exception>(() => TestServer.Create(services, new Startup().Configuration));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateInvokesApp()
|
||||||
|
{
|
||||||
|
TestServer server = TestServer.Create(app =>
|
||||||
|
{
|
||||||
|
app.Run(context =>
|
||||||
|
{
|
||||||
|
return context.Response.WriteAsync("CreateInvokesApp");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
string result = await server.CreateClient().GetStringAsync("/path");
|
||||||
|
Assert.Equal("CreateInvokesApp", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DisposeStreamIgnored()
|
||||||
|
{
|
||||||
|
TestServer server = TestServer.Create(app =>
|
||||||
|
{
|
||||||
|
app.Run(async context =>
|
||||||
|
{
|
||||||
|
await context.Response.WriteAsync("Response");
|
||||||
|
context.Response.Body.Dispose();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
HttpResponseMessage result = await server.CreateClient().GetAsync("/");
|
||||||
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
Assert.Equal("Response", await result.Content.ReadAsStringAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DisposedServerThrows()
|
||||||
|
{
|
||||||
|
TestServer server = TestServer.Create(app =>
|
||||||
|
{
|
||||||
|
app.Run(async context =>
|
||||||
|
{
|
||||||
|
await context.Response.WriteAsync("Response");
|
||||||
|
context.Response.Body.Dispose();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
HttpResponseMessage result = await server.CreateClient().GetAsync("/");
|
||||||
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
server.Dispose();
|
||||||
|
await Assert.ThrowsAsync<ObjectDisposedException>(() => server.CreateClient().GetAsync("/"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CancelAborts()
|
||||||
|
{
|
||||||
|
TestServer server = TestServer.Create(app =>
|
||||||
|
{
|
||||||
|
app.Run(context =>
|
||||||
|
{
|
||||||
|
TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
|
||||||
|
tcs.SetCanceled();
|
||||||
|
return tcs.Task;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.Throws<AggregateException>(() => { string result = server.CreateClient().GetStringAsync("/path").Result; });
|
||||||
|
}
|
||||||
|
|
||||||
public class Startup
|
public class Startup
|
||||||
{
|
{
|
||||||
public void Configuration(IBuilder builder)
|
public void Configuration(IBuilder builder)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue