| 
     | 
    @@ -0,0 +1,196 @@ | 
  
    
     | 
     | 
  
    namespace WebSocket | 
    
     | 
     | 
  
    
  | 
    
     | 
     | 
  
    // Appache 2.0 license | 
    
     | 
     | 
  
    
  | 
    
     | 
     | 
  
    // References: | 
    
     | 
     | 
  
    // [1] Proposed WebSockets Spec December 2011         http://tools.ietf.org/html/rfc6455    | 
    
     | 
     | 
  
    // [2] John McCutchan (Google Dart Team Member)       http://www.altdevblogaday.com/2012/01/23/writing-your-own-websocket-server/ | 
    
     | 
     | 
  
    // [3] A pretty good Python implemenation by mrrrgn   https://github.com/mrrrgn/websocket-data-frame-encoder-decoder/blob/master/frame.py | 
    
     | 
     | 
  
    // [4] WebSockets Organising body                     http://www.websocket.org/echo.html | 
    
     | 
     | 
  
    // [5] AndrewNewcomb's Gist (starting point)          https://gist.github.com/AndrewNewcomb/711664 | 
    
     | 
     | 
  
    // [6] C# version (Vladimir's starting point?)        http://nugget.codeplex.com | 
    
     | 
     | 
  
    // [7] VLADIMIR MATVEEV                               http://v2matveev.blogspot.com/2010/04/mailboxprocessors-practical-application.html | 
    
     | 
     | 
  
    // [8] Matt Moloney's Frame Implementation            https://gist.github.com/moloneymb/18959a56c10beb907608 | 
    
     | 
     | 
  
    
  | 
    
     | 
     | 
  
    // Disclaimer: very early stages of refactoring + still hacky | 
    
     | 
     | 
  
    
  | 
    
     | 
     | 
  
    open System | 
    
     | 
     | 
  
    open System.IO | 
    
     | 
     | 
  
    open System.Net | 
    
     | 
     | 
  
    open System.Net.Sockets | 
    
     | 
     | 
  
    open System.Text | 
    
     | 
     | 
  
    open System.Threading | 
    
     | 
     | 
  
    open System.Runtime.Serialization | 
    
     | 
     | 
  
    open System.Security.Cryptography | 
    
     | 
     | 
  
    
  | 
    
     | 
     | 
  
    module ServerUtil = | 
    
     | 
     | 
  
    
  | 
    
     | 
     | 
  
      let guid6455 = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" | 
    
     | 
     | 
  
    
  | 
    
     | 
     | 
  
      /// headers can occur in any order (See 1.3 in [1]) | 
    
     | 
     | 
  
      let isWebSocketsUpgrade (lines: string array) =  | 
    
     | 
     | 
  
        [| "GET /timer HTTP/1.1"; "Upgrade: websocket"; "Connection: Upgrade"|]  | 
    
     | 
     | 
  
        |> Array.map(fun x->lines |> Array.exists(fun y->x.ToLower()=y.ToLower())) | 
    
     | 
     | 
  
        |> Array.reduce(fun x y->x && y) | 
    
     | 
     | 
  
    
  | 
    
     | 
     | 
  
      /// see: [1] | 
    
     | 
     | 
  
      let calcWSAccept6455 (secWebSocketKey:string) =  | 
    
     | 
     | 
  
        let ck = secWebSocketKey + guid6455 | 
    
     | 
     | 
  
        let sha1 = SHA1CryptoServiceProvider.Create() | 
    
     | 
     | 
  
        let hashBytes = ck |> Encoding.ASCII.GetBytes |> sha1.ComputeHash | 
    
     | 
     | 
  
        let Sec_WebSocket_Accept = hashBytes |> Convert.ToBase64String | 
    
     | 
     | 
  
        Sec_WebSocket_Accept | 
    
     | 
     | 
  
    
  | 
    
     | 
     | 
  
      let createAcceptString6455 acceptCode =  | 
    
     | 
     | 
  
        "HTTP/1.1 101 Switching Protocols\r\n" + | 
    
     | 
     | 
  
        "Upgrade: websocket\r\n" + | 
    
     | 
     | 
  
        "Connection: Upgrade\r\n" + | 
    
     | 
     | 
  
        "Sec-WebSocket-Accept: " + acceptCode + "\r\n"  | 
    
     | 
     | 
  
        + "\r\n" | 
    
     | 
     | 
  
    
  | 
    
     | 
     | 
  
      let getKey key arr =  | 
    
     | 
     | 
  
          try  | 
    
     | 
     | 
  
            let item = (Array.find (fun (s:String) -> s.StartsWith(key)) arr) | 
    
     | 
     | 
  
            item.Substring key.Length | 
    
     | 
     | 
  
          with | 
    
     | 
     | 
  
            | _ -> "" | 
    
     | 
     | 
  
    
  | 
    
     | 
     | 
  
      let makeFrame_ShortTxt (P:byte array) = | 
    
     | 
     | 
  
        let message = new MemoryStream() | 
    
     | 
     | 
  
        try                          | 
    
     | 
     | 
  
          message.WriteByte(byte 0x81)                              // see 5.6 & 11.8 in [1].  See [2], and [3] | 
    
     | 
     | 
  
          message.WriteByte(byte P.Length)                          // assume length < 127 bytes.  as yet, no implementation of masking, extended payload length, or the mask | 
    
     | 
     | 
  
          message.Write(P,0,P.Length)                               // TODO: cobine with [8] | 
    
     | 
     | 
  
          message.ToArray() | 
    
     | 
     | 
  
        finally | 
    
     | 
     | 
  
          message.Close() | 
    
     | 
     | 
  
    
  | 
    
     | 
     | 
  
    
  | 
    
     | 
     | 
  
    open ServerUtil | 
    
     | 
     | 
  
    
  | 
    
     | 
     | 
  
    [<DataContract>] | 
    
     | 
     | 
  
    type Time = | 
    
     | 
     | 
  
        { [<DataMember(Name = "hour")>] mutable Hour : int | 
    
     | 
     | 
  
          [<DataMember(Name = "minute")>] mutable Minute : int | 
    
     | 
     | 
  
          [<DataMember(Name = "second")>] mutable Second : int } | 
    
     | 
     | 
  
        static member New(dt : DateTime) = {Hour = dt.Hour; Minute = dt.Minute; Second = dt.Second} | 
    
     | 
     | 
  
    
  | 
    
     | 
     | 
  
    type Msg = | 
    
     | 
     | 
  
        | Connect of MailboxProcessor<Time> | 
    
     | 
     | 
  
        | Disconnect of MailboxProcessor<Time> | 
    
     | 
     | 
  
        | Tick of Time | 
    
     | 
     | 
  
    
  | 
    
     | 
     | 
  
    module WebSocketServer = | 
    
     | 
     | 
  
    
  | 
    
     | 
     | 
  
      let port = 1900 | 
    
     | 
     | 
  
      let ipAddress = IPAddress.Loopback.ToString() | 
    
     | 
     | 
  
      let origin = "http://localhost" | 
    
     | 
     | 
  
    
  | 
    
     | 
     | 
  
      let startMailboxProcessor ct f = MailboxProcessor.Start(f, cancellationToken = ct) | 
    
     | 
     | 
  
    
  | 
    
     | 
     | 
  
      let timer (ctrl : MailboxProcessor<Msg>) interval = async { | 
    
     | 
     | 
  
          while true do | 
    
     | 
     | 
  
              do! Async.Sleep interval | 
    
     | 
     | 
  
              ctrl.Post(Tick <| Time.New(DateTime.Now))             | 
    
     | 
     | 
  
          } | 
    
     | 
     | 
  
    
  | 
    
     | 
     | 
  
      let runController (ct : CancellationToken) =  | 
    
     | 
     | 
  
          startMailboxProcessor ct (fun (inbox : MailboxProcessor<Msg>) -> | 
    
     | 
     | 
  
              let listeners = new ResizeArray<_>() | 
    
     | 
     | 
  
              async { | 
    
     | 
     | 
  
                  while not ct.IsCancellationRequested do | 
    
     | 
     | 
  
                      let! msg = inbox.Receive() | 
    
     | 
     | 
  
                      match msg with | 
    
     | 
     | 
  
                      | Connect l ->  | 
    
     | 
     | 
  
                          Console.WriteLine "Connect" | 
    
     | 
     | 
  
                          listeners.Add(l) | 
    
     | 
     | 
  
                      | Disconnect l ->  | 
    
     | 
     | 
  
                          Console.WriteLine "Disconnect" | 
    
     | 
     | 
  
                          listeners.Remove(l) |> ignore | 
    
     | 
     | 
  
                      | Tick msg -> listeners.ForEach(fun l -> l.Post msg) | 
    
     | 
     | 
  
              } | 
    
     | 
     | 
  
          ) | 
    
     | 
     | 
  
    
  | 
    
     | 
     | 
  
      let runWorker (tcp : TcpClient) (ctrl : MailboxProcessor<Msg>) ct =  | 
    
     | 
     | 
  
    
  | 
    
     | 
     | 
  
          ignore <| startMailboxProcessor ct (fun (inbox : MailboxProcessor<Time>) -> | 
    
     | 
     | 
  
              let rec handshake = async {             | 
    
     | 
     | 
  
                  let ns = tcp.GetStream() | 
    
     | 
     | 
  
                  let bytes = Array.create tcp.ReceiveBufferSize (byte 0) | 
    
     | 
     | 
  
                  let bytesReadCount = ns.Read (bytes, 0, bytes.Length) | 
    
     | 
     | 
  
    
  | 
    
     | 
     | 
  
                  if bytesReadCount > 8 then                 | 
    
     | 
     | 
  
                      let lines = bytes.[..(bytesReadCount-9)] | 
    
     | 
     | 
  
                                  |> System.Text.UTF8Encoding.UTF8.GetString  | 
    
     | 
     | 
  
                                  |> fun hs->hs.Split([|"\r\n"|], StringSplitOptions.RemoveEmptyEntries)  | 
    
     | 
     | 
  
                      match isWebSocketsUpgrade lines with | 
    
     | 
     | 
  
                      | true -> | 
    
     | 
     | 
  
                          let acceptStr =   | 
    
     | 
     | 
  
                                          (getKey "Sec-WebSocket-Key:" lines                  | 
    
     | 
     | 
  
                                          ).Substring(1)                                // skip space | 
    
     | 
     | 
  
                                          |> calcWSAccept6455  | 
    
     | 
     | 
  
                                          |> createAcceptString6455 | 
    
     | 
     | 
  
                          Console.WriteLine acceptStr                                   // debug | 
    
     | 
     | 
  
                          do! ns.AsyncWrite <| Encoding.ASCII.GetBytes acceptStr | 
    
     | 
     | 
  
                          return! run ns | 
    
     | 
     | 
  
                      | _ ->                       | 
    
     | 
     | 
  
                          tcp.Close()                                                   // validation failed - close connection | 
    
     | 
     | 
  
                  else | 
    
     | 
     | 
  
                      tcp.Close()                                                       // validation failed - close connection | 
    
     | 
     | 
  
                  } | 
    
     | 
     | 
  
              and run (ns : NetworkStream) = async { | 
    
     | 
     | 
  
                  let json = System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof<Time>) | 
    
     | 
     | 
  
                  ctrl.Post(Connect inbox) | 
    
     | 
     | 
  
                  try | 
    
     | 
     | 
  
                      while not ct.IsCancellationRequested do | 
    
     | 
     | 
  
                          let! time = inbox.Receive() | 
    
     | 
     | 
  
    
  | 
    
     | 
     | 
  
                          let payload = new MemoryStream() | 
    
     | 
     | 
  
                          json.WriteObject(payload, time) | 
    
     | 
     | 
  
    
  | 
    
     | 
     | 
  
                          let df = makeFrame_ShortTxt <| payload.ToArray() | 
    
     | 
     | 
  
                          ns.Write(df,0,df.Length) | 
    
     | 
     | 
  
    
  | 
    
     | 
     | 
  
                  finally | 
    
     | 
     | 
  
                      ns.Close() | 
    
     | 
     | 
  
                      ctrl.Post(Disconnect inbox) | 
    
     | 
     | 
  
              } | 
    
     | 
     | 
  
              handshake | 
    
     | 
     | 
  
         )  | 
    
     | 
     | 
  
    
  | 
    
     | 
     | 
  
      let runRequestDispatcher () = | 
    
     | 
     | 
  
          let listener = new TcpListener(IPAddress.Parse(ipAddress), port) | 
    
     | 
     | 
  
          let cts = new CancellationTokenSource() | 
    
     | 
     | 
  
          let token = cts.Token | 
    
     | 
     | 
  
    
  | 
    
     | 
     | 
  
          let controller = runController token | 
    
     | 
     | 
  
          Async.Start (timer controller 1000, token) | 
    
     | 
     | 
  
    
  | 
    
     | 
     | 
  
          let main = async { | 
    
     | 
     | 
  
              try | 
    
     | 
     | 
  
                  listener.Start(10) | 
    
     | 
     | 
  
                  while not cts.IsCancellationRequested do | 
    
     | 
     | 
  
                      let! client = Async.FromBeginEnd(listener.BeginAcceptTcpClient, listener.EndAcceptTcpClient) | 
    
     | 
     | 
  
                      //client.NoDelay <- true | 
    
     | 
     | 
  
                      runWorker client controller token | 
    
     | 
     | 
  
              finally | 
    
     | 
     | 
  
                  listener.Stop()        | 
    
     | 
     | 
  
          } | 
    
     | 
     | 
  
    
  | 
    
     | 
     | 
  
          Async.Start(main, token) | 
    
     | 
     | 
  
    
  | 
    
     | 
     | 
  
          { new IDisposable with member x.Dispose() = cts.Cancel()} | 
    
     | 
     | 
  
    
  | 
    
     | 
     | 
  
      let start() =  | 
    
     | 
     | 
  
        let dispose = runRequestDispatcher () | 
    
     | 
     | 
  
        printfn "press any key to stop..." | 
    
     | 
     | 
  
        Console.ReadKey() |> ignore | 
    
     | 
     | 
  
        dispose.Dispose() | 
    
     | 
     | 
  
    
  | 
    
     | 
     | 
  
    module Program =  | 
    
     | 
     | 
  
    
  | 
    
     | 
     | 
  
      [<EntryPoint>] | 
    
     | 
     | 
  
      let main argv =  | 
    
     | 
     | 
  
          //System.Threading.Server.runRequestDispatcher() | 
    
     | 
     | 
  
          WebSocketServer.start() | 
    
     | 
     | 
  
          0 // return an integer exit code |