[njs] Modules: added js_import directive.

Dmitry Volyntsev xeioex at nginx.com
Tue Apr 14 12:41:34 UTC 2020


details:   https://hg.nginx.org/njs/rev/777ed1eb1918
branches:  
changeset: 1371:777ed1eb1918
user:      Dmitry Volyntsev <xeioex at nginx.com>
date:      Tue Apr 14 12:18:25 2020 +0000
description:
Modules: added js_import directive.

diffstat:

 nginx/ngx_http_js_module.c   |  352 +++++++++++++++++++++++++++++++++-------
 nginx/ngx_stream_js_module.c |  361 ++++++++++++++++++++++++++++++++++--------
 2 files changed, 573 insertions(+), 140 deletions(-)

diffs (truncated from 1010 to 1000 lines):

diff -r a46c221089c0 -r 777ed1eb1918 nginx/ngx_http_js_module.c
--- a/nginx/ngx_http_js_module.c	Sun Apr 12 12:56:25 2020 +0000
+++ b/nginx/ngx_http_js_module.c	Tue Apr 14 12:18:25 2020 +0000
@@ -1,6 +1,7 @@
 
 /*
  * Copyright (C) Roman Arutyunyan
+ * Copyright (C) Dmitry Volyntsev
  * Copyright (C) NGINX, Inc.
  */
 
@@ -14,6 +15,10 @@
 
 typedef struct {
     njs_vm_t              *vm;
+    ngx_str_t              include;
+    u_char                *file;
+    ngx_uint_t             line;
+    ngx_array_t           *imports;
     ngx_array_t           *paths;
     njs_external_proto_t   req_proto;
 } ngx_http_js_main_conf_t;
