all repos — min @ 88e4f90f6e717a0c8df091a5c5f532b91a00f85a

A small but practical concatenative programming language.

lib/min_http.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
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
import httpclient, asynchttpserver, asyncdispatch, strutils, uri, critbits
import 
  ../core/parser, 
  ../core/consts,
  ../core/value, 
  ../core/interpreter, 
  ../core/utils

when defined(amd64):
  when defined(windows): 
    {.passL: "-static -Lvendor/openssl/windows -lssl -lcrypto -lws2_32".}
  elif defined(linux):
    {.passL: "-static -Lvendor/openssl/linux -lssl -lcrypto".}
  elif defined(macosx):
    {.passL: "-Bstatic -Lvendor/openssl/macosx -lssl -lcrypto -Bdynamic".}

var minUserAgent {.threadvar.} : string
minUserAgent = "$1 http-module/$2" % [pkgName, pkgVersion]

proc newCli(): HttpClient =
  return newHttpClient(userAgent = minUseragent)

proc newVal(i: In, headers: HttpHeaders): MinValue = 
  result = newDict(i.scope)
  for k, v in headers:
    result = i.dset(result, k, v.newVal)

type MinServerExit = ref object of CatchableError 

# Http

proc http_module*(i: In)=
  let def = i.define()

  def.symbol("request") do (i: In) {.gcsafe.}:
    let vals = i.expect "dict"
    let req = vals[0]
    let cli = newCli()
    var body = "".newVal
    var rawHeaders, meth, url: MinValue
    var headers = newHttpHeaders()
    if not req.dhas("method"):
      raiseInvalid("Request method not specified")
    if not req.dhas("url"):
      raiseInvalid("Request URL not specified")
    if req.dhas("headers"):
      rawHeaders = i.dget(req, "headers")
      if not rawHeaders.isDictionary:
        raiseInvalid("Headers must be specified as a dictionary")
      for item in rawHeaders.dVal.pairs:
        headers[item.key] = i.dget(rawHeaders, item.key).getString
    if req.dhas("body"):
      body = i.dget(req, "body")
    meth = i.dget(req, "method")
    url = i.dget(req, "url")
    let resp = cli.request(url = url.getString, httpMethod = meth.getString, body = body.getString, headers = headers)
    var res = newDict(i.scope)
    res = i.dset(res, "version", resp.version.newVal)
    res = i.dset(res, "status", resp.status[0..2].parseInt.newVal)
    res = i.dset(res, "headers", i.newVal(resp.headers))
    var b = ""
    if resp.status != $Http204:
      b = resp.body
    res = i.dset(res, "body", b.newVal)
    i.push res
  
  def.symbol("get-content") do (i: In):
    let vals = i.expect "string"
    let url = vals[0]
    let cli = newCli()
    i.push cli.getContent(url.getString).newVal

  def.symbol("download") do (i: In):
    let vals = i.expect("string", "string")
    let file = vals[0]
    let url = vals[1]
    let cli = newCli()
    cli.downloadFile(url.getString, file.getString)

  def.symbol("start-server") do (ii: In) {.gcsafe.}:
    let vals = ii.expect "dict"
    let cfg = vals[0]
    if not cfg.dhas("port"):
      raiseInvalid("Port not specified.")
    if not cfg.dhas("handler"):
      raiseInvalid("Handler quotation not specified.")
    let port = ii.dget(cfg, "port")
    var qhandler = ii.dget(cfg, "handler")
    var address = "".newVal
    if cfg.dhas("address"):
      address = ii.dget(cfg, "address")
    if not qhandler.isQuotation:
      raiseInvalid("Handler is not a quotation.")
    if not port.isInt:
      raiseInvalid("Port is not an integer.")
    var server = newAsyncHttpServer()
    var i {.threadvar.}: MinInterpreter
    i = ii
    proc handler(req: Request) {.async, gcsafe.} =
      var qreq = newDict(i.scope)
      qreq = i.dset(qreq, "url", newVal($req.url))
      qreq = i.dset(qreq, "headers", i.newVal(req.headers))
      qreq = i.dset(qreq, "method", newVal($req.reqMethod))
      qreq = i.dset(qreq, "hostname", newVal($req.hostname))
      qreq = i.dset(qreq, "version", newVal("$1.$2" % [$req.protocol.major, $req.protocol.minor]))
      qreq = i.dset(qreq, "body", newVal($req.body))
      i.push qreq
      i.dequote qhandler
      let qres = i.pop
      var body = "".newVal
      var rawHeaders = newDict(i.scope)
      var v = "1.1"
      var status = 200.newVal
      if not qres.isDictionary():
        raiseInvalid("Response is not a dictionary.")
      if qres.dhas("status"):
        status = i.dget(qres, "status")
      if not status.isInt and status.intVal < 600:
        raiseInvalid("Invalid status code: $1." % $status)
      if qres.dhas("body"):
        body = i.dget(qres, "body")
      if not body.isString:
        raiseInvalid("Response body is not a string.")
      if qres.dhas("version"):
        v = i.dget(qres, "version").getString
      if qres.dhas("headers"):
        rawHeaders = i.dget(qres, "headers")
      if not rawHeaders.isDictionary():
        raiseInvalid("Response headers are not in a dictionary.")
      var headers = newHttpHeaders()
      for k in items(i.keys(rawHeaders).qVal):
        headers[k.getString] = i.dget(rawHeaders, k.getString).getString
      await req.respond(status.intVal.HttpCode, body.getString, headers)
    try:
      waitFor server.serve(port = port.intVal.Port, callback = handler, address = address.getString)
    except MinServerExit:
      server.close()

  def.symbol("stop-server") do (i: In):
    raise MinServerExit()
    

  def.finalize("http")