mirror of
https://github.com/Smaug123/pulsing-server
synced 2025-10-05 15:08:39 +00:00
Add an example usage
This commit is contained in:
@@ -4,6 +4,8 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "PulsingServer", "PulsingSer
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Test", "Test\Test.fsproj", "{6234E354-B2B1-4A45-8134-C306846E5A71}"
|
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Test", "Test\Test.fsproj", "{6234E354-B2B1-4A45-8134-C306846E5A71}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "SampleApplication", "SampleApplication\SampleApplication.fsproj", "{6692F628-B3AB-49E4-90EF-A4275CC7A6E3}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
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}.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.ActiveCfg = Release|Any CPU
|
||||||
{6234E354-B2B1-4A45-8134-C306846E5A71}.Release|Any CPU.Build.0 = 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
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
26
SampleApplication/Program.fs
Normal file
26
SampleApplication/Program.fs
Normal file
@@ -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<Startup>()
|
||||||
|
|> ignore
|
||||||
|
)
|
||||||
|
|
||||||
|
[<EntryPoint>]
|
||||||
|
let main args =
|
||||||
|
createHostBuilder(args).Build().Run()
|
||||||
|
|
||||||
|
0 // Exit code
|
28
SampleApplication/Properties/launchSettings.json
Normal file
28
SampleApplication/Properties/launchSettings.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
SampleApplication/SampleApplication.fsproj
Normal file
16
SampleApplication/SampleApplication.fsproj
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Startup.fs" />
|
||||||
|
<Compile Include="Program.fs" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\PulsingServer\PulsingServer.fsproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
151
SampleApplication/Startup.fs
Normal file
151
SampleApplication/Startup.fs
Normal file
@@ -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<ParseOutput> = 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<Date option> = 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<Date option> =
|
||||||
|
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<ms> 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
|
9
SampleApplication/appsettings.Development.json
Normal file
9
SampleApplication/appsettings.Development.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft": "Warning",
|
||||||
|
"Microsoft.Hosting.Lifetime": "Information"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
SampleApplication/appsettings.json
Normal file
10
SampleApplication/appsettings.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft": "Warning",
|
||||||
|
"Microsoft.Hosting.Lifetime": "Information"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*"
|
||||||
|
}
|
Reference in New Issue
Block a user