NGINX JavaScript 사용하여 API 요청 일괄 처리

NGINX Plus R15와 함께 출시된 NGINX JavaScript 모듈은 이제 하위 요청을 발행할 수 있습니다. 즉, JavaScript 코드에서 요청을 시작할 수 있습니다. 이를 통해 완전히 새로운 문제들을 해결할 수 있습니다.

이러한 사용 사례 중 하나는 클라이언트의 단일 API 요청이 Backend 서버에 대한 여러 API 요청으로 전환되고 응답이 클라이언트에 대한 단일 응답으로 집계될 수 있도록 API 요청을 일괄 처리하는 것입니다. 이 포스트에서는 전자상거래 사이트를 예로 사용하고 있습니다. 클라이언트가 특정 제품에 대한 정보를 요청하면 카탈로그, 재고 및 고객 리뷰의 세 가지 Backend 서비스에 대한 하위 요청이 이루어집니다.

이 포스트는 하위 요청 예제를 기반으로 하며, 하위 요청을 사용하여 두 개의 Backend 서버에 동일한 요청을 보내고 첫 번째 응답만 클라이언트에 반환하는 방법을 보여줍니다.

js_import 지시문을 사용하도록 업데이트되었으며 NGINX Plus R23 이상의 JS_INCLUDE 지침을 대체합니다. – 예제 구성 섹션에는 NGINX 구성 및 JavaScript 파일의 올바른 구문이 표시됩니다.

목차

1. NGINX Javascript 소개
2. NGINX Javascript 솔루션 개요
3. JavaScript 프로그램
4. Minimal NGINX Plus 구성
5. 일괄 API 요청 구성
6. 예제 실행
7. NGINX Plus 구성을 확장하여 URI 변환
8. 추가 구성 요소
9. 결론

1. NGINX Javascript 소개

이 포스트의 목표는 간단한 API 요청 배치의 실제 사례를 제공하는 것입니다. 이러한 예는 다음과 같은 요구사항을 모두 충족합니다.

  • HTTP GET 메소드는 모든 요청에 사용됩니다.
  • 모든 응답은 JSON 형식입니다.
  • 두 가지 스타일의 API 요청이 지원됩니다.
    • API 프로그램에 대한 논쟁은 URI의 최종 요소입니다. 우리는 이것을 Final‑Element Request 스타일이라고 부릅니다. 이 예에서/batch-api/product/14286에 대한 클라이언트 요청이 /myapi/catalog/14286, /myapi/inventory/14286/myapi/review/14286에 대한 요청을 생성합니다.
    • 인수는 URI의 Query String에서 식별됩니다. 우리는 이것을 Query‑String Request 스타일이라고 합니다. 이 예에서 /batch-api2/product?item=14286에 대한 요청은 /myapi/catalog.php?item=14286, /myapi/inventory.php?item=14286/myapi/review.php?item=14286에 대한 요청을 생성합니다.
    • 두 스타일 모두 Query String에 추가 인수가 있을 수 있습니다.
  • 클라이언트 요청에 의해 트리거 된 하위 요청 집합은 동적으로 구성할 수 있습니다. 즉, NGINX Plus 구성을 수동으로 편집하고 다시 로드하지 않고도 변경할 수 있습니다.
  • Backend 서버에 대한 API 요청은 서로 독립적입니다.

2. NGINX Javascript 솔루션 개요

이 솔루션은 NGINX JavaScript의 새로운 하위 요청 기능을 사용하는 것 외에도 NGINX Plus의 Key-Value Store 기능을 사용하여 NGINX Plus API와 동적으로 구성할 수 있습니다.

다음 이미지는 지원되는 두 가지 요청 스타일을 보여줍니다.

Final‑Element Request 스타일

Client makes final-element style requests to an API

Query‑String Request 스타일

Client makes query-string style requests to an API

두 예 모두에서 클라이언트는 카탈로그, 인벤토리 및 검토 서비스에서 제품에 대한 정보를 가져와야 합니다. 이것은 아이템 번호를 URI의 일부로 전달하거나 Query String에 전달하는 요청을 NGINX Plus로 전송합니다. 그런 다음 요청이 세 서비스로 전송되고 응답은 클라이언트에 대한 단일 응답으로 집계됩니다.

