} ngx_qjs_fetch_t;
+typedef struct {
+ void *data;
+ JSValue header_value;
+} ngx_qjs_fetch_object_t;
+
+
static ngx_int_t ngx_qjs_method_process(JSContext *cx,
ngx_js_request_t *request);
static ngx_int_t ngx_qjs_headers_inherit(JSContext *cx,
JSValueConst this_val, int magic);
static JSValue ngx_qjs_ext_fetch_request_mode(JSContext *cx,
JSValueConst this_val);
+static void ngx_qjs_fetch_request_mark(JSRuntime *rt, JSValueConst val,
+ JS_MarkFunc *mark_func);
static void ngx_qjs_fetch_request_finalizer(JSRuntime *rt, JSValue val);
static JSValue ngx_qjs_fetch_response_ctor(JSContext *cx,
JSValueConst this_val);
static JSValue ngx_qjs_ext_fetch_response_field(JSContext *cx,
JSValueConst this_val, int magic);
+static void ngx_qjs_fetch_response_mark(JSRuntime *rt, JSValueConst val,
+ JS_MarkFunc *mark_func);
static void ngx_qjs_fetch_response_finalizer(JSRuntime *rt, JSValue val);
static JSValue ngx_qjs_fetch_flag(JSContext *cx, const ngx_qjs_entry_t *entries,
static const JSClassDef ngx_qjs_fetch_request_class = {
"Request",
.finalizer = ngx_qjs_fetch_request_finalizer,
+ .gc_mark = ngx_qjs_fetch_request_mark,
};
static const JSClassDef ngx_qjs_fetch_response_class = {
"Response",
.finalizer = ngx_qjs_fetch_response_finalizer,
+ .gc_mark = ngx_qjs_fetch_response_mark,
};
}
+static ngx_int_t
+ngx_qjs_fetch_object_set(JSContext *cx, JSValueConst value, void *data)
+{
+ ngx_qjs_fetch_object_t *object;
+
+ object = js_malloc(cx, sizeof(ngx_qjs_fetch_object_t));
+ if (object == NULL) {
+ return NGX_ERROR;
+ }
+
+ object->data = data;
+ object->header_value = JS_UNDEFINED;
+
+ JS_SetOpaque(value, object);
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_qjs_fetch_object_data(JSContext *cx, JSValueConst value,
+ JSClassID class_id)
+{
+ ngx_qjs_fetch_object_t *object;
+
+ object = JS_GetOpaque2(cx, value, class_id);
+
+ return (object != NULL) ? object->data : NULL;
+}
+
+
static JSValue
ngx_qjs_fetch_request_ctor(JSContext *cx, JSValueConst new_target, int argc,
JSValueConst *argv)
return JS_EXCEPTION;
}
- JS_SetOpaque(obj, request);
+ if (ngx_qjs_fetch_object_set(cx, obj, request) != NGX_OK) {
+ JS_FreeValue(cx, obj);
+ return JS_EXCEPTION;
+ }
return obj;
}
ngx_qjs_request_ctor(JSContext *cx, ngx_js_request_t *request,
ngx_url_t *u, int argc, JSValueConst *argv)
{
- JSValue input, init, value;
- ngx_int_t rc;
- ngx_pool_t *pool;
- ngx_js_request_t *orig;
+ JSValue input, init, value;
+ ngx_int_t rc;
+ ngx_pool_t *pool;
+ ngx_js_request_t *orig;
+ ngx_qjs_fetch_object_t *object;
input = argv[0];
if (JS_IsUndefined(input)) {
request->body.data = NULL;
request->body.len = 0;
request->headers.guard = GUARD_REQUEST;
- ngx_qjs_arg(request->header_value) = JS_UNDEFINED;
-
pool = ngx_qjs_external_pool(cx, JS_GetContextOpaque(cx));
rc = ngx_list_init(&request->headers.header_list, pool, 4,
}
} else {
- orig = JS_GetOpaque(input, NGX_QJS_CLASS_ID_FETCH_REQUEST);
- if (orig == NULL) {
+ object = JS_GetOpaque(input, NGX_QJS_CLASS_ID_FETCH_REQUEST);
+ if (object == NULL) {
JS_ThrowTypeError(cx,
"input is not string or a Request object");
return NGX_ERROR;
}
+ orig = object->data;
+
request->url = orig->url;
request->method = orig->method;
request->body = orig->body;
response->code = 200;
response->headers.guard = GUARD_RESPONSE;
- ngx_qjs_arg(response->header_value) = JS_UNDEFINED;
-
ret = ngx_list_init(&response->headers.header_list, pool, 4,
sizeof(ngx_js_tb_elt_t));
if (ret != NGX_OK) {
return JS_EXCEPTION;
}
- JS_SetOpaque(obj, response);
+ if (ngx_qjs_fetch_object_set(cx, obj, response) != NGX_OK) {
+ JS_FreeValue(cx, obj);
+ return JS_EXCEPTION;
+ }
return obj;
}
http->keepalive = (conf->fetch_keepalive > 0
&& !ngx_js_conf_dynamic_proxy(conf));
- ngx_qjs_arg(http->response.header_value) = JS_UNDEFINED;
-
http->append_headers = ngx_qjs_fetch_append_headers;
http->ready_handler = ngx_qjs_fetch_process_done;
http->error_handler = ngx_qjs_fetch_error;
return;
}
- JS_SetOpaque(fetch->response_value, &http->response);
+ if (ngx_qjs_fetch_object_set(fetch->cx, fetch->response_value,
+ &http->response)
+ != NGX_OK)
+ {
+ JS_FreeValue(fetch->cx, fetch->response_value);
+ fetch->response_value = JS_UNDEFINED;
+ ngx_qjs_fetch_error(http, "fetch response creation failed");
+ return;
+ }
ngx_qjs_fetch_done(fetch, fetch->response_value, NGX_OK);
}
JSValue result;
ngx_js_request_t *request;
- request = JS_GetOpaque2(cx, this_val, NGX_QJS_CLASS_ID_FETCH_REQUEST);
+ request = ngx_qjs_fetch_object_data(cx, this_val,
+ NGX_QJS_CLASS_ID_FETCH_REQUEST);
if (request == NULL) {
return JS_UNDEFINED;
}
{
ngx_js_request_t *request;
- request = JS_GetOpaque2(cx, this_val, NGX_QJS_CLASS_ID_FETCH_REQUEST);
+ request = ngx_qjs_fetch_object_data(cx, this_val,
+ NGX_QJS_CLASS_ID_FETCH_REQUEST);
if (request == NULL) {
return JS_UNDEFINED;
}
{
ngx_js_request_t *request;
- request = JS_GetOpaque2(cx, this_val, NGX_QJS_CLASS_ID_FETCH_REQUEST);
+ request = ngx_qjs_fetch_object_data(cx, this_val,
+ NGX_QJS_CLASS_ID_FETCH_REQUEST);
if (request == NULL) {
return JS_UNDEFINED;
}
{
ngx_js_request_t *request;
- request = JS_GetOpaque2(cx, this_val, NGX_QJS_CLASS_ID_FETCH_REQUEST);
+ request = ngx_qjs_fetch_object_data(cx, this_val,
+ NGX_QJS_CLASS_ID_FETCH_REQUEST);
if (request == NULL) {
return JS_UNDEFINED;
}
static JSValue
ngx_qjs_ext_fetch_request_headers(JSContext *cx, JSValueConst this_val)
{
- JSValue header;
- ngx_js_request_t *request;
+ JSValue header;
+ ngx_js_request_t *request;
+ ngx_qjs_fetch_object_t *object;
- request = JS_GetOpaque2(cx, this_val, NGX_QJS_CLASS_ID_FETCH_REQUEST);
- if (request == NULL) {
+ object = JS_GetOpaque2(cx, this_val, NGX_QJS_CLASS_ID_FETCH_REQUEST);
+ if (object == NULL) {
return JS_UNDEFINED;
}
- header = ngx_qjs_arg(request->header_value);
+ request = object->data;
+ header = object->header_value;
if (JS_IsUndefined(header)) {
header = JS_NewObjectClass(cx, NGX_QJS_CLASS_ID_FETCH_HEADERS);
JS_SetOpaque(header, &request->headers);
- ngx_qjs_arg(request->header_value) = header;
+ object->header_value = header;
}
return JS_DupValue(cx, header);
ngx_str_t *field;
ngx_js_request_t *request;
- request = JS_GetOpaque2(cx, this_val, NGX_QJS_CLASS_ID_FETCH_REQUEST);
+ request = ngx_qjs_fetch_object_data(cx, this_val,
+ NGX_QJS_CLASS_ID_FETCH_REQUEST);
if (request == NULL) {
return JS_UNDEFINED;
}
{
ngx_js_request_t *request;
- request = JS_GetOpaque2(cx, this_val, NGX_QJS_CLASS_ID_FETCH_REQUEST);
+ request = ngx_qjs_fetch_object_data(cx, this_val,
+ NGX_QJS_CLASS_ID_FETCH_REQUEST);
if (request == NULL) {
return JS_UNDEFINED;
}
}
+static void
+ngx_qjs_fetch_request_mark(JSRuntime *rt, JSValueConst val,
+ JS_MarkFunc *mark_func)
+{
+ ngx_qjs_fetch_object_t *object;
+
+ object = JS_GetOpaque(val, NGX_QJS_CLASS_ID_FETCH_REQUEST);
+ if (object != NULL) {
+ JS_MarkValue(rt, object->header_value, mark_func);
+ }
+}
+
+
static void
ngx_qjs_fetch_request_finalizer(JSRuntime *rt, JSValue val)
{
- ngx_js_request_t *request;
+ ngx_qjs_fetch_object_t *object;
- request = JS_GetOpaque(val, NGX_QJS_CLASS_ID_FETCH_REQUEST);
+ object = JS_GetOpaque(val, NGX_QJS_CLASS_ID_FETCH_REQUEST);
+ if (object == NULL) {
+ return;
+ }
- JS_FreeValueRT(rt, ngx_qjs_arg(request->header_value));
+ JS_FreeValueRT(rt, object->header_value);
+ js_free_rt(rt, object);
}
{
ngx_js_response_t *response;
- response = JS_GetOpaque2(cx, this_val, NGX_QJS_CLASS_ID_FETCH_RESPONSE);
+ response = ngx_qjs_fetch_object_data(cx, this_val,
+ NGX_QJS_CLASS_ID_FETCH_RESPONSE);
if (response == NULL) {
return JS_UNDEFINED;
}
{
ngx_js_response_t *response;
- response = JS_GetOpaque2(cx, this_val, NGX_QJS_CLASS_ID_FETCH_RESPONSE);
+ response = ngx_qjs_fetch_object_data(cx, this_val,
+ NGX_QJS_CLASS_ID_FETCH_RESPONSE);
if (response == NULL) {
return JS_UNDEFINED;
}
{
ngx_js_response_t *response;
- response = JS_GetOpaque2(cx, this_val, NGX_QJS_CLASS_ID_FETCH_RESPONSE);
+ response = ngx_qjs_fetch_object_data(cx, this_val,
+ NGX_QJS_CLASS_ID_FETCH_RESPONSE);
if (response == NULL) {
return JS_UNDEFINED;
}
{
ngx_js_response_t *response;
- response = JS_GetOpaque2(cx, this_val, NGX_QJS_CLASS_ID_FETCH_RESPONSE);
+ response = ngx_qjs_fetch_object_data(cx, this_val,
+ NGX_QJS_CLASS_ID_FETCH_RESPONSE);
if (response == NULL) {
return JS_UNDEFINED;
}
static JSValue
ngx_qjs_ext_fetch_response_headers(JSContext *cx, JSValueConst this_val)
{
- JSValue header;
- ngx_js_response_t *response;
+ JSValue header;
+ ngx_js_response_t *response;
+ ngx_qjs_fetch_object_t *object;
- response = JS_GetOpaque2(cx, this_val, NGX_QJS_CLASS_ID_FETCH_RESPONSE);
- if (response == NULL) {
+ object = JS_GetOpaque2(cx, this_val, NGX_QJS_CLASS_ID_FETCH_RESPONSE);
+ if (object == NULL) {
return JS_UNDEFINED;
}
- header = ngx_qjs_arg(response->header_value);
+ response = object->data;
+ header = object->header_value;
if (JS_IsUndefined(header)) {
header = JS_NewObjectClass(cx, NGX_QJS_CLASS_ID_FETCH_HEADERS);
JS_SetOpaque(header, &response->headers);
- ngx_qjs_arg(response->header_value) = header;
+ object->header_value = header;
}
return JS_DupValue(cx, header);
{
ngx_js_response_t *response;
- response = JS_GetOpaque2(cx, this_val, NGX_QJS_CLASS_ID_FETCH_RESPONSE);
+ response = ngx_qjs_fetch_object_data(cx, this_val,
+ NGX_QJS_CLASS_ID_FETCH_RESPONSE);
if (response == NULL) {
return JS_UNDEFINED;
}
njs_str_t string;
ngx_js_response_t *response;
- response = JS_GetOpaque2(cx, this_val, NGX_QJS_CLASS_ID_FETCH_RESPONSE);
+ response = ngx_qjs_fetch_object_data(cx, this_val,
+ NGX_QJS_CLASS_ID_FETCH_RESPONSE);
if (response == NULL) {
return JS_UNDEFINED;
}
{
ngx_js_response_t *response;
- response = JS_GetOpaque2(cx, this_val, NGX_QJS_CLASS_ID_FETCH_RESPONSE);
+ response = ngx_qjs_fetch_object_data(cx, this_val,
+ NGX_QJS_CLASS_ID_FETCH_RESPONSE);
if (response == NULL) {
return JS_UNDEFINED;
}
ngx_str_t *field;
ngx_js_response_t *response;
- response = JS_GetOpaque2(cx, this_val, NGX_QJS_CLASS_ID_FETCH_RESPONSE);
+ response = ngx_qjs_fetch_object_data(cx, this_val,
+ NGX_QJS_CLASS_ID_FETCH_RESPONSE);
if (response == NULL) {
return JS_UNDEFINED;
}
}
+static void
+ngx_qjs_fetch_response_mark(JSRuntime *rt, JSValueConst val,
+ JS_MarkFunc *mark_func)
+{
+ ngx_qjs_fetch_object_t *object;
+
+ object = JS_GetOpaque(val, NGX_QJS_CLASS_ID_FETCH_RESPONSE);
+ if (object != NULL) {
+ JS_MarkValue(rt, object->header_value, mark_func);
+ }
+}
+
+
static void
ngx_qjs_fetch_response_finalizer(JSRuntime *rt, JSValue val)
{
- ngx_js_response_t *response;
+ ngx_qjs_fetch_object_t *object;
- response = JS_GetOpaque(val, NGX_QJS_CLASS_ID_FETCH_RESPONSE);
+ object = JS_GetOpaque(val, NGX_QJS_CLASS_ID_FETCH_RESPONSE);
+ if (object == NULL) {
+ return;
+ }
- JS_FreeValueRT(rt, ngx_qjs_arg(response->header_value));
- njs_chb_destroy(&response->chain);
+ JS_FreeValueRT(rt, object->header_value);
+ js_free_rt(rt, object);
}
--- /dev/null
+#!/usr/bin/perl
+
+# (C) Dmitry Volyntsev
+# (C) F5, Inc.
+
+# Tests for QuickJS Fetch object finalization after pool destruction.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+
+BEGIN { use FindBin; chdir($FindBin::Bin); }
+
+use lib 'lib';
+use Test::Nginx;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+my $t = Test::Nginx->new()->has(qw/http rewrite/)
+ ->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+worker_processes 1;
+
+events {
+}
+
+http {
+ %%TEST_GLOBALS_HTTP%%
+
+ js_engine qjs;
+ js_import test.js;
+
+ server {
+ listen 127.0.0.1:8080;
+ server_name localhost;
+
+ location /reuse {
+ js_context_reuse 1;
+ js_content test.reuse;
+ }
+
+ location /no_reuse {
+ js_context_reuse 0;
+ js_content test.no_reuse;
+ }
+
+ location /target {
+ add_header X-Test preserved;
+ return 200 body;
+ }
+ }
+}
+
+EOF
+
+my $p0 = port(8080);
+
+$t->write_file('test.js', <<EOF);
+ var retained;
+
+ async function reuse(r) {
+ var result = 'ok';
+
+ switch (r.args.op) {
+ case 'request':
+ retained = new Request('http://127.0.0.1:$p0/', {
+ headers: {x: 'request'}
+ });
+ result = retained.headers.get('x');
+ break;
+
+ case 'response':
+ retained = new Response('response', {
+ headers: {x: 'response'}
+ });
+ result = retained.headers.get('x');
+ break;
+
+ case 'fetch':
+ retained = await ngx.fetch('http://127.0.0.1:$p0/target');
+ result = retained.headers.get('x-test');
+ break;
+
+ case 'release':
+ retained = null;
+ break;
+ }
+
+ r.return(200, result);
+ }
+
+ async function no_reuse(r) {
+ var object;
+
+ switch (r.args.type) {
+ case 'request':
+ object = new Request('http://127.0.0.1:$p0/');
+ break;
+
+ case 'response':
+ object = new Response('response');
+ break;
+
+ case 'fetch':
+ object = await ngx.fetch('http://127.0.0.1:$p0/target');
+ break;
+
+ case 'request_headers':
+ object = new Request('http://127.0.0.1:$p0/');
+ object.headers.owner = object;
+ r.return(200, 'ok');
+ return;
+
+ case 'response_headers':
+ object = new Response('response');
+ object.headers.owner = object;
+ r.return(200, 'ok');
+ return;
+ }
+
+ object.self = object;
+ r.return(200, 'ok');
+ }
+
+ export default {reuse, no_reuse};
+EOF
+
+$t->try_run('no QuickJS support')->plan(11);
+
+###############################################################################
+
+like(http_get('/reuse?op=request'), qr/request$/s, 'retain Request');
+like(http_get('/reuse?op=release'), qr/200 OK/, 'release Request');
+
+like(http_get('/reuse?op=response'), qr/response$/s, 'retain Response');
+like(http_get('/reuse?op=release'), qr/200 OK/, 'release Response');
+
+like(http_get('/reuse?op=fetch'), qr/preserved$/s, 'retain fetch Response');
+like(http_get('/reuse?op=release'), qr/200 OK/, 'release fetch Response');
+
+like(http_get('/no_reuse?type=request'), qr/200 OK/, 'Request cycle');
+like(http_get('/no_reuse?type=response'), qr/200 OK/, 'Response cycle');
+like(http_get('/no_reuse?type=fetch'), qr/200 OK/, 'fetch Response cycle');
+like(http_get('/no_reuse?type=request_headers'), qr/200 OK/,
+ 'Request Headers cycle');
+like(http_get('/no_reuse?type=response_headers'), qr/200 OK/,
+ 'Response Headers cycle');
+
+###############################################################################