[njs] Modules: added Request, Response and Headers ctors in Fetch API.

Dmitry Volyntsev xeioex at nginx.com
Tue Dec 13 17:13:19 UTC 2022


details:   https://hg.nginx.org/njs/rev/c43261bad627
branches:  
changeset: 2015:c43261bad627
user:      Dmitry Volyntsev <xeioex at nginx.com>
date:      Mon Dec 12 22:00:23 2022 -0800
description:
Modules: added Request, Response and Headers ctors in Fetch API.

    Added Headers method and properties: append(), delete(), get(),
        forEach(), has(), set().
    Added Request method and properties: arrayBuffer(), bodyUsed,
        cache, credentials, json(), method, mode, text(), url.
    Added Headers, Request, Response constructors.

    This closes #425 issue on Github.

diffstat:

 nginx/ngx_js_fetch.c |  2258 ++++++++++++++++++++++++++++++++++++++++++-------
 1 files changed, 1936 insertions(+), 322 deletions(-)

diffs (truncated from 2786 to 1000 lines):

diff -r f23c541c02ad -r c43261bad627 nginx/ngx_js_fetch.c
--- a/nginx/ngx_js_fetch.c	Mon Dec 12 21:55:47 2022 -0800
+++ b/nginx/ngx_js_fetch.c	Mon Dec 12 22:00:23 2022 -0800
@@ -18,6 +18,12 @@ typedef struct ngx_js_http_s  ngx_js_htt
 
 
 typedef struct {
+    njs_str_t       name;
+    njs_int_t       value;
+} ngx_js_entry_t;
+
+
+typedef struct {
     ngx_uint_t                     state;
     ngx_uint_t                     code;
     u_char                        *status_text;
@@ -41,6 +47,59 @@ typedef struct {
 } ngx_js_http_chunk_parse_t;
 
 
+typedef struct {
+    enum {
+        GUARD_NONE = 0,
+        GUARD_REQUEST,
+        GUARD_IMMUTABLE,
+        GUARD_RESPONSE,
+    }                              guard;
+    ngx_list_t                     header_list;
+} ngx_js_headers_t;
+
+
+typedef struct {
+    enum {
+        CACHE_MODE_DEFAULT = 0,
+        CACHE_MODE_NO_STORE,
+        CACHE_MODE_RELOAD,
+        CACHE_MODE_NO_CACHE,
+        CACHE_MODE_FORCE_CACHE,
+        CACHE_MODE_ONLY_IF_CACHED,
+    }                              cache_mode;
+    enum {
+        CREDENTIALS_SAME_ORIGIN = 0,
+        CREDENTIALS_INCLUDE,
+        CREDENTIALS_OMIT,
+    }                              credentials;
+    enum {
+        MODE_NO_CORS = 0,
+        MODE_SAME_ORIGIN,
+        MODE_CORS,
+        MODE_NAVIGATE,
+        MODE_WEBSOCKET,
+    }                              mode;
+    njs_str_t                      url;
+    njs_str_t                      method;
+    u_char                         m[8];
+    uint8_t                        body_used;
+    njs_str_t                      body;
+    ngx_js_headers_t               headers;
+    njs_opaque_value_t             header_value;
+} ngx_js_request_t;
+
+
+typedef struct {
+    njs_str_t                      url;
+    ngx_int_t                      code;
+    njs_str_t                      status_text;
+    uint8_t                        body_used;
+    njs_chb_t                      chain;
+    ngx_js_headers_t               headers;
+    njs_opaque_value_t             header_value;
+} ngx_js_response_t;
+
+
 struct ngx_js_http_s {
     ngx_log_t                     *log;
     ngx_pool_t                    *pool;
@@ -63,9 +122,6 @@ struct ngx_js_http_s {
     ngx_int_t                      buffer_size;
     ngx_int_t                      max_response_body_size;
 
-    njs_str_t                      url;
-    ngx_array_t                    headers;
-
     unsigned                       header_only;
 
 #if (NGX_SSL)
@@ -78,26 +134,35 @@ struct ngx_js_http_s {
     ngx_buf_t                     *chunk;
     njs_chb_t                      chain;
 
-    njs_opaque_value_t             reply;
+    ngx_js_response_t              response;
+    njs_opaque_value_t             response_value;
+
     njs_opaque_value_t             promise;
     njs_opaque_value_t             promise_callbacks[2];
 
     uint8_t                        done;
-    uint8_t                        body_used;
     ngx_js_http_parse_t            http_parse;
     ngx_js_http_chunk_parse_t      http_chunk_parse;
     ngx_int_t                    (*process)(ngx_js_http_t *http);
 };
 
 
+
+
 #define ngx_js_http_error(http, err, fmt, ...)                                \
     do {                                                                      \
-        njs_vm_value_error_set((http)->vm, njs_value_arg(&(http)->reply),     \
+        njs_vm_value_error_set((http)->vm,                                    \
+                               njs_value_arg(&(http)->response_value),        \
                                fmt, ##__VA_ARGS__);                           \
-        ngx_js_http_fetch_done(http, &(http)->reply, NJS_ERROR);              \
+        ngx_js_http_fetch_done(http, &(http)->response_value, NJS_ERROR);     \
     } while (0)
 
 
+static njs_int_t ngx_js_method_process(njs_vm_t *vm, ngx_js_request_t *r);
+static njs_int_t ngx_js_headers_inherit(njs_vm_t *vm, ngx_js_headers_t *headers,
+    ngx_js_headers_t *orig);
+static njs_int_t ngx_js_headers_fill(njs_vm_t *vm, ngx_js_headers_t *headers,
+    njs_value_t *init);
 static ngx_js_http_t *ngx_js_http_alloc(njs_vm_t *vm, ngx_pool_t *pool,
     ngx_log_t *log);
 static void njs_js_http_destructor(njs_external_ptr_t external,
@@ -113,6 +178,14 @@ static void ngx_js_http_connect(ngx_js_h
 static void ngx_js_http_next(ngx_js_http_t *http);
 static void ngx_js_http_write_handler(ngx_event_t *wev);
 static void ngx_js_http_read_handler(ngx_event_t *rev);
+
+static njs_int_t ngx_js_request_constructor(njs_vm_t *vm,
+    ngx_js_request_t *request, ngx_url_t *u, njs_external_ptr_t external,
+    njs_value_t *args, njs_uint_t nargs);
+
+static njs_int_t ngx_js_headers_append(njs_vm_t *vm, ngx_js_headers_t *headers,
+    u_char *name, size_t len, u_char *value, size_t vlen);
+
 static ngx_int_t ngx_js_http_process_status_line(ngx_js_http_t *http);
 static ngx_int_t ngx_js_http_process_headers(ngx_js_http_t *http);
 static ngx_int_t ngx_js_http_process_body(ngx_js_http_t *http);
@@ -124,15 +197,39 @@ static ngx_int_t ngx_js_http_parse_chunk
     ngx_buf_t *b, njs_chb_t *chain);
 static void ngx_js_http_dummy_handler(ngx_event_t *ev);
 
-static njs_int_t ngx_response_js_ext_headers_get(njs_vm_t *vm,
-    njs_value_t *args,  njs_uint_t nargs, njs_index_t as_array);
-static njs_int_t ngx_response_js_ext_headers_has(njs_vm_t *vm,
-    njs_value_t *args,  njs_uint_t nargs, njs_index_t unused);
-static njs_int_t ngx_response_js_ext_header(njs_vm_t *vm,
+static njs_int_t ngx_headers_js_ext_append(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t unused);
+static njs_int_t ngx_headers_js_ext_delete(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t unused);
+static njs_int_t ngx_headers_js_ext_for_each(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t as_array);
+static njs_int_t ngx_headers_js_ext_get(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t as_array);
+static njs_int_t ngx_headers_js_ext_has(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t unused);
+static njs_int_t ngx_headers_js_ext_prop(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 ngx_response_js_ext_keys(njs_vm_t *vm, njs_value_t *value,
+static njs_int_t ngx_headers_js_ext_keys(njs_vm_t *vm, njs_value_t *value,
     njs_value_t *keys);
+static njs_int_t ngx_headers_js_ext_set(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t unused);
+static njs_int_t ngx_request_js_ext_body(njs_vm_t *vm, njs_value_t *args,
+     njs_uint_t nargs, njs_index_t unused);
+static njs_int_t ngx_request_js_ext_body_used(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 ngx_request_js_ext_cache(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 ngx_request_js_ext_credentials(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 ngx_request_js_ext_headers(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 ngx_request_js_ext_mode(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 ngx_response_js_ext_status(njs_vm_t *vm,
     njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval,
     njs_value_t *retval);
@@ -145,6 +242,9 @@ static njs_int_t ngx_response_js_ext_ok(
 static njs_int_t ngx_response_js_ext_body_used(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 ngx_response_js_ext_headers(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 ngx_response_js_ext_type(njs_vm_t *vm,
     njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval,
     njs_value_t *retval);
@@ -158,7 +258,44 @@ static void ngx_js_http_ssl_handshake(ng
 static njs_int_t ngx_js_http_ssl_name(ngx_js_http_t *http);
 #endif
 
-static njs_external_t  ngx_js_ext_http_response_headers[] = {
+static void ngx_js_http_trim(u_char **value, size_t *len,
+    njs_bool_t trim_c0_control_or_space);
+static njs_int_t ngx_fetch_flag(njs_vm_t *vm, const ngx_js_entry_t *entries,
+    njs_int_t value, njs_value_t *retval);
+static njs_int_t ngx_fetch_flag_set(njs_vm_t *vm, const ngx_js_entry_t *entries,
+     njs_value_t *value, const char *type);
+
+
+static const ngx_js_entry_t ngx_js_fetch_credentials[] = {
+    { njs_str("same-origin"), CREDENTIALS_SAME_ORIGIN },
+    { njs_str("omit"), CREDENTIALS_OMIT },
+    { njs_str("include"), CREDENTIALS_INCLUDE },
+    { njs_null_str, 0 },
+};
+
+
+static const ngx_js_entry_t ngx_js_fetch_cache_modes[] = {
+    { njs_str("default"), CACHE_MODE_DEFAULT },
+    { njs_str("no-store"), CACHE_MODE_NO_STORE },
+    { njs_str("reload"), CACHE_MODE_RELOAD },
+    { njs_str("no-cache"), CACHE_MODE_NO_CACHE },
+    { njs_str("force-cache"), CACHE_MODE_FORCE_CACHE },
+    { njs_str("only-if-cached"), CACHE_MODE_ONLY_IF_CACHED },
+    { njs_null_str, 0 },
+};
+
+
+static const ngx_js_entry_t ngx_js_fetch_modes[] = {
+    { njs_str("no-cors"), MODE_NO_CORS },
+    { njs_str("cors"), MODE_CORS },
+    { njs_str("same-origin"), MODE_SAME_ORIGIN },
+    { njs_str("navigate"), MODE_NAVIGATE },
+    { njs_str("websocket"), MODE_WEBSOCKET },
+    { njs_null_str, 0 },
+};
+
+
+static njs_external_t  ngx_js_ext_http_headers[] = {
 
     {
         .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL,
@@ -169,13 +306,55 @@ static njs_external_t  ngx_js_ext_http_r
     },
 
     {
+        .flags = NJS_EXTERN_SELF,
+        .u.object = {
+            .enumerable = 1,
+            .prop_handler = ngx_headers_js_ext_prop,
+            .keys = ngx_headers_js_ext_keys,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("append"),
+        .writable = 1,
+        .configurable = 1,
+        .enumerable = 1,
+        .u.method = {
+            .native = ngx_headers_js_ext_append,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("delete"),
+        .writable = 1,
+        .configurable = 1,
+        .enumerable = 1,
+        .u.method = {
+            .native = ngx_headers_js_ext_delete,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("forEach"),
+        .writable = 1,
+        .configurable = 1,
+        .enumerable = 1,
+        .u.method = {
+            .native = ngx_headers_js_ext_for_each,
+        }
+    },
+
+    {
         .flags = NJS_EXTERN_METHOD,
         .name.string = njs_str("get"),
         .writable = 1,
         .configurable = 1,
         .enumerable = 1,
         .u.method = {
-            .native = ngx_response_js_ext_headers_get,
+            .native = ngx_headers_js_ext_get,
         }
     },
 
@@ -186,7 +365,7 @@ static njs_external_t  ngx_js_ext_http_r
         .configurable = 1,
         .enumerable = 1,
         .u.method = {
-            .native = ngx_response_js_ext_headers_get,
+            .native = ngx_headers_js_ext_get,
             .magic8 = 1
         }
     },
@@ -198,7 +377,135 @@ static njs_external_t  ngx_js_ext_http_r
         .configurable = 1,
         .enumerable = 1,
         .u.method = {
-            .native = ngx_response_js_ext_headers_has,
+            .native = ngx_headers_js_ext_has,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("set"),
+        .writable = 1,
+        .configurable = 1,
+        .enumerable = 1,
+        .u.method = {
+            .native = ngx_headers_js_ext_set,
+        }
+    },
+
+};
+
+
+static njs_external_t  ngx_js_ext_http_request[] = {
+
+    {
+        .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL,
+        .name.symbol = NJS_SYMBOL_TO_STRING_TAG,
+        .u.property = {
+            .value = "Request",
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("arrayBuffer"),
+        .writable = 1,
+        .configurable = 1,
+        .enumerable = 1,
+        .u.method = {
+            .native = ngx_request_js_ext_body,
+#define NGX_JS_BODY_ARRAY_BUFFER   0
+#define NGX_JS_BODY_JSON           1
+#define NGX_JS_BODY_TEXT           2
+            .magic8 = NGX_JS_BODY_ARRAY_BUFFER
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_PROPERTY,
+        .name.string = njs_str("bodyUsed"),
+        .enumerable = 1,
+        .u.property = {
+            .handler = ngx_request_js_ext_body_used,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_PROPERTY,
+        .name.string = njs_str("cache"),
+        .enumerable = 1,
+        .u.property = {
+            .handler = ngx_request_js_ext_cache,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_PROPERTY,
+        .name.string = njs_str("credentials"),
+        .enumerable = 1,
+        .u.property = {
+            .handler = ngx_request_js_ext_credentials,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("json"),
+        .writable = 1,
+        .configurable = 1,
+        .enumerable = 1,
+        .u.method = {
+            .native = ngx_request_js_ext_body,
+            .magic8 = NGX_JS_BODY_JSON
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_PROPERTY,
+        .name.string = njs_str("headers"),
+        .enumerable = 1,
+        .u.property = {
+            .handler = ngx_request_js_ext_headers,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_PROPERTY,
+        .name.string = njs_str("method"),
+        .enumerable = 1,
+        .u.property = {
+            .handler = ngx_js_ext_string,
+            .magic32 = offsetof(ngx_js_request_t, method),
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_PROPERTY,
+        .name.string = njs_str("mode"),
+        .enumerable = 1,
+        .u.property = {
+            .handler = ngx_request_js_ext_mode,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("text"),
+        .writable = 1,
+        .configurable = 1,
+        .enumerable = 1,
+        .u.method = {
+            .native = ngx_request_js_ext_body,
+            .magic8 = NGX_JS_BODY_TEXT
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_PROPERTY,
+        .name.string = njs_str("url"),
+        .enumerable = 1,
+        .u.property = {
+            .handler = ngx_js_ext_string,
+            .magic32 = offsetof(ngx_js_request_t, url),
         }
     },
 
@@ -223,9 +530,6 @@ static njs_external_t  ngx_js_ext_http_r
         .enumerable = 1,
         .u.method = {
             .native = ngx_response_js_ext_body,
-#define NGX_JS_BODY_ARRAY_BUFFER   0
-#define NGX_JS_BODY_JSON           1
-#define NGX_JS_BODY_TEXT           2
             .magic8 = NGX_JS_BODY_ARRAY_BUFFER
         }
     },
@@ -240,15 +544,11 @@ static njs_external_t  ngx_js_ext_http_r
     },
 
     {
-        .flags = NJS_EXTERN_OBJECT,
+        .flags = NJS_EXTERN_PROPERTY,
         .name.string = njs_str("headers"),
         .enumerable = 1,
-        .u.object = {
-            .enumerable = 1,
-            .properties = ngx_js_ext_http_response_headers,
-            .nproperties = njs_nitems(ngx_js_ext_http_response_headers),
-            .prop_handler = ngx_response_js_ext_header,
-            .keys = ngx_response_js_ext_keys,
+        .u.property = {
+            .handler = ngx_response_js_ext_headers,
         }
     },
 
@@ -329,47 +629,42 @@ static njs_external_t  ngx_js_ext_http_r
         .enumerable = 1,
         .u.property = {
             .handler = ngx_js_ext_string,
-            .magic32 = offsetof(ngx_js_http_t, url),
+            .magic32 = offsetof(ngx_js_response_t, url),
         }
     },
 };
 
 
-static njs_int_t    ngx_http_js_fetch_proto_id;
+static njs_int_t    ngx_http_js_fetch_request_proto_id;
+static njs_int_t    ngx_http_js_fetch_response_proto_id;
+static njs_int_t    ngx_http_js_fetch_headers_proto_id;
 
 
 njs_int_t
 ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     njs_index_t unused)
 {
-    int64_t              i, length;
     njs_int_t            ret;
-    njs_str_t            method, body, name, header;
     ngx_url_t            u;
-    njs_bool_t           has_host;
+    ngx_uint_t           i;
     ngx_pool_t          *pool;
-    njs_value_t         *init, *value, *headers, *keys;
+    njs_value_t         *init, *value;
     ngx_js_http_t       *http;
+    ngx_list_part_t     *part;
+    ngx_table_elt_t     *h;
+    ngx_js_request_t     request;
     ngx_connection_t    *c;
     ngx_resolver_ctx_t  *ctx;
     njs_external_ptr_t   external;
-    njs_opaque_value_t  *start, lvalue, headers_value;
-
-    static const njs_str_t body_key = njs_str("body");
-    static const njs_str_t headers_key = njs_str("headers");
+    njs_opaque_value_t   lvalue;
+
     static const njs_str_t buffer_size_key = njs_str("buffer_size");
     static const njs_str_t body_size_key = njs_str("max_response_body_size");
-    static const njs_str_t method_key = njs_str("method");
 #if (NGX_SSL)
     static const njs_str_t verify_key = njs_str("verify");
 #endif
 
-    external = njs_vm_external(vm, NJS_PROTO_ID_ANY, njs_argument(args, 0));
-    if (external == NULL) {
-        njs_vm_error(vm, "\"this\" is not an external");
-        return NJS_ERROR;
-    }
-
+    external = njs_vm_external_ptr(vm);
     c = ngx_external_connection(vm, external);
     pool = ngx_external_pool(vm, external);
 
@@ -379,76 +674,29 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value
     }
 
     http->external = external;
+    http->event_handler = ngx_external_event_handler(vm, external);
+
+    ret = ngx_js_request_constructor(vm, &request, &u, external, args, nargs);
+    if (ret != NJS_OK) {
+        goto fail;
+    }
+
+    http->response.url = request.url;
     http->timeout = ngx_external_fetch_timeout(vm, external);
-    http->event_handler = ngx_external_event_handler(vm, external);
     http->buffer_size = ngx_external_buffer_size(vm, external);
     http->max_response_body_size =
                            ngx_external_max_response_buffer_size(vm, external);
 
-    ret = ngx_js_string(vm, njs_arg(args, nargs, 1), &http->url);
-    if (ret != NJS_OK) {
-        njs_vm_error(vm, "failed to convert url arg");
-        goto fail;
-    }
-
-    ngx_memzero(&u, sizeof(ngx_url_t));
-
-    u.url.len = http->url.length;
-    u.url.data = http->url.start;
-    u.default_port = 80;
-    u.uri_part = 1;
-    u.no_resolve = 1;
-
-    if (u.url.len > 7
-        && ngx_strncasecmp(u.url.data, (u_char *) "http://", 7) == 0)
-    {
-        u.url.len -= 7;
-        u.url.data += 7;
-
 #if (NGX_SSL)
-    } else if (u.url.len > 8
-        && ngx_strncasecmp(u.url.data, (u_char *) "https://", 8) == 0)
-    {
-        u.url.len -= 8;
-        u.url.data += 8;
-        u.default_port = 443;
+    if (u.default_port == 443) {
         http->ssl = ngx_external_ssl(vm, external);
         http->ssl_verify = ngx_external_ssl_verify(vm, external);
+    }
 #endif
 
-    } else {
-        njs_vm_error(vm, "unsupported URL prefix");
-        goto fail;
-    }
-
-    if (ngx_parse_url(pool, &u) != NGX_OK) {
-        njs_vm_error(vm, "invalid url");
-        goto fail;
-    }
-
     init = njs_arg(args, nargs, 2);
 
-    method = njs_str_value("GET");
-    body = njs_str_value("");
-    headers = NULL;
-
     if (njs_value_is_object(init)) {
-        value = njs_vm_object_prop(vm, init, &method_key, &lvalue);
-        if (value != NULL && ngx_js_string(vm, value, &method) != NGX_OK) {
-            goto fail;
-        }
-
-        headers = njs_vm_object_prop(vm, init, &headers_key, &headers_value);
-        if (headers != NULL && !njs_value_is_object(headers)) {
-            njs_vm_error(vm, "headers is not an object");
-            goto fail;
-        }
-
-        value = njs_vm_object_prop(vm, init, &body_key, &lvalue);
-        if (value != NULL && ngx_js_string(vm, value, &body) != NGX_OK) {
-            goto fail;
-        }
-
         value = njs_vm_object_prop(vm, init, &buffer_size_key, &lvalue);
         if (value != NULL
             && ngx_js_integer(vm, value, &http->buffer_size)
@@ -473,11 +721,11 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value
 #endif
     }
 
+    http->header_only = njs_strstr_eq(&request.method, &njs_str_value("HEAD"));
+
     njs_chb_init(&http->chain, njs_vm_memory_pool(vm));
 
-    http->header_only = njs_strstr_case_eq(&method, &njs_str_value("HEAD"));
-
-    njs_chb_append(&http->chain, method.start, method.length);
+    njs_chb_append(&http->chain, request.method.start, request.method.length);
     njs_chb_append_literal(&http->chain, " ");
 
     if (u.uri.len == 0 || u.uri.data[0] != '/') {
@@ -486,59 +734,34 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value
 
     njs_chb_append(&http->chain, u.uri.data, u.uri.len);
     njs_chb_append_literal(&http->chain, " HTTP/1.1" CRLF);
+
+    njs_chb_append_literal(&http->chain, "Host: ");
+    njs_chb_append(&http->chain, u.host.data, u.host.len);
+    njs_chb_append_literal(&http->chain, CRLF);
     njs_chb_append_literal(&http->chain, "Connection: close" CRLF);
 
-    has_host = 0;
-
-    if (headers != NULL) {
-        keys = njs_vm_object_keys(vm, headers, njs_value_arg(&lvalue));
-        if (keys == NULL) {
-            goto fail;
-        }
-
-        start = (njs_opaque_value_t *) njs_vm_array_start(vm, keys);
-        if (start == NULL) {
-            goto fail;
-        }
-
-        (void) njs_vm_array_length(vm, keys, &length);
-
-        for (i = 0; i < length; i++) {
-            if (ngx_js_string(vm, njs_value_arg(start), &name) != NGX_OK) {
-                goto fail;
+    part = &request.headers.header_list.part;
+    h = part->elts;
+
+    for (i = 0; /* void */; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
             }
 
-            start++;
-
-            value = njs_vm_object_prop(vm, headers, &name, &lvalue);
-            if (value == NULL) {
-                goto fail;
-            }
-
-            if (njs_value_is_null_or_undefined(value)) {
-                continue;
-            }
-
-            if (ngx_js_string(vm, value, &header) != NGX_OK) {
-                goto fail;
-            }
-
-            if (name.length == 4
-                && ngx_strncasecmp(name.start, (u_char *) "Host", 4) == 0)
-            {
-                has_host = 1;
-            }
-
-            njs_chb_append(&http->chain, name.start, name.length);
-            njs_chb_append_literal(&http->chain, ": ");
-            njs_chb_append(&http->chain, header.start, header.length);
-            njs_chb_append_literal(&http->chain, CRLF);
+            part = part->next;
+            h = part->elts;
+            i = 0;
         }
-    }
-
-    if (!has_host) {
-        njs_chb_append_literal(&http->chain, "Host: ");
-        njs_chb_append(&http->chain, u.host.data, u.host.len);
+
+        if (h[i].hash == 0) {
+            continue;
+        }
+
+        njs_chb_append(&http->chain, h[i].key.data, h[i].key.len);
+        njs_chb_append_literal(&http->chain, ": ");
+        njs_chb_append(&http->chain, h[i].value.data, h[i].value.len);
         njs_chb_append_literal(&http->chain, CRLF);
     }
 
@@ -547,10 +770,10 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value
     http->tls_name.len = u.host.len;
 #endif
 
-    if (body.length != 0) {
+    if (request.body.length != 0) {
         njs_chb_sprintf(&http->chain, 32, "Content-Length: %uz" CRLF CRLF,
-                        body.length);
-        njs_chb_append(&http->chain, body.start, body.length);
+                        request.body.length);
+        njs_chb_append(&http->chain, request.body.start, request.body.length);
 
     } else {
         njs_chb_append_literal(&http->chain, CRLF);
@@ -609,6 +832,377 @@ fail:
 }
 
 
+static njs_int_t
+ngx_js_ext_headers_constructor(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t unused)
+{
+    ngx_int_t          rc;
+    njs_int_t          ret;
+    njs_value_t       *init;
+    ngx_pool_t        *pool;
+    ngx_js_headers_t  *headers;
+
+    pool = ngx_external_pool(vm, njs_vm_external_ptr(vm));
+
+    headers = ngx_palloc(pool, sizeof(ngx_js_headers_t));
+    if (headers == NULL) {
+        njs_vm_memory_error(vm);
+        return NJS_ERROR;
+    }
+
+    rc = ngx_list_init(&headers->header_list, pool, 4, sizeof(ngx_table_elt_t));
+    if (rc != NGX_OK) {
+        njs_vm_memory_error(vm);
+        return NJS_ERROR;
+    }
+
+    init = njs_arg(args, nargs, 1);
+
+    if (njs_value_is_object(init)) {
+        ret = ngx_js_headers_fill(vm, headers, init);
+        if (ret != NJS_OK) {
+            return NJS_ERROR;
+        }
+    }
+
+    return njs_vm_external_create(vm, njs_vm_retval(vm),
+                                  ngx_http_js_fetch_headers_proto_id, headers,
+                                  0);
+}
+
+
+static njs_int_t
+ngx_js_ext_request_constructor(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t unused)
+{
+    njs_int_t          ret;
+    ngx_url_t          u;
+    ngx_js_request_t  *request;
+
+    request = njs_mp_alloc(njs_vm_memory_pool(vm), sizeof(ngx_js_request_t));
+    if (request == NULL) {
+        njs_vm_memory_error(vm);
+        return NJS_ERROR;
+    }
+
+    ret = ngx_js_request_constructor(vm, request, &u, njs_vm_external_ptr(vm),
+                                     args, nargs);
+    if (ret != NJS_OK) {
+        return NJS_ERROR;
+    }
+
+    return njs_vm_external_create(vm, njs_vm_retval(vm),
+                                  ngx_http_js_fetch_request_proto_id, request,
+                                  0);
+}
+
+
+static njs_int_t
+ngx_js_ext_response_constructor(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t unused)
+{
+    u_char              *p, *end;
+    ngx_int_t            rc;
+    njs_int_t            ret;
+    njs_str_t            bd;
+    ngx_pool_t          *pool;
+    njs_value_t         *body, *init, *value;
+    ngx_js_response_t   *response;
+    njs_opaque_value_t   lvalue;
+
+    static const njs_str_t headers = njs_str("headers");
+    static const njs_str_t status = njs_str("status");
+    static const njs_str_t status_text = njs_str("statusText");
+
+    response = njs_mp_zalloc(njs_vm_memory_pool(vm), sizeof(ngx_js_response_t));
+    if (response == NULL) {
+        njs_vm_memory_error(vm);
+        return NJS_ERROR;
+    }
+
+    /*
+     * set by njs_mp_zalloc():
+     *
+     *  request->url.length = 0;
+     *  request->status_text.length = 0;
+     */
+
+    response->code = 200;
+    response->headers.guard = GUARD_RESPONSE;
+
+    pool = ngx_external_pool(vm, njs_vm_external_ptr(vm));
+
+    rc = ngx_list_init(&response->headers.header_list, pool, 4,
+                       sizeof(ngx_table_elt_t));
+    if (rc != NGX_OK) {
+        njs_vm_memory_error(vm);
+        return NJS_ERROR;
+    }
+
+    init = njs_arg(args, nargs, 2);
+
+    if (njs_value_is_object(init)) {
+        value = njs_vm_object_prop(vm, init, &status, &lvalue);
+        if (value != NULL) {
+            if (ngx_js_integer(vm, value, &response->code) != NGX_OK) {
+                njs_vm_error(vm, "invalid Response status");
+                return NJS_ERROR;
+            }
+
+            if (response->code < 200 || response->code > 599) {
+                njs_vm_error(vm, "status provided (%i) is outside of "
+                             "[200, 599] range", response->code);
+                return NJS_ERROR;
+            }
+        }
+
+        value = njs_vm_object_prop(vm, init, &status_text, &lvalue);
+        if (value != NULL) {
+            if (ngx_js_string(vm, value, &response->status_text) != NGX_OK) {
+                njs_vm_error(vm, "invalid Response statusText");
+                return NJS_ERROR;
+            }
+
+            p = response->status_text.start;
+            end = p + response->status_text.length;
+
+            while (p < end) {
+                if (*p != '\t' && *p < ' ') {
+                    njs_vm_error(vm, "invalid Response statusText");
+                    return NJS_ERROR;
+                }
+
+                p++;
+            }
+        }
+
+        value = njs_vm_object_prop(vm, init, &headers, &lvalue);
+        if (value != NULL) {
+            if (!njs_value_is_object(value)) {
+                njs_vm_error(vm, "Headers is not an object");
+                return NJS_ERROR;
+            }
+
+            ret = ngx_js_headers_fill(vm, &response->headers, value);
+            if (ret != NJS_OK) {
+                return NJS_ERROR;
+            }
+        }
+    }
+
+    njs_chb_init(&response->chain, njs_vm_memory_pool(vm));
+
+    body = njs_arg(args, nargs, 1);
+
+    if (!njs_value_is_null_or_undefined(body)) {
+        if (ngx_js_string(vm, body, &bd) != NGX_OK) {
+            njs_vm_error(vm, "invalid Response body");
+            return NJS_ERROR;
+        }
+
+        njs_chb_append(&response->chain, bd.start, bd.length);
+
+        if (njs_value_is_string(body)) {
+            ret = ngx_js_headers_append(vm, &response->headers,
+                                    (u_char *) "Content-Type",
+                                    njs_length("Content-Type"),
+                                    (u_char *) "text/plain;charset=UTF-8",
+                                    njs_length("text/plain;charset=UTF-8"));
+            if (ret != NJS_OK) {
+                return NJS_ERROR;
+            }
+        }
+    }
+
+    return njs_vm_external_create(vm, njs_vm_retval(vm),
+                                  ngx_http_js_fetch_response_proto_id, response,
+                                  0);
+}
+
+
+static njs_int_t
+ngx_js_method_process(njs_vm_t *vm, ngx_js_request_t *request)
+{
+    u_char           *s, *p;
+    const njs_str_t  *m;
+
+    static const njs_str_t forbidden[] = {
+        njs_str("CONNECT"),
+        njs_str("TRACE"),
+        njs_str("TRACK"),
+        njs_null_str,
+    };
+
+    static const njs_str_t to_normalize[] = {
+        njs_str("DELETE"),
+        njs_str("GET"),
+        njs_str("HEAD"),
+        njs_str("OPTIONS"),
+        njs_str("POST"),
+        njs_str("PUT"),
+        njs_null_str,
+    };
+
+    for (m = &forbidden[0]; m->length != 0; m++) {
+        if (njs_strstr_case_eq(&request->method, m)) {
+            njs_vm_error(vm, "forbidden method: %V", m);
+            return NJS_ERROR;
+        }
+    }
+
+    for (m = &to_normalize[0]; m->length != 0; m++) {
+        if (njs_strstr_case_eq(&request->method, m)) {
+            s = &request->m[0];
+            p = m->start;
+
+            while (*p != '\0') {
+                *s++ = njs_upper_case(*p++);
+            }
+
+            request->method.start = &request->m[0];
+            request->method.length = m->length;
+            break;
+        }
+    }
+
+    return NJS_OK;
+}
+
+
+static njs_int_t
+ngx_js_headers_inherit(njs_vm_t *vm, ngx_js_headers_t *headers,
+    ngx_js_headers_t *orig)
+{
+    njs_int_t         ret;
+    ngx_uint_t        i;
+    ngx_list_part_t  *part;
+    ngx_table_elt_t  *h;
+
+    part = &orig->header_list.part;
+    h = part->elts;
+
+    for (i = 0; /* void */; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+
+            part = part->next;
+            h = part->elts;
+            i = 0;
+        }
+
+        if (h[i].hash == 0) {


More information about the nginx-devel mailing list