diff --git a/nginx/config b/nginx/config
index 03ec03d0c..b994f97f3 100644
--- a/nginx/config
+++ b/nginx/config
@@ -6,9 +6,11 @@ NJS_ZLIB=${NJS_ZLIB:-YES}
 NJS_QUICKJS=${NJS_QUICKJS:-YES}
 
 NJS_DEPS="$ngx_addon_dir/ngx_js.h \
+    $ngx_addon_dir/ngx_js_http.h \
     $ngx_addon_dir/ngx_js_fetch.h \
     $ngx_addon_dir/ngx_js_shared_dict.h"
 NJS_SRCS="$ngx_addon_dir/ngx_js.c \
+    $ngx_addon_dir/ngx_js_http.c \
     $ngx_addon_dir/ngx_js_fetch.c \
     $ngx_addon_dir/ngx_js_regex.c \
     $ngx_addon_dir/ngx_js_shared_dict.c"
diff --git a/nginx/ngx_js.c b/nginx/ngx_js.c
index f91fcd993..5c71d1ae4 100644
--- a/nginx/ngx_js.c
+++ b/nginx/ngx_js.c
@@ -4493,3 +4493,19 @@ ngx_js_queue_pop(ngx_js_queue_t *queue)
 
     return item;
 }
+
+
+ngx_int_t
+ngx_njs_string(njs_vm_t *vm, njs_value_t *value, ngx_str_t *str)
+{
+    njs_str_t  s;
+
+    if (ngx_js_string(vm, value, &s) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    str->data = s.start;
+    str->len = s.length;
+
+    return NGX_OK;
+}
diff --git a/nginx/ngx_js.h b/nginx/ngx_js.h
index 0e811f44f..1dbb4c36b 100644
--- a/nginx/ngx_js.h
+++ b/nginx/ngx_js.h
@@ -422,6 +422,8 @@ ngx_js_queue_t *ngx_js_queue_create(ngx_pool_t *pool, ngx_uint_t capacity);
 ngx_int_t ngx_js_queue_push(ngx_js_queue_t *queue, void *item);
 void *ngx_js_queue_pop(ngx_js_queue_t *queue);
 
+ngx_int_t ngx_njs_string(njs_vm_t *vm, njs_value_t *value, ngx_str_t *str);
+
 
 extern njs_module_t  ngx_js_ngx_module;
 extern njs_module_t  njs_webcrypto_module;
diff --git a/nginx/ngx_js_fetch.c b/nginx/ngx_js_fetch.c
index 4902fe4fa..4b6f753d7 100644
--- a/nginx/ngx_js_fetch.c
+++ b/nginx/ngx_js_fetch.c
@@ -12,9 +12,7 @@
 #include <ngx_event.h>
 #include <ngx_event_connect.h>
 #include "ngx_js.h"
-
-
-typedef struct ngx_js_http_s  ngx_js_http_t;
+#include "ngx_js_http.h"
 
 
 typedef struct {
@@ -24,136 +22,16 @@ typedef struct {
 
 
 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;
-
-
-typedef struct ngx_js_tb_elt_s  ngx_js_tb_elt_t;
-
-struct ngx_js_tb_elt_s {
-    ngx_uint_t        hash;
-    ngx_str_t         key;
-    ngx_str_t         value;
-    ngx_js_tb_elt_t  *next;
-};
-
-
-typedef struct {
-    enum {
-        GUARD_NONE = 0,
-        GUARD_REQUEST,
-        GUARD_IMMUTABLE,
-        GUARD_RESPONSE,
-    }                              guard;
-    ngx_list_t                     header_list;
-    ngx_js_tb_elt_t               *content_type;
-} 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;
+    ngx_js_http_t                  http;
 
     njs_vm_t                      *vm;
     ngx_js_event_t                *event;
 
-    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;
-
-    unsigned                       header_only;
-
-#if (NGX_SSL)
-    ngx_str_t                      tls_name;
-    ngx_ssl_t                     *ssl;
-    njs_bool_t                     ssl_verify;
-#endif
-
-    ngx_buf_t                     *buffer;
-    ngx_buf_t                     *chunk;
-    njs_chb_t                      chain;
-
-    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;
-    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);
-};
+} ngx_js_fetch_t;
 
 
 static njs_int_t ngx_js_method_process(njs_vm_t *vm, ngx_js_request_t *r);
@@ -161,44 +39,29 @@ 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,
+static ngx_js_fetch_t *ngx_js_fetch_alloc(njs_vm_t *vm, ngx_pool_t *pool,
     ngx_log_t *log);
-static void ngx_js_http_resolve_done(ngx_js_http_t *http);
-static void ngx_js_http_close_peer(ngx_js_http_t *http);
-static void ngx_js_http_destructor(ngx_js_event_t *event);
-static ngx_resolver_ctx_t *ngx_js_http_resolve(ngx_js_http_t *http,
-    ngx_resolver_t *r, ngx_str_t *host, in_port_t port, ngx_msec_t timeout);
-static void ngx_js_http_resolve_handler(ngx_resolver_ctx_t *ctx);
+static void ngx_js_fetch_error(ngx_js_http_t *http, const char *err);
+static void ngx_js_fetch_destructor(ngx_js_event_t *event);
 static njs_int_t ngx_js_fetch_promissified_result(njs_vm_t *vm,
     njs_value_t *result, njs_int_t rc, njs_value_t *retval);
-static void ngx_js_http_fetch_done(ngx_js_http_t *http,
-    njs_opaque_value_t *retval, njs_int_t rc);
+static void ngx_js_fetch_done(ngx_js_fetch_t *fetch, 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,
     njs_value_t *retval);
-static void ngx_js_http_connect(ngx_js_http_t *http);
-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 ngx_int_t ngx_js_fetch_append_headers(ngx_js_http_t *http,
+    ngx_js_headers_t *headers, u_char *name, size_t len, u_char *value,
+    size_t vlen);
+static void ngx_js_fetch_process_done(ngx_js_http_t *http);
 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);
-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_headers_js_ext_append(njs_vm_t *vm, njs_value_t *args,
     njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
 static njs_int_t ngx_headers_js_ext_delete(njs_vm_t *vm, njs_value_t *args,
@@ -254,15 +117,6 @@ static njs_int_t ngx_response_js_ext_type(njs_vm_t *vm,
 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, njs_value_t *retval);
 
-#if (NGX_SSL)
-static void ngx_js_http_ssl_init_connection(ngx_js_http_t *http);
-static void ngx_js_http_ssl_handshake_handler(ngx_connection_t *c);
-static void ngx_js_http_ssl_handshake(ngx_js_http_t *http);
-static njs_int_t ngx_js_http_ssl_name(ngx_js_http_t *http);
-#endif
-
-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,
@@ -657,12 +511,14 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     njs_index_t unused, njs_value_t *retval)
 {
     njs_int_t            ret;
+    njs_str_t            str;
     ngx_url_t            u;
     ngx_uint_t           i;
     njs_bool_t           has_host;
     ngx_pool_t          *pool;
     njs_value_t         *init, *value;
     ngx_js_http_t       *http;
+    ngx_js_fetch_t      *fetch;
     ngx_list_part_t     *part;
     ngx_js_tb_elt_t     *h;
     ngx_js_request_t     request;
@@ -681,11 +537,13 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     c = ngx_external_connection(vm, external);
     pool = ngx_external_pool(vm, external);
 
-    http = ngx_js_http_alloc(vm, pool, c->log);
-    if (http == NULL) {
+    fetch = ngx_js_fetch_alloc(vm, pool, c->log);
+    if (fetch == NULL) {
         return NJS_ERROR;
     }
 
+    http = &fetch->http;
+
     ret = ngx_js_request_constructor(vm, &request, &u, external, args, nargs);
     if (ret != NJS_OK) {
         goto fail;
@@ -731,11 +589,15 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
 #endif
     }
 
-    http->header_only = njs_strstr_eq(&request.method, &njs_str_value("HEAD"));
+    str.start = request.method.data;
+    str.length = request.method.len;
+
+    http->header_only = njs_strstr_eq(&str, &njs_str_value("HEAD"));
 
     NJS_CHB_MP_INIT(&http->chain, njs_vm_memory_pool(vm));
+    NJS_CHB_MP_INIT(&http->response.chain, njs_vm_memory_pool(vm));
 
-    njs_chb_append(&http->chain, request.method.start, request.method.length);
+    njs_chb_append(&http->chain, request.method.data, request.method.len);
     njs_chb_append_literal(&http->chain, " ");
 
     if (u.uri.len == 0 || u.uri.data[0] != '/') {
@@ -825,10 +687,10 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     http->tls_name.len = u.host.len;
 #endif
 
-    if (request.body.length != 0) {
+    if (request.body.len != 0) {
         njs_chb_sprintf(&http->chain, 32, "Content-Length: %uz" CRLF CRLF,
-                        request.body.length);
-        njs_chb_append(&http->chain, request.body.start, request.body.length);
+                        request.body.len);
+        njs_chb_append(&http->chain, request.body.data, request.body.len);
 
     } else {
         njs_chb_append_literal(&http->chain, CRLF);
@@ -848,7 +710,7 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
             goto fail;
         }
 
-        njs_value_assign(retval, njs_value_arg(&http->promise));
+        njs_value_assign(retval, njs_value_arg(&fetch->promise));
 
         return NJS_OK;
     }
@@ -859,18 +721,17 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
 
     ngx_js_http_connect(http);
 
-    njs_value_assign(retval, njs_value_arg(&http->promise));
+    njs_value_assign(retval, njs_value_arg(&fetch->promise));
 
     return NJS_OK;
 
 fail:
 
-
     njs_vm_exception_get(vm, njs_value_arg(&lvalue));
 
-    ngx_js_http_fetch_done(http, &lvalue, NJS_ERROR);
+    ngx_js_fetch_done(fetch, &lvalue, NJS_ERROR);
 
-    njs_value_assign(retval, njs_value_arg(&http->promise));
+    njs_value_assign(retval, njs_value_arg(&fetch->promise));
 
     return NJS_OK;
 }
@@ -1004,13 +865,13 @@ ngx_js_ext_response_constructor(njs_vm_t *vm, njs_value_t *args,
 
         value = njs_vm_object_prop(vm, init, &status_text, &lvalue);
         if (value != NULL) {
-            if (ngx_js_string(vm, value, &response->status_text) != NGX_OK) {
+            if (ngx_njs_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;
+            p = response->status_text.data;
+            end = p + response->status_text.len;
 
             while (p < end) {
                 if (*p != '\t' && *p < ' ') {
@@ -1070,6 +931,7 @@ static njs_int_t
 ngx_js_method_process(njs_vm_t *vm, ngx_js_request_t *request)
 {
     u_char           *s, *p;
+    njs_str_t        str;
     const njs_str_t  *m;
 
     static const njs_str_t forbidden[] = {
@@ -1089,15 +951,18 @@ ngx_js_method_process(njs_vm_t *vm, ngx_js_request_t *request)
         njs_null_str,
     };
 
+    str.start = request->method.data;
+    str.length = request->method.len;
+
     for (m = &forbidden[0]; m->length != 0; m++) {
-        if (njs_strstr_case_eq(&request->method, m)) {
+        if (njs_strstr_case_eq(&str, 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)) {
+        if (njs_strstr_case_eq(&str, m)) {
             s = &request->m[0];
             p = m->start;
 
@@ -1105,8 +970,8 @@ ngx_js_method_process(njs_vm_t *vm, ngx_js_request_t *request)
                 *s++ = njs_upper_case(*p++);
             }
 
-            request->method.start = &request->m[0];
-            request->method.length = m->length;
+            request->method.data = &request->m[0];
+            request->method.len = m->length;
             break;
         }
     }
@@ -1249,30 +1114,36 @@ 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 ngx_js_fetch_t *
+ngx_js_fetch_alloc(njs_vm_t *vm, ngx_pool_t *pool, ngx_log_t *log)
 {
     njs_int_t        ret;
     ngx_js_ctx_t    *ctx;
     ngx_js_http_t   *http;
+    ngx_js_fetch_t  *fetch;
     ngx_js_event_t  *event;
     njs_function_t  *callback;
 
-    http = ngx_pcalloc(pool, sizeof(ngx_js_http_t));
-    if (http == NULL) {
+    fetch = ngx_pcalloc(pool, sizeof(ngx_js_fetch_t));
+    if (fetch == NULL) {
         goto failed;
     }
 
+    http = &fetch->http;
+
     http->pool = pool;
     http->log = log;
-    http->vm = vm;
 
     http->timeout = 10000;
 
     http->http_parse.content_length_n = -1;
 
-    ret = njs_vm_promise_create(vm, njs_value_arg(&http->promise),
-                                njs_value_arg(&http->promise_callbacks));
+    http->append_headers = ngx_js_fetch_append_headers;
+    http->ready_handler = ngx_js_fetch_process_done;
+    http->error_handler = ngx_js_fetch_error;
+
+    ret = njs_vm_promise_create(vm, njs_value_arg(&fetch->promise),
+                                njs_value_arg(&fetch->promise_callbacks));
     if (ret != NJS_OK) {
         goto failed;
     }
@@ -1291,17 +1162,18 @@ ngx_js_http_alloc(njs_vm_t *vm, ngx_pool_t *pool, ngx_log_t *log)
 
     event->ctx = vm;
     njs_value_function_set(njs_value_arg(&event->function), callback);
-    event->destructor = ngx_js_http_destructor;
+    event->destructor = ngx_js_fetch_destructor;
     event->fd = ctx->event_id++;
-    event->data = http;
+    event->data = fetch;
 
     ngx_js_add_event(ctx, event);
 
-    http->event = event;
+    fetch->vm = vm;
+    fetch->event = event;
 
-    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "js fetch alloc:%p", http);
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "js fetch alloc:%p", fetch);
 
-    return http;
+    return fetch;
 
 failed:
 
@@ -1312,194 +1184,31 @@ ngx_js_http_alloc(njs_vm_t *vm, ngx_pool_t *pool, ngx_log_t *log)
 
 
 static void
-ngx_js_http_error(ngx_js_http_t *http, const char *fmt, ...)
-{
-    u_char   *p, *end;
-    va_list   args;
-    u_char    err[NGX_MAX_ERROR_STR];
-
-    end = err + NGX_MAX_ERROR_STR - 1;
-
-    va_start(args, fmt);
-    p = njs_vsprintf(err, end, fmt, args);
-    *p = '\0';
-    va_end(args);
-
-    njs_vm_error(http->vm, (const char *) err);
-    njs_vm_exception_get(http->vm, njs_value_arg(&http->response_value));
-    ngx_js_http_fetch_done(http, &http->response_value, NJS_ERROR);
-}
-
-
-static ngx_resolver_ctx_t *
-ngx_js_http_resolve(ngx_js_http_t *http, ngx_resolver_t *r, ngx_str_t *host,
-    in_port_t port, ngx_msec_t timeout)
-{
-    ngx_int_t            ret;
-    ngx_resolver_ctx_t  *ctx;
-
-    ctx = ngx_resolve_start(r, NULL);
-    if (ctx == NULL) {
-        return NULL;
-    }
-
-    if (ctx == NGX_NO_RESOLVER) {
-        return ctx;
-    }
-
-    http->ctx = ctx;
-    http->port = port;
-
-    ctx->name = *host;
-    ctx->handler = ngx_js_http_resolve_handler;
-    ctx->data = http;
-    ctx->timeout = timeout;
-
-    ret = ngx_resolve_name(ctx);
-    if (ret != NGX_OK) {
-        http->ctx = NULL;
-        return NULL;
-    }
-
-    return ctx;
-}
-
-
-static void
-ngx_js_http_resolve_handler(ngx_resolver_ctx_t *ctx)
+ngx_js_fetch_error(ngx_js_http_t *http, const char *err)
 {
-    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, "\"%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_js_http_resolve_done(http);
-
-    ngx_js_http_connect(http);
-
-    return;
+    ngx_js_fetch_t  *fetch;
 
-failed:
-
-    ngx_js_http_error(http, "memory error");
-}
-
-
-static void
-ngx_js_http_close_connection(ngx_connection_t *c)
-{
-    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                   "js fetch close connection: %d", c->fd);
-
-#if (NGX_SSL)
-    if (c->ssl) {
-        c->ssl->no_wait_shutdown = 1;
-
-        if (ngx_ssl_shutdown(c) == NGX_AGAIN) {
-            c->ssl->handler = ngx_js_http_close_connection;
-            return;
-        }
-    }
-#endif
-
-    c->destroyed = 1;
-
-    ngx_close_connection(c);
-}
+    fetch = (ngx_js_fetch_t *) http;
 
+    njs_vm_error(fetch->vm, err);
 
-static void
-ngx_js_http_resolve_done(ngx_js_http_t *http)
-{
-    if (http->ctx != NULL) {
-        ngx_resolve_name_done(http->ctx);
-        http->ctx = NULL;
-    }
-}
+    njs_vm_exception_get(fetch->vm, njs_value_arg(&fetch->response_value));
 
-
-static void
-ngx_js_http_close_peer(ngx_js_http_t *http)
-{
-    if (http->peer.connection != NULL) {
-        ngx_js_http_close_connection(http->peer.connection);
-        http->peer.connection = NULL;
-    }
+    ngx_js_fetch_done(fetch, &fetch->response_value, NJS_ERROR);
 }
 
 
 static void
-ngx_js_http_destructor(ngx_js_event_t *event)
+ngx_js_fetch_destructor(ngx_js_event_t *event)
 {
-    ngx_js_http_t  *http;
+    ngx_js_http_t   *http;
+    ngx_js_fetch_t  *fetch;
 
-    http = event->data;
+    fetch = event->data;
+    http = &fetch->http;
 
     ngx_log_debug1(NGX_LOG_DEBUG_EVENT, http->log, 0, "js fetch destructor:%p",
-                   http);
+                   fetch);
 
     ngx_js_http_resolve_done(http);
     ngx_js_http_close_peer(http);
@@ -1552,26 +1261,29 @@ ngx_js_fetch_promissified_result(njs_vm_t *vm, njs_value_t *result,
 
 
 static void
-ngx_js_http_fetch_done(ngx_js_http_t *http, njs_opaque_value_t *retval,
+ngx_js_fetch_done(ngx_js_fetch_t *fetch, njs_opaque_value_t *retval,
     njs_int_t rc)
 {
     njs_vm_t            *vm;
     ngx_js_ctx_t        *ctx;
+    ngx_js_http_t       *http;
     ngx_js_event_t      *event;
     njs_opaque_value_t   arguments[2], *action;
 
+    http = &fetch->http;
+
     ngx_log_debug2(NGX_LOG_DEBUG_EVENT, http->log, 0,
-                   "js fetch done http:%p rc:%i", http, (ngx_int_t) rc);
+                   "js fetch done fetch:%p rc:%i", fetch, (ngx_int_t) rc);
 
     ngx_js_http_close_peer(http);
 
-    if (http->event != NULL) {
-        action = &http->promise_callbacks[(rc != NJS_OK)];
+    if (fetch->event != NULL) {
+        action = &fetch->promise_callbacks[(rc != NJS_OK)];
         njs_value_assign(&arguments[0], action);
         njs_value_assign(&arguments[1], retval);
 
-        vm = http->vm;
-        event = http->event;
+        vm = fetch->vm;
+        event = fetch->event;
 
         rc = ngx_js_call(vm, njs_value_function(njs_value_arg(&event->function)),
                          &arguments[0], 2);
@@ -1600,1627 +1312,349 @@ ngx_js_http_promise_trampoline(njs_vm_t *vm, njs_value_t *args,
 }
 
 
-static void
-ngx_js_http_connect(ngx_js_http_t *http)
+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)
 {
-    ngx_int_t    rc;
-    ngx_addr_t  *addr;
-
-    addr = &http->addrs[http->naddr];
-
-    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, http->log, 0,
-                   "js fetch connect %ui/%ui", http->naddr, http->naddrs);
-
-    http->peer.sockaddr = addr->sockaddr;
-    http->peer.socklen = addr->socklen;
-    http->peer.name = &addr->name;
-    http->peer.get = ngx_event_get_peer;
-    http->peer.log = http->log;
-    http->peer.log_error = NGX_ERROR_ERR;
-
-    rc = ngx_event_connect_peer(&http->peer);
+    njs_int_t            ret;
+    ngx_uint_t           rc;
+    ngx_pool_t          *pool;
+    njs_value_t         *input, *init, *value, *headers;
+    ngx_js_request_t    *orig;
+    njs_opaque_value_t   lvalue;
 
-    if (rc == NGX_ERROR) {
-        ngx_js_http_error(http, "connect failed");
-        return;
-    }
+    static const njs_str_t body_key = njs_str("body");
+    static const njs_str_t cache_key = njs_str("cache");
+    static const njs_str_t cred_key = njs_str("credentials");
+    static const njs_str_t headers_key = njs_str("headers");
+    static const njs_str_t mode_key = njs_str("mode");
+    static const njs_str_t method_key = njs_str("method");
 
-    if (rc == NGX_BUSY || rc == NGX_DECLINED) {
-        ngx_js_http_next(http);
-        return;
+    input = njs_arg(args, nargs, 1);
+    if (njs_value_is_undefined(input)) {
+        njs_vm_error(vm, "1st argument is required");
+        return NJS_ERROR;
     }
 
-    http->peer.connection->data = http;
-    http->peer.connection->pool = http->pool;
+    /*
+     * set by ngx_memzero():
+     *
+     *  request->url.length = 0;
+     *  request->body.length = 0;
+     *  request->cache_mode = CACHE_MODE_DEFAULT;
+     *  request->credentials = CREDENTIALS_SAME_ORIGIN;
+     *  request->mode = MODE_NO_CORS;
+     *  request->headers.content_type = NULL;
+     */
 
-    http->peer.connection->write->handler = ngx_js_http_write_handler;
-    http->peer.connection->read->handler = ngx_js_http_read_handler;
+    ngx_memzero(request, sizeof(ngx_js_request_t));
 
-    http->process = ngx_js_http_process_status_line;
+    request->method.data = (u_char *) "GET";
+    request->method.len = 3;
+    request->body.data = (u_char *) "";
+    request->body.len = 0;
+    request->headers.guard = GUARD_REQUEST;
 
-    ngx_add_timer(http->peer.connection->read, http->timeout);
-    ngx_add_timer(http->peer.connection->write, http->timeout);
+    pool = ngx_external_pool(vm, external);
 
-#if (NGX_SSL)
-    if (http->ssl != NULL && http->peer.connection->ssl == NULL) {
-        ngx_js_http_ssl_init_connection(http);
-        return;
+    rc = ngx_list_init(&request->headers.header_list, pool, 4,
+                       sizeof(ngx_js_tb_elt_t));
+    if (rc != NGX_OK) {
+        njs_vm_memory_error(vm);
+        return NJS_ERROR;
     }
-#endif
 
-    if (rc == NGX_OK) {
-        ngx_js_http_write_handler(http->peer.connection->write);
-    }
-}
+    if (njs_value_is_string(input)) {
+        ret = ngx_njs_string(vm, input, &request->url);
+        if (ret != NJS_OK) {
+            njs_vm_error(vm, "failed to convert url arg");
+            return NJS_ERROR;
+        }
 
+    } else {
+        orig = njs_vm_external(vm, ngx_http_js_fetch_request_proto_id, input);
+        if (orig == NULL) {
+            njs_vm_error(vm, "input is not string or a Request object");
+            return NJS_ERROR;
+        }
 
-#if (NGX_SSL)
+        request->url = orig->url;
+        request->method = orig->method;
+        request->body = orig->body;
+        request->body_used = orig->body_used;
+        request->cache_mode = orig->cache_mode;
+        request->credentials = orig->credentials;
+        request->mode = orig->mode;
 
-static void
-ngx_js_http_ssl_init_connection(ngx_js_http_t *http)
-{
-    ngx_int_t          rc;
-    ngx_connection_t  *c;
+        ret = ngx_js_headers_inherit(vm, &request->headers, &orig->headers);
+        if (ret != NJS_OK) {
+            return NJS_ERROR;
+        }
+    }
 
-    c = http->peer.connection;
+    ngx_js_http_trim(&request->url.data, &request->url.len, 1);
 
-    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, http->log, 0,
-                   "js fetch secure connect %ui/%ui", http->naddr,
-                   http->naddrs);
+    ngx_memzero(u, sizeof(ngx_url_t));
+
+    u->url.len = request->url.len;
+    u->url.data = request->url.data;
+    u->default_port = 80;
+    u->uri_part = 1;
+    u->no_resolve = 1;
 
-    if (ngx_ssl_create_connection(http->ssl, c, NGX_SSL_BUFFER|NGX_SSL_CLIENT)
-        != NGX_OK)
+    if (u->url.len > 7
+        && njs_strncasecmp(u->url.data, (u_char *) "http://", 7) == 0)
     {
-        ngx_js_http_error(http, "failed to create ssl connection");
-        return;
-    }
+        u->url.len -= 7;
+        u->url.data += 7;
 
-    c->sendfile = 0;
+#if (NGX_SSL)
+    } else if (u->url.len > 8
+        && njs_strncasecmp(u->url.data, (u_char *) "https://", 8) == 0)
+    {
+        u->url.len -= 8;
+        u->url.data += 8;
+        u->default_port = 443;
+#endif
 
-    if (ngx_js_http_ssl_name(http) != NGX_OK) {
-        ngx_js_http_error(http, "failed to create ssl connection");
-        return;
+    } else {
+        njs_vm_error(vm, "unsupported URL schema (only http or https are"
+                     " supported)");
+        return NJS_ERROR;
     }
 
-    c->log->action = "SSL handshaking to fetch target";
-
-    rc = ngx_ssl_handshake(c);
-
-    if (rc == NGX_AGAIN) {
-        c->data = http;
-        c->ssl->handler = ngx_js_http_ssl_handshake_handler;
-        return;
+    if (ngx_parse_url(pool, u) != NGX_OK) {
+        njs_vm_error(vm, "invalid url");
+        return NJS_ERROR;
     }
 
-    ngx_js_http_ssl_handshake(http);
-}
-
+    init = njs_arg(args, nargs, 2);
 
-static void
-ngx_js_http_ssl_handshake_handler(ngx_connection_t *c)
-{
-    ngx_js_http_t  *http;
+    if (njs_value_is_object(init)) {
+        value = njs_vm_object_prop(vm, init, &method_key, &lvalue);
+        if (value != NULL && ngx_njs_string(vm, value, &request->method)
+            != NGX_OK)
+        {
+            njs_vm_error(vm, "invalid Request method");
+            return NJS_ERROR;
+        }
 
-    http = c->data;
+        ret = ngx_js_method_process(vm, request);
+        if (ret != NJS_OK) {
+            return NJS_ERROR;
+        }
 
-    http->peer.connection->write->handler = ngx_js_http_write_handler;
-    http->peer.connection->read->handler = ngx_js_http_read_handler;
+        value = njs_vm_object_prop(vm, init, &cache_key, &lvalue);
+        if (value != NULL) {
+            ret = ngx_fetch_flag_set(vm, ngx_js_fetch_cache_modes, value,
+                                     "cache");
+            if (ret == NJS_ERROR) {
+                return NJS_ERROR;
+            }
 
-    ngx_js_http_ssl_handshake(http);
-}
-
-
-static void
-ngx_js_http_ssl_handshake(ngx_js_http_t *http)
-{
-    long               rc;
-    ngx_connection_t  *c;
-
-    c = http->peer.connection;
-
-    if (c->ssl->handshaked) {
-        if (http->ssl_verify) {
-            rc = SSL_get_verify_result(c->ssl->connection);
+            request->cache_mode = ret;
+        }
 
-            if (rc != X509_V_OK) {
-                ngx_log_error(NGX_LOG_ERR, c->log, 0,
-                              "js fetch SSL certificate verify error: (%l:%s)",
-                              rc, X509_verify_cert_error_string(rc));
-                goto failed;
+        value = njs_vm_object_prop(vm, init, &cred_key, &lvalue);
+        if (value != NULL) {
+            ret = ngx_fetch_flag_set(vm, ngx_js_fetch_credentials, value,
+                                     "credentials");
+            if (ret == NJS_ERROR) {
+                return NJS_ERROR;
             }
 
-            if (ngx_ssl_check_host(c, &http->tls_name) != NGX_OK) {
-                ngx_log_error(NGX_LOG_ERR, c->log, 0,
-                              "js fetch SSL certificate does not match \"%V\"",
-                              &http->tls_name);
-                goto failed;
-            }
+            request->credentials = ret;
         }
 
-        c->write->handler = ngx_js_http_write_handler;
-        c->read->handler = ngx_js_http_read_handler;
+        value = njs_vm_object_prop(vm, init, &mode_key, &lvalue);
+        if (value != NULL) {
+            ret = ngx_fetch_flag_set(vm, ngx_js_fetch_modes, value, "mode");
+            if (ret == NJS_ERROR) {
+                return NJS_ERROR;
+            }
 
-        if (c->read->ready) {
-            ngx_post_event(c->read, &ngx_posted_events);
+            request->mode = ret;
         }
 
-        http->process = ngx_js_http_process_status_line;
-        ngx_js_http_write_handler(c->write);
-
-        return;
-    }
-
-failed:
-
-    ngx_js_http_next(http);
- }
-
-
-static njs_int_t
-ngx_js_http_ssl_name(ngx_js_http_t *http)
-{
-#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
-    u_char  *p;
-
-    /* as per RFC 6066, literal IPv4 and IPv6 addresses are not permitted */
-    ngx_str_t  *name = &http->tls_name;
-
-    if (name->len == 0 || *name->data == '[') {
-        goto done;
-    }
-
-    if (ngx_inet_addr(name->data, name->len) != INADDR_NONE) {
-        goto done;
-    }
+        headers = njs_vm_object_prop(vm, init, &headers_key, &lvalue);
+        if (headers != NULL) {
+            if (!njs_value_is_object(headers)) {
+                njs_vm_error(vm, "Headers is not an object");
+                return NJS_ERROR;
+            }
 
-    /*
-     * SSL_set_tlsext_host_name() needs a null-terminated string,
-     * hence we explicitly null-terminate name here
-     */
+            /*
+             * There are no API to reset or destroy ngx_list,
+             * just allocating a new one.
+             */
 
-    p = ngx_pnalloc(http->pool, name->len + 1);
-    if (p == NULL) {
-        return NGX_ERROR;
-    }
+            ngx_memset(&request->headers, 0, sizeof(ngx_js_headers_t));
+            request->headers.guard = GUARD_REQUEST;
 
-    (void) ngx_cpystrn(p, name->data, name->len + 1);
+            rc = ngx_list_init(&request->headers.header_list, pool, 4,
+                               sizeof(ngx_js_tb_elt_t));
+            if (rc != NGX_OK) {
+                njs_vm_memory_error(vm);
+                return NJS_ERROR;
+            }
 
-    name->data = p;
+            ret = ngx_js_headers_fill(vm, &request->headers, headers);
+            if (ret != NJS_OK) {
+                return NJS_ERROR;
+            }
+        }
 
-    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, http->log, 0,
-                   "js fetch SSL server name: \"%s\"", name->data);
+        value = njs_vm_object_prop(vm, init, &body_key, &lvalue);
+        if (value != NULL) {
+            if (ngx_njs_string(vm, value, &request->body) != NGX_OK) {
+                njs_vm_error(vm, "invalid Request body");
+                return NJS_ERROR;
+            }
 
-    if (SSL_set_tlsext_host_name(http->peer.connection->ssl->connection,
-                                 (char *) name->data)
-        == 0)
-    {
-        ngx_ssl_error(NGX_LOG_ERR, http->log, 0,
-                      "SSL_set_tlsext_host_name(\"%s\") failed", name->data);
-        return NGX_ERROR;
+            if (request->headers.content_type == NULL
+                && njs_value_is_string(value))
+            {
+                ret = ngx_js_headers_append(vm, &request->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;
+                }
+            }
+        }
     }
 
-#endif
-done:
-
     return NJS_OK;
 }
 
-#endif
-
 
-static void
-ngx_js_http_next(ngx_js_http_t *http)
+static ngx_int_t
+ngx_js_fetch_append_headers(ngx_js_http_t *http, ngx_js_headers_t *headers,
+    u_char *name, size_t len, u_char *value, size_t vlen)
 {
-    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, http->log, 0, "js fetch next addr");
-
-    if (++http->naddr >= http->naddrs) {
-        ngx_js_http_error(http, "connect failed");
-        return;
-    }
+    ngx_js_fetch_t  *fetch;
 
-    if (http->peer.connection != NULL) {
-        ngx_js_http_close_connection(http->peer.connection);
-        http->peer.connection = NULL;
-    }
-
-    http->buffer = NULL;
+    fetch = (ngx_js_fetch_t *) http;
 
-    ngx_js_http_connect(http);
+    return ngx_js_headers_append(fetch->vm, headers, name, len, value, vlen);
 }
 
 
 static void
-ngx_js_http_write_handler(ngx_event_t *wev)
+ngx_js_fetch_process_done(ngx_js_http_t *http)
 {
-    ssize_t            n, size;
-    ngx_buf_t         *b;
-    ngx_js_http_t     *http;
-    ngx_connection_t  *c;
-
-    c = wev->data;
-    http = c->data;
-
-    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, wev->log, 0, "js fetch write handler");
-
-    if (wev->timedout) {
-        ngx_js_http_error(http, "write timed out");
-        return;
-    }
-
-#if (NGX_SSL)
-    if (http->ssl != NULL && http->peer.connection->ssl == NULL) {
-        ngx_js_http_ssl_init_connection(http);
-        return;
-    }
-#endif
-
-    b = http->buffer;
-
-    if (b == NULL) {
-        size = njs_chb_size(&http->chain);
-        if (size < 0) {
-            ngx_js_http_error(http, "memory error");
-            return;
-        }
+    njs_int_t       ret;
+    ngx_js_fetch_t  *fetch;
 
-        b = ngx_create_temp_buf(http->pool, size);
-        if (b == NULL) {
-            ngx_js_http_error(http, "memory error");
-            return;
-        }
-
-        njs_chb_join_to(&http->chain, b->last);
-        b->last += size;
-
-        http->buffer = b;
-    }
+    fetch = (ngx_js_fetch_t *) http;
 
-    size = b->last - b->pos;
-
-    n = c->send(c, b->pos, size);
-
-    if (n == NGX_ERROR) {
-        ngx_js_http_next(http);
+    ret = njs_vm_external_create(fetch->vm,
+                                 njs_value_arg(&fetch->response_value),
+                                 ngx_http_js_fetch_response_proto_id,
+                                 &fetch->http.response, 0);
+    if (ret != NJS_OK) {
+        ngx_js_fetch_error(http, "fetch response creation failed");
         return;
     }
 
-    if (n > 0) {
-        b->pos += n;
-
-        if (n == size) {
-            wev->handler = ngx_js_http_dummy_handler;
-
-            http->buffer = NULL;
-
-            if (wev->timer_set) {
-                ngx_del_timer(wev);
-            }
-
-            if (ngx_handle_write_event(wev, 0) != NGX_OK) {
-                ngx_js_http_error(http, "write failed");
-            }
-
-            return;
-        }
-    }
-
-    if (!wev->timer_set) {
-        ngx_add_timer(wev, http->timeout);
-    }
+    ngx_js_fetch_done(fetch, &fetch->response_value, NJS_OK);
 }
 
 
-static void
-ngx_js_http_read_handler(ngx_event_t *rev)
+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)
 {
-    ssize_t            n, size;
-    ngx_int_t          rc;
-    ngx_buf_t         *b;
-    ngx_js_http_t     *http;
-    ngx_connection_t  *c;
-
-    c = rev->data;
-    http = c->data;
+    u_char           *p, *end;
+    ngx_int_t         ret;
+    ngx_uint_t        i;
+    ngx_js_tb_elt_t  *h, **ph;
+    ngx_list_part_t  *part;
 
-    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, "js fetch read handler");
+    ngx_js_http_trim(&value, &vlen, 0);
 
-    if (rev->timedout) {
-        ngx_js_http_error(http, "read timed out");
-        return;
+    ret = ngx_js_check_header_name(name, len);
+    if (ret != NGX_OK) {
+        njs_vm_error(vm, "invalid header name");
+        return NJS_ERROR;
     }
 
-    if (http->buffer == NULL) {
-        b = ngx_create_temp_buf(http->pool, http->buffer_size);
-        if (b == NULL) {
-            ngx_js_http_error(http, "memory error");
-            return;
+    p = value;
+    end = p + vlen;
+
+    while (p < end) {
+        if (*p == '\0') {
+            njs_vm_error(vm, "invalid header value");
+            return NJS_ERROR;
         }
 
-        http->buffer = b;
+        p++;
     }
 
-    for ( ;; ) {
-        b = http->buffer;
-        size = b->end - b->last;
-
-        n = c->recv(c, b->last, size);
+    if (headers->guard == GUARD_IMMUTABLE) {
+        njs_vm_error(vm, "cannot append to immutable object");
+        return NJS_ERROR;
+    }
 
-        if (n > 0) {
-            b->last += n;
+    ph = NULL;
+    part = &headers->header_list.part;
+    h = part->elts;
 
-            rc = http->process(http);
+    for (i = 0; /* void */; i++) {
 
-            if (rc == NGX_ERROR) {
-                return;
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
             }
 
-            continue;
+            part = part->next;
+            h = part->elts;
+            i = 0;
         }
 
-        if (n == NGX_AGAIN) {
-            if (ngx_handle_read_event(rev, 0) != NGX_OK) {
-                ngx_js_http_error(http, "read failed");
-            }
-
-            return;
+        if (h[i].hash == 0) {
+            continue;
         }
 
-        if (n == NGX_ERROR) {
-            ngx_js_http_next(http);
-            return;
+        if (len == h[i].key.len
+            && (njs_strncasecmp(name, h[i].key.data, len) == 0))
+        {
+            ph = &h[i].next;
+            while (*ph) { ph = &(*ph)->next; }
+            break;
         }
-
-        break;
-    }
-
-    http->done = 1;
-
-    rc = http->process(http);
-
-    if (rc == NGX_DONE) {
-        /* handler was called */
-        return;
-    }
-
-    if (rc == NGX_AGAIN) {
-        ngx_js_http_error(http, "prematurely closed connection");
     }
-}
-
-
-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)
-{
-    njs_int_t            ret;
-    ngx_uint_t           rc;
-    ngx_pool_t          *pool;
-    njs_value_t         *input, *init, *value, *headers;
-    ngx_js_request_t    *orig;
-    njs_opaque_value_t   lvalue;
-
-    static const njs_str_t body_key = njs_str("body");
-    static const njs_str_t cache_key = njs_str("cache");
-    static const njs_str_t cred_key = njs_str("credentials");
-    static const njs_str_t headers_key = njs_str("headers");
-    static const njs_str_t mode_key = njs_str("mode");
-    static const njs_str_t method_key = njs_str("method");
 
-    input = njs_arg(args, nargs, 1);
-    if (njs_value_is_undefined(input)) {
-        njs_vm_error(vm, "1st argument is required");
+    h = ngx_list_push(&headers->header_list);
+    if (h == NULL) {
+        njs_vm_memory_error(vm);
         return NJS_ERROR;
     }
 
-    /*
-     * set by ngx_memzero():
-     *
-     *  request->url.length = 0;
-     *  request->body.length = 0;
-     *  request->cache_mode = CACHE_MODE_DEFAULT;
-     *  request->credentials = CREDENTIALS_SAME_ORIGIN;
-     *  request->mode = MODE_NO_CORS;
-     *  request->headers.content_type = NULL;
-     */
-
-    ngx_memzero(request, sizeof(ngx_js_request_t));
-
-    request->method = njs_str_value("GET");
-    request->body = njs_str_value("");
-    request->headers.guard = GUARD_REQUEST;
+    if (ph != NULL) {
+        *ph = h;
+    }
 
-    pool = ngx_external_pool(vm, external);
+    h->hash = 1;
+    h->key.data = name;
+    h->key.len = len;
+    h->value.data = value;
+    h->value.len = vlen;
+    h->next = NULL;
 
-    rc = ngx_list_init(&request->headers.header_list, pool, 4,
-                       sizeof(ngx_js_tb_elt_t));
-    if (rc != NGX_OK) {
-        njs_vm_memory_error(vm);
-        return NJS_ERROR;
-    }
-
-    if (njs_value_is_string(input)) {
-        ret = ngx_js_string(vm, input, &request->url);
-        if (ret != NJS_OK) {
-            njs_vm_error(vm, "failed to convert url arg");
-            return NJS_ERROR;
-        }
-
-    } else {
-        orig = njs_vm_external(vm, ngx_http_js_fetch_request_proto_id, input);
-        if (orig == NULL) {
-            njs_vm_error(vm, "input is not string or a Request object");
-            return NJS_ERROR;
-        }
-
-        request->url = orig->url;
-        request->method = orig->method;
-        request->body = orig->body;
-        request->body_used = orig->body_used;
-        request->cache_mode = orig->cache_mode;
-        request->credentials = orig->credentials;
-        request->mode = orig->mode;
-
-        ret = ngx_js_headers_inherit(vm, &request->headers, &orig->headers);
-        if (ret != NJS_OK) {
-            return NJS_ERROR;
-        }
-    }
-
-    ngx_js_http_trim(&request->url.start, &request->url.length, 1);
-
-    ngx_memzero(u, sizeof(ngx_url_t));
-
-    u->url.len = request->url.length;
-    u->url.data = request->url.start;
-    u->default_port = 80;
-    u->uri_part = 1;
-    u->no_resolve = 1;
-
-    if (u->url.len > 7
-        && njs_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
-        && njs_strncasecmp(u->url.data, (u_char *) "https://", 8) == 0)
-    {
-        u->url.len -= 8;
-        u->url.data += 8;
-        u->default_port = 443;
-#endif
-
-    } else {
-        njs_vm_error(vm, "unsupported URL schema (only http or https are"
-                     " supported)");
-        return NJS_ERROR;
-    }
-
-    if (ngx_parse_url(pool, u) != NGX_OK) {
-        njs_vm_error(vm, "invalid url");
-        return NJS_ERROR;
-    }
-
-    init = njs_arg(args, nargs, 2);
-
-    if (njs_value_is_object(init)) {
-        value = njs_vm_object_prop(vm, init, &method_key, &lvalue);
-        if (value != NULL && ngx_js_string(vm, value, &request->method)
-            != NGX_OK)
-        {
-            njs_vm_error(vm, "invalid Request method");
-            return NJS_ERROR;
-        }
-
-        ret = ngx_js_method_process(vm, request);
-        if (ret != NJS_OK) {
-            return NJS_ERROR;
-        }
-
-        value = njs_vm_object_prop(vm, init, &cache_key, &lvalue);
-        if (value != NULL) {
-            ret = ngx_fetch_flag_set(vm, ngx_js_fetch_cache_modes, value,
-                                     "cache");
-            if (ret == NJS_ERROR) {
-                return NJS_ERROR;
-            }
-
-            request->cache_mode = ret;
-        }
-
-        value = njs_vm_object_prop(vm, init, &cred_key, &lvalue);
-        if (value != NULL) {
-            ret = ngx_fetch_flag_set(vm, ngx_js_fetch_credentials, value,
-                                     "credentials");
-            if (ret == NJS_ERROR) {
-                return NJS_ERROR;
-            }
-
-            request->credentials = ret;
-        }
-
-        value = njs_vm_object_prop(vm, init, &mode_key, &lvalue);
-        if (value != NULL) {
-            ret = ngx_fetch_flag_set(vm, ngx_js_fetch_modes, value, "mode");
-            if (ret == NJS_ERROR) {
-                return NJS_ERROR;
-            }
-
-            request->mode = ret;
-        }
-
-        headers = njs_vm_object_prop(vm, init, &headers_key, &lvalue);
-        if (headers != NULL) {
-            if (!njs_value_is_object(headers)) {
-                njs_vm_error(vm, "Headers is not an object");
-                return NJS_ERROR;
-            }
-
-            /*
-             * There are no API to reset or destroy ngx_list,
-             * just allocating a new one.
-             */
-
-            ngx_memset(&request->headers, 0, sizeof(ngx_js_headers_t));
-            request->headers.guard = GUARD_REQUEST;
-
-            rc = ngx_list_init(&request->headers.header_list, pool, 4,
-                               sizeof(ngx_js_tb_elt_t));
-            if (rc != NGX_OK) {
-                njs_vm_memory_error(vm);
-                return NJS_ERROR;
-            }
-
-            ret = ngx_js_headers_fill(vm, &request->headers, headers);
-            if (ret != NJS_OK) {
-                return NJS_ERROR;
-            }
-        }
-
-        value = njs_vm_object_prop(vm, init, &body_key, &lvalue);
-        if (value != NULL) {
-            if (ngx_js_string(vm, value, &request->body) != NGX_OK) {
-                njs_vm_error(vm, "invalid Request body");
-                return NJS_ERROR;
-            }
-
-            if (request->headers.content_type == NULL
-                && njs_value_is_string(value))
-            {
-                ret = ngx_js_headers_append(vm, &request->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;
-                }
-            }
-        }
+    if (len == njs_strlen("Content-Type")
+        && ngx_strncasecmp(name, (u_char *) "Content-Type", len) == 0)
+    {
+        headers->content_type = h;
     }
 
     return NJS_OK;
 }
 
 
-njs_inline njs_int_t
-ngx_js_http_whitespace(u_char c)
-{
-    switch (c) {
-    case 0x09:  /* <TAB>  */
-    case 0x0A:  /* <LF>   */
-    case 0x0D:  /* <CR>   */
-    case 0x20:  /* <SP>   */
-        return 1;
-
-    default:
-        return 0;
-    }
-}
-
-
-static void
-ngx_js_http_trim(u_char **value, size_t *len,
-    njs_bool_t trim_c0_control_or_space)
-{
-    u_char  *start, *end;
-
-    start = *value;
-    end = start + *len;
-
-    for ( ;; ) {
-        if (start == end) {
-            break;
-        }
-
-        if (ngx_js_http_whitespace(*start)
-            || (trim_c0_control_or_space && *start <= ' '))
-        {
-            start++;
-            continue;
-        }
-
-        break;
-    }
-
-    for ( ;; ) {
-        if (start == end) {
-            break;
-        }
-
-        end--;
-
-        if (ngx_js_http_whitespace(*end)
-            || (trim_c0_control_or_space && *end <= ' '))
-        {
-            continue;
-        }
-
-        end++;
-        break;
-    }
-
-    *value = start;
-    *len = end - start;
-}
-
-
-static const uint32_t  token_map[] = {
-    0x00000000,  /* 0000 0000 0000 0000  0000 0000 0000 0000 */
-
-                 /* ?>=< ;:98 7654 3210  /.-, +*)( '&%$ #"!  */
-    0x03ff6cfa,  /* 0000 0011 1111 1111  0110 1100 1111 1010 */
-
-                 /* _^]\ [ZYX WVUT SRQP  ONML KJIH GFED CBA@ */
-    0xc7fffffe,  /* 1100 0111 1111 1111  1111 1111 1111 1110 */
-
-                 /*  ~}| {zyx wvut srqp  onml kjih gfed cba` */
-    0x57ffffff,  /* 0101 0111 1111 1111  1111 1111 1111 1111 */
-
-    0x00000000,  /* 0000 0000 0000 0000  0000 0000 0000 0000 */
-    0x00000000,  /* 0000 0000 0000 0000  0000 0000 0000 0000 */
-    0x00000000,  /* 0000 0000 0000 0000  0000 0000 0000 0000 */
-    0x00000000,  /* 0000 0000 0000 0000  0000 0000 0000 0000 */
-};
-
-
-njs_inline njs_bool_t
-njs_is_token(uint32_t byte)
-{
-    return ((token_map[byte >> 5] & ((uint32_t) 1 << (byte & 0x1f))) != 0);
-}
-
-
-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)
-{
-    u_char           *p, *end;
-    ngx_uint_t        i;
-    ngx_js_tb_elt_t  *h, **ph;
-    ngx_list_part_t  *part;
-
-    ngx_js_http_trim(&value, &vlen, 0);
-
-    p = name;
-    end = p + len;
-
-    while (p < end) {
-        if (!njs_is_token(*p)) {
-            njs_vm_error(vm, "invalid header name");
-            return NJS_ERROR;
-        }
-
-        p++;
-    }
-
-    p = value;
-    end = p + vlen;
-
-    while (p < end) {
-        if (*p == '\0') {
-            njs_vm_error(vm, "invalid header value");
-            return NJS_ERROR;
-        }
-
-        p++;
-    }
-
-    if (headers->guard == GUARD_IMMUTABLE) {
-        njs_vm_error(vm, "cannot append to immutable object");
-        return NJS_ERROR;
-    }
-
-    ph = NULL;
-    part = &headers->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) {
-            continue;
-        }
-
-        if (len == h[i].key.len
-            && (njs_strncasecmp(name, h[i].key.data, len) == 0))
-        {
-            ph = &h[i].next;
-            while (*ph) { ph = &(*ph)->next; }
-            break;
-        }
-    }
-
-    h = ngx_list_push(&headers->header_list);
-    if (h == NULL) {
-        njs_vm_memory_error(vm);
-        return NJS_ERROR;
-    }
-
-    if (ph != NULL) {
-        *ph = h;
-    }
-
-    h->hash = 1;
-    h->key.data = name;
-    h->key.len = len;
-    h->value.data = value;
-    h->value.len = vlen;
-    h->next = NULL;
-
-    if (len == njs_strlen("Content-Type")
-        && ngx_strncasecmp(name, (u_char *) "Content-Type", len) == 0)
-    {
-        headers->content_type = h;
-    }
-
-    return NJS_OK;
-}
-
-
-static ngx_int_t
-ngx_js_http_process_status_line(ngx_js_http_t *http)
-{
-    ngx_int_t             rc;
-    ngx_js_http_parse_t  *hp;
-
-    hp = &http->http_parse;
-
-    rc = ngx_js_http_parse_status_line(hp, http->buffer);
-
-    if (rc == NGX_OK) {
-        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, http->log, 0, "js fetch status %ui",
-                       hp->code);
-
-        http->response.code = hp->code;
-        http->response.status_text.start = hp->status_text;
-        http->response.status_text.length = hp->status_text_end
-                                                             - hp->status_text;
-        http->process = ngx_js_http_process_headers;
-
-        return http->process(http);
-    }
-
-    if (rc == NGX_AGAIN) {
-        return NGX_AGAIN;
-    }
-
-    /* rc == NGX_ERROR */
-
-    ngx_js_http_error(http, "invalid fetch status line");
-
-    return NGX_ERROR;
-}
-
-
-static ngx_int_t
-ngx_js_http_process_headers(ngx_js_http_t *http)
-{
-    size_t                len, vlen;
-    ngx_int_t             rc;
-    njs_int_t             ret;
-    ngx_js_http_parse_t  *hp;
-
-    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, http->log, 0,
-                   "js fetch process headers");
-
-    hp = &http->http_parse;
-
-    if (http->response.headers.header_list.size == 0) {
-        rc = ngx_list_init(&http->response.headers.header_list, http->pool, 4,
-                           sizeof(ngx_js_tb_elt_t));
-        if (rc != NGX_OK) {
-            ngx_js_http_error(http, "alloc failed");
-            return NGX_ERROR;
-        }
-    }
-
-    for ( ;; ) {
-        rc = ngx_js_http_parse_header_line(hp, http->buffer);
-
-        if (rc == NGX_OK) {
-            len = hp->header_name_end - hp->header_name_start;
-            vlen = hp->header_end - hp->header_start;
-
-            ret = ngx_js_headers_append(http->vm, &http->response.headers,
-                                        hp->header_name_start, len,
-                                        hp->header_start, vlen);
-
-            if (ret == NJS_ERROR) {
-                ngx_js_http_error(http, "cannot add respose header");
-                return NGX_ERROR;
-            }
-
-            ngx_log_debug4(NGX_LOG_DEBUG_EVENT, http->log, 0,
-                           "js fetch header \"%*s: %*s\"",
-                           len, hp->header_name_start, vlen, hp->header_start);
-
-            if (len == njs_strlen("Transfer-Encoding")
-                && vlen == njs_strlen("chunked")
-                && ngx_strncasecmp(hp->header_name_start,
-                                   (u_char *) "Transfer-Encoding", len) == 0
-                && ngx_strncasecmp(hp->header_start, (u_char *) "chunked",
-                                   vlen) == 0)
-            {
-                hp->chunked = 1;
-            }
-
-            if (len == njs_strlen("Content-Length")
-                && ngx_strncasecmp(hp->header_name_start,
-                                   (u_char *) "Content-Length", len) == 0)
-            {
-                hp->content_length_n = ngx_atoof(hp->header_start, vlen);
-                if (hp->content_length_n == NGX_ERROR) {
-                    ngx_js_http_error(http, "invalid fetch content length");
-                    return NGX_ERROR;
-                }
-
-                if (!http->header_only
-                    && hp->content_length_n
-                       > (off_t) http->max_response_body_size)
-                {
-                    ngx_js_http_error(http,
-                                      "fetch content length is too large");
-                    return NGX_ERROR;
-                }
-            }
-
-            continue;
-        }
-
-        if (rc == NGX_DONE) {
-            http->response.headers.guard = GUARD_IMMUTABLE;
-            break;
-        }
-
-        if (rc == NGX_AGAIN) {
-            return NGX_AGAIN;
-        }
-
-        /* rc == NGX_ERROR */
-
-        ngx_js_http_error(http, "invalid fetch header");
-
-        return NGX_ERROR;
-    }
-
-    njs_chb_destroy(&http->chain);
-
-    NJS_CHB_MP_INIT(&http->response.chain, njs_vm_memory_pool(http->vm));
-
-    http->process = ngx_js_http_process_body;
-
-    return http->process(http);
-}
-
-
-static ngx_int_t
-ngx_js_http_process_body(ngx_js_http_t *http)
-{
-    ssize_t     size, chsize, need;
-    ngx_int_t   rc;
-    njs_int_t   ret;
-    ngx_buf_t  *b;
-
-    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, http->log, 0,
-                   "js fetch process body done:%ui", (ngx_uint_t) http->done);
-
-    if (http->done) {
-        size = njs_chb_size(&http->response.chain);
-        if (size < 0) {
-            ngx_js_http_error(http, "memory error");
-            return NGX_ERROR;
-        }
-
-        if (!http->header_only
-            && http->http_parse.chunked
-            && http->http_parse.content_length_n == -1)
-        {
-            ngx_js_http_error(http, "invalid fetch chunked response");
-            return NGX_ERROR;
-        }
-
-        if (http->header_only
-            || http->http_parse.content_length_n == -1
-            || size == http->http_parse.content_length_n)
-        {
-            ret = njs_vm_external_create(http->vm,
-                                         njs_value_arg(&http->response_value),
-                                         ngx_http_js_fetch_response_proto_id,
-                                         &http->response, 0);
-            if (ret != NJS_OK) {
-                ngx_js_http_error(http, "fetch response creation failed");
-                return NGX_ERROR;
-            }
-
-            ngx_js_http_fetch_done(http, &http->response_value, NJS_OK);
-            return NGX_DONE;
-        }
-
-        if (size < http->http_parse.content_length_n) {
-            return NGX_AGAIN;
-        }
-
-        ngx_js_http_error(http, "fetch trailing data");
-        return NGX_ERROR;
-    }
-
-    b = http->buffer;
-
-    if (http->http_parse.chunked) {
-        rc = ngx_js_http_parse_chunked(&http->http_chunk_parse, b,
-                                       &http->response.chain);
-        if (rc == NGX_ERROR) {
-            ngx_js_http_error(http, "invalid fetch chunked response");
-            return NGX_ERROR;
-        }
-
-        size = njs_chb_size(&http->response.chain);
-
-        if (rc == NGX_OK) {
-            http->http_parse.content_length_n = size;
-        }
-
-        if (size > http->max_response_body_size * 10) {
-            ngx_js_http_error(http, "very large fetch chunked response");
-            return NGX_ERROR;
-        }
-
-        b->pos = http->http_chunk_parse.pos;
-
-    } else {
-        size = njs_chb_size(&http->response.chain);
-
-        if (http->header_only) {
-            need = 0;
-
-        } else  if (http->http_parse.content_length_n == -1) {
-            need = http->max_response_body_size - size;
-
-        } else {
-            need = http->http_parse.content_length_n - size;
-        }
-
-        chsize = ngx_min(need, b->last - b->pos);
-
-        if (size + chsize > http->max_response_body_size) {
-            ngx_js_http_error(http, "fetch response body is too large");
-            return NGX_ERROR;
-        }
-
-        if (chsize > 0) {
-            njs_chb_append(&http->response.chain, b->pos, chsize);
-            b->pos += chsize;
-        }
-
-        rc = (need > chsize) ? NGX_AGAIN : NGX_DONE;
-    }
-
-    if (b->pos == b->end) {
-        if (http->chunk == NULL) {
-            b = ngx_create_temp_buf(http->pool, http->buffer_size);
-            if (b == NULL) {
-                ngx_js_http_error(http, "memory error");
-                return NGX_ERROR;
-            }
-
-            http->buffer = b;
-            http->chunk = b;
-
-        } else {
-            b->last = b->start;
-            b->pos = b->start;
-        }
-    }
-
-    return rc;
-}
-
-
-static ngx_int_t
-ngx_js_http_parse_status_line(ngx_js_http_parse_t *hp, ngx_buf_t *b)
-{
-    u_char   ch;
-    u_char  *p;
-    enum {
-        sw_start = 0,
-        sw_H,
-        sw_HT,
-        sw_HTT,
-        sw_HTTP,
-        sw_first_major_digit,
-        sw_major_digit,
-        sw_first_minor_digit,
-        sw_minor_digit,
-        sw_status,
-        sw_space_after_status,
-        sw_status_text,
-        sw_almost_done
-    } state;
-
-    state = hp->state;
-
-    for (p = b->pos; p < b->last; p++) {
-        ch = *p;
-
-        switch (state) {
-
-        /* "HTTP/" */
-        case sw_start:
-            switch (ch) {
-            case 'H':
-                state = sw_H;
-                break;
-            default:
-                return NGX_ERROR;
-            }
-            break;
-
-        case sw_H:
-            switch (ch) {
-            case 'T':
-                state = sw_HT;
-                break;
-            default:
-                return NGX_ERROR;
-            }
-            break;
-
-        case sw_HT:
-            switch (ch) {
-            case 'T':
-                state = sw_HTT;
-                break;
-            default:
-                return NGX_ERROR;
-            }
-            break;
-
-        case sw_HTT:
-            switch (ch) {
-            case 'P':
-                state = sw_HTTP;
-                break;
-            default:
-                return NGX_ERROR;
-            }
-            break;
-
-        case sw_HTTP:
-            switch (ch) {
-            case '/':
-                state = sw_first_major_digit;
-                break;
-            default:
-                return NGX_ERROR;
-            }
-            break;
-
-        /* the first digit of major HTTP version */
-        case sw_first_major_digit:
-            if (ch < '1' || ch > '9') {
-                return NGX_ERROR;
-            }
-
-            state = sw_major_digit;
-            break;
-
-        /* the major HTTP version or dot */
-        case sw_major_digit:
-            if (ch == '.') {
-                state = sw_first_minor_digit;
-                break;
-            }
-
-            if (ch < '0' || ch > '9') {
-                return NGX_ERROR;
-            }
-
-            break;
-
-        /* the first digit of minor HTTP version */
-        case sw_first_minor_digit:
-            if (ch < '0' || ch > '9') {
-                return NGX_ERROR;
-            }
-
-            state = sw_minor_digit;
-            break;
-
-        /* the minor HTTP version or the end of the request line */
-        case sw_minor_digit:
-            if (ch == ' ') {
-                state = sw_status;
-                break;
-            }
-
-            if (ch < '0' || ch > '9') {
-                return NGX_ERROR;
-            }
-
-            break;
-
-        /* HTTP status code */
-        case sw_status:
-            if (ch == ' ') {
-                break;
-            }
-
-            if (ch < '0' || ch > '9') {
-                return NGX_ERROR;
-            }
-
-            hp->code = hp->code * 10 + (ch - '0');
-
-            if (++hp->count == 3) {
-                state = sw_space_after_status;
-            }
-
-            break;
-
-        /* space or end of line */
-        case sw_space_after_status:
-            switch (ch) {
-            case ' ':
-                state = sw_status_text;
-                break;
-            case '.':                    /* IIS may send 403.1, 403.2, etc */
-                state = sw_status_text;
-                break;
-            case CR:
-                break;
-            case LF:
-                goto done;
-            default:
-                return NGX_ERROR;
-            }
-            break;
-
-        /* any text until end of line */
-        case sw_status_text:
-            switch (ch) {
-            case CR:
-                hp->status_text_end = p;
-                state = sw_almost_done;
-                break;
-            case LF:
-                hp->status_text_end = p;
-                goto done;
-            }
-
-            if (hp->status_text == NULL) {
-                hp->status_text = p;
-            }
-
-            break;
-
-        /* end of status line */
-        case sw_almost_done:
-            switch (ch) {
-            case LF:
-                goto done;
-            default:
-                return NGX_ERROR;
-            }
-        }
-    }
-
-    b->pos = p;
-    hp->state = state;
-
-    return NGX_AGAIN;
-
-done:
-
-    b->pos = p + 1;
-    hp->state = sw_start;
-
-    return NGX_OK;
-}
-
-
-static ngx_int_t
-ngx_js_http_parse_header_line(ngx_js_http_parse_t *hp, ngx_buf_t *b)
-{
-    u_char  c, ch, *p;
-    enum {
-        sw_start = 0,
-        sw_name,
-        sw_space_before_value,
-        sw_value,
-        sw_space_after_value,
-        sw_almost_done,
-        sw_header_almost_done
-    } state;
-
-    state = hp->state;
-
-    for (p = b->pos; p < b->last; p++) {
-        ch = *p;
-
-        switch (state) {
-
-        /* first char */
-        case sw_start:
-
-            switch (ch) {
-            case CR:
-                hp->header_end = p;
-                state = sw_header_almost_done;
-                break;
-            case LF:
-                hp->header_end = p;
-                goto header_done;
-            default:
-                state = sw_name;
-                hp->header_name_start = p;
-
-                c = (u_char) (ch | 0x20);
-                if (c >= 'a' && c <= 'z') {
-                    break;
-                }
-
-                if (ch >= '0' && ch <= '9') {
-                    break;
-                }
-
-                return NGX_ERROR;
-            }
-            break;
-
-        /* header name */
-        case sw_name:
-            c = (u_char) (ch | 0x20);
-            if (c >= 'a' && c <= 'z') {
-                break;
-            }
-
-            if (ch == ':') {
-                hp->header_name_end = p;
-                state = sw_space_before_value;
-                break;
-            }
-
-            if (ch == '-' || ch == '_') {
-                break;
-            }
-
-            if (ch >= '0' && ch <= '9') {
-                break;
-            }
-
-            if (ch == CR) {
-                hp->header_name_end = p;
-                hp->header_start = p;
-                hp->header_end = p;
-                state = sw_almost_done;
-                break;
-            }
-
-            if (ch == LF) {
-                hp->header_name_end = p;
-                hp->header_start = p;
-                hp->header_end = p;
-                goto done;
-            }
-
-            return NGX_ERROR;
-
-        /* space* before header value */
-        case sw_space_before_value:
-            switch (ch) {
-            case ' ':
-                break;
-            case CR:
-                hp->header_start = p;
-                hp->header_end = p;
-                state = sw_almost_done;
-                break;
-            case LF:
-                hp->header_start = p;
-                hp->header_end = p;
-                goto done;
-            default:
-                hp->header_start = p;
-                state = sw_value;
-                break;
-            }
-            break;
-
-        /* header value */
-        case sw_value:
-            switch (ch) {
-            case ' ':
-                hp->header_end = p;
-                state = sw_space_after_value;
-                break;
-            case CR:
-                hp->header_end = p;
-                state = sw_almost_done;
-                break;
-            case LF:
-                hp->header_end = p;
-                goto done;
-            }
-            break;
-
-        /* space* before end of header line */
-        case sw_space_after_value:
-            switch (ch) {
-            case ' ':
-                break;
-            case CR:
-                state = sw_almost_done;
-                break;
-            case LF:
-                goto done;
-            default:
-                state = sw_value;
-                break;
-            }
-            break;
-
-        /* end of header line */
-        case sw_almost_done:
-            switch (ch) {
-            case LF:
-                goto done;
-            default:
-                return NGX_ERROR;
-            }
-
-        /* end of header */
-        case sw_header_almost_done:
-            switch (ch) {
-            case LF:
-                goto header_done;
-            default:
-                return NGX_ERROR;
-            }
-        }
-    }
-
-    b->pos = p;
-    hp->state = state;
-
-    return NGX_AGAIN;
-
-done:
-
-    b->pos = p + 1;
-    hp->state = sw_start;
-
-    return NGX_OK;
-
-header_done:
-
-    b->pos = p + 1;
-    hp->state = sw_start;
-
-    return NGX_DONE;
-}
-
-
-#define                                                                       \
-ngx_size_is_sufficient(cs)                                                    \
-    (cs < ((__typeof__(cs)) 1 << (sizeof(cs) * 8 - 4)))
-
-
-#define NGX_JS_HTTP_CHUNK_MIDDLE     0
-#define NGX_JS_HTTP_CHUNK_ON_BORDER  1
-#define NGX_JS_HTTP_CHUNK_END        2
-
-
-static ngx_int_t
-ngx_js_http_chunk_buffer(ngx_js_http_chunk_parse_t *hcp, ngx_buf_t *b,
-    njs_chb_t *chain)
-{
-    size_t  size;
-
-    size = b->last - hcp->pos;
-
-    if (hcp->chunk_size < size) {
-        njs_chb_append(chain, hcp->pos, hcp->chunk_size);
-        hcp->pos += hcp->chunk_size;
-
-        return NGX_JS_HTTP_CHUNK_END;
-    }
-
-    njs_chb_append(chain, hcp->pos, size);
-    hcp->pos += size;
-
-    hcp->chunk_size -= size;
-
-    if (hcp->chunk_size == 0) {
-        return NGX_JS_HTTP_CHUNK_ON_BORDER;
-    }
-
-    return NGX_JS_HTTP_CHUNK_MIDDLE;
-}
-
-
-static ngx_int_t
-ngx_js_http_parse_chunked(ngx_js_http_chunk_parse_t *hcp,
-    ngx_buf_t *b, njs_chb_t *chain)
-{
-    u_char     c, ch;
-    ngx_int_t  rc;
-
-    enum {
-        sw_start = 0,
-        sw_chunk_size,
-        sw_chunk_size_linefeed,
-        sw_chunk_end_newline,
-        sw_chunk_end_linefeed,
-        sw_chunk,
-    } state;
-
-    state = hcp->state;
-
-    hcp->pos = b->pos;
-
-    while (hcp->pos < b->last) {
-        /*
-         * The sw_chunk state is tested outside the switch
-         * to preserve hcp->pos and to not touch memory.
-         */
-        if (state == sw_chunk) {
-            rc = ngx_js_http_chunk_buffer(hcp, b, chain);
-            if (rc == NGX_ERROR) {
-                return rc;
-            }
-
-            if (rc == NGX_JS_HTTP_CHUNK_MIDDLE) {
-                break;
-            }
-
-            state = sw_chunk_end_newline;
-
-            if (rc == NGX_JS_HTTP_CHUNK_ON_BORDER) {
-                break;
-            }
-
-            /* rc == NGX_JS_HTTP_CHUNK_END */
-        }
-
-        ch = *hcp->pos++;
-
-        switch (state) {
-
-        case sw_start:
-            state = sw_chunk_size;
-
-            c = ch - '0';
-
-            if (c <= 9) {
-                hcp->chunk_size = c;
-                continue;
-            }
-
-            c = (ch | 0x20) - 'a';
-
-            if (c <= 5) {
-                hcp->chunk_size = 0x0A + c;
-                continue;
-            }
-
-            return NGX_ERROR;
-
-        case sw_chunk_size:
-
-            c = ch - '0';
-
-            if (c > 9) {
-                c = (ch | 0x20) - 'a';
-
-                if (c <= 5) {
-                    c += 0x0A;
-
-                } else if (ch == '\r') {
-                    state = sw_chunk_size_linefeed;
-                    continue;
-
-                } else {
-                    return NGX_ERROR;
-                }
-            }
-
-            if (ngx_size_is_sufficient(hcp->chunk_size)) {
-                hcp->chunk_size = (hcp->chunk_size << 4) + c;
-                continue;
-            }
-
-            return NGX_ERROR;
-
-        case sw_chunk_size_linefeed:
-            if (ch == '\n') {
-
-                if (hcp->chunk_size != 0) {
-                    state = sw_chunk;
-                    continue;
-                }
-
-                hcp->last = 1;
-                state = sw_chunk_end_newline;
-                continue;
-            }
-
-            return NGX_ERROR;
-
-        case sw_chunk_end_newline:
-            if (ch == '\r') {
-                state = sw_chunk_end_linefeed;
-                continue;
-            }
-
-            return NGX_ERROR;
-
-        case sw_chunk_end_linefeed:
-            if (ch == '\n') {
-
-                if (!hcp->last) {
-                    state = sw_start;
-                    continue;
-                }
-
-                return NGX_OK;
-            }
-
-            return NGX_ERROR;
-
-        case sw_chunk:
-            /*
-             * This state is processed before the switch.
-             * It added here just to suppress a warning.
-             */
-            continue;
-        }
-    }
-
-    hcp->state = state;
-
-    return NGX_AGAIN;
-}
-
-
-static void
-ngx_js_http_dummy_handler(ngx_event_t *ev)
-{
-    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "js fetch dummy handler");
-}
-
-
 static njs_int_t
 ngx_headers_js_get(njs_vm_t *vm, njs_value_t *value, njs_str_t *name,
     njs_value_t *retval, njs_bool_t as_array)
@@ -3761,8 +2195,8 @@ ngx_request_js_ext_body(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     switch (type) {
     case NGX_JS_BODY_ARRAY_BUFFER:
         ret = njs_vm_value_array_buffer_set(vm, njs_value_arg(&result),
-                                            request->body.start,
-                                            request->body.length);
+                                            request->body.data,
+                                            request->body.len);
         if (ret != NJS_OK) {
             njs_vm_memory_error(vm);
             return NJS_ERROR;
@@ -3774,8 +2208,8 @@ ngx_request_js_ext_body(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
     case NGX_JS_BODY_TEXT:
     default:
         ret = njs_vm_value_string_create(vm, njs_value_arg(&result),
-                                         request->body.start,
-                                         request->body.length);
+                                         request->body.data,
+                                         request->body.len);
         if (ret != NJS_OK) {
             njs_vm_memory_error(vm);
             return NJS_ERROR;
@@ -4056,8 +2490,8 @@ ngx_response_js_ext_status_text(njs_vm_t *vm, njs_object_prop_t *prop,
         return NJS_DECLINED;
     }
 
-    njs_vm_value_string_create(vm, retval, response->status_text.start,
-                               response->status_text.length);
+    njs_vm_value_string_create(vm, retval, response->status_text.data,
+                               response->status_text.len);
 
     return NJS_OK;
 }
diff --git a/nginx/ngx_js_http.c b/nginx/ngx_js_http.c
new file mode 100644
index 000000000..5be5cc804
--- /dev/null
+++ b/nginx/ngx_js_http.c
@@ -0,0 +1,1532 @@
+
+/*
+ * Copyright (C) Dmitry Volyntsev
+ * Copyright (C) hongzhidao
+ * Copyright (C) Antoine Bonavita
+ * 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"
+#include "ngx_js_http.h"
+
+
+static void ngx_js_http_resolve_handler(ngx_resolver_ctx_t *ctx);
+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 void ngx_js_http_dummy_handler(ngx_event_t *ev);
+
+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);
+
+#if (NGX_SSL)
+static void ngx_js_http_ssl_init_connection(ngx_js_http_t *http);
+static void ngx_js_http_ssl_handshake_handler(ngx_connection_t *c);
+static void ngx_js_http_ssl_handshake(ngx_js_http_t *http);
+static njs_int_t ngx_js_http_ssl_name(ngx_js_http_t *http);
+#endif
+
+
+static void
+ngx_js_http_error(ngx_js_http_t *http, const char *fmt, ...)
+{
+    u_char   *p, *end;
+    va_list   args;
+    u_char    err[NGX_MAX_ERROR_STR];
+
+    end = err + NGX_MAX_ERROR_STR - 1;
+
+    va_start(args, fmt);
+    p = njs_vsprintf(err, end, fmt, args);
+    *p = '\0';
+    va_end(args);
+
+    http->error_handler(http, (const char *) err);
+}
+
+
+ngx_resolver_ctx_t *
+ngx_js_http_resolve(ngx_js_http_t *http, ngx_resolver_t *r, ngx_str_t *host,
+    in_port_t port, ngx_msec_t timeout)
+{
+    ngx_int_t            ret;
+    ngx_resolver_ctx_t  *ctx;
+
+    ctx = ngx_resolve_start(r, NULL);
+    if (ctx == NULL) {
+        return NULL;
+    }
+
+    if (ctx == NGX_NO_RESOLVER) {
+        return ctx;
+    }
+
+    http->ctx = ctx;
+    http->port = port;
+
+    ctx->name = *host;
+    ctx->handler = ngx_js_http_resolve_handler;
+    ctx->data = http;
+    ctx->timeout = timeout;
+
+    ret = ngx_resolve_name(ctx);
+    if (ret != NGX_OK) {
+        http->ctx = NULL;
+        return NULL;
+    }
+
+    return ctx;
+}
+
+
+static void
+ngx_js_http_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, "\"%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 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_js_http_resolve_done(http);
+
+    ngx_js_http_connect(http);
+
+    return;
+
+failed:
+
+    ngx_js_http_error(http, "memory error");
+}
+
+
+static void
+ngx_js_http_close_connection(ngx_connection_t *c)
+{
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "js http close connection: %d", c->fd);
+
+#if (NGX_SSL)
+    if (c->ssl) {
+        c->ssl->no_wait_shutdown = 1;
+
+        if (ngx_ssl_shutdown(c) == NGX_AGAIN) {
+            c->ssl->handler = ngx_js_http_close_connection;
+            return;
+        }
+    }
+#endif
+
+    c->destroyed = 1;
+
+    ngx_close_connection(c);
+}
+
+
+void
+ngx_js_http_resolve_done(ngx_js_http_t *http)
+{
+    if (http->ctx != NULL) {
+        ngx_resolve_name_done(http->ctx);
+        http->ctx = NULL;
+    }
+}
+
+
+void
+ngx_js_http_close_peer(ngx_js_http_t *http)
+{
+    if (http->peer.connection != NULL) {
+        ngx_js_http_close_connection(http->peer.connection);
+        http->peer.connection = NULL;
+    }
+}
+
+
+void
+ngx_js_http_connect(ngx_js_http_t *http)
+{
+    ngx_int_t    rc;
+    ngx_addr_t  *addr;
+
+    addr = &http->addrs[http->naddr];
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, http->log, 0,
+                   "js http connect %ui/%ui", http->naddr, http->naddrs);
+
+    http->peer.sockaddr = addr->sockaddr;
+    http->peer.socklen = addr->socklen;
+    http->peer.name = &addr->name;
+    http->peer.get = ngx_event_get_peer;
+    http->peer.log = http->log;
+    http->peer.log_error = NGX_ERROR_ERR;
+
+    rc = ngx_event_connect_peer(&http->peer);
+
+    if (rc == NGX_ERROR) {
+        ngx_js_http_error(http, "connect failed");
+        return;
+    }
+
+    if (rc == NGX_BUSY || rc == NGX_DECLINED) {
+        ngx_js_http_next(http);
+        return;
+    }
+
+    http->peer.connection->data = http;
+    http->peer.connection->pool = http->pool;
+
+    http->peer.connection->write->handler = ngx_js_http_write_handler;
+    http->peer.connection->read->handler = ngx_js_http_read_handler;
+
+    http->process = ngx_js_http_process_status_line;
+
+    ngx_add_timer(http->peer.connection->read, http->timeout);
+    ngx_add_timer(http->peer.connection->write, http->timeout);
+
+#if (NGX_SSL)
+    if (http->ssl != NULL && http->peer.connection->ssl == NULL) {
+        ngx_js_http_ssl_init_connection(http);
+        return;
+    }
+#endif
+
+    if (rc == NGX_OK) {
+        ngx_js_http_write_handler(http->peer.connection->write);
+    }
+}
+
+
+#if (NGX_SSL)
+
+static void
+ngx_js_http_ssl_init_connection(ngx_js_http_t *http)
+{
+    ngx_int_t          rc;
+    ngx_connection_t  *c;
+
+    c = http->peer.connection;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, http->log, 0,
+                   "js http secure connect %ui/%ui", http->naddr,
+                   http->naddrs);
+
+    if (ngx_ssl_create_connection(http->ssl, c, NGX_SSL_BUFFER|NGX_SSL_CLIENT)
+        != NGX_OK)
+    {
+        ngx_js_http_error(http, "failed to create ssl connection");
+        return;
+    }
+
+    c->sendfile = 0;
+
+    if (ngx_js_http_ssl_name(http) != NGX_OK) {
+        ngx_js_http_error(http, "failed to create ssl connection");
+        return;
+    }
+
+    c->log->action = "SSL handshaking to http target";
+
+    rc = ngx_ssl_handshake(c);
+
+    if (rc == NGX_AGAIN) {
+        c->data = http;
+        c->ssl->handler = ngx_js_http_ssl_handshake_handler;
+        return;
+    }
+
+    ngx_js_http_ssl_handshake(http);
+}
+
+
+static void
+ngx_js_http_ssl_handshake_handler(ngx_connection_t *c)
+{
+    ngx_js_http_t  *http;
+
+    http = c->data;
+
+    http->peer.connection->write->handler = ngx_js_http_write_handler;
+    http->peer.connection->read->handler = ngx_js_http_read_handler;
+
+    ngx_js_http_ssl_handshake(http);
+}
+
+
+static void
+ngx_js_http_ssl_handshake(ngx_js_http_t *http)
+{
+    long               rc;
+    ngx_connection_t  *c;
+
+    c = http->peer.connection;
+
+    if (c->ssl->handshaked) {
+        if (http->ssl_verify) {
+            rc = SSL_get_verify_result(c->ssl->connection);
+
+            if (rc != X509_V_OK) {
+                ngx_log_error(NGX_LOG_ERR, c->log, 0,
+                              "js http SSL certificate verify error: (%l:%s)",
+                              rc, X509_verify_cert_error_string(rc));
+                goto failed;
+            }
+
+            if (ngx_ssl_check_host(c, &http->tls_name) != NGX_OK) {
+                ngx_log_error(NGX_LOG_ERR, c->log, 0,
+                              "js http SSL certificate does not match \"%V\"",
+                              &http->tls_name);
+                goto failed;
+            }
+        }
+
+        c->write->handler = ngx_js_http_write_handler;
+        c->read->handler = ngx_js_http_read_handler;
+
+        if (c->read->ready) {
+            ngx_post_event(c->read, &ngx_posted_events);
+        }
+
+        http->process = ngx_js_http_process_status_line;
+        ngx_js_http_write_handler(c->write);
+
+        return;
+    }
+
+failed:
+
+    ngx_js_http_next(http);
+}
+
+
+static njs_int_t
+ngx_js_http_ssl_name(ngx_js_http_t *http)
+{
+#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
+    u_char  *p;
+
+    /* as per RFC 6066, literal IPv4 and IPv6 addresses are not permitted */
+    ngx_str_t  *name = &http->tls_name;
+
+    if (name->len == 0 || *name->data == '[') {
+        goto done;
+    }
+
+    if (ngx_inet_addr(name->data, name->len) != INADDR_NONE) {
+        goto done;
+    }
+
+    /*
+     * SSL_set_tlsext_host_name() needs a null-terminated string,
+     * hence we explicitly null-terminate name here
+     */
+
+    p = ngx_pnalloc(http->pool, name->len + 1);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    (void) ngx_cpystrn(p, name->data, name->len + 1);
+
+    name->data = p;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, http->log, 0,
+                   "js http SSL server name: \"%s\"", name->data);
+
+    if (SSL_set_tlsext_host_name(http->peer.connection->ssl->connection,
+                                 (char *) name->data)
+        == 0)
+    {
+        ngx_ssl_error(NGX_LOG_ERR, http->log, 0,
+                      "SSL_set_tlsext_host_name(\"%s\") failed", name->data);
+        return NGX_ERROR;
+    }
+
+#endif
+done:
+
+    return NJS_OK;
+}
+
+#endif
+
+
+static void
+ngx_js_http_next(ngx_js_http_t *http)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, http->log, 0, "js http next addr");
+
+    if (++http->naddr >= http->naddrs) {
+        ngx_js_http_error(http, "connect failed");
+        return;
+    }
+
+    if (http->peer.connection != NULL) {
+        ngx_js_http_close_connection(http->peer.connection);
+        http->peer.connection = NULL;
+    }
+
+    http->buffer = NULL;
+
+    ngx_js_http_connect(http);
+}
+
+
+static void
+ngx_js_http_write_handler(ngx_event_t *wev)
+{
+    ssize_t            n, size;
+    ngx_buf_t         *b;
+    ngx_js_http_t     *http;
+    ngx_connection_t  *c;
+
+    c = wev->data;
+    http = c->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, wev->log, 0, "js http write handler");
+
+    if (wev->timedout) {
+        ngx_js_http_error(http, "write timed out");
+        return;
+    }
+
+#if (NGX_SSL)
+    if (http->ssl != NULL && http->peer.connection->ssl == NULL) {
+        ngx_js_http_ssl_init_connection(http);
+        return;
+    }
+#endif
+
+    b = http->buffer;
+
+    if (b == NULL) {
+        size = njs_chb_size(&http->chain);
+        if (size < 0) {
+            ngx_js_http_error(http, "memory error");
+            return;
+        }
+
+        b = ngx_create_temp_buf(http->pool, size);
+        if (b == NULL) {
+            ngx_js_http_error(http, "memory error");
+            return;
+        }
+
+        njs_chb_join_to(&http->chain, b->last);
+        b->last += size;
+
+        http->buffer = b;
+    }
+
+    size = b->last - b->pos;
+
+    n = c->send(c, b->pos, size);
+
+    if (n == NGX_ERROR) {
+        ngx_js_http_next(http);
+        return;
+    }
+
+    if (n > 0) {
+        b->pos += n;
+
+        if (n == size) {
+            wev->handler = ngx_js_http_dummy_handler;
+
+            http->buffer = NULL;
+
+            if (wev->timer_set) {
+                ngx_del_timer(wev);
+            }
+
+            if (ngx_handle_write_event(wev, 0) != NGX_OK) {
+                ngx_js_http_error(http, "write failed");
+            }
+
+            return;
+        }
+    }
+
+    if (!wev->timer_set) {
+        ngx_add_timer(wev, http->timeout);
+    }
+}
+
+
+static void
+ngx_js_http_read_handler(ngx_event_t *rev)
+{
+    ssize_t            n, size;
+    ngx_int_t          rc;
+    ngx_buf_t         *b;
+    ngx_js_http_t     *http;
+    ngx_connection_t  *c;
+
+    c = rev->data;
+    http = c->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, "js http read handler");
+
+    if (rev->timedout) {
+        ngx_js_http_error(http, "read timed out");
+        return;
+    }
+
+    if (http->buffer == NULL) {
+        b = ngx_create_temp_buf(http->pool, http->buffer_size);
+        if (b == NULL) {
+            ngx_js_http_error(http, "memory error");
+            return;
+        }
+
+        http->buffer = b;
+    }
+
+    for ( ;; ) {
+        b = http->buffer;
+        size = b->end - b->last;
+
+        n = c->recv(c, b->last, size);
+
+        if (n > 0) {
+            b->last += n;
+
+            rc = http->process(http);
+
+            if (rc == NGX_ERROR) {
+                return;
+            }
+
+            continue;
+        }
+
+        if (n == NGX_AGAIN) {
+            if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+                ngx_js_http_error(http, "read failed");
+            }
+
+            return;
+        }
+
+        if (n == NGX_ERROR) {
+            ngx_js_http_next(http);
+            return;
+        }
+
+        break;
+    }
+
+    http->done = 1;
+
+    rc = http->process(http);
+
+    if (rc == NGX_DONE) {
+        /* handler was called */
+        return;
+    }
+
+    if (rc == NGX_AGAIN) {
+        ngx_js_http_error(http, "prematurely closed connection");
+    }
+}
+
+
+static void
+ngx_js_http_dummy_handler(ngx_event_t *ev)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "js http dummy handler");
+}
+
+
+static ngx_int_t
+ngx_js_http_process_status_line(ngx_js_http_t *http)
+{
+    ngx_int_t             rc;
+    ngx_js_http_parse_t  *hp;
+
+    hp = &http->http_parse;
+
+    rc = ngx_js_http_parse_status_line(hp, http->buffer);
+
+    if (rc == NGX_OK) {
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, http->log, 0, "js http status %ui",
+                       hp->code);
+
+        http->response.code = hp->code;
+        http->response.status_text.data = hp->status_text;
+        http->response.status_text.len = hp->status_text_end - hp->status_text;
+        http->process = ngx_js_http_process_headers;
+
+        return http->process(http);
+    }
+
+    if (rc == NGX_AGAIN) {
+        return NGX_AGAIN;
+    }
+
+    /* rc == NGX_ERROR */
+
+    ngx_js_http_error(http, "invalid http status line");
+
+    return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_js_http_process_headers(ngx_js_http_t *http)
+{
+    size_t                len, vlen;
+    ngx_int_t             rc;
+    njs_int_t             ret;
+    ngx_js_http_parse_t  *hp;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, http->log, 0,
+                   "js http process headers");
+
+    hp = &http->http_parse;
+
+    if (http->response.headers.header_list.size == 0) {
+        rc = ngx_list_init(&http->response.headers.header_list, http->pool, 4,
+                           sizeof(ngx_js_tb_elt_t));
+        if (rc != NGX_OK) {
+            ngx_js_http_error(http, "alloc failed");
+            return NGX_ERROR;
+        }
+    }
+
+    for ( ;; ) {
+        rc = ngx_js_http_parse_header_line(hp, http->buffer);
+
+        if (rc == NGX_OK) {
+            len = hp->header_name_end - hp->header_name_start;
+            vlen = hp->header_end - hp->header_start;
+
+            ret = http->append_headers(http, &http->response.headers,
+                                       hp->header_name_start, len,
+                                       hp->header_start, vlen);
+
+            if (ret == NJS_ERROR) {
+                ngx_js_http_error(http, "cannot add respose header");
+                return NGX_ERROR;
+            }
+
+            ngx_log_debug4(NGX_LOG_DEBUG_EVENT, http->log, 0,
+                           "js http header \"%*s: %*s\"",
+                           len, hp->header_name_start, vlen, hp->header_start);
+
+            if (len == njs_strlen("Transfer-Encoding")
+                && vlen == njs_strlen("chunked")
+                && ngx_strncasecmp(hp->header_name_start,
+                                   (u_char *) "Transfer-Encoding", len) == 0
+                && ngx_strncasecmp(hp->header_start, (u_char *) "chunked",
+                                   vlen) == 0)
+            {
+                hp->chunked = 1;
+            }
+
+            if (len == njs_strlen("Content-Length")
+                && ngx_strncasecmp(hp->header_name_start,
+                                   (u_char *) "Content-Length", len) == 0)
+            {
+                hp->content_length_n = ngx_atoof(hp->header_start, vlen);
+                if (hp->content_length_n == NGX_ERROR) {
+                    ngx_js_http_error(http, "invalid http content length");
+                    return NGX_ERROR;
+                }
+
+                if (!http->header_only
+                    && hp->content_length_n
+                       > (off_t) http->max_response_body_size)
+                {
+                    ngx_js_http_error(http,
+                                      "http content length is too large");
+                    return NGX_ERROR;
+                }
+            }
+
+            continue;
+        }
+
+        if (rc == NGX_DONE) {
+            http->response.headers.guard = GUARD_IMMUTABLE;
+            break;
+        }
+
+        if (rc == NGX_AGAIN) {
+            return NGX_AGAIN;
+        }
+
+        /* rc == NGX_ERROR */
+
+        ngx_js_http_error(http, "invalid http header");
+
+        return NGX_ERROR;
+    }
+
+    njs_chb_destroy(&http->chain);
+
+    http->process = ngx_js_http_process_body;
+
+    return http->process(http);
+}
+
+
+static ngx_int_t
+ngx_js_http_process_body(ngx_js_http_t *http)
+{
+    ssize_t     size, chsize, need;
+    ngx_int_t   rc;
+    ngx_buf_t  *b;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, http->log, 0,
+                   "js http process body done:%ui", (ngx_uint_t) http->done);
+
+    if (http->done) {
+        size = njs_chb_size(&http->response.chain);
+        if (size < 0) {
+            ngx_js_http_error(http, "memory error");
+            return NGX_ERROR;
+        }
+
+        if (!http->header_only
+            && http->http_parse.chunked
+            && http->http_parse.content_length_n == -1)
+        {
+            ngx_js_http_error(http, "invalid http chunked response");
+            return NGX_ERROR;
+        }
+
+        if (http->header_only
+            || http->http_parse.content_length_n == -1
+            || size == http->http_parse.content_length_n)
+        {
+            http->ready_handler(http);
+            return NGX_DONE;
+        }
+
+        if (size < http->http_parse.content_length_n) {
+            return NGX_AGAIN;
+        }
+
+        ngx_js_http_error(http, "http trailing data");
+        return NGX_ERROR;
+    }
+
+    b = http->buffer;
+
+    if (http->http_parse.chunked) {
+        rc = ngx_js_http_parse_chunked(&http->http_chunk_parse, b,
+                                       &http->response.chain);
+        if (rc == NGX_ERROR) {
+            ngx_js_http_error(http, "invalid http chunked response");
+            return NGX_ERROR;
+        }
+
+        size = njs_chb_size(&http->response.chain);
+
+        if (rc == NGX_OK) {
+            http->http_parse.content_length_n = size;
+        }
+
+        if (size > http->max_response_body_size * 10) {
+            ngx_js_http_error(http, "very large http chunked response");
+            return NGX_ERROR;
+        }
+
+        b->pos = http->http_chunk_parse.pos;
+
+    } else {
+        size = njs_chb_size(&http->response.chain);
+
+        if (http->header_only) {
+            need = 0;
+
+        } else  if (http->http_parse.content_length_n == -1) {
+            need = http->max_response_body_size - size;
+
+        } else {
+            need = http->http_parse.content_length_n - size;
+        }
+
+        chsize = ngx_min(need, b->last - b->pos);
+
+        if (size + chsize > http->max_response_body_size) {
+            ngx_js_http_error(http, "http response body is too large");
+            return NGX_ERROR;
+        }
+
+        if (chsize > 0) {
+            njs_chb_append(&http->response.chain, b->pos, chsize);
+            b->pos += chsize;
+        }
+
+        rc = (need > chsize) ? NGX_AGAIN : NGX_DONE;
+    }
+
+    if (b->pos == b->end) {
+        if (http->chunk == NULL) {
+            b = ngx_create_temp_buf(http->pool, http->buffer_size);
+            if (b == NULL) {
+                ngx_js_http_error(http, "memory error");
+                return NGX_ERROR;
+            }
+
+            http->buffer = b;
+            http->chunk = b;
+
+        } else {
+            b->last = b->start;
+            b->pos = b->start;
+        }
+    }
+
+    return rc;
+}
+
+
+static ngx_int_t
+ngx_js_http_parse_status_line(ngx_js_http_parse_t *hp, ngx_buf_t *b)
+{
+    u_char   ch;
+    u_char  *p;
+    enum {
+        sw_start = 0,
+        sw_H,
+        sw_HT,
+        sw_HTT,
+        sw_HTTP,
+        sw_first_major_digit,
+        sw_major_digit,
+        sw_first_minor_digit,
+        sw_minor_digit,
+        sw_status,
+        sw_space_after_status,
+        sw_status_text,
+        sw_almost_done
+    } state;
+
+    state = hp->state;
+
+    for (p = b->pos; p < b->last; p++) {
+        ch = *p;
+
+        switch (state) {
+
+        /* "HTTP/" */
+        case sw_start:
+            switch (ch) {
+            case 'H':
+                state = sw_H;
+                break;
+            default:
+                return NGX_ERROR;
+            }
+            break;
+
+        case sw_H:
+            switch (ch) {
+            case 'T':
+                state = sw_HT;
+                break;
+            default:
+                return NGX_ERROR;
+            }
+            break;
+
+        case sw_HT:
+            switch (ch) {
+            case 'T':
+                state = sw_HTT;
+                break;
+            default:
+                return NGX_ERROR;
+            }
+            break;
+
+        case sw_HTT:
+            switch (ch) {
+            case 'P':
+                state = sw_HTTP;
+                break;
+            default:
+                return NGX_ERROR;
+            }
+            break;
+
+        case sw_HTTP:
+            switch (ch) {
+            case '/':
+                state = sw_first_major_digit;
+                break;
+            default:
+                return NGX_ERROR;
+            }
+            break;
+
+        /* the first digit of major HTTP version */
+        case sw_first_major_digit:
+            if (ch < '1' || ch > '9') {
+                return NGX_ERROR;
+            }
+
+            state = sw_major_digit;
+            break;
+
+        /* the major HTTP version or dot */
+        case sw_major_digit:
+            if (ch == '.') {
+                state = sw_first_minor_digit;
+                break;
+            }
+
+            if (ch < '0' || ch > '9') {
+                return NGX_ERROR;
+            }
+
+            break;
+
+        /* the first digit of minor HTTP version */
+        case sw_first_minor_digit:
+            if (ch < '0' || ch > '9') {
+                return NGX_ERROR;
+            }
+
+            state = sw_minor_digit;
+            break;
+
+        /* the minor HTTP version or the end of the request line */
+        case sw_minor_digit:
+            if (ch == ' ') {
+                state = sw_status;
+                break;
+            }
+
+            if (ch < '0' || ch > '9') {
+                return NGX_ERROR;
+            }
+
+            break;
+
+        /* HTTP status code */
+        case sw_status:
+            if (ch == ' ') {
+                break;
+            }
+
+            if (ch < '0' || ch > '9') {
+                return NGX_ERROR;
+            }
+
+            hp->code = hp->code * 10 + (ch - '0');
+
+            if (++hp->count == 3) {
+                state = sw_space_after_status;
+            }
+
+            break;
+
+        /* space or end of line */
+        case sw_space_after_status:
+            switch (ch) {
+            case ' ':
+                state = sw_status_text;
+                break;
+            case '.':                    /* IIS may send 403.1, 403.2, etc */
+                state = sw_status_text;
+                break;
+            case CR:
+                break;
+            case LF:
+                goto done;
+            default:
+                return NGX_ERROR;
+            }
+            break;
+
+        /* any text until end of line */
+        case sw_status_text:
+            switch (ch) {
+            case CR:
+                hp->status_text_end = p;
+                state = sw_almost_done;
+                break;
+            case LF:
+                hp->status_text_end = p;
+                goto done;
+            }
+
+            if (hp->status_text == NULL) {
+                hp->status_text = p;
+            }
+
+            break;
+
+        /* end of status line */
+        case sw_almost_done:
+            switch (ch) {
+            case LF:
+                goto done;
+            default:
+                return NGX_ERROR;
+            }
+        }
+    }
+
+    b->pos = p;
+    hp->state = state;
+
+    return NGX_AGAIN;
+
+done:
+
+    b->pos = p + 1;
+    hp->state = sw_start;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_js_http_parse_header_line(ngx_js_http_parse_t *hp, ngx_buf_t *b)
+{
+    u_char  c, ch, *p;
+    enum {
+        sw_start = 0,
+        sw_name,
+        sw_space_before_value,
+        sw_value,
+        sw_space_after_value,
+        sw_almost_done,
+        sw_header_almost_done
+    } state;
+
+    state = hp->state;
+
+    for (p = b->pos; p < b->last; p++) {
+        ch = *p;
+
+        switch (state) {
+
+        /* first char */
+        case sw_start:
+
+            switch (ch) {
+            case CR:
+                hp->header_end = p;
+                state = sw_header_almost_done;
+                break;
+            case LF:
+                hp->header_end = p;
+                goto header_done;
+            default:
+                state = sw_name;
+                hp->header_name_start = p;
+
+                c = (u_char) (ch | 0x20);
+                if (c >= 'a' && c <= 'z') {
+                    break;
+                }
+
+                if (ch >= '0' && ch <= '9') {
+                    break;
+                }
+
+                return NGX_ERROR;
+            }
+            break;
+
+        /* header name */
+        case sw_name:
+            c = (u_char) (ch | 0x20);
+            if (c >= 'a' && c <= 'z') {
+                break;
+            }
+
+            if (ch == ':') {
+                hp->header_name_end = p;
+                state = sw_space_before_value;
+                break;
+            }
+
+            if (ch == '-' || ch == '_') {
+                break;
+            }
+
+            if (ch >= '0' && ch <= '9') {
+                break;
+            }
+
+            if (ch == CR) {
+                hp->header_name_end = p;
+                hp->header_start = p;
+                hp->header_end = p;
+                state = sw_almost_done;
+                break;
+            }
+
+            if (ch == LF) {
+                hp->header_name_end = p;
+                hp->header_start = p;
+                hp->header_end = p;
+                goto done;
+            }
+
+            return NGX_ERROR;
+
+        /* space* before header value */
+        case sw_space_before_value:
+            switch (ch) {
+            case ' ':
+                break;
+            case CR:
+                hp->header_start = p;
+                hp->header_end = p;
+                state = sw_almost_done;
+                break;
+            case LF:
+                hp->header_start = p;
+                hp->header_end = p;
+                goto done;
+            default:
+                hp->header_start = p;
+                state = sw_value;
+                break;
+            }
+            break;
+
+        /* header value */
+        case sw_value:
+            switch (ch) {
+            case ' ':
+                hp->header_end = p;
+                state = sw_space_after_value;
+                break;
+            case CR:
+                hp->header_end = p;
+                state = sw_almost_done;
+                break;
+            case LF:
+                hp->header_end = p;
+                goto done;
+            }
+            break;
+
+        /* space* before end of header line */
+        case sw_space_after_value:
+            switch (ch) {
+            case ' ':
+                break;
+            case CR:
+                state = sw_almost_done;
+                break;
+            case LF:
+                goto done;
+            default:
+                state = sw_value;
+                break;
+            }
+            break;
+
+        /* end of header line */
+        case sw_almost_done:
+            switch (ch) {
+            case LF:
+                goto done;
+            default:
+                return NGX_ERROR;
+            }
+
+        /* end of header */
+        case sw_header_almost_done:
+            switch (ch) {
+            case LF:
+                goto header_done;
+            default:
+                return NGX_ERROR;
+            }
+        }
+    }
+
+    b->pos = p;
+    hp->state = state;
+
+    return NGX_AGAIN;
+
+done:
+
+    b->pos = p + 1;
+    hp->state = sw_start;
+
+    return NGX_OK;
+
+header_done:
+
+    b->pos = p + 1;
+    hp->state = sw_start;
+
+    return NGX_DONE;
+}
+
+
+#define                                                                       \
+ngx_size_is_sufficient(cs)                                                    \
+    (cs < ((__typeof__(cs)) 1 << (sizeof(cs) * 8 - 4)))
+
+
+#define NGX_JS_HTTP_CHUNK_MIDDLE     0
+#define NGX_JS_HTTP_CHUNK_ON_BORDER  1
+#define NGX_JS_HTTP_CHUNK_END        2
+
+
+static ngx_int_t
+ngx_js_http_chunk_buffer(ngx_js_http_chunk_parse_t *hcp, ngx_buf_t *b,
+    njs_chb_t *chain)
+{
+    size_t  size;
+
+    size = b->last - hcp->pos;
+
+    if (hcp->chunk_size < size) {
+        njs_chb_append(chain, hcp->pos, hcp->chunk_size);
+        hcp->pos += hcp->chunk_size;
+
+        return NGX_JS_HTTP_CHUNK_END;
+    }
+
+    njs_chb_append(chain, hcp->pos, size);
+    hcp->pos += size;
+
+    hcp->chunk_size -= size;
+
+    if (hcp->chunk_size == 0) {
+        return NGX_JS_HTTP_CHUNK_ON_BORDER;
+    }
+
+    return NGX_JS_HTTP_CHUNK_MIDDLE;
+}
+
+
+static ngx_int_t
+ngx_js_http_parse_chunked(ngx_js_http_chunk_parse_t *hcp,
+    ngx_buf_t *b, njs_chb_t *chain)
+{
+    u_char     c, ch;
+    ngx_int_t  rc;
+
+    enum {
+        sw_start = 0,
+        sw_chunk_size,
+        sw_chunk_size_linefeed,
+        sw_chunk_end_newline,
+        sw_chunk_end_linefeed,
+        sw_chunk,
+    } state;
+
+    state = hcp->state;
+
+    hcp->pos = b->pos;
+
+    while (hcp->pos < b->last) {
+        /*
+         * The sw_chunk state is tested outside the switch
+         * to preserve hcp->pos and to not touch memory.
+         */
+        if (state == sw_chunk) {
+            rc = ngx_js_http_chunk_buffer(hcp, b, chain);
+            if (rc == NGX_ERROR) {
+                return rc;
+            }
+
+            if (rc == NGX_JS_HTTP_CHUNK_MIDDLE) {
+                break;
+            }
+
+            state = sw_chunk_end_newline;
+
+            if (rc == NGX_JS_HTTP_CHUNK_ON_BORDER) {
+                break;
+            }
+
+            /* rc == NGX_JS_HTTP_CHUNK_END */
+        }
+
+        ch = *hcp->pos++;
+
+        switch (state) {
+
+        case sw_start:
+            state = sw_chunk_size;
+
+            c = ch - '0';
+
+            if (c <= 9) {
+                hcp->chunk_size = c;
+                continue;
+            }
+
+            c = (ch | 0x20) - 'a';
+
+            if (c <= 5) {
+                hcp->chunk_size = 0x0A + c;
+                continue;
+            }
+
+            return NGX_ERROR;
+
+        case sw_chunk_size:
+
+            c = ch - '0';
+
+            if (c > 9) {
+                c = (ch | 0x20) - 'a';
+
+                if (c <= 5) {
+                    c += 0x0A;
+
+                } else if (ch == '\r') {
+                    state = sw_chunk_size_linefeed;
+                    continue;
+
+                } else {
+                    return NGX_ERROR;
+                }
+            }
+
+            if (ngx_size_is_sufficient(hcp->chunk_size)) {
+                hcp->chunk_size = (hcp->chunk_size << 4) + c;
+                continue;
+            }
+
+            return NGX_ERROR;
+
+        case sw_chunk_size_linefeed:
+            if (ch == '\n') {
+
+                if (hcp->chunk_size != 0) {
+                    state = sw_chunk;
+                    continue;
+                }
+
+                hcp->last = 1;
+                state = sw_chunk_end_newline;
+                continue;
+            }
+
+            return NGX_ERROR;
+
+        case sw_chunk_end_newline:
+            if (ch == '\r') {
+                state = sw_chunk_end_linefeed;
+                continue;
+            }
+
+            return NGX_ERROR;
+
+        case sw_chunk_end_linefeed:
+            if (ch == '\n') {
+
+                if (!hcp->last) {
+                    state = sw_start;
+                    continue;
+                }
+
+                return NGX_OK;
+            }
+
+            return NGX_ERROR;
+
+        case sw_chunk:
+            /*
+             * This state is processed before the switch.
+             * It added here just to suppress a warning.
+             */
+            continue;
+        }
+    }
+
+    hcp->state = state;
+
+    return NGX_AGAIN;
+}
+
+
+njs_inline njs_int_t
+ngx_js_http_whitespace(u_char c)
+{
+    switch (c) {
+    case 0x09:  /* <TAB>  */
+    case 0x0A:  /* <LF>   */
+    case 0x0D:  /* <CR>   */
+    case 0x20:  /* <SP>   */
+        return 1;
+
+    default:
+        return 0;
+    }
+}
+
+
+void
+ngx_js_http_trim(u_char **value, size_t *len,
+    njs_bool_t trim_c0_control_or_space)
+{
+    u_char  *start, *end;
+
+    start = *value;
+    end = start + *len;
+
+    for ( ;; ) {
+        if (start == end) {
+            break;
+        }
+
+        if (ngx_js_http_whitespace(*start)
+            || (trim_c0_control_or_space && *start <= ' '))
+        {
+            start++;
+            continue;
+        }
+
+        break;
+    }
+
+    for ( ;; ) {
+        if (start == end) {
+            break;
+        }
+
+        end--;
+
+        if (ngx_js_http_whitespace(*end)
+            || (trim_c0_control_or_space && *end <= ' '))
+        {
+            continue;
+        }
+
+        end++;
+        break;
+    }
+
+    *value = start;
+    *len = end - start;
+}
+
+
+static const uint32_t  token_map[] = {
+    0x00000000,  /* 0000 0000 0000 0000  0000 0000 0000 0000 */
+
+                 /* ?>=< ;:98 7654 3210  /.-, +*)( '&%$ #"!  */
+    0x03ff6cfa,  /* 0000 0011 1111 1111  0110 1100 1111 1010 */
+
+                 /* _^]\ [ZYX WVUT SRQP  ONML KJIH GFED CBA@ */
+    0xc7fffffe,  /* 1100 0111 1111 1111  1111 1111 1111 1110 */
+
+                 /*  ~}| {zyx wvut srqp  onml kjih gfed cba` */
+    0x57ffffff,  /* 0101 0111 1111 1111  1111 1111 1111 1111 */
+
+    0x00000000,  /* 0000 0000 0000 0000  0000 0000 0000 0000 */
+    0x00000000,  /* 0000 0000 0000 0000  0000 0000 0000 0000 */
+    0x00000000,  /* 0000 0000 0000 0000  0000 0000 0000 0000 */
+    0x00000000,  /* 0000 0000 0000 0000  0000 0000 0000 0000 */
+};
+
+
+njs_inline njs_bool_t
+njs_is_token(uint32_t byte)
+{
+    return ((token_map[byte >> 5] & ((uint32_t) 1 << (byte & 0x1f))) != 0);
+}
+
+
+ngx_int_t
+ngx_js_check_header_name(u_char *name, size_t len)
+{
+    u_char  *p, *end;
+
+    p = name;
+    end = p + len;
+
+    while (p < end) {
+        if (!njs_is_token(*p)) {
+            return NGX_ERROR;
+        }
+
+        p++;
+    }
+
+    return NGX_OK;
+}
diff --git a/nginx/ngx_js_http.h b/nginx/ngx_js_http.h
new file mode 100644
index 000000000..089517f2b
--- /dev/null
+++ b/nginx/ngx_js_http.h
@@ -0,0 +1,160 @@
+
+/*
+ * Copyright (C) Dmitry Volyntsev
+ * Copyright (C) hongzhidao
+ * Copyright (C) Antoine Bonavita
+ * Copyright (C) NGINX, Inc.
+ */
+
+
+#ifndef _NGX_JS_HTTP_H_INCLUDED_
+#define _NGX_JS_HTTP_H_INCLUDED_
+
+
+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;
+
+
+typedef struct ngx_js_tb_elt_s  ngx_js_tb_elt_t;
+
+struct ngx_js_tb_elt_s {
+    ngx_uint_t        hash;
+    ngx_str_t         key;
+    ngx_str_t         value;
+    ngx_js_tb_elt_t  *next;
+};
+
+
+typedef struct {
+    enum {
+        GUARD_NONE = 0,
+        GUARD_REQUEST,
+        GUARD_IMMUTABLE,
+        GUARD_RESPONSE,
+    }                              guard;
+    ngx_list_t                     header_list;
+    ngx_js_tb_elt_t               *content_type;
+} 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;
+    ngx_str_t                      url;
+    ngx_str_t                      method;
+    u_char                         m[8];
+    uint8_t                        body_used;
+    ngx_str_t                      body;
+    ngx_js_headers_t               headers;
+    njs_opaque_value_t             header_value;
+} ngx_js_request_t;
+
+
+typedef struct {
+    ngx_str_t                      url;
+    ngx_int_t                      code;
+    ngx_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;
+
+    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;
+
+    unsigned                       header_only;
+
+#if (NGX_SSL)
+    ngx_str_t                      tls_name;
+    ngx_ssl_t                     *ssl;
+    njs_bool_t                     ssl_verify;
+#endif
+
+    ngx_buf_t                     *buffer;
+    ngx_buf_t                     *chunk;
+    njs_chb_t                      chain;
+
+    ngx_js_response_t              response;
+
+    uint8_t                        done;
+    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);
+    ngx_int_t                    (*append_headers)(ngx_js_http_t *http,
+                                                   ngx_js_headers_t *headers,
+                                                   u_char *name, size_t len,
+                                                   u_char *value, size_t vlen);
+    void                         (*ready_handler)(ngx_js_http_t *http);
+    void                         (*error_handler)(ngx_js_http_t *http,
+                                                  const char *err);
+};
+
+
+ngx_resolver_ctx_t *ngx_js_http_resolve(ngx_js_http_t *http, ngx_resolver_t *r,
+    ngx_str_t *host, in_port_t port, ngx_msec_t timeout);
+void ngx_js_http_connect(ngx_js_http_t *http);
+void ngx_js_http_resolve_done(ngx_js_http_t *http);
+void ngx_js_http_close_peer(ngx_js_http_t *http);
+void ngx_js_http_trim(u_char **value, size_t *len,
+    njs_bool_t trim_c0_control_or_space);
+ngx_int_t ngx_js_check_header_name(u_char *name, size_t len);
+
+
+#endif /* _NGX_JS_HTTP_H_INCLUDED_ */