Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2916075
fetch.c
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
10 KB
Referenced Files
None
Subscribers
None
fetch.c
View Options
#include
<compat.h>
// IWYU pragma: keep
#include
<uv.h>
#include
<stdio.h>
#include
<stdlib.h>
#include
<string.h>
#include
<tlsuv/tlsuv.h>
#include
<tlsuv/http.h>
#include
<utarray.h>
#include
"ant.h"
#include
"errors.h"
#include
"common.h"
#include
"internal.h"
#include
"runtime.h"
#include
"modules/fetch.h"
#include
"modules/json.h"
#include
"modules/timer.h"
typedef
struct
{
char
*
data
;
size_t
size
;
size_t
capacity
;
}
fetch_buffer_t
;
typedef
struct
fetch_request_s
{
ant_t
*
js
;
jsval_t
promise
;
tlsuv_http_t
http_client
;
tlsuv_http_req_t
*
http_req
;
fetch_buffer_t
response_buffer
;
int
status_code
;
int
completed
;
int
failed
;
char
*
error_msg
;
jsval_t
headers_obj
;
char
*
method
;
char
*
body
;
size_t
body_len
;
}
fetch_request_t
;
static
UT_array
*
pending_requests
=
NULL
;
static
void
free_fetch_request
(
fetch_request_t
*
req
)
{
if
(
!
req
)
return
;
if
(
req
->
response_buffer
.
data
)
free
(
req
->
response_buffer
.
data
);
if
(
req
->
error_msg
)
free
(
req
->
error_msg
);
if
(
req
->
method
)
free
(
req
->
method
);
if
(
req
->
body
)
free
(
req
->
body
);
free
(
req
);
}
static
void
remove_pending_request
(
fetch_request_t
*
req
)
{
if
(
!
req
||
!
pending_requests
)
return
;
fetch_request_t
**
p
=
NULL
;
unsigned
int
i
=
0
;
while
((
p
=
(
fetch_request_t
**
)
utarray_next
(
pending_requests
,
p
)))
{
if
(
*
p
==
req
)
{
utarray_erase
(
pending_requests
,
i
,
1
);
break
;
}
i
++
;
}
}
static
jsval_t
fetch_fail_oom
(
ant_t
*
js
,
jsval_t
promise
,
fetch_request_t
*
req
,
bool
close_http
,
const
char
*
msg
,
size_t
len
)
{
jsval_t
err
=
js_mkstr
(
js
,
msg
,
len
);
js_reject_promise
(
js
,
promise
,
err
);
if
(
close_http
)
tlsuv_http_close
(
&
req
->
http_client
,
NULL
);
remove_pending_request
(
req
);
free_fetch_request
(
req
);
return
js_mkundef
();
}
static
jsval_t
response_text
(
ant_t
*
js
,
jsval_t
*
args
,
int
nargs
)
{
jsval_t
this
=
js_getthis
(
js
);
jsval_t
body
=
js_get_slot
(
js
,
this
,
SLOT_DATA
);
jsval_t
promise
=
js_mkpromise
(
js
);
js_resolve_promise
(
js
,
promise
,
body
);
return
promise
;
}
static
jsval_t
response_json
(
ant_t
*
js
,
jsval_t
*
args
,
int
nargs
)
{
jsval_t
this
=
js_getthis
(
js
);
jsval_t
body
=
js_get_slot
(
js
,
this
,
SLOT_DATA
);
jsval_t
parsed
=
js_json_parse
(
js
,
&
body
,
1
);
jsval_t
promise
=
js_mkpromise
(
js
);
if
(
vtype
(
parsed
)
==
T_ERR
)
{
js_reject_promise
(
js
,
promise
,
parsed
);
}
else
js_resolve_promise
(
js
,
promise
,
parsed
);
return
promise
;
}
static
jsval_t
create_response
(
ant_t
*
js
,
int
status
,
const
char
*
body
,
size_t
body_len
)
{
jsval_t
response_obj
=
js_mkobj
(
js
);
jsval_t
body_str
=
js_mkstr
(
js
,
body
,
body_len
);
js_set
(
js
,
response_obj
,
"ok"
,
js_bool
(
status
>=
200
&&
status
<
300
));
js_set
(
js
,
response_obj
,
"status"
,
js_mknum
(
status
));
js_set_slot
(
js
,
response_obj
,
SLOT_DATA
,
body_str
);
js_set
(
js
,
response_obj
,
"text"
,
js_mkfun
(
response_text
));
js_set
(
js
,
response_obj
,
"json"
,
js_mkfun
(
response_json
));
return
response_obj
;
}
static
void
complete_request
(
fetch_request_t
*
req
)
{
if
(
req
->
failed
)
{
jsval_t
err
=
js_mkstr
(
req
->
js
,
req
->
error_msg
?
req
->
error_msg
:
"Unknown error"
,
req
->
error_msg
?
strlen
(
req
->
error_msg
)
:
13
);
js_reject_promise
(
req
->
js
,
req
->
promise
,
err
);
}
else
{
const
char
*
body
=
req
->
response_buffer
.
data
?
req
->
response_buffer
.
data
:
""
;
size_t
body_len
=
req
->
response_buffer
.
data
?
req
->
response_buffer
.
size
:
0
;
jsval_t
response
=
create_response
(
req
->
js
,
req
->
status_code
,
body
,
body_len
);
js_resolve_promise
(
req
->
js
,
req
->
promise
,
response
);
}
remove_pending_request
(
req
);
free_fetch_request
(
req
);
}
static
void
on_http_close
(
tlsuv_http_t
*
client
)
{
fetch_request_t
*
req
=
(
fetch_request_t
*
)
client
->
data
;
if
(
req
&&
req
->
completed
)
complete_request
(
req
);
}
static
void
body_cb
(
tlsuv_http_req_t
*
http_req
,
char
*
body
,
ssize_t
len
)
{
fetch_request_t
*
req
=
(
fetch_request_t
*
)
http_req
->
data
;
if
(
len
==
UV_EOF
)
{
req
->
completed
=
1
;
tlsuv_http_close
(
&
req
->
http_client
,
on_http_close
);
return
;
}
if
(
len
<
0
)
{
req
->
failed
=
1
;
req
->
error_msg
=
strdup
(
uv_strerror
((
int
)
len
));
req
->
completed
=
1
;
tlsuv_http_close
(
&
req
->
http_client
,
on_http_close
);
return
;
}
if
(
req
->
response_buffer
.
size
+
len
>
req
->
response_buffer
.
capacity
)
{
size_t
new_capacity
=
req
->
response_buffer
.
capacity
*
2
;
while
(
new_capacity
<
req
->
response_buffer
.
size
+
len
)
new_capacity
*=
2
;
char
*
new_data
=
realloc
(
req
->
response_buffer
.
data
,
new_capacity
);
if
(
!
new_data
)
{
req
->
failed
=
1
;
req
->
error_msg
=
strdup
(
"Out of memory"
);
req
->
completed
=
1
;
tlsuv_http_close
(
&
req
->
http_client
,
on_http_close
);
return
;
}
req
->
response_buffer
.
data
=
new_data
;
req
->
response_buffer
.
capacity
=
new_capacity
;
}
memcpy
(
req
->
response_buffer
.
data
+
req
->
response_buffer
.
size
,
body
,
len
);
req
->
response_buffer
.
size
+=
len
;
}
static
void
resp_cb
(
tlsuv_http_resp_t
*
resp
,
void
*
data
)
{
(
void
)
data
;
fetch_request_t
*
req
=
(
fetch_request_t
*
)
resp
->
req
->
data
;
if
(
resp
->
code
<
0
)
{
req
->
failed
=
1
;
req
->
error_msg
=
strdup
(
uv_strerror
(
resp
->
code
));
req
->
completed
=
1
;
tlsuv_http_close
(
&
req
->
http_client
,
on_http_close
);
return
;
}
req
->
status_code
=
resp
->
code
;
resp
->
body_cb
=
body_cb
;
}
static
jsval_t
do_fetch_microtask
(
ant_t
*
js
,
jsval_t
*
args
,
int
nargs
)
{
(
void
)
args
;
(
void
)
nargs
;
jsval_t
current_func
=
js_getcurrentfunc
(
js
);
jsval_t
url_val
=
js_get
(
js
,
current_func
,
"url"
);
jsval_t
options_val
=
js_get
(
js
,
current_func
,
"options"
);
jsval_t
promise
=
js_get_slot
(
js
,
current_func
,
SLOT_DATA
);
char
*
url_str
=
js_getstr
(
js
,
url_val
,
NULL
);
if
(
!
url_str
)
{
jsval_t
err
=
js_mkstr
(
js
,
"Invalid URL"
,
11
);
js_reject_promise
(
js
,
promise
,
err
);
return
js_mkundef
();
}
fetch_request_t
*
req
=
calloc
(
1
,
sizeof
(
fetch_request_t
));
if
(
!
req
)
{
jsval_t
err
=
js_mkstr
(
js
,
"Out of memory"
,
13
);
js_reject_promise
(
js
,
promise
,
err
);
return
js_mkundef
();
}
req
->
js
=
js
;
req
->
promise
=
promise
;
req
->
headers_obj
=
options_val
;
req
->
response_buffer
.
capacity
=
16384
;
req
->
response_buffer
.
data
=
malloc
(
req
->
response_buffer
.
capacity
);
req
->
response_buffer
.
size
=
0
;
if
(
!
req
->
response_buffer
.
data
)
{
jsval_t
err
=
js_mkstr
(
js
,
"Out of memory"
,
13
);
js_reject_promise
(
js
,
promise
,
err
);
free
(
req
);
return
js_mkundef
();
}
utarray_push_back
(
pending_requests
,
&
req
);
const
char
*
scheme_end
=
strstr
(
url_str
,
"://"
);
if
(
!
scheme_end
)
return
fetch_fail_oom
(
js
,
promise
,
req
,
false
,
"Invalid URL: no scheme"
,
22
);
const
char
*
host_start
=
scheme_end
+
3
;
const
char
*
path_start
=
strchr
(
host_start
,
'/'
);
const
char
*
at_in_host
=
NULL
;
for
(
const
char
*
p
=
host_start
;
p
<
(
path_start
?
path_start
:
host_start
+
strlen
(
host_start
));
p
++
)
{
if
(
*
p
==
'@'
)
at_in_host
=
p
;
}
if
(
at_in_host
)
host_start
=
at_in_host
+
1
;
size_t
scheme_len
=
scheme_end
-
url_str
;
size_t
host_len
=
path_start
?
(
size_t
)(
path_start
-
host_start
)
:
strlen
(
host_start
);
const
char
*
path
=
path_start
?
path_start
:
"/"
;
if
(
host_len
==
0
)
return
fetch_fail_oom
(
js
,
promise
,
req
,
false
,
"Invalid URL: no host"
,
20
);
char
*
host_url
=
calloc
(
1
,
scheme_len
+
3
+
host_len
+
1
);
if
(
!
host_url
)
return
fetch_fail_oom
(
js
,
promise
,
req
,
false
,
"Out of memory"
,
13
);
snprintf
(
host_url
,
scheme_len
+
3
+
host_len
+
1
,
"%.*s://%.*s"
,
(
int
)
scheme_len
,
url_str
,
(
int
)
host_len
,
host_start
);
int
rc
=
tlsuv_http_init
(
uv_default_loop
(),
&
req
->
http_client
,
host_url
);
free
(
host_url
);
if
(
rc
!=
0
)
return
fetch_fail_oom
(
js
,
promise
,
req
,
false
,
"Failed to initialize HTTP client"
,
33
);
req
->
http_client
.
data
=
req
;
req
->
method
=
NULL
;
req
->
body
=
NULL
;
req
->
body_len
=
0
;
if
(
is_special_object
(
options_val
))
{
jsval_t
method_val
=
js_get
(
js
,
options_val
,
"method"
);
if
(
vtype
(
method_val
)
==
T_STR
)
{
char
*
str
=
js_getstr
(
js
,
method_val
,
NULL
);
if
(
str
)
{
req
->
method
=
strdup
(
str
);
if
(
!
req
->
method
)
return
fetch_fail_oom
(
js
,
promise
,
req
,
true
,
"Out of memory"
,
13
);
}
}
jsval_t
body_val
=
js_get
(
js
,
options_val
,
"body"
);
if
(
vtype
(
body_val
)
==
T_STR
)
{
size_t
len
;
char
*
str
=
js_getstr
(
js
,
body_val
,
&
len
);
if
(
str
&&
len
>
0
)
{
req
->
body
=
malloc
(
len
);
if
(
!
req
->
body
)
return
fetch_fail_oom
(
js
,
promise
,
req
,
true
,
"Out of memory"
,
13
);
memcpy
(
req
->
body
,
str
,
len
);
req
->
body_len
=
len
;
}
}
}
if
(
!
req
->
method
)
{
req
->
method
=
strdup
(
"GET"
);
if
(
!
req
->
method
)
return
fetch_fail_oom
(
js
,
promise
,
req
,
true
,
"Out of memory"
,
13
);
}
req
->
http_req
=
tlsuv_http_req
(
&
req
->
http_client
,
req
->
method
,
path
,
resp_cb
,
req
);
if
(
!
req
->
http_req
)
return
fetch_fail_oom
(
js
,
promise
,
req
,
true
,
"Failed to create HTTP request"
,
30
);
req
->
http_req
->
data
=
req
;
char
user_agent
[
256
];
snprintf
(
user_agent
,
sizeof
(
user_agent
),
"ant/%s"
,
ANT_VERSION
);
tlsuv_http_req_header
(
req
->
http_req
,
"User-Agent"
,
user_agent
);
if
(
is_special_object
(
options_val
))
{
jsval_t
headers_val
=
js_get
(
js
,
options_val
,
"headers"
);
if
(
is_special_object
(
headers_val
))
{
ant_iter_t
iter
=
js_prop_iter_begin
(
js
,
headers_val
);
const
char
*
key
;
size_t
key_len
;
jsval_t
value
;
while
(
js_prop_iter_next
(
&
iter
,
&
key
,
&
key_len
,
&
value
))
{
char
*
value_str
=
js_getstr
(
js
,
value
,
NULL
);
if
(
value_str
)
{
char
*
key_str
=
strndup
(
key
,
key_len
);
if
(
key_str
)
{
tlsuv_http_req_header
(
req
->
http_req
,
key_str
,
value_str
);
free
(
key_str
);
}
}
}
js_prop_iter_end
(
&
iter
);
}
}
if
(
req
->
body
&&
req
->
body_len
>
0
)
{
tlsuv_http_req_data
(
req
->
http_req
,
req
->
body
,
req
->
body_len
,
body_cb
);
}
return
js_mkundef
();
}
static
jsval_t
js_fetch
(
ant_t
*
js
,
jsval_t
*
args
,
int
nargs
)
{
if
(
nargs
<
1
)
return
js_mkerr
(
js
,
"fetch requires at least 1 argument"
);
if
(
vtype
(
args
[
0
])
!=
T_STR
)
return
js_mkerr
(
js
,
"fetch URL must be a string"
);
jsval_t
url_val
=
args
[
0
];
jsval_t
options_val
=
nargs
>
1
?
args
[
1
]
:
js_mkundef
();
jsval_t
promise
=
js_mkpromise
(
js
);
jsval_t
wrapper_obj
=
js_mkobj
(
js
);
js_set_slot
(
js
,
wrapper_obj
,
SLOT_DATA
,
promise
);
js_set_slot
(
js
,
wrapper_obj
,
SLOT_CFUNC
,
js_mkfun
(
do_fetch_microtask
));
js_set
(
js
,
wrapper_obj
,
"url"
,
url_val
);
js_set
(
js
,
wrapper_obj
,
"options"
,
options_val
);
queue_microtask
(
js
,
js_obj_to_func
(
wrapper_obj
));
return
promise
;
}
void
init_fetch_module
()
{
utarray_new
(
pending_requests
,
&
ut_ptr_icd
);
js_set
(
rt
->
js
,
rt
->
js
->
global
,
"fetch"
,
js_mkfun
(
js_fetch
));
}
int
has_pending_fetches
(
void
)
{
return
pending_requests
&&
utarray_len
(
pending_requests
)
>
0
;
}
void
fetch_gc_update_roots
(
GC_OP_VAL_ARGS
)
{
if
(
!
pending_requests
)
return
;
unsigned
int
len
=
utarray_len
(
pending_requests
);
for
(
unsigned
int
i
=
0
;
i
<
len
;
i
++
)
{
fetch_request_t
**
reqp
=
(
fetch_request_t
**
)
utarray_eltptr
(
pending_requests
,
i
);
if
(
reqp
&&
*
reqp
)
{
op_val
(
ctx
,
&
(
*
reqp
)
->
promise
);
op_val
(
ctx
,
&
(
*
reqp
)
->
headers_obj
);
}
}
}
File Metadata
Details
Attached
Mime Type
text/x-c
Expires
Thu, Mar 26, 4:40 PM (1 d, 4 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
511728
Default Alt Text
fetch.c (10 KB)
Attached To
Mode
rANT Ant
Attached
Detach File
Event Timeline
Log In to Comment