فهم كيفية توظيف FastCGI كوسيط لـ Nginx


محمد عزمي

لقد أصبح Nginx أحد أكثر التطبيقات البرمجية المتوفرة لخواديم الويب Web Servers مرونةً وقوة. ولكن إذا ما نظرنا إليه من الناحية التصميمية فإنه يعد أولًا وقبل كل شيء خادومًا وسيطا Proxy Server. إن التركيز على أداء Nginx كوسيط يعني أنه ذو أداء عالي عندما يعمل على معالجة الطلبات بالتعاون مع خواديم أخرى.

fastcgi-nginx_(1).thumb.png.69e40473044a

 

يمكن لـ Nginx أن يلعب دور الوسيط باستخدام بروتوكولات Protocols http ,FastCGI ,uwsgi ,SCGI ,memcached. سنناقش في هذه المقالة استخدام FastCGI للقيام بالوساطة proxying، فهو يعد واحدًا من أكثر البرتوكولات التي تُستخدم لذلك الغرض.

لماذا نستخدم FastCGI للقيام بالوساطة؟

يستخدم FastCGI للقيام بالوساطة ضمن Nginx كي يترجم طلبات المستخدمين Clients لخادوم التطبيقات Application Server الذي لا يقوم بمعالجة طلبات المستخدمين مباشرةً أو الذي لا ينبغي له ذلك. إنّ FastCGI هو برتوكول مبني على برتوكول Common Gateway Interface الأقدم والذي يشار إليه اختصارًا CGI، وقد أُريد من بروتوكول FastCGI أن يحسّن أداء البروتوكول السابق عبر عدم معالجة كل طلب في عملية Process خاصة به. يُستخدم البرتوكول ليمثّل واجهة interface ذات فعالية أكبر مع الخواديم التي تعالج طلبات المحتوى الديناميكي.

إن معالجة PHP هي أحد حالات الاستخدام use-case الرئيسية لبرتوكول FastCGI ضمن Nginx. وعلى عكس أباتشي Apache، الذي يستطيع معالجة PHP مباشرةً باستخدام وحدة mod_php، فإنه ينبغي على Nginx الاعتماد على معالج PHP منفصل لمعالجة طلبات PHP. غالبًا ما تُنجز تلك المعالجة باستخدام php_fpm وهو معالج PHP قد اختبر بشكل كبير للعمل مع Nginx.

يمكن أن يُستخدم Nginx مع FastCGI للعمل مع تطبيقات تستخدم لغاتٍ أخرى طالما أنه يتوفر مكونات مهيّأة للاستجابة لطلبات FastCGI.

أسياسيات قيام FastCGI بالوساطة

عادةً ما تتضمن طلبات الوساطة proxying requests عنوان الخادوم الوسيط- Nginx في حالتنا- الذي يوجّه الطلبات من قبل المستخدمين إلى الخادوم الوسيط بينه (الخلفي) Backend Server. يستخدم Nginx التعليمة fastcgi_pass للتعريف عن الخادوم الفعلي الذي ستوجّه إليه الطلبات باستخدام برتوكول FastCGI. فمثلًا، لتوجيه أي طلب موافق للغة PHP إلى الخادوم الخلفي المخصص لمعالجة PHP باستخدام برتوكول FastCGI، فقد يبدو المقطع البرمجي للموقع Location Block مشابهًا لما يلي:

# server context

location ~ \.php$ {
   fastcgi_pass 127.0.0.1:9000;
}

. . .

في الواقع، لن يعمل المقطع السابق إذا ما تم تنفيذه على حاله لأنه يقدّم معلوماتٍ قليلةً جدًا. في كل مرّة يتم فيها إنشاء اتصال وسيط Proxy Connection فإنه يجب ترجمة الطلب الأصلي لضمان أن يتمكن الخادوم الخلفي من فهم الطلب المُدار Proxied request. وباعتبار أننا نغيّر البرتوكولات بتمرير الطلب عبر FastCGI فإن ذلك يتطلب بعض العمل الإضافي، سنصطلح في هذه المقالة على عملية تمرير الطلبات عبر FastCGI بتمرير FastCGI أي FastCGI pass.

إنّ القيام بالوساطة من بروتوكول Http وإليه http-to-http Proxying ينطوي بشكل رئيسي على زيادة حجم الترويسة Header ليضمن أن يمتلك الخادوم الخلفي المعلومات التي يحتاجها للرد على الخادوم الوسيط والذي ينوب بدوره عن المستخدم Client، أما FastCGI فهو برتوكول منفصل لا يستطيع قراءة ترويسات Http. مع أخذ ذلك بالحسبان فإن أية معلومات ذات صلة يجب أن تمرّر للخادوم الخلفي عبر وسيلة أخرى.

إن الطريقة الأساسية لتمرير المعلومات الإضافية عند استخدام برتوكول FastCGI هي باستخدام الإعدادات Parameters. حيث يجب أن يُهيّئ الخادوم الخلفي لقراءة تلك الإعدادات ومعالجتها والتصرّف وفقًا لقيمها. يستطيع Nginx أن يحدّد إعدادات FastCGI باستخدام التعليمة fastcgi_param.

إن الحد الأدنى للإعدادات الواجب ضبطها حتى يعمل FastCGI كوكيل PHP مشابهة لما يلي:

# server context

location ~ \.php$ {
   fastcgi_param REQUEST_METHOD $request_method;
   fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
   fastcgi_pass 127.0.0.1:9000;
}

. . .

لقد قمنا في الإعدادات السابقة بضبط إعدادات FastCGI وهما REQUEST_METHOD و SCRIPT_FILENAME. إن كل من الإعدادات السابقة ضرورية للخادوم الخلفي حتى يتمكن من فهم طبيعة الطلب. إذ أن الإعداد الأول يخبره بنوع العملية التي يجب أن يقوم بإنجازها، أما الإعداد الثاني فيخبر مجموعة الخواديم Upstream - التي تعالج طلبات الخادوم الوسيط- بالملف الذي يجب أن ينفّذه.

استخدمنا في المثال بعض المتغيرات variables في Nginx لضبط قيم تلك الإعدادات. سيحوي المتغير request_method$ على نوع الوظيفة http method التي طلبها المستخدم.

من ناحية أخرى فإن قيمة الإعداد SCRIPT_FILENAME هي عبارة عن دمج قيمتي المتغيرين document_root$ و fastcgi_script_name$. أما المتغير document_root$ فسيحوي على مسار المجلد الرئيسي والذي ضبط عبر التعليمة root. سيضبط المتغير fastcgi_script_name$ بمعرف الموارد الموحد URI المطلوب. إن انتهى  معرف URI للطلب request URI  بالرمز (/)، فستلحق قيمة التعليمة fastcgi_index في النهاية. إن هذا النوع من المرجعية الذاتية self-referential في تعريف الموقع ممكن لأننا نشغّل معالج FastCGI على نفس الجهاز الذي يشغّل Nginx. فلنلقي نظرة على مثال آخر:

# server context
root /var/www/html;

location /scripts {
   fastcgi_param REQUEST_METHOD $request_method;
   fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
   fastcgi_index index.php;
   fastcgi_pass unix:/var/run/php5-fpm.sock;
}

. . .

إن اختير الموقع location في المثال السابق ليعالج طلبًا في المسار: /scripts/test/ فإن قيمة المسار الوسيط SCRIPT_FILENAME ستنتج عن دمج قيم تعليمة root ومعرّف URI للطلب بالإضافة للتعليمة fastcgi_index. وبالتالي ستكون قيمة الإعداد في مثالنا هذا هي: var/www/html/scripts/test/index.php/

هنالك تعديل هام آخر قمنا به في الإعدادات السابقة، إذ أننا ضبطنا خادوم FastCGI الخلفي FastCGI backend ليستخدم مقبس يونيكس Unix Socket عوضًا عن مقبس شبكة Network socket. يمكن لـ Nginx أن يستخدم أيًا من الواجهتين Interface ليتصل مع مجموعة خواديم FastCGI Upstream. إن كان معالج FastCGI يعمل على نفس الخادوم فعادةً ما يُستحسن استخدام مقبس يونيكس من أجل الأمان.

شرح إعدادات FastCGI

هنالك قاعدة أساسية لكتابة نص برمجي (كود) Code قابل للصيانة وهي محاولة اتباع مبدأ DRY أي لا تكرر نفسك "Don’t Repeat Yourself". يساعد ذلك في تقليل الأخطاء بالإضافة لجعل الكود قابلًا لإعادة الاستخدام كما أنها تتيح تنظيمًا أفضل. إنّ أحد أكثر التوصيات الأساسية لإدارة Nginx هي أن تُضبط كل الأوامر في أوسع مجال ممكن لها، تطبّق تلك الأهداف الأساسية على إعداداتNginx أيضًا.

عند التعامل مع إعدادات الوسيط لـ FastCGI فسيتم تشارك الغالبية العظمى للإعدادات في معظم حالات الاستخدام. بسبب ذلك وبسبب الطريقة التي تعمل بها وحدة الوراثة inheritance التابعة لـ Nginx فإنه دائمًا ما يستحسن تعريف الوسطاء في مجال عام.

التصريح عن تفاصيل إعدادات FastCGI في السياقات الأم Parent Contexts

إن أحد الطرق لتقليل التكرار هو التصريح عن تفاصيل الإعدادات في السياق الأم، أي سياق أعلى. يمكن لكل الإعدادات الموجودة خارج التعليمة fastcgi_pass أن تُضبط في مستويات أعلى. إذ أنها ستتدفق للأسفل نحو الموقع الذي تم منه تمرير الإعداد. وذلك يعني أنّه يمكن لعدّة أماكن أن تستخدم نفس الإعدادات.

فمثلًا، يمكننا أن نعدّل على القسم العلوي من المقطع البرمجي السابق لجعله مفيدًا في عدة مواقع:

# server context
root /var/www/html;

fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_index index.php;

location /scripts {
   fastcgi_pass unix:/var/run/php5-fpm.sock;
}

location ~ \.php$ {
   fastcgi_pass 127.0.0.1:9000;
}

. . .

إنّ كلًا من تصاريح fastcgi_param والتعليمة fastcgi_index في المثال السابق متوفرين لدى مقطعي الموقع location block الواردين لاحقًا. تلك هي إحدى الطرق لإزالة التكرار.

بكل الأحوال، إن للإعدادات السابقة مساوئ كبيرة، إن صُرّح عن أية fastcgi_param ضمن سياق أدنى فلن تتم وراثة أيٍّ من قيم fastcgi_param ضمن السياقات العليا (السياقات الأم). أي إما أن تستخدم القيم الموروثة فقط أو لا تستخدم أيًا منها.

تُعد التعليمة fastcgi_param مصفوفةً array من التعليمات في لغةNginx. من منظور المستخدمين فإن أي مصفوفة تعليمات هي بشكل أساسي تعليمة يمكن استخدامها أكثر من مرة ضمن سياق واحد. وأي تصريح لاحق سيضيف معلومات جديدة إلى ما يعرفه Nginx من التصريح السابق. لقد صمّمت التعليمة fastcgi_param كمصفوفة من التعليمات حتى تسمح للمستخدمين بضبط عدّة إعدادات.

تورّث أي مصفوفة تعليمات إلى سياق أدنى، أو سياق ابن child context، بطريقة مختلفة عن بعض التعليمات الأخرى. ستورّث معلومات مصفوفة التعليمات إلى سياقات الأبناء فقط إن لم تكن موجودة في أي مكان ضمن سياق الابن. وذلك يعني أنك إن استخدمت تعليمة fastcgi_param ضمن موقعك فإنها ستمحي القيم الموروثة من السياق الأعلى تمامًا.