@@ -25,6 +30,14 @@ typedef struct {
 
 
 typedef struct {
+    ngx_str_t              name;
+    ngx_str_t              path;
+    u_char                *file;
+    ngx_uint_t             line;
+} ngx_http_js_import_t;
+
+
+typedef struct {
     njs_vm_t              *vm;
     ngx_log_t             *log;
     ngx_uint_t             done;
@@ -131,10 +144,13 @@ static njs_int_t ngx_http_js_string(njs_
 
 static char *ngx_http_js_include(ngx_conf_t *cf, ngx_command_t *cmd,
     void *conf);
+static char *ngx_http_js_import(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 char *ngx_http_js_content(ngx_conf_t *cf, ngx_command_t *cmd,
     void *conf);
 static void *ngx_http_js_create_main_conf(ngx_conf_t *cf);
+static char *ngx_http_js_init_main_conf(ngx_conf_t *cf, void *conf);
 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);
@@ -146,6 +162,13 @@ static ngx_command_t  ngx_http_js_comman
       NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
       ngx_http_js_include,
       NGX_HTTP_MAIN_CONF_OFFSET,
+      offsetof(ngx_http_js_main_conf_t, include),
+      NULL },
+
+    { ngx_string("js_import"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE13,
+      ngx_http_js_import,
+      NGX_HTTP_MAIN_CONF_OFFSET,
       0,
       NULL },
 
@@ -179,7 +202,7 @@ static ngx_http_module_t  ngx_http_js_mo
     NULL,                          /* postconfiguration */
 
     ngx_http_js_create_main_conf,  /* create main configuration */
-    NULL,                          /* init main configuration */
+    ngx_http_js_init_main_conf,    /* init main configuration */
 
     NULL,                          /* create server configuration */
     NULL,                          /* merge server configuration */
@@ -2328,82 +2351,113 @@ ngx_http_js_string(njs_vm_t *vm, njs_val
 
 
 static char *
-ngx_http_js_include(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+ngx_http_js_init_main_conf(ngx_conf_t *cf, void *conf)
 {
     ngx_http_js_main_conf_t *jmcf = conf;
 
     size_t                 size;
-    u_char                *start, *end;
+    u_char                *start, *end, *p;
     ssize_t                n;
     ngx_fd_t               fd;
-    ngx_str_t             *m, *value, file;
+    ngx_str_t             *m, file;
     njs_int_t              rc;
     njs_str_t              text, path;
     ngx_uint_t             i;
+    njs_value_t           *value;
     njs_vm_opt_t           options;
     ngx_file_info_t        fi;
     ngx_pool_cleanup_t    *cln;
+    njs_opaque_value_t     lvalue, exception;
     njs_external_proto_t   proto;
-
-    if (jmcf->vm) {
-        return "is duplicate";
-    }
-
-    value = cf->args->elts;
-    file = value[1];
-
-    if (ngx_conf_full_name(cf->cycle, &file, 1) != NGX_OK) {
-        return NGX_CONF_ERROR;
+    ngx_http_js_import_t  *import;
+
+    static const njs_str_t line_number_key = njs_str("lineNumber");
+    static const njs_str_t file_name_key = njs_str("fileName");
+
+    if (jmcf->include.len == 0 && jmcf->imports == NGX_CONF_UNSET_PTR) {
+        return NGX_CONF_OK;
     }
 
-    fd = ngx_open_file(file.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);
-    if (fd == NGX_INVALID_FILE) {
-        ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
-                           ngx_open_file_n " \"%s\" failed", file.data);
-        return NGX_CONF_ERROR;
+    size = 0;
+    fd = NGX_INVALID_FILE;
+
+    if (jmcf->include.len != 0) {
+        file = jmcf->include;
+
+        if (ngx_conf_full_name(cf->cycle, &file, 1) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+
+        fd = ngx_open_file(file.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);
+        if (fd == NGX_INVALID_FILE) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno,
+                          ngx_open_file_n " \"%s\" failed", file.data);
+            return NGX_CONF_ERROR;
+        }
+
+        if (ngx_fd_info(fd, &fi) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno,
+                          ngx_fd_info_n " \"%s\" failed", file.data);
+            (void) ngx_close_file(fd);
+            return NGX_CONF_ERROR;
+        }
+
+        size = ngx_file_size(&fi);
+
+    } else {
+        import = jmcf->imports->elts;
+        for (i = 0; i < jmcf->imports->nelts; i++) {
+            size += sizeof("import  from '';\n") - 1 + import[i].name.len
+                    + import[i].path.len;
+        }
     }
 
-    if (ngx_fd_info(fd, &fi) == NGX_FILE_ERROR) {
-        ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
-                           ngx_fd_info_n " \"%s\" failed", file.data);
-        (void) ngx_close_file(fd);
-        return NGX_CONF_ERROR;
-    }
-
-    size = ngx_file_size(&fi);
-
     start = ngx_pnalloc(cf->pool, size);
     if (start == NULL) {
-        (void) ngx_close_file(fd);
-        return NGX_CONF_ERROR;
-    }
-
-    n = ngx_read_fd(fd, start,  size);
-
-    if (n == -1) {
-        ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
-                           ngx_read_fd_n " \"%s\" failed", file.data);
-
-        (void) ngx_close_file(fd);
+        if (fd != NGX_INVALID_FILE) {
+            (void) ngx_close_file(fd);
+        }
+
         return NGX_CONF_ERROR;
     }
 
-    if ((size_t) n != size) {
-        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
-                           ngx_read_fd_n " has read only %z of %O from \"%s\"",
-                           n, size, file.data);
-
-        (void) ngx_close_file(fd);
-        return NGX_CONF_ERROR;
+    if (jmcf->include.len != 0) {
+        n = ngx_read_fd(fd, start, size);
+
+        if (n == -1) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno,
+                          ngx_read_fd_n " \"%s\" failed", file.data);
+
+            (void) ngx_close_file(fd);
+            return NGX_CONF_ERROR;
+        }
+
+        if ((size_t) n != size) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                          ngx_read_fd_n " has read only %z "
+                          "of %O from \"%s\"", n, size, file.data);
+
+            (void) ngx_close_file(fd);
+            return NGX_CONF_ERROR;
+        }
+
+        if (ngx_close_file(fd) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno,
+                          ngx_close_file_n " %s failed", file.data);
+        }
+
+    } else {
+        p = start;
+        import = jmcf->imports->elts;
+        for (i = 0; i < jmcf->imports->nelts; i++) {
+            p = ngx_cpymem(p, "import ", sizeof("import ") - 1);
+            p = ngx_cpymem(p, import[i].name.data, import[i].name.len);
+            p = ngx_cpymem(p, " from '", sizeof(" from '") - 1);
+            p = ngx_cpymem(p, import[i].path.data, import[i].path.len);
+            p = ngx_cpymem(p, "';\n", sizeof("';\n") - 1);
+        }
     }
 
-    if (ngx_close_file(fd) == NGX_FILE_ERROR) {
-        ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
-                           ngx_close_file_n " %s failed", file.data);
-    }
-
-    end = start + size;
-
     njs_vm_opt_init(&options);
 
     options.backtrace = 1;
@@ -2411,13 +2465,19 @@ ngx_http_js_include(ngx_conf_t *cf, ngx_
     options.argv = ngx_argv;
     options.argc = ngx_argc;
 
-    file = value[1];
+    if (jmcf->include.len != 0) {
+        file = jmcf->include;
+
+    } else {
+        file = ngx_cycle->conf_prefix;
+    }
+
     options.file.start = file.data;
     options.file.length = file.len;
 
     jmcf->vm = njs_vm_create(&options);
     if (jmcf->vm == NULL) {
-        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "failed to create JS VM");
+        ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "failed to create js VM");
         return NGX_CONF_ERROR;
     }
 
@@ -2429,12 +2489,12 @@ ngx_http_js_include(ngx_conf_t *cf, ngx_
     cln->handler = ngx_http_js_cleanup_vm;
     cln->data = jmcf->vm;
 
-    path.start = ngx_cycle->prefix.data;
-    path.length = ngx_cycle->prefix.len;
+    path.start = ngx_cycle->conf_prefix.data;
+    path.length = ngx_cycle->conf_prefix.len;
 
     rc = njs_vm_add_path(jmcf->vm, &path);
     if (rc != NJS_OK) {
-        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "failed to add path");
+        ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "failed to add \"js_path\"");
         return NGX_CONF_ERROR;
     }
 
@@ -2442,7 +2502,7 @@ ngx_http_js_include(ngx_conf_t *cf, ngx_
         m = jmcf->paths->elts;
 
         for (i = 0; i < jmcf->paths->nelts; i++) {
-            if (ngx_conf_full_name(cf->cycle, &m[i], 0) != NGX_OK) {
+            if (ngx_conf_full_name(cf->cycle, &m[i], 1) != NGX_OK) {
                 return NGX_CONF_ERROR;
             }
 
@@ -2451,7 +2511,8 @@ ngx_http_js_include(ngx_conf_t *cf, ngx_
 
             rc = njs_vm_add_path(jmcf->vm, &path);
             if (rc != NJS_OK) {
-                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "failed to add path");
+                ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                              "failed to add \"js_path\"");
                 return NGX_CONF_ERROR;
             }
         }
@@ -2460,27 +2521,54 @@ ngx_http_js_include(ngx_conf_t *cf, ngx_
     proto = njs_vm_external_prototype(jmcf->vm, ngx_http_js_ext_request,
                                       njs_nitems(ngx_http_js_ext_request));
     if (proto == NULL) {
-        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "failed to add request proto");
+        ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                      "failed to add js request proto");
         return NGX_CONF_ERROR;
     }
 
     jmcf->req_proto = proto;
+    end = start + size;
 
     rc = njs_vm_compile(jmcf->vm, &start, end);
 
     if (rc != NJS_OK) {
+        njs_value_assign(&exception, njs_vm_retval(jmcf->vm));
         njs_vm_retval_string(jmcf->vm, &text);
 
-        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
-                           "%*s, included",
-                           text.length, text.start);
+        if (jmcf->include.len != 0) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "%*s, included in %s:%ui",
+                          text.length, text.start, jmcf->file, jmcf->line);
+            return NGX_CONF_ERROR;
+        }
+
+        value = njs_vm_object_prop(jmcf->vm, njs_value_arg(&exception),
+                                   &file_name_key, &lvalue);
+        if (value == NULL) {
+            value = njs_vm_object_prop(jmcf->vm, njs_value_arg(&exception),
+                                       &line_number_key, &lvalue);
+
+            if (value != NULL) {
+                i = njs_value_number(value) - 1;
+
+                if (i < jmcf->imports->nelts) {
+                    import = jmcf->imports->elts;
+                    ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                                  "%*s, included in %s:%ui", text.length,
+                                  text.start, import[i].file, import[i].line);
+                    return NGX_CONF_ERROR;
+                }
+            }
+        }
+
+        ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "%*s", text.length,
+                      text.start);
         return NGX_CONF_ERROR;
     }
 
     if (start != end) {
-        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
-                           "extra characters in js script: \"%*s\", included",
-                           end - start, start);
+        ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                      "extra characters in js script: \"%*s\"",
+                      end - start, start);
         return NGX_CONF_ERROR;
     }
 
