Created
August 4, 2011 17:51
-
-
Save jasonsirota/1125753 to your computer and use it in GitHub Desktop.
WCF OWIN
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Configuration; | |
using System.IO; | |
using System.Linq; | |
using System.Net; | |
using System.Net.Http; | |
using System.Net.Http.Headers; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using Gate.Startup; | |
namespace Gate.Wcf | |
{ | |
using AppDelegate = Action< // app | |
IDictionary<string, object>, // env | |
Action< // result | |
string, // status | |
IDictionary<string, string>, // headers | |
Func< // body | |
Func< // next | |
ArraySegment<byte>, // data | |
Action, // continuation | |
bool>, // async | |
Action<Exception>, // error | |
Action, // complete | |
Action>>, // cancel | |
Action<Exception>>; // fault | |
public class GateMessageHandler : DelegatingChannel | |
{ | |
readonly AppDelegate _app; | |
public GateMessageHandler(HttpMessageChannel innerChannel) | |
: base(innerChannel) | |
{ | |
var configurationString = ConfigurationManager.AppSettings["Gate.Startup"]; | |
_app = new AppBuilder() | |
.Configure(configurationString) | |
.Build(); | |
} | |
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) | |
{ | |
var env = GetOwinEnvironment(request); | |
return InvokeOwinAppAsync(env, request, cancellationToken); | |
} | |
Task<HttpResponseMessage> InvokeOwinAppAsync(IDictionary<string, object> env, HttpRequestMessage request, CancellationToken cancellationToken) | |
{ | |
return base.SendAsync(request,cancellationToken).ContinueWith( | |
(task) => | |
{ | |
var httpResponse = task.Result; | |
_app.Invoke(env, | |
(status, headers, body) => | |
{ | |
try | |
{ | |
httpResponse.StatusCode = ConvertStringToHttpStatusCode(status); | |
foreach (var header in headers.SelectMany(kv => kv.Value.Split("\r\n".ToArray(), StringSplitOptions.RemoveEmptyEntries).Select(v => new { kv.Key, Value = v }))) | |
{ | |
//httpResponse.Headers.Add(header.Key, header.Value); | |
} | |
if (body == null) | |
{ | |
//TODO: Figure out how to tell WCF Task that I am done | |
return; | |
} | |
var stream = new MemoryStream(); | |
httpResponse.Content = new StreamContent(stream); | |
body( | |
(data, continuation) => | |
{ | |
try | |
{ | |
if (continuation == null) | |
{ | |
stream.Write(data.Array, data.Offset, data.Count); | |
return false; | |
} | |
var sr = stream.BeginWrite(data.Array, data.Offset, data.Count, ar => | |
{ | |
if (ar.CompletedSynchronously) return; | |
try | |
{ | |
stream.EndWrite(ar); | |
} | |
catch (Exception ex) | |
{ | |
//TODO: How do you tell the task there's an exception? | |
throw ex; | |
} | |
continuation(); | |
}, null); | |
if (sr.CompletedSynchronously) | |
{ | |
stream.EndWrite(sr); | |
return false; | |
} | |
return true; | |
} | |
catch (Exception ex) | |
{ | |
//TODO: How do you tell the task there's an exception? | |
throw ex; | |
return false; | |
} | |
}, | |
//TODO: How do you tell the task there's an exception? | |
(ex) => { throw ex; }, | |
//TODO: Figure out how to end response | |
() => { return; } | |
//end body | |
); | |
} | |
catch (Exception ex) | |
{ | |
//TODO: How do I pass an error back to WCF Async | |
throw ex; | |
} | |
}, | |
ex => | |
{ | |
//TODO: How do I pass an error back to WCF Async | |
throw ex; | |
}); | |
return httpResponse; | |
}); | |
} | |
static Dictionary<string, object> GetOwinEnvironment(HttpRequestMessage request) | |
{ | |
//TODO: figure out how to get application path here | |
var pathBase = ""; | |
var path = request.RequestUri.AbsolutePath; | |
//environment | |
var env = new Dictionary<string, object>(); | |
new Owin(env) | |
{ | |
Version = "1.0", | |
Method = request.Method.ToString(), | |
Scheme = request.RequestUri.Scheme, | |
PathBase = pathBase, | |
Path = path, | |
QueryString = request.RequestUri.Query, | |
Headers = ConvertHeadersToDictionary(request.Headers), | |
Body = (next, error, complete) => | |
{ | |
var stream = request.Content.ContentReadStream; | |
var buffer = new byte[4096]; | |
var continuation = new AsyncCallback[1]; | |
bool[] stopped = { false }; | |
continuation[0] = result => | |
{ | |
if (result != null && result.CompletedSynchronously) return; | |
try | |
{ | |
for (; ; ) | |
{ | |
if (result != null) | |
{ | |
var count = stream.EndRead(result); | |
if (stopped[0]) return; | |
if (count <= 0) | |
{ | |
complete(); | |
return; | |
} | |
var data = new ArraySegment<byte>(buffer, 0, count); | |
if (next(data, () => continuation[0](null))) return; | |
} | |
if (stopped[0]) return; | |
result = stream.BeginRead(buffer, 0, buffer.Length, continuation[0], null); | |
} | |
} | |
catch (Exception ex) | |
{ | |
error(ex); | |
} | |
}; | |
continuation[0](null); | |
return () => { stopped[0] = true; }; | |
}, | |
}; | |
return env; | |
} | |
public static HttpStatusCode ConvertStringToHttpStatusCode(string statusCode) | |
{ | |
var status = 0; | |
var truncatedStatusCode = statusCode.Length >= 3 ? statusCode.Substring(0, 3) : ""; | |
if (!int.TryParse(truncatedStatusCode, out status)) throw new InvalidCastException("Status code returned by Application was not a valid HTTP status integer"); | |
return (HttpStatusCode)status; | |
} | |
private static IDictionary<string, string> ConvertHeadersToDictionary(IEnumerable<KeyValuePair<string, IEnumerable<string>>> httpRequestHeaders) | |
{ | |
var headers = httpRequestHeaders.ToDictionary( | |
item => item.Key, | |
item => string.Join("/r/n", item.Value) | |
); | |
return headers; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment