Skip to content

Commit fb71984

Browse files
committed
Fetch: added forward proxy support with HTTPS tunneling.
- js_fetch_proxy - Configures forward proxy URL example.conf: ... http { js_import main.js; server { listen 8080; resolver 127.0.0.1:5353; location /api { js_fetch_proxy http://user:pass@proxy.example.com:3128; js_content main.fetch_handler; } } } This implements #956 feature request on Github.
1 parent 8a529d5 commit fb71984

File tree

10 files changed

+978
-20
lines changed

10 files changed

+978
-20
lines changed

nginx/ngx_http_js_module.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,13 @@ static ngx_command_t ngx_http_js_commands[] = {
593593
offsetof(ngx_http_js_loc_conf_t, fetch_keepalive_timeout),
594594
NULL },
595595

596+
{ ngx_string("js_fetch_proxy"),
597+
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
598+
ngx_js_fetch_proxy,
599+
NGX_HTTP_LOC_CONF_OFFSET,
600+
0,
601+
NULL },
602+
596603
ngx_null_command
597604
};
598605

@@ -818,6 +825,16 @@ static njs_external_t ngx_http_js_ext_request[] = {
818825
}
819826
},
820827

828+
{
829+
.flags = NJS_EXTERN_PROPERTY,
830+
.name.string = njs_str("requestLine"),
831+
.enumerable = 1,
832+
.u.property = {
833+
.handler = ngx_js_ext_string,
834+
.magic32 = offsetof(ngx_http_request_t, request_line),
835+
}
836+
},
837+
821838
{
822839
.flags = NJS_EXTERN_PROPERTY,
823840
.name.string = njs_str("requestText"),
@@ -1080,6 +1097,8 @@ static const JSCFunctionListEntry ngx_http_qjs_ext_request[] = {
10801097
JS_CGETSET_DEF("remoteAddress", ngx_http_qjs_ext_remote_address, NULL),
10811098
JS_CGETSET_MAGIC_DEF("requestBuffer", ngx_http_qjs_ext_request_body, NULL,
10821099
NGX_JS_BUFFER),
1100+
JS_CGETSET_MAGIC_DEF("requestLine", ngx_http_qjs_ext_string, NULL,
1101+
offsetof(ngx_http_request_t, request_line)),
10831102
JS_CGETSET_MAGIC_DEF("requestText", ngx_http_qjs_ext_request_body, NULL,
10841103
NGX_JS_STRING),
10851104
JS_CGETSET_MAGIC_DEF("responseBuffer", ngx_http_qjs_ext_response_body, NULL,

nginx/ngx_js.c

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3305,6 +3305,85 @@ ngx_js_preload_object(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
33053305
}
33063306

33073307

3308+
char *
3309+
ngx_js_fetch_proxy(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
3310+
{
3311+
u_char *p, *at, *colon, *host_start;
3312+
size_t len;
3313+
ngx_url_t *u;
3314+
ngx_str_t *value;
3315+
ngx_str_t userpass, b64;
3316+
ngx_js_loc_conf_t *jscf;
3317+
3318+
jscf = conf;
3319+
3320+
value = cf->args->elts;
3321+
3322+
if (ngx_strncmp(value[1].data, "http://", sizeof("http://") - 1) != 0) {
3323+
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
3324+
"js_fetch_proxy URL must use http:// scheme");
3325+
return NGX_CONF_ERROR;
3326+
}
3327+
3328+
host_start = value[1].data + (sizeof("http://") - 1);
3329+
at = ngx_strlchr(host_start, value[1].data + value[1].len, '@');
3330+
3331+
if (at != NULL) {
3332+
colon = ngx_strlchr(host_start, at, ':');
3333+
3334+
if (colon == NULL) {
3335+
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
3336+
"js_fetch_proxy URL credentials must be in "
3337+
"user:password format");
3338+
return NGX_CONF_ERROR;
3339+
}
3340+
3341+
userpass.data = host_start;
3342+
userpass.len = at - host_start;
3343+
3344+
b64.len = ngx_base64_encoded_length(userpass.len);
3345+
b64.data = ngx_pnalloc(cf->pool, b64.len);
3346+
if (b64.data == NULL) {
3347+
return NGX_CONF_ERROR;
3348+
}
3349+
3350+
ngx_encode_base64(&b64, &userpass);
3351+
3352+
len = sizeof("Proxy-Authorization: Basic \r\n") - 1 + b64.len;
3353+
p = ngx_pnalloc(cf->pool, len);
3354+
if (p == NULL) {
3355+
return NGX_CONF_ERROR;
3356+
}
3357+
3358+
ngx_sprintf(p, "Proxy-Authorization: Basic %V\r\n", &b64);
3359+
jscf->fetch_proxy_auth_header.data = p;
3360+
jscf->fetch_proxy_auth_header.len = len;
3361+
3362+
host_start = at + 1;
3363+
}
3364+
3365+
u = ngx_pcalloc(cf->pool, sizeof(ngx_url_t));
3366+
if (u == NULL) {
3367+
return NGX_CONF_ERROR;
3368+
}
3369+
3370+
u->url.data = host_start;
3371+
u->url.len = value[1].data + value[1].len - host_start;
3372+
u->default_port = 3128;
3373+
u->no_resolve = 1;
3374+
3375+
if (ngx_parse_url(cf->pool, u) != NGX_OK) {
3376+
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
3377+
"invalid proxy URL: %V", &value[1]);
3378+
return NGX_CONF_ERROR;
3379+
}
3380+
3381+
jscf->fetch_proxy_url = u;
3382+
3383+
return NGX_CONF_OK;
3384+
}
3385+
3386+
33083387
static ngx_int_t
33093388
ngx_js_init_preload_vm(njs_vm_t *vm, ngx_js_loc_conf_t *conf)
33103389
{
@@ -4121,6 +4200,13 @@ ngx_js_merge_conf(ngx_conf_t *cf, void *parent, void *child,
41214200
ngx_conf_merge_msec_value(conf->fetch_keepalive_timeout,
41224201
prev->fetch_keepalive_timeout, 60000);
41234202

4203+
ngx_conf_merge_str_value(conf->fetch_proxy_auth_header,
4204+
prev->fetch_proxy_auth_header, "");
4205+
4206+
if (conf->fetch_proxy_url == NULL && prev->fetch_proxy_url != NULL) {
4207+
conf->fetch_proxy_url = prev->fetch_proxy_url;
4208+
}
4209+
41244210
ngx_queue_init(&conf->fetch_keepalive_cache);
41254211
ngx_queue_init(&conf->fetch_keepalive_free);
41264212

nginx/ngx_js.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,15 @@ typedef struct {
141141
ngx_msec_t fetch_keepalive_time; \
142142
ngx_msec_t fetch_keepalive_timeout; \
143143
ngx_queue_t fetch_keepalive_cache; \
144-
ngx_queue_t fetch_keepalive_free
144+
ngx_queue_t fetch_keepalive_free; \
145+
\
146+
ngx_url_t *fetch_proxy_url; \
147+
ngx_str_t fetch_proxy_auth_header
148+
149+
150+
#define ngx_js_proxy(conf) \
151+
((conf)->fetch_proxy_url != NULL \
152+
&& (conf)->fetch_proxy_url->host.len > 0)
145153

146154

147155
#if (NGX_SSL)
@@ -423,6 +431,7 @@ void ngx_js_logger(ngx_connection_t *c, ngx_uint_t level,
423431
char * ngx_js_import(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
424432
char * ngx_js_engine(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
425433
char * ngx_js_preload_object(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
434+
char * ngx_js_fetch_proxy(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
426435
ngx_int_t ngx_js_merge_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf,
427436
ngx_js_loc_conf_t *prev,
428437
ngx_int_t (*init_vm)(ngx_conf_t *cf, ngx_js_loc_conf_t *conf));

nginx/ngx_js_fetch.c

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,8 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
510510
ngx_url_t u;
511511
ngx_pool_t *pool;
512512
njs_value_t *init, *value;
513+
ngx_str_t *resolve_host;
514+
in_port_t resolve_port;
513515
ngx_js_http_t *http;
514516
ngx_js_fetch_t *fetch;
515517
ngx_js_request_t request;
@@ -595,12 +597,31 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
595597
NJS_CHB_MP_INIT(&http->chain, njs_vm_memory_pool(vm));
596598
NJS_CHB_MP_INIT(&http->response.chain, njs_vm_memory_pool(vm));
597599

598-
ngx_js_fetch_build_request(http, &request, &u.uri, &u);
600+
ngx_js_fetch_build_request(http, &request, &u.uri, &u,
601+
ngx_js_proxy(http->conf) && !ngx_js_https(&u));
602+
603+
resolve_host = NULL;
604+
resolve_port = 0;
605+
606+
if (ngx_js_proxy(http->conf)) {
607+
if (http->conf->fetch_proxy_url->addrs == NULL) {
608+
resolve_host = &http->conf->fetch_proxy_url->host;
609+
resolve_port = http->conf->fetch_proxy_url->port;
610+
}
611+
612+
} else if (u.addrs == NULL) {
613+
resolve_host = &u.host;
614+
resolve_port = u.port;
615+
}
616+
617+
if (resolve_host != NULL) {
618+
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, http->log, 0,
619+
"js http fetch: resolving");
599620

600-
if (u.addrs == NULL) {
601621
ctx = ngx_js_http_resolve(http, ngx_external_resolver(vm, external),
602-
&u.host, u.port,
622+
resolve_host, resolve_port,
603623
ngx_external_resolver_timeout(vm, external));
624+
604625
if (ctx == NULL) {
605626
njs_vm_memory_error(vm);
606627
return NJS_ERROR;
@@ -617,8 +638,16 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
617638
}
618639

619640
http->naddrs = 1;
620-
ngx_memcpy(&http->addr, &u.addrs[0], sizeof(ngx_addr_t));
621-
http->addrs = &http->addr;
641+
642+
if (ngx_js_proxy(http->conf)) {
643+
ngx_memcpy(&http->addr, &http->conf->fetch_proxy_url->addrs[0],
644+
sizeof(ngx_addr_t));
645+
http->addrs = &http->addr;
646+
647+
} else {
648+
ngx_memcpy(&http->addr, &u.addrs[0], sizeof(ngx_addr_t));
649+
http->addrs = &http->addr;
650+
}
622651

623652
ngx_js_http_connect(http);
624653

@@ -1031,6 +1060,12 @@ ngx_js_fetch_alloc(njs_vm_t *vm, ngx_pool_t *pool, ngx_log_t *log,
10311060
goto failed;
10321061
}
10331062

1063+
/*
1064+
* set by ngx_pcalloc():
1065+
*
1066+
* fetch->http.proxy.state = HTTP_STATE_DIRECT;
1067+
*/
1068+
10341069
http = &fetch->http;
10351070

10361071
http->pool = pool;

0 commit comments

Comments
 (0)