Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2959474
fetch.c
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
11 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
"config.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
{
struct
js
*
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
;
}
fetch_request_t
;
static
uv_loop_t
*
fetch_loop
=
NULL
;
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
);
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
response_text
(
struct
js
*
js
,
jsval_t
*
args
,
int
nargs
)
{
(
void
)
args
;
(
void
)
nargs
;
jsval_t
fn
=
js_getcurrentfunc
(
js
);
jsval_t
body
=
js_get_slot
(
js
,
fn
,
SLOT_DATA
);
jsval_t
promise
=
js_mkpromise
(
js
);
js_resolve_promise
(
js
,
promise
,
body
);
return
promise
;
}
static
jsval_t
response_json
(
struct
js
*
js
,
jsval_t
*
args
,
int
nargs
)
{
(
void
)
args
;
(
void
)
nargs
;
jsval_t
fn
=
js_getcurrentfunc
(
js
);
jsval_t
body
=
js_get_slot
(
js
,
fn
,
SLOT_DATA
);
jsval_t
parsed
=
js_json_parse
(
js
,
&
body
,
1
);
jsval_t
promise
=
js_mkpromise
(
js
);
if
(
js_type
(
parsed
)
==
JS_ERR
)
{
js_reject_promise
(
js
,
promise
,
parsed
);
}
else
js_resolve_promise
(
js
,
promise
,
parsed
);
return
promise
;
}
static
jsval_t
create_response
(
struct
js
*
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"
,
status
>=
200
&&
status
<
300
?
js_mktrue
()
:
js_mkfalse
());
js_set
(
js
,
response_obj
,
"status"
,
js_mknum
(
status
));
js_set
(
js
,
response_obj
,
"text"
,
js_heavy_mkfun
(
js
,
response_text
,
body_str
));
js_set
(
js
,
response_obj
,
"json"
,
js_heavy_mkfun
(
js
,
response_json
,
body_str
));
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
(
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
(
struct
js
*
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
(
js
,
current_func
,
"promise"
);
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
();
}
if
(
!
pending_requests
)
utarray_new
(
pending_requests
,
&
ut_ptr_icd
);
utarray_push_back
(
pending_requests
,
&
req
);
const
char
*
scheme_end
=
strstr
(
url_str
,
"://"
);
if
(
!
scheme_end
)
{
jsval_t
err
=
js_mkstr
(
js
,
"Invalid URL: no scheme"
,
22
);
js_reject_promise
(
js
,
promise
,
err
);
remove_pending_request
(
req
);
free_fetch_request
(
req
);
return
js_mkundef
();
}
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
)
{
jsval_t
err
=
js_mkstr
(
js
,
"Invalid URL: no host"
,
20
);
js_reject_promise
(
js
,
promise
,
err
);
remove_pending_request
(
req
);
free_fetch_request
(
req
);
return
js_mkundef
();
}
char
*
host_url
=
calloc
(
1
,
scheme_len
+
3
+
host_len
+
1
);
if
(
!
host_url
)
{
jsval_t
err
=
js_mkstr
(
js
,
"Out of memory"
,
13
);
js_reject_promise
(
js
,
promise
,
err
);
remove_pending_request
(
req
);
free_fetch_request
(
req
);
return
js_mkundef
();
}
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
(
fetch_loop
,
&
req
->
http_client
,
host_url
);
free
(
host_url
);
if
(
rc
!=
0
)
{
jsval_t
err
=
js_mkstr
(
js
,
"Failed to initialize HTTP client"
,
33
);
js_reject_promise
(
js
,
promise
,
err
);
remove_pending_request
(
req
);
free_fetch_request
(
req
);
return
js_mkundef
();
}
req
->
http_client
.
data
=
req
;
char
*
method
=
"GET"
;
char
*
body
=
NULL
;
size_t
body_len
=
0
;
if
(
js_type
(
options_val
)
==
JS_OBJ
)
{
jsval_t
method_val
=
js_get
(
js
,
options_val
,
"method"
);
if
(
js_type
(
method_val
)
==
JS_STR
)
{
char
*
method_str
=
js_getstr
(
js
,
method_val
,
NULL
);
if
(
method_str
)
method
=
method_str
;
}
jsval_t
body_val
=
js_get
(
js
,
options_val
,
"body"
);
if
(
js_type
(
body_val
)
==
JS_STR
)
{
body
=
js_getstr
(
js
,
body_val
,
NULL
);
if
(
body
)
body_len
=
strlen
(
body
);
}
}
req
->
http_req
=
tlsuv_http_req
(
&
req
->
http_client
,
method
,
path
,
resp_cb
,
req
);
if
(
!
req
->
http_req
)
{
jsval_t
err
=
js_mkstr
(
js
,
"Failed to create HTTP request"
,
30
);
js_reject_promise
(
js
,
promise
,
err
);
tlsuv_http_close
(
&
req
->
http_client
,
NULL
);
remove_pending_request
(
req
);
free_fetch_request
(
req
);
return
js_mkundef
();
}
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
(
js_type
(
options_val
)
==
JS_OBJ
)
{
jsval_t
headers_val
=
js_get
(
js
,
options_val
,
"headers"
);
if
(
js_type
(
headers_val
)
==
JS_OBJ
)
{
js_prop_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
(
body
&&
body_len
>
0
)
{
tlsuv_http_req_data
(
req
->
http_req
,
body
,
body_len
,
body_cb
);
}
return
js_mkundef
();
}
static
jsval_t
js_fetch
(
struct
js
*
js
,
jsval_t
*
args
,
int
nargs
)
{
if
(
nargs
<
1
)
return
js_mkerr
(
js
,
"fetch requires at least 1 argument"
);
if
(
js_type
(
args
[
0
])
!=
JS_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_CFUNC
,
js_mkfun
(
do_fetch_microtask
));
js_set
(
js
,
wrapper_obj
,
"url"
,
url_val
);
js_set
(
js
,
wrapper_obj
,
"options"
,
options_val
);
js_set
(
js
,
wrapper_obj
,
"promise"
,
promise
);
queue_microtask
(
js
,
js_obj_to_func
(
wrapper_obj
));
return
promise
;
}
void
init_fetch_module
()
{
if
(
!
fetch_loop
)
fetch_loop
=
uv_default_loop
();
struct
js
*
js
=
rt
->
js
;
js_set
(
js
,
js_glob
(
js
),
"fetch"
,
js_mkfun
(
js_fetch
));
}
int
has_pending_fetches
(
void
)
{
return
(
pending_requests
&&
utarray_len
(
pending_requests
)
>
0
)
||
(
fetch_loop
&&
uv_loop_alive
(
fetch_loop
));
}
void
fetch_poll_events
(
void
)
{
if
(
fetch_loop
&&
fetch_loop
==
uv_default_loop
()
&&
(
rt
->
flags
&
ANT_RUNTIME_EXT_EVENT_LOOP
))
return
;
if
(
fetch_loop
&&
uv_loop_alive
(
fetch_loop
))
{
uv_run
(
fetch_loop
,
UV_RUN_ONCE
);
if
(
pending_requests
&&
utarray_len
(
pending_requests
)
>
0
)
usleep
(
1000
);
}
}
void
fetch_gc_update_roots
(
GC_FWD_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
)
{
(
*
reqp
)
->
promise
=
fwd_val
(
ctx
,
(
*
reqp
)
->
promise
);
(
*
reqp
)
->
headers_obj
=
fwd_val
(
ctx
,
(
*
reqp
)
->
headers_obj
);
}
}
}
File Metadata
Details
Attached
Mime Type
text/x-c
Expires
Sat, Mar 28, 4:08 PM (2 d)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
514155
Default Alt Text
fetch.c (11 KB)
Attached To
Mode
rANT Ant
Attached
Detach File
Event Timeline
Log In to Comment