aboutsummaryrefslogtreecommitdiff
path: root/nginx/ngx_http_js_module.c
diff options
context:
space:
mode:
Diffstat (limited to 'nginx/ngx_http_js_module.c')
-rw-r--r--nginx/ngx_http_js_module.c1205
1 files changed, 1205 insertions, 0 deletions
diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c
new file mode 100644
index 00000000..a888744b
--- /dev/null
+++ b/nginx/ngx_http_js_module.c
@@ -0,0 +1,1205 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) NGINX, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_stub.h>
+#include <nxt_lvlhsh.h>
+#include <nxt_mem_cache_pool.h>
+#include <njscript.h>
+
+
+#define NGX_HTTP_JS_MCP_CLUSTER_SIZE (2 * ngx_pagesize)
+#define NGX_HTTP_JS_MCP_PAGE_ALIGNMENT 128
+#define NGX_HTTP_JS_MCP_PAGE_SIZE 512
+#define NGX_HTTP_JS_MCP_MIN_CHUNK_SIZE 16
+
+
+#define ngx_http_js_create_mem_cache_pool() \
+ nxt_mem_cache_pool_create(&ngx_http_js_mem_cache_pool_proto, NULL, NULL, \
+ NGX_HTTP_JS_MCP_CLUSTER_SIZE, \
+ NGX_HTTP_JS_MCP_PAGE_ALIGNMENT, \
+ NGX_HTTP_JS_MCP_PAGE_SIZE, \
+ NGX_HTTP_JS_MCP_MIN_CHUNK_SIZE)
+
+
+typedef struct {
+ njs_vm_t *vm;
+} ngx_http_js_loc_conf_t;
+
+
+typedef struct {
+ ngx_list_part_t *part;
+ ngx_uint_t item;
+} ngx_http_js_table_entry_t;
+
+
+static ngx_int_t ngx_http_js_handler(ngx_http_request_t *r);
+static ngx_int_t ngx_http_js_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static void ngx_http_js_cleanup_mem_cache_pool(void *data);
+
+static void *ngx_http_js_alloc(void *mem, size_t size);
+static void *ngx_http_js_calloc(void *mem, size_t size);
+static void *ngx_http_js_memalign(void *mem, size_t alignment, size_t size);
+static void ngx_http_js_free(void *mem, void *p);
+
+static njs_ret_t ngx_http_js_ext_undefined(njs_vm_t *vm, njs_value_t *value,
+ void *obj, uintptr_t data);
+static njs_ret_t ngx_http_js_ext_get_string(njs_vm_t *vm, njs_value_t *value,
+ void *obj, uintptr_t data);
+static njs_ret_t ngx_http_js_ext_set_string(njs_vm_t *vm, void *obj,
+ uintptr_t data, nxt_str_t *value);
+static njs_ret_t ngx_http_js_ext_each_header_start(njs_vm_t *vm, void *obj,
+ void *each, uintptr_t data);
+static njs_ret_t ngx_http_js_ext_each_header(njs_vm_t *vm, njs_value_t *value,
+ void *obj, void *each);
+static ngx_table_elt_t *ngx_http_js_get_header(ngx_list_part_t *part,
+ u_char *data, size_t len);
+static njs_ret_t ngx_http_js_ext_get_header_out(njs_vm_t *vm,
+ njs_value_t *value, void *obj, uintptr_t data);
+static njs_ret_t ngx_http_js_ext_set_header_out(njs_vm_t *vm, void *obj,
+ uintptr_t data, nxt_str_t *value);
+static njs_ret_t ngx_http_js_ext_each_header_out_start(njs_vm_t *vm, void *obj,
+ void *each); /*FIXME*/
+static njs_ret_t ngx_http_js_ext_get_status(njs_vm_t *vm, njs_value_t *value,
+ void *obj, uintptr_t data);
+static njs_ret_t ngx_http_js_ext_set_status(njs_vm_t *vm, void *obj,
+ uintptr_t data, nxt_str_t *value);
+static njs_ret_t ngx_http_js_ext_get_content_length(njs_vm_t *vm,
+ njs_value_t *value, void *obj, uintptr_t data);
+static njs_ret_t ngx_http_js_ext_set_content_length(njs_vm_t *vm, void *obj,
+ uintptr_t data, nxt_str_t *value);
+static njs_ret_t ngx_http_js_ext_send_header(njs_vm_t *vm, njs_param_t *param);
+static njs_ret_t ngx_http_js_ext_send(njs_vm_t *vm, njs_param_t *param);
+static njs_ret_t ngx_http_js_ext_finish(njs_vm_t *vm, njs_param_t *param);
+static njs_ret_t ngx_http_js_ext_get_http_version(njs_vm_t *vm,
+ njs_value_t *value, void *obj, uintptr_t data);
+static njs_ret_t ngx_http_js_ext_get_remote_address(njs_vm_t *vm,
+ njs_value_t *value, void *obj, uintptr_t data);
+static njs_ret_t ngx_http_js_ext_get_header_in(njs_vm_t *vm, njs_value_t *value,
+ void *obj, uintptr_t data);
+static njs_ret_t ngx_http_js_ext_each_header_in_start(njs_vm_t *vm, void *obj,
+ void *each); /*FIXME*/
+static njs_ret_t ngx_http_js_ext_get_arg(njs_vm_t *vm, njs_value_t *value,
+ void *obj, uintptr_t data);
+static njs_ret_t ngx_http_js_ext_each_arg_start(njs_vm_t *vm, void *obj,
+ void *each);
+static njs_ret_t ngx_http_js_ext_each_arg(njs_vm_t *vm, njs_value_t *value,
+ void *obj, void *each);
+
+static char *ngx_http_js_run(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_http_js_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static njs_vm_t *ngx_http_js_compile(ngx_conf_t *cf, ngx_str_t *script);
+static void *ngx_http_js_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_js_merge_loc_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+
+
+static ngx_command_t ngx_http_js_commands[] = {
+
+ { ngx_string("js_run"),
+ NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_TAKE1,
+ ngx_http_js_run,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("js_set"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE2,
+ ngx_http_js_set,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_js_module_ctx = {
+ NULL, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_http_js_create_loc_conf, /* create location configuration */
+ ngx_http_js_merge_loc_conf /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_js_module = {
+ NGX_MODULE_V1,
+ &ngx_http_js_module_ctx, /* module context */
+ ngx_http_js_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static const nxt_mem_proto_t ngx_http_js_mem_cache_pool_proto = {
+ ngx_http_js_alloc,
+ ngx_http_js_calloc,
+ ngx_http_js_memalign,
+ NULL,
+ ngx_http_js_free,
+ NULL,
+ NULL,
+};
+
+
+static njs_external_t ngx_http_js_ext_response[] = {
+
+ { nxt_string("headers"),
+ NJS_EXTERN_OBJECT,
+ NULL,
+ 0,
+ ngx_http_js_ext_get_header_out,
+ ngx_http_js_ext_set_header_out,
+ NULL,
+ ngx_http_js_ext_each_header_out_start,
+ ngx_http_js_ext_each_header,
+ NULL,
+ 0 },
+
+ { nxt_string("status"),
+ NJS_EXTERN_PROPERTY,
+ NULL,
+ 0,
+ ngx_http_js_ext_get_status,
+ ngx_http_js_ext_set_status,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ offsetof(ngx_http_request_t, headers_out.status) },
+
+ { nxt_string("contentType"),
+ NJS_EXTERN_PROPERTY,
+ NULL,
+ 0,
+ ngx_http_js_ext_get_string,
+ ngx_http_js_ext_set_string,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ offsetof(ngx_http_request_t, headers_out.content_type) },
+
+ { nxt_string("contentLength"),
+ NJS_EXTERN_PROPERTY,
+ NULL,
+ 0,
+ ngx_http_js_ext_get_content_length,
+ ngx_http_js_ext_set_content_length,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ 0 },
+
+ { nxt_string("sendHeader"),
+ NJS_EXTERN_METHOD,
+ NULL,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ ngx_http_js_ext_send_header,
+ 0 },
+
+ { nxt_string("send"),
+ NJS_EXTERN_METHOD,
+ NULL,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ ngx_http_js_ext_send,
+ 0 },
+
+ { nxt_string("finish"),
+ NJS_EXTERN_METHOD,
+ NULL,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ ngx_http_js_ext_finish,
+ 0 },
+};
+
+
+static njs_external_t ngx_http_js_ext_request[] = {
+
+ { nxt_string("response"),
+ NJS_EXTERN_OBJECT,
+ ngx_http_js_ext_response,
+ nxt_nitems(ngx_http_js_ext_response),
+ ngx_http_js_ext_undefined,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ 0 },
+
+ { nxt_string("uri"),
+ NJS_EXTERN_PROPERTY,
+ NULL,
+ 0,
+ ngx_http_js_ext_get_string,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ offsetof(ngx_http_request_t, uri) },
+
+ { nxt_string("method"),
+ NJS_EXTERN_PROPERTY,
+ NULL,
+ 0,
+ ngx_http_js_ext_get_string,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ offsetof(ngx_http_request_t, method_name) },
+
+ { nxt_string("httpVersion"),
+ NJS_EXTERN_PROPERTY,
+ NULL,
+ 0,
+ ngx_http_js_ext_get_http_version,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ 0 },
+
+ { nxt_string("remoteAddress"),
+ NJS_EXTERN_PROPERTY,
+ NULL,
+ 0,
+ ngx_http_js_ext_get_remote_address,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ 0 },
+
+ { nxt_string("headers"),
+ NJS_EXTERN_OBJECT,
+ NULL,
+ 0,
+ ngx_http_js_ext_get_header_in,
+ NULL,
+ NULL,
+ ngx_http_js_ext_each_header_in_start,
+ ngx_http_js_ext_each_header,
+ NULL,
+ 0 },
+
+ { nxt_string("args"),
+ NJS_EXTERN_OBJECT,
+ NULL,
+ 0,
+ ngx_http_js_ext_get_arg,
+ NULL,
+ NULL,
+ ngx_http_js_ext_each_arg_start,
+ ngx_http_js_ext_each_arg,
+ NULL,
+ 0 },
+};
+
+
+static njs_external_t ngx_http_js_externals[] = {
+
+ { nxt_string("$r"),
+ NJS_EXTERN_OBJECT,
+ ngx_http_js_ext_request,
+ nxt_nitems(ngx_http_js_ext_request),
+ ngx_http_js_ext_undefined,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ 0 },
+};
+
+
+static ngx_int_t
+ngx_http_js_handler(ngx_http_request_t *r)
+{
+ nxt_str_t value;
+ njs_vm_t *nvm;
+ ngx_pool_cleanup_t *cln;
+ nxt_mem_cache_pool_t *mcp;
+ ngx_http_js_loc_conf_t *jlcf;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http js handler");
+
+ mcp = ngx_http_js_create_mem_cache_pool();
+ if (mcp == NULL) {
+ return NGX_ERROR;
+ }
+
+ cln = ngx_pool_cleanup_add(r->pool, 0);
+ if (cln == NULL) {
+ return NGX_ERROR;
+ }
+
+ cln->handler = ngx_http_js_cleanup_mem_cache_pool;
+ cln->data = mcp;
+
+ jlcf = ngx_http_get_module_loc_conf(r, ngx_http_js_module);
+
+ nvm = njs_vm_clone(jlcf->vm, mcp, (void **) &r);
+ if (nvm == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (njs_vm_run(nvm) != NJS_OK) {
+ njs_vm_exception(nvm, &value);
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "js exception: %*s", value.len, value.data);
+
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_js_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+ uintptr_t data)
+{
+ njs_vm_t *vm = (njs_vm_t *) data;
+
+ nxt_str_t value;
+ njs_vm_t *nvm;
+ ngx_pool_cleanup_t *cln;
+ nxt_mem_cache_pool_t *mcp;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http js variable handler");
+
+ mcp = ngx_http_js_create_mem_cache_pool();
+ if (mcp == NULL) {
+ return NGX_ERROR;
+ }
+
+ cln = ngx_pool_cleanup_add(r->pool, 0);
+ if (cln == NULL) {
+ return NGX_ERROR;
+ }
+
+ cln->handler = ngx_http_js_cleanup_mem_cache_pool;
+ cln->data = mcp;
+
+ nvm = njs_vm_clone(vm, mcp, (void **) &r);
+ if (nvm == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (njs_vm_run(nvm) == NJS_OK) {
+ if (njs_vm_retval(nvm, &value) != NJS_OK) {
+ return NGX_ERROR;
+ }
+
+ v->len = value.len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = value.data;
+
+ } else {
+ njs_vm_exception(nvm, &value);
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "js exception: %*s", value.len, value.data);
+
+ v->not_found = 1;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http js variable done");
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_http_js_cleanup_mem_cache_pool(void *data)
+{
+ nxt_mem_cache_pool_t *mcp = data;
+
+ nxt_mem_cache_pool_destroy(mcp);
+}
+
+
+static void *
+ngx_http_js_alloc(void *mem, size_t size)
+{
+ return ngx_alloc(size, ngx_cycle->log);
+}
+
+
+static void *
+ngx_http_js_calloc(void *mem, size_t size)
+{
+ return ngx_calloc(size, ngx_cycle->log);
+}
+
+
+static void *
+ngx_http_js_memalign(void *mem, size_t alignment, size_t size)
+{
+ return ngx_memalign(alignment, size, ngx_cycle->log);
+}
+
+
+static void
+ngx_http_js_free(void *mem, void *p)
+{
+ ngx_free(p);
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_undefined(njs_vm_t *vm, njs_value_t *value, void *obj,
+ uintptr_t data)
+{
+ njs_void_set(value);
+
+ return NJS_OK;
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_get_string(njs_vm_t *vm, njs_value_t *value, void *obj,
+ uintptr_t data)
+{
+ char *p = obj;
+
+ ngx_str_t *field;
+
+ field = (ngx_str_t *) (p + data);
+
+ return njs_string_create(vm, value, field->data, field->len, 0);
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_set_string(njs_vm_t *vm, void *obj, uintptr_t data,
+ nxt_str_t *value)
+{
+ char *p = obj;
+
+ ngx_str_t *field;
+ ngx_http_request_t *r;
+
+ r = (ngx_http_request_t *) obj;
+
+ field = (ngx_str_t *) (p + data);
+ field->len = value->len;
+
+ field->data = ngx_pnalloc(r->pool, value->len);
+ if (field->data == NULL) {
+ return NJS_ERROR;
+ }
+
+ ngx_memcpy(field->data, value->data, value->len);
+
+ return NJS_OK;
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_each_header_start(njs_vm_t *vm, void *obj, void *each,
+ uintptr_t data)
+{
+ char *p = obj;
+
+ ngx_list_t *headers;
+ ngx_http_request_t *r;
+ ngx_http_js_table_entry_t *entry, **e;
+
+ r = (ngx_http_request_t *) obj;
+
+ entry = ngx_palloc(r->pool, sizeof(ngx_http_js_table_entry_t));
+ if (entry == NULL) {
+ return NJS_ERROR;
+ }
+
+ headers = (ngx_list_t *) (p + data);
+
+ entry->part = &headers->part;
+ entry->item = 0;
+
+ e = (ngx_http_js_table_entry_t **) each;
+ *e = entry;
+
+ return NJS_OK;
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_each_header(njs_vm_t *vm, njs_value_t *value, void *obj,
+ void *each)
+{
+ ngx_http_js_table_entry_t **e = each;
+
+ ngx_table_elt_t *header, *h;
+ ngx_http_js_table_entry_t *entry;
+
+ entry = *e;
+
+ while (entry->part) {
+
+ if (entry->item >= entry->part->nelts) {
+ entry->part = entry->part->next;
+ entry->item = 0;
+ continue;
+ }
+
+ header = entry->part->elts;
+ h = &header[entry->item++];
+
+ return njs_string_create(vm, value, h->key.data, h->key.len, 0);
+ }
+
+ return NJS_DONE;
+}
+
+
+static ngx_table_elt_t *
+ngx_http_js_get_header(ngx_list_part_t *part, u_char *data, size_t len)
+{
+ ngx_uint_t i;
+ ngx_table_elt_t *header, *h;
+
+ header = part->elts;
+
+ for (i = 0; /* void */ ; i++) {
+
+ if (i >= part->nelts) {
+ if (part->next == NULL) {
+ break;
+ }
+
+ part = part->next;
+ header = part->elts;
+ i = 0;
+ }
+
+ h = &header[i];
+
+ if (h->hash == 0) {
+ continue;
+ }
+
+ if (h->key.len == len && ngx_strncasecmp(h->key.data, data, len) == 0) {
+ return h;
+ }
+ }
+
+ return NULL;
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_get_header_out(njs_vm_t *vm, njs_value_t *value, void *obj,
+ uintptr_t data)
+{
+ nxt_str_t *v;
+ ngx_table_elt_t *h;
+ ngx_http_request_t *r;
+
+ r = (ngx_http_request_t *) obj;
+ v = (nxt_str_t *) data;
+
+ h = ngx_http_js_get_header(&r->headers_out.headers.part, v->data, v->len);
+ if (h == NULL) {
+ return njs_string_create(vm, value, NULL, 0, 0);
+ }
+
+ return njs_string_create(vm, value, h->value.data, h->value.len, 0);
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_set_header_out(njs_vm_t *vm, void *obj, uintptr_t data,
+ nxt_str_t *value)
+{
+ u_char *p;
+ nxt_str_t *v;
+ ngx_table_elt_t *h;
+ ngx_http_request_t *r;
+
+ r = (ngx_http_request_t *) obj;
+ v = (nxt_str_t *) data;
+
+ h = ngx_http_js_get_header(&r->headers_out.headers.part, v->data, v->len);
+
+ if (h == NULL || h->hash == 0) {
+ h = ngx_list_push(&r->headers_out.headers);
+ if (h == NULL) {
+ return NJS_ERROR;
+ }
+
+ p = ngx_pnalloc(r->pool, v->len);
+ if (p == NULL) {
+ return NJS_ERROR;
+ }
+
+ ngx_memcpy(p, v->data, v->len);
+
+ h->key.data = p;
+ h->key.len = v->len;
+ h->hash = 1;
+ }
+
+
+ p = ngx_pnalloc(r->pool, value->len);
+ if (p == NULL) {
+ return NJS_ERROR;
+ }
+
+ ngx_memcpy(p, value->data, value->len);
+
+ h->value.data = p;
+ h->value.len = value->len;
+
+ return NJS_OK;
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_each_header_out_start(njs_vm_t *vm, void *obj, void *each)
+{
+ return ngx_http_js_ext_each_header_start(vm, obj, each,
+ offsetof(ngx_http_request_t, headers_out.headers));
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_get_status(njs_vm_t *vm, njs_value_t *value, void *obj,
+ uintptr_t data)
+{
+ size_t len;
+ u_char *p;
+ ngx_http_request_t *r;
+
+ r = (ngx_http_request_t *) obj;
+
+ p = ngx_pnalloc(r->pool, 3);
+ if (p == NULL) {
+ return NJS_ERROR;
+ }
+
+ len = ngx_snprintf(p, 3, "%ui", r->headers_out.status) - p;
+
+ return njs_string_create(vm, value, p, len, 0);
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_set_status(njs_vm_t *vm, void *obj, uintptr_t data,
+ nxt_str_t *value)
+{
+ ngx_int_t n;
+ ngx_http_request_t *r;
+
+ n = ngx_atoi(value->data, value->len);
+ if (n == NGX_ERROR) {
+ return NJS_ERROR;
+ }
+
+ r = (ngx_http_request_t *) obj;
+
+ r->headers_out.status = n;
+
+ return NJS_OK;
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_get_content_length(njs_vm_t *vm, njs_value_t *value, void *obj,
+ uintptr_t data)
+{
+ size_t len;
+ u_char *p;
+ ngx_http_request_t *r;
+
+ r = (ngx_http_request_t *) obj;
+
+ p = ngx_pnalloc(r->pool, NGX_OFF_T_LEN);
+ if (p == NULL) {
+ return NJS_ERROR;
+ }
+
+ len = ngx_sprintf(p, "%O", r->headers_out.content_length_n) - p;
+
+ return njs_string_create(vm, value, p, len, 0);
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_set_content_length(njs_vm_t *vm, void *obj, uintptr_t data,
+ nxt_str_t *value)
+{
+ ngx_int_t n;
+ ngx_http_request_t *r;
+
+ n = ngx_atoi(value->data, value->len);
+ if (n == NGX_ERROR) {
+ return NJS_ERROR;
+ }
+
+ r = (ngx_http_request_t *) obj;
+
+ r->headers_out.content_length_n = n;
+
+ return NJS_OK;
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_send_header(njs_vm_t *vm, njs_param_t *param)
+{
+ ngx_http_request_t *r;
+
+ r = njs_value_data(param->object);
+
+ if (ngx_http_send_header(r) == NGX_ERROR) {
+ return NJS_ERROR;
+ }
+
+ return NJS_OK;
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_send(njs_vm_t *vm, njs_param_t *param)
+{
+ nxt_int_t ret;
+ nxt_str_t s;
+ ngx_buf_t *b;
+ uintptr_t nargs, next;
+ ngx_uint_t n;
+ njs_value_t *args;
+ ngx_chain_t *out, *cl, **ll;
+ ngx_http_request_t *r;
+
+ r = njs_value_data(param->object);
+
+ out = NULL;
+ ll = &out;
+
+ args = param->args;
+ nargs = param->nargs;
+
+ for (n = 0; n < nargs; n++) {
+ next = 0;
+
+ for ( ;; ) {
+ ret = njs_value_string_copy(vm, &s, njs_argument(args, n), &next);
+
+ if (ret == NJS_DECLINED) {
+ break;
+ }
+
+ if (ret == NJS_ERROR) {
+ return NJS_ERROR;
+ }
+
+ /* TODO: njs_value_release(vm, value) in buf completion */
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http js send: \"%*s\"", s.len, s.data);
+
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ return NJS_ERROR;
+ }
+
+ b->start = s.data;
+ b->pos = b->start;
+ b->end = s.data + s.len;
+ b->last = b->end;
+ b->memory = 1;
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NJS_ERROR;
+ }
+
+ cl->buf = b;
+
+ *ll = cl;
+ ll = &cl->next;
+ }
+ }
+
+ *ll = NULL;
+
+ if (ngx_http_output_filter(r, out) == NGX_ERROR) {
+ return NJS_ERROR;
+ }
+
+ return NJS_OK;
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_finish(njs_vm_t *vm, njs_param_t *param)
+{
+ ngx_http_request_t *r;
+
+ r = njs_value_data(param->object);
+
+ if (ngx_http_send_special(r, NGX_HTTP_LAST) == NGX_ERROR) {
+ return NJS_ERROR;
+ }
+
+ return NJS_OK;
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_get_http_version(njs_vm_t *vm, njs_value_t *value, void *obj,
+ uintptr_t data)
+{
+ ngx_str_t v;
+ ngx_http_request_t *r;
+
+ r = (ngx_http_request_t *) obj;
+
+ switch (r->http_version) {
+
+ case NGX_HTTP_VERSION_9:
+ ngx_str_set(&v, "0.9");
+ break;
+
+ case NGX_HTTP_VERSION_10:
+ ngx_str_set(&v, "1.0");
+ break;
+
+ default: /* NGX_HTTP_VERSION_11 */
+ ngx_str_set(&v, "1.1");
+ break;
+ }
+
+ return njs_string_create(vm, value, v.data, v.len, 0);
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_get_remote_address(njs_vm_t *vm, njs_value_t *value, void *obj,
+ uintptr_t data)
+{
+ ngx_connection_t *c;
+ ngx_http_request_t *r;
+
+ r = (ngx_http_request_t *) obj;
+ c = r->connection;
+
+ return njs_string_create(vm, value, c->addr_text.data, c->addr_text.len, 0);
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_get_header_in(njs_vm_t *vm, njs_value_t *value, void *obj,
+ uintptr_t data)
+{
+ nxt_str_t *v;
+ ngx_table_elt_t *h;
+ ngx_http_request_t *r;
+
+ r = (ngx_http_request_t *) obj;
+ v = (nxt_str_t *) data;
+
+ h = ngx_http_js_get_header(&r->headers_in.headers.part, v->data, v->len);
+ if (h == NULL) {
+ return njs_string_create(vm, value, NULL, 0, 0);
+ }
+
+ return njs_string_create(vm, value, h->value.data, h->value.len, 0);
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_each_header_in_start(njs_vm_t *vm, void *obj, void *each)
+{
+ return ngx_http_js_ext_each_header_start(vm, obj, each,
+ offsetof(ngx_http_request_t, headers_in.headers));
+}
+
+static njs_ret_t
+ngx_http_js_ext_get_arg(njs_vm_t *vm, njs_value_t *value, void *obj,
+ uintptr_t data)
+{
+ nxt_str_t *v;
+ ngx_str_t arg;
+ ngx_http_request_t *r;
+
+ r = (ngx_http_request_t *) obj;
+ v = (nxt_str_t *) data;
+
+ if (ngx_http_arg(r, v->data, v->len, &arg) == NGX_OK) {
+ return njs_string_create(vm, value, arg.data, arg.len, 0);
+ }
+
+ return njs_string_create(vm, value, NULL, 0, 0);
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_each_arg_start(njs_vm_t *vm, void *obj, void *each)
+{
+ ngx_str_t *entry, **e;
+ ngx_http_request_t *r;
+
+ r = (ngx_http_request_t *) obj;
+
+ entry = ngx_palloc(r->pool, sizeof(ngx_str_t));
+ if (entry == NULL) {
+ return NJS_ERROR;
+ }
+
+ *entry = r->args;
+
+ e = (ngx_str_t **) each;
+ *e = entry;
+
+ return NJS_OK;
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_each_arg(njs_vm_t *vm, njs_value_t *value, void *obj,
+ void *each)
+{
+ ngx_str_t **e = each;
+
+ size_t len;
+ u_char *p, *start, *end;
+ ngx_str_t *entry;
+
+ entry = *e;
+
+ if (entry->len == 0) {
+ return NJS_DONE;
+ }
+
+ start = entry->data;
+ end = start + entry->len;
+
+ p = ngx_strlchr(start, end, '=');
+ if (p == NULL) {
+ return NJS_ERROR;
+ }
+
+ len = p - start;
+ p++;
+
+ p = ngx_strlchr(p, end, '&');
+
+ if (p) {
+ entry->data = &p[1];
+ entry->len = end - entry->data;
+
+ } else {
+ entry->len = 0;
+ }
+
+ return njs_string_create(vm, value, start, len, 0);
+}
+
+
+static char *
+ngx_http_js_run(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_js_loc_conf_t *jlcf = conf;
+
+ ngx_str_t *value;
+ ngx_http_core_loc_conf_t *clcf;
+
+ value = cf->args->elts;
+
+ if (jlcf->vm) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "duplicate js handler \"%V\"", &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ jlcf->vm = ngx_http_js_compile(cf, &value[1]);
+ if (jlcf->vm == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+ clcf->handler = ngx_http_js_handler;
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_js_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ njs_vm_t *vm;
+ ngx_str_t *value;
+ ngx_http_variable_t *v;
+
+ value = cf->args->elts;
+
+ if (value[1].data[0] != '$') {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid variable name \"%V\"", &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ value[1].len--;
+ value[1].data++;
+
+ v = ngx_http_add_variable(cf, &value[1], NGX_HTTP_VAR_CHANGEABLE);
+ if (v == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ vm = ngx_http_js_compile(cf, &value[2]);
+ if (vm == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ v->get_handler = ngx_http_js_variable;
+ v->data = (uintptr_t) vm;
+
+ return NGX_CONF_OK;
+}
+
+
+static njs_vm_t *
+ngx_http_js_compile(ngx_conf_t *cf, ngx_str_t *script)
+{
+ u_char *start, *end;
+ nxt_int_t rc;
+ nxt_str_t s;
+ njs_vm_t *vm;
+ nxt_lvlhsh_t externals;
+ njs_vm_shared_t *shared;
+ nxt_mem_cache_pool_t *mcp;
+
+ mcp = ngx_http_js_create_mem_cache_pool();
+ if (mcp == NULL) {
+ return NULL;
+ }
+
+ shared = NULL;
+
+ nxt_lvlhsh_init(&externals);
+
+ if (njs_add_external(&externals, mcp, 0, ngx_http_js_externals,
+ nxt_nitems(ngx_http_js_externals))
+ != NJS_OK)
+ {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "could not add js externals");
+ return NULL;
+ }
+
+ vm = njs_vm_create(mcp, &shared, &externals);
+ if (vm == NULL) {
+ return NULL;
+ }
+
+ start = script->data;
+ end = start + script->len;
+
+ rc = njs_vm_compile(vm, &start, end);
+
+ if (rc != NJS_OK) {
+ njs_vm_exception(vm, &s);
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "js compilation error: \"%*s\"", s.len, s.data);
+ return NULL;
+ }
+
+ if (start != end) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "extra characters in js script: \"%*s\"",
+ end - start, start);
+ return NULL;
+ }
+
+ return vm;
+}
+
+
+static void *
+ngx_http_js_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_http_js_loc_conf_t *conf;
+
+ conf = ngx_palloc(cf->pool, sizeof(ngx_http_js_loc_conf_t));
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->vm = NULL;
+ */
+
+ return conf;
+}
+
+
+static char *
+ngx_http_js_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_http_js_loc_conf_t *prev = parent;
+ ngx_http_js_loc_conf_t *conf = child;
+
+ if (conf->vm == NULL) {
+ conf->vm = prev->vm;
+ }
+
+ return NGX_CONF_OK;
+}