about summary refs log tree commit diff stats
path: root/src/io/http.nim
blob: 36cd6473872e35c943915414f4ef5a83ad150e6e (plain) (blame)
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
import options
import streams
import strutils

import bindings/curl
import io/request
import ips/serialize
import types/url
import utils/twtstr

type
  HeaderOpaque* = ref object
    statusline: bool
    headers: HeaderList
    curl: CURL
    request: Request
    ostream: Stream

func newHeaderOpaque(curl: CURL, request: Request, ostream: Stream): HeaderOpaque =
  HeaderOpaque(headers: newHeaderList(), curl: curl, ostream: ostream, request: request)

template setopt(curl: CURL, opt: CURLoption, arg: typed) =
  discard curl_easy_setopt(curl, opt, arg)

template setopt(curl: CURL, opt: CURLoption, arg: string) =
  discard curl_easy_setopt(curl, opt, cstring(arg))

template getinfo(curl: CURL, info: CURLINFO, arg: typed) =
  discard curl_easy_getinfo(curl, info, arg)

proc curlWriteHeader(p: cstring, size: csize_t, nitems: csize_t, userdata: pointer): csize_t {.cdecl.} =
  var line = newString(nitems)
  for i in 0..<nitems:
    line[i] = p[i]

  let op = cast[HeaderOpaque](userdata)
  if not op.statusline:
    op.statusline = true
    op.ostream.swrite(int(CURLE_OK))
    var status: int
    op.curl.getinfo(CURLINFO_RESPONSE_CODE, addr status)
    op.ostream.swrite(status)
    return nitems

  let k = line.until(':')

  if k.len == line.len:
    # empty line (last, before body) or invalid (=> error)
    op.ostream.swrite(op.headers)
    return nitems

  let v = line.substr(k.len + 1).strip()
  op.headers.add(k, v)
  return nitems

proc curlWriteBody(p: cstring, size: csize_t, nmemb: csize_t, userdata: pointer): csize_t {.cdecl.} =
  let stream = cast[Stream](userdata)
  if nmemb > 0:
    stream.writeData(p, int(nmemb))
    stream.flush()
  return nmemb

proc loadHttp*(request: Request, ostream: Stream) =
  let curl = curl_easy_init()

  if curl == nil:
    ostream.swrite(-1)
    ostream.flush()
    return # fail

  let surl = request.url.serialize()
  curl.setopt(CURLOPT_URL, surl)

  curl.setopt(CURLOPT_WRITEDATA, ostream)
  curl.setopt(CURLOPT_WRITEFUNCTION, curlWriteBody)

  let headerres = curl.newHeaderOpaque(request, ostream)

  GC_ref(headerres) # this could get unref'd before writeheader finishes
  GC_ref(ostream) #TODO not sure about this one, but better safe than sorry
  defer:
    GC_unref(headerres)
    GC_unref(ostream)

  curl.setopt(CURLOPT_HEADERDATA, headerres)
  curl.setopt(CURLOPT_HEADERFUNCTION, curlWriteHeader)

  var mime: curl_mime = nil

  case request.httpmethod
  of HTTP_GET: curl.setopt(CURLOPT_HTTPGET, 1)
  of HTTP_POST:
    curl.setopt(CURLOPT_POST, 1)
    if request.multipart.issome:
      mime = curl_mime_init(curl)
      if mime == nil: return # fail
      for entry in request.multipart.get.content:
        let part = curl_mime_addpart(mime)
        if part == nil: return # fail
        curl_mime_name(part, cstring(entry.name))
        if entry.isFile:
          if entry.isStream:
            curl_mime_filedata(part, cstring(entry.filename))
          else:
            let fd = readFile(entry.filename)
            curl_mime_data(part, cstring(fd), csize_t(fd.len))
          # may be overridden by curl_mime_filedata, so set it here
          curl_mime_filename(part, cstring(entry.filename))
        else:
          curl_mime_data(part, cstring(entry.content), csize_t(entry.content.len))
      curl.setopt(CURLOPT_MIMEPOST, mime)
    elif request.body.issome:
      curl.setopt(CURLOPT_POSTFIELDS, cstring(request.body.get))
      curl.setopt(CURLOPT_POSTFIELDSIZE, request.body.get.len)
  else: discard #TODO

  var slist: curl_slist = nil
  for k, v in request.headers:
    let header = k & ": " & v
    slist = curl_slist_append(slist, cstring(header))
  if slist != nil:
    curl.setopt(CURLOPT_HTTPHEADER, slist)

  let res = curl_easy_perform(curl)
  if res != CURLE_OK:
    ostream.swrite(int(res))
    ostream.flush()

  curl_easy_cleanup(curl)
  if mime != nil:
    curl_mime_free(mime)
  if slist != nil:
    curl_slist_free_all(slist)