nginx / njs

A subset of JavaScript language to use in nginx
http://nginx.org/en/docs/njs/
BSD 2-Clause "Simplified" License
1.23k stars 155 forks source link

Token "async" not supported in this version #394

Closed fhirfly closed 3 years ago

fhirfly commented 3 years ago

I have this synchronization issue as described here:

https://stackoverflow.com/questions/67800478/nginx-service-callout-for-an-api-access-token-in-api-gateway-proxy-configuration

I am wondering if maybe it is impossible to wait for the function to return before nginix executes proxypass.

I tried doing async on function getGoogleAccessToken(r) and got the error I put in the issue header.

Any ideas on a workaround if the nginx wont wait on the njs function?

Maybe there is a bug in my code, but the logs seem to indicate this problem as coded.

jirutka commented 3 years ago

njs doesn’t support async/await yet. However, async/await is just a syntactic sugar for Promises, which njs does support. So you can just use Promise (API is basically the same as in Node.js or browsers).

Or if you really want async/await syntax, you can transpile your code using Babel and babel-plugin-transform-async-to-promises.

See also babel-preset-njs and njs-typescript-starter.

drsm commented 3 years ago

@fhirfly

You are having troubles with js_set, which is completely synchronous, so you can't wait for subrequest response inside its handler.

Any ideas on a workaround if the nginx wont wait on the njs function?

js_var $xxx;
server {
    listen 10000;
    location = /generate_some_token {
        return 200 "hereisthetoken"; 
    }
    location = /protected_api_endpoint {
        js_content test.protected_api_endpoint;
    }
    location = /proxy_to_api_endpoint {
        proxy_set_header xxx "$xxx";
        proxy_pass http://127.0.0.1:10000/protected_api_endpoint;
    }
    location = /entry_point {
        js_content test.entry_point;
    }
function protected_api_endpoint(r) {
    r.return(200, r.headersIn['xxx'] ? `Hello [${r.headersIn['xxx']}]!` : 'Bad Luck');
}
function entry_point(r) {
    r.subrequest('/generate_some_token')
    .then((res) => {
        r.variables['xxx'] = res.responseBody;
        r.internalRedirect('/proxy_to_api_endpoint');
    }) 
    .catch((e) => { r.error(e.message); r.return(500); });
}
$ curl http://127.0.0.1:10000/entry_point
Hello [hereisthetoken]!
fhirfly commented 3 years ago

So I tested the above theory and it works. The only issue with this code is that there is a 1:1:1 between /entrypoint, function entry_point, and /proxy_to_api_endpoint . Some of my configuration have many endpoints and I would like to reuse some of the code and not have a unmaintainable mess. Does the r.variable have the path of the original request or is there some other variable I can use in the internalredirect or in /proxy_to_api_endpoint to make this dynamic? My proxy doesn't rewrite the path, there is a constant relationship between the nginx gateway URL and the google api url . E.g, /Endpoint on the proxy passes to https://healthcare.googleapis.com/v1beta1/projects/projectname/locations/wherever/datasets/ds/fhirStores/sn/fhir/Endpoint, and the part before /Endpoint on the google side is constant. Thanks for the work around.

277hz commented 3 years ago

@fhirfly $request_uri does not get rewritten (https://nginx.org/en/docs/http/ngx_http_core_module.html#var_request_uri).

    location = /proxy_to_api_endpoint {
        #proxy_set_header xxx "$xxx";
        #proxy_pass http://127.0.0.1:10000/protected_api_endpoint;
        add_header xxx $xxx;
        return 200 $request_uri;
    }

# curl -vvv http://127.0.0.1/entry_point
...
< HTTP/1.1 200 OK
< Server: nginx/1.21.0
< Date: Fri, 04 Jun 2021 12:09:54 GMT
< Content-Type: application/octet-stream
< Content-Length: 12
< Connection: keep-alive
< xxx: hereisthetoken
<
* Connection #0 to host 127.0.0.1 left intact
/entry_point
drsm commented 3 years ago

@fhirfly

js_var $auth;
server {
    resolver 1.1.1.1 ipv6=off;
    listen 10000;
    location = /auth_token {
        internal;
        return 200 "hereisthetoken";
    }
    location = /auth_pass {
        internal;
        proxy_set_header auth "$auth";
        proxy_pass http://127.0.0.1:10000/protected_api_endpoint$request_uri;
    }
    location / {
        js_content test.entry_point;
    }
#google
    location /protected_api_endpoint {
        js_content test.protected_api_endpoint;
    }
}
function protected_api_endpoint(r) {
    r.return(200, r.headersIn['auth'] ? `Hello [auth: ${r.headersIn['auth']}][uri: ${r.uri}]!\n` : 'Bad Luck\n');
}

function entry_point(r) {
    r.subrequest('/auth_token')
    .then((res) => {
        r.variables['auth'] = res.responseText;
        r.internalRedirect('/auth_pass');
    })
    .catch((e) => { r.error(e.message); r.return(500);});
}
$ curl http://127.0.0.1:10000/
Hello [auth: hereisthetoken][uri: /protected_api_endpoint/]!
$ curl http://127.0.0.1:10000/one/two/three
Hello [auth: hereisthetoken][uri: /protected_api_endpoint/one/two/three]!
$ curl http://127.0.0.1:10000/one/two/three?four=five
Hello [auth: hereisthetoken][uri: /protected_api_endpoint/one/two/three]!