ในบทความนี้ ฉันต้องการแบ่งปันประสบการณ์ของฉันกับ NJS ซึ่งเป็นล่าม JavaScript สำหรับ Nginx ที่พัฒนาโดย Nginx Inc โดยอธิบายความสามารถหลักโดยใช้ตัวอย่างจริง NJS เป็นชุดย่อยของ JavaScript ที่ช่วยให้คุณสามารถขยายฟังก์ชันการทำงานของ Nginx ได้ ถึงคำถาม
กระโน้น…
ในงานสุดท้ายของฉัน ฉันได้สืบทอด Gitlab ด้วยไปป์ไลน์ CI/CD หลายแบบที่มี docker-compose, dind และความสุขอื่นๆ ซึ่งถูกถ่ายโอนไปยัง kaniko rails รูปภาพที่เคยใช้ใน CI ก่อนหน้านี้ถูกย้ายในรูปแบบดั้งเดิม พวกเขาทำงานได้อย่างถูกต้องจนถึงวันที่ IP gitlab ของเราเปลี่ยนไปและ CI กลายเป็นฟักทอง ปัญหาคือหนึ่งในอิมเมจนักเทียบท่าที่เข้าร่วมใน CI มีคอมไพล์ ซึ่งดึงโมดูล Python ผ่าน ssh สำหรับ ssh คุณต้องมีรหัสส่วนตัวและ... มันอยู่ในรูปภาพพร้อมกับknown_hosts และ CI ใด ๆ ล้มเหลวโดยมีข้อผิดพลาดในการตรวจสอบคีย์เนื่องจาก IP จริงไม่ตรงกันกับ IP ที่ระบุในknown_hosts ภาพใหม่ถูกประกอบอย่างรวดเร็วจาก Dockfiles ที่มีอยู่และเพิ่มตัวเลือก StrictHostKeyChecking no
. แต่รสชาติที่ไม่ดียังคงอยู่และมีความปรารถนาที่จะย้าย libs ไปยังที่เก็บ PyPI ส่วนตัว โบนัสเพิ่มเติมหลังจากเปลี่ยนมาใช้ PyPI ส่วนตัวคือไปป์ไลน์ที่ง่ายกว่าและคำอธิบายปกติของ Requirements.txt
สุภาพบุรุษได้เลือกแล้ว!
เราดำเนินการทุกอย่างบนคลาวด์และ Kubernetes และท้ายที่สุดแล้ว เราต้องการรับบริการเล็กๆ ที่เป็นคอนเทนเนอร์ไร้สัญชาติพร้อมพื้นที่จัดเก็บข้อมูลภายนอก เนื่องจากเราใช้ S3 จึงให้ความสำคัญกับมัน และหากเป็นไปได้ด้วยการรับรองความถูกต้องใน gitlab (คุณสามารถเพิ่มได้เองหากจำเป็น)
การค้นหาอย่างรวดเร็วให้ผลลัพธ์หลายประการ: s3pypi, pypicloud และตัวเลือกที่มีการสร้างไฟล์ html สำหรับหัวผักกาด "ด้วยตนเอง" ตัวเลือกสุดท้ายหายไปเอง
s3pypi: นี่คือ cli สำหรับการใช้โฮสติ้ง S3 เราอัปโหลดไฟล์ สร้าง html และอัปโหลดไปยังบัคเก็ตเดียวกัน เหมาะสำหรับใช้ในบ้าน
pypicloud: ดูเหมือนเป็นโปรเจ็กต์ที่น่าสนใจ แต่หลังจากอ่านเอกสารแล้ว ฉันก็ผิดหวัง แม้จะมีเอกสารที่ดีและความสามารถในการขยายให้เหมาะกับความต้องการของคุณ แต่ในความเป็นจริง กลับกลายเป็นว่ามีความซ้ำซ้อนและกำหนดค่าได้ยาก การแก้ไขโค้ดให้เหมาะสมกับงานของคุณตามประมาณการ ณ ขณะนั้น อาจจะใช้เวลาประมาณ 3-5 วัน บริการยังต้องการฐานข้อมูล เราทิ้งมันไว้ในกรณีที่เราไม่พบสิ่งอื่นใด
การค้นหาเชิงลึกมากขึ้นทำให้ได้โมดูลสำหรับ Nginx, ngx_aws_auth ผลการทดสอบของเขาคือ XML ที่แสดงในเบราว์เซอร์ ซึ่งแสดงเนื้อหาของบัคเก็ต S3 การกระทำครั้งสุดท้ายในขณะที่ทำการค้นหาคือหนึ่งปีที่แล้ว พื้นที่เก็บข้อมูลดูเหมือนถูกทิ้งร้าง
โดยเข้าไปอ่านที่มา
จากตัวอย่างนี้เป็นพื้นฐาน ชั่วโมงต่อมาฉันเห็น XML เดียวกันกับเมื่อใช้โมดูล ngx_aws_auth ในเบราว์เซอร์ของฉัน แต่ทุกอย่างถูกเขียนด้วย JS แล้ว
ฉันชอบโซลูชัน nginx มาก ประการแรก เอกสารที่ดีและตัวอย่างมากมาย ประการที่สอง เราได้รับข้อดีทั้งหมดของ Nginx สำหรับการทำงานกับไฟล์ (นอกกรอบ) ประการที่สาม ใครก็ตามที่รู้วิธีเขียนการกำหนดค่าสำหรับ Nginx จะสามารถเข้าใจได้ว่าอะไรคืออะไร ความเรียบง่ายก็เป็นข้อดีสำหรับฉันเช่นกัน เมื่อเทียบกับ Python หรือ Go (หากเขียนตั้งแต่เริ่มต้น) ไม่ต้องพูดถึง Nexus
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
: นี่คือตัวแปรที่สร้างคำขอไปยัง S3 ขึ้นอยู่กับ uri เริ่มต้น หากคุณต้องการรับเนื้อหาของ "รูท" คุณจะต้องสร้างคำขอ uri ที่ระบุตัวคั่น delimiter
ซึ่งจะส่งคืนรายการองค์ประกอบ XML ของ CommonPrefixes ทั้งหมดที่สอดคล้องกับไดเร็กทอรี (ในกรณีของ PyPI คือรายการแพ็คเกจทั้งหมด) หากคุณต้องการรับรายการเนื้อหาในไดเร็กทอรีเฉพาะ (รายการเวอร์ชันแพ็กเกจทั้งหมด) คำขอ uri จะต้องมีฟิลด์คำนำหน้าที่มีชื่อของไดเร็กทอรี (แพ็กเกจ) ซึ่งจำเป็นต้องลงท้ายด้วยเครื่องหมายทับ / มิฉะนั้น อาจเกิดการขัดแย้งกันได้เมื่อร้องขอเนื้อหาของไดเร็กทอรี เป็นต้น มีไดเร็กทอรี aiohttp-request และ aiohttp-requests และหากคำขอระบุไว้ /?prefix=aiohttp-request
จากนั้นการตอบกลับจะมีเนื้อหาของทั้งสองไดเร็กทอรี หากมีเครื่องหมายทับต่อท้าย /?prefix=aiohttp-request/
จากนั้นการตอบกลับจะมีเฉพาะไดเร็กทอรีที่จำเป็นเท่านั้น และหากเราขอไฟล์ ผลลัพธ์ที่ได้ก็ไม่ควรแตกต่างจากไฟล์ต้นฉบับ
บันทึกและรีสตาร์ท 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>"00000000000000000000000000000000-1"</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>"b2d76df4aeb4493c5456366748218093"</ETag>
<Size>93183</Size>
<Owner>
<ID>02d6176db174dc93cb1b899f7c6078f08654445fe8cf1b6ce98d8855f66bdbf4</ID>
<DisplayName></DisplayName>
</Owner>
<StorageClass>STANDARD</StorageClass>
</Contents>
</ListBucketResult>
จากรายการไฟล์เราจะรับเฉพาะองค์ประกอบเท่านั้น Key
.
สิ่งที่เหลืออยู่คือแยกวิเคราะห์ XML ที่เป็นผลลัพธ์แล้วส่งออกเป็น HTML โดยแทนที่ส่วนหัว Content-Type ด้วย text/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="/th/${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
เราทำซ้ำกับ libs ของเรา
# Создаем для тестов новое окружение
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 เราจะเปลี่ยนเส้นทางข้อมูลการตรวจสอบสิทธิ์ไปยังคำขอย่อยที่มีการเรียกใช้ฟังก์ชันในสคริปต์ สคริปต์จะสร้างคำขอย่อยอีกครั้งไปยัง URL ของ Gitlab และหากข้อมูลการตรวจสอบสิทธิ์ถูกระบุอย่างถูกต้อง 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 = need('aws-sdk') และไม่จำเป็นต้องเขียน “bike” ด้วยการตรวจสอบสิทธิ์ S3!
มาดูข้อเสียกันดีกว่า
สำหรับฉัน การไม่สามารถนำเข้าโมดูล JS ภายนอกกลายเป็นคุณสมบัติที่ไม่พึงประสงค์ แต่ก็คาดหวังได้ อธิบายไว้ในตัวอย่างข้างต้น need('crypto') is
ต้องปิดใช้งานการบีบอัดสำหรับโครงการปัจจุบันใน Nginx gzip off;
เนื่องจากไม่มีโมดูล gzip ใน NJS และไม่สามารถเชื่อมต่อได้ ดังนั้นจึงไม่มีวิธีทำงานกับข้อมูลที่บีบอัด จริงอยู่นี่ไม่ใช่ข้อเสียจริงๆสำหรับกรณีนี้ มีข้อความไม่มากและไฟล์ที่ถ่ายโอนได้รับการบีบอัดแล้วและการบีบอัดเพิ่มเติมจะไม่ช่วยอะไรได้มากนัก นอกจากนี้ นี่ไม่ใช่บริการโหลดหรือสำคัญที่คุณต้องกังวลกับการส่งเนื้อหาเร็วขึ้นสองสามมิลลิวินาที
การดีบักสคริปต์ใช้เวลานานและทำได้ผ่านการ "พิมพ์" ใน error.log เท่านั้น ขึ้นอยู่กับข้อมูลระดับการบันทึกที่ตั้งไว้ คำเตือนหรือข้อผิดพลาด คุณสามารถใช้ 3 วิธี r.log, r.warn, r.error ตามลำดับ ฉันพยายามแก้ไขสคริปต์บางตัวใน Chrome (v8) หรือเครื่องมือคอนโซล njs แต่ไม่ใช่ทุกสิ่งที่สามารถตรวจสอบได้ เมื่อทำการดีบักโค้ด หรือที่รู้จักกันในชื่อ Functional Testing ประวัติจะมีลักษณะดังนี้:
docker-compose restart nginx
curl localhost:8080/
docker-compose logs --tail 10 nginx
และอาจมีลำดับเช่นนี้ได้หลายร้อยลำดับ
การเขียนโค้ดโดยใช้แบบสอบถามย่อยและตัวแปรสำหรับสิ่งเหล่านั้นจะกลายเป็นเรื่องยุ่งเหยิง บางครั้งคุณเริ่มวิ่งไปรอบๆ หน้าต่าง IDE ต่างๆ โดยพยายามค้นหาลำดับการทำงานของโค้ดของคุณ ไม่ใช่เรื่องยากแต่บางครั้งก็น่ารำคาญมาก
ไม่มีการรองรับ ES6 อย่างเต็มรูปแบบ
อาจมีข้อบกพร่องอื่น ๆ แต่ฉันไม่พบสิ่งอื่นใด แบ่งปันข้อมูลหากคุณมีประสบการณ์เชิงลบในการใช้ NJS
ข้อสรุป
NJS เป็นล่ามโอเพ่นซอร์สน้ำหนักเบาที่ช่วยให้คุณสามารถใช้สคริปต์ JavaScript ต่างๆ ใน Nginx ในระหว่างการพัฒนา มีการให้ความสนใจอย่างมากต่อประสิทธิภาพ แน่นอนว่ายังขาดหายไปอีกมาก แต่โครงการนี้กำลังได้รับการพัฒนาโดยทีมงานเล็กๆ และพวกเขากำลังเพิ่มคุณสมบัติใหม่และแก้ไขข้อบกพร่องอย่างแข็งขัน ฉันหวังว่าสักวันหนึ่ง NJS จะอนุญาตให้คุณเชื่อมต่อโมดูลภายนอก ซึ่งจะทำให้ฟังก์ชัน Nginx แทบจะไร้ขีดจำกัด แต่มี NGINX Plus และมีแนวโน้มว่าจะไม่มีฟีเจอร์!
ที่มา: will.com