Ejiri m ikike na S3 mee ebe nchekwa PyPI nke m. Na Nginx

N'ime edemede a ọ ga-amasị m ịkọrọ ahụmịhe m na NJS, onye ntụgharị okwu Javascript maka Nginx nke Nginx Inc mepụtara, na-akọwa ike ya bụ isi site na iji ezigbo ọmụmaatụ. NJS bụ mpaghara Javascript na-enye gị ohere ịgbatị ọrụ Nginx. Maka ajụjụ a gini mere onye ntughari nke gi??? Dmitry Volyntsev zara n'ụzọ zuru ezu. Na nkenke: NJS bụ nginx-way, Javascript na-aga n'ihu karịa, "nwa afọ" na enweghị GC, n'adịghị ka Lua.

Ogologo oge gara aga…

N'ọrụ ikpeazụ m, eketara m gitlab na ọtụtụ ọkpọkọ motley CI/CD nwere docker-compose, dind na ihe ụtọ ndị ọzọ, bụ nke ebufere na ụgbọ okporo ígwè kaniko. Ihe oyiyi ndị ejiribu na CI bufere n'ụdị mbụ ha. Ha rụrụ ọrụ nke ọma ruo ụbọchị gitlab IP anyị gbanwere na CI ghọrọ ugu. Nsogbu bụ na otu n'ime ihe onyonyo docker sonyere na CI nwere git, nke dọtara Python modules site na ssh. Maka ssh ị chọrọ igodo nzuzo yana... ọ dị na foto a yana ndị ọrụ ama ama. Na CI ọ bụla dara site na mperi nkwenye isi n'ihi ndakọrịta dị n'etiti ezigbo IP na nke akọwapụtara na ama_hosts. Ewekọtara onyonyo ọhụrụ ngwa ngwa site na Dockfiles dị adị wee tinye nhọrọ ahụ StrictHostKeyChecking no. Mana uto ọjọọ ahụ dịgidere ma enwere ọchịchọ ịkwaga libs gaa na ebe nchekwa PyPI nkeonwe. Ego agbakwunyere, ka ị gbanwee gaa na PyPI nkeonwe, bụ pipeline dị mfe yana nkọwa nkịtị nke chọrọ.txt

Emeela nhọrọ, ndị nwe obodo!

Anyị na-agba ọsọ ihe niile dị n'ígwé ojii na Kubernetes, na njedebe anyị chọrọ inweta obere ọrụ nke bụ akpa na-enweghị obodo na nchekwa mpụga. Ọfọn, ebe anyị na-eji S3, e nyere ya ụzọ. Ma, ọ bụrụ na ọ ga-ekwe omume, na nyocha na gitlab (ị nwere ike itinye ya n'onwe gị ma ọ bụrụ na ọ dị mkpa).

Nchọ ngwa ngwa wetara ọtụtụ nsonaazụ: s3pypi, pypicloud na nhọrọ nwere “aka” imepụta faịlụ HTML maka turnips. Nhọrọ ikpeazụ kwụsịrị n'onwe ya.

s3pypi: Nke a bụ cli maka iji S3 Bochum. Anyị na-ebugote faịlụ ndị ahụ, mepụta html wee bulite ya n'otu bọket ahụ. Kwesịrị ekwesị maka iji ụlọ.

pypicloud: Ọ dị ka ọrụ na-adọrọ mmasị, ma mgbe m gụchara akwụkwọ ahụ, enwere m nkụda mmụọ. N'agbanyeghị akwụkwọ dị mma na ikike ịgbasawanye iji gboo mkpa gị, n'eziokwu ọ tụgharịrị bụrụ nke na-adịghị arụ ọrụ ma sie ike ịhazi. Imezi koodu ahụ ka ọ dabara maka ọrụ gị, dịka atụmatụ n'oge ahụ siri dị, gaara ewe ụbọchị 3-5. Ọrụ ahụ chọkwara nchekwa data. Anyị hapụrụ ya ma ọ bụrụ na anyị ahụghị ihe ọ bụla ọzọ.

Ọchịchọ dị omimi karị wetara modul maka Nginx, ngx_aws_auth. Nsonaazụ ule ya bụ XML gosipụtara na ihe nchọgharị, nke gosipụtara ọdịnaya nke bọket S3. Nkwenye ikpeazụ n'oge ọchụchọ ahụ bụ otu afọ gara aga. Ebe nchekwa ahụ dị ka agbahapụla.

