all repos — litestore @ 6921c719266e8c309f20f2947dc6228c7293d769

A minimalist nosql document store.

lib/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
import 
  asynchttpserver2,
  asyncdispatch, 
  times, 
  strutils, 
  pegs, 
  strtabs, 
  logger,
  cgi 
import 
  types, 
  utils, 
  api_v1

proc getReqInfo(req: Request): string =
  var url = req.url.path
  if req.url.anchor != "":
    url = url & "#" & req.url.anchor
  if req.url.query != "":
    url = url & "?" & req.url.query
  return req.hostname & " " & req.reqMethod & " " & url

proc handleCtrlC() {.noconv.} =
  echo ""
  LOG.info("Exiting...")
  quit()

proc processApiUrl(req: Request, LS: LiteStore, info: ResourceInfo): Response = 
  if info.version == "v1" and info.resource.match(peg"^docs / info$"):
    return api_v1.route(req, LS, info.resource, info.id)
  else:
    if info.version != "v1":
      return resError(Http400, "Bad request - Invalid API version: $1" % info.version)
    else:
      if info.resource.decodeURL.strip == "":
        return resError(Http400, "Bad request - No resource specified" % info.resource)
      else:
        return resError(Http400, "Bad request - Invalid resource: $1" % info.resource)


proc process(req: Request, LS: LiteStore): Response {.gcsafe.}=
  var matches = @["", "", ""]
  template route(req, peg: expr, op: stmt): stmt {.immediate.}=
    if req.url.path.find(peg, matches) != -1:
      op
  try: 
    var info: ResourceInfo
    req.route peg"^\/?$":
      info.version = "v1"
      info.resource = "info"
      return req.processApiUrl(LS, info)
    req.route peg"^\/favicon.ico$":
      result.code = Http200
      result.content = LS.favicon
      result.headers = {"Content-Type": "image/x-icon"}.newStringTable
      return result
    req.route PEG_DEFAULT_URL:
      info.version = "v1"
      info.resource = matches[0]
      info.id = matches[1]
      return req.processApiUrl(LS, info)
    req.route PEG_URL:
      info.version = matches[0]
      info.resource = matches[1]
      info.id = matches[2]
      return req.processApiUrl(LS, info)
    raise newException(EInvalidRequest, req.getReqInfo())
  except EInvalidRequest:
    let e = (ref EInvalidRequest)(getCurrentException())
    let trace = e.getStackTrace()
    return resError(Http400, "Bad Request: $1" % getCurrentExceptionMsg(), trace)
  except:
    let e = getCurrentException()
    let trace = e.getStackTrace()
    return resError(Http500, "Internal Server Error: $1" % getCurrentExceptionMsg(), trace)

setControlCHook(handleCtrlC)

proc serve*(LS: LiteStore) =
  var server = newAsyncHttpServer()
  proc handleHttpRequest(req: Request): Future[void] {.async, gcsafe, closure.} =
    LOG.info(getReqInfo(req).replace("$", "$$"))
    let res = req.process(LS)
    await req.respond(res.code, res.content, res.headers)
  LOG.info(LS.appname & " v" & LS.appversion & " started on " & LS.address & ":" & $LS.port & ".")
  if LS.mount:
    LOG.info("Mirroring datastore changes to: " & LS.directory)
  asyncCheck server.serve(LS.port.Port, handleHttpRequest, LS.address)