Add an example usage

This commit is contained in:
Smaug123
2021-02-17 18:18:01 +00:00
parent ac59bb21dc
commit 577e113bc3
7 changed files with 246 additions and 0 deletions

View File

@@ -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

View 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

View 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"
}
}
}
}

View 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>

View 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

View File

@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

View File

@@ -0,0 +1,10 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}