@@ -2489,6 +2577,132 @@ ngx_http_js_include(ngx_conf_t *cf, ngx_
 
 
 static char *
+ngx_http_js_include(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_js_main_conf_t *jmcf = conf;
+
+    if (jmcf->imports != NGX_CONF_UNSET_PTR) {
+        return "is incompatible with \"js_import\"";
+    }
+
+    jmcf->file = cf->conf_file->file.name.data;
+    jmcf->line = cf->conf_file->line;
+
+    return ngx_conf_set_str_slot(cf, cmd, conf);
+}
+
+
+static char *
+ngx_http_js_import(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_js_main_conf_t *jmcf = conf;
+
+    u_char                *p, *end, c;
+    ngx_int_t              from;
+    ngx_str_t             *value, name, path;
+    ngx_http_js_import_t  *import;
+
+    if (jmcf->include.len != 0) {
+        return "is incompatible with \"js_include\"";
+    }
+
+    value = cf->args->elts;
+    from = (cf->args->nelts == 4);
+
+    if (from) {
+        if (ngx_strcmp(value[2].data, "from") != 0) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid parameter \"%V\"", &value[2]);
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    name = value[1];
+    path = (from ? value[3] : value[1]);
+
+    if (!from) {
+        end = name.data + name.len;
+
+        for (p = end - 1; p >= name.data; p--) {
+            if (*p == '/') {
+                break;
+            }
+        }
+
+        name.data = p + 1;
+        name.len = end - p - 1;
+
+        if (name.len < 3
+            || ngx_memcmp(&name.data[name.len - 3], ".js", 3) != 0)
+        {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "cannot extract export name from file path "
+                               "\"%V\", use extended \"from\" syntax", &path);
+            return NGX_CONF_ERROR;
+        }
+
+        name.len -= 3;
+    }
+
+    if (name.len == 0) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "empty export name");
+        return NGX_CONF_ERROR;
+    }
+
+    p = name.data;
+    end = name.data + name.len;
+
+    while (p < end) {
+        c = ngx_tolower(*p);
+
+        if (*p != '_' && (c < 'a' || c > 'z')) {
+            if (p == name.data) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "cannot start "
+                                   "with \"%c\" in export name \"%V\"", *p,
+                                   &name);
+                return NGX_CONF_ERROR;
+            }
+
+            if (*p < '0' || *p > '9') {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid character "
+                                   "\"%c\" in export name \"%V\"", *p,
+                                   &name);
+                return NGX_CONF_ERROR;
+            }
+        }
+
+        p++;
+    }
+
+    if (ngx_strchr(path.data, '\'') != NULL) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid character \"'\" "
+                           "in file path \"%V\"", &path);
+        return NGX_CONF_ERROR;
+    }
+
+    if (jmcf->imports == NGX_CONF_UNSET_PTR) {
+        jmcf->imports = ngx_array_create(cf->pool, 4,
+                                         sizeof(ngx_http_js_import_t));
+        if (jmcf->imports == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    import = ngx_array_push(jmcf->imports);
+    if (import == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    import->name = name;
+    import->path = path;
+    import->file = cf->conf_file->file.name.data;
+    import->line = cf->conf_file->line;
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
 ngx_http_js_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
 {
     ngx_str_t            *value, *fname;
@@ -2560,10 +2774,14 @@ ngx_http_js_create_main_conf(ngx_conf_t 
      * set by ngx_pcalloc():
      *
      *     conf->vm = NULL;
+     *     conf->include = { 0, NULL };
+     *     conf->file = NULL;
+     *     conf->line = 0;
      *     conf->req_proto = NULL;
      */
 
     conf->paths = NGX_CONF_UNSET_PTR;
+    conf->imports = NGX_CONF_UNSET_PTR;
 
     return conf;
 }
diff -r a46c221089c0 -r 777ed1eb1918 nginx/ngx_stream_js_module.c
--- a/nginx/ngx_stream_js_module.c	Sun Apr 12 12:56:25 2020 +0000
+++ b/nginx/ngx_stream_js_module.c	Tue Apr 14 12:18:25 2020 +0000
@@ -15,12 +15,24 @@
 
 typedef struct {
     njs_vm_t              *vm;
+    ngx_str_t              include;
+    u_char                *file;
+    ngx_uint_t             line;
+    ngx_array_t           *imports;
     ngx_array_t           *paths;
     njs_external_proto_t   proto;
 } ngx_stream_js_main_conf_t;
 
 
 typedef struct {
+    ngx_str_t              name;
+    ngx_str_t              path;
+    u_char                *file;
+    ngx_uint_t             line;
+} ngx_stream_js_import_t;
+
+
+typedef struct {
     ngx_str_t              access;
     ngx_str_t              preread;
     ngx_str_t              filter;
@@ -103,9 +115,12 @@ static njs_int_t ngx_stream_js_string(nj
 
 static char *ngx_stream_js_include(ngx_conf_t *cf, ngx_command_t *cmd,
     void *conf);
+static char *ngx_stream_js_import(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
 static char *ngx_stream_js_set(ngx_conf_t *cf, ngx_command_t *cmd,
     void *conf);
 static void *ngx_stream_js_create_main_conf(ngx_conf_t *cf);
+static char *ngx_stream_js_init_main_conf(ngx_conf_t *cf, void *conf);
 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);
@@ -118,6 +133,13 @@ static ngx_command_t  ngx_stream_js_comm
       NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE1,
       ngx_stream_js_include,
       NGX_STREAM_MAIN_CONF_OFFSET,
+      offsetof(ngx_stream_js_main_conf_t, include),
+      NULL },
+
+    { ngx_string("js_import"),
+      NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE13,
+      ngx_stream_js_import,
+      NGX_STREAM_MAIN_CONF_OFFSET,
       0,
       NULL },
 
@@ -165,7 +187,7 @@ static ngx_stream_module_t  ngx_stream_j
     ngx_stream_js_init,             /* postconfiguration */
 
     ngx_stream_js_create_main_conf, /* create main configuration */
-    NULL,                           /* init main configuration */
+    ngx_stream_js_init_main_conf,   /* init main configuration */
 
     ngx_stream_js_create_srv_conf,  /* create server configuration */
     ngx_stream_js_merge_srv_conf,   /* merge server configuration */
@@ -1336,82 +1358,113 @@ ngx_stream_js_string(njs_vm_t *vm, njs_v
 
 
 static char *
-ngx_stream_js_include(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+ngx_stream_js_init_main_conf(ngx_conf_t *cf, void *conf)
 {
     ngx_stream_js_main_conf_t *jmcf = conf;
 
-    size_t                 size;
-    u_char                *start, *end;
-    ssize_t                n;
-    ngx_fd_t               fd;
-    ngx_str_t             *m, *value, file;
-    njs_int_t              rc;
-    njs_str_t              text, path;
-    ngx_uint_t             i;
-    njs_vm_opt_t           options;
-    ngx_file_info_t        fi;
-    ngx_pool_cleanup_t    *cln;
-    njs_external_proto_t   proto;
+    size_t                   size;
+    u_char                  *start, *end, *p;
+    ssize_t                  n;
+    ngx_fd_t                 fd;
+    ngx_str_t               *m, file;
+    njs_int_t                rc;
+    njs_str_t                text, path;
+    ngx_uint_t               i;
+    njs_value_t             *value;
+    njs_vm_opt_t             options;
+    ngx_file_info_t          fi;
+    ngx_pool_cleanup_t      *cln;
+    njs_opaque_value_t       lvalue, exception;
+    njs_external_proto_t     proto;
+    ngx_stream_js_import_t  *import;
 
-    if (jmcf->vm) {
-        return "is duplicate";
+    static const njs_str_t line_number_key = njs_str("lineNumber");
+    static const njs_str_t file_name_key = njs_str("fileName");
+
+    if (jmcf->include.len == 0 && jmcf->imports == NGX_CONF_UNSET_PTR) {
+        return NGX_CONF_OK;
     }
 
-    value = cf->args->elts;
-    file = value[1];
+    size = 0;
+    fd = NGX_INVALID_FILE;
+
+    if (jmcf->include.len != 0) {
+        file = jmcf->include;
+
+        if (ngx_conf_full_name(cf->cycle, &file, 1) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
 
-    if (ngx_conf_full_name(cf->cycle, &file, 1) != NGX_OK) {
-        return NGX_CONF_ERROR;
+        fd = ngx_open_file(file.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);
+        if (fd == NGX_INVALID_FILE) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno,
+                          ngx_open_file_n " \"%s\" failed", file.data);
+            return NGX_CONF_ERROR;
+        }
+
+        if (ngx_fd_info(fd, &fi) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno,
+                          ngx_fd_info_n " \"%s\" failed", file.data);
+            (void) ngx_close_file(fd);
+            return NGX_CONF_ERROR;
+        }
+
+        size = ngx_file_size(&fi);
+
+    } else {
+        import = jmcf->imports->elts;
+        for (i = 0; i < jmcf->imports->nelts; i++) {
+            size += sizeof("import  from '';\n") - 1 + import[i].name.len
+                    + import[i].path.len;
+        }
     }
 
-    fd = ngx_open_file(file.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);
-    if (fd == NGX_INVALID_FILE) {
-        ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
-                           ngx_open_file_n " \"%s\" failed", file.data);
+    start = ngx_pnalloc(cf->pool, size);
+    if (start == NULL) {
+        if (fd != NGX_INVALID_FILE) {
+            (void) ngx_close_file(fd);
+        }
+
         return NGX_CONF_ERROR;
     }
 
-    if (ngx_fd_info(fd, &fi) == NGX_FILE_ERROR) {
-        ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
-                           ngx_fd_info_n " \"%s\" failed", file.data);
-        (void) ngx_close_file(fd);
-        return NGX_CONF_ERROR;
-    }
+    if (jmcf->include.len != 0) {
+        n = ngx_read_fd(fd, start, size);
+
+        if (n == -1) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno,
+                          ngx_read_fd_n " \"%s\" failed", file.data);
 
-    size = ngx_file_size(&fi);
+            (void) ngx_close_file(fd);
+            return NGX_CONF_ERROR;
+        }
 
-    start = ngx_pnalloc(cf->pool, size);
-    if (start == NULL) {
-        (void) ngx_close_file(fd);
-        return NGX_CONF_ERROR;
-    }
-
-    n = ngx_read_fd(fd, start,  size);
+        if ((size_t) n != size) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                          ngx_read_fd_n " has read only %z "
+                          "of %O from \"%s\"", n, size, file.data);
 
-    if (n == -1) {
-        ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
-                           ngx_read_fd_n " \"%s\" failed", file.data);
+            (void) ngx_close_file(fd);
+            return NGX_CONF_ERROR;
+        }
 
-        (void) ngx_close_file(fd);
-        return NGX_CONF_ERROR;
-    }
+        if (ngx_close_file(fd) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno,
+                          ngx_close_file_n " %s failed", file.data);
+        }
 
-    if ((size_t) n != size) {
-        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
-                           ngx_read_fd_n " has read only %z of %uz from \"%s\"",
-                           n, size, file.data);
-
-        (void) ngx_close_file(fd);
-        return NGX_CONF_ERROR;
+    } else {
+        p = start;
+        import = jmcf->imports->elts;
+        for (i = 0; i < jmcf->imports->nelts; i++) {
+            p = ngx_cpymem(p, "import ", sizeof("import ") - 1);
+            p = ngx_cpymem(p, import[i].name.data, import[i].name.len);
+            p = ngx_cpymem(p, " from '", sizeof(" from '") - 1);
+            p = ngx_cpymem(p, import[i].path.data, import[i].path.len);
+            p = ngx_cpymem(p, "';\n", sizeof("';\n") - 1);
+        }
     }
 
-    if (ngx_close_file(fd) == NGX_FILE_ERROR) {
-        ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
-                           ngx_close_file_n " %s failed", file.data);
-    }
-
-    end = start + size;
-
     njs_vm_opt_init(&options);
 
     options.backtrace = 1;
