akane.nim 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. # author: Ethosa
  2. # ----- CORE ----- #
  3. import asyncdispatch
  4. import asynchttpserver
  5. export asyncdispatch
  6. export asynchttpserver
  7. # ----- SUPPORT ----- #
  8. import asyncfile # loadtemplate
  9. import strutils # startsWith, endsWith
  10. export strutils
  11. import macros
  12. import json # urlParams
  13. export json
  14. import uri # decodeUri
  15. export uri
  16. import os
  17. import re # regex
  18. export re
  19. type
  20. ServerRef* = ref object
  21. debug*: bool
  22. port*: uint16
  23. address*: string
  24. server*: AsyncHttpServer
  25. proc newServer*(address: string = "127.0.0.1",
  26. port: uint16 = 5000, debug: bool = false): ServerRef =
  27. ## Creates a new ServerRef object.
  28. ##
  29. ## Arguments:
  30. ## - ``address`` - server address, e.g. "127.0.0.1"
  31. ## - ``port`` - server port, e.g. 5000
  32. ## - ``debug`` - debug mode
  33. if not existsDir("templates"):
  34. createDir("templates")
  35. return ServerRef(
  36. address: address, port: port,
  37. server: newAsyncHttpServer(), debug: debug
  38. )
  39. proc loadtemplate*(name: string): Future[string] {.async, inline.} =
  40. var
  41. file = openAsync(("templates" / name) & ".html")
  42. readed = await file.readAll()
  43. file.close()
  44. return readed
  45. proc parseQuery*(request: Request): Future[JsonNode] {.async.} =
  46. ## Decodes query.
  47. ## e.g.:
  48. ## "a=5&b=10" -> {"a": "5", "b": "10"}
  49. var data = request.url.query.split("&")
  50. result = %*{}
  51. for i in data:
  52. var timed = i.split("=")
  53. if timed.len > 1:
  54. result[decodeUrl(timed[0])] = %decodeUrl(timed[1])
  55. macro answer*(request, message: untyped): untyped =
  56. ## Responds from server with utf-8.
  57. result = newCall(
  58. "respond",
  59. request,
  60. ident("Http200"),
  61. newCall(
  62. "&",
  63. newLit("<head><meta charset='utf-8'></head>"),
  64. message
  65. )
  66. )
  67. macro pages*(server: ServerRef, body: untyped): untyped =
  68. ## This macro provides convenient page adding.
  69. ##
  70. ## ..code-block::Nim
  71. ## server.pages:
  72. ## equals("/home"):
  73. ## echo url
  74. ## echo urlParams
  75. var
  76. stmtlist = newStmtList()
  77. notfound_declaration = false
  78. stmtlist.add( # let urlParams: JsonNode = await parseQuery(request)
  79. newNimNode(nnkLetSection).add(
  80. newNimNode(nnkIdentDefs).add(
  81. ident("urlParams"),
  82. ident("JsonNode"),
  83. newCall(
  84. "await",
  85. newCall(
  86. "parseQuery",
  87. ident("request")
  88. )
  89. )
  90. )
  91. ),
  92. newNimNode(nnkLetSection).add(
  93. newNimNode(nnkIdentDefs).add(
  94. ident("decoded_url"),
  95. ident("string"),
  96. newCall(
  97. "decodeUrl",
  98. newNimNode(nnkDotExpr).add(
  99. newNimNode(nnkDotExpr).add(
  100. ident("request"), ident("url")
  101. ),
  102. ident("path")
  103. )
  104. )
  105. )
  106. )
  107. )
  108. stmtlist.add(newNimNode(nnkIfStmt))
  109. for i in body: # for each page in statment list.
  110. let
  111. current = $i[0]
  112. path = if i.len == 3: i[1] else: newEmptyNode()
  113. slist = if i.len == 3: i[2] else: i[1]
  114. if (i.kind == nnkCall and i[0].kind == nnkIdent and
  115. (path.kind == nnkStrLit or path.kind == nnkCallStrLit or path.kind == nnkEmpty) and
  116. slist.kind == nnkStmtList):
  117. if current == "equals":
  118. slist.insert(0,
  119. newNimNode(nnkLetSection).add(
  120. newNimNode(nnkIdentDefs).add(
  121. ident("url"),
  122. ident("string"),
  123. path
  124. )
  125. )
  126. )
  127. stmtlist[2].add( # request.path.url == i[1]
  128. newNimNode(nnkElifBranch).add(
  129. newCall("==", path, ident("decoded_url")),
  130. slist))
  131. elif current == "startswith":
  132. slist.insert(0,
  133. newNimNode(nnkLetSection).add(
  134. newNimNode(nnkIdentDefs).add(
  135. ident("url"),
  136. ident("string"),
  137. newCall(
  138. "[]",
  139. ident("decoded_url"),
  140. newCall(
  141. "..^",
  142. newCall("len", path),
  143. newLit(1))
  144. )
  145. )
  146. )
  147. )
  148. stmtlist[2].add(
  149. newNimNode(nnkElifBranch).add(
  150. newCall(
  151. "startsWith",
  152. ident("decoded_url"),
  153. path),
  154. slist))
  155. elif current == "endswith":
  156. slist.insert(0,
  157. newNimNode(nnkLetSection).add(
  158. newNimNode(nnkIdentDefs).add(
  159. ident("url"),
  160. ident("string"),
  161. newCall(
  162. "[]",
  163. ident("decoded_url"),
  164. newCall(
  165. "..^",
  166. newLit(0),
  167. newCall("+", newLit(1), newCall("len", path))
  168. )
  169. )
  170. )
  171. )
  172. )
  173. stmtlist[2].add(
  174. newNimNode(nnkElifBranch).add(
  175. newCall(
  176. "endsWith",
  177. ident("decoded_url"),
  178. path),
  179. slist))
  180. elif current == "regex":
  181. slist.insert(0,
  182. newNimNode(nnkDiscardStmt).add(
  183. newCall("match", ident("decoded_url"), path, ident("url"))
  184. )
  185. )
  186. slist.insert(0,
  187. newNimNode(nnkVarSection).add(
  188. newNimNode(nnkIdentDefs).add(
  189. ident("url"),
  190. newNimNode(nnkBracketExpr).add(
  191. ident("array"),
  192. newLit(20),
  193. ident("string")
  194. ),
  195. newEmptyNode()
  196. )
  197. ))
  198. stmtlist[2].add(
  199. newNimNode(nnkElifBranch).add(
  200. newCall(
  201. "match",
  202. ident("decoded_url"),
  203. path),
  204. slist))
  205. elif current == "notfound":
  206. notfound_declaration = true
  207. stmtlist[2].add(newNimNode(nnkElse).add(slist))
  208. if not notfound_declaration:
  209. stmtlist[2].add(
  210. newNimNode(nnkElse).add(
  211. newCall(
  212. "await",
  213. newCall(
  214. "respond",
  215. ident("request"),
  216. ident("Http404"),
  217. newLit("Not found"))
  218. )
  219. )
  220. )
  221. result = newNimNode(nnkProcDef).add(
  222. newNimNode(nnkPostfix).add(
  223. ident("*"), ident("receivepages") # procedure name.
  224. ),
  225. newEmptyNode(), # for template and macros
  226. newEmptyNode(), # generics
  227. newNimNode(nnkFormalParams).add( # proc params
  228. newEmptyNode(), # return type
  229. newNimNode(nnkIdentDefs).add( # param
  230. ident("request"), # param name
  231. ident("Request"), # param type
  232. newEmptyNode() # param default value
  233. )
  234. ),
  235. newNimNode(nnkPragma).add( # pragma declaration
  236. ident("async"),
  237. ident("gcsafe")
  238. ),
  239. newEmptyNode(),
  240. stmtlist)
  241. macro start*(server: ServerRef): untyped =
  242. ## Starts server.
  243. result = quote do:
  244. if `server`.debug:
  245. echo "Server starts on http://", `server`.address, ":", `server`.port
  246. waitFor `server`.server.serve(Port(`server`.port), receivepages)