فمثلًا، يمكننا التعديل قليلًا على الإعدادات السابقة كما يلي:

# server context
root /var/www/html;

fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_index index.php;

location /scripts {
   fastcgi_pass unix:/var/run/php5-fpm.sock;
}

location ~ \.php$ {
   fastcgi_param QUERY_STRING $query_string;
   fastcgi_pass 127.0.0.1:9000;
}

. . .

قد تعتقد للوهلة الأولى أنه ستتم وراثة الإعدادات REQUEST_METHOD و SCRIPT_FILENAME في مقطع لموقع ثانٍ، بالإضافة لتوفير الإعداد QUERY_STRING ضمن ذلك السياق المحدّد.

لكن ما يحدث في الحقيقة هو أنّ كل قيم fastcgi_param التابعة للأب ستُمحى ضمن السياق الثاني، وسيضبط الإعداد QUERY_STRING فقط. بينما ستبقى الإعدادات REQUEST_METHOD و SCRIPT_FILE دون ضبط.

ملاحظة حول القيم المتعدّدة للوسطاء ضمن نفس السياق

يجدر بالذكر هنا أثار ضبط قيم متعدّدة لنفس الإعدادات ضمن سياق واحد. فلنأخذ المثال التالي لمناقشة ذلك:

# server context

location ~ \.php$ {
   fastcgi_param REQUEST_METHOD $request_method;
   fastcgi_param SCRIPT_FILENAME $request_uri;

   fastcgi_param DOCUMENT_ROOT initial;
   fastcgi_param DOCUMENT_ROOT override;

   fastcgi_param TEST one;
   fastcgi_param TEST two;
   fastcgi_param TEST three;

   fastcgi_pass 127.0.0.1:9000;
}

. . .

في المثال السابق، قمنا بضبط الإعدادات TEST و DOCUMENT_ROOT عدّة مرات ضمن سياقٍ واحد. باعتبار أن fastcgi_param مصفوفة تعليمات فإن كلّ تصريح لاحق سيُضاف إلى سجلات Nginx الداخلية. سيحظى الإعداد TEST بعدة تصاريح ضمن المصفوفة يضبط من خلالها بالقيم one و two و three.

من المهم أن ندرك هنا أنّ كلّ ذلك سيمرّر إلى خادوم FastCGI الخلفي دون أي معالجة إضافية من Nginx. يعني ذلك أنّ القرار حول كيفية معالجة تلك القيم يعود بشكل كامل إلى معالج Nginx المختار. للأسف، على اختلاف معالجات FastCGI فإن كلًا منها يعالج القيم الممرّرة بطريقة تختلف تمامًا عن الأخرى.

فمثلًا، إن استقبلت PHP-FPM الإعدادات السابقة فستفسر القيم النهائية لتحل محل أي قيم سابقة. بهذه الحالة سيُضبط الإعداد TEST بالقيمة three. وبالمثل سيضبط الإعداد DOCUMENT_ROOT بالقيمة override.

ولكن إن مُرّرت القيم السابقة لخادومٍ مثل FsgiWrap، فستفسَّر القيم بشكل مختلف كثيرًا. ففي البداية سيقوم بتمرير تمهيدي ليقرر أي القيم ينبغي استخدامها لتشغيل البريمج script. وسيستخدم القيمة initial للإعداد DOCUMENT_ROOT ليبحث عن البريمج. إلا أنه عندما سيمرّر القيم الفعلية للبريمج فسيمرر القيم الأخيرة كما يفعل PHP-FPM تمامًا.

إن التضارب inconsistency وعدم إمكانية التنبؤ unpredictability يعني أنك لا تستطيع، بل أنه لا ينبغي لك، أن تعتمد على الخادوم الخلفي لتفسير نيّتك بشكل صحيح إذا ما ضبطّت الإعدادات أكثر من مرّة واحدة. فالطريقة الوحيدة الآمنة هي بالتصريح عن كل إعداد مرّةً واحدة. يعني ذلك أيضًا أنه لا يوجد طريقة آمنة لإعادة تعريف overriding القيمة الافتراضية باستخدام تعليمة fastcgi_param.

طريقة تضمين إعدادات مكتوبة بملف منفصل ضمن ملف إعدادات FastCGI الأساسي

يوجد طريقة أخرى لتفصل بنود إعداداتك الشائعة، أي التي تقوم بها بشكل متكرّر. بإمكاننا استخدام تعليمة include لقراءة محتوى ملف منفصل وتضمينها في الموقع الذي تم فيه التصريح عن التعليمة.

يعني ذلك أنه بإمكاننا الاحتفاظ بكل الإعدادات الشائعة في ملف واحد ومن ثمّ تضمينه في أي مكان ضمن إعداداتنا إن اقتضى الأمر. وباعتبار أن Nginx سيضع محتوى الملف حيثما وجدت تعليمة include فلن نكون قد ورثنا من سياق الأم إلى سياق الابن. سيمنع ذلك قيم fastcgi_param من أن تُمحى وسيمنحنا القدرة على ضبط إعدادات إضافية عند الحاجة.

يمكننا في البداية أن نضبط قيم إعدادات FastCGI الشائعة لدينا في ملف منفصل وتخزينه ضمن المسار الحاوي على ملف الإعدادات الأصلي، سنقوم بتسمية ذلك الملف fastcgi_common:

fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

يمكننا الآن أن نستورد محتوى الملف حيثما نريد استخدام قيم تلك الإعدادات:

# server context
root /var/www/html;

location /scripts {
   include fastcgi_common;

   fastcgi_index index.php;
   fastcgi_pass unix:/var/run/php5-fpm.sock;
}

location ~ \.php$ {
   include fastcgi_common;
   fastcgi_param QUERY_STRING $query_string;
   fastcgi_param CONTENT_TYPE $content_type;
   fastcgi_param CONTENT_LENGTH $content_length;

   fastcgi_index index.php;
   fastcgi_pass 127.0.0.1:9000;
}

. . .