@@ -1419,13 +1472,19 @@ ngx_stream_js_include(ngx_conf_t *cf, ng
     options.argv = ngx_argv;
     options.argc = ngx_argc;
 
-    file = value[1];
+    if (jmcf->include.len != 0) {
+        file = jmcf->include;
+
+    } else {
+        file = ngx_cycle->conf_prefix;
+    }
+
     options.file.start = file.data;
     options.file.length = file.len;
 
     jmcf->vm = njs_vm_create(&options);
     if (jmcf->vm == NULL) {
-        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "failed to create JS VM");
+        ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "failed to create js VM");
         return NGX_CONF_ERROR;
     }
 
@@ -1437,12 +1496,12 @@ ngx_stream_js_include(ngx_conf_t *cf, ng
     cln->handler = ngx_stream_js_cleanup_vm;
     cln->data = jmcf->vm;
 
-    path.start = ngx_cycle->prefix.data;
-    path.length = ngx_cycle->prefix.len;
+    path.start = ngx_cycle->conf_prefix.data;
+    path.length = ngx_cycle->conf_prefix.len;
 
     rc = njs_vm_add_path(jmcf->vm, &path);
     if (rc != NJS_OK) {
-        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "failed to add path");
+        ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "failed to add \"js_path\"");
         return NGX_CONF_ERROR;
     }
 
@@ -1450,7 +1509,7 @@ ngx_stream_js_include(ngx_conf_t *cf, ng
         m = jmcf->paths->elts;
 
         for (i = 0; i < jmcf->paths->nelts; i++) {
-            if (ngx_conf_full_name(cf->cycle, &m[i], 0) != NGX_OK) {
+            if (ngx_conf_full_name(cf->cycle, &m[i], 1) != NGX_OK) {
                 return NGX_CONF_ERROR;
             }
 
@@ -1459,7 +1518,8 @@ ngx_stream_js_include(ngx_conf_t *cf, ng
 
             rc = njs_vm_add_path(jmcf->vm, &path);
             if (rc != NJS_OK) {
-                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "failed to add path");
+                ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                              "failed to add \"js_path\"");
                 return NGX_CONF_ERROR;
             }
         }
@@ -1467,29 +1527,55 @@ ngx_stream_js_include(ngx_conf_t *cf, ng
 
     proto = njs_vm_external_prototype(jmcf->vm, ngx_stream_js_ext_session,
                                       njs_nitems(ngx_stream_js_ext_session));
-
     if (proto == NULL) {
-        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "failed to add stream proto");
+        ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                      "failed to add js request proto");
         return NGX_CONF_ERROR;
     }
 
     jmcf->proto = proto;
+    end = start + size;
 
     rc = njs_vm_compile(jmcf->vm, &start, end);
 
     if (rc != NJS_OK) {
+        njs_value_assign(&exception, njs_vm_retval(jmcf->vm));
         njs_vm_retval_string(jmcf->vm, &text);
 
-        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
-                           "%*s, included",
-                           text.length, text.start);
+        if (jmcf->include.len != 0) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "%*s, included in %s:%ui",
+                          text.length, text.start, jmcf->file, jmcf->line);
+            return NGX_CONF_ERROR;
+        }
+
+        value = njs_vm_object_prop(jmcf->vm, njs_value_arg(&exception),
+                                   &file_name_key, &lvalue);
+        if (value == NULL) {
+            value = njs_vm_object_prop(jmcf->vm, njs_value_arg(&exception),
+                                       &line_number_key, &lvalue);
+
+            if (value != NULL) {
+                i = njs_value_number(value) - 1;
+
+                if (i < jmcf->imports->nelts) {
+                    import = jmcf->imports->elts;
+                    ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                                  "%*s, included in %s:%ui", text.length,
+                                  text.start, import[i].file, import[i].line);
+                    return NGX_CONF_ERROR;
+                }
+            }
+        }
+
+        ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "%*s", text.length,
+                      text.start);
         return NGX_CONF_ERROR;
     }
 
     if (start != end) {
-        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
-                           "extra characters in js script: \"%*s\", included",
-                           end - start, start);
+        ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                      "extra characters in js script: \"%*s\"",
+                      end - start, start);
         return NGX_CONF_ERROR;
     }
 
@@ -1498,6 +1584,131 @@ ngx_stream_js_include(ngx_conf_t *cf, ng
 
 
 static char *
+ngx_stream_js_include(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_stream_js_main_conf_t *jmcf = conf;
+
+    if (jmcf->imports != NGX_CONF_UNSET_PTR) {
+        return "is incompatible with \"js_import\"";
+    }
+
+    jmcf->file = cf->conf_file->file.name.data;
+    jmcf->line = cf->conf_file->line;
+
+    return ngx_conf_set_str_slot(cf, cmd, conf);
+}
+
+
+static char *
+ngx_stream_js_import(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_stream_js_main_conf_t *jmcf = conf;
+
+    u_char                  *p, *end, c;
+    ngx_int_t               from;
+    ngx_str_t               *value, name, path;
+    ngx_stream_js_import_t  *import;
+
+    if (jmcf->include.len != 0) {
+        return "is incompatible with \"js_include\"";
+    }
+
+    value = cf->args->elts;
+    from = (cf->args->nelts == 4);
+
+    if (from) {
+        if (ngx_strcmp(value[2].data, "from") != 0) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid parameter \"%V\"", &value[2]);
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    name = value[1];
+    path = (from ? value[3] : value[1]);
+
+    if (!from) {
+        end = name.data + name.len;
+
+        for (p = end - 1; p >= name.data; p--) {
+            if (*p == '/') {
+                break;
+            }
+        }
+
+        name.data = p + 1;
+        name.len = end - p - 1;
+
+        if (name.len < 3
+            || ngx_memcmp(&name.data[name.len - 3], ".js", 3) != 0)
+        {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "cannot extract export name from file path "
+                               "\"%V\", use extended \"from\" syntax", &path);
+            return NGX_CONF_ERROR;
+        }
+
+        name.len -= 3;
+    }
+
+    if (name.len == 0) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "empty \"name\" parameter");
+        return NGX_CONF_ERROR;
+    }
+
+    p = name.data;
+    end = name.data + name.len;
+
+    while (p < end) {
+        c = ngx_tolower(*p);
+
+        if (*p != '_' && (c < 'a' || c > 'z')) {
+            if (p == name.data) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "cannot start "
+                                   "with \"%c\" in export name \"%V\"", *p,
+                                   &name);
+                return NGX_CONF_ERROR;
+            }
+
+            if (*p < '0' || *p > '9') {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid character "
+                                   "\"%c\" in export name \"%V\"", *p, &name);
+                return NGX_CONF_ERROR;
+            }
+        }
+
+        p++;
+    }
+
+    if (ngx_strchr(path.data, '\'') != NULL) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid character \"'\" "
+                           "in file path \"%V\"", &path);
+        return NGX_CONF_ERROR;
+    }
+
+    if (jmcf->imports == NGX_CONF_UNSET_PTR) {
+        jmcf->imports = ngx_array_create(cf->pool, 4,
+                                         sizeof(ngx_stream_js_import_t));
+        if (jmcf->imports == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    import = ngx_array_push(jmcf->imports);
+    if (import == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    import->name = name;
+    import->path = path;
+    import->file = cf->conf_file->file.name.data;
+    import->line = cf->conf_file->line;
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
 ngx_stream_js_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
 {
     ngx_str_t              *value, *fname;
@@ -1547,10 +1758,14 @@ ngx_stream_js_create_main_conf(ngx_conf_
      * set by ngx_pcalloc():
      *
      *     conf->vm = NULL;
+     *     conf->include = { 0, NULL };


More information about the nginx-devel mailing list