From 577e113bc3029bb9625db49ad1626c27ebb61745 Mon Sep 17 00:00:00 2001 From: Smaug123 Date: Wed, 17 Feb 2021 18:18:01 +0000 Subject: [PATCH] Add an example usage --- PulsingServer.sln | 6 + SampleApplication/Program.fs | 26 +++ .../Properties/launchSettings.json | 28 ++++ SampleApplication/SampleApplication.fsproj | 16 ++ SampleApplication/Startup.fs | 151 ++++++++++++++++++ .../appsettings.Development.json | 9 ++ SampleApplication/appsettings.json | 10 ++ 7 files changed, 246 insertions(+) create mode 100644 SampleApplication/Program.fs create mode 100644 SampleApplication/Properties/launchSettings.json create mode 100644 SampleApplication/SampleApplication.fsproj create mode 100644 SampleApplication/Startup.fs create mode 100644 SampleApplication/appsettings.Development.json create mode 100644 SampleApplication/appsettings.json diff --git a/PulsingServer.sln b/PulsingServer.sln index 45dda64..20c5ae8 100644 --- a/PulsingServer.sln +++ b/PulsingServer.sln @@ -4,6 +4,8 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "PulsingServer", "PulsingSer EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Test", "Test\Test.fsproj", "{6234E354-B2B1-4A45-8134-C306846E5A71}" EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "SampleApplication", "SampleApplication\SampleApplication.fsproj", "{6692F628-B3AB-49E4-90EF-A4275CC7A6E3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -18,5 +20,9 @@ Global {6234E354-B2B1-4A45-8134-C306846E5A71}.Debug|Any CPU.Build.0 = Debug|Any CPU {6234E354-B2B1-4A45-8134-C306846E5A71}.Release|Any CPU.ActiveCfg = Release|Any CPU {6234E354-B2B1-4A45-8134-C306846E5A71}.Release|Any CPU.Build.0 = Release|Any CPU + {6692F628-B3AB-49E4-90EF-A4275CC7A6E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6692F628-B3AB-49E4-90EF-A4275CC7A6E3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6692F628-B3AB-49E4-90EF-A4275CC7A6E3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6692F628-B3AB-49E4-90EF-A4275CC7A6E3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/SampleApplication/Program.fs b/SampleApplication/Program.fs new file mode 100644 index 0000000..6a1d02c --- /dev/null +++ b/SampleApplication/Program.fs @@ -0,0 +1,26 @@ +namespace SampleApplication + +open System +open System.Collections.Generic +open System.IO +open System.Linq +open System.Threading.Tasks +open Microsoft.AspNetCore +open Microsoft.AspNetCore.Hosting +open Microsoft.Extensions.Configuration +open Microsoft.Extensions.Hosting +open Microsoft.Extensions.Logging + +module Program = + let createHostBuilder args = + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(fun webBuilder -> + webBuilder.UseStartup() + |> ignore + ) + + [] + let main args = + createHostBuilder(args).Build().Run() + + 0 // Exit code diff --git a/SampleApplication/Properties/launchSettings.json b/SampleApplication/Properties/launchSettings.json new file mode 100644 index 0000000..a9e99e2 --- /dev/null +++ b/SampleApplication/Properties/launchSettings.json @@ -0,0 +1,28 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:39625", + "sslPort": 44387 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "SampleApplication": { + "commandName": "Project", + "dotnetRunMessages": "true", + "launchBrowser": true, + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/SampleApplication/SampleApplication.fsproj b/SampleApplication/SampleApplication.fsproj new file mode 100644 index 0000000..3679688 --- /dev/null +++ b/SampleApplication/SampleApplication.fsproj @@ -0,0 +1,16 @@ + + + + net5.0 + + + + + + + + + + + + diff --git a/SampleApplication/Startup.fs b/SampleApplication/Startup.fs new file mode 100644 index 0000000..a6e5480 --- /dev/null +++ b/SampleApplication/Startup.fs @@ -0,0 +1,151 @@ +namespace SampleApplication + +open System +open System.IO +open System.Net +open System.Net.Http +open System.Threading.Tasks +open Microsoft.AspNetCore.Builder +open Microsoft.AspNetCore.Hosting +open Microsoft.AspNetCore.Http +open Microsoft.Extensions.DependencyInjection +open Microsoft.Extensions.Hosting + +open PulsingServer + +type private State = + /// We have parsed the first digit of the hour, and it is this. + | ParsedHourFirstDigit of byte + /// We have parsed the entire hour, and it is this. We await a colon. + | ParsedHourAwaitingColon of byte + /// We have parsed the entire hour, and it is this. We await the first digit of the minute. + | ParsedHour of byte + /// We have parsed the entire hour, and the first digit of the minute. + | ParsedMinuteFirstDigit of byte * byte + /// We have parsed the entire hour, and the entire minute. We are now waiting for a colon. + | ParsedMinuteAwaitingColon of byte * byte + /// We have parsed the entire hour, and the entire minute. We are now waiting for the first digit of the second. + | ParsedMinute of byte * byte + /// We have parsed the hour and minute completely, and have parsed the first digit of the second. + | ParsedSecondFirstDigit of byte * byte * byte + | Waiting + +type private Date = + { + Hour : byte + Minute : byte + Second : byte + } + +type private ParseOutput = + | StreamEnded + | State of State + | Complete of Date + +type Startup() = + + let client = new WebClient() + + let servers = Array.init 2 (fun _ -> ServerAgent.make (Some { Hour = 0uy ; Minute = 0uy ; Second = 0uy })) + + /// Convert an input byte to the integer digit it is. + /// For example, ord('0') will match to Char(0). + let (|Char|_|) (b : byte) : byte option = + if byte '0' <= b && b <= byte '9' then + Some (b - byte '0') + else None + + /// Extremely rough-and-ready function to get a time out of a stream which contains + /// text. + /// We expect the time to be expressed as hh:mm:ss + /// and we do not bother our pretty little heads with Unicode issues. + let parseDateInner (buffer : byte array) (s : Stream) (state : State) : Async = async { + let! written = s.ReadAsync(buffer, 0, 1) |> Async.AwaitTask + if written = 0 then return StreamEnded else + + match state with + | Waiting -> + match buffer.[0] with + | Char b -> return State (State.ParsedHourFirstDigit b) + | _ -> return State Waiting + | ParsedHourFirstDigit hour -> + match buffer.[0] with + | Char b -> return State (State.ParsedHourAwaitingColon (hour * 10uy + b)) + | _ -> return State Waiting + | ParsedHourAwaitingColon hour -> + match buffer.[0] with + | b when b = byte ':' -> return State (State.ParsedHour hour) + | _ -> return State Waiting + | ParsedHour hour -> + match buffer.[0] with + | Char b -> return State (State.ParsedMinuteFirstDigit (hour, b)) + | _ -> return State Waiting + | ParsedMinuteFirstDigit (hour, min) -> + match buffer.[0] with + | Char b -> return State (State.ParsedMinuteAwaitingColon (hour, 10uy * b + min)) + | _ -> return State Waiting + | ParsedMinuteAwaitingColon (hour, min) -> + match buffer.[0] with + | b when b = byte ':' -> return State (State.ParsedMinute (hour, min)) + | _ -> return State Waiting + | ParsedMinute (hour, min) -> + match buffer.[0] with + | Char b -> return State (State.ParsedSecondFirstDigit (hour, min, b)) + | _ -> return State Waiting + | ParsedSecondFirstDigit (hour, min, sec) -> + match buffer.[0] with + | Char b -> return Complete { Hour = hour ; Minute = min ; Second = 10uy * sec + b } + | _ -> return State Waiting + } + + let parseDate (stream : Stream) : Async = async { + let buffer = [| 0uy |] + let rec go (state : State) = async { + match! parseDateInner buffer stream state with + | StreamEnded -> return None + | Complete d -> return Some d + | State state -> + return! go state + } + + return! go State.Waiting + } + + let update : Async = + async { + // Note: there is absolutely no error handling here at all. + // Obviously that would be desirable. + let result = client.OpenRead (Uri "insert your URL here") + return! parseDate result + } + + // Note that we haven't kicked this off yet - it's still an Async + let pulses = ExternalInfoProvider.make Async.Sleep update 500 servers + + // This method gets called by the runtime. Use this method to add services to the container. + // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 + member _.ConfigureServices(services: IServiceCollection) = + pulses |> Async.RunSynchronously |> ignore + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + member _.Configure(app: IApplicationBuilder, env: IWebHostEnvironment) = + if env.IsDevelopment() then + app.UseDeveloperExceptionPage() |> ignore + + app.UseRouting() + .UseEndpoints(fun endpoints -> + endpoints.MapGet("/", fun context -> + async { + let! answer = ServerAgent.giveNextResponse servers.[0] + match answer with + | None -> do! context.Response.WriteAsync("Oh noes") |> Async.AwaitTask + | Some date -> + do! context.Response.WriteAsync(sprintf "%i:%i:%i" date.Hour date.Minute date.Second) |> Async.AwaitTask + return () + } + |> Async.StartAsTask + :> Task + ) + |> ignore + ) + |> ignore \ No newline at end of file diff --git a/SampleApplication/appsettings.Development.json b/SampleApplication/appsettings.Development.json new file mode 100644 index 0000000..8983e0f --- /dev/null +++ b/SampleApplication/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/SampleApplication/appsettings.json b/SampleApplication/appsettings.json new file mode 100644 index 0000000..d9d9a9b --- /dev/null +++ b/SampleApplication/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +}