JSValue response_body;
} ngx_http_qjs_request_t;
+
+typedef struct {
+ ngx_js_opaque_t opaque;
+ ngx_uint_t buffer_type;
+} ngx_http_qjs_variables_t;
+
+
+typedef struct {
+ ngx_qjs_owner_t *owner;
+ ngx_http_js_ctx_t *ctx;
+} ngx_http_qjs_subrequest_t;
+
#endif
static ngx_int_t ngx_http_qjs_body_resolve(ngx_http_request_t *r,
ngx_http_js_ctx_t *ctx, void *event);
static JSValue ngx_http_qjs_form_to_value(JSContext *cx, ngx_http_request_t *r,
- ngx_http_js_ctx_t *ctx, ngx_uint_t max_keys);
+ ngx_http_js_ctx_t *ctx, ngx_uint_t max_keys, JSValueConst request);
static JSValue ngx_http_qjs_request_form_entry_value(JSContext *cx,
ngx_js_form_entry_t *entry);
static ngx_int_t ngx_http_qjs_request_form_max_keys(JSContext *cx,
JSValueConst options, ngx_uint_t *max_keys);
static JSValue ngx_http_qjs_request_form_make(JSContext *cx,
- ngx_js_form_t *form);
+ JSValueConst request, ngx_js_form_t *form);
static JSValue ngx_http_qjs_ext_read_request_form(JSContext *cx,
JSValueConst this_val, int argc, JSValueConst *argv);
static JSValue ngx_http_qjs_ext_request_form_get(JSContext *cx,
static int ngx_http_qjs_headers_out_delete_property(JSContext *cx,
JSValueConst obj, JSAtom prop);
-static ngx_http_request_t *ngx_http_qjs_request(JSValueConst val);
+static ngx_http_request_t *ngx_http_qjs_request(JSContext *cx,
+ JSValueConst val);
static JSValue ngx_http_qjs_request_make(JSContext *cx, ngx_int_t proto_id,
- ngx_http_request_t *r);
+ ngx_http_request_t *r, ngx_qjs_owner_t *owner);
+static JSValue ngx_http_qjs_view_make(JSContext *cx, JSValueConst request,
+ JSClassID request_class, JSClassID view_class, void *external);
+static JSValue ngx_http_qjs_variables_make(JSContext *cx, JSValueConst request,
+ JSClassID request_class, int type);
static void ngx_http_qjs_request_mark(JSRuntime *rt, JSValueConst val,
JS_MarkFunc *mark_func);
static void ngx_http_qjs_request_finalizer(JSRuntime *rt, JSValue val);
static JSClassDef ngx_http_qjs_request_form_class = {
"RequestForm",
- .finalizer = NULL,
+ .finalizer = ngx_qjs_opaque_finalizer,
};
static JSClassDef ngx_http_qjs_variables_class = {
"Variables",
- .finalizer = NULL,
+ .finalizer = ngx_qjs_opaque_finalizer,
.exotic = & (JSClassExoticMethods) {
.get_own_property = ngx_http_qjs_variables_own_property,
.set_property = ngx_http_qjs_variables_set_property,
static JSClassDef ngx_http_qjs_headers_in_class = {
"headersIn",
- .finalizer = NULL,
+ .finalizer = ngx_qjs_opaque_finalizer,
.exotic = & (JSClassExoticMethods) {
.get_own_property = ngx_http_qjs_headers_in_own_property,
.get_own_property_names = ngx_http_qjs_headers_in_own_property_names,
static JSClassDef ngx_http_qjs_headers_out_class = {
"headersOut",
- .finalizer = NULL,
+ .finalizer = ngx_qjs_opaque_finalizer,
.exotic = & (JSClassExoticMethods) {
.get_own_property = ngx_http_qjs_headers_out_own_property,
.get_own_property_names = ngx_http_qjs_headers_out_own_property_names,
ngx_http_request_t *r;
ngx_http_qjs_request_t *req;
- req = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_HTTP_REQUEST);
+ req = (ngx_http_qjs_request_t *) ngx_qjs_opaque_get(cx, this_val,
+ NGX_QJS_CLASS_ID_HTTP_REQUEST);
if (req == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a request object");
+ return JS_EXCEPTION;
}
if (!JS_IsUndefined(req->args)) {
ngx_http_js_ctx_t *ctx;
ngx_http_request_t *r;
- r = ngx_http_qjs_request(this_val);
+ r = ngx_http_qjs_request(cx, this_val);
if (r == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a request object");
+ return JS_EXCEPTION;
}
ctx = ngx_http_get_module_ctx(r, ngx_http_js_module);
ngx_http_js_ctx_t *ctx;
ngx_http_request_t *r;
- r = ngx_http_qjs_request(this_val);
+ r = ngx_http_qjs_request(cx, this_val);
if (r == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a request object");
+ return JS_EXCEPTION;
}
if (ngx_http_send_special(r, NGX_HTTP_LAST) == NGX_ERROR) {
static JSValue
ngx_http_qjs_ext_headers_in(JSContext *cx, JSValueConst this_val)
{
- JSValue obj;
- ngx_http_request_t *r;
-
- r = ngx_http_qjs_request(this_val);
- if (r == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a request object");
- }
-
- obj = JS_NewObjectProtoClass(cx, JS_NULL, NGX_QJS_CLASS_ID_HTTP_HEADERS_IN);
-
- JS_SetOpaque(obj, r);
-
- return obj;
+ return ngx_http_qjs_view_make(cx, this_val,
+ NGX_QJS_CLASS_ID_HTTP_REQUEST,
+ NGX_QJS_CLASS_ID_HTTP_HEADERS_IN, NULL);
}
static JSValue
ngx_http_qjs_ext_headers_out(JSContext *cx, JSValueConst this_val)
{
- JSValue obj;
- ngx_http_request_t *r;
-
- r = ngx_http_qjs_request(this_val);
- if (r == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a request object");
- }
-
- obj = JS_NewObjectProtoClass(cx, JS_NULL,
- NGX_QJS_CLASS_ID_HTTP_HEADERS_OUT);
-
- JS_SetOpaque(obj, r);
-
- return obj;
+ return ngx_http_qjs_view_make(cx, this_val,
+ NGX_QJS_CLASS_ID_HTTP_REQUEST,
+ NGX_QJS_CLASS_ID_HTTP_HEADERS_OUT, NULL);
}
ngx_str_t v;
ngx_http_request_t *r;
- r = ngx_http_qjs_request(this_val);
+ r = ngx_http_qjs_request(cx, this_val);
if (r == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a request object");
+ return JS_EXCEPTION;
}
switch (r->http_version) {
{
ngx_http_request_t *r;
- r = ngx_http_qjs_request(this_val);
+ r = ngx_http_qjs_request(cx, this_val);
if (r == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a request object");
+ return JS_EXCEPTION;
}
return JS_NewBool(cx, r->internal);
ngx_http_js_ctx_t *ctx;
ngx_http_request_t *r;
- r = ngx_http_qjs_request(this_val);
+ r = ngx_http_qjs_request(cx, this_val);
if (r == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a request object");
+ return JS_EXCEPTION;
}
if (r->parent != NULL) {
const char *msg;
ngx_http_request_t *r;
- r = ngx_http_qjs_request(this_val);
+ r = ngx_http_qjs_request(cx, this_val);
if (r == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a request object");
+ return JS_EXCEPTION;
}
for (n = 0; n < argc; n++) {
ngx_http_qjs_ext_periodic_variables(JSContext *cx,
JSValueConst this_val, int type)
{
- JSValue obj;
- ngx_http_qjs_request_t *req;
-
- req = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_HTTP_PERIODIC);
- if (req == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a periodic object");
- }
-
- obj = JS_NewObjectProtoClass(cx, JS_NULL, NGX_QJS_CLASS_ID_HTTP_VARS);
-
- /*
- * Using lowest bit of the pointer to store the buffer type.
- */
- type = (type == NGX_JS_BUFFER) ? 1 : 0;
- JS_SetOpaque(obj, (void *) ((uintptr_t) req->opaque.external
- | (uintptr_t) type));
-
- return obj;
+ return ngx_http_qjs_variables_make(cx, this_val,
+ NGX_QJS_CLASS_ID_HTTP_PERIODIC, type);
}
ngx_http_js_ctx_t *ctx;
ngx_http_request_t *r;
- r = ngx_http_qjs_request(this_val);
+ r = ngx_http_qjs_request(cx, this_val);
if (r == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a request object");
+ return JS_EXCEPTION;
}
ctx = r->parent ? ngx_http_get_module_ctx(r->parent, ngx_http_js_module)
ngx_connection_t *c;
ngx_http_request_t *r;
- r = ngx_http_qjs_request(this_val);
+ r = ngx_http_qjs_request(cx, this_val);
if (r == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a request object");
+ return JS_EXCEPTION;
}
c = r->connection;
ngx_http_request_t *r;
ngx_http_qjs_request_t *req;
- req = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_HTTP_REQUEST);
+ req = (ngx_http_qjs_request_t *) ngx_qjs_opaque_get(cx, this_val,
+ NGX_QJS_CLASS_ID_HTTP_REQUEST);
if (req == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a request object");
+ return JS_EXCEPTION;
}
buffer_type = ngx_js_buffer_type(type);
ngx_http_js_ctx_t *ctx;
ngx_http_qjs_request_t *req;
- req = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_HTTP_REQUEST);
+ req = (ngx_http_qjs_request_t *) ngx_qjs_opaque_get(cx, this_val,
+ NGX_QJS_CLASS_ID_HTTP_REQUEST);
if (req == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a request object");
+ return JS_EXCEPTION;
}
buffer_type = ngx_js_buffer_type(type);
cx = ctx->engine->u.qjs.ctx;
if (ngx_http_js_body_read_is_form(ctx->body_read_state)) {
- result = ngx_http_qjs_form_to_value(cx, r, ctx, (uintptr_t) ev->data);
+ result = ngx_http_qjs_form_to_value(cx, r, ctx, (uintptr_t) ev->data,
+ ev->args[2]);
} else {
result = ngx_http_qjs_body_to_value(cx, ctx, (uintptr_t) ev->data);
ngx_http_request_t *r;
ngx_http_qjs_request_t *req;
- req = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_HTTP_REQUEST);
+ req = (ngx_http_qjs_request_t *) ngx_qjs_opaque_get(cx, this_val,
+ NGX_QJS_CLASS_ID_HTTP_REQUEST);
if (req == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a request object");
+ return JS_EXCEPTION;
}
r = req->opaque.external;
return JS_ThrowTypeError(cx, "request body is already being read");
}
- event = ngx_pcalloc(r->pool, sizeof(ngx_qjs_event_t) + sizeof(JSValue) * 2);
+ event = ngx_pcalloc(r->pool, sizeof(ngx_qjs_event_t) + sizeof(JSValue) * 3);
if (event == NULL) {
return JS_ThrowOutOfMemory(cx);
}
}
event->function = JS_DupValue(cx, event->args[0]);
+ event->args[2] = JS_DupValue(cx, this_val);
event->destructor = ngx_http_js_read_body_event_destructor;
ngx_js_add_event(ctx, event);
static JSValue
-ngx_http_qjs_request_form_make(JSContext *cx, ngx_js_form_t *form)
+ngx_http_qjs_request_form_make(JSContext *cx, JSValueConst request,
+ ngx_js_form_t *form)
{
- JSValue obj;
-
- obj = JS_NewObjectClass(cx, NGX_QJS_CLASS_ID_HTTP_FORM);
- if (JS_IsException(obj)) {
- return JS_EXCEPTION;
- }
-
- JS_SetOpaque(obj, form);
-
- return obj;
+ return ngx_http_qjs_view_make(cx, request,
+ NGX_QJS_CLASS_ID_HTTP_REQUEST,
+ NGX_QJS_CLASS_ID_HTTP_FORM, form);
}
static JSValue
ngx_http_qjs_form_to_value(JSContext *cx, ngx_http_request_t *r,
- ngx_http_js_ctx_t *ctx, ngx_uint_t max_keys)
+ ngx_http_js_ctx_t *ctx, ngx_uint_t max_keys, JSValueConst request)
{
ngx_int_t rc;
ngx_str_t error;
rc = ngx_http_js_request_form(r, ctx, max_keys, &form, &error);
if (rc == NGX_OK) {
- return ngx_http_qjs_request_form_make(cx, form);
+ return ngx_http_qjs_request_form_make(cx, request, form);
}
if (rc == NGX_JS_FORM_TYPE_ERROR) {
ngx_http_request_t *r;
ngx_http_qjs_request_t *req;
- req = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_HTTP_REQUEST);
+ req = (ngx_http_qjs_request_t *) ngx_qjs_opaque_get(cx, this_val,
+ NGX_QJS_CLASS_ID_HTTP_REQUEST);
if (req == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a request object");
+ return JS_EXCEPTION;
}
if (ngx_http_qjs_request_form_max_keys(cx, argv[0], &max_keys) != NGX_OK) {
return JS_EXCEPTION;
}
+ if (ngx_qjs_opaque_get(cx, this_val, NGX_QJS_CLASS_ID_HTTP_REQUEST)
+ == NULL)
+ {
+ return JS_EXCEPTION;
+ }
+
r = req->opaque.external;
ctx = ngx_http_get_module_ctx(r, ngx_http_js_module);
return JS_ThrowTypeError(cx, "request body is already being read");
}
- event = ngx_pcalloc(r->pool, sizeof(ngx_qjs_event_t) + sizeof(JSValue) * 2);
+ event = ngx_pcalloc(r->pool, sizeof(ngx_qjs_event_t) + sizeof(JSValue) * 3);
if (event == NULL) {
return JS_ThrowOutOfMemory(cx);
}
}
event->function = JS_DupValue(cx, event->args[0]);
+ event->args[2] = JS_DupValue(cx, this_val);
event->destructor = ngx_http_js_read_body_event_destructor;
ngx_js_add_event(ctx, event);
return JS_ThrowInternalError(cx, "failed to read request body");
}
- return ngx_http_qjs_form_to_value(cx, r, ctx, max_keys);
+ return ngx_http_qjs_form_to_value(cx, r, ctx, max_keys, this_val);
}
const char *name;
ngx_uint_t i, n;
ngx_js_form_t *form;
+ ngx_js_opaque_t *opaque;
ngx_js_form_entry_t *entry;
- form = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_HTTP_FORM);
- if (form == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a RequestForm");
+ opaque = ngx_qjs_opaque_get(cx, this_val, NGX_QJS_CLASS_ID_HTTP_FORM);
+ if (opaque == NULL) {
+ return JS_EXCEPTION;
}
+ form = opaque->external;
name = JS_ToCStringLen(cx, &name_len, argv[0]);
if (name == NULL) {
return JS_ThrowTypeError(cx, "\"name\" must be a string");
}
+ if (ngx_qjs_opaque_get(cx, this_val, NGX_QJS_CLASS_ID_HTTP_FORM) == NULL) {
+ JS_FreeCString(cx, name);
+ return JS_EXCEPTION;
+ }
+
if (as_array) {
array = JS_NewArray(cx);
if (JS_IsException(array)) {
const char *name;
ngx_uint_t i;
ngx_js_form_t *form;
+ ngx_js_opaque_t *opaque;
ngx_js_form_entry_t *entry;
- form = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_HTTP_FORM);
- if (form == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a RequestForm");
+ opaque = ngx_qjs_opaque_get(cx, this_val, NGX_QJS_CLASS_ID_HTTP_FORM);
+ if (opaque == NULL) {
+ return JS_EXCEPTION;
}
+ form = opaque->external;
name = JS_ToCStringLen(cx, &name_len, argv[0]);
if (name == NULL) {
return JS_ThrowTypeError(cx, "\"name\" must be a string");
}
+ if (ngx_qjs_opaque_get(cx, this_val, NGX_QJS_CLASS_ID_HTTP_FORM) == NULL) {
+ JS_FreeCString(cx, name);
+ return JS_EXCEPTION;
+ }
entry = form->entries.elts;
for (i = 0; i < form->entries.nelts; i++) {
JSValue args[3], ret;
ngx_uint_t i;
ngx_js_form_t *form;
+ ngx_js_opaque_t *opaque;
ngx_js_form_entry_t *entry;
- form = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_HTTP_FORM);
- if (form == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a RequestForm");
+ opaque = ngx_qjs_opaque_get(cx, this_val, NGX_QJS_CLASS_ID_HTTP_FORM);
+ if (opaque == NULL) {
+ return JS_EXCEPTION;
}
+ form = opaque->external;
if (!JS_IsFunction(cx, argv[0])) {
return JS_ThrowTypeError(cx, "\"callback\" is not a function");
}
}
JS_FreeValue(cx, ret);
+
+ if (ngx_qjs_opaque_get(cx, this_val, NGX_QJS_CLASS_ID_HTTP_FORM)
+ == NULL)
+ {
+ return JS_EXCEPTION;
+ }
}
return JS_UNDEFINED;
ngx_http_qjs_ext_request_form_has_files(JSContext *cx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
- ngx_js_form_t *form;
+ ngx_js_form_t *form;
+ ngx_js_opaque_t *opaque;
- form = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_HTTP_FORM);
- if (form == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a RequestForm");
+ opaque = ngx_qjs_opaque_get(cx, this_val, NGX_QJS_CLASS_ID_HTTP_FORM);
+ if (opaque == NULL) {
+ return JS_EXCEPTION;
}
+ form = opaque->external;
return JS_NewBool(cx, form->has_files);
}
JS_FreeValue(cx, event->function);
JS_FreeValue(cx, event->args[0]);
JS_FreeValue(cx, event->args[1]);
+ JS_FreeValue(cx, event->args[2]);
}
ngx_http_request_t *r;
ngx_http_complex_value_t cv;
- r = ngx_http_qjs_request(this_val);
+ r = ngx_http_qjs_request(cx, this_val);
if (r == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a request object");
+ return JS_EXCEPTION;
}
if (ngx_qjs_integer(cx, argv[0], &status) != NGX_OK) {
ngx_http_js_ctx_t *ctx;
ngx_http_request_t *r;
- r = ngx_http_qjs_request(this_val);
+ r = ngx_http_qjs_request(cx, this_val);
if (r == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a request object");
+ return JS_EXCEPTION;
}
ctx = ngx_http_get_module_ctx(r, ngx_http_js_module);
{
ngx_http_request_t *r;
- r = ngx_http_qjs_request(this_val);
+ r = ngx_http_qjs_request(cx, this_val);
if (r == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a request object");
+ return JS_EXCEPTION;
}
return JS_NewInt32(cx, r->headers_out.status);
ngx_int_t n;
ngx_http_request_t *r;
- r = ngx_http_qjs_request(this_val);
+ r = ngx_http_qjs_request(cx, this_val);
if (r == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a request object");
+ return JS_EXCEPTION;
}
if (ngx_qjs_integer(cx, value, &n) != NGX_OK) {
ngx_str_t *field;
ngx_http_request_t *r;
- r = ngx_http_qjs_request(this_val);
+ r = ngx_http_qjs_request(cx, this_val);
if (r == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a request object");
+ return JS_EXCEPTION;
}
field = (ngx_str_t *) ((u_char *) r + offset);
ngx_http_js_ctx_t *ctx;
ngx_http_request_t *r;
- r = ngx_http_qjs_request(this_val);
+ r = ngx_http_qjs_request(cx, this_val);
if (r == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a request object");
+ return JS_EXCEPTION;
}
ctx = ngx_http_get_module_ctx(r, ngx_http_js_module);
ngx_http_js_ctx_t *ctx;
ngx_http_request_t *r;
- r = ngx_http_qjs_request(this_val);
+ r = ngx_http_qjs_request(cx, this_val);
if (r == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a request object");
+ return JS_EXCEPTION;
}
ctx = ngx_http_get_module_ctx(r, ngx_http_js_module);
{
ngx_http_request_t *r;
- r = ngx_http_qjs_request(this_val);
+ r = ngx_http_qjs_request(cx, this_val);
if (r == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a request object");
+ return JS_EXCEPTION;
}
if (ngx_http_set_content_type(r) != NGX_OK) {
ngx_js_ctx_t *ctx;
ngx_http_request_t *r;
- r = ngx_http_qjs_request(this_val);
+ r = ngx_http_qjs_request(cx, this_val);
if (r == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a request object");
+ return JS_EXCEPTION;
}
ctx = ngx_http_get_module_ctx(r, ngx_http_js_module);
{
ngx_qjs_event_t *event = data;
- JSValue reply;
- JSContext *cx;
- ngx_http_js_ctx_t *ctx, *sctx;
+ JSValue reply;
+ JSContext *cx;
+ ngx_http_js_ctx_t *ctx, *sctx;
+ ngx_http_qjs_subrequest_t *subrequest;
+
+ subrequest = event->data;
+ if (subrequest->owner == NULL || !subrequest->owner->alive) {
+ return NGX_OK;
+ }
if (rc != NGX_OK || r->connection->error || r->buffered) {
return rc;
sctx->done = 1;
- ctx = ngx_http_get_module_ctx(r->parent, ngx_http_js_module);
+ ctx = subrequest->ctx;
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"js subrequest done s: %ui parent ctx: %p",
r->headers_out.status, ctx);
- if (ctx == NULL) {
+ if (ctx == NULL
+ || ngx_http_get_module_ctx(r->parent, ngx_http_js_module) != ctx)
+ {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"js subrequest: failed to get the parent context");
return NGX_ERROR;
}
- cx = ctx->engine->u.qjs.ctx;
+ cx = event->ctx;
- reply = ngx_http_qjs_request_make(cx, NGX_QJS_CLASS_ID_HTTP_REQUEST, r);
+ reply = ngx_http_qjs_request_make(cx, NGX_QJS_CLASS_ID_HTTP_REQUEST, r,
+ subrequest->owner);
if (JS_IsException(reply)) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"js subrequest reply creation failed");
static void
ngx_http_js_subrequest_event_destructor(ngx_qjs_event_t *event)
{
- JSContext *cx;
+ JSContext *cx;
+ ngx_http_qjs_subrequest_t *subrequest;
cx = event->ctx;
+ subrequest = event->data;
JS_FreeValue(cx, event->function);
JS_FreeValue(cx, event->args[0]);
JS_FreeValue(cx, event->args[1]);
+ ngx_qjs_owner_release(JS_GetRuntime(cx), subrequest->owner);
+ subrequest->owner = NULL;
}
ngx_qjs_event_t *event;
ngx_http_js_ctx_t *ctx;
ngx_http_request_t *r, *sr;
+ ngx_http_qjs_request_t *req;
ngx_http_request_body_t *rb;
+ ngx_http_qjs_subrequest_t *subrequest;
ngx_http_post_subrequest_t *ps;
- r = ngx_http_qjs_request(this_val);
+ r = ngx_http_qjs_request(cx, this_val);
if (r == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a request object");
+ return JS_EXCEPTION;
}
- ctx = ngx_http_get_module_ctx(r, ngx_http_js_module);
+ ctx = (ngx_http_js_ctx_t *) ngx_qjs_external_ctx(cx,
+ JS_GetContextOpaque(cx));
if (r->subrequest_in_memory) {
return JS_ThrowTypeError(cx, "subrequest can only be created for "
promise = !!JS_IsUndefined(callback);
event = ngx_pcalloc(r->pool, sizeof(ngx_qjs_event_t)
- + sizeof(JSValue) * 2);
+ + sizeof(JSValue) * 2
+ + sizeof(ngx_http_qjs_subrequest_t));
if (event == NULL) {
return JS_ThrowOutOfMemory(cx);
}
event->fd = ctx->event_id++;
event->args = (JSValue *) &event[1];
event->destructor = ngx_http_js_subrequest_event_destructor;
+ subrequest = (ngx_http_qjs_subrequest_t *) &event->args[2];
+ event->data = subrequest;
if (promise) {
retval = JS_NewPromiseCapability(cx, &event->args[0]);
}
event->function = JS_DupValue(cx, callback);
+ req = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_HTTP_REQUEST);
+ subrequest->owner = req->opaque.owner;
+ subrequest->owner->references++;
+ subrequest->ctx = ctx;
ps->handler = ngx_http_qjs_subrequest_done;
ps->data = event;
if (ngx_http_subrequest(r, &uri, args.len ? &args : NULL, &sr, ps, flags)
!= NGX_OK)
{
+ if (event != NULL) {
+ ngx_http_js_subrequest_event_destructor(event);
+ }
+
+ JS_FreeValue(cx, retval);
return JS_ThrowInternalError(cx, "subrequest creation failed");
}
ngx_table_elt_t *header, *h;
ngx_http_request_t *r;
- r = ngx_http_qjs_request(this_val);
+ r = ngx_http_qjs_request(cx, this_val);
if (r == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a request object");
+ return JS_EXCEPTION;
}
headers = (out) ? &r->headers_out.headers : &r->headers_in.headers;
static JSValue
ngx_http_qjs_ext_variables(JSContext *cx, JSValueConst this_val, int type)
{
- JSValue obj;
- ngx_http_request_t *r;
-
- r = ngx_http_qjs_request(this_val);
- if (r == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a request object");
- }
-
- obj = JS_NewObjectProtoClass(cx, JS_NULL, NGX_QJS_CLASS_ID_HTTP_VARS);
-
- /*
- * Using lowest bit of the pointer to store the buffer type.
- */
- type = (type == NGX_JS_BUFFER) ? 1 : 0;
- JS_SetOpaque(obj, (void *) ((uintptr_t) r | (uintptr_t) type));
-
- return obj;
+ return ngx_http_qjs_variables_make(cx, this_val,
+ NGX_QJS_CLASS_ID_HTTP_REQUEST, type);
}
ngx_http_variable_t *v;
ngx_http_core_main_conf_t *cmcf;
- r = ngx_http_qjs_request(this_val);
+ r = ngx_http_qjs_request(cx, this_val);
if (r == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a request object");
+ return JS_EXCEPTION;
}
prefix = NULL;
ngx_str_t name, name_lc;
ngx_uint_t i, key, start, length, is_capture;
ngx_http_request_t *r;
+ ngx_http_qjs_variables_t *variables;
ngx_http_variable_value_t *vv;
u_char storage[64];
- r = JS_GetOpaque(obj, NGX_QJS_CLASS_ID_HTTP_VARS);
-
- buffer_type = ((uintptr_t) r & 1) ? NGX_JS_BUFFER : NGX_JS_STRING;
- r = (ngx_http_request_t *) ((uintptr_t) r & ~(uintptr_t) 1);
-
- if (r == NULL) {
- (void) JS_ThrowTypeError(cx, "\"this\" is not a request object");
+ variables = (ngx_http_qjs_variables_t *) ngx_qjs_opaque_get(cx, obj,
+ NGX_QJS_CLASS_ID_HTTP_VARS);
+ if (variables == NULL) {
return -1;
}
+ buffer_type = variables->buffer_type;
+ r = variables->opaque.external;
+
name.data = (u_char *) JS_AtomToCString(cx, prop);
if (name.data == NULL) {
return -1;
vv = ngx_http_get_variable(r, &name_lc, key);
JS_FreeCString(cx, (char *) name.data);
+ if (ngx_qjs_opaque_get(cx, obj, NGX_QJS_CLASS_ID_HTTP_VARS) == NULL) {
+ return -1;
+ }
+
if (vv == NULL || vv->not_found) {
return 0;
}
ngx_uint_t key;
ngx_http_request_t *r;
ngx_http_variable_t *v;
+ ngx_http_qjs_variables_t *variables;
ngx_http_variable_value_t *vv;
ngx_http_core_main_conf_t *cmcf;
u_char storage[64];
- r = JS_GetOpaque(obj, NGX_QJS_CLASS_ID_HTTP_VARS);
-
- r = (ngx_http_request_t *) ((uintptr_t) r & ~(uintptr_t) 1);
-
- if (r == NULL) {
- (void) JS_ThrowTypeError(cx, "\"this\" is not a request object");
+ variables = (ngx_http_qjs_variables_t *) ngx_qjs_opaque_get(cx, obj,
+ NGX_QJS_CLASS_ID_HTTP_VARS);
+ if (variables == NULL) {
return -1;
}
+ r = variables->opaque.external;
+
name.data = (u_char *) JS_AtomToCString(cx, prop);
if (name.data == NULL) {
return -1;
return -1;
}
+ if (ngx_qjs_opaque_get(cx, obj, NGX_QJS_CLASS_ID_HTTP_VARS) == NULL) {
+ return -1;
+ }
+
if (v->set_handler != NULL) {
vv = ngx_pcalloc(r->pool, sizeof(ngx_http_variable_value_t));
if (vv == NULL) {
{
int ret;
JSValue keys;
+ ngx_js_opaque_t *opaque;
ngx_http_request_t *r;
- r = JS_GetOpaque(obj, NGX_QJS_CLASS_ID_HTTP_HEADERS_IN);
- if (r == NULL) {
- (void) JS_ThrowTypeError(cx, "\"this\" is not a headers_in object");
+ opaque = ngx_qjs_opaque_get(cx, obj, NGX_QJS_CLASS_ID_HTTP_HEADERS_IN);
+ if (opaque == NULL) {
return -1;
}
+ r = opaque->external;
+
keys = JS_NewObject(cx);
if (JS_IsException(keys)) {
return -1;
int ret;
unsigned flags;
ngx_str_t name;
+ ngx_js_opaque_t *opaque;
ngx_http_request_t *r;
ngx_http_js_header_t *h;
#undef header
};
- r = JS_GetOpaque(obj, NGX_QJS_CLASS_ID_HTTP_HEADERS_IN);
- if (r == NULL) {
- (void) JS_ThrowTypeError(cx, "\"this\" is not a headers_in object");
+ opaque = ngx_qjs_opaque_get(cx, obj, NGX_QJS_CLASS_ID_HTTP_HEADERS_IN);
+ if (opaque == NULL) {
return -1;
}
+ r = opaque->external;
+
name.data = (u_char *) JS_AtomToCString(cx, prop);
if (name.data == NULL) {
return -1;
int ret;
JSAtom key;
JSValue keys;
+ ngx_js_opaque_t *opaque;
ngx_http_request_t *r;
- r = JS_GetOpaque(obj, NGX_QJS_CLASS_ID_HTTP_HEADERS_OUT);
- if (r == NULL) {
- (void) JS_ThrowTypeError(cx, "\"this\" is not a headers_out object");
+ opaque = ngx_qjs_opaque_get(cx, obj, NGX_QJS_CLASS_ID_HTTP_HEADERS_OUT);
+ if (opaque == NULL) {
return -1;
}
+ r = opaque->external;
+
keys = JS_NewObject(cx);
if (JS_IsException(keys)) {
return -1;
{
int ret;
ngx_str_t name;
+ ngx_js_opaque_t *opaque;
ngx_http_request_t *r;
- r = JS_GetOpaque(obj, NGX_QJS_CLASS_ID_HTTP_HEADERS_OUT);
- if (r == NULL) {
- (void) JS_ThrowTypeError(cx, "\"this\" is not a headers_out object");
+ opaque = ngx_qjs_opaque_get(cx, obj, NGX_QJS_CLASS_ID_HTTP_HEADERS_OUT);
+ if (opaque == NULL) {
return -1;
}
+ r = opaque->external;
+
name.data = (u_char *) JS_AtomToCString(cx, prop);
if (name.data == NULL) {
return -1;
{
int ret;
ngx_str_t name;
+ ngx_js_opaque_t *opaque;
ngx_http_request_t *r;
- r = JS_GetOpaque(obj, NGX_QJS_CLASS_ID_HTTP_HEADERS_OUT);
- if (r == NULL) {
- (void) JS_ThrowTypeError(cx, "\"this\" is not a headers_out object");
+ opaque = ngx_qjs_opaque_get(cx, obj, NGX_QJS_CLASS_ID_HTTP_HEADERS_OUT);
+ if (opaque == NULL) {
return -1;
}
+ r = opaque->external;
+
if (!JS_IsUndefined(setter) || !JS_IsUndefined(getter)) {
(void) JS_ThrowTypeError(cx, "cannot define getter or setter");
return -1;
{
int ret;
ngx_str_t name;
+ ngx_js_opaque_t *opaque;
ngx_http_request_t *r;
- r = JS_GetOpaque(obj, NGX_QJS_CLASS_ID_HTTP_HEADERS_OUT);
- if (r == NULL) {
- (void) JS_ThrowTypeError(cx, "\"this\" is not a headers_out object");
+ opaque = ngx_qjs_opaque_get(cx, obj, NGX_QJS_CLASS_ID_HTTP_HEADERS_OUT);
+ if (opaque == NULL) {
return -1;
}
+ r = opaque->external;
+
name.data = (u_char *) JS_AtomToCString(cx, prop);
if (name.data == NULL) {
return -1;
}
+static JSValue
+ngx_http_qjs_view_make(JSContext *cx, JSValueConst request,
+ JSClassID request_class, JSClassID view_class, void *external)
+{
+ JSValue obj;
+ ngx_js_opaque_t *parent, *opaque;
+
+ parent = ngx_qjs_opaque_get(cx, request, request_class);
+ if (parent == NULL) {
+ return JS_EXCEPTION;
+ }
+
+ if (view_class == NGX_QJS_CLASS_ID_HTTP_FORM) {
+ obj = JS_NewObjectClass(cx, view_class);
+
+ } else {
+ obj = JS_NewObjectProtoClass(cx, JS_NULL, view_class);
+ }
+
+ if (JS_IsException(obj)) {
+ return obj;
+ }
+
+ opaque = ngx_qjs_opaque_alloc(cx, sizeof(ngx_js_opaque_t),
+ external != NULL ? external : parent->external,
+ parent->owner);
+ if (opaque == NULL) {
+ JS_FreeValue(cx, obj);
+ return JS_EXCEPTION;
+ }
+
+ JS_SetOpaque(obj, opaque);
+
+ return obj;
+}
+
+
+static JSValue
+ngx_http_qjs_variables_make(JSContext *cx, JSValueConst request,
+ JSClassID request_class, int type)
+{
+ JSValue obj;
+ ngx_js_opaque_t *parent;
+ ngx_http_qjs_variables_t *variables;
+
+ parent = ngx_qjs_opaque_get(cx, request, request_class);
+ if (parent == NULL) {
+ return JS_EXCEPTION;
+ }
+
+ obj = JS_NewObjectProtoClass(cx, JS_NULL, NGX_QJS_CLASS_ID_HTTP_VARS);
+ if (JS_IsException(obj)) {
+ return obj;
+ }
+
+ variables = (ngx_http_qjs_variables_t *) ngx_qjs_opaque_alloc(cx,
+ sizeof(ngx_http_qjs_variables_t),
+ parent->external, parent->owner);
+ if (variables == NULL) {
+ JS_FreeValue(cx, obj);
+ return JS_EXCEPTION;
+ }
+
+ variables->buffer_type = type == NGX_JS_BUFFER ? NGX_JS_BUFFER
+ : NGX_JS_STRING;
+ JS_SetOpaque(obj, variables);
+
+ return obj;
+}
+
+
static ngx_http_request_t *
-ngx_http_qjs_request(JSValueConst val)
+ngx_http_qjs_request(JSContext *cx, JSValueConst val)
{
- ngx_http_qjs_request_t *req;
+ ngx_js_opaque_t *opaque;
- req = JS_GetOpaque(val, NGX_QJS_CLASS_ID_HTTP_REQUEST);
- if (req == NULL) {
+ opaque = ngx_qjs_opaque_get(cx, val, NGX_QJS_CLASS_ID_HTTP_REQUEST);
+ if (opaque == NULL) {
return NULL;
}
- return req->opaque.external;
+ return opaque->external;
}
static JSValue
ngx_http_qjs_request_make(JSContext *cx, ngx_int_t proto_id,
- ngx_http_request_t *r)
+ ngx_http_request_t *r, ngx_qjs_owner_t *owner)
{
JSValue request;
ngx_http_qjs_request_t *req;
return JS_EXCEPTION;
}
- req = js_malloc(cx, sizeof(ngx_http_qjs_request_t));
+ req = (ngx_http_qjs_request_t *) ngx_qjs_opaque_alloc(cx,
+ sizeof(ngx_http_qjs_request_t), r, owner);
if (req == NULL) {
JS_FreeValue(cx, request);
- return JS_ThrowOutOfMemory(cx);
+ return JS_EXCEPTION;
}
- req->opaque.external = r;
req->args = JS_UNDEFINED;
req->request_body = JS_UNDEFINED;
req->response_body = JS_UNDEFINED;
JS_FreeValueRT(rt, req->request_body);
JS_FreeValueRT(rt, req->response_body);
- js_free_rt(rt, req);
+ ngx_qjs_opaque_free(rt, &req->opaque);
}
return;
}
- js_free_rt(rt, req);
+ ngx_qjs_opaque_free(rt, &req->opaque);
}
}
ngx_qjs_arg(ctx->args[0]) = ngx_http_qjs_request_make(cx, proto_id,
- external);
+ external, NULL);
if (JS_IsException(ngx_qjs_arg(ctx->args[0]))) {
goto failed;
}
JSValue callbacks[NGX_JS_EVENT_MAX];
} ngx_stream_qjs_session_t;
+
+typedef struct {
+ ngx_js_opaque_t opaque;
+ ngx_uint_t buffer_type;
+} ngx_stream_qjs_variables_t;
+
#endif
#define ngx_stream_pending(ctx) \
static ngx_int_t ngx_stream_qjs_body_filter(ngx_stream_session_t *s,
ngx_stream_js_ctx_t *ctx, ngx_chain_t *in, ngx_uint_t from_upstream);
-static ngx_stream_session_t *ngx_stream_qjs_session(JSValueConst val);
+static ngx_stream_session_t *ngx_stream_qjs_session(JSContext *cx,
+ JSValueConst val);
+static JSValue ngx_stream_qjs_variables_make(JSContext *cx,
+ JSValueConst session, JSClassID class_id, int type);
static JSValue ngx_stream_qjs_session_make(JSContext *cx, ngx_int_t proto_id,
ngx_stream_session_t *s);
static void ngx_stream_qjs_session_mark(JSRuntime *rt, JSValueConst val,
static JSClassDef ngx_stream_qjs_variables_class = {
"Variables",
- .finalizer = NULL,
+ .finalizer = ngx_qjs_opaque_finalizer,
.exotic = & (JSClassExoticMethods) {
.get_own_property = ngx_stream_qjs_variables_own_property,
.set_property = ngx_stream_qjs_variables_set_property,
ngx_stream_js_ctx_t *ctx;
ngx_stream_session_t *s;
- s = ngx_stream_qjs_session(this_val);
+ s = ngx_stream_qjs_session(cx, this_val);
if (s == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a session object");
+ return JS_EXCEPTION;
}
status = (ngx_int_t) magic;
const char *msg;
ngx_stream_session_t *s;
- s = ngx_stream_qjs_session(this_val);
+ s = ngx_stream_qjs_session(cx, this_val);
if (s == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a session object");
+ return JS_EXCEPTION;
}
for (n = 0; n < argc; n++) {
ngx_stream_qjs_session_t *ses;
const ngx_stream_qjs_event_t *e;
- ses = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_STREAM_SESSION);
+ ses = (ngx_stream_qjs_session_t *) ngx_qjs_opaque_get(cx, this_val,
+ NGX_QJS_CLASS_ID_STREAM_SESSION);
if (ses == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a session object");
+ return JS_EXCEPTION;
}
s = ses->opaque.external;
return JS_EXCEPTION;
}
+ if (ngx_stream_qjs_session(cx, this_val) == NULL) {
+ return JS_EXCEPTION;
+ }
+
if (JS_IsFunction(cx, ngx_qjs_arg(ctx->events[e->id].function))) {
return JS_ThrowTypeError(cx, "event handler \"%s\" is already set",
e->name.data);
ngx_stream_session_t *s;
const ngx_stream_qjs_event_t *e;
- s = ngx_stream_qjs_session(this_val);
+ s = ngx_stream_qjs_session(cx, this_val);
if (s == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a session object");
+ return JS_EXCEPTION;
}
ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module);
return JS_EXCEPTION;
}
+ if (ngx_stream_qjs_session(cx, this_val) == NULL) {
+ return JS_EXCEPTION;
+ }
+
ngx_qjs_arg(ctx->events[e->id].function) = JS_NULL;
ctx->events[e->id].data_type = NGX_JS_UNSET;
ngx_stream_qjs_ext_periodic_variables(JSContext *cx,
JSValueConst this_val, int type)
{
- JSValue obj;
- ngx_stream_qjs_session_t *ses;
-
- ses = JS_GetOpaque(this_val, NGX_QJS_CLASS_ID_STREAM_PERIODIC);
- if (ses == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a periodic object");
- }
-
- obj = JS_NewObjectProtoClass(cx, JS_NULL, NGX_QJS_CLASS_ID_STREAM_VARS);
-
- /*
- * Using lowest bit of the pointer to store the buffer type.
- */
- type = (type == NGX_JS_BUFFER) ? 1 : 0;
- JS_SetOpaque(obj, (void *) ((uintptr_t) ses->opaque.external
- | (uintptr_t) type));
-
- return obj;
+ return ngx_stream_qjs_variables_make(cx, this_val,
+ NGX_QJS_CLASS_ID_STREAM_PERIODIC, type);
}
ngx_connection_t *c;
ngx_stream_session_t *s;
- s = ngx_stream_qjs_session(this_val);
+ s = ngx_stream_qjs_session(cx, this_val);
if (s == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a session object");
+ return JS_EXCEPTION;
}
c = s->connection;
ngx_stream_js_ctx_t *ctx;
ngx_stream_session_t *s;
- s = ngx_stream_qjs_session(this_val);
+ s = ngx_stream_qjs_session(cx, this_val);
if (s == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a session object");
+ return JS_EXCEPTION;
}
c = s->connection;
ngx_js_ctx_t *ctx;
ngx_stream_session_t *s;
- s = ngx_stream_qjs_session(this_val);
+ s = ngx_stream_qjs_session(cx, this_val);
if (s == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a session object");
+ return JS_EXCEPTION;
}
ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module);
static JSValue
ngx_stream_qjs_ext_variables(JSContext *cx, JSValueConst this_val, int type)
{
- JSValue obj;
- ngx_stream_session_t *s;
-
- s = ngx_stream_qjs_session(this_val);
- if (s == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a session object");
- }
-
- obj = JS_NewObjectProtoClass(cx, JS_NULL, NGX_QJS_CLASS_ID_STREAM_VARS);
-
- /*
- * Using lowest bit of the pointer to store the buffer type.
- */
- type = (type == NGX_JS_BUFFER) ? 1 : 0;
- JS_SetOpaque(obj, (void *) ((uintptr_t) s | (uintptr_t) type));
-
- return obj;
+ return ngx_stream_qjs_variables_make(cx, this_val,
+ NGX_QJS_CLASS_ID_STREAM_SESSION, type);
}
ngx_stream_variable_t *v;
ngx_stream_core_main_conf_t *cmcf;
- s = ngx_stream_qjs_session(this_val);
+ s = ngx_stream_qjs_session(cx, this_val);
if (s == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a session object");
+ return JS_EXCEPTION;
}
prefix = NULL;
ngx_uint_t *field;
ngx_stream_session_t *s;
- s = ngx_stream_qjs_session(this_val);
+ s = ngx_stream_qjs_session(cx, this_val);
if (s == NULL) {
- return JS_ThrowTypeError(cx, "\"this\" is not a session object");
+ return JS_EXCEPTION;
}
field = (ngx_uint_t *) ((u_char *) s + offset);
ngx_str_t name, name_lc;
ngx_uint_t key;
ngx_stream_session_t *s;
+ ngx_stream_qjs_variables_t *variables;
ngx_stream_variable_value_t *vv;
u_char storage[64];
- s = JS_GetOpaque(obj, NGX_QJS_CLASS_ID_STREAM_VARS);
-
- buffer_type = ((uintptr_t) s & 1) ? NGX_JS_BUFFER : NGX_JS_STRING;
- s = (ngx_stream_session_t *) ((uintptr_t) s & ~(uintptr_t) 1);
-
- if (s == NULL) {
- (void) JS_ThrowTypeError(cx, "\"this\" is not a session object");
+ variables = (ngx_stream_qjs_variables_t *) ngx_qjs_opaque_get(cx, obj,
+ NGX_QJS_CLASS_ID_STREAM_VARS);
+ if (variables == NULL) {
return -1;
}
+ buffer_type = variables->buffer_type;
+ s = variables->opaque.external;
+
name.data = (u_char *) JS_AtomToCString(cx, prop);
if (name.data == NULL) {
return -1;
vv = ngx_stream_get_variable(s, &name_lc, key);
JS_FreeCString(cx, (char *) name.data);
+ if (ngx_qjs_opaque_get(cx, obj, NGX_QJS_CLASS_ID_STREAM_VARS) == NULL) {
+ return -1;
+ }
+
if (vv == NULL || vv->not_found) {
return 0;
}
ngx_uint_t key;
ngx_stream_session_t *s;
ngx_stream_variable_t *v;
+ ngx_stream_qjs_variables_t *variables;
ngx_stream_variable_value_t *vv;
ngx_stream_core_main_conf_t *cmcf;
u_char storage[64];
- s = JS_GetOpaque(obj, NGX_QJS_CLASS_ID_STREAM_VARS);
-
- s = (ngx_stream_session_t *) ((uintptr_t) s & ~(uintptr_t) 1);
-
- if (s == NULL) {
- (void) JS_ThrowTypeError(cx, "\"this\" is not a session object");
+ variables = (ngx_stream_qjs_variables_t *) ngx_qjs_opaque_get(cx, obj,
+ NGX_QJS_CLASS_ID_STREAM_VARS);
+ if (variables == NULL) {
return -1;
}
+ s = variables->opaque.external;
+
name.data = (u_char *) JS_AtomToCString(cx, prop);
if (name.data == NULL) {
return -1;
return -1;
}
+ if (ngx_qjs_opaque_get(cx, obj, NGX_QJS_CLASS_ID_STREAM_VARS) == NULL) {
+ return -1;
+ }
+
if (v->set_handler != NULL) {
vv = ngx_pcalloc(s->connection->pool,
sizeof(ngx_stream_variable_value_t));
}
+static JSValue
+ngx_stream_qjs_variables_make(JSContext *cx, JSValueConst session,
+ JSClassID class_id, int type)
+{
+ JSValue obj;
+ ngx_js_opaque_t *parent;
+ ngx_stream_qjs_variables_t *variables;
+
+ parent = ngx_qjs_opaque_get(cx, session, class_id);
+ if (parent == NULL) {
+ return JS_EXCEPTION;
+ }
+
+ obj = JS_NewObjectProtoClass(cx, JS_NULL, NGX_QJS_CLASS_ID_STREAM_VARS);
+ if (JS_IsException(obj)) {
+ return obj;
+ }
+
+ variables = (ngx_stream_qjs_variables_t *) ngx_qjs_opaque_alloc(cx,
+ sizeof(ngx_stream_qjs_variables_t),
+ parent->external, parent->owner);
+ if (variables == NULL) {
+ JS_FreeValue(cx, obj);
+ return JS_EXCEPTION;
+ }
+
+ variables->buffer_type = type == NGX_JS_BUFFER ? NGX_JS_BUFFER
+ : NGX_JS_STRING;
+ JS_SetOpaque(obj, variables);
+
+ return obj;
+}
+
+
static ngx_stream_session_t *
-ngx_stream_qjs_session(JSValueConst val)
+ngx_stream_qjs_session(JSContext *cx, JSValueConst val)
{
- ngx_stream_qjs_session_t *ses;
+ ngx_js_opaque_t *opaque;
- ses = JS_GetOpaque(val, NGX_QJS_CLASS_ID_STREAM_SESSION);
- if (ses == NULL) {
+ opaque = ngx_qjs_opaque_get(cx, val, NGX_QJS_CLASS_ID_STREAM_SESSION);
+ if (opaque == NULL) {
return NULL;
}
- return ses->opaque.external;
+ return opaque->external;
}
return JS_EXCEPTION;
}
- ses = js_malloc(cx, sizeof(ngx_stream_qjs_session_t));
+ ses = (ngx_stream_qjs_session_t *) ngx_qjs_opaque_alloc(cx,
+ sizeof(ngx_stream_qjs_session_t), s, NULL);
if (ses == NULL) {
JS_FreeValue(cx, session);
- return JS_ThrowOutOfMemory(cx);
+ return JS_EXCEPTION;
}
- ses->opaque.external = s;
-
for (i = 0; i < NGX_JS_EVENT_MAX; i++) {
ses->callbacks[i] = JS_UNDEFINED;
}
JS_FreeValueRT(rt, ses->callbacks[i]);
}
- js_free_rt(rt, ses);
+ ngx_qjs_opaque_free(rt, &ses->opaque);
}
return;
}
- js_free_rt(rt, ses);
+ ngx_qjs_opaque_free(rt, &ses->opaque);
}
--- /dev/null
+#!/usr/bin/perl
+
+# (C) Dmitry Volyntsev
+# (C) F5, Inc.
+
+# Tests for QuickJS HTTP object lifetime after request cleanup.
+
+###############################################################################
+
+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_context_reuse 1;
+ js_import test.js;
+ js_var $test_value original;
+
+ server {
+ listen 127.0.0.1:8080;
+ server_name localhost;
+
+ location /engine {
+ js_content test.engine;
+ }
+
+ location /a {
+ js_content test.content;
+ }
+
+ location /child {
+ return 200 'child';
+ }
+ }
+}
+
+EOF
+
+$t->write_file('test.js', <<'EOF');
+ let saved, snapshots;
+
+ function engine(r) {
+ r.return(200, njs.engine);
+ }
+
+ const checks = {
+ headers_in: () => saved.host,
+ headers_in_keys: () => Object.keys(saved),
+ headers_out: () => saved['X-Test'],
+ headers_out_set: () => { saved['X-Test'] = 'changed'; },
+ headers_out_delete: () => { delete saved['X-Test']; },
+ headers_out_define: () => {
+ Object.defineProperty(saved, 'X-Test', {value: 'changed'});
+ },
+ headers_out_keys: () => Object.keys(saved),
+ variables: () => saved.test_value,
+ variables_set: () => { saved.test_value = 'changed'; },
+ raw_variables: () => saved.test_value,
+ form: () => saved.get('x'),
+ form_all: () => saved.getAll('x'),
+ form_has: () => saved.has('x'),
+ form_each: () => saved.forEach(() => {}),
+ form_files: () => saved.hasFiles(),
+ form_file: () => saved.get('upload'),
+ request: () => saved.uri,
+ args_hot: () => saved.args,
+ body_cold: () => saved.requestText,
+ body_hot: () => saved.requestText,
+ body_switch: () => saved.requestBuffer,
+ read_text: () => saved.readRequestText(),
+ read_form: () => saved.readRequestForm(),
+ reply: () => saved.status,
+ reply_callback: () => saved.status,
+ reply_headers: () => saved.headersOut,
+ reply_header_view: () => saved['Content-Type'],
+ reply_variables: () => saved.test_value,
+ reply_body: () => saved.responseBuffer
+ };
+
+ async function content(r) {
+ const name = r.args.case;
+ if (r.args.keep) {
+ snapshots = {args: r.args, uri: r.uri};
+
+ if (name.startsWith('headers_in')) {
+ saved = r.headersIn;
+ } else if (name.startsWith('headers_out')) {
+ r.headersOut['X-Test'] = 'original';
+ saved = r.headersOut;
+ } else if (name.startsWith('variables')) {
+ saved = r.variables;
+ } else if (name === 'raw_variables') {
+ saved = r.rawVariables;
+ snapshots.buffer = saved.test_value;
+ } else if (name.startsWith('form')) {
+ saved = await r.readRequestForm();
+ snapshots.form = saved.get('x');
+ if (name === 'form_file') {
+ snapshots.filename = saved.get('upload').name;
+ }
+ } else if (name.startsWith('reply')) {
+ if (name === 'reply_callback') {
+ saved = await new Promise(resolve => {
+ r.subrequest('/child', resolve);
+ });
+ } else {
+ saved = await r.subrequest('/child');
+ }
+
+ if (saved.status !== 200 || saved.headersOut === undefined)
+ {
+ throw new Error('reply expired before parent cleanup');
+ }
+
+ snapshots.reply = saved.responseText;
+ if (name === 'reply_header_view') {
+ saved = saved.headersOut;
+ } else if (name === 'reply_variables') {
+ saved = saved.variables;
+ }
+ } else {
+ saved = r;
+ if (name === 'body_hot' || name === 'body_switch') {
+ snapshots.body = r.requestText;
+ }
+ }
+
+ r.return(200, 'kept');
+ return;
+ }
+
+ let expired = false;
+ try {
+ checks[name]();
+ } catch (e) {
+ expired = e instanceof TypeError;
+ }
+
+ const independent = snapshots.args.keep === '1'
+ && snapshots.uri === '/a'
+ && (snapshots.form === undefined
+ || snapshots.form === 'original')
+ && (snapshots.body === undefined
+ || snapshots.body === 'x=original')
+ && (snapshots.filename === undefined
+ || snapshots.filename === 'test.txt')
+ && (snapshots.reply === undefined
+ || snapshots.reply === 'child')
+ && (snapshots.buffer === undefined
+ || snapshots.buffer.toString() === 'original');
+
+ saved = null;
+ snapshots = null;
+ r.return(200, expired + ':' + independent);
+ }
+
+ export default {content, engine};
+
+EOF
+
+my @cases = qw/
+ headers_in headers_in_keys
+ headers_out headers_out_set headers_out_delete headers_out_define
+ headers_out_keys variables variables_set raw_variables
+ form form_all form_has form_each form_files form_file
+ request args_hot body_cold body_hot body_switch read_text read_form
+ reply reply_callback reply_headers reply_header_view reply_variables
+ reply_body
+/;
+
+@cases = grep { $_ eq $ENV{NJS_LIFETIME_CASE} } @cases
+ if defined $ENV{NJS_LIFETIME_CASE};
+
+$t->try_run('no njs available');
+
+plan(skip_all => 'QuickJS-specific lifetime tests')
+ if http_get('/engine') =~ /njs$/m;
+
+$t->plan(2 * @cases);
+
+###############################################################################
+
+for my $case (@cases) {
+ my $type = 'application/x-www-form-urlencoded';
+ my $body = 'x=original';
+
+ if ($case eq 'form_file') {
+ $type = 'multipart/form-data; boundary=A';
+ $body = "--A\r\nContent-Disposition: form-data; name=\"x\"\r\n\r\n"
+ . "original\r\n--A\r\n"
+ . "Content-Disposition: form-data; name=\"upload\"; "
+ . "filename=\"test.txt\"\r\n\r\npayload\r\n--A--\r\n";
+ }
+
+ like(http("POST /a?keep=1&case=$case HTTP/1.0\r\n"
+ . "Host: localhost\r\n"
+ . "Content-Type: $type\r\n"
+ . "Content-Length: " . length($body) . "\r\n\r\n$body"),
+ qr/kept$/, "$case before cleanup");
+ like(http_get("/a?case=$case"), qr/true:true$/,
+ "$case expires without invalidating extracted values");
+}
+
+###############################################################################