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
144
145
146
147
148
149
150
151
152
153
|
import options
import streams
import strutils
import bindings/curl
import io/headers
import io/request
import ips/serialize
import types/blob
import types/formdata
import types/url
import utils/opt
import utils/twtstr
type
HandleData* = ref HandleDataObj
HandleDataObj = object
curl*: CURL
statusline: bool
headers: Headers
request: Request
ostream*: Stream
mime: curl_mime
slist: curl_slist
func newHandleData(curl: CURL, request: Request, ostream: Stream): HandleData =
let handleData = HandleData(
headers: newHeaders(),
curl: curl,
ostream: ostream,
request: request
)
return handleData
proc cleanup*(handleData: HandleData) =
if handleData.mime != nil:
curl_mime_free(handleData.mime)
if handleData.slist != nil:
curl_slist_free_all(handleData.slist)
curl_easy_cleanup(handleData.curl)
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[HandleData](userdata)
if not op.statusline:
op.statusline = true
try:
op.ostream.swrite(int(CURLE_OK))
except IOError: # Broken pipe
return 0
var status: clong
op.curl.getinfo(CURLINFO_RESPONSE_CODE, addr status)
op.ostream.swrite(cast[int](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 handleData = cast[HandleData](userdata)
if nmemb > 0:
try:
handleData.ostream.writeData(p, int(nmemb))
except IOError: # Broken pipe
return 0
return nmemb
proc applyPostBody(curl: CURL, request: Request, handleData: HandleData) =
if request.multipart.isOk:
handleData.mime = curl_mime_init(curl)
if handleData.mime == nil:
# fail (TODO: raise?)
handleData.ostream.swrite(-1)
handleData.ostream.flush()
return
for entry in request.multipart.get:
let part = curl_mime_addpart(handleData.mime)
if part == nil:
# fail (TODO: raise?)
handleData.ostream.swrite(-1)
handleData.ostream.flush()
return
curl_mime_name(part, cstring(entry.name))
if entry.isstr:
curl_mime_data(part, cstring(entry.svalue), csize_t(entry.svalue.len))
else:
let blob = entry.value
if blob.isfile: #TODO ?
curl_mime_filedata(part, cstring(WebFile(blob).path))
else:
curl_mime_data(part, blob.buffer, csize_t(blob.size))
# may be overridden by curl_mime_filedata, so set it here
curl_mime_filename(part, cstring(entry.filename))
curl.setopt(CURLOPT_MIMEPOST, handleData.mime)
elif request.body.issome:
curl.setopt(CURLOPT_POSTFIELDS, cstring(request.body.get))
curl.setopt(CURLOPT_POSTFIELDSIZE, request.body.get.len)
proc loadHttp*(curlm: CURLM, request: Request, ostream: Stream): HandleData =
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)
let handleData = curl.newHandleData(request, ostream)
curl.setopt(CURLOPT_WRITEDATA, handleData)
curl.setopt(CURLOPT_WRITEFUNCTION, curlWriteBody)
curl.setopt(CURLOPT_HEADERDATA, handleData)
curl.setopt(CURLOPT_HEADERFUNCTION, curlWriteHeader)
if request.proxy != nil:
let purl = request.proxy.serialize()
curl.setopt(CURLOPT_PROXY, purl)
case request.httpmethod
of HTTP_GET:
curl.setopt(CURLOPT_HTTPGET, 1)
of HTTP_POST:
curl.setopt(CURLOPT_POST, 1)
curl.applyPostBody(request, handleData)
else: discard #TODO
for k, v in request.headers:
let header = k & ": " & v
handleData.slist = curl_slist_append(handleData.slist, cstring(header))
if handleData.slist != nil:
curl.setopt(CURLOPT_HTTPHEADER, handleData.slist)
let res = curl_multi_add_handle(curlm, curl)
if res != CURLM_OK:
ostream.swrite(int(res))
ostream.flush()
#TODO: raise here?
return
return handleData
|