خطأ ProxyError في Requests على واجهة API خاصة؟ افحص البيئة أولًا
يدمج Requests إعدادات الوكيل والمصادقة وCA من البيئة. اقرأ ما تراه العملية، واستبعد المضيف الخاص، وأبقِ تحقق TLS مفعّلًا.
VPN قائم، ولوحة المعلومات الداخلية تُفتح في المتصفح، ومع ذلك يرفع سكربت Python خطأ وكيل. لا شيء خطأ في النفق في تلك الصورة: المفسّر يقرأ عنوان وكيل من بيئته الخاصة ويرسل الطلب إليه. يقرأ هذا الدليل البيئة التي تراها العملية فعليًا، ويطبّق أضيق استبعاد، ثم لا ينظر في سياسة جلسة صريحة إلا بعد ذلك. كل خطوة تنتهي بأمر يحدد ناتجه ما إذا كنت ستواصل.
اقرأ البيئة التي تراها العملية فعليًا
إعدادات الوكيل في سطح المكتب وتصديرات الصدفة وأسطر Environment= في وحدة systemd ثلاثة مصادر مختلفة، وRequests لا يقرأ إلا آخر مصدر يرثه. شغّل التشخيص أدناه في الصدفة أو الحاوية أو الوحدة نفسها التي تشغّل السكربت الفاشل، لا في طرفيتك التفاعلية. المتغير المصدَّر في صدفة تسجيل الدخول غير مرئي لعملية في حاوية، وهذه الفجوة تفسر كثيرًا من التقارير عن وكيل يظهر من العدم. الأمران يطبعان البيانات نفسها بمستويين مختلفين من التفصيل.
env | grep -i -E 'proxy|requests_ca_bundle|curl_ca_bundle'
python3 -c "import os;[print(k,'=',v) for k,v in sorted(os.environ.items()) if 'proxy' in k.lower()]"
تُقرأ كلتا كتابتي كل متغير، ويُطبَّق كل مدخل مكرر بالترتيب الذي وصل به إلى العملية، لذا يمكن لـ HTTP_PROXY قديم أن يتجاوز القيمة التي صدّرتها للتو. متغيرات حزمة CA تهم هنا أيضًا، لأنها تغيّر ما يُحلّ إليه verify من دون أن تظهر في أي مكان في الكود. إذا كان الناتج فارغًا، فالبيئة ليست مشكلتك ويمكنك التوقف عن قراءة هذا القسم. وإذا سرد وكيلًا لا تعرفه، فاعثر على الملف أو الوحدة التي تضبطه قبل أن تغيّر أي شيء.
اسأل Requests عمّا سيفعله بعنوان URL محدد. يعرض هذا النداء قيم الوكيل والتحقق والشهادة المدمجة لذلك الطلب من دون إرسال أي حزمة.
python3 -c "import requests;s=requests.Session();print(s.merge_environment_settings('https://api.corp.example/status',{},None,None,None))"
مدخل proxies غير الفارغ للمضيف الخاص هو جوابك، وقيمة verify المطبوعة تخبرك بحزمة CA التي سيثق بها الطلب. إذا بدا الاثنان صحيحين، فالفشل في وقت لاحق من المكدس وعليك الانتقال إلى قسم التحقق. لا تخمّن من الكود: الدمج يحدث لكل طلب، ولهذا يمكن أن تتصرف جلسة واحدة بشكل مختلف مع مضيفين.
استبعد المضيف الخاص قبل أن تعطّل أي شيء
حين يكون الوكيل مطلوبًا فعلًا لوجهات أخرى، فالإصلاح الضيق هو استبعاد لهذا المضيف. أضف إلى القائمة القائمة بدل استبدالها، لأن محو NO_PROXY يكسر كل وجهة داخلية أخرى كانت تعتمد عليه. الأوامر أدناه تضيف المدخل ثم تسأل Requests هل ينطبق التجاوز.
export NO_PROXY="${NO_PROXY:+$NO_PROXY,}api.corp.example,.corp.example"
python3 -c "import requests;print(requests.utils.should_bypass_proxies('https://api.corp.example/status',None))"
المطابقة بحسب لاحقة المضيف، لذا يغطي .corp.example النطاق كله بينما يغطي api.corp.example اسمًا واحدًا. النجمة المجردة تعطّل الوكيل لكل مضيف ولا تكاد تكون ما تريده مهمة إنتاجية. يعتمد احترام مدخل CIDR على إصداري Requests وurllib3 في الصورة، لذا اختبر المدخل الدقيق بدل افتراض أنه يعمل. تحقق من نتيجة should_bypass_proxies للعنوان الحقيقي، لا لاسم مضيف كتبته من الذاكرة.
أبقِ الطبقتين منفصلتين في ذهنك. تجاوز وكيل HTTP لا يتجاوز VPN، لأن قرار الوكيل يحدث في المكتبة وقرار النفق يحدث في جدول توجيه النواة.
لا تعطّل trust_env إلا كسياسة جلسة متعمدة
إذا كان على المهمة كلها تجاهل الإعدادات المحيطة، فابنِ الجلسة صراحة وسجّل القرار في الكود. هذه سياسة لا حل بديل، وهي تنتمي إلى diff يراه المراجع. يحتفظ المثال أدناه بمهلة ومسار CA صريح وفحص للحالة.
import requests
with requests.Session() as session:
session.trust_env = False
response = session.get(
"https://api.corp.example/status",
timeout=(5, 20),
verify="/etc/ssl/certs/corp-ca.pem",
)
response.raise_for_status()
إيقاف trust_env يغيّر ثلاثة سلوكيات دفعة واحدة، وواحد منها فقط يتعلق بالوكيل. يتوقف Requests عن قراءة بيانات اعتماد .netrc، ويتوقف المتغيران REQUESTS_CA_BUNDLE و CURL_CA_BUNDLE عن التأثير في verify، الذي يعود عندها إلى مخزن الشهادات المضمّن. إذا كانت مؤسستك تنهي TLS بشهادة CA خاصة بها، فإن هذا التراجع يرفع خطأ تحقق يبدو غير متعلق بالتغيير الذي أجريته للتو. تمرير verify صراحة هو ما يبقي الأثرين منفصلين.
ما يجب ألا تفعله هو ضبط verify=False لإسكات الخطأ الناتج. فذلك يقايض إعداد وكيل خاطئ باتصال غير محقق، ويخفي مشكلة الشهادة التي كان الاستبعاد يهدف إلى كشفها.
تحقق من الطبقة الفاشلة واحتفظ بإمكانية التراجع
ثلاثة أعطال تبدو متشابهة في السجل ولا تشترك في شيء: وكيل لا يستطيع الوصول إلى الوجهة يرفع ProxyError أو يعيد 502، ومشكلة ثقة ترفع SSLError، ورفض تطبيقي يعيد 401 أو 403. أعد إنتاج الطلب خارج Requests لتعرف أيها لديك. الأوامر أدناه تزيل الوكيل، وتختبر سلسلة الشهادات، ثم تعيد النداء عبر المكتبة.
curl -sv --noproxy '*' https://api.corp.example/status -o /dev/null
openssl s_client -connect api.corp.example:443 -servername api.corp.example </dev/null
python3 -c "import requests;r=requests.get('https://api.corp.example/status',timeout=10);print(r.status_code)"
إذا نجح نداء curl وفشل نداء Python، فالبيئة لا تزال الفرق. وإذا فشل الاثنان في مرحلة TLS، فالوكيل لم يكن المشكلة قط والسلسلة هي المشكلة. وإذا أعاد الاثنان 401، فقد وصل الطلب إلى التطبيق والعمل المتبقي هو المصادقة. تسجيل أي الثلاثة لاحظته يوفر على الشخص التالي ساعة.
التراجع سطر واحد في أي اتجاه: أزل الاستبعاد، أو أعد trust_env إلى حاله. احفظ قيمة NO_PROXY السابقة قبل أن تضيف إليها، وفضّل سكربت غلاف أو مدخل Environment= على مستوى الوحدة على تصدير يعيش في تاريخ طرفيتك فقط. التغييران مرئيان في بيئة العملية، لذا يعيدك إعادة التشغيل عادةً إلى السلوك القديم.
اختم بتأكيد أربعة أمور: الإعدادات المدمجة للعنوان الخاص لا تُظهر وكيلًا، والشهادة تتحقق مقابل CA المقصود، والطلب يعيد الحالة التي تتوقعها، والتغيير مُثبَّت في مكان قابل للتكرار. سكربت أخضر مع بيئة غير مفسّرة إصلاح ينكسر مجددًا على الجهاز التالي. الأوامر في هذا الدليل هي التي تلصقها في التذكرة.