Site na ịga na isi mmalite na ịgụ PEP-503 Achọpụtara m na XML nwere ike ịtụgharị na HTML na ofufe wee nye ya pip. Mgbe m mechara ntakịrị ntakịrị gbasara Nginx na S3, ahụrụ m ihe atụ nke nyocha na S3 nke edere na JS maka Nginx. Otu a ka m siri zute NJS.

N'iji ihe atụ a dịka ndabere, otu awa ka e mesịrị, ahụrụ m na ihe nchọgharị m otu XML dị ka mgbe m na-eji modul ngx_aws_auth, mana edeworị ihe niile na JS.

Ihe ngwọta nginx masịrị m nke ukwuu. Nke mbụ, akwụkwọ dị mma na ọtụtụ ihe atụ, nke abụọ, anyị na-enweta ihe ọma niile nke Nginx maka ịrụ ọrụ na faịlụ (site na igbe), nke atọ, onye ọ bụla maara otú e si ede configs maka Nginx ga-enwe ike ịchọpụta ihe bụ ihe. Minimalism bụkwa ihe mgbakwunye maka m, ma e jiri ya tụnyere Python ma ọ bụ Go (ọ bụrụ na edere ya site na ọkọ), ọ bụghị ikwu okwu nexus.

TL;DR Mgbe ụbọchị abụọ gachara, ejirila ụdị nnwale nke PyPi na CI.

Olee otú ọ na-arụ ọrụ?

A na-etinye modul ahụ na Nginx ngx_http_js_module, gụnyere na foto docker gọọmentị. Anyị na-ebubata edemede anyị site na iji ntuziaka js_importna nhazi Nginx. A na-akpọ ọrụ a site na ntuziaka js_content. A na-eji ntuziaka a iji tọọ mgbanwe js_set, nke na-ewere dị ka arụmụka naanị ọrụ akọwara na edemede ahụ. Mana anyị nwere ike mebe subqueries na NJS naanị site na iji Nginx, ọ bụghị arịrịọ XMLHttp ọ bụla. Iji mee nke a, a ghaghị itinye ebe kwekọrọ na nhazi Nginx. Na edemede ahụ ga-akọwarịrị arịrịọ maka ebe a. Iji nwee ike ịnweta ọrụ site na nhazi Nginx, a ga-ebupụ aha ọrụ ahụ na edemede n'onwe ya 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}

Mgbe achọrọ na ihe nchọgharị http://localhost:8080/ anyị banye location /nke ntuziaka js_content na-akpọ ọrụ request kọwara na edemede anyị script.js. N'aka nke ya, na ọrụ request a na-eme subquery location = /sub-query, na usoro (na ihe atụ ugbu a GET) nwetara site na arụmụka ahụ (r), gafere n'ezoghị ọnụ mgbe akpọrọ ọrụ a. A ga-ahazi nzaghachi arịrịọ n'ime ọrụ ahụ call_back.

Na-agbalị S3

Iji rịọ maka nchekwa S3 nkeonwe, anyị chọrọ:

ACCESS_KEY

SECRET_KEY

S3_BUCKET

Site na usoro http a na-eji, ụbọchị/oge dị ugbu a, S3_NAME na URI, a na-emepụta ụfọdụ ụdị eriri, nke ejiri SECRET_KEY bịanyere aka na ya (HMAC_SHA1). Ọzọ bụ ahịrị dị ka AWS $ACCESS_KEY:$HASH, enwere ike iji na nkụnye eji isi mee ikike. A ga-atụkwasịrịrị otu ụbọchị/oge ejiri mepụta eriri na nzọụkwụ gara aga na nkụnye eji isi mee X-amz-date. Na koodu ọ dị ka nke a:

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 Banye aka v2 ọmụmaatụ ikike, gbanwere ka ọ bụrụ ọkwa ewepụrụ)

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}

