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 ที่ระบุ การเรียกนี้แสดงค่าพร็อกซี verify และ cert ที่รวมแล้วสำหรับคำขอนั้นโดยไม่ส่งแพ็กเก็ต
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 กับ URL จริง ไม่ใช่ชื่อโฮสต์ที่พิมพ์จากความจำ
แยกสองชั้นออกจากกันในหัว การข้ามพร็อกซี HTTP ไม่ได้ข้าม VPN เพราะการตัดสินใจเรื่องพร็อกซีเกิดในไลบรารี และการตัดสินใจเรื่องอุโมงค์เกิดในตารางเส้นทางของเคอร์เนล
ปิด trust_env เฉพาะในฐานะนโยบายเซสชันที่ตั้งใจ
หากทั้งงานต้องเพิกเฉยต่อการกำหนดค่าสภาพแวดล้อม ให้สร้างเซสชันอย่างชัดเจนและบันทึกการตัดสินใจในโค้ด นั่นคือ นโยบาย ไม่ใช่ทางลัด และควรอยู่ใน diff ที่ผู้ตรวจสอบมองเห็น ตัวอย่างด้านล่างคงเวลา timeout พาธ 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= ระดับยูนิตแทนการส่งออกที่อยู่ในประวัติเทอร์มินัลของคุณเท่านั้น การเปลี่ยนแปลงทั้งสองมองเห็นได้ในสภาพแวดล้อมของโปรเซส ดังนั้นการรีสตาร์ทมักทำให้คุณกลับไปสู่พฤติกรรมเดิม
ปิดท้ายด้วยการยืนยันสี่สิ่ง: การตั้งค่าที่รวมแล้วสำหรับ URL ส่วนตัวไม่แสดงพร็อกซี ใบรับรองตรวจสอบกับ CA ที่ตั้งใจ คำขอคืนสถานะที่คุณคาดหวัง และการเปลี่ยนแปลงถูก commit ไว้ในที่ที่ทำซ้ำได้ สคริปต์สีเขียวที่มีสภาพแวดล้อมอธิบายไม่ได้คือการแก้ไขที่จะพังอีกในเครื่องถัดไป คำสั่งในคู่มือนี้คือสิ่งที่ต้องวางในตั๋ว