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
|
import std/streams
import std/unicode
import bindings/quickjs
import io/promise
import js/error
import js/javascript
import loader/headers
import loader/request
import types/blob
import types/url
import chakasu/charset
import chakasu/decoderstream
import chakasu/encoderstream
type
ResponseType* = enum
TYPE_DEFAULT = "default"
TYPE_BASIC = "basic"
TYPE_CORS = "cors"
TYPE_ERROR = "error"
TYPE_OPAQUE = "opaque"
TYPE_OPAQUEREDIRECT = "opaqueredirect"
#TODO fully implement headers guards
HeadersGuard* = enum
GUARD_IMMUTABLE = "immutable"
GUARD_REQUEST = "request"
GUARD_REQUEST_NO_CORS = "request-no-cors"
GUARD_RESPONSE = "response"
GUARD_NONE = "none"
Response* = ref object
responseType* {.jsget: "type".}: ResponseType
res*: int
fd*: int
body*: Stream
bodyUsed* {.jsget.}: bool
contentType*: string
status* {.jsget.}: uint16
headers* {.jsget.}: Headers
headersGuard: HeadersGuard
redirect*: Request
url*: URL #TODO should be urllist?
unregisterFun*: proc()
bodyRead*: Promise[string]
charset*: Charset
internalMessage*: string # should NOT be exposed to JS!
jsDestructor(Response)
proc newResponse*(res: int, request: Request, fd = -1, stream: Stream = nil):
Response =
return Response(
res: res,
url: request.url,
body: stream,
bodyRead: Promise[string](),
fd: fd
)
func makeNetworkError*(): Response =
#TODO use "create" function
#TODO headers immutable
return Response(
res: 0,
responseType: TYPE_ERROR,
status: 0,
headers: newHeaders(),
headersGuard: GUARD_IMMUTABLE
)
proc error(): Response {.jsstfunc: "Response".} =
return makeNetworkError()
func sok(response: Response): bool {.jsfget: "ok".} =
return response.status in 200u16 .. 299u16
func surl(response: Response): string {.jsfget: "url".} =
if response.responseType == TYPE_ERROR:
return ""
return $response.url
#TODO: this should be a property of body
proc close*(response: Response) {.jsfunc.} =
response.bodyUsed = true
if response.unregisterFun != nil:
response.unregisterFun()
if response.body != nil:
response.body.close()
proc text*(response: Response): Promise[JSResult[string]] {.jsfunc.} =
if response.body == nil:
let p = newPromise[JSResult[string]]()
p.resolve(JSResult[string].ok(""))
return p
if response.bodyUsed:
let p = newPromise[JSResult[string]]()
let err = JSResult[string]
.err(newTypeError("Body has already been consumed"))
p.resolve(err)
return p
let bodyRead = response.bodyRead
response.bodyRead = nil
return bodyRead.then(proc(s: string): JSResult[string] =
let cs = if response.charset == CHARSET_UNKNOWN:
CHARSET_UTF_8
else:
response.charset
if cs == CHARSET_UTF_8 and s.validateUtf8() == -1:
ok(s)
else:
let ss = newStringStream(s)
let ds = newDecoderStream(ss, cs)
let es = newEncoderStream(ds, CHARSET_UTF_8)
return ok(es.readAll())
)
proc blob*(response: Response): Promise[JSResult[Blob]] {.jsfunc.} =
if response.bodyRead == nil:
let p = newPromise[JSResult[Blob]]()
let err = JSResult[Blob]
.err(newTypeError("Body has already been consumed"))
p.resolve(err)
return p
let bodyRead = response.bodyRead
response.bodyRead = nil
return bodyRead.then(proc(s: string): JSResult[Blob] =
if s.len == 0:
return ok(newBlob(nil, 0, response.contentType, nil))
GC_ref(s)
let deallocFun = proc() =
GC_unref(s)
let blob = newBlob(unsafeAddr s[0], s.len, response.contentType, deallocFun)
ok(blob))
proc json(ctx: JSContext, this: Response): Promise[JSResult[JSValue]]
{.jsfunc.} =
return this.text().then(proc(s: JSResult[string]): JSResult[JSValue] =
let s = ?s
return ok(JS_ParseJSON(ctx, cstring(s), cast[csize_t](s.len),
cstring"<input>")))
proc addResponseModule*(ctx: JSContext) =
ctx.registerType(Response)
|