|
/// <summary> |
|
/// ALL of this was copied directly out of Nancy.Owin.dll's source: |
|
/// https://github.com/NancyFx/Nancy/blob/master/src/Nancy.Owin/NancyOwinHost.cs |
|
/// |
|
/// For the SOLE PURPOSE of customizving: |
|
/// var nancyRequestStream = new RequestStream(owinRequestBody, ExpectedLength(owinRequestHeaders), 1048576, false); |
|
/// </summary> |
|
namespace FunkyTown |
|
{ |
|
using System; |
|
using System.Collections.Generic; |
|
using System.Globalization; |
|
using System.IO; |
|
using System.Linq; |
|
using System.Net; |
|
using System.Threading.Tasks; |
|
using System.Security.Cryptography.X509Certificates; |
|
|
|
using Nancy; |
|
using Nancy.Owin; |
|
using Nancy.IO; |
|
using Nancy.Helpers; |
|
|
|
/// <summary> |
|
/// Nancy host for OWIN hosts |
|
/// </summary> |
|
public class NancyOwinHost |
|
{ |
|
private readonly Func<IDictionary<string, object>, Task> next; |
|
|
|
private readonly NancyOptions options; |
|
|
|
private readonly INancyEngine engine; |
|
|
|
/// <summary> |
|
/// The request environment key |
|
/// </summary> |
|
public const string RequestEnvironmentKey = "OWIN_REQUEST_ENVIRONMENT"; |
|
|
|
/// <summary> |
|
/// Initializes a new instance of the <see cref="NancyOwinHost"/> class. |
|
/// </summary> |
|
/// <param name="next">Next middleware to run if necessary</param> |
|
/// <param name="options">The nancy options that should be used by the host.</param> |
|
public NancyOwinHost(Func<IDictionary<string, object>, Task> next, NancyOptions options) |
|
{ |
|
this.next = next; |
|
this.options = options; |
|
options.Bootstrapper.Initialise(); |
|
this.engine = options.Bootstrapper.GetEngine(); |
|
} |
|
|
|
/// <summary> |
|
/// OWIN App Action |
|
/// </summary> |
|
/// <param name="environment">Application environment</param> |
|
/// <returns>Returns result</returns> |
|
public Task Invoke(IDictionary<string, object> environment) |
|
{ |
|
var owinRequestMethod = Get<string>(environment, "owin.RequestMethod"); |
|
var owinRequestScheme = Get<string>(environment, "owin.RequestScheme"); |
|
var owinRequestHeaders = Get<IDictionary<string, string[]>>(environment, "owin.RequestHeaders"); |
|
var owinRequestPathBase = Get<string>(environment, "owin.RequestPathBase"); |
|
var owinRequestPath = Get<string>(environment, "owin.RequestPath"); |
|
var owinRequestQueryString = Get<string>(environment, "owin.RequestQueryString"); |
|
var owinRequestBody = Get<Stream>(environment, "owin.RequestBody"); |
|
var owinRequestHost = GetHeader(owinRequestHeaders, "Host") ?? Dns.GetHostName(); |
|
|
|
byte[] certificate = null; |
|
if (this.options.EnableClientCertificates) |
|
{ |
|
var clientCertificate = Get<X509Certificate>(environment, "ssl.ClientCertificate"); |
|
certificate = (clientCertificate == null) ? null : clientCertificate.GetRawCertData(); |
|
} |
|
|
|
var serverClientIp = Get<string>(environment, "server.RemoteIpAddress"); |
|
|
|
var url = CreateUrl(owinRequestHost, owinRequestScheme, owinRequestPathBase, owinRequestPath, owinRequestQueryString); |
|
|
|
// HACKY HACK HACK!!! |
|
|
|
// If you are running a version of Nancy previous to 0.22.0, you need to set the memory-stream threshold |
|
// higher than you think you'll hit, because due to another bug Nancy ignores the last boolean parameter |
|
// telling it to never switch between memory-stream and file-stream. |
|
|
|
// So, for versions of Nancy earlier than 0.22.0 |
|
// Override the default memory-stream threshold from something like 81920 to 1 MEGABYTE |
|
// And hope it works out ;) |
|
// var nancyRequestStream = new RequestStream(owinRequestBody, ExpectedLength(owinRequestHeaders), 1048576, true); |
|
|
|
// For versions of Nancy 0.22.0 and later, it's enough to use the default threshold length |
|
// and specify false for the last param to disable stream switching altogether, |
|
// as the bug has been fixed and Nancy will now respect that setting. |
|
var nancyRequestStream = new RequestStream(owinRequestBody, ExpectedLength(owinRequestHeaders), true); |
|
|
|
var nancyRequest = new Request( |
|
owinRequestMethod, |
|
url, |
|
nancyRequestStream, |
|
owinRequestHeaders.ToDictionary(kv => kv.Key, kv => (IEnumerable<string>)kv.Value, StringComparer.OrdinalIgnoreCase), |
|
serverClientIp, |
|
certificate); |
|
|
|
var tcs = new TaskCompletionSource<int>(); |
|
|
|
this.engine.HandleRequest( |
|
nancyRequest, |
|
StoreEnvironment(environment), |
|
RequestComplete(environment, this.options.PerformPassThrough, this.next, tcs), |
|
RequestErrored(tcs)); |
|
|
|
return tcs.Task; |
|
} |
|
|
|
/// <summary> |
|
/// Gets a delegate to handle converting a nancy response |
|
/// to the format required by OWIN and signals that the we are |
|
/// now complete. |
|
/// </summary> |
|
/// <param name="environment">OWIN environment</param> |
|
/// <param name="next">A delegate that represents the next stage in OWIN pipeline</param> |
|
/// <param name="tcs">The task completion source to signal</param> |
|
/// <param name="performPassThrough">A delegate that determines if pass through should be performed</param> |
|
/// <returns>Delegate</returns> |
|
private static Action<NancyContext> RequestComplete( |
|
IDictionary<string, object> environment, |
|
Func<NancyContext, bool> performPassThrough, |
|
Func<IDictionary<string, object>, Task> next, |
|
TaskCompletionSource<int> tcs) |
|
{ |
|
return context => |
|
{ |
|
var owinResponseHeaders = Get<IDictionary<string, string[]>>(environment, "owin.ResponseHeaders"); |
|
var owinResponseBody = Get<Stream>(environment, "owin.ResponseBody"); |
|
|
|
var nancyResponse = context.Response; |
|
if (!performPassThrough(context)) |
|
{ |
|
environment["owin.ResponseStatusCode"] = (int)nancyResponse.StatusCode; |
|
|
|
if (nancyResponse.ReasonPhrase != null) |
|
{ |
|
environment["owin.ResponseReasonPhrase"] = nancyResponse.ReasonPhrase; |
|
} |
|
|
|
foreach (var responseHeader in nancyResponse.Headers) |
|
{ |
|
owinResponseHeaders[responseHeader.Key] = new[] {responseHeader.Value}; |
|
} |
|
|
|
if (!string.IsNullOrWhiteSpace(nancyResponse.ContentType)) |
|
{ |
|
owinResponseHeaders["Content-Type"] = new[] {nancyResponse.ContentType}; |
|
} |
|
|
|
if (nancyResponse.Cookies != null && nancyResponse.Cookies.Count != 0) |
|
{ |
|
const string setCookieHeaderKey = "Set-Cookie"; |
|
string[] setCookieHeader = owinResponseHeaders.ContainsKey(setCookieHeaderKey) |
|
? owinResponseHeaders[setCookieHeaderKey] |
|
: new string[0]; |
|
owinResponseHeaders[setCookieHeaderKey] = setCookieHeader |
|
.Concat(nancyResponse.Cookies.Select(cookie => cookie.ToString())) |
|
.ToArray(); |
|
} |
|
|
|
nancyResponse.Contents(owinResponseBody); |
|
tcs.SetResult(0); |
|
} |
|
else |
|
{ |
|
next(environment).WhenCompleted(t => tcs.SetResult(0), t => tcs.SetException(t.Exception)); |
|
} |
|
|
|
context.Dispose(); |
|
}; |
|
} |
|
|
|
/// <summary> |
|
/// Gets a delegate to handle request errors |
|
/// </summary> |
|
/// <param name="tcs">Completion source to signal</param> |
|
/// <returns>Delegate</returns> |
|
private static Action<Exception> RequestErrored(TaskCompletionSource<int> tcs) |
|
{ |
|
return tcs.SetException; |
|
} |
|
|
|
private static T Get<T>(IDictionary<string, object> env, string key) |
|
{ |
|
object value; |
|
return env.TryGetValue(key, out value) && value is T ? (T)value : default(T); |
|
} |
|
|
|
private static string GetHeader(IDictionary<string, string[]> headers, string key) |
|
{ |
|
string[] value; |
|
return headers.TryGetValue(key, out value) && value != null ? string.Join(",", value.ToArray()) : null; |
|
} |
|
|
|
private static long ExpectedLength(IDictionary<string, string[]> headers) |
|
{ |
|
var header = GetHeader(headers, "Content-Length"); |
|
if (string.IsNullOrWhiteSpace(header)) |
|
return 0; |
|
|
|
int contentLength; |
|
return int.TryParse(header, NumberStyles.Any, CultureInfo.InvariantCulture, out contentLength) ? contentLength : 0; |
|
} |
|
|
|
/// <summary> |
|
/// Creates the Nancy URL |
|
/// </summary> |
|
/// <param name="owinRequestHost">OWIN Hostname</param> |
|
/// <param name="owinRequestScheme">OWIN Scheme</param> |
|
/// <param name="owinRequestPathBase">OWIN Base path</param> |
|
/// <param name="owinRequestPath">OWIN Path</param> |
|
/// <param name="owinRequestQueryString">OWIN Querystring</param> |
|
/// <returns></returns> |
|
private static Url CreateUrl( |
|
string owinRequestHost, |
|
string owinRequestScheme, |
|
string owinRequestPathBase, |
|
string owinRequestPath, |
|
string owinRequestQueryString) |
|
{ |
|
int? port = null; |
|
|
|
var hostnameParts = owinRequestHost.Split(':'); |
|
if (hostnameParts.Length == 2) |
|
{ |
|
owinRequestHost = hostnameParts[0]; |
|
|
|
int tempPort; |
|
if (int.TryParse(hostnameParts[1], out tempPort)) |
|
{ |
|
port = tempPort; |
|
} |
|
} |
|
|
|
var url = new Url |
|
{ |
|
Scheme = owinRequestScheme, |
|
HostName = owinRequestHost, |
|
Port = port, |
|
BasePath = owinRequestPathBase, |
|
Path = owinRequestPath, |
|
Query = owinRequestQueryString, |
|
}; |
|
return url; |
|
} |
|
|
|
/// <summary> |
|
/// Gets a delegate to store the OWIN environment into the NancyContext |
|
/// </summary> |
|
/// <param name="environment">OWIN Environment</param> |
|
/// <returns>Delegate</returns> |
|
private static Func<NancyContext, NancyContext> StoreEnvironment(IDictionary<string, object> environment) |
|
{ |
|
return context => |
|
{ |
|
environment["nancy.NancyContext"] = context; |
|
context.Items[RequestEnvironmentKey] = environment; |
|
return context; |
|
}; |
|
} |
|
} |
|
} |