{"action":"created","comment":{"url":"https://api.github.com/repos/SuaveIO/suave/pulls/comments/38276555","id":38276555,"diff_hunk":"@@ -0,0 +1,621 @@\n+module Suave.Owin\n+\n+// Following the specification:\n+// https://github.com/owin/owin/blob/master/spec/owin-1.1.0.md\n+\n+open System\n+open System.Net\n+open System.Threading\n+open System.Collections\n+open System.Collections.Generic\n+open System.Threading.Tasks\n+open System.Security.Claims\n+open System.Security.Principal\n+open System.Runtime.InteropServices\n+open Suave\n+open Suave.Http\n+open Suave.Types\n+open Suave.Sockets\n+\n+type OwinEnvironment =\n+  IDictionary<string, obj>\n+\n+type OwinApp =\n+  OwinEnvironment -> Async<unit>\n+\n+type OwinAppFunc =\n+  Func<OwinEnvironment, Task>\n+\n+[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]\n+module OwinConstants =\n+  // 3.2.1 Request Data\n+  [<CompiledName (\"RequestScheme\")>]\n+  let [<Literal>] requestScheme = \"owin.RequestScheme\"\n+  [<CompiledName (\"RequestMethod\")>]\n+  let [<Literal>] requestMethod = \"owin.RequestMethod\"\n+  [<CompiledName (\"RequestPathBase\")>]\n+  let [<Literal>] requestPathBase = \"owin.RequestPathBase\"\n+  [<CompiledName (\"RequestPath\")>]\n+  let [<Literal>] requestPath = \"owin.RequestPath\"\n+  [<CompiledName (\"RequestQueryString\")>]\n+  let [<Literal>] requestQueryString = \"owin.RequestQueryString\"\n+  [<CompiledName (\"RequestProtocol\")>]\n+  let [<Literal>] requestProtocol = \"owin.RequestProtocol\"\n+  [<CompiledName (\"RequestHeaders\")>]\n+  let [<Literal>] requestHeaders = \"owin.RequestHeaders\"\n+  [<CompiledName (\"RequestBody\")>]\n+  let [<Literal>] requestBody = \"owin.RequestBody\"\n+  [<CompiledName (\"RequestId\")>]\n+  let [<Literal>] requestId = \"owin.RequestId\"\n+  [<CompiledName (\"RequestUser\")>]\n+  let [<Literal>] requestUser = \"owin.RequestUser\"\n+\n+  // 3.2.2 Response Data\n+  [<CompiledName (\"ResponseStatusCode\")>]\n+  let [<Literal>] responseStatusCode = \"owin.ResponseStatusCode\"\n+  [<CompiledName (\"ResponseReasonPhrase\")>]\n+  let [<Literal>] responseReasonPhrase = \"owin.ResponseReasonPhrase\"\n+  [<CompiledName (\"ResponseProtocol\")>]\n+  let [<Literal>] responseProtocol = \"owin.ResponseProtocol\"\n+  [<CompiledName (\"ResponseHeaders\")>]\n+  let [<Literal>] responseHeaders = \"owin.ResponseHeaders\"\n+\n+  /// The server provides a response body Stream with the “owin.ResponseBody” key\n+  /// in the initial environment dictionary.  The headers, status code, reason\n+  /// phrase, etc., can be modified up until the first write to the response body\n+  /// stream.  Upon first write, the server validates and sends the headers.\n+  /// Applications MAY choose to buffer response data to delay the header\n+  /// finalization.\n+  ///\n+  /// Currently, Suave buffers the response data.\n+  [<CompiledName (\"ResponseBody\")>]\n+  let [<Literal>] responseBody = \"owin.ResponseBody\"\n+\n+  // 3.2.3 Other Data\n+  [<CompiledName (\"CallCancelled\")>]\n+  let [<Literal>] callCancelled = \"owin.CallCancelled\"\n+  [<CompiledName (\"OwinVersion\")>]\n+  let [<Literal>] owinVersion = \"owin.Version\"\n+\n+  /// http://owin.org/spec/CommonKeys.html\n+  [<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]\n+  module CommonKeys =\n+    [<CompiledName (\"ClientCertificate\")>]\n+    let [<Literal>] clientCertificate = \"ssl.ClientCertificate\"\n+    [<CompiledName (\"TraceOutput\")>]\n+    let [<Literal>] traceOutput = \"host.TraceOutput\"\n+    [<CompiledName (\"Addresses\")>]\n+    let [<Literal>] addresses = \"host.Addresses\"\n+    [<CompiledName (\"RemoteIpAddress\")>]\n+    let [<Literal>] remoteIpAddress = \"server.RemoteIpAddress\"\n+    [<CompiledName (\"RemotePort\")>]\n+    let [<Literal>] remotePort = \"server.RemotePort\"\n+    [<CompiledName (\"LocalIpAddress\")>]\n+    let [<Literal>] localIpAddress = \"server.LocalIpAddress\"\n+    [<CompiledName (\"LocalPort\")>]\n+    let [<Literal>] localPort = \"server.LocalPort\"\n+    [<CompiledName (\"IsLocal\")>]\n+    let [<Literal>] isLocal = \"server.IsLocal\"\n+    [<CompiledName (\"Capabilities\")>]\n+    let [<Literal>] capabilities = \"server.Capabilities\"\n+    [<CompiledName (\"ServerName\")>]\n+    let [<Literal>] serverName = \"server.Name\"\n+    [<CompiledName (\"OnSendingHeaders\")>]\n+    let [<Literal>] onSendingHeaders = \"server.OnSendingHeaders\"\n+\n+  /// http://owin.org/extensions/owin-SendFile-Extension-v0.3.0.htm\n+  [<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]\n+  module SendFiles =\n+    // 3.1. Startup\n+    [<CompiledName (\"Version\")>]\n+    let [<Literal>] version = \"sendfile.Version\"\n+    [<CompiledName (\"Support\")>]\n+    let [<Literal>] support = \"sendfile.Support\"\n+    [<CompiledName (\"Concurrency\")>]\n+    let [<Literal>] concurrency = \"sendfile.Concurrency\"\n+\n+    // 3.2. Per Request\n+    [<CompiledName (\"SendAsync\")>]\n+    let [<Literal>] sendAsync = \"sendfile.SendAsync\"\n+\n+  /// http://owin.org/extensions/owin-OpaqueStream-Extension-v0.3.0.htm\n+  [<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]\n+  module Opaque =\n+    // 3.1. Startup\n+    [<CompiledName (\"Version\")>]\n+    let [<Literal>] version = \"opaque.Version\"\n+\n+    // 3.2. Per Request\n+    [<CompiledName (\"Upgrade\")>]\n+    let [<Literal>] upgrade = \"opaque.Upgrade\"\n+\n+    // 5. Consumption\n+    [<CompiledName (\"Stream\")>]\n+    let [<Literal>] stream = \"opaque.Stream\"\n+    [<CompiledName (\"CallCanceled\")>]\n+    let [<Literal>] callCancelled = \"opaque.CallCancelled\"\n+\n+  /// http://owin.org/extensions/owin-WebSocket-Extension-v0.4.0.htm\n+  [<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]\n+  module WebSocket =\n+    // 3.1. Startup\n+    [<CompiledName (\"Version\")>]\n+    let [<Literal>] version = \"websocket.Version\"\n+\n+    // 3.2. Per Request\n+    [<CompiledName (\"Accept\")>]\n+    let [<Literal>] accept = \"websocket.Accept\"\n+\n+    // 4. Accept\n+    [<CompiledName (\"SubProtocol\")>]\n+    let [<Literal>] subProtocol = \"websocket.SubProtocol\"\n+\n+    // 5. Consumption\n+    [<CompiledName (\"SendAsync\")>]\n+    let [<Literal>] sendAsync = \"websocket.SendAsync\"\n+    [<CompiledName (\"ReceiveAsync\")>]\n+    let [<Literal>] receiveAsync = \"websocket.ReceiveAsync\"\n+    [<CompiledName (\"CloseAsync\")>]\n+    let [<Literal>] closeAsync = \"websocket.CloseAsync\"\n+    [<CompiledName (\"CallCancelled\")>]\n+    let [<Literal>] callCancelled = \"websocket.CallCancelled\"\n+    [<CompiledName (\"ClientCloseStatus\")>]\n+    let [<Literal>] clientCloseStatus = \"websocket.ClientCloseStatus\"\n+    [<CompiledName (\"ClientCloseDescription\")>]\n+    let [<Literal>] clientCloseDescription = \"websocket.ClientCloseDescription\"\n+\n+[<RequireQualifiedAccess>]\n+[<CompilationRepresentation (CompilationRepresentationFlags.ModuleSuffix)>]\n+module OwinAppFunc =\n+\n+  open Suave.Utils.Aether\n+  open Suave.Utils.Aether.Operators\n+  open Suave.Utils\n+  open Suave.Sockets.Control\n+  open Suave.Web.ParsingAndControl\n+\n+  type OwinRequest =\n+    abstract OnSendingHeadersAction : Action<Action<obj>, obj>\n+\n+  let untyped<'t> : Iso<'t, obj> =\n+    box, unbox\n+\n+  let inline stringlyTyped (convertBack : string -> 'a) : Property<'a, string> =\n+    string, fun v x -> convertBack v\n+\n+  type private Clock = uint64\n+\n+  type internal DeltaDictionary(initialHeaders : Map<string, string[]>) =\n+    let changed : Map<string, Clock * string []> ref = ref Map.empty\n+    let removed : Map<string, Clock> ref = ref Map.empty\n+    let mutable clock = 1UL\n+\n+    member x.Delta = // TODO: memoize function keyed on logical clock\n+      let keys =\n+        let changed = !changed |> Map.toSeq |> Seq.map fst |> Set.ofSeq\n+        let removed = !removed |> Map.toSeq |> Seq.map fst |> Set.ofSeq\n+        Set.union changed removed\n+\n+      let decide headers key =\n+        match !removed |> Map.tryFind key with\n+        | Some rmCl ->\n+          match !changed |> Map.tryFind key with\n+          | Some (chCl, value) ->\n+            if chCl > rmCl then\n+              headers |> Map.put key value\n+            else\n+              headers |> Map.remove key\n+\n+          | None ->\n+            headers |> Map.remove key\n+            \n+        | None ->\n+          headers |> Map.put key (!changed |> Map.find key |> snd)\n+          \n+      keys |> Seq.fold decide initialHeaders\n+\n+    member x.DeltaList =\n+      x.Delta |> Seq.map (fun kvp -> kvp.Key, String.concat \", \" kvp.Value)\n+              |> Seq.toList\n+\n+    new(dic : (string * string) list) =\n+      DeltaDictionary(dic |> List.map (fun (key, value) -> key, [| value |]) |> Map.ofList)\n+\n+    interface IDictionary<string, string[]> with\n+      member x.Item\n+        with get key =\n+          match !removed |> Map.tryFind key with\n+          | Some rmCl ->\n+            match !changed |> Map.tryFind key with\n+            | Some (chCl, value) ->\n+              if chCl > rmCl then value else raise (KeyNotFoundException())\n+\n+            | None ->\n+              raise (KeyNotFoundException())\n+\n+          | None ->\n+            !changed\n+            |> Map.tryFind key\n+            |> Option.fold (fun s t -> snd t) (initialHeaders |> Map.find key)\n+\n+        and set key value =\n+          changed := !changed |> Map.put key (clock, value)\n+          clock <- clock + 1UL\n+\n+      member x.Remove key =\n+        let res =\n+          match !removed |> Map.tryFind key with\n+          | Some rmCl ->\n+            match !changed |> Map.tryFind key with\n+            | Some (chCl, value) ->\n+              if chCl > rmCl then\n+                removed := !removed |> Map.put key clock\n+                true // changed after removed, so remove it again\n+              else\n+                false // removed after changed, nothing to do\n+\n+            | None ->\n+              false // already removed, never changed\n+\n+          | None ->\n+            match !changed |> Map.tryFind key with\n+            | Some (chCl, value) ->\n+              removed := !removed |> Map.put key clock\n+              true // remove after changed\n+\n+            | None ->\n+              match initialHeaders |> Map.tryFind key with\n+              | Some _ ->\n+                removed := !removed |> Map.put key clock\n+                true // remove from initial\n+              | None ->\n+                false // never present\n+\n+        clock <- clock + 1UL\n+        res\n+\n+      member x.Keys = (x.Delta :> IDictionary<_, _>).Keys\n+      member x.Values = (x.Delta :> IDictionary<_, _>).Values\n+      member x.ContainsKey key = (x.Delta :> IDictionary<_, _>).ContainsKey key\n+      member x.Add (key, value) = invalidOp \"Add is not supported\"\n+      member x.TryGetValue (key, [<Out>] res : byref<string[]>) = (x.Delta :> IDictionary<_, _>).TryGetValue(key, ref res)\n+\n+    interface ICollection<KeyValuePair<string, string[]>> with\n+      member x.Add kvp = invalidOp \"Add is not supported\"\n+      member x.Count = (x.Delta :> IDictionary<_, _>).Count\n+      member x.IsReadOnly = false\n+      member x.Clear() = invalidOp \"Clear is not supported\"\n+      member x.Contains kvp = (x.Delta :> ICollection<_>).Contains kvp\n+      member x.CopyTo (array, arrayIndex) = (x.Delta :> ICollection<_>).CopyTo(array, arrayIndex)\n+      member x.Remove kvp = (x :> IDictionary<_, _>).Remove kvp.Key\n+\n+    interface IEnumerable<KeyValuePair<string, string[]>> with\n+      member x.GetEnumerator() = (x.Delta :> ICollection<_>).GetEnumerator()\n+\n+    interface IEnumerable with\n+      member x.GetEnumerator() = (x.Delta :> IEnumerable).GetEnumerator()\n+\n+  module internal SirLensALot =\n+\n+    let req l =\n+      HttpContext.request_ >--> l <--> untyped\n+\n+    let run l =\n+      HttpContext.runtime_ >--> l <--> untyped\n+\n+    let res l =\n+      HttpContext.response_ >--> l <--> untyped\n+\n+    let uriScheme : Property<_, string> =\n+      (fun (uri : Uri) -> uri.Scheme),\n+      (fun v uri -> UriBuilder(uri, Scheme = v).Uri)\n+\n+    let uriAbsolutePath : Property<_, _> =\n+      (fun (uri : Uri) -> uri.AbsolutePath),\n+      (fun v uri -> UriBuilder(uri, Path = v).Uri)\n+\n+    let hv2p : Property<string, string> =\n+      (function | \"2.0\" -> \"HTTP/2.0\"\n+                | \"1.0\" -> \"HTTP/1.0\"\n+                | _ | \"1.1\" -> \"HTTP/1.1\"),\n+      (fun v -> function | \"HTTP/2.0\" -> \"2.0\"\n+                         | \"HTTP/1.0\" -> \"1.0\"\n+                         | \"HTTP/1.1\" -> \"1.1\"\n+                         | x -> x)\n+\n+\n+    let bytesToStream : Property<byte[], IO.Stream> =\n+      (fun x -> upcast new IO.MemoryStream(x)),\n+      (fun v x ->\n+        use ms = new IO.MemoryStream()\n+        v.CopyTo ms // TO CONSIDER: save the stream instead and read from it when writing data\n+        ms.ToArray())\n+\n+    let claimsPrincipal : Property<Map<string, obj>, ClaimsPrincipal> =\n+      (fun x ->\n+        x\n+        |> Map.tryFind \"principal\" // TODO: add support for ClaimsPrincipal in Core\n+        |> function\n+        | Some x -> unbox x\n+        | None -> raise (KeyNotFoundException()) // spec doesn't say\n+      ),\n+      (fun v x -> x |> Map.put \"principal\" (box v))\n+\n+    let i2sc : Property<HttpCode, int> =\n+      (fun x -> x.code),\n+      (fun v x -> HttpCode.TryParse v |> Option.get) // NOTE: assumes user only sets valid codes\n+\n+    let owinMap ct requestHeadersLens responseHeadersLens responseStreamLens =\n+      [ // 3.2.1 Request Data\n+        // writeable / value???\n+        OwinConstants.requestScheme,        HttpContext.request_ >--> HttpRequest.url_ >--> uriScheme <--> untyped\n+        // writeable / value\n+        OwinConstants.requestMethod,        req HttpRequest.method_\n+        // writeable / value\n+        OwinConstants.requestPathBase,      run HttpRuntime.homeDirectory_\n+        // writeable / value\n+        OwinConstants.requestPath,          HttpContext.request_ >--> HttpRequest.url_ >--> uriAbsolutePath <--> untyped\n+        // writeable / value\n+        OwinConstants.requestQueryString,   req HttpRequest.rawQuery_\n+        // writeable / value???\n+        OwinConstants.requestProtocol,      HttpContext.request_ >--> HttpRequest.httpVersion_ >--> hv2p <--> untyped\n+        // !! mutation expected (!)\n+        OwinConstants.requestHeaders,       HttpContext.request_ >--> HttpRequest.headers_ >--> requestHeadersLens <--> untyped\n+        // writeable / value\n+        OwinConstants.requestBody,          HttpContext.request_ >--> HttpRequest.rawForm_ >--> bytesToStream <--> untyped\n+        OwinConstants.requestId,            HttpContext.request_ >--> HttpRequest.trace_ >--> Logging.TraceHeader.traceId_ >--> stringlyTyped uint64 <--> untyped\n+        // writeable / value\n+        OwinConstants.requestUser,          HttpContext.userState_ >--> claimsPrincipal <--> untyped\n+\n+        // 3.2.2 Response Data\n+        // writeable / value\n+        OwinConstants.responseStatusCode,   HttpContext.response_ >--> HttpResult.status_ >--> i2sc <--> untyped\n+        // writeable / value\n+        OwinConstants.responseReasonPhrase, HttpContext.response_ <--> untyped // TODO: add support for modifying phrasing to Core?\n+        // writeable / value???\n+        OwinConstants.responseProtocol,     HttpContext.request_ >--> HttpRequest.httpVersion_ >--> hv2p <--> untyped\n+        // !! mutation expected\n+        OwinConstants.responseHeaders,      HttpContext.response_ >--> HttpResult.headers_ >--> responseHeadersLens <--> untyped\n+        // !! mutation expected\n+        OwinConstants.responseBody,         HttpContext.response_ >--> HttpResult.content_ >--> responseStreamLens <--> untyped\n+\n+        // 3.2.3 Other Data\n+        OwinConstants.callCancelled, ((fun x -> ct), (fun v x -> x)) <--> untyped // TODO: support cancellation token in HttpRequest signalling aborted request\n+        OwinConstants.owinVersion, ((fun x -> \"1.3\"), (fun v x -> x)) <--> untyped\n+\n+        // per-request storage\n+        \"userData\", HttpContext.userState_ <--> untyped\n+      ]\n+\n+  type internal OwinDictionary(initialState) =\n+    // TODO: support streaming optionally\n+      //let impl conn = AsyncSocket.transferStream conn v\n+      //SocketTask impl\n+    // TODO: cancelled request\n+    // TODO: provide typed API in OwinRequest\n+\n+    let cts = new CancellationTokenSource()\n+    let state : HttpContext ref = ref initialState //externally owned\n+\n+    // NOTE: I cannot make OwinDictionary immutable, because the Task OWIN returns doesn't have\n+    // a proper return value (only unit)\n+    //\n+    // the first time any of these are requested, set the ref, return the reference\n+    let requestHeaders : DeltaDictionary option ref = ref None\n+    let requestHeadersLens : Property<(string * string) list, IDictionary<string, string[]>> =\n+      (fun x ->\n+        upcast (match !requestHeaders with\n+                | None   -> let v = DeltaDictionary x in requestHeaders := Some v ; v\n+                | Some v -> v)),\n+      (fun v x -> invalidOp \"setting RequestHeaders IDictionary<string, string[]> is not supported\")\n+\n+    let responseHeaders : DeltaDictionary option ref = ref None\n+    let responseHeadersLens : Property<(string * string) list, IDictionary<string, string[]>> =\n+      (fun x ->\n+        upcast (match !responseHeaders with\n+                | None   -> let v = DeltaDictionary x in responseHeaders := Some v ; v\n+                | Some v -> v)),\n+      (fun v x -> invalidOp \"setting ResponseHeaders IDictionary<string, string[]> is not supported\")\n+\n+    let responseStream = new IO.MemoryStream()\n+    let responseStreamLens : Property<HttpContent, IO.Stream> =\n+      (fun x ->\n+        let bs = Lens.getPartialOrElse HttpContent.BytesPLens [||] x\n+        responseStream.Write(bs, 0, bs.Length)\n+        upcast responseStream),\n+      (fun _ x -> invalidOp \"setting responseStream to a value is not supported in OWIN\")\n+\n+    let owinMap = SirLensALot.owinMap cts.Token requestHeadersLens responseHeadersLens responseStreamLens\n+    let owinKeys = owinMap |> List.map fst |> Set.ofSeq\n+    let owinRW   = owinMap |> Map.ofList\n+\n+    let owinLensLens key : Lens<Map<string, Property<HttpContext, obj>>, Property<HttpContext, obj>> =\n+      (Map.tryFind key >> function\n+                          | None -> invalidOp (sprintf \"Couldn't use key '%s' to modify OwinEnvironment\" key)\n+                          | Some lens -> lens),\n+      (fun v x -> invalidOp \"cannot set owin properties\")\n+\n+    member x.Interface =\n+      x :> IDictionary<string, obj>\n+\n+    /// Calling this returns a valid HttpResult that we can use for Suave\n+    member x.Finalise() =\n+      let reqHeaders_, respHeaders_, bytes_ =\n+        HttpContext.request_ >--> HttpRequest.headers_,\n+        HttpContext.response_ >--> HttpResult.headers_,\n+        HttpContext.response_ >--> HttpResult.content_ >-?> HttpContent.BytesPLens\n+\n+      let setResponseHeaders (rhs : DeltaDictionary) =\n+        Lens.set respHeaders_ rhs.DeltaList\n+\n+      let setRequestHeaders (rhs : DeltaDictionary) =\n+        Lens.set reqHeaders_ rhs.DeltaList\n+\n+      let setResponseData (ms : IO.MemoryStream) =\n+        ms.Seek(0L, IO.SeekOrigin.Begin) |> ignore\n+        Lens.setPartial bytes_ (ms.ToArray())\n+\n+      !state\n+      |> Option.foldBack setRequestHeaders !requestHeaders\n+      |> Option.foldBack setResponseHeaders !responseHeaders\n+      |> setResponseData responseStream\n+\n+    interface OwinRequest with\n+      member x.OnSendingHeadersAction =\n+        Action<_, _>(fun a -> fun x -> ())\n+\n+    interface IDictionary<string, obj> with\n+      member x.Remove k =\n+        invalidOp \"Remove not supported\"\n+\n+      member x.Item\n+        with get key =\n+          Lens.get (Lens.get (owinLensLens key) owinRW) !state\n+        and set key value =\n+          state := Lens.set (Lens.get (owinLensLens key) owinRW) value !state\n+\n+      member x.Keys =\n+        (owinRW :> IDictionary<_, _>).Keys\n+\n+      member x.Values =\n+        invalidOp \"omg the amount of lensing needed\"\n+        //(owinRW :> IDictionary<_, _> |> Seq.map (fun (kv) -> Lens.get kv.Value !state)).Values\n+\n+      member x.ContainsKey k =\n+        owinKeys.Contains k\n+\n+      member x.Add (key, v) =\n+        (x :> IDictionary<_, _>).[key] <- v\n+\n+      member x.TryGetValue (key, [<Out>] res : byref<obj>) =\n+        if owinKeys |> Set.contains key then\n+          res <- (x :> IDictionary<_, _>).[key]\n+          true\n+        else\n+          false\n+\n+    interface ICollection<KeyValuePair<string, obj>> with\n+      member x.Add kvp = (x :> IDictionary<_, _>).Add(kvp.Key, kvp.Value)\n+      member x.Count = owinKeys.Count\n+      member x.IsReadOnly = false\n+      member x.Clear() = invalidOp \"Clear is not supported\"\n+      member x.Contains kvp = owinKeys.Contains kvp.Key\n+      member x.CopyTo (array, arrayIndex) = invalidOp \"CopyTo is not supported\"\n+      member x.Remove kvp = (x :> IDictionary<_, _>).Remove kvp.Key\n+\n+    interface IEnumerable<KeyValuePair<string, obj>> with\n+      member x.GetEnumerator() =\n+        (x :> IDictionary<_, _>).Keys\n+        |> Seq.map (fun key -> KeyValuePair(key, (x :> IDictionary<_, _>).[key]))\n+        |> fun seq -> seq.GetEnumerator()\n+\n+    interface IEnumerable with\n+      member x.GetEnumerator() =\n+        (x :> IDictionary<_, _>).Keys\n+        |> Seq.map (fun key -> KeyValuePair(key, (x :> IDictionary<_, _>).[key]))\n+        |> fun seq -> (seq :> IEnumerable).GetEnumerator()\n+\n+    interface IDisposable with\n+      member x.Dispose() =\n+        (responseStream :> IDisposable).Dispose()\n+        (cts :> IDisposable).Dispose()\n+\n+  [<CompiledName \"OfOwin\">]\n+  let ofOwin (owin : OwinApp) : WebPart =\n+    fun (ctx : HttpContext) ->\n+      let impl (conn, response) : SocketOp<unit> = socket {\n+        // disposable because it buffers what the OwinApp writes to the stream\n+        use wrapper = new OwinDictionary({ ctx with response = response })\n+        do! SocketOp.ofAsync (owin wrapper.Interface)\n+        let ctx = wrapper.Finalise()\n+        //TO CONSIDER: do (wrapper :> OwinRequest).OnSendingHeadersAction\n+        do! Web.ParsingAndControl.writePreamble ctx\n+        do! Web.ParsingAndControl.writeContent ctx ctx.response.content\n+      }\n+\n+      { ctx with\n+          response =\n+            { ctx.response with\n+                content = SocketTask impl\n+                writePreamble = false\n+            }\n+      }\n+      |> succeed\n+\n+  [<CompiledName \"OfOwinFunc\">]\n+  let ofOwinFunc (owin : OwinAppFunc) =\n+    ofOwin (fun e -> Async.AwaitTask ((owin.Invoke e).ContinueWith<_>(fun _ -> ())))\n+\n+module OwinServerFactory =","path":"src/Suave/Owin.fs","position":549,"original_position":549,"commit_id":"45ecbae298327483a86ac0e8f61166d35ea24c40","original_commit_id":"8c37af42c277a044c63d458ddb61251400392a10","user":{"login":"panesofglass","id":8803,"avatar_url":"https://avatars.githubusercontent.com/u/8803?v=3","gravatar_id":"","url":"https://api.github.com/users/panesofglass","html_url":"https://github.com/panesofglass","followers_url":"https://api.github.com/users/panesofglass/followers","following_url":"https://api.github.com/users/panesofglass/following{/other_user}","gists_url":"https://api.github.com/users/panesofglass/gists{/gist_id}","starred_url":"https://api.github.com/users/panesofglass/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/panesofglass/subscriptions","organizations_url":"https://api.github.com/users/panesofglass/orgs","repos_url":"https://api.github.com/users/panesofglass/repos","events_url":"https://api.github.com/users/panesofglass/events{/privacy}","received_events_url":"https://api.github.com/users/panesofglass/received_events","type":"User","site_admin":false},"body":"I will focus on this later tonight.","created_at":"2015-08-30T22:04:53Z","updated_at":"2015-08-30T22:04:53Z","html_url":"https://github.com/SuaveIO/suave/pull/142#discussion_r38276555","pull_request_url":"https://api.github.com/repos/SuaveIO/suave/pulls/142","_links":{"self":{"href":"https://api.github.com/repos/SuaveIO/suave/pulls/comments/38276555"},"html":{"href":"https://github.com/SuaveIO/suave/pull/142#discussion_r38276555"},"pull_request":{"href":"https://api.github.com/repos/SuaveIO/suave/pulls/142"}}},"pull_request":{"url":"https://api.github.com/repos/SuaveIO/suave/pulls/142","id":24811593,"html_url":"https://github.com/SuaveIO/suave/pull/142","diff_url":"https://github.com/SuaveIO/suave/pull/142.diff","patch_url":"https://github.com/SuaveIO/suave/pull/142.patch","issue_url":"https://api.github.com/repos/SuaveIO/suave/issues/142","number":142,"state":"open","locked":false,"title":"Feature/owin","user":{"login":"haf","id":193115,"avatar_url":"https://avatars.githubusercontent.com/u/193115?v=3","gravatar_id":"","url":"https://api.github.com/users/haf","html_url":"https://github.com/haf","followers_url":"https://api.github.com/users/haf/followers","following_url":"https://api.github.com/users/haf/following{/other_user}","gists_url":"https://api.github.com/users/haf/gists{/gist_id}","starred_url":"https://api.github.com/users/haf/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/haf/subscriptions","organizations_url":"https://api.github.com/users/haf/orgs","repos_url":"https://api.github.com/users/haf/repos","events_url":"https://api.github.com/users/haf/events{/privacy}","received_events_url":"https://api.github.com/users/haf/received_events","type":"User","site_admin":false},"body":"This branch aims to bring OWIN support to Suave.\r\n\r\nCurrent state: completely untested. Needs an OWIN test suite.","created_at":"2014-11-21T00:55:03Z","updated_at":"2015-08-30T22:04:53Z","closed_at":null,"merged_at":null,"merge_commit_sha":"39b405d6cd5f30f60556e2f61b609640f1d31070","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/SuaveIO/suave/pulls/142/commits","review_comments_url":"https://api.github.com/repos/SuaveIO/suave/pulls/142/comments","review_comment_url":"https://api.github.com/repos/SuaveIO/suave/pulls/comments{/number}","comments_url":"https://api.github.com/repos/SuaveIO/suave/issues/142/comments","statuses_url":"https://api.github.com/repos/SuaveIO/suave/statuses/45ecbae298327483a86ac0e8f61166d35ea24c40","head":{"label":"SuaveIO:feature/owin","ref":"feature/owin","sha":"45ecbae298327483a86ac0e8f61166d35ea24c40","user":{"login":"SuaveIO","id":5822862,"avatar_url":"https://avatars.githubusercontent.com/u/5822862?v=3","gravatar_id":"","url":"https://api.github.com/users/SuaveIO","html_url":"https://github.com/SuaveIO","followers_url":"https://api.github.com/users/SuaveIO/followers","following_url":"https://api.github.com/users/SuaveIO/following{/other_user}","gists_url":"https://api.github.com/users/SuaveIO/gists{/gist_id}","starred_url":"https://api.github.com/users/SuaveIO/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/SuaveIO/subscriptions","organizations_url":"https://api.github.com/users/SuaveIO/orgs","repos_url":"https://api.github.com/users/SuaveIO/repos","events_url":"https://api.github.com/users/SuaveIO/events{/privacy}","received_events_url":"https://api.github.com/users/SuaveIO/received_events","type":"Organization","site_admin":false},"repo":{"id":755200,"name":"suave","full_name":"SuaveIO/suave","owner":{"login":"SuaveIO","id":5822862,"avatar_url":"https://avatars.githubusercontent.com/u/5822862?v=3","gravatar_id":"","url":"https://api.github.com/users/SuaveIO","html_url":"https://github.com/SuaveIO","followers_url":"https://api.github.com/users/SuaveIO/followers","following_url":"https://api.github.com/users/SuaveIO/following{/other_user}","gists_url":"https://api.github.com/users/SuaveIO/gists{/gist_id}","starred_url":"https://api.github.com/users/SuaveIO/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/SuaveIO/subscriptions","organizations_url":"https://api.github.com/users/SuaveIO/orgs","repos_url":"https://api.github.com/users/SuaveIO/repos","events_url":"https://api.github.com/users/SuaveIO/events{/privacy}","received_events_url":"https://api.github.com/users/SuaveIO/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/SuaveIO/suave","description":"Suave is a simple web development F# library providing a lightweight web server and a set of combinators to manipulate route flow and task composition.","fork":false,"url":"https://api.github.com/repos/SuaveIO/suave","forks_url":"https://api.github.com/repos/SuaveIO/suave/forks","keys_url":"https://api.github.com/repos/SuaveIO/suave/keys{/key_id}","collaborators_url":"https://api.github.com/repos/SuaveIO/suave/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/SuaveIO/suave/teams","hooks_url":"https://api.github.com/repos/SuaveIO/suave/hooks","issue_events_url":"https://api.github.com/repos/SuaveIO/suave/issues/events{/number}","events_url":"https://api.github.com/repos/SuaveIO/suave/events","assignees_url":"https://api.github.com/repos/SuaveIO/suave/assignees{/user}","branches_url":"https://api.github.com/repos/SuaveIO/suave/branches{/branch}","tags_url":"https://api.github.com/repos/SuaveIO/suave/tags","blobs_url":"https://api.github.com/repos/SuaveIO/suave/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/SuaveIO/suave/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/SuaveIO/suave/git/refs{/sha}","trees_url":"https://api.github.com/repos/SuaveIO/suave/git/trees{/sha}","statuses_url":"https://api.github.com/repos/SuaveIO/suave/statuses/{sha}","languages_url":"https://api.github.com/repos/SuaveIO/suave/languages","stargazers_url":"https://api.github.com/repos/SuaveIO/suave/stargazers","contributors_url":"https://api.github.com/repos/SuaveIO/suave/contributors","subscribers_url":"https://api.github.com/repos/SuaveIO/suave/subscribers","subscription_url":"https://api.github.com/repos/SuaveIO/suave/subscription","commits_url":"https://api.github.com/repos/SuaveIO/suave/commits{/sha}","git_commits_url":"https://api.github.com/repos/SuaveIO/suave/git/commits{/sha}","comments_url":"https://api.github.com/repos/SuaveIO/suave/comments{/number}","issue_comment_url":"https://api.github.com/repos/SuaveIO/suave/issues/comments{/number}","contents_url":"https://api.github.com/repos/SuaveIO/suave/contents/{+path}","compare_url":"https://api.github.com/repos/SuaveIO/suave/compare/{base}...{head}","merges_url":"https://api.github.com/repos/SuaveIO/suave/merges","archive_url":"https://api.github.com/repos/SuaveIO/suave/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/SuaveIO/suave/downloads","issues_url":"https://api.github.com/repos/SuaveIO/suave/issues{/number}","pulls_url":"https://api.github.com/repos/SuaveIO/suave/pulls{/number}","milestones_url":"https://api.github.com/repos/SuaveIO/suave/milestones{/number}","notifications_url":"https://api.github.com/repos/SuaveIO/suave/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/SuaveIO/suave/labels{/name}","releases_url":"https://api.github.com/repos/SuaveIO/suave/releases{/id}","created_at":"2010-07-03T15:26:06Z","updated_at":"2015-08-30T15:32:13Z","pushed_at":"2015-08-30T21:39:33Z","git_url":"git://github.com/SuaveIO/suave.git","ssh_url":"git@github.com:SuaveIO/suave.git","clone_url":"https://github.com/SuaveIO/suave.git","svn_url":"https://github.com/SuaveIO/suave","homepage":"http://suave.io","size":11381,"stargazers_count":295,"watchers_count":295,"language":"F#","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":56,"mirror_url":null,"open_issues_count":45,"forks":56,"open_issues":45,"watchers":295,"default_branch":"master"}},"base":{"label":"SuaveIO:master","ref":"master","sha":"a02519ba56fa13064cdce6c25645b864ce9d8be1","user":{"login":"SuaveIO","id":5822862,"avatar_url":"https://avatars.githubusercontent.com/u/5822862?v=3","gravatar_id":"","url":"https://api.github.com/users/SuaveIO","html_url":"https://github.com/SuaveIO","followers_url":"https://api.github.com/users/SuaveIO/followers","following_url":"https://api.github.com/users/SuaveIO/following{/other_user}","gists_url":"https://api.github.com/users/SuaveIO/gists{/gist_id}","starred_url":"https://api.github.com/users/SuaveIO/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/SuaveIO/subscriptions","organizations_url":"https://api.github.com/users/SuaveIO/orgs","repos_url":"https://api.github.com/users/SuaveIO/repos","events_url":"https://api.github.com/users/SuaveIO/events{/privacy}","received_events_url":"https://api.github.com/users/SuaveIO/received_events","type":"Organization","site_admin":false},"repo":{"id":755200,"name":"suave","full_name":"SuaveIO/suave","owner":{"login":"SuaveIO","id":5822862,"avatar_url":"https://avatars.githubusercontent.com/u/5822862?v=3","gravatar_id":"","url":"https://api.github.com/users/SuaveIO","html_url":"https://github.com/SuaveIO","followers_url":"https://api.github.com/users/SuaveIO/followers","following_url":"https://api.github.com/users/SuaveIO/following{/other_user}","gists_url":"https://api.github.com/users/SuaveIO/gists{/gist_id}","starred_url":"https://api.github.com/users/SuaveIO/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/SuaveIO/subscriptions","organizations_url":"https://api.github.com/users/SuaveIO/orgs","repos_url":"https://api.github.com/users/SuaveIO/repos","events_url":"https://api.github.com/users/SuaveIO/events{/privacy}","received_events_url":"https://api.github.com/users/SuaveIO/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/SuaveIO/suave","description":"Suave is a simple web development F# library providing a lightweight web server and a set of combinators to manipulate route flow and task composition.","fork":false,"url":"https://api.github.com/repos/SuaveIO/suave","forks_url":"https://api.github.com/repos/SuaveIO/suave/forks","keys_url":"https://api.github.com/repos/SuaveIO/suave/keys{/key_id}","collaborators_url":"https://api.github.com/repos/SuaveIO/suave/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/SuaveIO/suave/teams","hooks_url":"https://api.github.com/repos/SuaveIO/suave/hooks","issue_events_url":"https://api.github.com/repos/SuaveIO/suave/issues/events{/number}","events_url":"https://api.github.com/repos/SuaveIO/suave/events","assignees_url":"https://api.github.com/repos/SuaveIO/suave/assignees{/user}","branches_url":"https://api.github.com/repos/SuaveIO/suave/branches{/branch}","tags_url":"https://api.github.com/repos/SuaveIO/suave/tags","blobs_url":"https://api.github.com/repos/SuaveIO/suave/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/SuaveIO/suave/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/SuaveIO/suave/git/refs{/sha}","trees_url":"https://api.github.com/repos/SuaveIO/suave/git/trees{/sha}","statuses_url":"https://api.github.com/repos/SuaveIO/suave/statuses/{sha}","languages_url":"https://api.github.com/repos/SuaveIO/suave/languages","stargazers_url":"https://api.github.com/repos/SuaveIO/suave/stargazers","contributors_url":"https://api.github.com/repos/SuaveIO/suave/contributors","subscribers_url":"https://api.github.com/repos/SuaveIO/suave/subscribers","subscription_url":"https://api.github.com/repos/SuaveIO/suave/subscription","commits_url":"https://api.github.com/repos/SuaveIO/suave/commits{/sha}","git_commits_url":"https://api.github.com/repos/SuaveIO/suave/git/commits{/sha}","comments_url":"https://api.github.com/repos/SuaveIO/suave/comments{/number}","issue_comment_url":"https://api.github.com/repos/SuaveIO/suave/issues/comments{/number}","contents_url":"https://api.github.com/repos/SuaveIO/suave/contents/{+path}","compare_url":"https://api.github.com/repos/SuaveIO/suave/compare/{base}...{head}","merges_url":"https://api.github.com/repos/SuaveIO/suave/merges","archive_url":"https://api.github.com/repos/SuaveIO/suave/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/SuaveIO/suave/downloads","issues_url":"https://api.github.com/repos/SuaveIO/suave/issues{/number}","pulls_url":"https://api.github.com/repos/SuaveIO/suave/pulls{/number}","milestones_url":"https://api.github.com/repos/SuaveIO/suave/milestones{/number}","notifications_url":"https://api.github.com/repos/SuaveIO/suave/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/SuaveIO/suave/labels{/name}","releases_url":"https://api.github.com/repos/SuaveIO/suave/releases{/id}","created_at":"2010-07-03T15:26:06Z","updated_at":"2015-08-30T15:32:13Z","pushed_at":"2015-08-30T21:39:33Z","git_url":"git://github.com/SuaveIO/suave.git","ssh_url":"git@github.com:SuaveIO/suave.git","clone_url":"https://github.com/SuaveIO/suave.git","svn_url":"https://github.com/SuaveIO/suave","homepage":"http://suave.io","size":11381,"stargazers_count":295,"watchers_count":295,"language":"F#","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":56,"mirror_url":null,"open_issues_count":45,"forks":56,"open_issues":45,"watchers":295,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/SuaveIO/suave/pulls/142"},"html":{"href":"https://github.com/SuaveIO/suave/pull/142"},"issue":{"href":"https://api.github.com/repos/SuaveIO/suave/issues/142"},"comments":{"href":"https://api.github.com/repos/SuaveIO/suave/issues/142/comments"},"review_comments":{"href":"https://api.github.com/repos/SuaveIO/suave/pulls/142/comments"},"review_comment":{"href":"https://api.github.com/repos/SuaveIO/suave/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/SuaveIO/suave/pulls/142/commits"},"statuses":{"href":"https://api.github.com/repos/SuaveIO/suave/statuses/45ecbae298327483a86ac0e8f61166d35ea24c40"}}}}