-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathProgram.fs
More file actions
298 lines (254 loc) · 11.3 KB
/
Program.fs
File metadata and controls
298 lines (254 loc) · 11.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
module App
open System
open System.Security.Claims
open System.Threading
open Microsoft.AspNetCore
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Hosting
open Microsoft.AspNetCore.Http
open Microsoft.AspNetCore.Http.Features
open Microsoft.AspNetCore.Authentication
open Microsoft.AspNetCore.Authentication.Cookies
open Microsoft.Extensions.Configuration
open Microsoft.Extensions.Logging
open Microsoft.Extensions.DependencyInjection
open FSharp.Control.Tasks.V2.ContextInsensitive
open Microsoft.AspNetCore.SignalR
open Giraffe
open Giraffe.GiraffeViewEngine
open Microsoft.AspNetCore.Cors.Infrastructure
open System.IO
open Microsoft.Extensions.Hosting
open System.Threading.Tasks
open Connections
type IClientApi =
abstract Alive : unit -> Task
abstract LockBoard : bool -> Task
abstract LoginResponse : bool * string -> Task
abstract Message : string -> Task
abstract GameState : string -> Task
type GameHub() =
inherit Hub<IClientApi>()
/// Accept client logins
member this.Login(userId : string, pwd: string) =
let connectionId = this.Context.ConnectionId
task {
// Maybe Async.StartImmediateAsTask is the right thing to do instead of Async.StartAsTask
match! tryAuthenticate userId pwd |> Async.StartAsTask with
| true ->
register userId connectionId
this.Clients.Client(connectionId).LoginResponse(true, userId) |> ignore
this.Clients.All.Message(sprintf "New Player: %s (%s)" userId connectionId) |> ignore
| false ->
this.Clients.Client(connectionId).LoginResponse(false, userId) |> ignore
}
/// Handle client logout
member this.Logout(userId : string) =
this.Clients.All.Message(sprintf "Player left: %s" userId) |> ignore
deregisterAll userId
/// Handle player changing direction
// member this.Turn (playerId :string, direction :string) =
// task { }
// updatePlayerDirection playerId direction
/// Pass along message from one client to all clients
member this.Send(message : string) =
this.Clients.All.Message(message) |> ignore
type GameService(hubContext : IHubContext<GameHub, IClientApi>) =
inherit BackgroundService()
override this.ExecuteAsync(stoppingToken : CancellationToken) =
let pingTimer = new System.Timers.Timer(1000.0)
pingTimer.Elapsed.Add (fun _ ->
printfn "Timer elapsed"
hubContext.Clients.All.Message("stateSerialized")
|> ignore)
//this.HubContext.Clients.All.LoginResponse(true, "stateSerialized") |> ignore)
pingTimer.Start()
Task.CompletedTask
[<CLIMutable>]
type Person =
{ Name : string }
let layout (content : XmlNode list) =
html [] [ head [] [ title [] [ str "Giraffe" ] ]
body [] content ]
let partial() = p [] [ str "Some partial text." ]
let personView (model : Person) =
[ div [ _class "container" ]
[ h3 [ _title "Some title attribute" ]
[ sprintf "Hello, %s" model.Name |> str ]
a [ _href "https://github.com/giraffe-fsharp/Giraffe" ]
[ str "Github" ] ]
div [] [ partial() ] ]
|> layout
// ---------------------------------
// Error handler
// ---------------------------------
let errorHandler (ex : Exception) (logger : ILogger) =
logger.LogError
(EventId(), ex,
"An unhandled exception has occurred while executing the request.")
clearResponse >=> setStatusCode 500 >=> text ex.Message
// ---------------------------------
// Web app
// ---------------------------------
let authScheme = CookieAuthenticationDefaults.AuthenticationScheme
let accessDenied = setStatusCode 401 >=> text "Access Denied"
let mustBeUser = requiresAuthentication accessDenied
let mustBeAdmin =
requiresAuthentication accessDenied >=> requiresRole "Admin" accessDenied
let mustBeJohn =
requiresAuthentication accessDenied
>=> authorizeUser (fun u -> u.HasClaim(ClaimTypes.Name, "John"))
accessDenied
let loginHandler =
fun (next : HttpFunc) (ctx : HttpContext) ->
task {
let issuer = "http://localhost:5000"
let claims =
[ Claim(ClaimTypes.Name, "John", ClaimValueTypes.String, issuer)
Claim
(ClaimTypes.Surname, "Doe", ClaimValueTypes.String, issuer)
Claim
(ClaimTypes.Role, "Admin", ClaimValueTypes.String, issuer) ]
let identity = ClaimsIdentity(claims, authScheme)
let user = ClaimsPrincipal(identity)
do! ctx.SignInAsync(authScheme, user)
return! text "Successfully logged in" next ctx
}
let userHandler =
fun (next : HttpFunc) (ctx : HttpContext) ->
text ctx.User.Identity.Name next ctx
let showUserHandler id = mustBeAdmin >=> text (sprintf "User ID: %i" id)
let configuredHandler =
fun (next : HttpFunc) (ctx : HttpContext) ->
let configuration = ctx.GetService<IConfiguration>()
text configuration.["HelloMessage"] next ctx
let fileUploadHandler =
fun (next : HttpFunc) (ctx : HttpContext) ->
task {
return! (match ctx.Request.HasFormContentType with
| false -> RequestErrors.BAD_REQUEST "Bad request"
| true ->
ctx.Request.Form.Files
|> Seq.fold
(fun acc file ->
sprintf "%s\n%s" acc file.FileName) ""
|> text) next ctx
}
let fileUploadHandler2 =
fun (next : HttpFunc) (ctx : HttpContext) ->
task {
let formFeature = ctx.Features.Get<IFormFeature>()
let! form = formFeature.ReadFormAsync CancellationToken.None
return! (form.Files
|> Seq.fold
(fun acc file -> sprintf "%s\n%s" acc file.FileName)
""
|> text) next ctx
}
let cacheHandler1 : HttpHandler =
publicResponseCaching 30 None
>=> warbler (fun _ -> text (Guid.NewGuid().ToString()))
let cacheHandler2 : HttpHandler =
responseCaching (Public(TimeSpan.FromSeconds(float 30))) None
(Some [| "key1"; "key2" |])
>=> warbler (fun _ -> text (Guid.NewGuid().ToString()))
let cacheHandler3 : HttpHandler =
noResponseCaching >=> warbler (fun _ -> text (Guid.NewGuid().ToString()))
let time() = System.DateTime.Now.ToString()
[<CLIMutable>]
type Car =
{ Name : string
Make : string
Wheels : int
Built : DateTime }
interface IModelValidation<Car> with
member this.Validate() =
if this.Wheels > 1 && this.Wheels <= 6 then Ok this
else
Error
(RequestErrors.BAD_REQUEST
"Wheels must be a value between 2 and 6.")
let parsingErrorHandler err = RequestErrors.BAD_REQUEST err
let webApp =
choose [ GET >=> choose [ route "/" >=> text "index"
route "/ping" >=> text "pong"
route "/error"
>=> (fun _ _ -> failwith "Something went wrong!")
route "/login" >=> loginHandler
route "/logout" >=> signOut authScheme
>=> text "Successfully logged out."
route "/user" >=> mustBeUser >=> userHandler
route "/john-only" >=> mustBeJohn >=> userHandler
routef "/user/%i" showUserHandler
route "/person"
>=> (personView { Name = "Html Node" } |> htmlView)
route "/once" >=> (time() |> text)
route "/everytime"
>=> warbler (fun _ -> (time() |> text))
route "/configured" >=> configuredHandler
route "/upload" >=> fileUploadHandler
route "/upload2" >=> fileUploadHandler2
route "/cache/1" >=> cacheHandler1
route "/cache/2" >=> cacheHandler2
route "/cache/3" >=> cacheHandler3 ]
route "/car" >=> bindModel<Car> None json
route "/car2"
>=> tryBindQuery<Car> parsingErrorHandler None (validateModel xml)
RequestErrors.notFound (text "Not Found") ]
// ---------------------------------
// Main
// ---------------------------------
let cookieAuth (o : CookieAuthenticationOptions) =
do o.Cookie.HttpOnly <- true
o.Cookie.SecurePolicy <- CookieSecurePolicy.SameAsRequest
o.SlidingExpiration <- true
o.ExpireTimeSpan <- TimeSpan.FromDays 7.0
let configureApp (app : IApplicationBuilder) =
app.UseGiraffeErrorHandler(errorHandler) |> ignore
//.UseEndpoints(fun endpoints -> endpoints.MapHub<GameHub>("/gameHub") |> ignore)
app.UseStaticFiles() |> ignore
app.UseAuthentication() |> ignore
app.UseResponseCaching() |> ignore
app.UseCors("CorsPolicy") |> ignore
// In the older version of the framework we used to use the following:
// app.UseSignalR(fun routes -> routes.MapHub<GameHub>(PathString "/gameHub")) |> ignore
// Still works but deprecated. It is replaced by the following TWO lines
app.UseRouting() |> ignore
app.UseEndpoints
(fun endpoints -> endpoints.MapHub<GameHub>("/gameHub") |> ignore)
|> ignore
app.UseGiraffe webApp |> ignore
let configureServices (services : IServiceCollection) =
services.AddResponseCaching()
.AddGiraffe()
.AddHostedService<GameService>()
.AddAuthentication(authScheme)
.AddCookie(cookieAuth) |> ignore
services.Configure(fun (options : CookiePolicyOptions) ->
options.CheckConsentNeeded <- fun context -> true
options.MinimumSameSitePolicy <- SameSiteMode.None)
|> ignore
services.AddCors
(fun options ->
options.AddPolicy
("CorsPolicy",
(fun builder ->
builder.AllowAnyMethod().AllowAnyHeader()
.WithOrigins("http://localhost:55830").AllowCredentials()
|> ignore))) |> ignore
services.AddSignalR() |> ignore
services.AddDataProtection() |> ignore
let configureLogging (loggerBuilder : ILoggingBuilder) =
loggerBuilder.AddFilter(fun lvl -> lvl.Equals LogLevel.Error).AddConsole()
.AddDebug() |> ignore
[<EntryPoint>]
let main _ =
let contentRoot = Directory.GetCurrentDirectory()
let webRoot = Path.Combine(contentRoot, "WebRoot")
WebHost.CreateDefaultBuilder()
.Configure(Action<IApplicationBuilder> configureApp)
.ConfigureServices(configureServices)
.ConfigureLogging(configureLogging).UseContentRoot(contentRoot)
.UseIISIntegration().UseWebRoot(webRoot)
.UseUrls([| "http://10.0.1.200:8080" |]).Build().Run()
0