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
import asyncdispatch, asynchttpserver, macros
import strutils, parseutils
import cookies, uri, times, re, json
import tables
export asyncdispatch, asynchttpserver, cookies, uri
export strutils, re
export tables, json
type
ServerRef* = ref object
port*: uint16
address*: string
server*: AsyncHttpServer
proc newServer* (address: string = "0.0.0.0", port: uint16 = 5000): ServerRef =
return ServerRef(
address: address,
port: port,
server: newAsyncHttpServer()
)
proc cookies*(req: Request): Table[string, string] =
result = initTable[string, string]()
if (let cookie = req.headers.getOrDefault("Cookie"); cookie != ""):
var i = 0
while true:
i += skipWhile(cookie, {' ', '\t'}, i)
var keystart = i
i += skipUntil(cookie, {'='}, i)
var keyend = i-1
if i >= len(cookie): break
inc(i) # skip '='
var valstart = i
i += skipUntil(cookie, {';'}, i)
result[substr(cookie, keystart, keyend)] = substr(cookie, valstart, i-1)
if i >= len(cookie): break
inc(i) # skip ';'
proc parseQuery*(data: string): Table[string, string] =
result = initTable[string, string]()
let data = data.split("&")
for i in data:
let timed = i.split("=")
if timed.len > 1:
result[decodeUrl(timed[0])] = decodeUrl(timed[1])
proc daysForward*(days: int): DateTime =
return getTime().utc + initTimeInterval(days = days)
template setCookie* (key: string, value: string, time: DateTime) =
headers.add("Set-Cookie", setCookie(key, value, time, request.headers["host"], noName=true))
proc redirect* (request: Request, url: string) {.async.}=
await request.respond(Http303, "", newHttpHeaders([("Location", url)]))
proc respondJson* (request: Request, httpCode: HttpCode, status: string, message: string, data: JsonNode) {.async.} =
let response = %* {
"status": status,
"msg": message,
"data": data
}
await request.respond(httpCode, $response, newHttpHeaders([("Content-Type","application/json")]))
macro pages*(server: ServerRef, body: untyped): untyped =
## This macro provides convenient page adding.
##
## `body` should be StmtList.
## page type can be:
## - ``equals``
## - ``startsWith``
## - ``endsWith``
## - ``regex``
## - ``notfound`` - this page uses without URL argument.
##
## When a new request to the server is received, variables are automatically created:
## - ``request`` - new Request.
## - ``url`` - matched URL.
## - ``equals`` - URL is request.url.path
## - ``startsWith`` - URL is text after `startswith`.
## - ``endsWith`` - URL is text before `endswith`.
## - ``regex`` - `url` param not created.
## - ``notfound`` - `url` param not created.
## - ``decoded_url`` - URL always is request.url.path
var
stmtlist = newStmtList()
notfound_declaration = false
stmtlist.add(
newNimNode(nnkLetSection).add( # let urlParams: JsonNode = await parseQuery(request)
newNimNode(nnkIdentDefs).add( # let decode_url: string = decodeUrl(request.url.path)
ident("decoded_url"),
ident("string"),
newCall(
"decodeUrl",
newNimNode(nnkDotExpr).add(
newNimNode(nnkDotExpr).add(
ident("request"), ident("url")
),
ident("path")
)
)
)
)
)
stmtlist.add(newNimNode(nnkIfStmt))
var ifstmtlist = stmtlist[1]
for i in body: # for each page in statment list.
let
current = if i.len == 3: $i[0] else: "equals"
path = if i.len == 3: i[1] else: i[0]
slist = if i.len == 3: i[2] else: i[1]
if (i.kind == nnkCall and
(path.kind == nnkStrLit or path.kind == nnkCallStrLit or path.kind == nnkEmpty) and
slist.kind == nnkStmtList):
if current == "equals":
slist.insert(0, # let url: string = `path`
newNimNode(nnkLetSection).add(
newNimNode(nnkIdentDefs).add(
ident("url"), ident("string"), path
)
)
)
ifstmtlist.add( # decoded_url == `path`
newNimNode(nnkElifBranch).add(
newCall("==", path, ident("decoded_url")),
slist
)
)
elif current == "startsWith":
slist.insert(0, # let url = decoded_url[`path`.len..^1]
newNimNode(nnkLetSection).add(
newNimNode(nnkIdentDefs).add(
ident("url"),
ident("string"),
newCall(
"[]",
ident("decoded_url"),
newCall("..^", newCall("len", path), newLit(1))
)
)
)
)
ifstmtlist.add( # decode_url.startsWith(`path`)
newNimNode(nnkElifBranch).add(
newCall("startsWith", ident("decoded_url"), path),
slist
)
)
elif current == "endsWith":
slist.insert(0, # let url: string = decoded_url[0..^`path`.len]
newNimNode(nnkLetSection).add(
newNimNode(nnkIdentDefs).add(
ident("url"),
ident("string"),
newCall(
"[]",
ident("decoded_url"),
newCall(
"..^", newLit(0), newCall("+", newLit(1), newCall("len", path))
)
)
)
)
)
ifstmtlist.add( # decode_url.endsWith(`path`)
newNimNode(nnkElifBranch).add(
newCall("endsWith", ident("decoded_url"), path),
slist
)
)
elif current == "regex":
ifstmtlist.add( # decode_url.match(`path`)
newNimNode(nnkElifBranch).add(
newCall("match", ident("decoded_url"), path),
slist))
ifstmtlist.add(
newNimNode(nnkElse).add(
newCall( # await request.respond(Http404, "Not found")
"await",
newCall("respond", ident("request"), ident("Http404"), newLit("Not found"), newCall("newHttpHeaders"))
)
)
)
result = newNimNode(nnkProcDef).add(
ident("receivepages"), # procedure name.
newEmptyNode(), # for template and macros
newEmptyNode(), # generics
newNimNode(nnkFormalParams).add( # proc params
newEmptyNode(), # return type
newNimNode(nnkIdentDefs).add( # param
ident("request"), # param name
ident("Request"), # param type
newEmptyNode() # param default value
)
),
newNimNode(nnkPragma).add( # pragma declaration
ident("async"),
ident("gcsafe")
),
newEmptyNode(),
stmtlist)
macro start*(server: ServerRef): untyped =
result = quote do:
echo "Server starts on http://", `server`.address, ":", `server`.port
waitFor `server`.server.serve(Port(`server`.port), receivepages, `server`.address)