이것을 NGINX Plus에서 작동시키기 위해 필요한 두 가지 구성 요소가 있습니다. 바로 NGINX JavaScript 프로그램NGINX Plus 구성입니다.

3. NGINX JavaScript 프로그램

JavaScript 함수 batchAPI ()는 각 클라이언트 요청에 대해 호출됩니다. Key-Value Store에서 쉼표로 구분된 API URIs 목록을 가져와서 이 목록을 반복하여 각각에 대한 하위 요청을 만들고 각 응답을 처리하기 위해 Callback 함수를 지정합니다. Final‑Element Request 스타일의 경우 NGINX 변수 $uri_suffix에 저장된 URI의 마지막 요소는 원래 클라이언트 URI의 다른 Query의 Query 인수와 함께 각 하위 요청의 Query String로 전달됩니다. Query‑String Request 스타일의 경우 클라이언트 요청의 Query String이 각 하위 요청의 Query String로 전달됩니다.

통화는 Key-Value Store에 나열된 순서대로 이루어지지만 비동기식이므로 응답은 임의의 순서로 돌아올 수 있습니다. 응답은 수신된 순서대로 클라이언트에 대한 하나의 응답으로 집계됩니다. 다음은 NGINX JavaScript 프로그램의 Minimal 버전입니다.

function batchAPI(r) {
    var n = 0, requestCount = 0;
    var resp = "[";
    var errorOccured = false;
    var keyval = "batch_api";

    function done(reply) { // Callback for completed subrequests
        n++;
        if (errorOccured) { /* Once one response has an error stop processing
                               any more responses */
            return;
        }
        if (n < requestCount) {
            if (reply.status != 200) {
                errorOccured = true;
                r.log("Error in response " + n.toString() + " " + reply.uri +
                      " " + reply.status.toString());
                r.return(reply.status, "Error in response " + n.toString() +
                         " " + reply.uri + "\n");
            } else {
                resp += '["' + reply.uri + '",' + reply.body + '],';
            }
        } else { // Last response
            if (reply.status != 200) {
                errorOccured = true;
                r.log("Error in response " + n.toString() + " " + reply.uri +
                      " " + reply.status.toString());
                r.return(reply.status, "Error in response " + n.toString() +
                         " " + reply.uri + "\n");
            } else {
                resp += '["' + reply.uri + '",' + reply.body + ']';
                r.return(200, resp);
            }
        }
    }

    var argInURI = r.variables.batch_api_arg_in_uri.toLowerCase();
    if (argInURI != "on") {
        keyval = "batch_api2";
    }

    var apiURIs = r.variables[keyval].split(",");
    requestCount = apiURIs.length;
    for (var i = 0; i < requestCount; i++) {
        if (argInURI == "on") {
            r.subrequest(apiURIs[i] + "/" + r.variables.uri_suffix,
                         r.variables.args, done);
        } else {
            r.subrequest(apiURIs[i], r.variables.args, done);
        }
    }
}

export default { batchAPI }

보다 광범위한 오류 처리, 로깅 및 주석이 포함된 NGINX JavaScript 프로그램 버전은 GitHub Gist repo에서 batch-api.js로 사용할 수 있습니다.

4. Minimal NGINX Plus 구성

다음 NGINX Plus 구성은 Upstream 서버 그룹을 사용하여 위에서 설명한 두 가지 요청 스타일을 구현합니다. 간단하게 하기 위해 이 구성은 Upstream 서버가 /myapi/service/item#(Final‑Element 스타일) 형식의 호출을 /myapi/service.php/?item=item#(Query‑String 스타일), 샘플 카탈로그, 인벤토리 및 리뷰 서비스용 언어인 PHP의 “일반적인” URI 형식입니다.

변환 자체를 수행하는 보다 광범위한 NGINX Plus 구성은 아래의 URI 변환을 위해 NGINX 구성 확장을 참조하십시오.

섹션 별로 구성 파일 섹션을 살펴보고 각 부분이 무엇을 하는지 설명하겠습니다.


js_import batch-api-min.js;
  • URI의 마지막 요소가 API 프로그램에 대한 인수를 식별하는 API 요청 목록을 저장할 Key-Value Store를 정의합니다. 이 키는 $uri_prefix 변수를 사용하여 마지막 /: 이전의 URI 부분을 캡처합니다.
keyval_zone zone=batch_api:64k state=/etc/nginx/state-files/batch-api.json;
keyval $uri_prefix $batch_api zone=batch_api;
  • NGINX Plus R16 이상에서는 두 가지 추가 Key-Value 기능을 활용할 수 있습니다.
    • keyval_zone 지시문에 timeout 매개 변수를 추가하여 Key-Value Store의 항목에 대한 만료 시간을 설정합니다. 예를 들어, 배치된 각 URI가 매일 만료되도록 하려면 timeout=1d를 추가합니다.
    • keyval_zone 지시문에 sync 매개 변수를 추가하여 NGINX Plus 인스턴스 클러스터에서 Key-Value Store를 동기화하십시오. 이 경우 timeout 매개 변수도 포함해야 합니다.
  • Query String에서 인수가 식별되는 API에 대해 다른 Key-Value Store를 정의합니다. 여기서 키($uri)는 Query String을 포함하지 않고 전체 URI를 캡처합니다.
keyval_zone zone=batch_api2:64k state=/etc/nginx/state-files/batch-api2.json;
keyval $uri $batch_api2 zone=batch_api2;
  • 두 개의 map을 정의하여 요청을 수락하는 API에 대해 URI를 두 부분으로 분할합니다. 여기서 인수는 URI의 마지막 요소입니다. $uri_prefix 변수는 마지막 / 및 $uri_suffix가 마지막 요소를 캡처하기 전에 URI 요소를 캡처합니다.
map $uri $uri_prefix {
    ~^(?<p>.+)\/.+$ $p;
}

map $uri $uri_suffix {
    ~^.+\/(?<s>.+)$ $s;
}
  • API 서버의 Upstream 그룹을 정의하십시오. (여기서는 NGINX Plus와 함께 localhost에서 실행됩니다.)

upstream api_servers {
    zone api_servers 64k;
    server 127.0.0.1:9080;
    server 127.0.0.1:9081;
}
  • 클라이언트 요청 및 하위 요청을 처리하는 가상 서버를 정의합니다.
server {
    listen 80;
  • 배치에 표시되는 Final‑Element 스타일을 사용하는 클라이언트 요청을 처리할 location 정의$batch_api_arg_in_uri 변수를 on으로 설정하는 API 함수입니다.
    location /batch-api {
        set $batch_api_arg_in_uri on;
        js_content batch-api-min.batchAPI;
    }
  • 일괄 처리에 표시되는 Query‑String 스타일을 사용하는 클라이언트 요청을 처리할 위치 정의$batch_api_arg_in_uri 변수를 off로 설정하는 API 함수입니다.
    location /batch-api2 {
        set $batch_api_arg_in_uri off;
        js_content batch-api-min.batchAPI;
    }
  • 하위 요청을 처리하는 위치를 정의합니다.
    location /myapi {
        proxy_pass http://api_servers;
    }
  • Key‑Value Store에서 데이터를 유지 관리할 수 있도록 NGINX Plus API를 활성화합니다.
    location /api {
        api write=on;
        # directives to restrict access to the API
    }

전체 구성은 다음과 같습니다.

js_import batch-api-min.js;

# keyval_zone for APIs where the last portion of the URI is an argument
# The key is the portion of the URL before the last part
keyval_zone zone=batch_api:64k state=/etc/nginx/state-files/batch-api.json;
keyval $uri_prefix $batch_api zone=batch_api;

# keyval_zone for APIs where the last portion of the URI is an argument
# The key is the URI
keyval_zone zone=batch_api2:64k state=/etc/nginx/state-files/batch-api2.json;
keyval $uri $batch_api2 zone=batch_api2;

map $uri $uri_prefix {
    ~^(?<p>.+)\/.+$ $p;
}

map $uri $uri_suffix {
    ~^.+\/(?<s>.+)$ $s;
}

upstream api_servers {
    zone api_servers 64k;
    server 127.0.0.1:9080;
    server 127.0.0.1:9081;
}

server {
    listen 80;

    location /batch-api {
        set $batch_api_arg_in_uri on;
        js_content batch-api-min.batchAPI;
    }

    location /batch-api2 {
        set $batch_api_arg_in_uri off;
        js_content batch-api-min.batchAPI;
    }

    location /myapi {
        proxy_pass http://api_servers;
    }

    location /api {
        api write=on;
        # directives to restrict access to the API
    }
}

5. 일괄 API 요청 구성

우리는 NGINX Plus Key-Value Store 기능을 사용하여 인바운드 클라이언트 요청을 실행할 API 요청 세트에 매핑하고 있습니다. 이전 섹션의 구성은 두 가지 요청 스타일에 대해 별도의 Key-Value Store를 정의합니다.

  • Final‑Element 스타일의 Key-Value Store는 batch_api라고 하며 Key는 $uri_prefix 변수이며 요청 URI의 최종/이전의 요소를 캡처합니다.
  • Query‑String 스타일의 Key-Value Store는 batch_api2라고 하며 Key는 $uri 변수이며 Query String 없이 전체 요청 URI를 캡처합니다.

두 Key-Value Store 모두에서 각 키와 관련된 값은 $batch_api 또는 $batch_api2 변수에서 런타임에 사용할 수 있는 쉼표로 구분된 URI 목록입니다.

Final‑Element 스타일 요청의 경우 /batch-api/product에 대한 클라이언트 요청을 /myapi/catalog, /myapi/inventory/myapi/review의 세 서비스 요청에 매핑하여 /batch-api/product/14286에 대한 요청인 /myapi/catalog/14286, /myapi/product/review 요청을 생성합니다.

이러한 매핑을 batch_api Key-Value Store에 추가하기 위해 로컬 시스템에서 실행되는 NGINX Plus 인스턴스에 다음 요청을 보냅니다:

$ curl -iX POST -d '{"/batch-api/product":"/myapi/catalog,/myapi/inventory,/myapi/review"}' http://localhost/api/3/http/keyvals/batch_api

Query-String 스타일 요청의 경우 /batch-api2/product에 대한 클라이언트 요청을 세 서비스 /myapi/catalog.php, /myapi/inventory.php/myapi/review.php 에 대한 요청에 매핑하려고 합니다. 즉 /myapi-api2/product?item=14286에 대한 요청이 /myapi/catalog?item=14286, /myapi/product?item=14286/myapi/review?item=14286에 대한 요청으로 이어집니다.

이러한 매핑을 batch_api2 Key-Value Store에 추가하려면 다음 요청을 전송합니다.

$ curl -iX POST -d '{"/batch-api2/product":"/myapi/catalog.php,/myapi/inventory.php,/myapi/review.php"}' http://localhost/api/3/http/keyvals/batch_api2

6. 예제 실행

동일한 PHP 페이지는 두 요청 스타일 모두에서 작성된 요청을 처리합니다. 간단하게 하기 위해 Backend 서버는 Final‑Element 스타일 URI (/myapi/service/item#)를 Query‑String URI (/myapi/service.php?item=item#)로 변환할 수 있다고 가정합니다. PHP 프로그램의 “기본”URI 형식이므로 Query‑String 스타일 URI를 번역할 필요는 없습니다.

모든 서비스는 서비스 이름 및 항목 인수를 포함하는 JSON- 형식의 응답을 반환합니다. 예를 들어, /myapi/catalog.php?item=14286에 대한 요청은 catalog.php의 다음 응답을 초래합니다.

{"service":"Catalog","item":"14286"}

이러한 예에서 사용되는 PHP 프로그램은 응답에 서비스 이름을 포함하지만 모든 서비스의 경우에는 그렇지 않을 수 있습니다. 이를 생성한 서비스에 대한 응답을 일치 시키기 위해 JavaScript 프로그램은 집계된 응답에 URI를 포함합니다.

예를 들어 /batch-api/product/14286에 대한 요청에 대한 집계된 응답은 다음과 같습니다(세 가지 구성 요소 응답의 순서는 다를 수 있습니다).

[["/myapi/review/14286",{"service":"Review","item":"14286"}], ["/myapi/inventory/14286",{"service":"Inventory","item":"14286"}], ["/myapi/catalog/14286",{"service":"Catalog","item":"14286"}]]

7. NGINX Plus 구성을 확장하여 URI 변환

위에서 논의된 Minimal NGINX Plus 구성은 API 서버가 /myapi/service/item# 형식의 URI를/myapi/service.php/?item=item#의 URI를 변환할 수 있다고 가정합니다. 또한 다음을 변경하여 NGINX Plus 구성에서 변환을 구현할 수도 있습니다.

upstream services {
    zone services 64k;
    server 127.0.0.1:9000;
}
  • services Upstream 그룹에서 받은 요청을 수신할 새 가상 서버를 추가하십시오. Query‑String 요청이 수신되면 URI에는 이미 .php 확장이 있기 때문에 api_servers Upstream 그룹에 Proxy 됩니다. Final‑Element 스타일 요청이 접수되면 URI의 마지막 요소가 제거되어 Query‑String 인수로 변환되도록 URI가 다시 작성됩니다.

server {
    listen 9000;

    location / {
        rewrite ^(.+)\/(.+)$ $1.php?item=$2 last;
    }

    location ~ \.php$ {
        proxy_pass http://api_servers;
    }
}

/myapi location를 새로운 Upstream 그룹으로 Proxy 하여 변경합니다.

    location /myapi {
        proxy_pass http://services;
    }

전체 구성은 다음과 같습니다.

js_import batch-api.js;

# keyval_zone for APIs where the last portion of the URI is an argument
# The key is the portion of the URL before the last part set in the map
keyval_zone zone=batch_api:64k state=/etc/nginx/state-files/batch-api.json;
keyval $uri_prefix $batch_api zone=batch_api;

# keyval_zone for APIs where the last portion of the URI is an argument
# The key is the URI
keyval_zone zone=batch_api2:64k state=/etc/nginx/state-files/batch-api2.json;
keyval $uri $batch_api2 zone=batch_api2;

# These maps are for breaking the URI into two parts for APIs where the
# last part of the URI is an argument.  For URIs of the form:
#     /<part 1>/<part 2>/<part n>
#     $uri_prefix = /<part 1>/<part 2>
#     $uri_suffix = <part n>
map $uri $uri_prefix {
    ~^(?<p>.+)\/.+$ $p;
}

map $uri $uri_suffix {
    ~^.+\/(?<s>.+)$ $s;
}

upstream api_servers {
    zone api_servers 64k;
    server 127.0.0.1:9080;
    server 127.0.0.1:9081;
}

upstream services {
    zone services 64k;
    server 127.0.0.1:9000;
}

server {
    listen 9000;

    location / {
        rewrite ^(.+)\/(.+)$ $1.php?item=$2 last;
    }

    location ~ \.php$ {
        proxy_pass http://api_servers;
    }
}

server {
    listen 80;

    set $batch_api_verbose on;

    location /batch-api {
        set $batch_api_arg_in_uri on;
        js_content batch-api.batchAPI;
    }

    location /batch-api2 {
        set $batch_api_arg_in_uri off;
        js_content batch-api.batchAPI;
    }

    location /myapi {
        proxy_pass http://services;
    }

    location /api {
        api write=on;
        # directives to restrict access to the API
    }
}

8. 추가 구성 요소

샘플 JavaScript 프로그램과 NGINX Plus 구성은 다른 스타일의 API에 적용할 수 있지만, 이 포스트를 만드는 데 사용되는 모든 구성 요소를 사용하여 테스트하고 싶다면 GitHub에서 PHP 페이지를 제공하기 위한 NGINX Unit 구성을 포함하여 사용할 수 있습니다

batch-api.js
catalog.php
inventory.php
review.php
unit.config

9. 결론

예제는 API 요청을 일괄 처리하고 클라이언트에 집계된 응답을 제공하는 방법을 보여줍니다. NGINX Plus Key-Value Store를 사용하면 NGINX Plus API를 사용하여 API 요청을 동적으로 구성할 수 있습니다. API 시스템은 정확한 작동 방식이 상당히 다양하므로 특정 API의 요구에 맞게 이 예제를 개선할 수 있는 많은 기능이 있습니다.

NGINX Plus API 및 Key-Value Store 와 같은 NGINX Plus 기능을 직접 사용해 보거나 테스트해 보려면 지금 30일 무료 평가판을 신청하거나 사용 사례에 대해 최신 소식을 빠르게 전달받고 싶으시면 아래 뉴스레터를 구독하세요.

NGINX STORE 뉴스레터 및 최신 소식 구독하기

* indicates required