A obere nkọwa banyere _subrequest_uri: nke a bụ mgbanwe na, dabere na mbụ uri, na-etolite arịrịọ na S3. Ọ bụrụ na ịchọrọ ịnweta ọdịnaya nke "mgbọrọgwụ", mgbe ahụ ịkwesịrị ịmepụta arịrịọ uri na-egosi njedebe. delimiter, nke ga-eweghachi ndepụta nke ihe niile CommonPrefixes xml, kwekọrọ na akwụkwọ ndekọ aha (n'ihe gbasara PyPI, ndepụta ngwugwu niile). Ọ bụrụ na ịchọrọ ịnweta ndepụta nke ọdịnaya na akwụkwọ ndekọ aha (ndepụta nke ụdị ngwugwu niile), mgbe ahụ arịrịọ uri ga-enwerịrị ubi prefix na aha ndekọ (ngwugwu) na-ejedebe na slash /. Ma ọ bụghị ya, esemokwu ga-ekwe omume mgbe ị na-arịọ ọdịnaya nke ndekọ aha, dịka ọmụmaatụ. Enwere akwụkwọ ndekọ aha aiohttp-request na aiohttp-requests ma ọ bụrụ na arịrịọ ahụ akọwapụtara /?prefix=aiohttp-request, mgbe ahụ nzaghachi ga-enwe ọdịnaya nke akwụkwọ ndekọ aha abụọ ahụ. Ọ bụrụ na enwere slash na njedebe, /?prefix=aiohttp-request/, mgbe ahụ nzaghachi ga-enwe naanị ndekọ achọrọ. Ma ọ bụrụ na anyị rịọrọ faịlụ, uri ga-esi na ya pụta ekwesịghị ịdị iche na nke mbụ.

Chekwa ma malitegharịa Nginx. Na ihe nchọgharị anyị na-abanye adreesị Nginx anyị, nsonaazụ nke arịrịọ ahụ ga-abụ XML, dịka ọmụmaatụ:

Ndepụta ndekọ aha

<?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>

Site na ndepụta ndekọ aha, ị ga-achọ naanị ihe ndị ahụ CommonPrefixes.

Site n'ịgbakwunye ndekọ anyị chọrọ na adreesị anyị na ihe nchọgharị ahụ, anyị ga-enwetakwa ọdịnaya ya n'ụdị XML:

Ndepụta faịlụ dị na ndekọ

<?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>

Site na ndepụta faịlụ anyị ga-ewere naanị ihe Key.

Naanị ihe fọdụrụ bụ ịtụgharị XML nke ga-esi na ya pụta wee zipụ ya dị ka HTML, na-ebu ụzọ dochie isi isi ụdị ọdịnaya na ederede/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="/ig/${reValue.groups.v}">${a_text}</a>`);
    }
  }

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

Na-anwale PyPI

Anyị na-enyocha na ọ dịghị ihe na-agbaji n'ebe ọ bụla na ngwugwu ndị a maara na-arụ ọrụ.

# Создаем для тестов новое окружение
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

Anyị na-eji libs anyị na-ekwughachi.

# Создаем для тестов новое окружение
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

Na CI, imepụta na ịkwanye ngwugwu dị ka nke a:

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}"

Nyocha

Na Gitlab ọ ga-ekwe omume iji JWT maka nyocha/ikike nke ọrụ mpụga. Iji ntuziaka auth_request na Nginx, anyị ga-atụgharị data nyocha ahụ gaa na arịrịọ nke nwere oku ọrụ na edemede. Edemede a ga-eme arịrịọ ọzọ na Gitlab url ma ọ bụrụ na akọwapụtara data nyocha ahụ nke ọma, mgbe ahụ Gitlab ga-eweghachite koodu 200 na nbudata / nbudata ngwugwu ahụ ga-ahapụ. Kedu ihe kpatara na ị gaghị eji otu subquery wee zipu data ozugbo na Gitlab? N'ihi na mgbe ahụ, anyị ga-edezi faịlụ nhazi Nginx oge ọ bụla anyị mere mgbanwe ọ bụla na ikike, nke a bụ ọrụ na-agwụ ike. Ọzọkwa, ọ bụrụ na Kubernetes na-eji usoro faịlụ mgbọrọgwụ na-agụ naanị, mgbe ahụ nke a na-agbakwụnye ọbụna mgbagwoju anya mgbe ọ na-anọchi nginx.conf site na nhazi nhazi. Ọ na-aghọkwa ihe na-agaghị ekwe omume ịhazi Nginx site na nhazi ma n'otu oge na-eji atumatu na-egbochi njikọ nke mpịakọta (pvc) na usoro faịlụ mgbọrọgwụ na-agụ naanị (nke a na-emekwa).

N'iji NJS etiti, anyị na-enweta ohere iji gbanwee paramita akọwapụtara na nhazi nginx site na iji mgbanwe gburugburu ebe obibi wee mee ụfọdụ nlele na edemede (dịka ọmụmaatụ, URL akọwapụtara ezighi ezi).

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}

O yikarịrị ka ajụjụ a bụ ịsa: -Gịnị kpatara na ị gaghị eji modul emebere? Emeelarị ihe niile ebe ahụ! Dịka ọmụmaatụ, var AWS = chọrọ ('aws-sdk') na ọ dịghị mkpa iji nyocha S3 dee "igwe igwe"!

Ka anyị gaa n'ihu na ọghọm

Maka m, enweghị ike ibubata modul JS mpụga ghọrọ ihe na-adịghị mma, mana a na-atụ anya ya. Akọwara na ihe atụ n'elu chọrọ ('crypto') bụ modul wuo ma na-achọ naanị ọrụ maka ha. Enweghịkwa ụzọ isi jiri koodu si na scripts mee ihe ma ị ga-edegharị na mado ya na faịlụ dị iche iche. Enwere m olileanya na otu ụbọchị a ga-arụ ọrụ a.

A ga-enwekwa nkwarụ mkpakọ maka ọrụ dị ugbu a na Nginx gzip off;

N'ihi na ọ dịghị gzip modul na NJS na ọ gaghị ekwe omume ijikọ ya; ya mere, ọ dịghị ụzọ na-arụ ọrụ na abịakọrọ data. N'ezie, nke a abụghị n'ezie mwepu maka ikpe a. Enweghị ọtụtụ ederede, na faịlụ ndị ebugharị abanyelarị na mkpakọ ọzọ agaghị enyere ha aka nke ukwuu. Ọzọkwa, nke a abụghị ọrụ a kwajuru ma ọ bụ dị oke egwu nke na ị ga-enye gị nsogbu n'iwepụta ọdịnaya ntakịrị nkeji ole na ole ngwa ngwa.

Imebi edemede na-ewe ogologo oge ma ọ ga-ekwe omume naanị site na "mbipụta" na njehie.log. Dabere na ozi ọkwa ndekọ nke edobere, dọọ aka ná ntị ma ọ bụ njehie, ọ ga-ekwe omume iji ụzọ atọ r.log, r.warn, r.error n'otu n'otu. M na-agbalị imezigharị ụfọdụ scripts na Chrome (v3) ma ọ bụ ngwá ọrụ njs console, mana ọ bụghị ihe niile ka enwere ike ịlele ebe ahụ. Mgbe koodu debugging, aka functional test, akụkọ ihe mere eme na-adị ka nke a:

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

na enwere ike inwe ọtụtụ narị usoro dị otú ahụ.

Ide koodu site na iji subqueries na mgbanwe maka ha na-atụgharị ka ọ bụrụ tangle gbagọrọ agbagọ. Mgbe ụfọdụ, ị na-amalite ịgba ọsọ gburugburu windo IDE dị iche iche na-agbalị ịchọpụta usoro omume nke koodu gị. Ọ naghị esi ike, ma mgbe ụfọdụ ọ na-akpasu iwe.

Enweghị nkwado zuru oke maka ES6.

Enwere ike inwe mmejọ ndị ọzọ, mana ahụbeghị m ihe ọ bụla ọzọ. Kekọrịta ozi ma ọ bụrụ na ị nwere ahụmịhe na-adịghị mma site na iji NJS.

nkwubi

NJS bụ ntụgharị okwu mepere emepe nke na-enye gị ohere itinye edemede Javascript dị iche iche na Nginx. N'oge mmepe ya, a na-elebara anya nke ọma na arụmọrụ. N'ezie, a ka nwere ọtụtụ ihe na-efu, ma a na-emepụta ọrụ ahụ site na otu obere ìgwè ma na-arụsi ọrụ ike na-agbakwụnye atụmatụ ọhụrụ na idozi ahụhụ. Enwere m olileanya na otu ụbọchị NJS ga-enye gị ohere ijikọ modul mpụga, nke ga-eme ka ọrụ Nginx bụrụ nke na-akparaghị ókè. Mana enwere NGINX Plus ma eleghị anya ọ gaghị enwe njirimara!

Ebe nchekwa nwere koodu zuru oke maka akụkọ

njs-pypi nwere nkwado AWS Sign v4

Nkọwa ntuziaka nke modul ngx_http_js_module

Ebe nchekwa NJS gọọmentị и akwụkwọ

Ọmụmaatụ nke iji NJS sitere na Dmitry Volintsev

njs - scripting Javascript nke ala na nginx / Okwu Dmitry Volnyev na Saint HighLoad++ 2019

NJS na mmepụta / Okwu Vasily Soshnikov na HighLoad++ 2019

Ịbanye na nyochaa arịrịọ REST na AWS

isi: www.habr.com