mirror of
https://github.com/Smaug123/pulsing-server
synced 2025-10-05 06:58: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
|
||||
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
|
||||
|
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