Make lower level public API. Layering.
This commit is contained in:
parent
af1a97cd7c
commit
60f09fbc93
|
|
@ -1,55 +1,69 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNet.Server.WebListener;
|
using Microsoft.AspNet.Server.WebListener;
|
||||||
|
|
||||||
using AppFunc = System.Func<System.Collections.Generic.IDictionary<string, object>, System.Threading.Tasks.Task>;
|
namespace HelloWorld
|
||||||
|
|
||||||
public class Program
|
|
||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
public class Program
|
||||||
{
|
{
|
||||||
using (CreateServer(new AppFunc(HelloWorldApp)))
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Running, press enter to exit...");
|
using (OwinWebListener listener = new OwinWebListener())
|
||||||
Console.ReadLine();
|
{
|
||||||
|
listener.UrlPrefixes.Add(UrlPrefix.Create("http://localhost:8080"));
|
||||||
|
listener.Start();
|
||||||
|
|
||||||
|
Console.WriteLine("Running...");
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
RequestContext context = listener.GetContextAsync().Result;
|
||||||
|
Console.WriteLine("Accepted");
|
||||||
|
|
||||||
|
// Context:
|
||||||
|
// context.User;
|
||||||
|
// context.DisconnectToken
|
||||||
|
// context.Dispose()
|
||||||
|
// context.Abort();
|
||||||
|
|
||||||
|
// Request
|
||||||
|
// context.Request.ProtocolVersion
|
||||||
|
// context.Request.IsLocal
|
||||||
|
// context.Request.Headers // TODO: Header helpers?
|
||||||
|
// context.Request.Method
|
||||||
|
// context.Request.Body
|
||||||
|
// Content-Length - long?
|
||||||
|
// Content-Type - string
|
||||||
|
// IsSecureConnection
|
||||||
|
// HasEntityBody
|
||||||
|
|
||||||
|
// TODO: Request fields
|
||||||
|
// Content-Encoding - Encoding
|
||||||
|
// Host
|
||||||
|
// Client certs - GetCertAsync, CertErrors
|
||||||
|
// Cookies
|
||||||
|
// KeepAlive
|
||||||
|
// QueryString (parsed)
|
||||||
|
// RequestTraceIdentifier
|
||||||
|
// RawUrl
|
||||||
|
// URI
|
||||||
|
// IsWebSocketRequest
|
||||||
|
// LocalEndpoint vs LocalIP & LocalPort
|
||||||
|
// RemoteEndpoint vs RemoteIP & RemotePort
|
||||||
|
// AcceptTypes string[]
|
||||||
|
// ServiceName
|
||||||
|
// TransportContext
|
||||||
|
|
||||||
|
// Response
|
||||||
|
byte[] bytes = Encoding.ASCII.GetBytes("Hello World: " + DateTime.Now);
|
||||||
|
|
||||||
|
context.Response.ContentLength = bytes.Length;
|
||||||
|
context.Response.ContentType = "text/plain";
|
||||||
|
|
||||||
|
context.Response.Body.Write(bytes, 0, bytes.Length);
|
||||||
|
context.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static 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;
|
|
||||||
|
|
||||||
return OwinServerFactory.Create(app, properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Task HelloWorldApp(IDictionary<string, object> environment)
|
|
||||||
{
|
|
||||||
string responseText = "Hello World";
|
|
||||||
byte[] responseBytes = Encoding.UTF8.GetBytes(responseText);
|
|
||||||
|
|
||||||
// See http://owin.org/spec/owin-1.0.0.html for standard environment keys.
|
|
||||||
Stream responseStream = (Stream)environment["owin.ResponseBody"];
|
|
||||||
IDictionary<string, string[]> responseHeaders =
|
|
||||||
(IDictionary<string, string[]>)environment["owin.ResponseHeaders"];
|
|
||||||
|
|
||||||
responseHeaders["Content-Length"] = new string[] { responseBytes.Length.ToString(CultureInfo.InvariantCulture) };
|
|
||||||
responseHeaders["Content-Type"] = new string[] { "text/plain" };
|
|
||||||
|
|
||||||
return responseStream.WriteAsync(responseBytes, 0, responseBytes.Length);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -75,7 +75,7 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
// The native request queue
|
// The native request queue
|
||||||
private long? _requestQueueLength;
|
private long? _requestQueueLength;
|
||||||
|
|
||||||
internal OwinWebListener()
|
public OwinWebListener()
|
||||||
{
|
{
|
||||||
if (!UnsafeNclNativeMethods.HttpApi.Supported)
|
if (!UnsafeNclNativeMethods.HttpApi.Supported)
|
||||||
{
|
{
|
||||||
|
|
@ -149,7 +149,7 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal bool IsListening
|
public bool IsListening
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
|
@ -332,7 +332,7 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
CheckDisposed();
|
CheckDisposed();
|
||||||
|
|
||||||
|
|
@ -639,7 +639,7 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
}
|
}
|
||||||
|
|
||||||
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Disposed by callback")]
|
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Disposed by callback")]
|
||||||
internal Task<RequestContext> GetContextAsync()
|
public Task<RequestContext> GetContextAsync()
|
||||||
{
|
{
|
||||||
AsyncAcceptContext asyncResult = null;
|
AsyncAcceptContext asyncResult = null;
|
||||||
try
|
try
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,9 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
{
|
{
|
||||||
internal enum BoundaryType
|
internal enum BoundaryType
|
||||||
{
|
{
|
||||||
ContentLength = 0, // Content-Length: XXX
|
None = 0,
|
||||||
Chunked = 1, // Transfer-Encoding: chunked
|
Chunked = 1, // Transfer-Encoding: chunked
|
||||||
Raw = 2, // the app is responsible for sending the correct headers and body encoding
|
ContentLength = 2, // Content-Length: XXX
|
||||||
Multipart = 3,
|
Invalid = 3,
|
||||||
None = 4,
|
|
||||||
Invalid = 5,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// <copyright file="EntitySendFormat.cs" company="Microsoft">
|
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
// </copyright>
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Server.WebListener
|
|
||||||
{
|
|
||||||
internal enum EntitySendFormat
|
|
||||||
{
|
|
||||||
ContentLength = 0, // Content-Length: XXX
|
|
||||||
Chunked = 1, // Transfer-Encoding: chunked
|
|
||||||
/*
|
|
||||||
Raw = 2, // the app is responsible for sending the correct headers and body encoding
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -163,7 +163,19 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
{
|
{
|
||||||
if (_httpProtocolVersion == null)
|
if (_httpProtocolVersion == null)
|
||||||
{
|
{
|
||||||
_httpProtocolVersion = Request.Protocol;
|
if (Request.ProtocolVersion.Major == 1)
|
||||||
|
{
|
||||||
|
if (Request.ProtocolVersion.Minor == 1)
|
||||||
|
{
|
||||||
|
_httpProtocolVersion = "HTTP/1.1";
|
||||||
|
}
|
||||||
|
else if (Request.ProtocolVersion.Minor == 0)
|
||||||
|
{
|
||||||
|
_httpProtocolVersion = "HTTP/1.0";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_httpProtocolVersion = "HTTP/" + Request.ProtocolVersion.ToString(2);
|
||||||
}
|
}
|
||||||
return _httpProtocolVersion;
|
return _httpProtocolVersion;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Server.WebListener
|
namespace Microsoft.AspNet.Server.WebListener
|
||||||
{
|
{
|
||||||
internal sealed class Request : IDisposable
|
public sealed class Request
|
||||||
{
|
{
|
||||||
private RequestContext _requestContext;
|
private RequestContext _requestContext;
|
||||||
private NativeRequestContext _nativeRequestContext;
|
private NativeRequestContext _nativeRequestContext;
|
||||||
|
|
@ -47,7 +47,7 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
|
|
||||||
private IDictionary<string, string[]> _headers;
|
private IDictionary<string, string[]> _headers;
|
||||||
private BoundaryType _contentBoundaryType;
|
private BoundaryType _contentBoundaryType;
|
||||||
private long _contentLength;
|
private long? _contentLength;
|
||||||
private Stream _nativeStream;
|
private Stream _nativeStream;
|
||||||
|
|
||||||
private SocketAddress _localEndPoint;
|
private SocketAddress _localEndPoint;
|
||||||
|
|
@ -202,32 +202,30 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Move this to the constructor, that's where it will be called.
|
public long? ContentLength
|
||||||
internal long ContentLength64
|
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (_contentBoundaryType == BoundaryType.None)
|
if (_contentBoundaryType == BoundaryType.None)
|
||||||
{
|
{
|
||||||
string transferEncoding = Headers.Get(HttpKnownHeaderNames.TransferEncoding) ?? string.Empty;
|
string transferEncoding = Headers.Get(HttpKnownHeaderNames.TransferEncoding) ?? string.Empty;
|
||||||
if ("chunked".Equals(transferEncoding.Trim(), StringComparison.OrdinalIgnoreCase))
|
if (string.Equals("chunked", transferEncoding.Trim(), StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
_contentBoundaryType = BoundaryType.Chunked;
|
_contentBoundaryType = BoundaryType.Chunked;
|
||||||
_contentLength = -1;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_contentLength = 0;
|
|
||||||
_contentBoundaryType = BoundaryType.ContentLength;
|
|
||||||
string length = Headers.Get(HttpKnownHeaderNames.ContentLength) ?? string.Empty;
|
string length = Headers.Get(HttpKnownHeaderNames.ContentLength) ?? string.Empty;
|
||||||
if (length != null)
|
long value;
|
||||||
|
if (long.TryParse(length.Trim(), NumberStyles.None,
|
||||||
|
CultureInfo.InvariantCulture.NumberFormat, out value))
|
||||||
{
|
{
|
||||||
if (!long.TryParse(length.Trim(), NumberStyles.None,
|
_contentBoundaryType = BoundaryType.ContentLength;
|
||||||
CultureInfo.InvariantCulture.NumberFormat, out _contentLength))
|
_contentLength = value;
|
||||||
{
|
}
|
||||||
_contentLength = 0;
|
else
|
||||||
_contentBoundaryType = BoundaryType.Invalid;
|
{
|
||||||
}
|
_contentBoundaryType = BoundaryType.Invalid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -252,7 +250,6 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
{
|
{
|
||||||
if (_nativeStream == null)
|
if (_nativeStream == null)
|
||||||
{
|
{
|
||||||
// TODO: Move this to the constructor (or a lazy Env dictionary)
|
|
||||||
_nativeStream = HasEntityBody ? new RequestStream(RequestContext) : Stream.Null;
|
_nativeStream = HasEntityBody ? new RequestStream(RequestContext) : Stream.Null;
|
||||||
}
|
}
|
||||||
return _nativeStream;
|
return _nativeStream;
|
||||||
|
|
@ -281,7 +278,7 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal bool IsSecureConnection
|
public bool IsSecureConnection
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
|
@ -297,7 +294,7 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
internal Version ProtocolVersion
|
public Version ProtocolVersion
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
|
@ -305,34 +302,13 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Protocol
|
public bool HasEntityBody
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (_httpVersion.Major == 1)
|
// accessing the ContentLength property delay creates _contentBoundaryType
|
||||||
{
|
return (ContentLength.HasValue && ContentLength.Value > 0 && _contentBoundaryType == BoundaryType.ContentLength)
|
||||||
if (_httpVersion.Minor == 1)
|
|| _contentBoundaryType == BoundaryType.Chunked;
|
||||||
{
|
|
||||||
return "HTTP/1.1";
|
|
||||||
}
|
|
||||||
else if (_httpVersion.Minor == 0)
|
|
||||||
{
|
|
||||||
return "HTTP/1.0";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "HTTP/" + _httpVersion.ToString(2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Move this to the constructor
|
|
||||||
internal bool HasEntityBody
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
// accessing the ContentLength property delay creates m_BoundaryType
|
|
||||||
return (ContentLength64 > 0 && _contentBoundaryType == BoundaryType.ContentLength) ||
|
|
||||||
_contentBoundaryType == BoundaryType.Chunked || _contentBoundaryType == BoundaryType.Multipart;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -418,6 +394,14 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string ContentType
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Headers.Get(HttpKnownHeaderNames.ContentLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal IPrincipal User
|
internal IPrincipal User
|
||||||
{
|
{
|
||||||
get { return _user; }
|
get { return _user; }
|
||||||
|
|
@ -443,6 +427,11 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal UnsafeNclNativeMethods.HttpApi.HTTP_VERB GetKnownMethod()
|
||||||
|
{
|
||||||
|
return UnsafeNclNativeMethods.HttpApi.GetKnownVerb(RequestBuffer, OriginalBlobAddress);
|
||||||
|
}
|
||||||
|
|
||||||
#if NET45
|
#if NET45
|
||||||
// Populates the client certificate. The result may be null if there is no client cert.
|
// Populates the client certificate. The result may be null if there is no client cert.
|
||||||
// TODO: Does it make sense for this to be invoked multiple times (e.g. renegotiate)? Client and server code appear to
|
// TODO: Does it make sense for this to be invoked multiple times (e.g. renegotiate)? Client and server code appear to
|
||||||
|
|
@ -500,7 +489,7 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
}
|
}
|
||||||
|
|
||||||
// should only be called from RequestContext
|
// should only be called from RequestContext
|
||||||
public void Dispose()
|
internal void Dispose()
|
||||||
{
|
{
|
||||||
// TODO: Verbose log
|
// TODO: Verbose log
|
||||||
_isDisposed = true;
|
_isDisposed = true;
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Security.Principal;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Logging;
|
using Microsoft.AspNet.Logging;
|
||||||
|
|
@ -17,14 +17,11 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
{
|
{
|
||||||
using OpaqueFunc = Func<IDictionary<string, object>, Task>;
|
using OpaqueFunc = Func<IDictionary<string, object>, Task>;
|
||||||
|
|
||||||
internal sealed class RequestContext : IDisposable
|
public sealed class RequestContext : IDisposable
|
||||||
{
|
{
|
||||||
private static readonly string[] ZeroContentLength = new[] { "0" };
|
|
||||||
|
|
||||||
private OwinWebListener _server;
|
private OwinWebListener _server;
|
||||||
private Request _request;
|
private Request _request;
|
||||||
private Response _response;
|
private Response _response;
|
||||||
private CancellationTokenSource _cts;
|
|
||||||
private NativeRequestContext _memoryBlob;
|
private NativeRequestContext _memoryBlob;
|
||||||
private OpaqueFunc _opaqueCallback;
|
private OpaqueFunc _opaqueCallback;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
@ -38,11 +35,10 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
_memoryBlob = memoryBlob;
|
_memoryBlob = memoryBlob;
|
||||||
_request = new Request(this, _memoryBlob);
|
_request = new Request(this, _memoryBlob);
|
||||||
_response = new Response(this);
|
_response = new Response(this);
|
||||||
_cts = new CancellationTokenSource();
|
|
||||||
_request.ReleasePins();
|
_request.ReleasePins();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Request Request
|
public Request Request
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
|
@ -50,7 +46,7 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Response Response
|
public Response Response
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
|
@ -58,7 +54,12 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal CancellationToken DisconnectToken
|
public IPrincipal User
|
||||||
|
{
|
||||||
|
get { return _request.User; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public CancellationToken DisconnectToken
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
|
@ -146,32 +147,20 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_request.Dispose();
|
_request.Dispose();
|
||||||
_cts.Dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Abort()
|
public void Abort()
|
||||||
{
|
{
|
||||||
|
// May be called from Dispose() code path, don't check _disposed.
|
||||||
// TODO: Verbose log
|
// TODO: Verbose log
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
try
|
if (_disconnectRegistration.HasValue)
|
||||||
{
|
|
||||||
_cts.Cancel();
|
|
||||||
}
|
|
||||||
catch (ObjectDisposedException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
catch (AggregateException)
|
|
||||||
{
|
{
|
||||||
|
_disconnectRegistration.Value.Dispose();
|
||||||
}
|
}
|
||||||
ForceCancelRequest(RequestQueueHandle, _request.RequestId);
|
ForceCancelRequest(RequestQueueHandle, _request.RequestId);
|
||||||
_request.Dispose();
|
_request.Dispose();
|
||||||
_cts.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal UnsafeNclNativeMethods.HttpApi.HTTP_VERB GetKnownMethod()
|
|
||||||
{
|
|
||||||
return UnsafeNclNativeMethods.HttpApi.GetKnownVerb(Request.RequestBuffer, Request.OriginalBlobAddress);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is only called while processing incoming requests. We don't have to worry about cancelling
|
// This is only called while processing incoming requests. We don't have to worry about cancelling
|
||||||
|
|
@ -256,13 +245,5 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
|
|
||||||
return opaqueEnv;
|
return opaqueEnv;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void SetFatalResponse()
|
|
||||||
{
|
|
||||||
Response.StatusCode = 500;
|
|
||||||
Response.ReasonPhrase = string.Empty;
|
|
||||||
Response.Headers.Clear();
|
|
||||||
Response.Headers.Add(HttpKnownHeaderNames.ContentLength, ZeroContentLength);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
@ -15,8 +16,10 @@ using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Server.WebListener
|
namespace Microsoft.AspNet.Server.WebListener
|
||||||
{
|
{
|
||||||
internal sealed unsafe class Response : IDisposable
|
public sealed unsafe class Response
|
||||||
{
|
{
|
||||||
|
private static readonly string[] ZeroContentLength = new[] { "0" };
|
||||||
|
|
||||||
private ResponseState _responseState;
|
private ResponseState _responseState;
|
||||||
private IDictionary<string, string[]> _headers;
|
private IDictionary<string, string[]> _headers;
|
||||||
private string _reasonPhrase;
|
private string _reasonPhrase;
|
||||||
|
|
@ -99,7 +102,7 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal ResponseStream Body
|
public Stream Body
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
|
@ -136,14 +139,6 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal EntitySendFormat EntitySendFormat
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return (EntitySendFormat)_boundaryType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IDictionary<string, string[]> Headers
|
public IDictionary<string, string[]> Headers
|
||||||
{
|
{
|
||||||
get { return _headers; }
|
get { return _headers; }
|
||||||
|
|
@ -157,7 +152,7 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal long ContentLength64
|
internal long CalculatedLength
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
|
@ -165,6 +160,73 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Header accessors
|
||||||
|
public long? ContentLength
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
string contentLengthString = Headers.Get(HttpKnownHeaderNames.ContentLength);
|
||||||
|
long contentLength;
|
||||||
|
if (!string.IsNullOrWhiteSpace(contentLengthString))
|
||||||
|
{
|
||||||
|
contentLengthString = contentLengthString.Trim();
|
||||||
|
if (string.Equals("0", contentLengthString, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else if (long.TryParse(contentLengthString, NumberStyles.None, CultureInfo.InvariantCulture.NumberFormat, out contentLength))
|
||||||
|
{
|
||||||
|
return contentLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
CheckResponseStarted();
|
||||||
|
if (!value.HasValue)
|
||||||
|
{
|
||||||
|
Headers.Remove(HttpKnownHeaderNames.ContentLength);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (value.Value < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException("value", value.Value, "Cannot be negative.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.Value == 0)
|
||||||
|
{
|
||||||
|
Headers[HttpKnownHeaderNames.ContentLength] = ZeroContentLength;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Headers[HttpKnownHeaderNames.ContentLength] = new[] { value.Value.ToString(CultureInfo.InvariantCulture) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ContentType
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Headers.Get(HttpKnownHeaderNames.ContentLength);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
CheckResponseStarted();
|
||||||
|
if (string.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
Headers.Remove(HttpKnownHeaderNames.ContentType);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Headers[HttpKnownHeaderNames.ContentType] = new[] { value };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Version GetProtocolVersion()
|
private Version GetProtocolVersion()
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
|
|
@ -203,24 +265,17 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
return Request.ProtocolVersion;
|
return Request.ProtocolVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
// should only be called from RequestContext
|
||||||
|
internal void Dispose()
|
||||||
{
|
{
|
||||||
Dispose(true);
|
if (_responseState >= ResponseState.Closed)
|
||||||
}
|
|
||||||
|
|
||||||
private void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (disposing)
|
|
||||||
{
|
{
|
||||||
if (_responseState >= ResponseState.Closed)
|
return;
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// TODO: Verbose log
|
|
||||||
EnsureResponseStream();
|
|
||||||
_nativeStream.Dispose();
|
|
||||||
_responseState = ResponseState.Closed;
|
|
||||||
}
|
}
|
||||||
|
// TODO: Verbose log
|
||||||
|
EnsureResponseStream();
|
||||||
|
_nativeStream.Dispose();
|
||||||
|
_responseState = ResponseState.Closed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// old API, now private, and helper methods
|
// old API, now private, and helper methods
|
||||||
|
|
@ -434,7 +489,7 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
}
|
}
|
||||||
|
|
||||||
// Content-Length takes priority
|
// Content-Length takes priority
|
||||||
string contentLengthString = Headers.Get(HttpKnownHeaderNames.ContentLength);
|
long? contentLength = ContentLength;
|
||||||
string transferEncodingString = Headers.Get(HttpKnownHeaderNames.TransferEncoding);
|
string transferEncodingString = Headers.Get(HttpKnownHeaderNames.TransferEncoding);
|
||||||
|
|
||||||
if (responseVersion == Constants.V1_0 && !string.IsNullOrEmpty(transferEncodingString)
|
if (responseVersion == Constants.V1_0 && !string.IsNullOrEmpty(transferEncodingString)
|
||||||
|
|
@ -445,27 +500,14 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
transferEncodingString = null;
|
transferEncodingString = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(contentLengthString))
|
if (contentLength.HasValue)
|
||||||
{
|
{
|
||||||
contentLengthString = contentLengthString.Trim();
|
_contentLength = contentLength.Value;
|
||||||
if (string.Equals("0", contentLengthString, StringComparison.Ordinal))
|
_boundaryType = BoundaryType.ContentLength;
|
||||||
|
if (_contentLength == 0)
|
||||||
{
|
{
|
||||||
_boundaryType = BoundaryType.ContentLength;
|
|
||||||
_contentLength = 0;
|
|
||||||
flags = UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.NONE;
|
flags = UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.NONE;
|
||||||
}
|
}
|
||||||
else if (long.TryParse(contentLengthString, NumberStyles.None, CultureInfo.InvariantCulture.NumberFormat, out _contentLength))
|
|
||||||
{
|
|
||||||
_boundaryType = BoundaryType.ContentLength;
|
|
||||||
if (_contentLength == 0)
|
|
||||||
{
|
|
||||||
flags = UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.NONE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_boundaryType = BoundaryType.Invalid;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (!string.IsNullOrWhiteSpace(transferEncodingString)
|
else if (!string.IsNullOrWhiteSpace(transferEncodingString)
|
||||||
&& string.Equals("chunked", transferEncodingString.Trim(), StringComparison.OrdinalIgnoreCase))
|
&& string.Equals("chunked", transferEncodingString.Trim(), StringComparison.OrdinalIgnoreCase))
|
||||||
|
|
@ -509,7 +551,6 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also, Keep-Alive vs Connection Close
|
// Also, Keep-Alive vs Connection Close
|
||||||
|
|
||||||
if (!keepAlive)
|
if (!keepAlive)
|
||||||
{
|
{
|
||||||
if (!closeSet)
|
if (!closeSet)
|
||||||
|
|
|
||||||
|
|
@ -195,14 +195,14 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
}
|
}
|
||||||
if (_leftToWrite == long.MinValue)
|
if (_leftToWrite == long.MinValue)
|
||||||
{
|
{
|
||||||
UnsafeNclNativeMethods.HttpApi.HTTP_VERB method = _requestContext.GetKnownMethod();
|
UnsafeNclNativeMethods.HttpApi.HTTP_VERB method = _requestContext.Request.GetKnownMethod();
|
||||||
if (method == UnsafeNclNativeMethods.HttpApi.HTTP_VERB.HttpVerbHEAD)
|
if (method == UnsafeNclNativeMethods.HttpApi.HTTP_VERB.HttpVerbHEAD)
|
||||||
{
|
{
|
||||||
_leftToWrite = 0;
|
_leftToWrite = 0;
|
||||||
}
|
}
|
||||||
else if (_requestContext.Response.EntitySendFormat == EntitySendFormat.ContentLength)
|
else if (_requestContext.Response.BoundaryType == BoundaryType.ContentLength)
|
||||||
{
|
{
|
||||||
_leftToWrite = _requestContext.Response.ContentLength64;
|
_leftToWrite = _requestContext.Response.CalculatedLength;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,18 @@
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// <copyright file="HttpListenerException.cs" company="Microsoft">
|
// <copyright company="Microsoft">
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// </copyright>
|
// </copyright>
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Server.WebListener
|
namespace Microsoft.AspNet.Server.WebListener
|
||||||
{
|
{
|
||||||
#if NET45
|
[SuppressMessage("Microsoft.Usage", "CA2237:MarkISerializableTypesWithSerializable")]
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2237:MarkISerializableTypesWithSerializable")]
|
public class WebListenerException : Win32Exception
|
||||||
#endif
|
|
||||||
internal class WebListenerException : Win32Exception
|
|
||||||
{
|
{
|
||||||
internal WebListenerException()
|
internal WebListenerException()
|
||||||
: base(Marshal.GetLastWin32Error())
|
: base(Marshal.GetLastWin32Error())
|
||||||
|
|
@ -29,12 +28,14 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
: base(errorCode, message)
|
: base(errorCode, message)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
#if NET45
|
||||||
|
// the base class returns the HResult with this property
|
||||||
|
// we need the Win32 Error Code, hence the override.
|
||||||
public override int ErrorCode
|
public override int ErrorCode
|
||||||
|
#else
|
||||||
|
public int ErrorCode
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
// the base class returns the HResult with this property
|
|
||||||
// we need the Win32 Error Code, hence the override.
|
|
||||||
|
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return NativeErrorCode;
|
return NativeErrorCode;
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,7 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// We haven't sent a response yet, try to send a 500 Internal Server Error
|
// We haven't sent a response yet, try to send a 500 Internal Server Error
|
||||||
requestContext.SetFatalResponse();
|
SetFatalResponse(requestContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
requestContext.Dispose();
|
requestContext.Dispose();
|
||||||
|
|
@ -148,10 +148,17 @@ namespace Microsoft.AspNet.Server.WebListener
|
||||||
{
|
{
|
||||||
LogHelper.LogException(_logger, "ProcessRequestAsync", ex);
|
LogHelper.LogException(_logger, "ProcessRequestAsync", ex);
|
||||||
requestContext.Abort();
|
requestContext.Abort();
|
||||||
requestContext.Dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void SetFatalResponse(RequestContext context)
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = 500;
|
||||||
|
context.Response.ReasonPhrase = string.Empty;
|
||||||
|
context.Response.Headers.Clear();
|
||||||
|
context.Response.ContentLength = 0;
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_listener.Dispose();
|
_listener.Dispose();
|
||||||
|
|
|
||||||
|
|
@ -1,112 +0,0 @@
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
// <copyright file="HttpListenerException.cs" company="Microsoft">
|
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
// </copyright>
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
#if !NET45
|
|
||||||
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace System.ComponentModel
|
|
||||||
{
|
|
||||||
internal class Win32Exception : ExternalException
|
|
||||||
{
|
|
||||||
/// <devdoc>
|
|
||||||
/// <para>Represents the Win32 error code associated with this exception. This
|
|
||||||
/// field is read-only.</para>
|
|
||||||
/// </devdoc>
|
|
||||||
private readonly int nativeErrorCode;
|
|
||||||
|
|
||||||
/// <devdoc>
|
|
||||||
/// <para>Initializes a new instance of the <see cref='System.ComponentModel.Win32Exception'/> class with the last Win32 error
|
|
||||||
/// that occured.</para>
|
|
||||||
/// </devdoc>
|
|
||||||
public Win32Exception()
|
|
||||||
: this(Marshal.GetLastWin32Error())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
/// <devdoc>
|
|
||||||
/// <para>Initializes a new instance of the <see cref='System.ComponentModel.Win32Exception'/> class with the specified error.</para>
|
|
||||||
/// </devdoc>
|
|
||||||
public Win32Exception(int error)
|
|
||||||
: this(error, GetErrorMessage(error))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
/// <devdoc>
|
|
||||||
/// <para>Initializes a new instance of the <see cref='System.ComponentModel.Win32Exception'/> class with the specified error and the
|
|
||||||
/// specified detailed description.</para>
|
|
||||||
/// </devdoc>
|
|
||||||
public Win32Exception(int error, string message)
|
|
||||||
: base(message)
|
|
||||||
{
|
|
||||||
nativeErrorCode = error;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <devdoc>
|
|
||||||
/// Initializes a new instance of the Exception class with a specified error message.
|
|
||||||
/// FxCop CA1032: Multiple constructors are required to correctly implement a custom exception.
|
|
||||||
/// </devdoc>
|
|
||||||
public Win32Exception(string message)
|
|
||||||
: this(Marshal.GetLastWin32Error(), message)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <devdoc>
|
|
||||||
/// Initializes a new instance of the Exception class with a specified error message and a
|
|
||||||
/// reference to the inner exception that is the cause of this exception.
|
|
||||||
/// FxCop CA1032: Multiple constructors are required to correctly implement a custom exception.
|
|
||||||
/// </devdoc>
|
|
||||||
public Win32Exception(string message, Exception innerException)
|
|
||||||
: base(message, innerException)
|
|
||||||
{
|
|
||||||
nativeErrorCode = Marshal.GetLastWin32Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <devdoc>
|
|
||||||
/// <para>Represents the Win32 error code associated with this exception. This
|
|
||||||
/// field is read-only.</para>
|
|
||||||
/// </devdoc>
|
|
||||||
public int NativeErrorCode
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return nativeErrorCode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetErrorMessage(int error)
|
|
||||||
{
|
|
||||||
//get the system error message...
|
|
||||||
string errorMsg = "";
|
|
||||||
StringBuilder sb = new StringBuilder(256);
|
|
||||||
int result = SafeNativeMethods.FormatMessage(
|
|
||||||
SafeNativeMethods.FORMAT_MESSAGE_IGNORE_INSERTS |
|
|
||||||
SafeNativeMethods.FORMAT_MESSAGE_FROM_SYSTEM |
|
|
||||||
SafeNativeMethods.FORMAT_MESSAGE_ARGUMENT_ARRAY,
|
|
||||||
IntPtr.Zero, (uint)error, 0, sb, sb.Capacity + 1,
|
|
||||||
null);
|
|
||||||
if (result != 0)
|
|
||||||
{
|
|
||||||
int i = sb.Length;
|
|
||||||
while (i > 0)
|
|
||||||
{
|
|
||||||
char ch = sb[i - 1];
|
|
||||||
if (ch > 32 && ch != '.') break;
|
|
||||||
i--;
|
|
||||||
}
|
|
||||||
errorMsg = sb.ToString(0, i);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
errorMsg = "Unknown error (0x" + Convert.ToString(error, 16) + ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
return errorMsg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -37,7 +37,8 @@
|
||||||
"System.Threading": "4.0.0.0",
|
"System.Threading": "4.0.0.0",
|
||||||
"System.Threading.Overlapped": "4.0.0.0",
|
"System.Threading.Overlapped": "4.0.0.0",
|
||||||
"System.Threading.Tasks": "4.0.10.0",
|
"System.Threading.Tasks": "4.0.10.0",
|
||||||
"System.Threading.ThreadPool": "4.0.10.0"
|
"System.Threading.ThreadPool": "4.0.10.0",
|
||||||
|
"Microsoft.Win32.Primitives": "4.0.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue