[njs] Modules: added ngx.fetch().

Dmitry Volyntsev xeioex at nginx.com
Thu Jan 21 18:45:29 UTC 2021


details:   https://hg.nginx.org/njs/rev/81040de6b085
branches:  
changeset: 1593:81040de6b085
user:      Dmitry Volyntsev <xeioex at nginx.com>
date:      Thu Jan 21 18:44:58 2021 +0000
description:
Modules: added ngx.fetch().

This is an initial implementation of Fetch API.

The following init options are supported:
body, headers, buffer_size (nginx specific), max_response_body_size
(nginx specific), method.

The following properties and methods of Response object are implemented:
arrayBuffer(), bodyUsed, json(), headers, ok, redirect, status, statusText,
text(), type, url.

The following properties and methods of Header object are implemented:
get(), getAll(), has().

Notable limitations: only http:// scheme is supported, redirects
are not handled.

In collaboration with 洪志道 (Hong Zhi Dao).

diffstat:

 nginx/config                 |     3 +-
 nginx/ngx_http_js_module.c   |    65 +-
 nginx/ngx_js.c               |    28 +
 nginx/ngx_js.h               |    24 +-
 nginx/ngx_js_fetch.c         |  2212 ++++++++++++++++++++++++++++++++++++++++++
 nginx/ngx_js_fetch.h         |    18 +
 nginx/ngx_stream_js_module.c |    40 +-
 7 files changed, 2378 insertions(+), 12 deletions(-)

diffs (truncated from 2551 to 1000 lines):

diff -r dc7d94c05669 -r 81040de6b085 nginx/config
--- a/nginx/config	Mon Jan 11 19:53:10 2021 +0000
+++ b/nginx/config	Thu Jan 21 18:44:58 2021 +0000
@@ -1,7 +1,8 @@
 ngx_addon_name="ngx_js_module"
 
 NJS_DEPS="$ngx_addon_dir/ngx_js.h"
-NJS_SRCS="$ngx_addon_dir/ngx_js.c"
+NJS_SRCS="$ngx_addon_dir/ngx_js.c \
+    $ngx_addon_dir/ngx_js_fetch.c"
 
 if [ $HTTP != NO ]; then
     ngx_module_type=HTTP
diff -r dc7d94c05669 -r 81040de6b085 nginx/ngx_http_js_module.c
--- a/nginx/ngx_http_js_module.c	Mon Jan 11 19:53:10 2021 +0000
+++ b/nginx/ngx_http_js_module.c	Thu Jan 21 18:44:58 2021 +0000
@@ -179,6 +179,13 @@ static njs_host_event_t ngx_http_js_set_
 static void ngx_http_js_clear_timer(njs_external_ptr_t external,
     njs_host_event_t event);
 static void ngx_http_js_timer_handler(ngx_event_t *ev);
+static ngx_pool_t *ngx_http_js_pool(njs_vm_t *vm, ngx_http_request_t *r);
+static ngx_resolver_t *ngx_http_js_resolver(njs_vm_t *vm,
+    ngx_http_request_t *r);
+static ngx_msec_t ngx_http_js_resolver_timeout(njs_vm_t *vm,
+    ngx_http_request_t *r);
+static void ngx_http_js_handle_vm_event(ngx_http_request_t *r,
+    njs_vm_event_t vm_event, njs_value_t *args, njs_uint_t nargs);
 static void ngx_http_js_handle_event(ngx_http_request_t *r,
     njs_vm_event_t vm_event, njs_value_t *args, njs_uint_t nargs);
 
@@ -576,11 +583,15 @@ static njs_vm_ops_t ngx_http_js_ops = {
 
 static uintptr_t ngx_http_js_uptr[] = {
     offsetof(ngx_http_request_t, connection),
+    (uintptr_t) ngx_http_js_pool,
+    (uintptr_t) ngx_http_js_resolver,
+    (uintptr_t) ngx_http_js_resolver_timeout,
+    (uintptr_t) ngx_http_js_handle_event,
 };
 
 
 static njs_vm_meta_t ngx_http_js_metas = {
-    .size = 1,
+    .size = 5,
     .values = ngx_http_js_uptr
 };
 
@@ -2754,7 +2765,7 @@ ngx_http_js_subrequest_done(ngx_http_req
         return NGX_ERROR;
     }
 
-    ngx_http_js_handle_event(r->parent, vm_event, njs_value_arg(&reply), 1);
+    ngx_http_js_handle_vm_event(r->parent, vm_event, njs_value_arg(&reply), 1);
 
     return NGX_OK;
 }
@@ -2895,7 +2906,6 @@ ngx_http_js_clear_timer(njs_external_ptr
 static void
 ngx_http_js_timer_handler(ngx_event_t *ev)
 {
-    ngx_connection_t     *c;
     ngx_http_request_t   *r;
     ngx_http_js_event_t  *js_event;
 
@@ -2903,16 +2913,41 @@ ngx_http_js_timer_handler(ngx_event_t *e
 
     r = js_event->request;
 
-    c = r->connection;
-
     ngx_http_js_handle_event(r, js_event->vm_event, NULL, 0);
-
-    ngx_http_run_posted_requests(c);
+}
+
+
+static ngx_pool_t *
+ngx_http_js_pool(njs_vm_t *vm, ngx_http_request_t *r)
+{
+    return r->pool;
+}
+
+
+static ngx_resolver_t *
+ngx_http_js_resolver(njs_vm_t *vm, ngx_http_request_t *r)
+{
+    ngx_http_core_loc_conf_t  *clcf;
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    return clcf->resolver;
+}
+
+
+static ngx_msec_t
+ngx_http_js_resolver_timeout(njs_vm_t *vm, ngx_http_request_t *r)
+{
+    ngx_http_core_loc_conf_t  *clcf;
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    return clcf->resolver_timeout;
 }
 
 
 static void
-ngx_http_js_handle_event(ngx_http_request_t *r, njs_vm_event_t vm_event,
+ngx_http_js_handle_vm_event(ngx_http_request_t *r, njs_vm_event_t vm_event,
     njs_value_t *args, njs_uint_t nargs)
 {
     njs_int_t           rc;
@@ -2925,6 +2960,10 @@ ngx_http_js_handle_event(ngx_http_reques
 
     rc = njs_vm_run(ctx->vm);
 
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http js post event handler rc: %i event: %p",
+                   (ngx_int_t) rc, vm_event);
+
     if (rc == NJS_ERROR) {
         njs_vm_retval_string(ctx->vm, &exception);
 
@@ -2940,6 +2979,16 @@ ngx_http_js_handle_event(ngx_http_reques
 }
 
 
+static void
+ngx_http_js_handle_event(ngx_http_request_t *r, njs_vm_event_t vm_event,
+    njs_value_t *args, njs_uint_t nargs)
+{
+    ngx_http_js_handle_vm_event(r, vm_event, args, nargs);
+
+    ngx_http_run_posted_requests(r->connection);
+}
+
+
 static char *
 ngx_http_js_init_main_conf(ngx_conf_t *cf, void *conf)
 {
diff -r dc7d94c05669 -r 81040de6b085 nginx/ngx_js.c
--- a/nginx/ngx_js.c	Mon Jan 11 19:53:10 2021 +0000
+++ b/nginx/ngx_js.c	Thu Jan 21 18:44:58 2021 +0000
@@ -9,6 +9,7 @@
 #include <ngx_config.h>
 #include <ngx_core.h>
 #include "ngx_js.h"
+#include "ngx_js_fetch.h"
 
 
 static njs_external_t  ngx_js_ext_core[] = {
@@ -50,6 +51,17 @@ static njs_external_t  ngx_js_ext_core[]
             .magic32 = NGX_LOG_ERR,
         }
     },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("fetch"),
+        .writable = 1,
+        .configurable = 1,
+        .enumerable = 1,
+        .u.method = {
+            .native = ngx_js_ext_fetch,
+        }
+    },
 };
 
 
@@ -117,10 +129,16 @@ ngx_js_string(njs_vm_t *vm, njs_value_t 
 ngx_int_t
 ngx_js_core_init(njs_vm_t *vm, ngx_log_t *log)
 {
+    ngx_int_t           rc;
     njs_int_t           ret, proto_id;
     njs_str_t           name;
     njs_opaque_value_t  value;
 
+    rc = ngx_js_fetch_init(vm, log);
+    if (rc != NGX_OK) {
+        return NGX_ERROR;
+    }
+
     proto_id = njs_vm_external_prototype(vm, ngx_js_ext_core,
                                          njs_nitems(ngx_js_ext_core));
     if (proto_id < 0) {
@@ -178,6 +196,16 @@ ngx_js_ext_constant(njs_vm_t *vm, njs_ob
 
 
 njs_int_t
+ngx_js_ext_boolean(njs_vm_t *vm, njs_object_prop_t *prop,
+    njs_value_t *value, njs_value_t *setval, njs_value_t *retval)
+{
+    njs_value_boolean_set(retval, njs_vm_prop_magic32(prop));
+
+    return NJS_OK;
+}
+
+
+njs_int_t
 ngx_js_ext_log(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     njs_index_t level)
 {
diff -r dc7d94c05669 -r 81040de6b085 nginx/ngx_js.h
--- a/nginx/ngx_js.h	Mon Jan 11 19:53:10 2021 +0000
+++ b/nginx/ngx_js.h	Thu Jan 21 18:44:58 2021 +0000
@@ -20,10 +20,28 @@
 #define NGX_JS_BUFFER  2
 
 #define NGX_JS_PROTO_MAIN      0
+#define NGX_JS_PROTO_RESPONSE  1
 
 
-#define ngx_external_connection(vm, ext)                                    \
-    (*((ngx_connection_t **) ((u_char *) ext + njs_vm_meta(vm, 0))))
+typedef ngx_pool_t *(*ngx_external_pool_pt)(njs_vm_t *vm, njs_external_ptr_t e);
+typedef void (*ngx_js_event_handler_pt)(njs_external_ptr_t e,
+	njs_vm_event_t vm_event, njs_value_t *args, njs_uint_t nargs);
+typedef ngx_resolver_t *(*ngx_external_resolver_pt)(njs_vm_t *vm,
+    njs_external_ptr_t e);
+typedef ngx_msec_t (*ngx_external_resolver_timeout_pt)(njs_vm_t *vm,
+    njs_external_ptr_t e);
+
+
+#define ngx_external_connection(vm, e)                                        \
+    (*((ngx_connection_t **) ((u_char *) (e) + njs_vm_meta(vm, 0))))
+#define ngx_external_pool(vm, e)                                              \
+	((ngx_external_pool_pt) njs_vm_meta(vm, 1))(vm, e)
+#define ngx_external_resolver(vm, e)                                          \
+	((ngx_external_resolver_pt) njs_vm_meta(vm, 2))(vm, e)
+#define ngx_external_resolver_timeout(vm, e)                                  \
+	((ngx_external_resolver_timeout_pt) njs_vm_meta(vm, 3))(vm, e)
+#define ngx_external_event_handler(vm, e)                                     \
+    ((ngx_js_event_handler_pt) njs_vm_meta(vm, 4))
 
 
 #define ngx_js_prop(vm, type, value, start, len)                              \
@@ -41,6 +59,8 @@ njs_int_t ngx_js_ext_string(njs_vm_t *vm
     njs_value_t *value, njs_value_t *setval, njs_value_t *retval);
 njs_int_t ngx_js_ext_constant(njs_vm_t *vm, njs_object_prop_t *prop,
     njs_value_t *value, njs_value_t *setval, njs_value_t *retval);
+njs_int_t ngx_js_ext_boolean(njs_vm_t *vm, njs_object_prop_t *prop,
+    njs_value_t *value, njs_value_t *setval, njs_value_t *retval);
 
 ngx_int_t ngx_js_core_init(njs_vm_t *vm, ngx_log_t *log);
 
diff -r dc7d94c05669 -r 81040de6b085 nginx/ngx_js_fetch.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nginx/ngx_js_fetch.c	Thu Jan 21 18:44:58 2021 +0000
@@ -0,0 +1,2212 @@
+
+/*
+ * Copyright (C) Dmitry Volyntsev
+ * Copyright (C) hongzhidao
+ * Copyright (C) NGINX, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_event_connect.h>
+#include "ngx_js.h"
+
+
+typedef struct ngx_js_http_s  ngx_js_http_t;
+
+
+typedef struct {
+    ngx_uint_t                     state;
+    ngx_uint_t                     code;
+    u_char                        *status_text;
+    u_char                        *status_text_end;
+    ngx_uint_t                     count;
+    ngx_flag_t                     chunked;
+    off_t                          content_length_n;
+
+    u_char                        *header_name_start;
+    u_char                        *header_name_end;
+    u_char                        *header_start;
+    u_char                        *header_end;
+} ngx_js_http_parse_t;
+
+
+typedef struct {
+    u_char                        *pos;
+    uint64_t                       chunk_size;
+    uint8_t                        state;
+    uint8_t                        last;
+} ngx_js_http_chunk_parse_t;
+
+
+struct ngx_js_http_s {
+    ngx_log_t                     *log;
+    ngx_pool_t                    *pool;
+
+    njs_vm_t                      *vm;
+    njs_external_ptr_t             external;
+    njs_vm_event_t                 vm_event;
+    ngx_js_event_handler_pt        event_handler;
+
+    ngx_resolver_ctx_t            *ctx;
+    ngx_addr_t                     addr;
+    ngx_addr_t                    *addrs;
+    ngx_uint_t                     naddrs;
+    ngx_uint_t                     naddr;
+    in_port_t                      port;
+
+    ngx_peer_connection_t          peer;
+    ngx_msec_t                     timeout;
+
+    ngx_int_t                      buffer_size;
+    ngx_int_t                      max_response_body_size;
+
+    njs_str_t                      url;
+    ngx_array_t                    headers;
+
+    ngx_buf_t                     *buffer;
+    ngx_buf_t                     *chunk;
+    njs_chb_t                      chain;
+
+    njs_opaque_value_t             reply;
+    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),     \
+                               fmt, ##__VA_ARGS__);                           \
+        ngx_js_http_fetch_done(http, &(http)->reply, NJS_ERROR);              \
+    } while (0)
+
+
+static ngx_js_http_t *ngx_js_http_alloc(njs_vm_t *vm, ngx_pool_t *pool,
+    ngx_log_t *log);
+static void ngx_js_resolve_handler(ngx_resolver_ctx_t *ctx);
+static njs_int_t ngx_js_fetch_result(njs_vm_t *vm, ngx_js_http_t *http,
+    njs_value_t *result, njs_int_t rc);
+static njs_int_t ngx_js_fetch_promissified_result(njs_vm_t *vm,
+    njs_value_t *result, njs_int_t rc);
+static void ngx_js_http_fetch_done(ngx_js_http_t *http,
+    njs_opaque_value_t *retval, njs_int_t rc);
+static njs_int_t ngx_js_http_promise_trampoline(njs_vm_t *vm,
+    njs_value_t *args, njs_uint_t nargs, njs_index_t unused);
+static njs_int_t ngx_js_http_connect(ngx_js_http_t *http);
+static njs_int_t 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 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);
+static ngx_int_t ngx_js_http_parse_status_line(ngx_js_http_parse_t *hp,
+    ngx_buf_t *b);
+static ngx_int_t ngx_js_http_parse_header_line(ngx_js_http_parse_t *hp,
+    ngx_buf_t *b);
+static ngx_int_t ngx_js_http_parse_chunked(ngx_js_http_chunk_parse_t *hcp,
+    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,
+    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,
+    njs_value_t *keys);
+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);
+static njs_int_t ngx_response_js_ext_status_text(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_ok(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_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_type(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_body(njs_vm_t *vm, njs_value_t *args,
+     njs_uint_t nargs, njs_index_t unused);
+
+
+static njs_external_t  ngx_js_ext_http_response_headers[] = {
+
+    {
+        .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL,
+        .name.symbol = NJS_SYMBOL_TO_STRING_TAG,
+        .u.property = {
+            .value = "Headers",
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("get"),
+        .writable = 1,
+        .configurable = 1,
+        .enumerable = 1,
+        .u.method = {
+            .native = ngx_response_js_ext_headers_get,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("getAll"),
+        .writable = 1,
+        .configurable = 1,
+        .enumerable = 1,
+        .u.method = {
+            .native = ngx_response_js_ext_headers_get,
+            .magic8 = 1
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("has"),
+        .writable = 1,
+        .configurable = 1,
+        .enumerable = 1,
+        .u.method = {
+            .native = ngx_response_js_ext_headers_has,
+        }
+    },
+
+};
+
+
+static njs_external_t  ngx_js_ext_http_response[] = {
+
+    {
+        .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL,
+        .name.symbol = NJS_SYMBOL_TO_STRING_TAG,
+        .u.property = {
+            .value = "Response",
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("arrayBuffer"),
+        .writable = 1,
+        .configurable = 1,
+        .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
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_PROPERTY,
+        .name.string = njs_str("bodyUsed"),
+        .enumerable = 1,
+        .u.property = {
+            .handler = ngx_response_js_ext_body_used,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_OBJECT,
+        .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,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("json"),
+        .writable = 1,
+        .configurable = 1,
+        .enumerable = 1,
+        .u.method = {
+            .native = ngx_response_js_ext_body,
+            .magic8 = NGX_JS_BODY_JSON
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_PROPERTY,
+        .name.string = njs_str("ok"),
+        .enumerable = 1,
+        .u.property = {
+            .handler = ngx_response_js_ext_ok,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_PROPERTY,
+        .name.string = njs_str("redirected"),
+        .enumerable = 1,
+        .u.property = {
+            .handler = ngx_js_ext_boolean,
+            .magic32 = 0,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_PROPERTY,
+        .name.string = njs_str("status"),
+        .enumerable = 1,
+        .u.property = {
+            .handler = ngx_response_js_ext_status,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_PROPERTY,
+        .name.string = njs_str("statusText"),
+        .enumerable = 1,
+        .u.property = {
+            .handler = ngx_response_js_ext_status_text,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_METHOD,
+        .name.string = njs_str("text"),
+        .writable = 1,
+        .configurable = 1,
+        .enumerable = 1,
+        .u.method = {
+            .native = ngx_response_js_ext_body,
+            .magic8 = NGX_JS_BODY_TEXT
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_PROPERTY,
+        .name.string = njs_str("type"),
+        .enumerable = 1,
+        .u.property = {
+            .handler = ngx_response_js_ext_type,
+        }
+    },
+
+    {
+        .flags = NJS_EXTERN_PROPERTY,
+        .name.string = njs_str("url"),
+        .enumerable = 1,
+        .u.property = {
+            .handler = ngx_js_ext_string,
+            .magic32 = offsetof(ngx_js_http_t, url),
+        }
+    },
+};
+
+
+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_pool_t          *pool;
+    njs_value_t         *init, *value, *headers, *keys;
+    ngx_js_http_t       *http;
+    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");
+    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");
+
+    external = njs_vm_external(vm, njs_argument(args, 0));
+    if (external == NULL) {
+        njs_vm_error(vm, "\"this\" is not an external");
+        return NJS_ERROR;
+    }
+
+    c = ngx_external_connection(vm, external);
+    pool = ngx_external_pool(vm, external);
+
+    http = ngx_js_http_alloc(vm, pool, c->log);
+    if (http == NULL) {
+        return NJS_ERROR;
+    }
+
+    http->external = external;
+    http->event_handler = ngx_external_event_handler(vm, external);
+    http->buffer_size = 4096;
+    http->max_response_body_size = 32 * 1024;
+
+    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;
+
+    } 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)
+               != NGX_OK)
+        {
+            goto fail;
+        }
+
+        value = njs_vm_object_prop(vm, init, &body_size_key, &lvalue);
+        if (value != NULL
+            && ngx_js_integer(vm, value, &http->max_response_body_size)
+               != NGX_OK)
+        {
+            goto fail;
+        }
+    }
+
+    njs_chb_init(&http->chain, njs_vm_memory_pool(vm));
+
+    njs_chb_append(&http->chain, method.start, method.length);
+    njs_chb_append_literal(&http->chain, " ");
+
+    if (u.uri.len == 0 || u.uri.data[0] != '/') {
+        njs_chb_append_literal(&http->chain, "/");
+    }
+
+    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, "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;
+            }
+
+            start++;
+
+            value = njs_vm_object_prop(vm, headers, &name, &lvalue);
+            if (ret != NJS_OK) {
+                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);
+        }
+    }
+
+    if (!has_host) {
+        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);
+    }
+
+    if (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);
+
+    } else {
+        njs_chb_append_literal(&http->chain, CRLF);
+    }
+
+    if (u.addrs == NULL) {
+        ctx = ngx_resolve_start(ngx_external_resolver(vm, external), NULL);
+        if (ctx == NULL) {
+            njs_vm_memory_error(vm);
+            return NJS_ERROR;
+        }
+
+        if (ctx == NGX_NO_RESOLVER) {
+            njs_vm_error(vm, "no resolver defined");
+            goto fail;
+        }
+
+        http->ctx = ctx;
+        http->port = u.port;
+
+        ctx->name = u.host;
+        ctx->handler = ngx_js_resolve_handler;
+        ctx->data = http;
+        ctx->timeout = ngx_external_resolver_timeout(vm, external);
+
+        ret = ngx_resolve_name(http->ctx);
+        if (ret != NGX_OK) {
+            http->ctx = NULL;
+            njs_vm_memory_error(vm);
+            return NJS_ERROR;
+        }
+
+    } else {
+        http->naddrs = 1;
+        ngx_memcpy(&http->addr, &u.addrs[0], sizeof(ngx_addr_t));
+        http->addrs = &http->addr;
+
+        ret = ngx_js_http_connect(http);
+    }
+
+    return ngx_js_fetch_result(vm, http, njs_value_arg(&http->reply), ret);
+
+fail:
+
+    return ngx_js_fetch_result(vm, http, njs_vm_retval(vm), NJS_ERROR);
+}
+
+
+static ngx_js_http_t *
+ngx_js_http_alloc(njs_vm_t *vm, ngx_pool_t *pool, ngx_log_t *log)
+{
+    ngx_js_http_t  *http;
+
+    http = ngx_pcalloc(pool, sizeof(ngx_js_http_t));
+    if (http == NULL) {
+        goto failed;
+    }
+
+    http->pool = pool;
+    http->log = log;
+    http->vm = vm;
+
+    http->timeout = 10000;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "js http alloc:%p", http);
+
+    return http;
+
+failed:
+
+    njs_vm_error(vm, "internal error");
+
+    return NULL;
+}
+
+
+static void
+ngx_js_resolve_handler(ngx_resolver_ctx_t *ctx)
+{
+    u_char           *p;
+    size_t            len;
+    socklen_t         socklen;
+    ngx_uint_t        i;
+    ngx_js_http_t    *http;
+    struct sockaddr  *sockaddr;
+
+    http = ctx->data;
+
+    if (ctx->state) {
+        ngx_js_http_error(http, 0, "\"%V\" could not be resolved (%i: %s)",
+                          &ctx->name, ctx->state,
+                          ngx_resolver_strerror(ctx->state));
+        return;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, http->log, 0,
+                   "http fetch resolved: \"%V\"", &ctx->name);
+
+#if (NGX_DEBUG)
+    {
+    u_char      text[NGX_SOCKADDR_STRLEN];
+    ngx_str_t   addr;
+    ngx_uint_t  i;
+
+    addr.data = text;
+
+    for (i = 0; i < ctx->naddrs; i++) {
+        addr.len = ngx_sock_ntop(ctx->addrs[i].sockaddr, ctx->addrs[i].socklen,
+                                 text, NGX_SOCKADDR_STRLEN, 0);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, http->log, 0,
+                       "name was resolved to \"%V\"", &addr);
+    }
+    }
+#endif
+
+    http->naddrs = ctx->naddrs;
+    http->addrs = ngx_pcalloc(http->pool, http->naddrs * sizeof(ngx_addr_t));
+
+    if (http->addrs == NULL) {
+        goto failed;
+    }
+
+    for (i = 0; i < ctx->naddrs; i++) {
+        socklen = ctx->addrs[i].socklen;
+
+        sockaddr = ngx_palloc(http->pool, socklen);
+        if (sockaddr == NULL) {
+            goto failed;
+        }
+
+        ngx_memcpy(sockaddr, ctx->addrs[i].sockaddr, socklen);
+        ngx_inet_set_port(sockaddr, http->port);
+
+        http->addrs[i].sockaddr = sockaddr;
+        http->addrs[i].socklen = socklen;
+
+        p = ngx_pnalloc(http->pool, NGX_SOCKADDR_STRLEN);
+        if (p == NULL) {
+            goto failed;
+        }
+
+        len = ngx_sock_ntop(sockaddr, socklen, p, NGX_SOCKADDR_STRLEN, 1);
+        http->addrs[i].name.len = len;
+        http->addrs[i].name.data = p;
+    }
+
+    ngx_resolve_name_done(ctx);
+    http->ctx = NULL;
+
+    (void) ngx_js_http_connect(http);
+
+    return;
+
+failed:
+
+    ngx_js_http_error(http, 0, "memory error");
+}
+
+
+static void
+njs_js_http_destructor(njs_external_ptr_t external, njs_host_event_t host)
+{
+    ngx_js_http_t  *http;
+
+    http = host;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, http->log, 0, "js http destructor:%p",
+                   http);
+
+    if (http->ctx != NULL) {
+        ngx_resolve_name_done(http->ctx);
+        http->ctx = NULL;
+    }
+
+    if (http->peer.connection != NULL) {
+        ngx_close_connection(http->peer.connection);
+        http->peer.connection = NULL;
+    }
+}
+
+
+static njs_int_t
+ngx_js_fetch_result(njs_vm_t *vm, ngx_js_http_t *http, njs_value_t *result,
+    njs_int_t rc)
+{
+    njs_int_t            ret;
+    njs_function_t      *callback;
+    njs_vm_event_t       vm_event;
+    njs_opaque_value_t   arguments[2];
+
+    ret = njs_vm_promise_create(vm, njs_value_arg(&http->promise),
+                                njs_value_arg(&http->promise_callbacks));
+    if (ret != NJS_OK) {
+        goto error;
+    }
+
+    callback = njs_vm_function_alloc(vm, ngx_js_http_promise_trampoline);
+    if (callback == NULL) {
+        goto error;
+    }
+
+    vm_event = njs_vm_add_event(vm, callback, 1, http, njs_js_http_destructor);
+    if (vm_event == NULL) {
+        goto error;
+    }
+
+    http->vm_event = vm_event;
+
+    if (rc == NJS_ERROR) {
+        njs_value_assign(&arguments[0], &http->promise_callbacks[1]);
+        njs_value_assign(&arguments[1], result);
+
+        ret = njs_vm_post_event(vm, vm_event, njs_value_arg(&arguments), 2);
+        if (ret == NJS_ERROR) {
+            goto error;
+        }
+    }
+
+    njs_vm_retval_set(vm, njs_value_arg(&http->promise));
+
+    return NJS_OK;
+
+error:
+
+    njs_vm_error(vm, "internal error");
+
+    return NJS_ERROR;
+}
+
+
+static njs_int_t
+ngx_js_fetch_promissified_result(njs_vm_t *vm, njs_value_t *result,
+    njs_int_t rc)
+{
+    njs_int_t            ret;
+    njs_function_t      *callback;
+    njs_vm_event_t       vm_event;
+    njs_opaque_value_t   retval, arguments[2];
+
+    ret = njs_vm_promise_create(vm, njs_value_arg(&retval),
+                                njs_value_arg(&arguments));
+    if (ret != NJS_OK) {
+        goto error;
+    }
+
+    callback = njs_vm_function_alloc(vm, ngx_js_http_promise_trampoline);


More information about the nginx-devel mailing list