Plugging in Elm and Suave with websockets on .NET Core 2.0
Getting started with Elm, Suave, .NET Core, and websockets
Topics covered
- Getting started with a .NET Core F# project
- A bit about using Paket
- A bit about Suave
- Getting started with websockets and Suave
- Getting started with Elm websockets
Why am I doing this?
I want to learn more about Elm, F#, Suave, websockets, and developing on .NET Core. So I decided to build a demo application with Elm on the front end, connecting to a Suave websocket server on the backend. I built this on .NET Core 2.
Create a basic DotNet Core F# console project
To get started let's first scaffold out a basic .NET Core F# console project. In this example I am using .NET Core 2.0 SDK. You can install this on Windows, macOS, and Linux. I have it installed on both my WSL with Ubuntu and Windows 10.
Go here to learn more about how to install .NET Core for your system.
Create a new project F# console project
.NET core provides a number of different templates to help you scaffold out a new project. I am going to create a console project in F#. Once you have the .NET Core 2.0 SDK installed you can do this. The command is the same for all OSes.
I will walk through setting up the project manually but as always you can find my code on Github:
Get the Suave websocket server here.
Get the Elm websocket client here. Note this branch has not yet been merged into master.
First create a folder where you will keep your files. By default the name of the folder will be used to name your project files.
mkdir websocket-log
cd websocket-log
Now you can run the dotnet new command. I highly recommand running dotnet new
to checkout all of the options available.
dotnet new console --lang F#
This creates Program.fs
, which is the entry point for the console app, and a websocket-log.fsproj
which configures your project and helps the compiler do its thing.
Get Suave into the project
I am going to use Paket to manage dependencies. You can read more about it here. We can use Visual Studio Code to run Paket commands to do what you see in that link, so that is what I am going to do here.
Let's open our project in Visual Studio Code (VS Code) from the command line. Note: I did this from git-bash on my Windows rather than from the WSL because VS Code can't do intellisense if your dependencies are on the WSL and VS Code is running on Windows. I have .NET core installed on both WSL and Windows.
First make sure you are in your project directory.
$ ll
total 10
drwxr-xr-x 1 Marnee 197121 0 Dec 24 10:40 obj/
-rw-r--r-- 1 Marnee 197121 172 Dec 24 10:40 Program.fs
-rw-r--r-- 1 Marnee 197121 1809 Dec 24 10:47 README.md
-rw-r--r-- 1 Marnee 197121 252 Dec 24 10:40 websocket-log.fsproj
code .
This will open VS Code.
Initialize paket
In VS Code hit Ctl + Shift + P. This opens a command window. Enter paket and you should see a number of command options. Select Paket: Init
.
This creates a number of new files and folders related to paket. You will put a list of your dependencies in paket.dependenies
and this is where we will put our reference to Suave.
Open paket.dependencies
.
Notice that there isn't much in there except:
source https://www.nuget.org/api/v2
This means that paket will use nuget as a source of packages.
Below this line add a reference to Suave.
nuget suave
Your file will look like this
source https://www.nuget.org/api/v2
nuget suave
Now we want paket to bring Suave and its dependencies into our project. We can use VS Code to do this.
Hit Ctl + Shift + P and enter Paket
and then select Paket: Install
.
Note: VS Code will display a log of everythihng Paket is doing in the terminal window under OUTPUT.
Once that is done you can open the packages
folder and see all of the dependencies paket pulled in.
Now we need to add Suave to the project using the .NET Core CLI. Make sure you are in the same directory as your project file and do this:
dotnet add package suave
This will add Suave to the project file.
A bit about Suave
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.
This means you can build a web server and even a REST API with Suave by composing small parts together. You can learn a little more on my previous article on upgrading the Suave bootstrapper.
Building a websocket server in Suave
To do this I followed this example in the Suave Github repo. Basically I copied the code from the Program.fs
file into my project's Program.fs
file.
Note: after copying in the code I got a lot or red-squigglies. To get rid of them and get some intellisense, you'll want to build the project. Go to the command line, make sure you are in the same directory at the project file and then
dotnet build
Note: If you work in a Dropbox folder you will need to pause syncing to get dotnet build
to run, otherwise you will probably get permission errors like this.
C:\Program Files\dotnet\sdk\2.1.2\Microsoft.Common.targets(127,3): error MSB4024: The imported project file "C:\Users\Marnee\Dropbox\github\project-mandelbrot\websocket-log\obj\websocket-log.fsproj.proj-info.targets" could not be loaded. The process cannot access the file 'C:\Users\Marnee\Dropbox\github\project-mandelbrot\websocket-log\obj\websocket-log.fsproj.proj-info.targets' because it is being used by another process. [C:\Users\Marnee\Dropbox\github\project-mandelbrot\websocket-log\websocket-log.fsproj]
Some of the example code doesn't build
I am still working on why this is happening, but the example code doesn't build because of one line (see line 84):
path "/websocketWithSubprotocol" >=> handShakeWithSubprotocol (chooseSubprotocol "test") ws
This is the error:
error FS0039: The value or constructor 'handShakeWithSubprotocol' is not defined. Maybe you want one of the following:↔ handShakeResponse↔ handShake [C:\Users\Marnee\Dropbox\github\project-mandelbrot\websocket-log\websocket-log.fsproj]
I have looked in the Suave source code and handShakeWithSubprotocol
is in the code.
I just commented out this line and was able to get the code to dotnet build
.
Now you have a Suave webserver with support for a websocket.
You can run it like this:
dotnet run
This starts a webserver that you can connect to with:
http://localhost:8000
$ dotnet run
[12:12:13 VRB] Creating buffer bank, total 827392 bytes
[12:12:13 DBG] Initialising BufferManager with 827392
[12:12:13 INF] Smooth! Suave listener started in 125.597 with binding 127.0.0.1:8080
W00t! You now have a websocket server. Now let's try to use it.
It's a simple chat
The Suave websocket example sets up a websocket server that can act as a simple chat. It just echoes back what is sent to it. Let's walk through the code.
At the bottom of Program.fs we have main
, the entry point of the console app. Here we start our webserver with verbose logging.
[<EntryPoint>]
let main _ =
startWebServer { defaultConfig with logger = Targets.create Verbose [||] } app
0
Moving up we see app
, which is a WebPart
, the basic building block of a Suave webserver.
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." ]
choose
gives us the option to define routes. Here we have two paths that are combined with functions that will execute if we go to that route. handShake
is a combinator that
captures a Websocket and passes it to the provided continuation
What's a continuation?
A continuation is a function that you pass into another function to tell it what to do next.
The /websocket
is a websocket handling function that will execute ws
.
The /websocketWithError
is a websocket handling function that will execute wsWithErrorHandling
.
ws
is a function that takes a WebSocket
and an HttpContext
and sets up a loop that continuously reads from the websocket and sends a response. wsWithErrorHandling
does the same with a bit of error handling.
Elm websockets
Lucky for us, the Elm websocket example assumes a websocket server echos back what is sent to it, so I didn't have to change the Suave code to make this work.
If you want to learn more about getting started with Elm, see my pervious article on using Elm and IPFS, You can't stop the signal. You can get the code from my Github repo. Note this branch has not yet been merged into master.
The code from the Elm websocket example will work with the Suave websocket server we created with only one modification. In the source code, look for suave-connection.elm
.
Find echoServer
. Here is where we tell the client the URL to the websocket server, in this case I changed the example code to ws://localhost:8080/websocket
, which is my Suave websocket server.
To run the Elm client do the following:
elm-make suave-connection.elm
This creates the index.html
file.
Now you can run the Elm client with:
elm-reactor
This starts a webserver that you can get to with localhost:8000
.
$ elm-reactor
elm-reactor 0.18.0
Listening on http://localhost:8000
Start the Suave websocket server. Remember we do it like this.
dotnet run
Now we can try out the Elm client. Load it up in your browser:
http://localhost:8000
In the text box enter something and click Send
. You should get some text back. If you check your browsers developer console, on the Network tab you should see the WS transaction.
What's next?
Ok this is cute and all but I should probably try building something a bit more useful. I am thinking about a websocket client that displays a continuous log. Also I really need to find out what is going on with handshakewithsubprotocol
.
Update: I answered my own question on StackOverflow. Looks like this function is not a part of v2.2.1, which is the latest stable release.
As always if you have any questions please tweet me.
Full Stack .NET Programmer and Ham