لقد قمنا هنا بنقل قيم بعض الإعدادات الشائعة إلى ملف اسمه fastcgi_common والموجود في المسار الافتراضي الحاوي على إعدادات NGINX. ثمّ نستورد القيم المصرّح عنها في الملف عندما نحتاج لإدخالها. هنالك العديد من الأمور التي يجب ملاحظتها حول هذه الإعدادات:

أولًا، لم نضع في الملف الذي ننوي استيراده أي قيم قد نرغب بتخصيصها وفقًا لموقعه، بسبب مشكلة التفسير التي ذكرناها سابقًا والتي تحدث عندما نضبط عدة قيم لنفس الإعداد، ولأنه لا يمكن ضبط التعليمات التي ليست على شكل مصفوفة non-array directives إلا مرّةً واحدة خلال السياق، لذا نضع العناصر التي لن نرغب بتغييرها فقط في الملف المشترك. إنّ كلّ تعليمة (أو إعداد أساسي) قد نرغب بتخصيصه وفقًا لموقعه يجب ألا يضمّن في الملف المشترك.

الأمر الآخر الذي ربما قد لاحظته أننا قد ضبطنا بعض إعدادات FastCGI الإضافية في المقطع البرمجي للموقع الثاني. وتلك هي الإمكانية التي كنا نأمل الوصول إليها. تمكّنا من إضافة إعدادات fastcgi_param إضافية كلما اقتضت الحاجة ومن دون أن تُمسح القيم المشتركة.

استخدام الملف fastcgi_params أو الملف fastcgi.conf

بأخذ الاستراتيجية السابقة بالحسبان فإن مطوري Nginx والعديد من فرق تحزيم التوزيعات distribution packaging teams قد عملوا على توفير مجموعة من الإعدادات شائعة الاستخدام والتي يمكن أن تضمنها في مواقع تمرير FastCGI أي FastCGI pass location. تدعى تلك الإعدادات fastcgi_params أو fastcgi.conf.

إنّ هذين الملفين متشابهين كثيرًا ولكن مع وجود اختلاف وحيد ممثّل في تمرير قيم متعدّدة لإعداد واحد، وهي المشكلة التي ناقشناها سابقًا، لا يحتوي fastcgi_params على تصريح عن الإعداد SCRIPT_FILENAME على عكس الملف fastcgi.conf.

لقد أوجِد الملف fastcgi_params منذ وقتٍ أبعد. ثم ظهرت الحاجة لإنشاء ملف جديد عندما اتُّخذ قرارٌ لتوفير قيمةٍ افتراضية للإعداد SCRIPT_FILENAME وذلك حتى نتجنب تجزئة الإعدادات التي تعتمد على fastcgi_params، إن عدم فعل ذلك قد يؤدي إلى ضبط ذلك الإعداد في كل من الملف المشترك وموقع تمرير FastCGI.

العديد من مشرفي تحزيم التوزيعات الشهيرة قد صوّتوا لتضمين إحدى هذين الملفين فقط أو لتطابق محتواهما تمامًا. إن توفّر لديك أحد هذين الملفين فقط فاستخدمه وعدّل عليه كما تشاء ليناسب احتياجاتك.

إن كان كلا الملفين متوفرين لك، فقد يكون من الأفضل أن تضمّن الملف fastcgi.conf في معظم مواقع تمرير FastCGI، وذلك لأنه يتضمّن تصريحًا عن الإعداد SCRIPT_FILENAME. عادةً ما يُرغب بذلك إلا أنّ هنالك بعض الحالات التي قد ترغب فيها بتخصص هذه القيمة.

يمكن تضمين هذين الملفين عبر الإشارة إلى موقعهما بالنسبة إلى المجلد الجذر root الحاوي على إعدادات Nginx. إن مسار المجلد الجذر لإعداداتNginx يكون مشابهًا لما يلي: etc/nginx/ وذلك إن تم تثبيته باستخدام مدير الحزم package manager.

يمكنك تضمين الملفين كما يلي:

# server context

location ~ \.php$ {
   include fastcgi_params;
   # You would use "fastcgi_param SCRIPT_FILENAME . . ." here afterwards
   . . .
}

أو كما يلي:

# server context

location ~ \.php$ {
   include fastcgi.conf;
   . . .
}

تعليمات وإعدادات ومتغيرات FastCGI المهمّة

لقد قمنا في القسم السابق بضبط عدد لا بأس به من الإعدادات بهدف شرح مفاهيمٍ أخرى، وعادةً ما كنا نضبطها بمتغيرات Nginx. كما أننا قدمنا بعض تعليمات FastCGI من دون شرحٍ وفير. سنناقش في هذا القسم بعض التعليمات شائعة الضبط، وبعض الإعدادات التي قد تحتاج لتعديلها، بالإضافة لبعض المتغيرات التي قد تحوي معلوماتٍ تحتاجها.

تعليمات FastCGI الشائعة:

سنستعرض فيما يلي بعض أكثر التعليمات المفيدة للعمل مع تمريرات FastCGI:

  • fastcgi_pass: هي التعليمة التي تقوم بعملية تمرير الطلبات الموجودة ضمن السياق الحالي إلى الخادوم الخلفي. تعرٍّف هذه التعليمة الموقع الذي يمكن الوصول عبره إلى معالج FastCGI.
  • fastcgi_param: وهي مصفوفة التعليمات التي يمكن استخدامها لضبط قيم الإعدادات. غالبًا ما يقترن استخدام هذه التعليمة مع متغيرات Nginx لضبط وسطاء FastCGI بالقيم المحدّدة في الطلب.
  • try_files: ليست تعليمةً خاصة بـ FastCGI، إلا أنها استخدامها شائع في موقع تمرير FastCGI، عادةً ما تستخدم كجزء من إجراء الطلب الوقائي للتأكد من وجود الملف المطلوب قبل تمريره إلى معالج FastCGI.
  • include: مجدّدًا، ليست تعليمةً خاصة بـ FastCGI، إلا أنها تستخدم بكثافة عالية في سياقات تمرير FastCGI أي FastCGI pass contexts. غالبًا ما تستخدم لتضمين تفاصيل الإعدادات الشائعة والمشتركة بين عدّة مواقع.
  • fastcgi_split_path_info: تستخدم هذه التعليمة لتعريف التعابير النمطية regular expression مع مجموعتي التقاط captured groups. تُستخدم المجموعة الأولى كقيمة للمتغير fastcgi_script_name$ بينما تستخدم المجموعة الثانية كقيمة للمتغير fastcgi_path_info$. عادةً ما تستخدم كلا المجموعتين لتحليل parse الطلب بشكل صحيح حتى يعرف المعالج أي الأجزاء المشكّلة للطلب تمثل الملفات الواجب تشغيلها وأي الأجزاء تمثل المعلومات الإضافية الواجب تمريرها إلى السكربت.
  • fastcgi_index: تعرّف هذه التعليمة ملف الفهرسة index file الذي ينبغي الحاقه بقيمة الإعداد fastcgi_script_name$ والتي تنتهي بـ (/.عادةً ما يكون ذلك مفيدًا إن كان الإعداد SCRIPT_FILENAME مضبوطًا بقيمة document_root$fastcgi_script_name$ وكان مقطع الموقع معدّا لقبول الطلبات الحاوية على معلومات المسار الإضافية.
  • fastcgi_intercept_errors: تعرّف هذه التعليمة فيما إذا كان ينبغي معالجة الأخطاء المتلقاة من خادوم FastCGI أو تمريرها مباشرةً إلى المستخدم.

تمثّل التعليمات السابقة أكثر التعليمات التي ستستخدمها عندما تصمّم تمرير FastCGI نموذجي. ومع أنك قد لا تستخدمها كلها بنفس الوقت، إلا أنك ستلاحظ أنها تتفاعل بشكل وثيق جدًا مع إعدادات ومتغيرات FastCGI التي سنتحدث عنها فيما يلي.

المتغيرات شائعة الاستخدام مع FastCGI:

قبل أن نتمكن من الكلام عن الإعدادات التي ستستخدمها على الأرجح مع تمريرات FastCGI، علينا الكلام قليلًا عن بعض نتغيرات Nginx الشائعة والتي ستستفيد منها في ضبط تلك الإعدادات. تعرّف وحدة FastCGI الخاصة بـ Nginx بـ Nginx’s FastCGI module بعض تلك المتغيرات، إلا أن معظمها من الوحدة الأساسية Core module.

  • query_string$ أو args$: هي القيم الممرّرة arguments في الطلب الأصلي للمستخدم.

  • is_args$: ستساوي قيمتها “?” إن حوى الطلب على قيم ممرّرة وإلا فستُضبط قيمتها بسلسلة نصية فارغة. يفيد ذلك عند إنشاء إعدادات قد تأخذ قيمًا أو لا تأخذ.

  • request_method$: يشير هذا المتغير إلى نوع الطلب request method المستخدم الأصلي. يمكن أن يفيد ذلك بتحديد فيما إذا كان ينبغي السماح لعملية operation ما في السياق الحالي أم لا.

  • content_type$: يضبط هذا المتغير بقيمة الحقل content-type الموجود في ترويسة الطلب. يحتاج الوسيط إلى هذه المعلومات إن كان نوع الطلب المستخدم هو POST وذلك حتى يتمكن من معالجة المحتويات اللاحقة بشكل صحيح.

  • content_length$: يضبط هذا المتغير قيمة الحقل Content-Length الموجود في ترويسة المستخدم. هذه المعلومات لازمة لأي طلب للمستخدم من النوع POST.

  • fastcgi_script_name$: يحتوي هذا المتغير على ملف السكربت الذي يجب تشغيله. إن انتهى الطلب بـ (/) فستحلق قيمة التعليمة fastcgi_index إلى النهاية. وبحال استخدام التعليمة fast_split_path_info فستضبط قيمة هذا المتحول بالمجموعة اللاقطة الأولى المعرّفة من قبل التعليمة. ينبغي على قيمة هذا المتغير أن تشير إلى الملف الفعلي الذي يجب تشغيله.

  • request_filename$: يحتوي هذا المتغير على مسار الملف المطلوب. يحصل المتغير على هذه القيمة بأخذ المسار الحالي لمجلد المستندات الأساسي document root، مع أخذ كل من التعليمتين root و alias بعين الاعتبار. بالإضافة لأخذ قيمة المتغير fastcgi_script_name$. تعد هذه الطريقة مرنةً للغاية لإسناد قيمة للإعداد SCRIPT_FILENAME.

  • request_uri$: يحتوي هذا المتغير على الطلب كاملًا كما استُقبل من المستخدم، يتضمّن ذلك السكربت، أية معلومات مسار إضافية، بالإضافة إلى أي عبارة استعلام query string.

  • fastcgi_path_info$: يحتوي هذا المتغير على معلومات المسار الإضافية التي قد تتوفّر بعد اسم السكربت في الطلب. وقد تحوي قيمته أحيانًا على موقعٍ آخر ينبغي على السكربت أن يعلم به حتى يتم تنفيذه. يحصل هذا المتغير على قيمته من مجموعة التعابير النمطية regex الثانية عند استخدام تعليمة fastcgi_split_path_info.

  • document_root$: يحتوي هذا المتغير على المسار الحالي لمجلد المستندات الأساسي. وسيتم ضبطه تبعًا لتعليمتي root و alias.

  • uri$: يحتوي هذا المتغير على قيمة URI الحالية بعد تسويتها normalization. وبما أن تعليمات معينة تقوم بإعادة الكتابة rewrite أو بإعادة التوجيه الداخلي internallly redirect يمكن أن تترك أثرًا على معرّف URI فإن هذا المتغير سيستخلّص تلك التعديلات.

كما ترى، يوجد العديد من المتغيرات المتوفّرة لك عندما تقرّر كيف تضبط إعدادات FastCGI. العديد منها متشابه إلا أنها تملك بعض الفوارق الصغيرة والتي ستنعكس على تنفيذ بريمجك.

إعدادات FastCGI الشائعة:

تمثّل إعدادات FastCGI المعلومات التي نرغب بجعلها متاحةً لمعالج FastCGI - الذي نرسل إليه الطلبات- على شكل أزواج مفتاح-قيمة key-value. لن تحتاج كلّ التطبيقات إلى نفس الإعدادات لذلك غالبًا ما ستحتاج لمراجعة توثيق التطبيق.

تُعد بعض تلك الإعدادات ضروريةً للمعالج حتى يتمكن من تحديد السكربت - الذي ينبغي تشغيله- بشكل صحيح. بينما أُتيح البعض الآخر للسكربت، إذ أنها قد تغيّر سلوكه إن كان معدًّا ليعتمد على مجموعةٍ من الإعدادات.

  • QUERY_STRING: ينبغي أن يُضبط هذا الإعداد بأي عبارة استعلام مقدّمة من قِبل المستخدم. عادةً ما تكون على شكل أزواج مفتاح-قيمة مزوّدةً بعد إشارة "?"  في معرّف URI. عادةً ما يُضبط هذا الإعداد بقيمة أحد المتحولين query_string$ أو args$، إذ ينبغي عليهما أن يحويا على نفس البيانات.

  • REQUEST_METHOD: يدّل هذا الإعداد معالجَ FastCGI على نوع الإجراء الذي طلبه المستخدم. وهو أحد الإعدادات القلائل التي يجب ضبطها حتى يتم التمرير بشكل صحيح.

  • CONTENT_TYPE: إن ضُبط نوع الطلب في الإعداد السابق ليكون POST فيجب أن يُضبط هذا الإعداد. يشير هذا الإعداد إلى نوع البيانات التي ينبغي على معالج FastCGI توقعها. غالبًا ما يضبط هذا الإعداد بالمتغير content_type$ فقط، والذي يضبط بدوره وفقًا للمعلومات المأخودة من الطلب الأصلي.

  • CONTENT_LENGTH: يجب أن يضبط هذا الإعداد إن كان نوع الطلب هو POST. يشير هذا الإعداد إلى طول المحتوى، وغالبًا ما يضبط بالمتغير content_length$ والذي يأخذ قيمته من المعلومات المتوفّرة في طلب المستخدم الأصلي.

  • SCRIPT_NAME: يستخدم هذا الإعداد ليُشير إلى اسم السركبت الأساسي الذي يجب تشغيله. وهو إعداد هامٌ للغاية يمكن ضبطه بعدّة طرق وفقًا لاحتياجاتك. عادةً ما يُضبط بالمتغير fastcgi_script_name$ والذي يجب أن يكون معرّف URI للطلب، أو معرف URI للطلب متبوعًا بـ fastcgi_index إن انتهى المعرّف بـ (/)، أو المجموعة اللاقطة الأولى إن استُخدم fastcgi_fix_path_info.

  • SCRIPT_FILENAME: يحدّد هذا الإعداد الموقع الفعلي على القرص للسكربت الذي يجب تشغيله. وبسبب ارتباطه مع الإعداد SCRIPT_NAME، تقترح بعض أدلة الاستخدام Guides أن تستعيض عن استخدامه بالمتغيرين document_root$fastcgi_script_name$. يتوفر بديلٌ آخر يملك العديد من المحاسن وهو استخدام request_filename$.

  • REQUEST_URI: ينبغي أن تحوي على معرّف URI للطلب كاملًا ومن من دون تعديل، بالإضافة إلى السكربت الذي يجب تشغيله، ومعلومات المسار الإضافية، وأي قيمٍ ممرّرة. تفضّل بعض التطبيقات أن تحلّل تلك المعلومات بنفسها، ويقدم هذا الإعداد المعلومات اللازمة لهم للقيام بذلك.

  • PATH_INFO: إن ضُبط cgi.fix_pathinfo بالقيمة “1” في ملف إعدادات PHP، فسيحوي هذا الإعداد على أية معلومات مسار إضافية additional path information مذكورة بعد اسم السكربت. عادةً ما يُستخدم هذا الإعداد لتعريف الملف الممرّر file argument والذي سيحدّد تصرُّف السكربت. قد يترتّب على ضبط cgi.fix_pathinfo بالقيمة "1" آثارٌ أمنية إن لم تكن طلبات السكربت قد فُحصت بوسائل أخرى (سنناقش ذلك لاحقًا). قد يُضبط هذا الإعداد أحيانًا بالمتغير fastcgi_path_info$ والذي يحوي على المجموعة اللاقطة لثانية من التعليمة fastcgi_split_path_info. وفي حالاتٍ أخرى، سيكون هنالك حاجةٌ لاستخدام متغير مؤقت، إذ أنه يمكن لمعالجةٍ أخرى أن تمسح تلك القيمة أحيانًا.

  • PATH_TRANSLATED: يربط هذا الإعداد بين معلومات المسار المحتواة ضمن PATH_INFO والمسار الفعلي على نظام الملفات filesysem path. عادةً ما يُضبط هذا الإعداد بـ document_root$fastcgi_path_info$، ولكن أحيانًا يجب استبدال المتغير الأخير بالمتغير المؤقت الذي أشرنا إليه سابقًا.

التحقق من الطلبات قبل تمريرها إلى FastCGI    

إنّ أحد المواضيع بالغة الأهمية التي لم نغطها حتى الآن هو كيفية تمرير الطلبات الديناميكية للخادوم بشكلٍ آمن. إن تمرير كلّ الطلبات للخادوم الخلفي بغض النظر عن صحتها لا يسبب خفض فاعلية الخادوم فحسب، بل إنه خَطِرٌ أيضًا. إذ أنه من الممكن للمهاجمين إنشاء طلباتٍ خبيثة في محاولةٍ منهم لتشغيل نص برمجي ما. في الواقع، حتى نعالج هذا الخطأ علينا أن نضمن إرسال الطلبات الصالحة فقط لمعالج FastCGI، يمكننا القيام بذلك بعدة طرق وذلك تبعًا لاحتياجات إعدادنا الخاص وفيما إن كان معالج FastCGI يعمل على نفس النظام المشغِّل لـ Nginx أم لا.  

إحدى القواعد الأساسية التي يجب أن تحدد لنا كيفية ضبط الإعدادات، هي أنه علينا ألا نسمح بأي معالجة أو تفسير لملفات المستخدم. إذ أنه من السهل نسبيًا على المستخدمين المخادعين malicious users أن يضمّنوا نصًا برمجيًا صالحًا valid code في ملفاتٍ تبدو في الظاهر أنها ملفاتٍ غيرُ مؤذيةٍ كالصور. حالما يُرفع ملفٌ كهذا إلى الخادوم علينا أن نضمن ألا يجد طريقًا للوصول إلى معالج FastCGI.

إنّ المشكلة الأساسية التي نحاول حلها هنا هي في الواقع محدّدةٌ في مواصفات CGI specification. تتيح لك تلك المواصفات تحديد ملف السكربت الذي ترغب بتشغيله متبوعًا بمعلومات المسار الإضافية التي يمكن للسكربت أن يستخدمها. إن نموذج التنفيذ هذا يسمح للمستخدمين بطلب معرف URI قد يبدو كسكربت صالح legitimate، إلا أن الجزء الذي سيتم تنفيذه سيكون مذكورًا قبله في المسار.

فالنأخذ مثلًا طلبًا لـ: test.jpg/index.php/ إن كانت إعداداتك تمرّر ببساطة كل الطلبات التي تنتهي بـ .php إلى المعالج دون التحقق من مدى صحتها فسيتحقق المعالج، إن كان يحقّق المواصفات، من وجود ذلك الموقع وسينفذّه إن أمكن. أما إذا لم يجد الملف فسيتّبع المواصفات وسيحاول تنفيذ الملف test.jpg/ وسيعتبر أن index.php/ تمثّل معلومات المسار الإضافية الخاصة بالسكربت. كما ترى يمكن لذلك أن يسمح ببعض النتائج غير المرغوب بها إطلاقًا عندما يجتمع مع فكرة رفع المستخدم للملفات.

هنالك مجموعة من الطرق المختلفة لحل تلك المشكلة. أسهل تلك الطرق هو ببساطة تعطيل خاصية معالجة معلومات المسار من قِبَل المعالج، وذلك إن كان تطبيقك لا يعتمد على تلك المعلومات الإضافية. يمكن تعطيل تلك الخاصية لدى PHP-FPM من خلال الملف php.ini، فمثلًا إن كنت تستخدم نظام أوبونتو Ubuntu فيمكن تحرير الملف التالي:

sudo nano /etc/php5/fpm/php.ini

ببساطة، ابحث عن الخيار cgi.fix_pathinfo وألغِ تعليقه ثم اضبطه بالقيمة “0” لتعطّل هذه “الميزة” كما يلي:

cgi.fix_pathinfo=0

أعد تشغيل عملية PHP-FPM حتى تأخذ التعديلات حيّز التنفيذ:

sudo service php5-fpm restart

سيدفع ذلك PHP لتنفيذ الجزء الأخير من المسار فقط، وبالتالي ففي المثال السابق إن لم يكن الملف test.jpg/index.php/ موجودًا فسيصدر PHP خطأ عوضًا عن محاولة تنفيذ الملف test.jpg/ إذا كان معالج FastCGI يعمل على نفس الخادوم الذي يُشغل Nginx فلدينا خيارٌ آخر وهو ببساطةٍ التحقق من وجود الملفات على القرص قبل تمريرها إلى المعالج. فإذا لم يكن الملف التالي موجودًا test.jpg/index.php/ فسيصدر خطأ. أما إن كان موجودًا فسيُرسل إلى الخادوم الخلفي لتتم معالجته. سينتج عن ذلك عمليًا سلوكٌ يشبه إلى حد بعيد ما رأيناه سابقًا.

location ~ \.php$ {
       try_files $uri =404;
       . . .
}

إن كان تطبيقك يعتمد في سلوكه على معلومات المسار حتى يقوم بعملية التفسير بشكل صحيح فما زال بإمكانك السماح بتطبيق ذلك السلوك بأمان عبر القيام بالاختبارات قبل اتخاذ قرارٍ بإرسال الطلب إلى الخادوم الخلفي. فمثلًا، بإمكاننا مطابقة المجلدات التي نخرن فيها الملفات المرفوعة غير الموثوقة تحديدًا وضمان ألا تمرّر إلى المعالج. مثلًا، إن كان مجلد الملفات المرفوعة على المسار: /uploads/ فعلينا إنشاء مقطع موقع يتم مطابقته قبل أن يتم تقييم أي عبارة منتظمة:

location ^~ /uploads {

}

بإمكاننا إضافة أي نوع من معالجة ملفات PHP داخل المقطع البرمجي:

location ^~ /uploads {
   location ~* \.php$ { return 403; }
}

سيعمل الموقع الأم parent location على مطابقة أي طلب يبدأ بـ uploads/ وأيُّ طلب يتعامل مع ملفات PHP سيُرد عليه بالخطأ 403 عوضًا عن إرساله إلى الخادوم الخلفي.

يمكنك أيضًا استخدام التعليمة fastcgi_splite_path_info للتعريف يدويًا عن الجزء من الطلب، الذي يجب تفسيره كسكربت والجزء الدي يجب تعريفه على أنه معلومات المسار الإضافية باستخدام التعابير النمطية. يسمح لك ذلك بأن تستمر في الاعتماد على وظيفة معلومات المسار، على أن تعرّف بدقة ما تعتبره بريمجًا وما تعتبره مسارًا.

فمثلًا، بإمكاننا إعداد مقطع لموقع يقوم باعتبار أن أول جزء مكون للمسار ينتهي ب .php هو السكربت الذي ينبغي تشغيله. ويعتبر الباقي معلومات المسار الإضافية. وذلك يعني أنه من أجل الطلب: test.jpg/index.php/ فسيتم إرسال كامل المسار إلى المعالج باعتباره اسم السكربت ولا يحوي معلومات مسار إضافية.

يمكن أن يكون الموقع كالآتي:

location ~ [^/]\.php(/|$) {

   fastcgi_split_path_info ^(.+?\.php)(.*)$;
   set $orig_path $fastcgi_path_info;

   try_files $fastcgi_script_name =404;

   fastcgi_pass unix:/var/run/php5-fpm.sock;
   fastcgi_index index.php;
   include fastcgi_params;

   fastcgi_param SCRIPT_FILENAME $request_filename;
   fastcgi_param PATH_INFO $orig_path;
   fastcgi_param PATH_TRANSLATED $document_root$orig_path;
}

ينبغي أن يعمل المقطع السابق عندما تكون قيمة cgi.fix_pathinfo في إعداد PHP مضبوطةً بالقيمة "1" حتى تسمح بمعلومات المسار الإضافية. وهنا، إن مقطع الموقع السابق لا يطابق الطلبات التي تنتهي بـ .php وحسب، بل تلك التي تنتهي بـ .php قبل إشارة "/" مباشرةً والتي تدل على مكون إضافي يتبع مسار المجلد directory.

تعرّف التعليمة fastcgi_split_path_info داخل المقطع مجموعتين لاقطتين captured group باستخدام التعابير النمطية. تطابق المجموعة الأولى الجزء الممتد من بداية عنوان URI وحتى أول ورود لـ .php ويضعه في المتغير fastcgi_script_name$. ومن ثم يضع أي معلومة مذكورة في الجزء الباقي، والممتد بدءًا من النقطة التي انتهى عندها الجزء الأول وحتى نهاية العنوان، في المجموعة اللاقطة الثانية والتي تخزّن في متغير يدعى fastcgi_path_info$.

نستخدم التعليمة set لنخزن القيمة المحفوظة في fastcgi_path_info$، حتى هذه النقطة، في متغير يدعى orig_path$. وذلك لأن قيمة المتغير fastcgi_path_info$ ستُمحى في لحظة عبر التعليمة try_files.

بإمكاننا التحقق من اسم السكربت الذي التقطناه سابقًا باستخدام التعليمة try_files. وهي عبارة عن عملية تطبّق على ملف file operation تتأكد من أنّ السكربت الذي نحاول تشغيله مخزن على القرص. ولكن لذلك تأثيرًا جانبيًا يتمثل في مسح قيمة المتحول fastcgi_path_info$.

بعد القيام بالتمرير التقليدي عبر FastCGI فإننا نضبط SCRIPT_FILENAME كما العادة. نضبط أيضًا الإعداد PATH_INFO بالقيمة التي المخزّنة في المتغير orig_path$. فبالرغم من أن قيمة fastcgi_path_info$ قد مُسحت، إلا أن قيمته الأصلية محفوظة في ذلك المتغير. كما أننا نضبط الوسيط PATH_TRANSLATED ليربط map بين معلومات المسار الإضافية والموقع الذي تُحفظ فيه على القرص، نقوم بذلك عبر دمج قيمتي المتغيرين document_root$ و orig_path$.

يسمح لنا ذلك ببناء طلباتٍ مثل: index.php/users/view/ بحيث يتمكن الملف: index.php/ من معالجة المعلومات المتعلقة بالمجلد: users/view/ وبنفس الوقت نتجنب المواقف التي سيُشغّل بها الطلب: test.jpg/index.php/ سيُضبط البريمج دائمًا بأقصر مكوّن ينتهي بـ .php، وبالتالي سنتجنب تلك المشكلة.

يمكننا أيضًا تحقيق ذلك باستخدام تعليمة alias إن احتجنا إلى تغيير موقع ملفات السكربتات. علينا فقط أن نأخذ ذلك بالحسبان في كل من ترويسة الموقع وتعريف fastcgi_split_path_info.

location ~ /test/.+[^/]\.php(/|$) {

   alias /var/www/html;

   fastcgi_split_path_info ^/test(.+?\.php)(.*)$;
   set $orig_path $fastcgi_path_info;

   try_files $fastcgi_script_name =404;

   fastcgi_pass unix:/var/run/php5-fpm.sock;
   fastcgi_index index.php;
   include fastcgi_params;

   fastcgi_param SCRIPT_FILENAME $request_filename;
   fastcgi_param PATH_INFO $orig_path;
   fastcgi_param PATH_TRANSLATED $document_root$orig_path;
}

سوف يسمح لك ذلك لتشغيل تطبيقاتك التي تستخدم الإعداد PATH_INFO بأمان. تذكّر أنه عليك تعديل الخيار cgi.fix_pathinfo في ملف php.ini وضبطه بالقيمة "1" حتى يعمل بشكل صحيح. قد يترتب عليك أيضًا أن تعطّل security.limit_extensions في الملف php-fpm.conf.

الخلاصة

نأمل أنك تملك الآن فهمًا أفضل لإمكانية قيام FastCGI بالوساطة عنNginx. تسمح هذه الإمكانية لـ Nginx باستخدام قوته في معالجة الاتصالات بسرعة وتحضير المحتوى الثابت static content، بينما تُحمّل مسؤولية المحتوى الديناميكي dynamic content لبرمجيات مناسبة لذلك. يسمح FastCGI لـ Nginx بالعمل مع عدد كبير من التطبيقات، عبر إعدادات آمنة وذات أداء.

ترجمة – وبتصرّف– للمقال Understanding and Implementing FastCGI Proxying in Nginx لمؤلفه Justin Ellingwood.





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن