Ես ստեղծել եմ իմ սեփական PyPI պահոցը թույլտվությամբ և S3-ով: Nginx-ի վրա

Այս հոդվածում ես կցանկանայի կիսվել իմ փորձով NJS-ի հետ՝ Nginx Inc-ի կողմից մշակված Nginx-ի JavaScript թարգմանչի հետ՝ նկարագրելով դրա հիմնական հնարավորությունները՝ օգտագործելով իրական օրինակ: NJS-ը JavaScript-ի ենթաբազմություն է, որը թույլ է տալիս ընդլայնել Nginx-ի ֆունկցիոնալությունը: Հարցին ինչու ձեր սեփական թարգմանիչը??? Դմիտրի Վոլինցևը մանրամասն պատասխանել է. Մի խոսքով, NJS-ը nginx-way է, իսկ JavaScript-ն ավելի առաջադեմ է, «հայրենի» և առանց GC-ի՝ ի տարբերություն Lua-ի:

Երկար ժամանակ առաջ…

Իմ վերջին աշխատանքի ժամանակ ես ժառանգեցի gitlab մի շարք խայտաբղետ CI/CD խողովակաշարերով՝ docker-compose, dind և այլ հաճույքներով, որոնք փոխանցվեցին kaniko rails-ին: Պատկերները, որոնք նախկինում օգտագործվել են CI-ում, տեղափոխվել են իրենց սկզբնական տեսքով: Նրանք ճիշտ աշխատեցին մինչև այն օրը, երբ մեր gitlab IP-ն փոխվեց և CI-ն վերածվեց դդմի։ Խնդիրն այն էր, որ դոկերի պատկերներից մեկը, որը մասնակցում էր CI-ին, ուներ git, որը քաշում էր Python մոդուլները ssh-ի միջոցով: ssh-ի համար պետք է մասնավոր բանալի և... այն պատկերում էր՝ known_hosts-ի հետ միասին։ Եվ ցանկացած CI ձախողվեց առանցքային ստուգման սխալի պատճառով իրական IP-ի և հայտնի_hosts-ում նշվածի անհամապատասխանության պատճառով: Գոյություն ունեցող Dockfiles-ից արագ հավաքվեց նոր պատկեր և ավելացվեց տարբերակը StrictHostKeyChecking no. Բայց անճաշակությունը մնաց, և ցանկություն առաջացավ տեղափոխել libs-ը մասնավոր PyPI պահոց: Լրացուցիչ բոնուս, մասնավոր PyPI-ին անցնելուց հետո, ավելի պարզ խողովակաշարն էր և պահանջների.txt-ի նորմալ նկարագրությունը։

Ընտրությունը կատարված է, պարոնայք։

Մենք ամեն ինչ գործարկում ենք ամպերի և Kubernetes-ի մեջ, և վերջում մենք ուզում էինք ստանալ մի փոքրիկ ծառայություն, որը քաղաքացիություն չունեցող կոնտեյներ էր արտաքին պահեստով: Դե, քանի որ մենք օգտագործում ենք S3, առաջնահերթությունը տրվեց դրան։ Եվ, հնարավորության դեպքում, վավերացումով gitlab-ում (անհրաժեշտության դեպքում կարող եք ինքներդ ավելացնել):

Արագ որոնումը տվեց մի քանի արդյունք՝ s3pypi, pypicloud և շաղգամի համար html ֆայլերի «ձեռքով» ստեղծման տարբերակ: Վերջին տարբերակն ինքնին անհետացավ.

s3pypi. Սա S3 հոստինգ օգտագործելու համար է: Մենք վերբեռնում ենք ֆայլերը, ստեղծում html-ը և վերբեռնում այն ​​նույն դույլում։ Հարմար է տնային օգտագործման համար։

pypicloud. Թվում էր, թե հետաքրքիր նախագիծ էր, բայց փաստաթղթերը կարդալուց հետո ես հիասթափվեցի: Չնայած լավ փաստաթղթերին և ձեր կարիքներին համապատասխան ընդլայնելու հնարավորությանը, իրականում պարզվեց, որ այն ավելորդ է և դժվար է կարգավորել: Կոդի ուղղումը ձեր առաջադրանքներին համապատասխանելու համար, ըստ այն ժամանակվա գնահատականների, կտևի 3-5 օր: Ծառայությանը անհրաժեշտ է նաև տվյալների բազա։ Մենք թողեցինք այն, եթե այլ բան չգտանք:

Ավելի խորը որոնումը տվեց Nginx-ի մոդուլ՝ ngx_aws_auth: Նրա փորձարկման արդյունքը բրաուզերում ցուցադրվել է XML-ը, որը ցույց է տվել S3 դույլի պարունակությունը։ Վերջին անգամ խուզարկության ժամանակ կատարվել է մեկ տարի առաջ։ Պահեստը լքված տեսք ուներ:

Աղբյուր գնալով և կարդալով PEP-503 Ես հասկացա, որ XML-ը կարող է անմիջապես վերածվել HTML-ի և տրվել pip-ին: Nginx-ի և S3-ի մասին մի փոքր ավելի շատ գուգլելուց հետո ես հանդիպեցի S3-ում նույնականացման օրինակի, որը գրված էր JS-ով Nginx-ի համար: Այդպես ես հանդիպեցի NJS-ին:

Այս օրինակը հիմք ընդունելով, մեկ ժամ անց ես իմ բրաուզերում տեսա նույն XML-ը, ինչ ngx_aws_auth մոդուլն օգտագործելիս, բայց ամեն ինչ արդեն գրված էր JS-ով։

Ինձ շատ դուր եկավ nginx լուծումը: Նախ, լավ փաստաթղթեր և բազմաթիվ օրինակներ, երկրորդը, մենք ստանում ենք Nginx-ի բոլոր առավելությունները ֆայլերի հետ աշխատելու համար (արկղից դուրս), երրորդ, յուրաքանչյուր ոք, ով գիտի, թե ինչպես գրել կոնֆիգուրացիաներ Nginx-ի համար, կկարողանա պարզել, թե ինչն է: Մինիմալիզմը նույնպես ինձ համար պլյուս է, համեմատած Python-ի կամ Go-ի հետ (եթե զրոյից գրված է), էլ չեմ խոսում նեքսուսի մասին։

TL;DR 2 օր անց PyPi-ի փորձնական տարբերակը արդեն օգտագործվել է CI-ում:

Ինչպես է դա աշխատում.

Մոդուլը բեռնված է Nginx-ում ngx_http_js_module, ներառված է պաշտոնական դոկերի պատկերում: Մենք ներմուծում ենք մեր սցենարը՝ օգտագործելով հրահանգը js_importդեպի Nginx կոնֆիգուրացիա: Ֆունկցիան կոչվում է հրահանգով js_content. Հրահանգը օգտագործվում է փոփոխականներ սահմանելու համար js_set, որը որպես փաստարկ ընդունում է միայն սկրիպտում նկարագրված ֆունկցիան։ Բայց մենք կարող ենք ենթահարցումներ կատարել NJS-ում միայն Nginx-ի միջոցով, ոչ թե XMLHttpRequest-ի: Դա անելու համար Nginx կոնֆիգուրացիայի մեջ պետք է ավելացվի համապատասխան տեղադրությունը: Եվ սցենարը պետք է նկարագրի ենթահարկ այս վայրին: Nginx կոնֆիգուրից ֆունկցիա մուտք գործելու համար ֆունկցիայի անունը պետք է արտահանվի հենց սկրիպտում export default.

nginx.conf

load_module modules/ngx_http_js_module.so;
http {
  js_import   imported_name  from script.js;

server {
  listen 8080;
  ...
  location = /sub-query {
    internal;

    proxy_pass http://upstream;
  }

  location / {
    js_content imported_name.request;
  }
}

script.js

function request(r) {
  function call_back(resp) {
    // handler's code
    r.return(resp.status, resp.responseBody);
  }

  r.subrequest('/sub-query', { method: r.method }, call_back);
}

export default {request}

Երբ պահանջվում է դիտարկիչում http://localhost:8080/ մտնում ենք location /որում հրահանգը js_content կանչում է ֆունկցիա request նկարագրված է մեր սցենարում script.js. Իր հերթին ֆունկցիայի մեջ request ենթահարցում է արվում location = /sub-query, փաստարկից ստացված մեթոդով (ներկայիս օրինակում՝ GET): (r), անուղղակիորեն անցել է այս ֆունկցիան կանչելիս: Ենթահարցման պատասխանը կմշակվի գործառույթում call_back.

Փորձում ենք S3-ը

Անձնական S3 պահեստին հարցում կատարելու համար մեզ անհրաժեշտ է.

ACCESS_KEY

SECRET_KEY

S3_BUCKET

Օգտագործված http մեթոդից, ընթացիկ ամսաթվից/ժամից, S3_NAME-ից և URI-ից, ստեղծվում է տողի որոշակի տեսակ, որը ստորագրվում է (HMAC_SHA1)՝ օգտագործելով SECRET_KEY: Հաջորդը նման տող է AWS $ACCESS_KEY:$HASH, կարող է օգտագործվել թույլտվության վերնագրում: Նույն ամսաթիվը/ժամը, որն օգտագործվել է նախորդ քայլում տողը ստեղծելու համար, պետք է ավելացվի վերնագրին X-amz-date. Կոդում այն ​​ունի հետևյալ տեսքը.

nginx.conf

load_module modules/ngx_http_js_module.so;
http {
  js_import   s3      from     s3.js;

  js_set      $s3_datetime     s3.date_now;
  js_set      $s3_auth         s3.s3_sign;

server {
  listen 8080;
  ...
  location ~* /s3-query/(?<s3_path>.*) {
    internal;

    proxy_set_header    X-amz-date     $s3_datetime;
    proxy_set_header    Authorization  $s3_auth;

    proxy_pass          $s3_endpoint/$s3_path;
  }

  location ~ "^/(?<prefix>[w-]*)[/]?(?<postfix>[w-.]*)$" {
    js_content s3.request;
  }
}

s3.js(AWS Sign v2 թույլտվության օրինակ, փոխվել է հնացած կարգավիճակի)

var crypt = require('crypto');

var s3_bucket = process.env.S3_BUCKET;
var s3_access_key = process.env.S3_ACCESS_KEY;
var s3_secret_key = process.env.S3_SECRET_KEY;
var _datetime = new Date().toISOString().replace(/[:-]|.d{3}/g, '');

function date_now() {
  return _datetime
}

function s3_sign(r) {
  var s2s = r.method + 'nnnn';

  s2s += `x-amz-date:${date_now()}n`;
  s2s += '/' + s3_bucket;
  s2s += r.uri.endsWith('/') ? '/' : r.variables.s3_path;

  return `AWS ${s3_access_key}:${crypt.createHmac('sha1', s3_secret_key).update(s2s).digest('base64')}`;
}

function request(r) {
  var v = r.variables;

  function call_back(resp) {
    r.return(resp.status, resp.responseBody);
  }

  var _subrequest_uri = r.uri;
  if (r.uri === '/') {
    // root
    _subrequest_uri = '/?delimiter=/';

  } else if (v.prefix !== '' && v.postfix === '') {
    // directory
    var slash = v.prefix.endsWith('/') ? '' : '/';
    _subrequest_uri = '/?prefix=' + v.prefix + slash;
  }

  r.subrequest(`/s3-query${_subrequest_uri}`, { method: r.method }, call_back);
}

export default {request, s3_sign, date_now}

Մի փոքր պարզաբանում մասին _subrequest_uriՍա փոփոխական է, որը, կախված սկզբնական uri-ից, հարցում է կազմում S3-ին: Եթե ​​Ձեզ անհրաժեշտ է ստանալ «արմատի» բովանդակությունը, ապա դուք պետք է ստեղծեք uri հարցում՝ նշելով սահմանազատողը delimiter, որը կվերադարձնի բոլոր CommonPrefixes xml տարրերի ցանկը՝ համապատասխան դիրեկտորիաներին (PyPI-ի դեպքում՝ բոլոր փաթեթների ցանկը)։ Եթե ​​Ձեզ անհրաժեշտ է ստանալ բովանդակության ցանկ կոնկրետ գրացուցակում (փաթեթի բոլոր տարբերակների ցանկը), ապա uri հարցումը պետք է պարունակի նախածանցի դաշտ, որտեղ գրացուցակի (փաթեթի) անվանումը անպայմանորեն ավարտվում է կտրվածքով /: Հակառակ դեպքում, օրինակ, գրացուցակի բովանդակությունը պահանջելիս հնարավոր են բախումներ: Կան դիրեկտորիաներ aiohttp-խնդրանք և aiohttp-հարցումներ և եթե հարցումը սահմանում է /?prefix=aiohttp-request, ապա պատասխանը կպարունակի երկու դիրեկտորիաների բովանդակությունը: Եթե ​​վերջում կա շեղ, /?prefix=aiohttp-request/, ապա պատասխանը կպարունակի միայն անհրաժեշտ գրացուցակը: Իսկ եթե մենք ֆայլ ենք պահանջում, ապա ստացված uri-ն չպետք է տարբերվի սկզբնականից։

Պահպանեք և վերագործարկեք Nginx-ը: Բրաուզերում մենք մուտքագրում ենք մեր Nginx-ի հասցեն, հարցման արդյունքը կլինի XML, օրինակ.

Ցուցակ դիրեկտորիաների

<?xml version="1.0" encoding="UTF-8"?>
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <Name>myback-space</Name>
  <Prefix></Prefix>
  <Marker></Marker>
  <MaxKeys>10000</MaxKeys>
  <Delimiter>/</Delimiter>
  <IsTruncated>false</IsTruncated>
  <CommonPrefixes>
    <Prefix>new/</Prefix>
  </CommonPrefixes>
  <CommonPrefixes>
    <Prefix>old/</Prefix>
  </CommonPrefixes>
</ListBucketResult>

Գրացուցակների ցանկից ձեզ անհրաժեշտ կլինեն միայն տարրերը CommonPrefixes.

Բրաուզերում ավելացնելով մեզ անհրաժեշտ գրացուցակը մեր հասցեին, մենք կստանանք նաև դրա բովանդակությունը XML ձևով.

Գրացուցակում գտնվող ֆայլերի ցանկը

<?xml version="1.0" encoding="UTF-8"?>
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <Name> myback-space</Name>
  <Prefix>old/</Prefix>
  <Marker></Marker>
  <MaxKeys>10000</MaxKeys>
  <Delimiter></Delimiter>
  <IsTruncated>false</IsTruncated>
  <Contents>
    <Key>old/giphy.mp4</Key>
    <LastModified>2020-08-21T20:27:46.000Z</LastModified>
    <ETag>&#34;00000000000000000000000000000000-1&#34;</ETag>
    <Size>1350084</Size>
    <Owner>
      <ID>02d6176db174dc93cb1b899f7c6078f08654445fe8cf1b6ce98d8855f66bdbf4</ID>
      <DisplayName></DisplayName>
    </Owner>
    <StorageClass>STANDARD</StorageClass>
  </Contents>
  <Contents>
    <Key>old/hsd-k8s.jpg</Key>
    <LastModified>2020-08-31T16:40:01.000Z</LastModified>
    <ETag>&#34;b2d76df4aeb4493c5456366748218093&#34;</ETag>
    <Size>93183</Size>
    <Owner>
      <ID>02d6176db174dc93cb1b899f7c6078f08654445fe8cf1b6ce98d8855f66bdbf4</ID>
      <DisplayName></DisplayName>
    </Owner>
    <StorageClass>STANDARD</StorageClass>
  </Contents>
</ListBucketResult>

Ֆայլերի ցանկից մենք կվերցնենք միայն տարրեր Key.

Մնում է միայն վերլուծել ստացված XML-ը և ուղարկել այն որպես HTML՝ նախ Content-Type վերնագիրը փոխարինելով տեքստով/html:

function request(r) {
  var v = r.variables;

  function call_back(resp) {
    var body = resp.responseBody;

    if (r.method !== 'PUT' && resp.status < 400 && v.postfix === '') {
      r.headersOut['Content-Type'] = "text/html; charset=utf-8";
      body = toHTML(body);
    }

    r.return(resp.status, body);
  }
  
  var _subrequest_uri = r.uri;
  ...
}

function toHTML(xml_str) {
  var keysMap = {
    'CommonPrefixes': 'Prefix',
    'Contents': 'Key',
  };

  var pattern = `<k>(?<v>.*?)</k>`;
  var out = [];

  for(var group_key in keysMap) {
    var reS;
    var reGroup = new RegExp(pattern.replace(/k/g, group_key), 'g');

    while(reS = reGroup.exec(xml_str)) {
      var data = new RegExp(pattern.replace(/k/g, keysMap[group_key]), 'g');
      var reValue = data.exec(reS);
      var a_text = '';

      if (group_key === 'CommonPrefixes') {
        a_text = reValue.groups.v.replace(///g, '');
      } else {
        a_text = reValue.groups.v.split('/').slice(-1);
      }

      out.push(`<a href="/hy/${reValue.groups.v}">${a_text}</a>`);
    }
  }

  return '<html><body>n' + out.join('</br>n') + 'n</html></body>'
}

Փորձում ենք PyPI-ն

Մենք ստուգում ենք, որ ոչինչ չի կոտրվում այն ​​փաթեթների վրա, որոնք հայտնի են, որ աշխատում են:

# Создаем для тестов новое окружение
python3 -m venv venv
. ./venv/bin/activate

# Скачиваем рабочие пакеты.
pip download aiohttp

# Загружаем в приватную репу
for wheel in *.whl; do curl -T $wheel http://localhost:8080/${wheel%%-*}/$wheel; done

rm -f *.whl

# Устанавливаем из приватной репы
pip install aiohttp -i http://localhost:8080

Մենք կրկնում ենք մեր շրթունքներով.

# Создаем для тестов новое окружение
python3 -m venv venv
. ./venv/bin/activate

pip install setuptools wheel
python setup.py bdist_wheel
for wheel in dist/*.whl; do curl -T $wheel http://localhost:8080/${wheel%%-*}/$wheel; done

pip install our_pkg --extra-index-url http://localhost:8080

CI-ում փաթեթ ստեղծելը և բեռնելը հետևյալն է.

pip install setuptools wheel
python setup.py bdist_wheel

curl -sSfT dist/*.whl -u "gitlab-ci-token:${CI_JOB_TOKEN}" "https://pypi.our-domain.com/${CI_PROJECT_NAME}"

Նույնականացմանը

Gitlab-ում հնարավոր է օգտագործել JWT արտաքին ծառայությունների իսկությունը/թույլատրումը: Օգտագործելով auth_request դիրեկտիվը Nginx-ում, մենք նույնականացման տվյալները կվերահղենք դեպի ենթահարցում, որը պարունակում է ֆունկցիայի կանչ սկրիպտում: Սկրիպտը ևս մեկ ենթահարկ կկատարի Gitlab-ի url-ին, և եթե վավերացման տվյալները ճիշտ են նշված, ապա Gitlab-ը կվերադարձնի 200 կոդը, և փաթեթի վերբեռնումը/ներբեռնումը կթույլատրվի: Ինչու չօգտագործել մեկ ենթահարկ և անմիջապես ուղարկել տվյալները Gitlab-ին: Որովհետև մենք ստիպված կլինենք խմբագրել Nginx կազմաձևման ֆայլը ամեն անգամ, երբ թույլտվության մեջ որևէ փոփոխություն ենք կատարում, և սա բավականին հոգնեցուցիչ խնդիր է: Բացի այդ, եթե Kubernetes-ը օգտագործում է միայն կարդալու արմատային ֆայլային համակարգի քաղաքականություն, ապա դա էլ ավելի է բարդացնում nginx.conf-ը configmap-ի միջոցով: Եվ բացարձակապես անհնար է դառնում կարգավորել Nginx-ը configmap-ի միջոցով՝ միաժամանակ օգտագործելով ծավալների (pvc) և միայն կարդալու համար արմատային ֆայլային համակարգի միացումն արգելող քաղաքականություն (դա նույնպես տեղի է ունենում):

Օգտագործելով NJS միջանկյալը, մենք հնարավորություն ենք ստանում փոխել նշված պարամետրերը nginx կոնֆիգուրայում՝ օգտագործելով շրջակա միջավայրի փոփոխականները և կատարել որոշ ստուգումներ սկրիպտում (օրինակ՝ սխալ նշված URL):

nginx.conf

location = /auth-provider {
  internal;

  proxy_pass $auth_url;
}

location = /auth {
  internal;

  proxy_set_header Content-Length "";
  proxy_pass_request_body off;
  js_content auth.auth;
}

location ~ "^/(?<prefix>[w-]*)[/]?(?<postfix>[w-.]*)$" {
  auth_request /auth;

  js_content s3.request;
}

s3.js

var env = process.env;
var env_bool = new RegExp(/[Tt]rue|[Yy]es|[Oo]n|[TtYy]|1/);
var auth_disabled  = env_bool.test(env.DISABLE_AUTH);
var gitlab_url = env.AUTH_URL;

function url() {
  return `${gitlab_url}/jwt/auth?service=container_registry`
}

function auth(r) {
  if (auth_disabled) {
    r.return(202, '{"auth": "disabled"}');
    return null
  }

  r.subrequest('/auth-provider',
                {method: 'GET', body: ''},
                function(res) {
                  r.return(res.status, "");
                });
}

export default {auth, url}

Ամենայն հավանականությամբ, հարցն առաջանում է. - Ինչու՞ չօգտագործել պատրաստի մոդուլներ: Այնտեղ արդեն ամեն ինչ արված է։ Օրինակ, var AWS = պահանջել ('aws-sdk') և կարիք չկա գրել «հեծանիվ» S3 իսկորոշմամբ:

Անցնենք մինուսներին

Ինձ համար արտաքին JS մոդուլներ ներմուծելու անկարողությունը դարձավ տհաճ, բայց սպասված հատկանիշ։ Վերևի օրինակում նկարագրված պահանջը («կրիպտո») է ներկառուցված մոդուլներ և պահանջում են միայն գործեր նրանց համար: Նաև սկրիպտներից ծածկագիրը նորից օգտագործելու միջոց չկա, և դուք պետք է այն պատճենեք և տեղադրեք տարբեր ֆայլերում: Հուսով եմ, որ մի օր այս ֆունկցիոնալությունը կիրականացվի:

Սեղմումը պետք է նաև անջատված լինի Nginx-ի ընթացիկ նախագծի համար gzip off;

Քանի որ NJS-ում gzip մոդուլ չկա, և այն հնարավոր չէ միացնել, հետևաբար, սեղմված տվյալների հետ աշխատելու միջոց չկա: Ճիշտ է, սա իրականում մինուս չէ այս գործի համար։ Տեքստը շատ չէ, իսկ փոխանցված ֆայլերն արդեն սեղմված են, և լրացուցիչ սեղմումը նրանց առանձնապես չի օգնի։ Բացի այդ, սա այնքան բեռնված կամ կարևոր ծառայություն չէ, որ դուք ստիպված լինեք անհանգստանալ բովանդակությունը մի քանի միլիվայրկյան ավելի արագ մատուցելու համար:

Սցենարը վրիպազերծելը երկար ժամանակ է պահանջում և հնարավոր է միայն error.log-ի «տպումների» միջոցով: Կախված գրանցման սահմանված մակարդակի տեղեկատվությունից, զգուշացումից կամ սխալից, հնարավոր է օգտագործել համապատասխանաբար r.log, r.warn, r.error 3 մեթոդները: Փորձում եմ կարգաբերել որոշ սցենարներ Chrome-ում (v8) կամ njs կոնսոլի գործիքում, բայց այնտեղ ամեն ինչ չէ, որ կարելի է ստուգել: Կոդը վրիպազերծելիս, որը կոչվում է ֆունկցիոնալ թեստավորում, պատմությունն այսպիսի տեսք ունի.

docker-compose restart nginx
curl localhost:8080/
docker-compose logs --tail 10 nginx

և կարող են լինել հարյուրավոր նման հաջորդականություններ:

Նրանց համար ենթհարցումներ և փոփոխականներ օգտագործելով կոդ գրելը վերածվում է խճճված խճճանքի։ Երբեմն դուք սկսում եք շտապել տարբեր IDE պատուհանների շուրջ՝ փորձելով պարզել ձեր կոդի գործողությունների հաջորդականությունը: Դա դժվար չէ, բայց երբեմն դա շատ նյարդայնացնում է:

ES6-ի համար լիարժեք աջակցություն չկա:

Կարող են լինել նաև այլ թերություններ, բայց ես այլ բանի չեմ հանդիպել։ Կիսվեք տեղեկություններով, եթե ունեք NJS-ի օգտագործման բացասական փորձ:

Ամփոփում

NJS-ը թեթև բաց կոդով թարգմանիչ է, որը թույլ է տալիս իրականացնել տարբեր JavaScript սկրիպտներ Nginx-ում: Նրա մշակման ընթացքում մեծ ուշադրություն է դարձվել կատարմանը։ Իհարկե, դեռ շատ բան կա, բայց նախագիծը մշակվում է փոքր թիմի կողմից, և նրանք ակտիվորեն ավելացնում են նոր հնարավորություններ և շտկում սխալները: Հուսով եմ, որ մի օր NJS-ը ձեզ թույլ կտա միացնել արտաքին մոդուլներ, ինչը Nginx-ի գործունակությունը կդարձնի գրեթե անսահմանափակ։ Բայց կա NGINX Plus և, ամենայն հավանականությամբ, գործառույթներ չեն լինի:

Հոդվածի ամբողջական կոդով պահեստ

njs-pypi AWS Sign v4 աջակցությամբ

ngx_http_js_module մոդուլի հրահանգների նկարագրությունը

NJS-ի պաշտոնական պահոց и փաստաթղթերը

Դմիտրի Վոլինցևից NJS-ի օգտագործման օրինակներ

njs - տեղական JavaScript սկրիպտավորում nginx-ում / Դմիտրի Վոլնևի ելույթը Saint HighLoad++ 2019-ում

NJS արտադրության մեջ / Վասիլի Սոշնիկովի ելույթը HighLoad++ 2019-ում

Հանգստության հարցումների ստորագրում և վավերացում AWS-ում

Source: www.habr.com