Skip to content

Commit 6a98328

Browse files
committed
refac (api): replace saturn with giraffe
1 parent 61b239c commit 6a98328

File tree

8 files changed

+99
-99
lines changed

8 files changed

+99
-99
lines changed
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
module API.Controller.Health
22

3+
open System.Net
4+
35
open Giraffe.Core
46

57
/// GET /health endpoint controller.
68
/// Returns a text showing that the API is healthy.
79
let index () : HttpHandler =
8-
setStatusCode 200 >=> text "API instance is healthy!"
10+
int HttpStatusCode.OK |> setStatusCode
11+
>=> json {| Message = "API instance is healthy!" |}
Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
module API.Controller.Prediction
22

3+
open System.Net
34
open Microsoft.AspNetCore.Http
5+
46
open Giraffe
5-
open Saturn
67

78
open API.DataScience
89

@@ -13,16 +14,17 @@ let createController () : HttpHandler =
1314
fun (next: HttpFunc) (ctx: HttpContext) ->
1415
task {
1516
// TODO: deal with problems when parsing the payload.
16-
let! cnf = Controller.getJson<CreatePayload> ctx
17+
let serializer = ctx.GetJsonSerializer()
18+
let! cnf = serializer.DeserializeAsync<CreatePayload> ctx.Request.Body
1719

1820
let prediction = getPredictionModel cnf.crimesPerCapta
1921

22+
// TODO: create a type for this return structure
2023
let result =
21-
(sprintf
22-
"Request OK\nId: %d\nCrimesPerCapta: %f\nPrice Prediction: %f"
23-
(cnf.id)
24-
(cnf.crimesPerCapta)
25-
(prediction))
24+
{| Message = "OK"
25+
Id = cnf.id
26+
CrimesPerCapta = cnf.crimesPerCapta
27+
PricePrediction = prediction |}
2628

27-
return! (setStatusCode 200 >=> text result) next ctx
29+
return! (int HttpStatusCode.OK |> setStatusCode >=> json result) next ctx
2830
}

fsharp-api/DataScience/DataScience.fs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ module API.DataScience
66
open FSharp.Data
77
open Deedle
88
open FSharp.Stats
9-
open Fitting.LinearRegression.OrdinaryLeastSquares
9+
open Fitting.LinearRegression.OLS
1010

1111
let private loadData () =
1212
// Retrieve data using the FSharp.Data package
@@ -34,8 +34,8 @@ let private predictPriceByCrimesPerCapta (data: Frame<'a, string>) =
3434
|> Series.values
3535
|> Seq.toList
3636
|> List.unzip
37-
|> (fun (x1, x2) -> Linear.Univariable.coefficient (vector x1) (vector x2))
38-
|> Linear.Univariable.fit
37+
|> (fun (x1, x2) -> Linear.Univariable.fit (vector x1) (vector x2))
38+
|> Linear.Univariable.predict
3939

4040
let getPredictionModel (crimesPerCapta: float) =
4141
loadData () |> transform |> predictPriceByCrimesPerCapta <| (crimesPerCapta)

fsharp-api/Env.fs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ let private setEnvVarDefaultValue (defaultValue: string) (readEnvVar: string) =
88
| _ -> readEnvVar
99

1010
let HOST =
11-
Environment.GetEnvironmentVariable "HOST" |> setEnvVarDefaultValue "localhost"
11+
Environment.GetEnvironmentVariable "HOST"
12+
|> setEnvVarDefaultValue "http://localhost"
1213

1314
let PORT = Environment.GetEnvironmentVariable "PORT" |> setEnvVarDefaultValue "8085"

fsharp-api/FsharpAPI.fsproj

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,14 @@
1212
<Compile Include="Controllers/Prediction.controller.fs" />
1313
<Compile Include="Router/Prometheus.middleware.fs" />
1414
<Compile Include="Router/Router.fs" />
15-
<Compile Include="Server.fs" />
1615
<Compile Include="Program.fs" />
1716
</ItemGroup>
1817
<ItemGroup>
19-
<PackageReference Include="Deedle" Version="2.5.0" />
20-
<PackageReference Include="FSharp.Data" Version="4.2.8" />
21-
<PackageReference Include="FSharp.Stats" Version="0.4.5" />
22-
<PackageReference Include="prometheus-net" Version="8.0.1" />
23-
<PackageReference Include="prometheus-net.AspNetCore" Version="8.0.1" />
24-
<PackageReference Include="Saturn" Version="0.16.1" />
18+
<PackageReference Include="Deedle" Version="3.0.0" />
19+
<PackageReference Include="FSharp.Data" Version="6.6.0" />
20+
<PackageReference Include="FSharp.Stats" Version="0.6.0" />
21+
<PackageReference Include="Giraffe" Version="8.0.0-alpha-002" />
22+
<PackageReference Include="prometheus-net" Version="8.2.1" />
23+
<PackageReference Include="prometheus-net.AspNetCore" Version="8.2.1" />
2524
</ItemGroup>
2625
</Project>

fsharp-api/Program.fs

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,66 @@
1-
open Saturn
1+
open System.Net
2+
open System.IO.Compression
3+
open Microsoft.AspNetCore.Builder
4+
open Microsoft.Extensions.DependencyInjection
5+
open Microsoft.Extensions.Hosting
6+
open Microsoft.Extensions.Logging
7+
open Microsoft.AspNetCore.Antiforgery
8+
open Microsoft.AspNetCore.ResponseCompression
29

3-
open API.Server
10+
open Giraffe
11+
open Giraffe.EndpointRouting
12+
open Prometheus
13+
14+
open API.Router
15+
16+
let PROMETHEUS_PORT: uint16 = 9085us
17+
18+
let notFoundHandler =
19+
int HttpStatusCode.NotFound |> setStatusCode
20+
>=> json {| Message = "Route not Found" |}
21+
22+
let private configureLogging (loggingBuilder: ILoggingBuilder) =
23+
// https://learn.microsoft.com/en-us/aspnet/core/fundamentals/logging#logging-providers
24+
loggingBuilder.ClearProviders().AddConsole().SetMinimumLevel LogLevel.Debug
25+
|> ignore
26+
27+
let private configureServices (services: IServiceCollection) =
28+
services
29+
.AddAntiforgery() // https://learn.microsoft.com/en-us/aspnet/core/security/anti-request-forgery#antiforgery-with-minimal-apis
30+
.AddResponseCompression(fun (options: ResponseCompressionOptions) ->
31+
// https://learn.microsoft.com/en-us/aspnet/core/performance/response-compression
32+
options.EnableForHttps <- true
33+
options.Providers.Add<BrotliCompressionProvider>()
34+
options.Providers.Add<GzipCompressionProvider>())
35+
.Configure<BrotliCompressionProviderOptions>(fun (options: BrotliCompressionProviderOptions) ->
36+
options.Level <- CompressionLevel.Fastest)
37+
.Configure<GzipCompressionProviderOptions>(fun (options: GzipCompressionProviderOptions) ->
38+
options.Level <- CompressionLevel.SmallestSize)
39+
.AddMetricServer(fun (options: KestrelMetricServerOptions) -> options.Port <- PROMETHEUS_PORT)
40+
.AddRouting()
41+
.AddGiraffe()
42+
|> ignore
43+
44+
let private configureApp (appBuilder: IApplicationBuilder) =
45+
appBuilder
46+
.UseAntiforgery()
47+
.UseResponseCompression()
48+
.UseRouting()
49+
.UseEndpoints(_.MapGiraffeEndpoints(appRouter))
50+
.UseGiraffe(notFoundHandler)
451

552
[<EntryPoint>]
6-
let main (_args: string[]) =
7-
do run (serverConfig (Env.HOST) (Env.PORT))
53+
let main (args: string[]) =
54+
let builder = WebApplication.CreateBuilder(args)
55+
configureLogging builder.Logging
56+
configureServices builder.Services
57+
58+
let app = builder.Build()
59+
60+
// https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis/webapplication#working-with-ports
61+
app.Urls.Add($"{Env.HOST}:{Env.PORT}")
62+
63+
configureApp app
64+
app.Run()
65+
866
0

fsharp-api/Router/Router.fs

Lines changed: 12 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,19 @@
11
module API.Router
22

3-
open Saturn
4-
open Microsoft.AspNetCore.Http
3+
open System.Net
4+
55
open Giraffe
6+
open Giraffe.EndpointRouting
67

78
open API.Controller
89
open API.PrometheusMiddleware
910

10-
let private postOpMiddlewareHook (middleware: HttpHandler) : HttpHandler =
11-
fun (next: HttpFunc) (ctx: HttpContext) ->
12-
task {
13-
let! routerContext = middleware next ctx
14-
15-
match routerContext with
16-
| Some routerCtx ->
17-
18-
let httpContext = DefaultHttpContext()
19-
20-
httpContext.SetStatusCode routerCtx.Response.StatusCode
21-
httpContext.Request.Path <- routerCtx.Request.Path
22-
httpContext.Request.Method <- routerCtx.Request.Method
23-
24-
let! _ = requestCounter next httpContext
25-
26-
return! next routerCtx
27-
| None -> return routerContext
28-
}
29-
30-
// https://github.com/SaturnFramework/Saturn/issues/225
31-
// Requests must have the header: Accept: application/json
32-
// curl -H "Accept: application/json" localhost:8085/api/test
33-
let private apiPredictionRouter =
34-
router {
35-
pipe_through acceptJson
36-
37-
post "/prediction" (Prediction.createController ())
38-
}
39-
40-
let appRouter =
41-
router {
42-
pipe_through requestDuration
43-
44-
get ("/health") (postOpMiddlewareHook ((Health.index ())))
45-
46-
forward ("/api") (postOpMiddlewareHook apiPredictionRouter)
47-
48-
not_found_handler (postOpMiddlewareHook (setStatusCode 404 >=> text "Endpoint not found."))
49-
}
11+
let appRouter: Endpoint list =
12+
[ GET
13+
[ route "/health" (requestCounter >=> requestDuration >=> Health.index ())
14+
routef "/ping/%s" (fun name ->
15+
requestCounter
16+
>=> requestDuration
17+
>=> (int HttpStatusCode.OK |> setStatusCode)
18+
>=> json {| Message = $"Pong from {name}!" |}) ]
19+
POST [ route "/api/prediction" (requestCounter >=> requestDuration >=> Prediction.createController ()) ] ]

fsharp-api/Server.fs

Lines changed: 0 additions & 33 deletions
This file was deleted.

0 commit comments

Comments
 (0)