-
Notifications
You must be signed in to change notification settings - Fork 109
/
Saturn.Sample.fs
167 lines (137 loc) · 5.81 KB
/
Saturn.Sample.fs
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
module Sample
open Saturn
open Giraffe
open Microsoft.Extensions.Hosting
open Microsoft.AspNetCore.Builder
// Saturn is using standard HttpHandlers from Giraffe
let apiHelloWorld = text "hello world from API"
let apiHelloWorld2 = text "hello world from API 2"
let apiDeleteExample = text "this is a delete example"
let apiDeleteExample2 str = sprintf "Echo: %s" str |> text
let otherHelloWorld = text "hello world from OTHER"
let otherHelloWorld2 = text "hello world from OTHER 2"
let helloWorld = text "hello world"
let helloWorld2 = text "hello world2"
let helloWorldName str = text ("hello world, " + str)
let helloWorldNameAge (str, age) = text (sprintf "hello world, %s, You're %i" str age)
// Pipeline CE is used to compose HttpHandlers together in more declarative way.
// At the moment only low level helpers in form of custom CE keywords are provided.
// But I hope to add some more high level helpers (for example `accept_json` instead of `must_accept "application/json" )
// Phoenix default set of plugs can be good source of ideas.
// Pipelines are composed together with `plug` keyword
let apiHeaderPipe = pipeline {
set_header "myCustomHeaderApi" "api"
}
let otherHeaderPipe = pipeline {
set_header "myCustomHeaderOther" "other"
}
let headerPipe = pipeline {
set_header "myCustomHeader" "abcd"
set_header "myCustomHeader2" "zxcv"
}
let endpointPipe = pipeline {
plug fetchSession
plug head
plug requestId
}
//`route` CE is used to declare routers/subrouters (using TokenRouter). It provides custom keywords for all HTTP methods
// supported by TokenRouter which supports type-safe formatting of routes. It's composed together with pipelines
// with `pipe_through` method which means that all handlers registed in router will be piped through given pipeline
// It enables composition with other routers (and any HttpHandlers) with `forward` keyword - it will behave
// like `subRoute`, modify value of `Path` on HttpContext. It also enables setting custom error/not found handler
// with `error_handler` keyword.
// It automatically supports grouping handlers registered for same path into `choose` combinator, what is
// not supported out of the box in TokenRouter - if you have multiple handlers registerd for `get "/"` they will be grouped,
// on the TokenRouter matching we will create single `route "/"` call, but the HttpHandler passed to it will be `choose` build
// from all registered handlers for this route.
let apiRouter = router {
pipe_through apiHeaderPipe
not_found_handler (setStatusCode 404 >=> text "Api 404")
get "/" apiHelloWorld
get "/a" apiHelloWorld2
delete "/del" apiDeleteExample
deletef "/del/%s" apiDeleteExample2
}
// `controller<'Key>` CE is higher level abstraction following convention of Phoenix Controllers and `resources` macro. It will create
// complex routing for predefined set of operations which looks like this:
// [
// GET [
// route "/" index
// route "/add" add
// routef "/%?" show
// routef "/%?/edit" edit
// ]
// POST [
// route "/" create
// ]
// PUT [
// route "/%?" update
// ]
// PATCH [
// route "/%?" update
// ]
// DELETE [
// route "/%?" delete
// ]
// ]
// The exact format argument of `routef` routes is created based on generic type passed to CE - it supports same types what Giraffe `routef`
// If any of the actions is not provided in CE it won't be added to routing table.
// By convention given handlers should do following actions:
// index -render list of all items
// add - render form for adding new item
// show - render single item
// edit - render form for editing item
// create - add item
// update - update item
// delete - delete item
let userController = controller {
not_found_handler (setStatusCode 404 >=> text "Users 404")
index (fun ctx -> "Index handler" |> Controller.text ctx)
add (fun ctx -> "Add handler" |> Controller.text ctx)
show (fun ctx id -> (sprintf "Show handler - %s" id) |> Controller.text ctx)
edit (fun ctx id -> (sprintf "Edit handler - %s" id) |> Controller.text ctx)
}
// Since all computation expressions produces `HttpHandler` everything can be easily composed together in nice declarative way.
// I belive that aim of the Saturn should be providing a more streamlined, higher level developer experience on top of the great
// Giraffe's model. It's bit like Phoenix using Plug model under the hood.
let topRouter = router {
pipe_through headerPipe
not_found_handler (SiteMap.page)
get "/" helloWorld
get "/a" helloWorld2
getf "/name/%s" helloWorldName
getf "/name/%s/%i" helloWorldNameAge
forward "/other" (router {
pipe_through otherHeaderPipe
not_found_handler (setStatusCode 404 >=> text "Other 404")
get "/" otherHelloWorld
get "/a" otherHelloWorld2
})
// or can be defined separately and used as HttpHandler
forward "/api" apiRouter
// same with controllers
forward "/users" userController
forward "/error" (fun nxt ctx -> failwith "Sample error")
}
// Saturn provides easy, declarative way to define application and hosting configuration.
// Things like enabling in-memory cache for session handling shouldn't require changes in multiple places, and be enabled with single "feature toggle"
// It also enables defining common set of Pipelines that will be executed for every request, before dispatching to router
let app = application {
pipe_through endpointPipe
use_router topRouter
url "http://0.0.0.0:8085/"
memory_cache
use_static "static"
use_gzip
app_config (fun app ->
let env = Environment.getWebHostEnvironment app
if (env.IsDevelopment()) then
app.UseDeveloperExceptionPage()
else
app
)
}
[<EntryPoint>]
let main _ =
run app
0 // return an integer exit code