all repos — min @ 568d793dca984d3e6735c9a6845f5fe9a31ec1be

A small but practical concatenative programming language.

core/server.nim

 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
import 
  net,
  asynchttpserver,
  asyncdispatch,
  httpclient,
  streams,
  critbits,
  pegs,
  strutils

import
  regex,
  types,
  parser,
  interpreter,
  utils


proc validUrl(req: Request, url: string): bool =
  return req.url.path == url or req.url.path == url & "/"

proc validMethod(req: Request, meth: string): bool =
  return req.reqMethod == meth or req.reqMethod == meth.toLower

proc parseException(host: string): string =
  let e = getCurrentException()
  result = """($1 ("$2" "$3"))""" % [host, regex.replace($e.name, ":.+$", ""), e.msg]

proc exec(req: Request, interpreter: MinInterpreter, hosts: CritBitTree[string]): string {.gcsafe.}=
  let filename = "request"
  let s = newStringStream(req.body)
  var i = interpreter
  i.open(s, filename)
  discard i.parser.getToken() 
  try:
    i.interpret()
    result = i.dump()
  except:
    echo getCurrentExceptionMsg()
    result = i.link.name.parseException
  finally:
    i.close()

proc process(req: Request, i: MinInterpreter, hosts: CritBitTree[string]): string {.gcsafe.} =
  var matches = @["", "", ""]
  template route(req, peg: expr, op: stmt): stmt {.immediate.}=
    if req.url.path.find(peg, matches) != -1:
      op
  req.route peg"^\/?$":
    if not req.validMethod("GET"):
      raiseServer(Http405, "Method Not Allowed: " & req.reqMethod)
    return "MiNiM Host '$1'" % [i.link.name]
  req.route peg"^\/exec\/?$":
    if not req.validMethod("POST"):
      raiseServer(Http405, "Method Not Allowed: " & req.reqMethod)
    return exec(req, i, hosts)
  raiseServer(Http400, "Bad Request: POST "& req.url.path)
  

proc init*(link: ref MinLink) {.thread.} =
  proc handleHttpRequest(req: Request): Future[void] {.async.} =
    var res: string
    var code: HttpCode = Http200
    try:
      res = req.process(link.interpreter, link.hosts)
    except MinServerError:
      let e: MinServerError = (MinServerError)getCurrentException()
      res = e.msg
      code = e.code
    await req.respond(code, res)
  asyncCheck link.server.serve(link.port, handleHttpRequest, link.address)

proc remoteExec*(i: var MinInterpreter, host, content: string): string {.gcsafe.}=
  if i.link.hosts.hasKey(host):
    let url = "http://" & i.link.hosts[host] & "/exec"
    try:
      result = "($1 $2)" % [host, url.postContent(body = content, sslContext = nil)]
    except:
      result = host.parseException
  else:
    raiseServer(Http404, "Not Found: Host '$1'" % [host])

proc syncHosts*(i: In): CritBitTree[string] {.gcsafe.}=
  var cmd = ""
  for key, val in i.link.hosts.pairs:
    cmd = cmd & """ ($1 "$2")""" % [key, val] 
  cmd = "(" & cmd.strip & ") set-hosts"
  for key, val in i.link.hosts.pairs:
    if key != i.link.name:
      result[key] = i.remoteExec(key, cmd)

proc checkHost*(link: ref MinLink) =
  let host = link.address & ":" & $link.port
  try:
    link.hosts = cfgGet("hosts").critbit
  except:
    discard
  var name: string
  for key, value in link.hosts.pairs:
    if host == value:
      name = key
      break
  if not name.isNil:
    link.hosts.excl name
  link.hosts[link.name] = host
  discard link.interpreter.syncHosts()
  
proc executeOnHost*(i: var MinInterpreter, host: string, q: MinValue): string =
  if not i.link.hosts.hasKey(host):
    raiseInvalid("Unknown host: " & host)
  return i.remoteExec(host, $q & " unquote")

proc newMinLink*(name, address: string, port: int, i: var MinInterpreter): ref MinLink =
  var link: ref MinLink = new MinLink
  result = link
  result.server = newAsyncHttpServer()
  result.name = name
  result.address = address
  result.port = port.Port
  i.link = result
  result.interpreter = i