Skip to content

Instantly share code, notes, and snippets.

@AngelMunoz
Last active December 3, 2022 11:14
Show Gist options
  • Save AngelMunoz/01f9bccbf338c1be18470ec684e91898 to your computer and use it in GitHub Desktop.
Save AngelMunoz/01f9bccbf338c1be18470ec684e91898 to your computer and use it in GitHub Desktop.
Implement SSE with F# and Saturn
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
const source = new EventSource("/sse");
source.onopen = function() {
console.log("Connection Opened")
}
source.addEventListener('reload', (event) => {
console.log("Reloading, file changed: ", event.data)
});
source.addEventListener('start', (event) => {
console.log(event.data)
})
source.addEventListener('message', function(event) {
console.log(event)
console.log(event.data)
});
source.addEventListener('error', function(err) {
console.error(err);
});
</script>
</body>
</html>
open FSharp.Control.Tasks
open Giraffe
open Saturn
open Saturn.Endpoint.Router
open Microsoft.AspNetCore.Http
open System
open System.IO
open Microsoft.AspNetCore.Http.Features
open FSharp.Control.Reactive
type INotifierService =
inherit IDisposable
abstract OnFileChanged : IObservable<string>
let getNotifier (path: string) : INotifierService =
let fsw = new FileSystemWatcher(path)
fsw.NotifyFilter <- NotifyFilters.FileName ||| NotifyFilters.Size
fsw.EnableRaisingEvents <- true
let changed =
fsw.Changed
|> Observable.map (fun args -> args.Name)
let deleted =
fsw.Deleted
|> Observable.map (fun args -> args.Name)
let renamed =
fsw.Renamed
|> Observable.map (fun args -> args.Name)
let created =
fsw.Created
|> Observable.map (fun args -> args.Name)
let obs =
Observable.mergeSeq [ changed
deleted
renamed
created ]
{ new INotifierService with
override _.Dispose() : unit = fsw.Dispose()
override _.OnFileChanged: IObservable<string> = obs }
let sse next (ctx: HttpContext) =
task {
let res = ctx.Response
ctx.SetStatusCode 200
ctx.SetHttpHeader("Content-Type", "text/event-stream")
ctx.SetHttpHeader("Cache-Control", "no-cache")
let notifier = getNotifier @"C:\Users\scyth\Desktop"
let onFileChanged =
notifier.OnFileChanged
|> Observable.subscribe
(fun filename ->
task {
do! res.WriteAsync $"event:reload\ndata:{filename}\n\n"
do! res.Body.FlushAsync()
}
|> Async.AwaitTask
|> Async.Start)
do! res.WriteAsync($"event:start\ndata:{DateTime.Now}\n\n")
do! res.Body.FlushAsync()
ctx.RequestAborted.Register
(fun _ ->
notifier.Dispose()
onFileChanged.Dispose())
|> ignore
while true do
do! Async.Sleep(TimeSpan.FromSeconds 1.)
return! text "" next ctx
}
let appRouter = router { get "/sse" sse }
[<EntryPoint>]
let main args =
let app =
application {
use_endpoint_router appRouter
use_static "wwwroot"
}
run app
0
open FSharp.Control.Tasks
open Giraffe
open Saturn
open Saturn.Endpoint.Router
open Microsoft.AspNetCore.Http
open System
open System.Text.Json
let sse next (ctx: HttpContext) =
task {
let res = ctx.Response
ctx.SetStatusCode 200
ctx.SetHttpHeader("Content-Type", "text/event-stream")
ctx.SetHttpHeader("Cache-Control", "no-cache")
let data = JsonSerializer.Serialize({| event = "reload" |})
let mutable count = 0
while true do
do! res.WriteAsync($"data: {data}\nid:{count}\n\n")
do! res.Body.FlushAsync()
do! Async.Sleep (TimeSpan.FromSeconds 1.)
count <- count + 1
return! text "" next ctx
}
let appRouter =
router {
get "/sse" sse
}
[<EntryPoint>]
let main args =
let app =
application {
use_endpoint_router appRouter
use_static "wwwroot"
}
run app
0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment