Suave with f# - How to have a rest api and websocket port in an f# chat application?

547 views Asked by At

I have an f# chat application that needs rest apis exposed as well as have websockets for real time messaging. I am using Suave framework.

I have a frontend which has a "Chat" button that runs javascript on click. The javascript triggers the creates a web socket for the websocket url (/websocket) and establishes a connection for the real time chats. However, I have another button "FetchUsers" for fetching all the users in the system. But for this, I will need a different path (/people). I dont think I can use the (/websocket) path because in the backend, there is only one function which can be defined for a particular path.

To achieve this I have come up with the idea that I could expose 2 different ports : one for the rest api and one for the web sockets. My rest api (localhost:8082/people) is used to get all the users in the system and the web sockets (localhost:8080/websocket) is for sending chat messages to all users in the system

I am open to any other suggestions as to how can I implement the chat application.

Program.fs

let app : WebPart = 
  choose [
    path "/websocket" >=> handShake ws
    path "/websocketWithSubprotocol" >=> handShakeWithSubprotocol (chooseSubprotocol "test") ws
    path "/websocketWithError" >=> handShake wsWithErrorHandling
    GET >=> choose [ path "/" >=> file "index.html"; browseHome ]
    NOT_FOUND "Found no handlers." ]

let myCfg =
  { defaultConfig with
      bindings = [                        
                   HttpBinding.createSimple HTTP "127.0.0.1" 8080
                   HttpBinding.createSimple HTTP "127.0.0.1" 8082 
                  ]
    }
[<EntryPoint>]
let main _ =
  let personWebPart = rest "people" {
        GetAll = Db.getPeople
        Create = Db.createPerson
    }
  startWebServer myCfg personWebPart
  startWebServer myCfg app

Restful.fs

module RestFul =
    open Suave.Web
    open Suave.Successful
    open Newtonsoft.Json    
    open Suave
    open Suave.Operators
    open Suave.Filters        
    open Suave.Files
    open Suave.RequestErrors
    
    open Newtonsoft.Json.Serialization
    type RestResource<'a> = {
        GetAll : unit -> 'a seq
        Create : 'a -> 'a            
    }

    let fromJson<'a> json =
        JsonConvert.DeserializeObject(json, typeof<'a>) :?> 'a

    let getResourceFromReq<'a> (req : HttpRequest) =
        let getString (rawForm: byte[]) =
            System.Text.Encoding.UTF8.GetString(rawForm)
        req.rawForm |> getString |> fromJson<'a>

    let JSON v =
        let jsonSerializerSettings = JsonSerializerSettings()
        jsonSerializerSettings.ContractResolver <- CamelCasePropertyNamesContractResolver()

        JsonConvert.SerializeObject(v, jsonSerializerSettings)
        |> OK
        >=> Writers.setMimeType "application/json; charset=utf-8"

    let rest resourceName resource =
        let resourcePath = "/" + resourceName
        let getAll = warbler (fun _ -> resource.GetAll () |> JSON)
        
        path resourcePath >=> choose [
            GET >=> getAll
            POST >=> request (getResourceFromReq >> resource.Create >> JSON)
        ]

How can assign the websocket to port 8080 and rest api to port 8082? I am doing this for now. The server starts up on the ports but I am only able to access the rest url ie localhost:8080/people

startWebServer myCfg personWebPart
startWebServer myCfg app

Reiterating my previous point, I would like to know any other approaches as to how can I implement the chat application.

1

There are 1 answers

0
akara On

It has been awhile since I've used Suave but if I recall it has WebSocket support using a custom "socket" computation expression. You loop in the expression, wait for a message, and then handle it. Note that waiting on the Websocket is in itself an async operation and that each client has their own loop.

Example is on their Github: https://github.com/SuaveIO/suave/blob/master/examples/WebSocket/Program.fs