diff options
Diffstat (limited to 'nginx')
-rw-r--r-- | nginx/config | 6 | ||||
-rw-r--r-- | nginx/ngx_http_js_module.c | 48 | ||||
-rw-r--r-- | nginx/ngx_js.c | 34 | ||||
-rw-r--r-- | nginx/ngx_js.h | 18 | ||||
-rw-r--r-- | nginx/ngx_js_shared_dict.c | 1586 | ||||
-rw-r--r-- | nginx/ngx_js_shared_dict.h | 19 | ||||
-rw-r--r-- | nginx/ngx_stream_js_module.c | 48 | ||||
-rw-r--r-- | nginx/t/js_shared_dict.t | 299 | ||||
-rw-r--r-- | nginx/t/stream_js_shared_dict.t | 188 |
9 files changed, 2240 insertions, 6 deletions
diff --git a/nginx/config b/nginx/config index 1bd922f4..700ae4ab 100644 --- a/nginx/config +++ b/nginx/config @@ -5,10 +5,12 @@ NJS_LIBXSLT=${NJS_LIBXSLT:-YES} NJS_ZLIB=${NJS_ZLIB:-YES} NJS_DEPS="$ngx_addon_dir/ngx_js.h \ - $ngx_addon_dir/ngx_js_fetch.h" + $ngx_addon_dir/ngx_js_fetch.h \ + $ngx_addon_dir/ngx_js_shared_dict.h" NJS_SRCS="$ngx_addon_dir/ngx_js.c \ $ngx_addon_dir/ngx_js_fetch.c \ - $ngx_addon_dir/ngx_js_regex.c" + $ngx_addon_dir/ngx_js_regex.c \ + $ngx_addon_dir/ngx_js_shared_dict.c" NJS_OPENSSL_LIB= NJS_XSLT_LIB= diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c index df3a0e5a..5f6c73da 100644 --- a/nginx/ngx_http_js_module.c +++ b/nginx/ngx_http_js_module.c @@ -252,10 +252,13 @@ static char *ngx_http_js_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_js_var(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_js_content(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static char *ngx_http_js_shared_dict_zone(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); static char *ngx_http_js_body_filter_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_int_t ngx_http_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf); +static void *ngx_http_js_create_main_conf(ngx_conf_t *cf); 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); @@ -393,6 +396,13 @@ static ngx_command_t ngx_http_js_commands[] = { #endif + { ngx_string("js_shared_dict_zone"), + NGX_HTTP_MAIN_CONF|NGX_CONF_1MORE, + ngx_http_js_shared_dict_zone, + 0, + 0, + NULL }, + ngx_null_command }; @@ -401,7 +411,7 @@ static ngx_http_module_t ngx_http_js_module_ctx = { NULL, /* preconfiguration */ ngx_http_js_init, /* postconfiguration */ - NULL, /* create main configuration */ + ngx_http_js_create_main_conf, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ @@ -775,6 +785,7 @@ static uintptr_t ngx_http_js_uptr[] = { (uintptr_t) ngx_http_js_fetch_timeout, (uintptr_t) ngx_http_js_buffer_size, (uintptr_t) ngx_http_js_max_response_buffer_size, + (uintptr_t) 0 /* main_conf ptr */, }; @@ -798,6 +809,7 @@ njs_module_t *njs_http_js_addon_modules[] = { */ &ngx_js_ngx_module, &ngx_js_fetch_module, + &ngx_js_shared_dict_module, #ifdef NJS_HAVE_OPENSSL &njs_webcrypto_module, #endif @@ -4149,10 +4161,14 @@ ngx_js_http_init(njs_vm_t *vm) static ngx_int_t ngx_http_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf) { - njs_vm_opt_t options; + njs_vm_opt_t options; + ngx_js_main_conf_t *jmcf; njs_vm_opt_init(&options); + jmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_js_module); + ngx_http_js_uptr[NGX_JS_MAIN_CONF_INDEX] = (uintptr_t) jmcf; + options.backtrace = 1; options.unhandled_rejection = NJS_VM_OPT_UNHANDLED_REJECTION_THROW; options.ops = &ngx_http_js_ops; @@ -4293,6 +4309,14 @@ ngx_http_js_content(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) static char * +ngx_http_js_shared_dict_zone(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ + return ngx_js_shared_dict_zone(cf, cmd, conf, &ngx_http_js_module); +} + + +static char * ngx_http_js_body_filter_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_js_loc_conf_t *jlcf = conf; @@ -4331,6 +4355,26 @@ ngx_http_js_body_filter_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) static void * +ngx_http_js_create_main_conf(ngx_conf_t *cf) +{ + ngx_js_main_conf_t *jmcf; + + jmcf = ngx_pcalloc(cf->pool, sizeof(ngx_js_main_conf_t)); + if (jmcf == NULL) { + return NULL; + } + + /* + * set by ngx_pcalloc(): + * + * jmcf->dicts = NULL; + */ + + return jmcf; +} + + +static void * ngx_http_js_create_loc_conf(ngx_conf_t *cf) { ngx_http_js_loc_conf_t *conf = diff --git a/nginx/ngx_js.c b/nginx/ngx_js.c index a3b91ca4..f98676c8 100644 --- a/nginx/ngx_js.c +++ b/nginx/ngx_js.c @@ -32,6 +32,28 @@ static void ngx_js_cleanup_vm(void *data); static njs_int_t ngx_js_core_init(njs_vm_t *vm); +static njs_external_t ngx_js_ext_global_shared[] = { + + { + .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL, + .name.symbol = NJS_SYMBOL_TO_STRING_TAG, + .u.property = { + .value = "GlobalShared", + } + }, + + { + .flags = NJS_EXTERN_SELF, + .u.object = { + .enumerable = 1, + .prop_handler = njs_js_ext_global_shared_prop, + .keys = njs_js_ext_global_shared_keys, + } + }, + +}; + + static njs_external_t ngx_js_ext_core[] = { { @@ -113,6 +135,18 @@ static njs_external_t ngx_js_ext_core[] = { }, { + .flags = NJS_EXTERN_OBJECT, + .name.string = njs_str("shared"), + .enumerable = 1, + .writable = 1, + .u.object = { + .enumerable = 1, + .properties = ngx_js_ext_global_shared, + .nproperties = njs_nitems(ngx_js_ext_global_shared), + } + }, + + { .flags = NJS_EXTERN_PROPERTY, .name.string = njs_str("prefix"), .enumerable = 1, diff --git a/nginx/ngx_js.h b/nginx/ngx_js.h index 4ed00249..0febe836 100644 --- a/nginx/ngx_js.h +++ b/nginx/ngx_js.h @@ -14,6 +14,7 @@ #include <ngx_core.h> #include <njs.h> #include "ngx_js_fetch.h" +#include "ngx_js_shared_dict.h" #define NGX_JS_UNSET 0 @@ -43,6 +44,9 @@ typedef ngx_flag_t (*ngx_external_size_pt)(njs_vm_t *vm, njs_external_ptr_t e); typedef ngx_ssl_t *(*ngx_external_ssl_pt)(njs_vm_t *vm, njs_external_ptr_t e); + +typedef struct ngx_js_dict_s ngx_js_dict_t; + typedef struct { ngx_str_t name; ngx_str_t path; @@ -51,6 +55,10 @@ typedef struct { } ngx_js_named_path_t; +#define NGX_JS_COMMON_MAIN_CONF \ + ngx_js_dict_t *dicts \ + + #define _NGX_JS_COMMON_LOC_CONF \ njs_vm_t *vm; \ ngx_array_t *imports; \ @@ -81,6 +89,11 @@ typedef struct { typedef struct { + NGX_JS_COMMON_MAIN_CONF; +} ngx_js_main_conf_t; + + +typedef struct { NGX_JS_COMMON_LOC_CONF; } ngx_js_loc_conf_t; @@ -105,6 +118,9 @@ typedef struct { ((ngx_external_size_pt) njs_vm_meta(vm, 8))(vm, e) #define ngx_external_max_response_buffer_size(vm, e) \ ((ngx_external_size_pt) njs_vm_meta(vm, 9))(vm, e) +#define NGX_JS_MAIN_CONF_INDEX 10 +#define ngx_main_conf(vm) \ + ((ngx_js_main_conf_t *) njs_vm_meta(vm, NGX_JS_MAIN_CONF_INDEX)) #define ngx_js_prop(vm, type, value, start, len) \ @@ -134,6 +150,8 @@ ngx_int_t ngx_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf, ngx_js_loc_conf_t *ngx_js_create_conf(ngx_conf_t *cf, size_t size); char * ngx_js_merge_conf(ngx_conf_t *cf, void *parent, void *child, ngx_int_t (*init_vm)(ngx_conf_t *cf, ngx_js_loc_conf_t *conf)); +char *ngx_js_shared_dict_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf, + void *tag); njs_int_t ngx_js_ext_string(njs_vm_t *vm, njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval, njs_value_t *retval); diff --git a/nginx/ngx_js_shared_dict.c b/nginx/ngx_js_shared_dict.c new file mode 100644 index 00000000..30cbd1c3 --- /dev/null +++ b/nginx/ngx_js_shared_dict.c @@ -0,0 +1,1586 @@ + +/* + * Copyright (C) Dmitry Volyntsev + * Copyright (C) NGINX, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include "ngx_js.h" +#include "ngx_js_shared_dict.h" + + +typedef struct { + ngx_rbtree_t rbtree; + ngx_rbtree_node_t sentinel; + ngx_atomic_t rwlock; + + ngx_rbtree_t rbtree_expire; + ngx_rbtree_node_t sentinel_expire; +} ngx_js_dict_sh_t; + + +typedef struct { + ngx_str_node_t sn; + ngx_rbtree_node_t expire; + union { + ngx_str_t value; + double number; + } u; +} ngx_js_dict_node_t; + + +struct ngx_js_dict_s { + ngx_shm_zone_t *shm_zone; + ngx_js_dict_sh_t *sh; + ngx_slab_pool_t *shpool; + + ngx_msec_t timeout; + ngx_flag_t evict; +#define NGX_JS_DICT_TYPE_STRING 0 +#define NGX_JS_DICT_TYPE_NUMBER 1 + ngx_uint_t type; + + ngx_js_dict_t *next; +}; + + +static njs_int_t njs_js_ext_shared_dict_capacity(njs_vm_t *vm, + njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval, + njs_value_t *retval); +static njs_int_t njs_js_ext_shared_dict_clear(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t flags, njs_value_t *retval); +static njs_int_t njs_js_ext_shared_dict_delete(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); +static njs_int_t njs_js_ext_shared_dict_free_space(njs_vm_t *vm, + njs_value_t *args, njs_uint_t nargs, njs_index_t unused, + njs_value_t *retval); +static njs_int_t njs_js_ext_shared_dict_get(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); +static njs_int_t njs_js_ext_shared_dict_has(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); +static njs_int_t njs_js_ext_shared_dict_keys(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); +static njs_int_t njs_js_ext_shared_dict_incr(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); +static njs_int_t njs_js_ext_shared_dict_name(njs_vm_t *vm, + njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval, + njs_value_t *retval); +static njs_int_t njs_js_ext_shared_dict_pop(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); +static njs_int_t njs_js_ext_shared_dict_set(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t flags, njs_value_t *retval); +static njs_int_t njs_js_ext_shared_dict_size(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); +static njs_int_t njs_js_ext_shared_dict_type(njs_vm_t *vm, + njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval, + njs_value_t *retval); +static ngx_js_dict_node_t *ngx_js_dict_lookup(ngx_js_dict_t *dict, + njs_str_t *key); + +#define NGX_JS_DICT_FLAG_MUST_EXIST 1 +#define NGX_JS_DICT_FLAG_MUST_NOT_EXIST 2 + +static ngx_int_t ngx_js_dict_set(njs_vm_t *vm, ngx_js_dict_t *dict, + njs_str_t *key, njs_value_t *value, unsigned flags); +static ngx_int_t ngx_js_dict_add(ngx_js_dict_t *dict, njs_str_t *key, + njs_value_t *value, ngx_msec_t now); +static ngx_int_t ngx_js_dict_update(ngx_js_dict_t *dict, + ngx_js_dict_node_t *node, njs_value_t *value, ngx_msec_t now); +static ngx_int_t ngx_js_dict_get(njs_vm_t *vm, ngx_js_dict_t *dict, + njs_str_t *key, njs_value_t *retval); +static ngx_int_t ngx_js_dict_incr(ngx_js_dict_t *dict, njs_str_t *key, + njs_value_t *delta, njs_value_t *init, double *value); +static ngx_int_t ngx_js_dict_delete(njs_vm_t *vm, ngx_js_dict_t *dict, + njs_str_t *key, njs_value_t *retval); +static ngx_int_t ngx_js_dict_copy_value_locked(njs_vm_t *vm, + ngx_js_dict_t *dict, ngx_js_dict_node_t *node, njs_value_t *retval); + +static void ngx_js_dict_expire(ngx_js_dict_t *dict, ngx_msec_t now); +static void ngx_js_dict_evict(ngx_js_dict_t *dict, ngx_int_t count); + +static njs_int_t ngx_js_dict_shared_error_name(njs_vm_t *vm, + njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval, + njs_value_t *retval); + +static ngx_int_t ngx_js_dict_init_zone(ngx_shm_zone_t *shm_zone, void *data); +static njs_int_t ngx_js_shared_dict_preinit(njs_vm_t *vm); +static njs_int_t ngx_js_shared_dict_init(njs_vm_t *vm); + + +static njs_external_t ngx_js_ext_shared_dict[] = { + + { + .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL, + .name.symbol = NJS_SYMBOL_TO_STRING_TAG, + .u.property = { + .value = "SharedDict", + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("add"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = njs_js_ext_shared_dict_set, + .magic8 = NGX_JS_DICT_FLAG_MUST_NOT_EXIST, + } + }, + + { + .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("capacity"), + .enumerable = 1, + .u.property = { + .handler = njs_js_ext_shared_dict_capacity, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("clear"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = njs_js_ext_shared_dict_clear, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("freeSpace"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = njs_js_ext_shared_dict_free_space, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("delete"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = njs_js_ext_shared_dict_delete, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("incr"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = njs_js_ext_shared_dict_incr, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("get"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = njs_js_ext_shared_dict_get, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("has"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = njs_js_ext_shared_dict_has, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("keys"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = njs_js_ext_shared_dict_keys, + } + }, + + { + .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("name"), + .enumerable = 1, + .u.property = { + .handler = njs_js_ext_shared_dict_name, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("pop"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = njs_js_ext_shared_dict_pop, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("replace"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = njs_js_ext_shared_dict_set, + .magic8 = NGX_JS_DICT_FLAG_MUST_EXIST, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("set"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = njs_js_ext_shared_dict_set, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("size"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = njs_js_ext_shared_dict_size, + } + }, + + { + .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("type"), + .enumerable = 1, + .u.property = { + .handler = njs_js_ext_shared_dict_type, + } + }, + +}; + + +static njs_external_t ngx_js_ext_error_ctor_props[] = { + + { + .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("name"), + .enumerable = 1, + .u.property = { + .handler = ngx_js_dict_shared_error_name, + } + }, + + { + .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("prototype"), + .enumerable = 1, + .u.property = { + .handler = njs_object_prototype_create, + } + }, + +}; + + +static njs_external_t ngx_js_ext_error_proto_props[] = { + + { + .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("name"), + .enumerable = 1, + .u.property = { + .handler = ngx_js_dict_shared_error_name, + } + }, + + { + .flags = NJS_EXTERN_PROPERTY, + .name.string = njs_str("prototype"), + .enumerable = 1, + .u.property = { + .handler = njs_object_prototype_create, + } + }, + +}; + + +static njs_int_t ngx_js_shared_dict_proto_id; +static njs_int_t ngx_js_shared_dict_error_id; + + +njs_module_t ngx_js_shared_dict_module = { + .name = njs_str("shared_dict"), + .preinit = ngx_js_shared_dict_preinit, + .init = ngx_js_shared_dict_init, +}; + + +njs_int_t +njs_js_ext_global_shared_prop(njs_vm_t *vm, njs_object_prop_t *prop, + njs_value_t *value, njs_value_t *setval, njs_value_t *retval) +{ + njs_int_t ret; + njs_str_t name; + ngx_js_dict_t *dict; + ngx_shm_zone_t *shm_zone; + ngx_js_main_conf_t *conf; + + ret = njs_vm_prop_name(vm, prop, &name); + if (ret != NJS_OK) { + return NJS_ERROR; + } + + conf = ngx_main_conf(vm); + + for (dict = conf->dicts; dict != NULL; dict = dict->next) { + shm_zone = dict->shm_zone; + + if (shm_zone->shm.name.len == name.length + && ngx_strncmp(shm_zone->shm.name.data, name.start, + name.length) + == 0) + { + ret = njs_vm_external_create(vm, retval, + ngx_js_shared_dict_proto_id, + shm_zone, 0); + if (ret != NJS_OK) { + njs_vm_internal_error(vm, "sharedDict creation failed"); + return NJS_ERROR; + } + + return NJS_OK; + } + } + + njs_value_null_set(retval); + + return NJS_DECLINED; +} + + +njs_int_t +njs_js_ext_global_shared_keys(njs_vm_t *vm, njs_value_t *unused, + njs_value_t *keys) +{ + njs_int_t rc; + njs_value_t *value; + ngx_js_dict_t *dict; + ngx_shm_zone_t *shm_zone; + ngx_js_main_conf_t *conf; + + conf = ngx_main_conf(vm); + + rc = njs_vm_array_alloc(vm, keys, 4); + if (rc != NJS_OK) { + return NJS_ERROR; + } + + for (dict = conf->dicts; dict != NULL; dict = dict->next) { + shm_zone = dict->shm_zone; + + value = njs_vm_array_push(vm, keys); + if (value == NULL) { + return NJS_ERROR; + } + + rc = njs_vm_value_string_set(vm, value, shm_zone->shm.name.data, + shm_zone->shm.name.len); + if (rc != NJS_OK) { + return NJS_ERROR; + } + } + + return NJS_OK; +} + + +static njs_int_t +njs_js_ext_shared_dict_capacity(njs_vm_t *vm, njs_object_prop_t *prop, + njs_value_t *value, njs_value_t *setval, njs_value_t *retval) +{ + ngx_shm_zone_t *shm_zone; + + shm_zone = njs_vm_external(vm, ngx_js_shared_dict_proto_id, value); + if (shm_zone == NULL) { + njs_value_undefined_set(retval); + return NJS_DECLINED; + } + + njs_value_number_set(retval, shm_zone->shm.size); + + return NJS_OK; +} + + +static njs_int_t +njs_js_ext_shared_dict_clear(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused, njs_value_t *retval) +{ + ngx_js_dict_t *dict; + ngx_shm_zone_t *shm_zone; + + shm_zone = njs_vm_external(vm, ngx_js_shared_dict_proto_id, + njs_argument(args, 0)); + if (shm_zone == NULL) { + njs_vm_type_error(vm, "\"this\" is not a shared dict"); + return NJS_ERROR; + } + + dict = shm_zone->data; + + ngx_rwlock_wlock(&dict->sh->rwlock); + + ngx_js_dict_evict(dict, 0x7fffffff /* INT_MAX */); + + ngx_rwlock_unlock(&dict->sh->rwlock); + + njs_value_undefined_set(retval); + + return NJS_OK; +} + + +static njs_int_t +njs_js_ext_shared_dict_free_space(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) +{ + size_t bytes; + ngx_js_dict_t *dict; + ngx_shm_zone_t *shm_zone; + + shm_zone = njs_vm_external(vm, ngx_js_shared_dict_proto_id, + njs_argument(args, 0)); + if (shm_zone == NULL) { + njs_vm_type_error(vm, "\"this\" is not a shared dict"); + return NJS_ERROR; + } + + dict = shm_zone->data; + + ngx_rwlock_rlock(&dict->sh->rwlock); + bytes = dict->shpool->pfree * ngx_pagesize; + ngx_rwlock_unlock(&dict->sh->rwlock); + + njs_value_number_set(retval, bytes); + + return NJS_OK; +} + + +static njs_int_t +njs_js_ext_shared_dict_delete(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused, njs_value_t *retval) +{ + ngx_int_t rc; + njs_str_t key; + ngx_shm_zone_t *shm_zone; + + shm_zone = njs_vm_external(vm, ngx_js_shared_dict_proto_id, + njs_argument(args, 0)); + if (shm_zone == NULL) { + njs_vm_type_error(vm, "\"this\" is not a shared dict"); + return NJS_ERROR; + } + + if (ngx_js_string(vm, njs_arg(args, nargs, 1), &key) != NGX_OK) { + return NJS_ERROR; + } + + rc = ngx_js_dict_delete(vm, shm_zone->data, &key, NULL); + + njs_value_boolean_set(retval, rc == NGX_OK); + + return NJS_OK; +} + + +static njs_int_t +njs_js_ext_shared_dict_get(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused, njs_value_t *retval) +{ + ngx_int_t rc; + njs_str_t key; + ngx_shm_zone_t *shm_zone; + + shm_zone = njs_vm_external(vm, ngx_js_shared_dict_proto_id, + njs_argument(args, 0)); + if (shm_zone == NULL) { + njs_vm_type_error(vm, "\"this\" is not a shared dict"); + return NJS_ERROR; + } + + if (ngx_js_string(vm, njs_arg(args, nargs, 1), &key) != NGX_OK) { + return NJS_ERROR; + } + + rc = ngx_js_dict_get(vm, shm_zone->data, &key, retval); + if (njs_slow_path(rc == NGX_ERROR)) { + njs_vm_error(vm, "failed to get value from shared dict"); + return NJS_ERROR; + } + + return NJS_OK; +} + + +static njs_int_t +njs_js_ext_shared_dict_has(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused, njs_value_t *retval) +{ + njs_str_t key; + ngx_msec_t now; + ngx_time_t *tp; + ngx_js_dict_t *dict; + ngx_shm_zone_t *shm_zone; + ngx_js_dict_node_t *node; + + shm_zone = njs_vm_external(vm, ngx_js_shared_dict_proto_id, + njs_argument(args, 0)); + if (shm_zone == NULL) { + njs_vm_type_error(vm, "\"this\" is not a shared dict"); + return NJS_ERROR; + } + + if (ngx_js_string(vm, njs_arg(args, nargs, 1), &key) != NGX_OK) { + return NJS_ERROR; + } + + dict = shm_zone->data; + + ngx_rwlock_rlock(&dict->sh->rwlock); + + node = ngx_js_dict_lookup(dict, &key); + + if (node != NULL && dict->timeout) { + tp = ngx_timeofday(); + now = tp->sec * 1000 + tp->msec; + + if (now >= node->expire.key) { + node = NULL; + } + } + + ngx_rwlock_unlock(&dict->sh->rwlock); + + njs_value_boolean_set(retval, node != NULL); + + return NJS_OK; +} + + +static njs_int_t +njs_js_ext_shared_dict_keys(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused, njs_value_t *retval) +{ + njs_int_t rc; + ngx_int_t max_count; + njs_value_t *value; + ngx_rbtree_t *rbtree; + ngx_js_dict_t *dict; + ngx_shm_zone_t *shm_zone; + ngx_rbtree_node_t *rn; + ngx_js_dict_node_t *node; + + shm_zone = njs_vm_external(vm, ngx_js_shared_dict_proto_id, + njs_argument(args, 0)); + if (shm_zone == NULL) { + njs_vm_type_error(vm, "\"this\" is not a shared dict"); + return NJS_ERROR; + } + + dict = shm_zone->data; + + max_count = 1024; + + if (nargs > 1) { + if (ngx_js_integer(vm, njs_arg(args, nargs, 1), &max_count) != NGX_OK) { + return NJS_ERROR; + } + } + + rc = njs_vm_array_alloc(vm, retval, 8); + if (rc != NJS_OK) { + return NJS_ERROR; + } + + ngx_rwlock_rlock(&dict->sh->rwlock); + + rbtree = &dict->sh->rbtree; + + if (rbtree->root == rbtree->sentinel) { + goto done; + } + + for (rn = ngx_rbtree_min(rbtree->root, rbtree->sentinel); + rn != NULL; + rn = ngx_rbtree_next(rbtree, rn)) + { + if (max_count-- == 0) { + break; + } + + node = (ngx_js_dict_node_t *) rn; + + value = njs_vm_array_push(vm, retval); + if (value == NULL) { + goto fail; + } + + rc = njs_vm_value_string_set(vm, value, node->sn.str.data, + node->sn.str.len); + if (rc != NJS_OK) { + goto fail; + } + } + +done: + + ngx_rwlock_unlock(&dict->sh->rwlock); + + return NJS_OK; + +fail: + + ngx_rwlock_unlock(&dict->sh->rwlock); + + return NJS_ERROR; +} + + +static njs_int_t +njs_js_ext_shared_dict_incr(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused, njs_value_t *retval) +{ + double value; + ngx_int_t rc; + njs_str_t key; + njs_value_t *delta, *init; + ngx_js_dict_t *dict; + ngx_shm_zone_t *shm_zone; + njs_opaque_value_t lvalue; + + shm_zone = njs_vm_external(vm, ngx_js_shared_dict_proto_id, + njs_argument(args, 0)); + if (shm_zone == NULL) { + njs_vm_type_error(vm, "\"this\" is not a shared dict"); + return NJS_ERROR; + } + + dict = shm_zone->data; + + if (dict->type != NGX_JS_DICT_TYPE_NUMBER) { + njs_vm_type_error(vm, "shared dict is not a number dict"); + return NJS_ERROR; + } + + if (ngx_js_string(vm, njs_arg(args, nargs, 1), &key) != NGX_OK) { + return NJS_ERROR; + } + + delta = njs_arg(args, nargs, 2); + if (!njs_value_is_number(delta)) { + njs_vm_type_error(vm, "delta is not a number"); + return NJS_ERROR; + } + + init = njs_lvalue_arg(njs_value_arg(&lvalue), args, nargs, 3); + if (!njs_value_is_number(init) && !njs_value_is_undefined(init)) { + njs_vm_type_error(vm, "init value is not a number"); + return NJS_ERROR; + } + + if (njs_value_is_undefined(init)) { + njs_value_number_set(init, 0); + } + + rc = ngx_js_dict_incr(shm_zone->data, &key, delta, init, &value); + if (rc == NGX_ERROR) { + njs_vm_error(vm, "failed to increment value in shared dict"); + return NJS_ERROR; + } + + njs_value_number_set(retval, value); + + return NJS_OK; +} + + +static njs_int_t +njs_js_ext_shared_dict_name(njs_vm_t *vm, njs_object_prop_t *prop, + njs_value_t *value, njs_value_t *setval, njs_value_t *retval) +{ + ngx_shm_zone_t *shm_zone; + + shm_zone = njs_vm_external(vm, ngx_js_shared_dict_proto_id, value); + if (shm_zone == NULL) { + njs_value_undefined_set(retval); + return NJS_DECLINED; + } + + return njs_vm_value_string_set(vm, retval, shm_zone->shm.name.data, + shm_zone->shm.name.len); +} + + +static njs_int_t +njs_js_ext_shared_dict_pop(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused, njs_value_t *retval) +{ + ngx_int_t rc; + njs_str_t key; + ngx_shm_zone_t *shm_zone; + + shm_zone = njs_vm_external(vm, ngx_js_shared_dict_proto_id, + njs_argument(args, 0)); + if (shm_zone == NULL) { + njs_vm_type_error(vm, "\"this\" is not a shared dict"); + return NJS_ERROR; + } + + if (ngx_js_string(vm, njs_arg(args, nargs, 1), &key) != NGX_OK) { + return NJS_ERROR; + } + + rc = ngx_js_dict_delete(vm, shm_zone->data, &key, retval); + + if (rc == NGX_DECLINED) { + njs_value_undefined_set(retval); + } + + return (rc != NGX_ERROR) ? NJS_OK : NJS_ERROR; +} + + +static njs_int_t +njs_js_ext_shared_dict_set(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t flags, njs_value_t *retval) +{ + njs_str_t key; + ngx_int_t rc; + njs_value_t *value; + ngx_js_dict_t *dict; + ngx_shm_zone_t *shm_zone; + + shm_zone = njs_vm_external(vm, ngx_js_shared_dict_proto_id, + njs_argument(args, 0)); + if (shm_zone == NULL) { + njs_vm_type_error(vm, "\"this\" is not a shared dict"); + return NJS_ERROR; + } + + if (ngx_js_string(vm, njs_arg(args, nargs, 1), &key) != NGX_OK) { + return NJS_ERROR; + } + + dict = shm_zone->data; + value = njs_arg(args, nargs, 2); + + if (dict->type == NGX_JS_DICT_TYPE_STRING) { + if (!njs_value_is_string(value)) { + njs_vm_type_error(vm, "string value is expected"); + return NJS_ERROR; + } + + } else { + if (!njs_value_is_number(value)) { + njs_vm_type_error(vm, "number value is expected"); + return NJS_ERROR; + } + } + + rc = ngx_js_dict_set(vm, shm_zone->data, &key, value, flags); + if (rc == NGX_ERROR) { + return NJS_ERROR; + } + + if (flags) { + /* add() or replace(). */ + njs_value_boolean_set(retval, rc == NGX_OK); + + } else { + njs_value_assign(retval, njs_argument(args, 0)); + } + + return NJS_OK; +} + + +static njs_int_t +njs_js_ext_shared_dict_size(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused, njs_value_t *retval) +{ + njs_int_t items; + ngx_rbtree_t *rbtree; + ngx_js_dict_t *dict; + ngx_shm_zone_t *shm_zone; + ngx_rbtree_node_t *rn; + + shm_zone = njs_vm_external(vm, ngx_js_shared_dict_proto_id, + njs_argument(args, 0)); + if (shm_zone == NULL) { + njs_vm_type_error(vm, "\"this\" is not a shared dict"); + return NJS_ERROR; + } + + dict = shm_zone->data; + + ngx_rwlock_rlock(&dict->sh->rwlock); + + rbtree = &dict->sh->rbtree; + + if (rbtree->root == rbtree->sentinel) { + ngx_rwlock_unlock(&dict->sh->rwlock); + njs_value_number_set(retval, 0); + return NJS_OK; + } + + items = 0; + + for (rn = ngx_rbtree_min(rbtree->root, rbtree->sentinel); + rn != NULL; + rn = ngx_rbtree_next(rbtree, rn)) + { + items++; + } + + ngx_rwlock_unlock(&dict->sh->rwlock); + njs_value_number_set(retval, items); + + return NJS_OK; +} + + +static njs_int_t +njs_js_ext_shared_dict_type(njs_vm_t *vm, njs_object_prop_t *prop, + njs_value_t *value, njs_value_t *setval, njs_value_t *retval) +{ + njs_str_t type; + ngx_js_dict_t *dict; + ngx_shm_zone_t *shm_zone; + + shm_zone = njs_vm_external(vm, ngx_js_shared_dict_proto_id, value); + if (shm_zone == NULL) { + njs_value_undefined_set(retval); + return NJS_DECLINED; + } + + dict = shm_zone->data; + + switch (dict->type) { + case NGX_JS_DICT_TYPE_STRING: + type = njs_str_value("string"); + break; + + default: + type = njs_str_value("number"); + break; + } + + return njs_vm_value_string_set(vm, retval, type.start, type.length); +} + + +static ngx_js_dict_node_t * +ngx_js_dict_lookup(ngx_js_dict_t *dict, njs_str_t *key) +{ + uint32_t hash; + ngx_str_t k; + ngx_rbtree_t *rbtree; + + rbtree = &dict->sh->rbtree; + + hash = ngx_crc32_long(key->start, key->length); + + k.data = key->start; + k.len = key->length; + + return (ngx_js_dict_node_t *) ngx_str_rbtree_lookup(rbtree, &k, hash); +} + + +static void * +ngx_js_dict_alloc(ngx_js_dict_t *dict, size_t n) +{ + void *p; + + p = ngx_slab_alloc_locked(dict->shpool, n); + + if (p == NULL && dict->evict) { + ngx_js_dict_evict(dict, 16); + p = ngx_slab_alloc_locked(dict->shpool, n); + } + + return p; +} + + +static void +ngx_js_dict_node_free(ngx_js_dict_t *dict, ngx_js_dict_node_t *node) +{ + ngx_slab_pool_t *shpool; + + shpool = dict->shpool; + + if (dict->type == NGX_JS_DICT_TYPE_STRING) { + ngx_slab_free_locked(shpool, node->u.value.data); + } + + ngx_slab_free_locked(shpool, node); +} + + +static ngx_int_t +ngx_js_dict_set(njs_vm_t *vm, ngx_js_dict_t *dict, njs_str_t *key, + njs_value_t *value, unsigned flags) +{ + ngx_msec_t now; + ngx_time_t *tp; + ngx_js_dict_node_t *node; + + tp = ngx_timeofday(); + now = tp->sec * 1000 + tp->msec; + + ngx_rwlock_wlock(&dict->sh->rwlock); + + node = ngx_js_dict_lookup(dict, key); + + if (node == NULL) { + if (flags & NGX_JS_DICT_FLAG_MUST_EXIST) { + ngx_rwlock_unlock(&dict->sh->rwlock); + return NGX_DECLINED; + } + + if (ngx_js_dict_add(dict, key, value, now) != NGX_OK) { + goto memory_error; + } + + } else { + if (flags & NGX_JS_DICT_FLAG_MUST_NOT_EXIST) { + if (!dict->timeout || now < node->expire.key) { + ngx_rwlock_unlock(&dict->sh->rwlock); + return NGX_DECLINED; + } + } + + if (ngx_js_dict_update(dict, node, value, now) != NGX_OK) { + goto memory_error; + } + } + + ngx_rwlock_unlock(&dict->sh->rwlock); + + return NGX_OK; + +memory_error: + + ngx_rwlock_unlock(&dict->sh->rwlock); + + njs_vm_error3(vm, ngx_js_shared_dict_error_id, "", 0); + + return NGX_ERROR; +} + + +static ngx_int_t +ngx_js_dict_add(ngx_js_dict_t *dict, njs_str_t *key, njs_value_t *value, + ngx_msec_t now) +{ + size_t n; + uint32_t hash; + njs_str_t string; + ngx_js_dict_node_t *node; + + if (dict->timeout) { + ngx_js_dict_expire(dict, now); + } + + n = sizeof(ngx_js_dict_node_t) + key->length; + hash = ngx_crc32_long(key->start, key->length); + + node = ngx_js_dict_alloc(dict, n); + if (node == NULL) { + return NGX_ERROR; + } + + node->sn.str.data = (u_char *) node + sizeof(ngx_js_dict_node_t); + + if (dict->type == NGX_JS_DICT_TYPE_STRING) { + njs_value_string_get(value, &string); + node->u.value.data = ngx_js_dict_alloc(dict, string.length); + if (node->u.value.data == NULL) { + ngx_slab_free_locked(dict->shpool, node); + return NGX_ERROR; + } + + ngx_memcpy(node->u.value.data, string.start, string.length); + node->u.value.len = string.length; + + } else { + node->u.number = njs_value_number(value); + } + + node->sn.node.key = hash; + + ngx_memcpy(node->sn.str.data, key->start, key->length); + node->sn.str.len = key->length; + + ngx_rbtree_insert(&dict->sh->rbtree, &node->sn.node); + + if (dict->timeout) { + node->expire.key = now + dict->timeout; + ngx_rbtree_insert(&dict->sh->rbtree_expire, &node->expire); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_js_dict_update(ngx_js_dict_t *dict, ngx_js_dict_node_t *node, + njs_value_t *value, ngx_msec_t now) +{ + u_char *p; + njs_str_t string; + + if (dict->type == NGX_JS_DICT_TYPE_STRING) { + njs_value_string_get(value, &string); + + p = ngx_js_dict_alloc(dict, string.length); + if (p == NULL) { + return NGX_ERROR; + } + + ngx_slab_free_locked(dict->shpool, node->u.value.data); + ngx_memcpy(p, string.start, string.length); + + node->u.value.data = p; + node->u.value.len = string.length; + + } else { + node->u.number = njs_value_number(value); + } + + if (dict->timeout) { + ngx_rbtree_delete(&dict->sh->rbtree_expire, &node->expire); + node->expire.key = now + dict->timeout; + ngx_rbtree_insert(&dict->sh->rbtree_expire, &node->expire); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_js_dict_delete(njs_vm_t *vm, ngx_js_dict_t *dict, njs_str_t *key, + njs_value_t *retval) +{ + ngx_int_t rc; + ngx_msec_t now; + ngx_time_t *tp; + ngx_js_dict_node_t *node; + + ngx_rwlock_rlock(&dict->sh->rwlock); + + node = ngx_js_dict_lookup(dict, key); + + if (node == NULL) { + ngx_rwlock_unlock(&dict->sh->rwlock); + return NGX_DECLINED; + } + + if (dict->timeout) { + ngx_rbtree_delete(&dict->sh->rbtree_expire, &node->expire); + } + + ngx_rbtree_delete(&dict->sh->rbtree, (ngx_rbtree_node_t *) node); + + if (retval != NULL) { + tp = ngx_timeofday(); + now = tp->sec * 1000 + tp->msec; + + if (!dict->timeout || now < node->expire.key) { + rc = ngx_js_dict_copy_value_locked(vm, dict, node, retval); + + } else { + rc = NGX_DECLINED; + } + + } else { + rc = NGX_OK; + } + + ngx_js_dict_node_free(dict, node); + + ngx_rwlock_unlock(&dict->sh->rwlock); + + return rc; +} + + +static ngx_int_t +ngx_js_dict_incr(ngx_js_dict_t *dict, njs_str_t *key, njs_value_t *delta, + njs_value_t *init, double *value) +{ + ngx_msec_t now; + ngx_time_t *tp; + ngx_js_dict_node_t *node; + + tp = ngx_timeofday(); + now = tp->sec * 1000 + tp->msec; + + ngx_rwlock_wlock(&dict->sh->rwlock); + + node = ngx_js_dict_lookup(dict, key); + + if (node == NULL) { + njs_value_number_set(init, njs_value_number(init) + + njs_value_number(delta)); + if (ngx_js_dict_add(dict, key, init, now) != NGX_OK) { + ngx_rwlock_unlock(&dict->sh->rwlock); + return NGX_ERROR; + } + + *value = njs_value_number(init); + + } else { + node->u.number += njs_value_number(delta); + *value = node->u.number; + + if (dict->timeout) { + ngx_rbtree_delete(&dict->sh->rbtree_expire, &node->expire); + node->expire.key = now + dict->timeout; + ngx_rbtree_insert(&dict->sh->rbtree_expire, &node->expire); + } + } + + ngx_rwlock_unlock(&dict->sh->rwlock); + + return NGX_OK; +} + + +static ngx_int_t +ngx_js_dict_get(njs_vm_t *vm, ngx_js_dict_t *dict, njs_str_t *key, + njs_value_t *retval) +{ + ngx_int_t rc; + ngx_msec_t now; + ngx_time_t *tp; + ngx_js_dict_node_t *node; + + ngx_rwlock_rlock(&dict->sh->rwlock); + + node = ngx_js_dict_lookup(dict, key); + + if (node == NULL) { + goto not_found; + } + + if (dict->timeout) { + tp = ngx_timeofday(); + now = tp->sec * 1000 + tp->msec; + + if (now >= node->expire.key) { + goto not_found; + } + } + + rc = ngx_js_dict_copy_value_locked(vm, dict, node, retval); + ngx_rwlock_unlock(&dict->sh->rwlock); + + return rc; + +not_found: + + ngx_rwlock_unlock(&dict->sh->rwlock); + njs_value_undefined_set(retval); + + return NGX_OK; +} + + +static ngx_int_t +ngx_js_dict_copy_value_locked(njs_vm_t *vm, ngx_js_dict_t *dict, + ngx_js_dict_node_t *node, njs_value_t *retval) +{ + njs_int_t ret; + njs_str_t string; + ngx_uint_t type; + ngx_pool_t *pool; + + type = dict->type; + + if (type == NGX_JS_DICT_TYPE_STRING) { + pool = ngx_external_pool(vm, njs_vm_external_ptr(vm)); + + string.length = node->u.value.len; + string.start = ngx_pstrdup(pool, &node->u.value); + if (string.start == NULL) { + return NGX_ERROR; + } + + ret = njs_vm_value_string_set(vm, retval, string.start, string.length); + if (ret != NJS_OK) { + return NGX_ERROR; + } + + } else { + njs_value_number_set(retval, node->u.number); + } + + return NGX_OK; +} + + +static void +ngx_js_dict_expire(ngx_js_dict_t *dict, ngx_msec_t now) +{ + ngx_rbtree_t *rbtree; + ngx_rbtree_node_t *rn, *next; + ngx_js_dict_node_t *node; + + rbtree = &dict->sh->rbtree_expire; + + if (rbtree->root == rbtree->sentinel) { + return; + } + + for (rn = ngx_rbtree_min(rbtree->root, rbtree->sentinel); + rn != NULL; + rn = next) + { + if (rn->key > now) { + return; + } + + node = (ngx_js_dict_node_t *) + ((char *) rn - offsetof(ngx_js_dict_node_t, expire)); + + next = ngx_rbtree_next(rbtree, rn); + + ngx_rbtree_delete(rbtree, rn); + + ngx_rbtree_delete(&dict->sh->rbtree, (ngx_rbtree_node_t *) node); + + ngx_js_dict_node_free(dict, node); + } +} + + +static void +ngx_js_dict_evict(ngx_js_dict_t *dict, ngx_int_t count) +{ + ngx_rbtree_t *rbtree; + ngx_rbtree_node_t *rn, *next; + ngx_js_dict_node_t *node; + + rbtree = &dict->sh->rbtree_expire; + + if (rbtree->root == rbtree->sentinel) { + return; + } + + for (rn = ngx_rbtree_min(rbtree->root, rbtree->sentinel); + rn != NULL; + rn = next) + { + if (count-- == 0) { + return; + } + + node = (ngx_js_dict_node_t *) + ((char *) rn - offsetof(ngx_js_dict_node_t, expire)); + + next = ngx_rbtree_next(rbtree, rn); + + ngx_rbtree_delete(rbtree, rn); + + ngx_rbtree_delete(&dict->sh->rbtree, (ngx_rbtree_node_t *) node); + + ngx_js_dict_node_free(dict, node); + } +} + + +static njs_int_t +ngx_js_dict_shared_error_name(njs_vm_t *vm, njs_object_prop_t *prop, + njs_value_t *value, njs_value_t *setval, njs_value_t *retval) +{ + return njs_vm_value_string_set(vm, retval, (u_char *) "SharedMemoryError", + 17); +} + + +static ngx_int_t +ngx_js_dict_init_zone(ngx_shm_zone_t *shm_zone, void *data) +{ + ngx_js_dict_t *prev = data; + + size_t len; + ngx_js_dict_t *dict; + + dict = shm_zone->data; + + if (prev) { + + if (dict->timeout && !prev->timeout) { + ngx_log_error(NGX_LOG_EMERG, shm_zone->shm.log, 0, + "js_shared_dict_zone \"%V\" uses timeout %M " + "while previously it did not use timeout", + &shm_zone->shm.name, dict->timeout); + return NGX_ERROR; + } + + if (dict->type != prev->type) { + ngx_log_error(NGX_LOG_EMERG, shm_zone->shm.log, 0, + "js_shared_dict_zone \"%V\" had previously a " + "different type", &shm_zone->shm.name, dict->timeout); + return NGX_ERROR; + } + + dict->sh = prev->sh; + dict->shpool = prev->shpool; + + return NGX_OK; + } + + dict->shpool = (ngx_slab_pool_t *) shm_zone->shm.addr; + + if (shm_zone->shm.exists) { + dict->sh = dict->shpool->data; + return NGX_OK; + } + + dict->sh = ngx_slab_calloc(dict->shpool, sizeof(ngx_js_dict_sh_t)); + if (dict->sh == NULL) { + return NGX_ERROR; + } + + dict->shpool->data = dict->sh; + + ngx_rbtree_init(&dict->sh->rbtree, &dict->sh->sentinel, + ngx_str_rbtree_insert_value); + + if (dict->timeout) { + ngx_rbtree_init(&dict->sh->rbtree_expire, + &dict->sh->sentinel_expire, + ngx_rbtree_insert_timer_value); + } + + len = sizeof(" in js shared dict zone \"\"") + shm_zone->shm.name.len; + + dict->shpool->log_ctx = ngx_slab_alloc(dict->shpool, len); + if (dict->shpool->log_ctx == NULL) { + return NGX_ERROR; + } + + ngx_sprintf(dict->shpool->log_ctx, " in js shared zone \"%V\"%Z", + &shm_zone->shm.name); + + return NGX_OK; +} + + +char * +ngx_js_shared_dict_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf, + void *tag) +{ + ngx_js_main_conf_t *jmcf = conf; + + u_char *p; + ssize_t size; + ngx_str_t *value, name, s; + ngx_flag_t evict; + ngx_msec_t timeout; + ngx_uint_t i, type; + ngx_js_dict_t *dict; + ngx_shm_zone_t *shm_zone; + + size = 0; + evict = 0; + timeout = 0; + name.len = 0; + type = NGX_JS_DICT_TYPE_STRING; + + value = cf->args->elts; + + for (i = 1; i < cf->args->nelts; i++) { + + if (ngx_strncmp(value[i].data, "zone=", 5) == 0) { + + name.data = value[i].data + 5; + + p = (u_char *) ngx_strchr(name.data, ':'); + + if (p == NULL) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid zone size \"%V\"", &value[i]); + return NGX_CONF_ERROR; + } + + name.len = p - name.data; + + if (name.len == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid zone name \"%V\"", &value[i]); + return NGX_CONF_ERROR; + } + + s.data = p + 1; + s.len = value[i].data + value[i].len - s.data; + + size = ngx_parse_size(&s); + + if (size == NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid zone size \"%V\"", &value[i]); + return NGX_CONF_ERROR; + } + + if (size < (ssize_t) (8 * ngx_pagesize)) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "zone \"%V\" is too small", &value[i]); + return NGX_CONF_ERROR; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "evict", 5) == 0) { + evict = 1; + continue; + } + + if (ngx_strncmp(value[i].data, "timeout=", 8) == 0) { + + s.data = value[i].data + 8; + s.len = value[i].len - 8; + + timeout = ngx_parse_time(&s, 0); + if (timeout == (ngx_msec_t) NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid timeout value \"%V\"", &value[i]); + return NGX_CONF_ERROR; + } + + continue; + } + + if (ngx_strncmp(value[i].data, "type=", 5) == 0) { + + if (ngx_strcmp(&value[i].data[5], "string") == 0) { + type = NGX_JS_DICT_TYPE_STRING; + + } else if (ngx_strcmp(&value[i].data[5], "number") == 0) { + type = NGX_JS_DICT_TYPE_NUMBER; + + } else { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid dict type \"%s\"", + &value[i].data[5]); + return NGX_CONF_ERROR; + } + + continue; + } + } + + if (name.len == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"%V\" must have \"zone\" parameter", &cmd->name); + return NGX_CONF_ERROR; + } + + if (evict && timeout == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "evict requires timeout="); + return NGX_CONF_ERROR; + } + + shm_zone = ngx_shared_memory_add(cf, &name, size, tag); + if (shm_zone == NULL) { + return NGX_CONF_ERROR; + } + + if (shm_zone->data) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "duplicate zone \"%V\"", + &name); + return NGX_CONF_ERROR; + } + + dict = ngx_pcalloc(cf->pool, sizeof(ngx_js_dict_t)); + if (dict == NULL) { + return NGX_CONF_ERROR; + } + + dict->shm_zone = shm_zone; + dict->next = jmcf->dicts; + jmcf->dicts = dict; + + shm_zone->data = dict; + shm_zone->init = ngx_js_dict_init_zone; + + dict->evict = evict; + dict->timeout = timeout; + dict->type = type; + + return NGX_CONF_OK; +} + + +static njs_int_t +ngx_js_shared_dict_preinit(njs_vm_t *vm) +{ + static const njs_str_t error_name = njs_str("SharedMemoryError"); + + ngx_js_shared_dict_error_id = + njs_vm_external_constructor(vm, &error_name, + njs_error_constructor, ngx_js_ext_error_ctor_props, + njs_nitems(ngx_js_ext_error_ctor_props), + ngx_js_ext_error_proto_props, + njs_nitems(ngx_js_ext_error_ctor_props)); + if (njs_slow_path(ngx_js_shared_dict_error_id < 0)) { + return NJS_ERROR; + } + + return NJS_OK; +} + + +static njs_int_t +ngx_js_shared_dict_init(njs_vm_t *vm) +{ + ngx_js_shared_dict_proto_id = njs_vm_external_prototype(vm, + ngx_js_ext_shared_dict, + njs_nitems(ngx_js_ext_shared_dict)); + if (ngx_js_shared_dict_proto_id < 0) { + return NJS_ERROR; + } + + return NJS_OK; +} diff --git a/nginx/ngx_js_shared_dict.h b/nginx/ngx_js_shared_dict.h new file mode 100644 index 00000000..4110c6cd --- /dev/null +++ b/nginx/ngx_js_shared_dict.h @@ -0,0 +1,19 @@ + +/* + * Copyright (C) Dmitry Volyntsev + * Copyright (C) NGINX, Inc. + */ + + +#ifndef _NGX_JS_SHARED_DICT_H_INCLUDED_ +#define _NGX_JS_SHARED_DICT_H_INCLUDED_ + +njs_int_t njs_js_ext_global_shared_prop(njs_vm_t *vm, njs_object_prop_t *prop, + njs_value_t *value, njs_value_t *setval, njs_value_t *retval); +njs_int_t njs_js_ext_global_shared_keys(njs_vm_t *vm, njs_value_t *value, + njs_value_t *keys); + +extern njs_module_t ngx_js_shared_dict_module; + + +#endif /* _NGX_JS_FETCH_H_INCLUDED_ */ diff --git a/nginx/ngx_stream_js_module.c b/nginx/ngx_stream_js_module.c index 3e2a2dab..b9bba7d6 100644 --- a/nginx/ngx_stream_js_module.c +++ b/nginx/ngx_stream_js_module.c @@ -122,9 +122,12 @@ static char *ngx_stream_js_var(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_int_t ngx_stream_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf); +static void *ngx_stream_js_create_main_conf(ngx_conf_t *cf); static void *ngx_stream_js_create_srv_conf(ngx_conf_t *cf); static char *ngx_stream_js_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child); +static char *ngx_stream_js_shared_dict_zone(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); static ngx_ssl_t *ngx_stream_js_ssl(njs_vm_t *vm, ngx_stream_session_t *s); static ngx_flag_t ngx_stream_js_ssl_verify(njs_vm_t *vm, @@ -260,6 +263,13 @@ static ngx_command_t ngx_stream_js_commands[] = { #endif + { ngx_string("js_shared_dict_zone"), + NGX_STREAM_MAIN_CONF|NGX_CONF_1MORE, + ngx_stream_js_shared_dict_zone, + 0, + 0, + NULL }, + ngx_null_command }; @@ -268,7 +278,7 @@ static ngx_stream_module_t ngx_stream_js_module_ctx = { NULL, /* preconfiguration */ ngx_stream_js_init, /* postconfiguration */ - NULL, /* create main configuration */ + ngx_stream_js_create_main_conf, /* create main configuration */ NULL, /* init main configuration */ ngx_stream_js_create_srv_conf, /* create server configuration */ @@ -550,6 +560,7 @@ static uintptr_t ngx_stream_js_uptr[] = { (uintptr_t) ngx_stream_js_fetch_timeout, (uintptr_t) ngx_stream_js_buffer_size, (uintptr_t) ngx_stream_js_max_response_buffer_size, + (uintptr_t) 0 /* main_conf ptr */, }; @@ -580,6 +591,7 @@ njs_module_t *njs_stream_js_addon_modules[] = { */ &ngx_js_ngx_module, &ngx_js_fetch_module, + &ngx_js_shared_dict_module, #ifdef NJS_HAVE_OPENSSL &njs_webcrypto_module, #endif @@ -1722,10 +1734,14 @@ ngx_js_stream_init(njs_vm_t *vm) static ngx_int_t ngx_stream_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf) { - njs_vm_opt_t options; + njs_vm_opt_t options; + ngx_js_main_conf_t *jmcf; njs_vm_opt_init(&options); + jmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_js_module); + ngx_stream_js_uptr[NGX_JS_MAIN_CONF_INDEX] = (uintptr_t) jmcf; + options.backtrace = 1; options.unhandled_rejection = NJS_VM_OPT_UNHANDLED_REJECTION_THROW; options.ops = &ngx_stream_js_ops; @@ -1831,6 +1847,26 @@ ngx_stream_js_var(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) static void * +ngx_stream_js_create_main_conf(ngx_conf_t *cf) +{ + ngx_js_main_conf_t *jmcf; + + jmcf = ngx_pcalloc(cf->pool, sizeof(ngx_js_main_conf_t)); + if (jmcf == NULL) { + return NULL; + } + + /* + * set by ngx_pcalloc(): + * + * jmcf->dicts = NULL; + */ + + return jmcf; +} + + +static void * ngx_stream_js_create_srv_conf(ngx_conf_t *cf) { ngx_stream_js_srv_conf_t *conf = @@ -1862,6 +1898,14 @@ ngx_stream_js_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) } +static char * +ngx_stream_js_shared_dict_zone(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ + return ngx_js_shared_dict_zone(cf, cmd, conf, &ngx_stream_js_module); +} + + static ngx_int_t ngx_stream_js_init(ngx_conf_t *cf) { diff --git a/nginx/t/js_shared_dict.t b/nginx/t/js_shared_dict.t new file mode 100644 index 00000000..867b2add --- /dev/null +++ b/nginx/t/js_shared_dict.t @@ -0,0 +1,299 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for js_shared_dict_zone directive. + +############################################################################### + +use warnings; +use strict; + +use Test::More; +use Socket qw/ CRLF /; + +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/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + js_shared_dict_zone zone=foo:32k timeout=2s evict; + js_shared_dict_zone zone=bar:64k type=string; + js_shared_dict_zone zone=waka:32k type=number; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /add { + js_content test.add; + } + + location /capacity { + js_content test.capacity; + } + + location /chain { + js_content test.chain; + } + + location /clear { + js_content test.clear; + } + + location /delete { + js_content test.del; + } + + location /free_space { + js_content test.free_space; + } + + location /get { + js_content test.get; + } + + location /has { + js_content test.has; + } + + location /incr { + js_content test.incr; + } + + location /keys { + js_content test.keys; + } + + location /name { + js_content test.name; + } + + location /pop { + js_content test.pop; + } + + location /replace { + js_content test.replace; + } + + location /set { + js_content test.set; + } + + location /size { + js_content test.size; + } + + location /zones { + js_content test.zones; + } + } +} + +EOF + +$t->write_file('test.js', <<'EOF'); + function convertToValue(dict, v) { + if (dict.type == 'number') { + return parseInt(v); + + } else if (v == 'empty') { + v = ''; + } + + return v; + } + + function add(r) { + var dict = ngx.shared[r.args.dict]; + var value = convertToValue(dict, r.args.value); + r.return(200, dict.add(r.args.key, value)); + } + + function capacity(r) { + var dict = ngx.shared[r.args.dict]; + r.return(200, dict.capacity); + } + + function chain(r) { + var dict = ngx.shared[r.args.dict]; + var val = dict.set(r.args.key, r.args.value).get(r.args.key); + r.return(200, val); + } + + function clear(r) { + var dict = ngx.shared[r.args.dict]; + var result = dict.clear(); + r.return(200, result === undefined ? 'undefined' : result); + } + + function del(r) { + var dict = ngx.shared[r.args.dict]; + r.return(200, dict.delete(r.args.key)); + } + + function free_space(r) { + var dict = ngx.shared[r.args.dict]; + var free_space = dict.freeSpace(); + + r.return(200, free_space >= 0 && free_space <= dict.capacity); + } + + function get(r) { + var dict = ngx.shared[r.args.dict]; + var val = dict.get(r.args.key); + + if (val == '') { + val = 'empty'; + + } else if (val === undefined) { + val = 'undefined'; + } + + r.return(200, val); + } + + function has(r) { + var dict = ngx.shared[r.args.dict]; + r.return(200, dict.has(r.args.key)); + } + + function incr(r) { + var dict = ngx.shared[r.args.dict]; + var val = dict.incr(r.args.key, parseInt(r.args.by), + parseInt(r.args.def)); + r.return(200, val); + } + + function keys(r) { + var ks; + + if (r.args.max) { + ks = ngx.shared[r.args.dict].keys(parseInt(r.args.max)); + + } else { + ks = ngx.shared[r.args.dict].keys(); + } + + r.return(200, ks.toSorted()); + } + + function name(r) { + r.return(200, ngx.shared[r.args.dict].name); + } + + function replace(r) { + var dict = ngx.shared[r.args.dict]; + var value = convertToValue(dict, r.args.value); + r.return(200, dict.replace(r.args.key, value)); + } + + function pop(r) { + var dict = ngx.shared[r.args.dict]; + var val = dict.pop(r.args.key); + if (val == '') { + val = 'empty'; + + } else if (val === undefined) { + val = 'undefined'; + } + + r.return(200, val); + } + + function set(r) { + var dict = ngx.shared[r.args.dict]; + var value = convertToValue(dict, r.args.value); + r.return(200, dict.set(r.args.key, value) === dict); + } + + function size(r) { + var dict = ngx.shared[r.args.dict]; + r.return(200, `size: ${dict.size()}`); + } + + function zones(r) { + r.return(200, Object.keys(ngx.shared).sort()); + } + + export default { add, capacity, chain, clear, del, free_space, get, has, + incr, keys, name, pop, replace, set, size, zones }; +EOF + +$t->try_run('no js_shared_dict_zone')->plan(38); + +############################################################################### + +like(http_get('/zones'), qr/bar,foo/, 'available zones'); +like(http_get('/capacity?dict=foo'), qr/32768/, 'foo capacity'); +like(http_get('/capacity?dict=bar'), qr/65536/, 'bar capacity'); +like(http_get('/free_space?dict=foo'), qr/true/, 'foo free space'); +like(http_get('/name?dict=foo'), qr/foo/, 'foo name'); +like(http_get('/size?dict=foo'), qr/size: 0/, 'no of items in foo'); + +like(http_get('/add?dict=foo&key=FOO&value=xxx'), qr/true/, 'add foo.FOO'); +like(http_get('/add?dict=foo&key=FOO&value=xxx'), qr/false/, + 'failed add foo.FOO'); +like(http_get('/set?dict=foo&key=FOO2&value=yyy'), qr/true/, 'set foo.FOO2'); +like(http_get('/set?dict=foo&key=FOO3&value=empty'), qr/true/, 'set foo.FOO3'); +like(http_get('/set?dict=bar&key=FOO&value=zzz'), qr/true/, 'set bar.FOO'); +like(http_get('/set?dict=waka&key=FOO&value=42'), qr/true/, 'set waka.FOO'); +like(http_get('/chain?dict=bar&key=FOO2&value=aaa'), qr/aaa/, 'chain bar.FOO2'); + +like(http_get('/incr?dict=waka&key=FOO&by=5'), qr/47/, 'incr waka.FOO'); +like(http_get('/incr?dict=waka&key=FOO2&by=1'), qr/1/, 'incr waka.FOO2'); +like(http_get('/incr?dict=waka&key=FOO2&by=2'), qr/3/, 'incr waka.FOO2'); +like(http_get('/incr?dict=waka&key=FOO3&by=3&def=5'), qr/8/, 'incr waka.FOO3'); + +like(http_get('/has?dict=foo&key=FOO'), qr/true/, 'has foo.FOO'); +like(http_get('/has?dict=foo&key=NOT_EXISTING'), qr/false/, + 'failed has foo.NOT_EXISTING'); +like(http_get('/has?dict=waka&key=FOO'), qr/true/, 'has waka.FOO'); + +$t->reload(); + +like(http_get('/keys?dict=foo'), qr/FOO\,FOO2\,FOO3/, 'foo keys'); +like(http_get('/keys?dict=foo&max=2'), qr/FOO\,FOO3/, 'foo keys max 2'); +like(http_get('/get?dict=foo&key=FOO2'), qr/yyy/, 'get foo.FOO2'); +like(http_get('/get?dict=bar&key=FOO'), qr/zzz/, 'get bar.FOO'); +like(http_get('/get?dict=foo&key=FOO'), qr/xxx/, 'get foo.FOO'); +like(http_get('/get?dict=waka&key=FOO'), qr/47/, 'get waka.FOO'); +like(http_get('/delete?dict=foo&key=FOO'), qr/true/, 'delete foo.FOO'); +like(http_get('/get?dict=foo&key=FOO'), qr/undefined/, 'get foo.FOO'); +like(http_get('/get?dict=foo&key=FOO3'), qr/empty/, 'get foo.FOO3'); +like(http_get('/replace?dict=foo&key=FOO2&value=aaa'), qr/true/, + 'replace foo.FOO2'); +like(http_get('/replace?dict=foo&key=NOT_EXISTING&value=aaa'), qr/false/, + 'failed replace foo.NOT_EXISTING'); + +select undef, undef, undef, 2.1; + +like(http_get('/get?dict=foo&key=FOO'), qr/undefined/, 'get expired foo.FOO'); +like(http_get('/pop?dict=foo&key=FOO'), qr/undefined/, 'pop expired foo.FOO'); +like(http_get('/size?dict=foo'), qr/size: 2/, 'no of items in foo'); +like(http_get('/pop?dict=bar&key=FOO'), qr/zzz/, 'pop bar.FOO'); +like(http_get('/pop?dict=bar&key=FOO'), qr/undefined/, 'pop deleted bar.FOO'); +like(http_get('/clear?dict=foo'), qr/undefined/, 'clear foo'); +like(http_get('/size?dict=foo'), qr/size: 0/, 'no of items in foo after clear'); diff --git a/nginx/t/stream_js_shared_dict.t b/nginx/t/stream_js_shared_dict.t new file mode 100644 index 00000000..e8e482f4 --- /dev/null +++ b/nginx/t/stream_js_shared_dict.t @@ -0,0 +1,188 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for js_shared_dict_zone directive. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::Stream qw/ stream /; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http rewrite stream/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location / { + return 200; + } + } +} + +stream { + %%TEST_GLOBALS_STREAM%% + + js_import test.js; + + js_shared_dict_zone zone=foo:32k; + + server { + listen 127.0.0.1:8081; + js_preread test.preread_verify; + proxy_pass 127.0.0.1:8090; + } + + server { + listen 127.0.0.1:8082; + js_preread test.control_access; + proxy_pass 127.0.0.1:8080; + } +} + +EOF + +$t->write_file('test.js', <<EOF); + import qs from 'querystring'; + + function preread_verify(s) { + var collect = Buffer.from([]); + + s.on('upstream', function (data, flags) { + collect = Buffer.concat([collect, data]); + + if (collect.length >= 4 && collect.readUInt16BE(0) == 0xabcd) { + let id = collect.slice(2,4); + + ngx.shared.foo.get(id) ? s.done(): s.deny(); + + } else if (collect.length) { + s.deny(); + } + }); + } + + function control_access(s) { + var req = ''; + + s.on('upload', function(data, flags) { + req += data; + + var n = req.search('\\n'); + if (n != -1) { + var params = req.substr(0, n).split(' ')[1].split('?')[1]; + + var args = qs.parse(params); + switch (args.action) { + case 'set': + ngx.shared.foo.set(args.key, args.value); + break; + case 'del': + ngx.shared.foo.delete(args.key); + break; + } + + s.done(); + } + }); + } + + export default { preread_verify, control_access }; + +EOF + +$t->try_run('no js_shared_dict_zone')->plan(9); + +$t->run_daemon(\&stream_daemon, port(8090)); +$t->waitforsocket('127.0.0.1:' . port(8090)); + +############################################################################### + +is(stream('127.0.0.1:' . port(8081))->io("\xAB\xCDQZ##"), "", + 'access failed, QZ is not in the shared dict'); +is(stream('127.0.0.1:' . port(8081))->io("\xAB\xCDQQ##"), "", + 'access failed, QQ is not in the shared dict'); +like(get('/?action=set&key=QZ&value=1'), qr/200/, 'set foo.QZ'); +is(stream('127.0.0.1:' . port(8081))->io("\xAB\xCDQZ##"), "\xAB\xCDQZ##", + 'access granted'); +is(stream('127.0.0.1:' . port(8081))->io("\xAB\xCDQQ##"), "", + 'access failed, QQ is not in the shared dict'); +like(get('/?action=del&key=QZ'), qr/200/, 'del foo.QZ'); +like(get('/?action=set&key=QQ&value=1'), qr/200/, 'set foo.QQ'); +is(stream('127.0.0.1:' . port(8081))->io("\xAB\xCDQZ##"), "", + 'access failed, QZ is not in the shared dict'); +is(stream('127.0.0.1:' . port(8081))->io("\xAB\xCDQQ##"), "\xAB\xCDQQ##", + 'access granted'); + +############################################################################### + +sub stream_daemon { + my $server = IO::Socket::INET->new( + Proto => 'tcp', + LocalAddr => '127.0.0.1:' . port(8090), + Listen => 5, + Reuse => 1 + ) + or die "Can't create listening socket: $!\n"; + + local $SIG{PIPE} = 'IGNORE'; + + while (my $client = $server->accept()) { + $client->autoflush(1); + + log2c("(new connection $client)"); + + $client->sysread(my $buffer, 65536) or next; + + log2i("$client $buffer"); + + log2o("$client $buffer"); + + $client->syswrite($buffer); + + close $client; + } +} + +sub log2i { Test::Nginx::log_core('|| <<', @_); } +sub log2o { Test::Nginx::log_core('|| >>', @_); } +sub log2c { Test::Nginx::log_core('||', @_); } + +sub get { + my ($url, %extra) = @_; + + my $s = IO::Socket::INET->new( + Proto => 'tcp', + PeerAddr => '127.0.0.1:' . port(8082) + ) or die "Can't connect to nginx: $!\n"; + + return http_get($url, socket => $s); +} + +############################################################################### |