123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264 |
- # author: Ethosa
- # ----- CORE ----- #
- import asyncdispatch
- import asynchttpserver
- export asyncdispatch
- export asynchttpserver
- # ----- SUPPORT ----- #
- import asyncfile # loadtemplate
- import strutils # startsWith, endsWith
- export strutils
- import macros
- import json # urlParams
- export json
- import uri # decodeUri
- export uri
- import os
- import re # regex
- export re
- type
- ServerRef* = ref object
- debug*: bool
- port*: uint16
- address*: string
- server*: AsyncHttpServer
- proc newServer*(address: string = "127.0.0.1",
- port: uint16 = 5000, debug: bool = false): ServerRef =
- ## Creates a new ServerRef object.
- ##
- ## Arguments:
- ## - ``address`` - server address, e.g. "127.0.0.1"
- ## - ``port`` - server port, e.g. 5000
- ## - ``debug`` - debug mode
- if not existsDir("templates"):
- createDir("templates")
- return ServerRef(
- address: address, port: port,
- server: newAsyncHttpServer(), debug: debug
- )
- proc loadtemplate*(name: string): Future[string] {.async, inline.} =
- var
- file = openAsync(("templates" / name) & ".html")
- readed = await file.readAll()
- file.close()
- return readed
- proc parseQuery*(request: Request): Future[JsonNode] {.async.} =
- ## Decodes query.
- ## e.g.:
- ## "a=5&b=10" -> {"a": "5", "b": "10"}
- var data = request.url.query.split("&")
- result = %*{}
- for i in data:
- var timed = i.split("=")
- if timed.len > 1:
- result[decodeUrl(timed[0])] = %decodeUrl(timed[1])
- macro answer*(request, message: untyped): untyped =
- ## Responds from server with utf-8.
- result = newCall(
- "respond",
- request,
- ident("Http200"),
- newCall(
- "&",
- newLit("<head><meta charset='utf-8'></head>"),
- message
- )
- )
- macro pages*(server: ServerRef, body: untyped): untyped =
- ## This macro provides convenient page adding.
- ##
- ## ..code-block::Nim
- ## server.pages:
- ## equals("/home"):
- ## echo url
- ## echo urlParams
- var
- stmtlist = newStmtList()
- notfound_declaration = false
- stmtlist.add( # let urlParams: JsonNode = await parseQuery(request)
- newNimNode(nnkLetSection).add(
- newNimNode(nnkIdentDefs).add(
- ident("urlParams"),
- ident("JsonNode"),
- newCall(
- "await",
- newCall(
- "parseQuery",
- ident("request")
- )
- )
- )
- ),
- newNimNode(nnkLetSection).add(
- newNimNode(nnkIdentDefs).add(
- ident("decoded_url"),
- ident("string"),
- newCall(
- "decodeUrl",
- newNimNode(nnkDotExpr).add(
- newNimNode(nnkDotExpr).add(
- ident("request"), ident("url")
- ),
- ident("path")
- )
- )
- )
- )
- )
- stmtlist.add(newNimNode(nnkIfStmt))
- for i in body: # for each page in statment list.
- let
- current = $i[0]
- path = if i.len == 3: i[1] else: newEmptyNode()
- slist = if i.len == 3: i[2] else: i[1]
- if (i.kind == nnkCall and i[0].kind == nnkIdent and
- (path.kind == nnkStrLit or path.kind == nnkCallStrLit or path.kind == nnkEmpty) and
- slist.kind == nnkStmtList):
- if current == "equals":
- slist.insert(0,
- newNimNode(nnkLetSection).add(
- newNimNode(nnkIdentDefs).add(
- ident("url"),
- ident("string"),
- path
- )
- )
- )
- stmtlist[2].add( # request.path.url == i[1]
- newNimNode(nnkElifBranch).add(
- newCall("==", path, ident("decoded_url")),
- slist))
- elif current == "startswith":
- slist.insert(0,
- newNimNode(nnkLetSection).add(
- newNimNode(nnkIdentDefs).add(
- ident("url"),
- ident("string"),
- newCall(
- "[]",
- ident("decoded_url"),
- newCall(
- "..^",
- newCall("len", path),
- newLit(1))
- )
- )
- )
- )
- stmtlist[2].add(
- newNimNode(nnkElifBranch).add(
- newCall(
- "startsWith",
- ident("decoded_url"),
- path),
- slist))
- elif current == "endswith":
- slist.insert(0,
- newNimNode(nnkLetSection).add(
- newNimNode(nnkIdentDefs).add(
- ident("url"),
- ident("string"),
- newCall(
- "[]",
- ident("decoded_url"),
- newCall(
- "..^",
- newLit(0),
- newCall("+", newLit(1), newCall("len", path))
- )
- )
- )
- )
- )
- stmtlist[2].add(
- newNimNode(nnkElifBranch).add(
- newCall(
- "endsWith",
- ident("decoded_url"),
- path),
- slist))
- elif current == "regex":
- slist.insert(0,
- newNimNode(nnkDiscardStmt).add(
- newCall("match", ident("decoded_url"), path, ident("url"))
- )
- )
- slist.insert(0,
- newNimNode(nnkVarSection).add(
- newNimNode(nnkIdentDefs).add(
- ident("url"),
- newNimNode(nnkBracketExpr).add(
- ident("array"),
- newLit(20),
- ident("string")
- ),
- newEmptyNode()
- )
- ))
- stmtlist[2].add(
- newNimNode(nnkElifBranch).add(
- newCall(
- "match",
- ident("decoded_url"),
- path),
- slist))
- elif current == "notfound":
- notfound_declaration = true
- stmtlist[2].add(newNimNode(nnkElse).add(slist))
- if not notfound_declaration:
- stmtlist[2].add(
- newNimNode(nnkElse).add(
- newCall(
- "await",
- newCall(
- "respond",
- ident("request"),
- ident("Http404"),
- newLit("Not found"))
- )
- )
- )
- result = newNimNode(nnkProcDef).add(
- newNimNode(nnkPostfix).add(
- ident("*"), 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 =
- ## Starts server.
- result = quote do:
- if `server`.debug:
- echo "Server starts on http://", `server`.address, ":", `server`.port
- waitFor `server`.server.serve(Port(`server`.port), receivepages)
|