-
-
Save panesofglass/1126690 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.ServiceModel; | |
using System.ServiceModel.Web; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using Gate.Helpers; | |
using Microsoft.ApplicationServer.Http; | |
using Microsoft.ApplicationServer.Http.Activation; | |
using Microsoft.ApplicationServer.Http.Description; | |
namespace Gate.Wcf | |
{ | |
class Program | |
{ | |
[ServiceContract] | |
public class TestService | |
{ | |
[WebGet(UriTemplate = "*")] | |
public HttpResponseMessage Get(HttpRequestMessage request) | |
{ | |
return new HttpResponseMessage(HttpStatusCode.OK, "OK") { Content = new ObjectContent<string>("Hello, world!") }; | |
} | |
[WebInvoke(UriTemplate = "*", Method = "*")] | |
public HttpResponseMessage Invoke(HttpRequestMessage request) | |
{ | |
return new HttpResponseMessage(HttpStatusCode.OK, "OK") { Content = new ObjectContent<string>("Hello, world!") }; | |
} | |
} | |
static void Main(string[] args) | |
{ | |
var config = HttpHostConfiguration.Create().AddMessageHandlers(typeof(GateMessageHandler)); | |
var host = new HttpConfigurableServiceHost<TestService>(config, new Uri[] { new Uri("http://localhost:1000/") }); | |
host.Open(); | |
Console.WriteLine("Listening on localhost port 1000..."); | |
Console.WriteLine("Press any key to stop listening."); | |
Console.ReadKey(); | |
host.Close(); | |
} | |
} | |
} | |
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 = AppBuilder.BuildConfiguration(configurationString); | |
} | |
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 Request(env) | |
{ | |
Version = "1.0", | |
Method = request.Method.ToString(), | |
Scheme = request.RequestUri.Scheme, | |
PathBase = pathBase, | |
Path = path, | |
QueryString = request.RequestUri.Query, | |
Headers = ConvertHeadersToDictionary(request.Headers) | |
.Union(ConvertHeadersToDictionary(request.Content.Headers)) | |
.ToDictionary(x => x.Key, x => x.Value), | |
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
I think I'm approaching this backwards. The above is really a way of getting WCF Web API apps to run on any host. What I've started adding is WCF Web API as a host for OWIN apps.