docker/nginx/default.conf:13 كان فيه:
access_log off; # أثناء اختبار الضغط؛ فعّله عند الحاجة
مُطفأً من اختبار ضغط قديم ونُسي.
أثره أن أي بحث في سجلّ الوصول يرجع صفراً دائماً:
docker compose logs nginx | grep -c "send_fcm" → 0
فيبدو الاستنتاج أن التطبيق لا يستدعي المسار إطلاقاً، والحقيقة أن السجلّ
فارغ لا أن الطلبات غائبة. استنتاج خاطئ تماماً بُني على قياس معطّل.
مع توجيه error_log في www-pool.conf (الالتزام e106d2df) تكتمل الرؤية:
nginx يقول هل وصل الطلب، وPHP يقول ماذا ردّ Google.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
www-pool.conf كان يوجّه slowlog إلى /proc/self/fd/2 لكن لا شيء لـ error_log،
فكل رسائل error_log() في الباك إند تُبتلع ولا يظهر أي سطر في:
docker compose logs php
والباك إند مليء بها ويعتمد عليها في التشخيص: [accept_ride] و [FCM_DEBUG]
(يطبع حمولة FCM ورد Google كاملاً) و 📲 [FCM_RESULT] و [SOCKET_DEBUG] و
[SSRF_BLOCKED] و [SOCKET_BLOCKED].
النتيجة العملية أن سبب فشل الإشعارات كان يمكن قراءته في سطر واحد من رد
Google (UNREGISTERED / INVALID_ARGUMENT / 403) بدل الاستنتاج من الكود.
catch_workers_output=yes ينقل stdout/stderr للعمّال، و decorate_workers_output=no
يمنع بادئات الضجيج، وphp_admin_value[error_log] يثبّت المسار على fd/2.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nginx على السيرفر يرفض التحميل:
nginx: [emerg] unknown directive "http2" in siro-sockets-tls.conf:30
التوجيه http2 on; لم يُضَف إلا في nginx 1.25.1. والحذف هو الصحيح لا مجرد
حلٍّ سريع: ترقية WebSocket تعتمد على ترويسة Upgrade في HTTP/1.1 ولا معنى
لها في HTTP/2 أصلاً (وproxy_http_version 1.1 مضبوط في الـ location).
تحذير تشغيلي: بعد up --force-recreate انتقلت الحاويتان إلى 127.0.0.1:12020
و13030، فإن فشل nginx -t يبقى المنفذان 2020/3030 بلا أي مستمع (Connection
refused) حتى ينجح reload. رتّب النشر: صحّح الإعداد ⇒ nginx -t ⇒ reload.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
السبب (مثبَّت على الإنتاج): التزام c35b350b بتاريخ 2026-07-23 — وهو التزام
تلقائي برسالة "Update: <time>" — أضاف المنفذ للروابط:
- 'https://jordan-siro.intaleqapp.com'
+ 'https://jordan-siro.intaleqapp.com:2020'
وكانت القيمة العاملة قبل ذلك بلا منفذ (443) فيُنهي البروكسي TLS ويمرّر.
بإضافة :2020 صار التطبيق يضرب حاوية Workerman مباشرة، وهي تفتح المنفذ نصاً
صريحاً (new SocketIO(2020) بلا سياق SSL)، فمصافحة TLS تتجمّد حتى المهلة:
❌ Socket Connect Error: timeout
❌ Socket Connect Timeout: 20000
ونتيجته أن السائق لا يبعث update_location إطلاقاً ⇒ لا GPS في Redis ⇒ لا
موقع للراكب لا عبر السوكيت ولا عبر الـ polling (كلاهما يقرأ من نفس المصدر).
مثبَّت بالأدلة على السيرفر:
- ss: المنفذان 2020/3030 يملكهما docker-proxy (نص صريح)، و443 nginx.
- curl على 127.0.0.1:2020 يرد {"sid":...,"upgrades":["websocket"]} — أي أن
الحاوية سليمة تماماً والناقص هو طبقة TLS وحدها.
الحل بلا أي بناء للتطبيقات (وبلا تنزيل مستوى الأمان — الـ JWT يُرسَل في
query string فلا يجوز إطلاقاً تحويله إلى http):
- الحاويتان تُنشران على 127.0.0.1:12020 و 127.0.0.1:13030 فقط.
- nginx على المضيف يستمع على 2020/3030 بشهادة الدومين ويمرّر إليهما مع
ترقية WebSocket (nginx/siro-sockets-tls.conf).
- proxy_read/send_timeout 3600s: اتصال السائق يعيش ساعات، وبلا ذلك يقطعه
nginx كل 60ث فتدخل الحاوية حلقة إعادة اتصال دائمة.
- proxy_buffering off و access_log off (نبضات GPS تُغرق القرص).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
سوكيت الراكب (سبب عدم ظهور معلومات السائق عند القبول):
- تطبيق الراكب كان يرسل id فقط بلا jwt، و passenger_socket.php يرفض أي
اتصال بلا jwt ⇒ الراكب لا ينضم لغرفته أبداً ولا يستلم ride_status_change
ولا driver_location_update. تظهر حالة القبول عبر الـ polling فقط بينما
driver_info يصل بالسوكيت وحده. (سوكيت السائق يعتبر الـ jwt اختيارياً،
ومن هنا جاء التباين بين التطبيقين.)
- cancelled_by_driver كان يسقط من switch حالات الراكب فيبقى معلّقاً بعد
إلغاء السائق.
- حماية socket (late) من القراءة قبل التهيئة عند الانسحاب بلا jwt.
الإشعارات والرسائل (سبب "مرات توصل ومرات لا"):
- جدول tokens يخزّن توكن الراكب مشفّراً، و getRideWaiting.php كان يرجعه
بلا فك تشفير ⇒ من يقبل من قائمة السوق يحمل blob مشفّراً يستخدمه كـ FCM
target فيرفضه FCM بـ 400: لا إشعار قبول ولا رسائل. ومن يقبل من الـ
dispatch/FCM يحمل نصاً صريحاً فتعمل. الفرق كان في طريقة القبول.
- acceptRide.php يحلّ التوكن من القاعدة دائماً ولا يثق بالعميل (أصحّ أمنياً).
- market_new_ride كان لا يحمل passengerId ولا الإحداثيات فتصل "null"؛
أُضيفت بلا أي PII لأن الحمولة تُبَثّ لكل سائق قريب لا للفائز فقط.
- send_fcm.php: مهلة على OAuth (كان يعلّق حتى مهلة PHP فتُسقط الرسالة
بصمت)، توحيد ding→default لأندرويد، وحقن title/body/tone في data
مطابقةً لـ FcmService.
- تطبيق السائق يقرأ title/body من data أولاً مثل الراكب، ولا يعرض فقاعة
فارغة للرسائل الصامتة.
السوكيت والإعدادات:
- forwardLocationToPassengerSocket كان يقرأ lat/lng والحمولة فيها
latitude/longitude ⇒ المسافة تخرج ضخمة والـ throttle معطّل تماماً
فيُعاد التوجيه مع كل نبضة GPS.
- notifyPassengerOnRideServer كان يرجع null بصمت مطلق عند حجب العنوان.
- العنوان الافتراضي لسيرفر الموقع كان nginx/loction_server/driver_socket.php
وهو ديمون Workerman لا يُخدَم عبر nginx ⇒ صار socket_driver:2021.
(LOCATION_API_URL بقي على nginx لأن api_get_nearby.php سكربت عادي.)
- ride_server/passenger_socket.php (النسخة التي يشغّلها Docker) كانت ناقصة
كل كود مواصلاتي الموجود في passenger_server/ ⇒ نُقل مع REDIS_HOST.
- .env.example: ALLOWED_SOCKET_URLS يغطّي أسماء حاويات Docker، وإضافة
PASSENGER_SOCKET_INTERNAL_URL.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>