Lỗi proxy Ruby: đọc exception Net::HTTP, Faraday và cách sửa
Lỗi proxy Ruby thường hiện ra dưới dạng một exception ngắn, nhìn tên lớp lỗi khó biết hỏng ở proxy, ở mật khẩu hay ở website. ProxyVN dựng lại 34 tình huống trên Ruby 3.4.12 với Net::HTTP và Faraday qua một proxy có mật khẩu, ghi đúng thông báo nhận được, thời gian chờ và số kết nối proxy thật sự nhận. Bài dành cho anh em đã gắn proxy nhưng script vẫn lỗi.
Lỗi proxy Ruby thường gặp và bảng tra nhanh
Phần lớn lỗi proxy trong Ruby rơi vào sáu nhóm: không tới được proxy, sai tài khoản proxy, mật khẩu có ký tự đặc biệt, biến môi trường bị bỏ qua, lỗi chứng chỉ và retry ngầm. Bảng dưới gom kết quả chạy thật để anh em tra từ thông báo lỗi ngược về nguyên nhân.
Bảng tra từ thông báo lỗi
Thông báo nhận được |
Thư viện |
Nguyên nhân |
|---|---|---|
Errno::ECONNREFUSED: Failed to open TCP connection to ...:18099 |
Net::HTTP |
Sai cổng proxy hoặc proxy đã tắt |
Faraday::ConnectionFailed: Failed to open TCP connection |
Faraday |
Cùng nguyên nhân, Faraday bọc lại |
Net::HTTPClientException: 407 "Proxy Authentication Required" |
Net::HTTP, đích HTTPS |
Sai user hoặc mật khẩu proxy |
Faraday::ConnectionFailed: 407 "Proxy Authentication Required" |
Faraday, đích HTTPS |
Sai user hoặc mật khẩu proxy |
Trả về HTTP 407, không có exception |
Net::HTTP và Faraday, đích HTTP |
Sai mật khẩu nhưng code chỉ đọc response |
URI::InvalidURIError: bad URI (is not URI?) |
Cả hai |
Mật khẩu có @ hoặc : chưa mã hóa |
Socket::ResolutionError ... (getaddrinfo: No such host is known.) |
Net::HTTP |
Sai tên máy chủ proxy |
Net::ReadTimeout with #<TCPSocket:(closed)> |
Net::HTTP |
Proxy nhận kết nối nhưng không trả lời |
Faraday::TimeoutError: Net::ReadTimeout |
Faraday |
Cùng nguyên nhân |
SSL_connect returned=1 ... certificate verify failed |
Cả hai |
Chứng chỉ website hết hạn hoặc sai tên miền |
Có bốn lỗi không hiện ra thành exception mà chỉ làm request đi sai đường. Đó là https_proxy bị Net::HTTP bỏ qua, http_proxy bị Faraday bỏ qua khi đích là HTTPS, no_proxy khớp nhầm, và tiền tố socks5 trong biến môi trường. Bốn lỗi này nguy hiểm hơn, vì script vẫn chạy và trả 200.
Ba câu hỏi trước khi sửa
Trước khi đổi code, mình trả lời ba câu. Request có tới được proxy không. Proxy có nhận tài khoản không. Proxy có đi tiếp được tới website không. Mỗi câu ứng với một giai đoạn khác nhau trong kết nối, và tên lớp lỗi cho biết request dừng ở giai đoạn nào.
Errno và ConnectionFailed với chữ "Failed to open TCP connection" nghĩa là chưa tới được proxy. Mã 407 nghĩa là đã tới proxy nhưng bị từ chối tài khoản. Lỗi SSL nghĩa là proxy đã mở đường hầm, request đã tới website, và vấn đề nằm ở chứng chỉ của website. Đọc đúng giai đoạn thì sửa đúng chỗ, khỏi đổi qua đổi lại proxy vô ích.
Nếu cần nền tảng cấu hình proxy cho từng thư viện Ruby trước, anh em đọc bài Ruby proxy của ProxyVN. Bài này đi thẳng vào phần lỗi.
Môi trường tái hiện lỗi proxy Ruby
Mọi thông báo trong bài lấy từ lần chạy ngày 26/10/2026 trên Windows 11. Phiên bản: Ruby 3.4.12 cài bằng RubyInstaller, net-http 0.6.0, OpenSSL 3.6.5, faraday 2.15.0, faraday-net_http 3.5.0 và faraday-retry 2.4.0.
Proxy thử nghiệm và cách đếm request
Proxy thử nghiệm là một script Python nhỏ chạy trên máy, cổng 18080, yêu cầu tài khoản u1 với mật khẩu p@ss:w0rd. Mật khẩu cố ý có dấu @ và dấu hai chấm, hai ký tự gây lỗi nhiều nhất. Cổng 18081 nhận kết nối rồi im lặng, dùng để tái hiện proxy treo. Cổng 18099 không có gì chạy, dùng để tái hiện sai cổng.
Proxy ghi mọi request vào nhật ký, kèm trạng thái tài khoản là đúng, sai hay không gửi. Nhờ nhật ký này mình đếm được số kết nối thật mà Ruby mở, thay vì chỉ nhìn kết quả cuối. Đây là cách phát hiện retry ngầm và các request đi thẳng không qua proxy.
Vì sao không dựa vào IP trả về
Proxy chạy ngay trên máy nên IP ra ngoài của request qua proxy và request đi thẳng là một. Gọi api.ipify.org trong hai trường hợp đều trả cùng một địa chỉ. Ở đây mình dựa vào nhật ký proxy: có dòng CONNECT tức là đã qua proxy, không có dòng nào tức là đi thẳng.
Với proxy thật của nhà cung cấp thì so IP là đủ, vì IP proxy khác IP mạng nhà. Nhưng cách đếm ở phía proxy vẫn có ích khi anh em nghi ngờ thư viện tự thử lại hoặc tự chọn đường. Nhiều nhà cung cấp có trang thống kê số request theo thời gian, xem ở đó cũng được.
Lỗi proxy Ruby khi không tới được proxy
Nhóm lỗi đầu tiên xảy ra trước khi proxy kịp đọc tài khoản. Nguyên nhân thường là gõ sai cổng, proxy đã hết hạn, tường lửa chặn chiều ra, hoặc proxy quá tải không trả lời.
ECONNREFUSED khi sai cổng hoặc proxy đã tắt
Gọi qua cổng 18099 không có gì chạy, Net::HTTP báo Errno::ECONNREFUSED kèm dòng "Failed to open TCP connection to 127.0.0.1:18099" và thông báo của Windows "No connection could be made because the target machine actively refused it". Lỗi hiện sau 2,6 giây. Faraday báo cùng nội dung nhưng bọc trong Faraday::ConnectionFailed, sau 2,1 giây.
Thông báo ghi rõ địa chỉ và cổng proxy, không phải địa chỉ website. Đó là dấu hiệu chắc nhất rằng lỗi nằm ở bước nối tới proxy. Kiểm tra lại cổng trong thông tin nhà cung cấp gửi, rồi thử nối tới proxy bằng một công cụ khác như curl để loại trừ code Ruby. Bài lỗi ERR_CONNECTION_REFUSED có thêm các nguyên nhân phía mạng.
Mình hay gặp nhất là nhầm cổng HTTP với cổng SOCKS5. Nhiều nhà cung cấp cấp một proxy trên hai cổng, mỗi cổng một giao thức. Ruby chỉ nói HTTP với proxy, nên trỏ vào cổng SOCKS5 sẽ ra lỗi khác, nói ở phần biến môi trường bên dưới.
ResolutionError khi sai tên máy chủ proxy
Một số nhà cung cấp cấp proxy dưới dạng tên miền, kiểu gw.tennhacungcap.com, thay vì IP. Gõ sai một chữ trong tên đó, Net::HTTP báo Socket::ResolutionError: Failed to open TCP connection to proxy.khong-ton-tai.invalid:8080 (getaddrinfo: No such host is known.). Lỗi hiện ngay, chưa tới 0,1 giây. Faraday báo cùng nội dung trong Faraday::ConnectionFailed.
Từ khóa cần nhìn là getaddrinfo. Nó nghĩa là máy không đổi được tên proxy ra IP, request chưa rời khỏi máy. Socket::ResolutionError là lớp con của SocketError có từ Ruby 3.3, nên code cũ bắt SocketError vẫn bắt được. Kiểm tra chính tả tên proxy, rồi thử nslookup tên đó trên cùng máy. Máy trong mạng công ty đôi khi chặn DNS ra ngoài, khi đó phải dùng IP proxy thay cho tên.
Tác vụ nền không thấy cùng cấu hình
Một lỗi hay gặp không nằm ở code mà ở tiến trình. Script chạy tốt trong terminal, đưa vào dịch vụ nền hay lịch chạy định kỳ thì đi thẳng hoặc báo KeyError. Lý do là dịch vụ không kế thừa biến môi trường của phiên đăng nhập. http_proxy và PROXY_PASSWORD đặt trong terminal không tồn tại ở đó.
Mình dùng ENV.fetch thay cho ENV[] để thiếu biến thì dừng ngay với KeyError, thay vì chạy tiếp với proxy rỗng và đi thẳng. Khi dừng, ghi tên biến bị thiếu, không ghi giá trị. Cấu hình của dịch vụ nền cần khai báo riêng trong file dịch vụ hoặc kho bí mật của hệ thống triển khai.
ReadTimeout khi proxy nhận kết nối rồi im lặng
Cổng 18081 nhận kết nối TCP nhưng không gửi gì. Với đích HTTPS và read_timeout 5 giây, Net::HTTP báo Net::ReadTimeout with #<TCPSocket:(closed)> đúng sau 5,0 giây. Faraday báo Faraday::TimeoutError với cùng nội dung bên trong.
Điểm đáng chú ý là lỗi tên ReadTimeout chứ không phải OpenTimeout. Kết nối TCP tới proxy mở được ngay, nên open_timeout không có việc gì. Request dừng ở bước chờ proxy trả lời lệnh CONNECT, và bước đó tính theo read_timeout. Nhiều người tăng open_timeout lên 30 giây rồi thắc mắc sao vẫn lỗi sau 5 giây.
Proxy treo kiểu này thường là proxy quá tải, hoặc anh em nối nhầm vào một cổng không phải proxy. Chờ lâu hơn ít khi giải quyết được. Mình đặt read_timeout ngắn cho bước kiểm tra proxy, rồi chuyển sang proxy khác thay vì để worker chờ.
Lỗi proxy Ruby 407 và hai lớp xác thực
Mã 407 nghĩa là proxy đòi tài khoản hoặc từ chối tài khoản đã gửi. Lỗi này hiện ra theo hai kiểu khác nhau tùy đích là HTTP hay HTTPS, và đây là chỗ nhiều code bắt lỗi bị hụt.
Đích HTTPS: 407 thành exception
Với đích HTTPS, Net::HTTP gửi lệnh CONNECT trước để proxy mở đường hầm. Proxy trả 407 thì Net::HTTP ném ngay Net::HTTPClientException với thông báo 407 "Proxy Authentication Required". Lỗi hiện gần như tức thì, dưới 0,1 giây. Faraday ném Faraday::ConnectionFailed với cùng thông báo.
Lý do là phản hồi của lệnh CONNECT không phải phản hồi của website. Thư viện không có response nào để trả cho code, nên chỉ còn cách ném exception. Nhật ký proxy ghi đúng một dòng CONNECT với tài khoản sai, không có lần thử lại.
Đích HTTP: 407 thành response bình thường
Với đích HTTP không mã hóa, client gửi thẳng request tới proxy, không có bước CONNECT. Proxy trả 407 như một response HTTP bình thường. Net::HTTP trả response mã 407, không ném exception. Faraday cũng trả response status 407.
Code chỉ bắt exception sẽ coi đây là thành công rồi đi parse một trang trống. Mình đã gặp script ghi hàng nghìn dòng dữ liệu rỗng vì vậy. Cách sửa là luôn kiểm tra mã trạng thái. Với Faraday, thêm middleware raise_error: lần chạy thử với raise_error ném Faraday::ProxyAuthError, một lớp lỗi riêng cho 407, dễ bắt hơn hẳn.
conn = Faraday.new(proxy: { uri: 'http://gw.example:8080', user: 'u1', password: ENV.fetch('PROXY_PASSWORD') }) do |f|
f.response :raise_error
f.adapter :net_http
end
Đừng nhầm 407 với 401
407 là proxy từ chối. 401 là website từ chối. Tài khoản proxy đi trong header Proxy-Authorization, còn token hay mật khẩu của website đi trong header Authorization. Đặt tài khoản proxy vào Authorization thì proxy vẫn báo 407, còn website nhận được mật khẩu proxy, một kiểu lộ thông tin không đáng có.

Phân biệt hai lớp xác thực
Khi gặp 407, kiểm tra lần lượt: user và mật khẩu có đúng không, gói proxy còn hạn không, và nhà cung cấp có đang dùng xác thực theo IP thay vì mật khẩu không. Với xác thực theo IP, IP mạng của anh em phải nằm trong danh sách cho phép. Bài sửa lỗi 407 Proxy Authentication Required có thêm các trường hợp phía nhà cung cấp, còn bài IP whitelist hay user password so sánh hai cách xác thực.
Lỗi proxy Ruby do ký tự đặc biệt trong mật khẩu
Mật khẩu proxy do nhà cung cấp sinh ra thường có ký tự đặc biệt. Ghép thẳng vào chuỗi URI là cách nhanh nhất để gặp lỗi.
InvalidURIError khi ghép chuỗi
Đặt http_proxy thành http://u1:p@ss:w0rd@127.0.0.1:18080, Net::HTTP báo URI::InvalidURIError: bad URI (is not URI?) ngay khi khởi tạo, chưa mở kết nối nào. Truyền cùng chuỗi đó vào tham số proxy của Faraday cũng ra đúng lỗi này. Parser không biết dấu @ nào là dấu ngăn giữa tài khoản và địa chỉ.
Lỗi này còn tốt vì nó hiện ra ngay. Trường hợp tệ hơn là mật khẩu chỉ chứa dấu hai chấm hoặc dấu thăng. Chuỗi vẫn parse được nhưng ra user, mật khẩu hoặc cổng sai, và lỗi hiện ra thành 407 hay ECONNREFUSED ở một chỗ không liên quan.
Hai cách sửa đã thử
Cách thứ nhất là mã hóa riêng mật khẩu bằng URI.encode_www_form_component trước khi ghép. Với mật khẩu p@ss:w0rd, chuỗi mã hóa là p%40ss%3Aw0rd. Net::HTTP đọc biến môi trường này và giải mã đúng, nhật ký proxy ghi tài khoản hợp lệ. Faraday nhận chuỗi URI đã mã hóa cũng gửi đúng tài khoản.
Cách thứ hai, mình thích hơn, là không ghép chuỗi. Net::HTTP.new nhận host, cổng, user và mật khẩu proxy thành bốn tham số riêng. Faraday nhận một hash với uri, user và password tách nhau. Mật khẩu đi thẳng vào header, không qua parser nào. Đọc mật khẩu từ biến môi trường riêng, như PROXY_PASSWORD, để khỏi phải nghĩ tới chuyện mã hóa.
require 'net/http'
target = URI('https://api.ipify.org/')
http = Net::HTTP.new(target.hostname, target.port,
ENV.fetch('PROXY_HOST'), Integer(ENV.fetch('PROXY_PORT')),
ENV.fetch('PROXY_USER'), ENV.fetch('PROXY_PASSWORD'))
http.use_ssl = true
puts http.start { |s| s.get(target.request_uri).code }
Nhà cung cấp thường gửi proxy theo dạng ip:port:user:pass. Tách bằng split dấu hai chấm sẽ hỏng nếu mật khẩu có dấu hai chấm. Mình tách tối đa bốn phần với split(':', 4), phần cuối giữ nguyên mật khẩu. Bài định dạng proxy liệt kê các kiểu viết hay gặp.
Lỗi proxy Ruby khi đặt biến môi trường
Đây là nhóm lỗi khó thấy nhất, vì không có exception nào. Request vẫn trả 200, chỉ là không đi qua proxy. Mình chỉ phát hiện được nhờ nhật ký proxy trống trơn.
Net::HTTP chỉ đọc http_proxy
Đặt https_proxy trỏ tới proxy thử nghiệm, rồi gọi Net::HTTP.start tới api.ipify.org qua HTTPS. Kết quả trả 200, nhưng nhật ký proxy không có dòng nào: request đi thẳng. Đổi sang đặt http_proxy với cùng giá trị thì nhật ký có ngay dòng CONNECT với tài khoản đúng.
Lý do nằm trong mã nguồn net/http. Hàm proxy_uri dựng một URI dạng http từ địa chỉ đích rồi gọi find_proxy, nên luôn tra biến http_proxy, kể cả khi đích là HTTPS. Ai quen quy ước của curl, đặt https_proxy cho đích HTTPS, sẽ vấp đúng chỗ này. Theo tài liệu Net::HTTP, proxy mặc định lấy từ môi trường khi không truyền địa chỉ proxy.
Faraday thì ngược lại
Với Faraday, mình làm phép thử đối xứng. Chỉ đặt https_proxy: request tới đích HTTPS đi qua proxy, nhật ký có dòng CONNECT. Chỉ đặt http_proxy: request tới cùng đích HTTPS đi thẳng, dù đối tượng connection vẫn hiện proxy là địa chỉ trong http_proxy.
Nghĩa là cùng một máy, cùng một biến môi trường, Net::HTTP và Faraday đi hai đường khác nhau. Một ứng dụng dùng cả hai thư viện, ví dụ gem gọi API dùng Net::HTTP còn code chính dùng Faraday, sẽ có request qua proxy và request đi thẳng xen kẽ. Cách an toàn là đặt cả http_proxy lẫn https_proxy cùng giá trị, hoặc truyền proxy tường minh trong code.

Net HTTP và Faraday
Tiền tố socks5 bị bỏ qua
Đặt http_proxy thành socks5://127.0.0.1:18080, Net::HTTP không báo lỗi gì về giao thức. Nó bỏ qua phần socks5 và gửi lệnh CONNECT kiểu HTTP tới địa chỉ đó. Proxy thử nghiệm là proxy HTTP nên trả 407 vì không có tài khoản. Với một proxy SOCKS5 thật, lệnh HTTP đó sẽ làm kết nối bị đóng hoặc treo.
Net::HTTP không hỗ trợ SOCKS. Khi nhà cung cấp cấp cả hai giao thức, dùng cổng HTTP cho Ruby. Bài proxy HTTP và SOCKS5 giải thích khác biệt giữa hai giao thức.
no_proxy và cách tắt proxy có chủ đích
Đặt no_proxy thành ipify.org hoặc .ipify.org, Net::HTTP coi api.ipify.org khớp và đi thẳng, proxy? trả false. Danh sách no_proxy khớp theo đuôi tên miền, nên một mục quá rộng có thể kéo cả website cần đo ra khỏi proxy. Kiểm tra bằng proxy? trên chính đối tượng Net::HTTP, đừng đoán.
Muốn một client đi thẳng có chủ đích, truyền nil ở vị trí địa chỉ proxy: Net::HTTP.new(host, port, nil). Lần chạy thử với http_proxy đang đặt vẫn cho proxy? bằng false. Cách này rõ ràng hơn việc xóa biến môi trường giữa chừng, vì không ảnh hưởng client khác trong cùng tiến trình.
Lỗi proxy Ruby về TLS qua đường hầm
Khi đường hầm CONNECT đã mở, Ruby bắt tay TLS trực tiếp với website bên trong đường hầm. Lỗi chứng chỉ ở bước này là lỗi của website, không phải của proxy. Nhưng thông báo lỗi dễ làm anh em nghĩ ngược lại.
Thông báo ghi địa chỉ proxy
Gọi expired.badssl.com qua proxy, Net::HTTP báo OpenSSL::SSL::SSLError: SSL_connect returned=1 errno=0 peeraddr=127.0.0.1:18080 state=error: certificate verify failed (certificate has expired). Gọi wrong.host.badssl.com thì phần cuối đổi thành hostname mismatch.
Phần peeraddr ghi địa chỉ proxy vì socket TCP nối tới proxy, TLS chạy bên trong. Đọc vội sẽ tưởng proxy có chứng chỉ hỏng. Phần trong ngoặc cuối dòng mới là nguyên nhân thật: chứng chỉ của website hết hạn hoặc không khớp tên miền.
Khi chạy lặp lại, mình gặp thêm một biến thể: state=error: unexpected eof while reading, không có ngoặc nào. Đó là lúc đường hầm bị đóng ngang trong khi đang bắt tay TLS, do website hoặc proxy cắt kết nối. Nhật ký proxy lần đó ghi website không trả lời trong 15 giây. Lỗi này không liên quan chứng chỉ, chạy lại hoặc đổi proxy thường hết.

TLS và phân loại lỗi
Không tắt kiểm tra chứng chỉ
Cách sửa nhanh hay thấy trên mạng là đặt verify_mode thành VERIFY_NONE. Làm vậy lỗi biến mất, nhưng chương trình chấp nhận bất kỳ máy nào giả làm website. Qua proxy của bên thứ ba, đây đúng là tình huống cần kiểm tra chứng chỉ nhất.
Mình kiểm tra theo thứ tự: đồng hồ máy có đúng không, URL có dùng IP thay cho tên miền không, và kho CA của Ruby có cập nhật không. Với lỗi hostname mismatch, kiểm tra xem có đang gọi bằng IP hoặc bằng tên miền phụ khác với tên trên chứng chỉ không. Tài liệu SSLContext của Ruby OpenSSL mô tả các tùy chọn CA nếu công ty dùng CA riêng. Bài lỗi chứng chỉ proxy có thêm trường hợp phía trình duyệt.
Một chi tiết nữa: lần gọi expired.badssl.com mất 8,4 giây, lâu hơn hẳn các lần gọi khác. Đó là thời gian website phản hồi qua đường hầm, không phải proxy chậm. Đo thời gian từng giai đoạn thì mới biết chậm ở đâu.
Lỗi proxy Ruby do retry ngầm
Retry ngầm không làm script hỏng, nhưng làm anh em đếm sai số request và đổ lỗi nhầm cho proxy. Hai thư viện có hai kiểu retry khác nhau.
Net::HTTP tự thử lại một lần
Với đích HTTP và proxy treo, read_timeout 5 giây, lỗi Net::ReadTimeout hiện sau 10,0 giây chứ không phải 5. Nhật ký proxy ghi hai kết nối cách nhau 5 giây. Net::HTTP mặc định tự thử lại một lần với các request idempotent như GET khi gặp timeout.
Đặt max_retries bằng 0, lỗi hiện sau đúng 5,0 giây và nhật ký chỉ có một kết nối. Với đích HTTPS, lỗi treo ở bước CONNECT hiện sau 5,0 giây, chỉ một kết nối, vì việc thử lại không bao trùm bước mở đường hầm. Hai hành vi khác nhau chỉ vì đích là HTTP hay HTTPS.
Faraday retry cộng dồn số kết nối
Thêm middleware retry với max 2 vào Faraday, gọi qua proxy treo với timeout 3 giây, lỗi Faraday::TimeoutError hiện sau 10 giây. Nhật ký proxy ghi ba kết nối: một lần gốc và hai lần thử lại. Adapter net_http của Faraday không cộng thêm lần thử của Net::HTTP trong lần chạy này.
Nếu gói proxy tính theo số request hay theo lưu lượng, retry ngầm làm hóa đơn lớn hơn số dòng trong hàng đợi. Mình tắt retry của Net::HTTP bằng max_retries 0, chỉ để một tầng retry ở ứng dụng hoặc ở middleware, và đếm số lần gọi ra mạng ở đúng tầng đó. Tài liệu tùy chọn request của Faraday ghi các trường timeout của từng request.
Mã kiểm tra lỗi proxy Ruby trước khi chạy lô
Trước khi chạy hàng nghìn URL, mình chạy một script kiểm tra ngắn để biết proxy có ổn ở từng giai đoạn không. Script in ra giai đoạn dừng lại, không chỉ in lỗi.
Script chẩn đoán theo giai đoạn
Script tách bốn giai đoạn: đọc cấu hình, nối tới proxy, mở đường hầm, nhận response. Mỗi giai đoạn bắt đúng nhóm lỗi tương ứng, nên khi lỗi xảy ra anh em biết ngay nó nằm ở đâu. Thời gian chờ đặt ngắn để không phải ngồi chờ.
require 'net/http'
require 'uri'
def check_proxy(url)
t = URI(url)
http = Net::HTTP.new(t.hostname, t.port, ENV.fetch('PROXY_HOST'),
Integer(ENV.fetch('PROXY_PORT')), ENV.fetch('PROXY_USER'),
ENV.fetch('PROXY_PASSWORD'))
http.use_ssl = t.scheme == 'https'
http.open_timeout = 5
http.read_timeout = 10
http.max_retries = 0
res = http.start { |s| s.get(t.request_uri) }
return "proxy_auth: 407" if res.code == '407'
"ok: HTTP #{res.code}, #{res.body.to_s.bytesize} byte"
rescue KeyError => e
"config: thieu #{e.key}"
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, SocketError => e
"connect: #{e.class}"
rescue Net::OpenTimeout, Net::ReadTimeout => e
"timeout: #{e.class}"
rescue Net::HTTPClientException => e
"proxy_auth: #{e.message}"
rescue OpenSSL::SSL::SSLError => e
"tls: #{e.message.split('(').last.delete(')')}"
end
puts check_proxy(ARGV[0] || 'https://api.ipify.org/')
Script bắt KeyError riêng để phân biệt thiếu biến môi trường với lỗi mạng. Nhánh 407 có hai đường vì đích HTTP trả response còn đích HTTPS ném exception, như phần trên đã thấy. Nhánh TLS chỉ lấy phần trong ngoặc cuối thông báo, tức nguyên nhân thật, bỏ qua phần peeraddr dễ gây hiểu lầm.
Đọc kết quả và quyết định
Kết quả config thì sửa biến môi trường của tiến trình đang chạy, đừng sửa code. Kết quả connect hay timeout thì kiểm tra cổng và trạng thái proxy, thử proxy khác trong danh sách. Kết quả proxy_auth thì kiểm tra tài khoản và hạn của gói. Kết quả tls thì xem lại URL đích và đồng hồ máy.

Kiểm tra trước khi triển khai
Kết quả ok chưa đủ để chạy lô lớn. Mình kiểm tra thêm số byte có hợp lý không, vì trang chặn hay trang đăng nhập cũng trả 200. Bài web scraping proxy hoạt động thế nào nói cách đặt lớp proxy trong cả quy trình thu thập dữ liệu.
Chọn proxy để giảm lỗi ngay từ đầu
Phần lớn lỗi trong bài đến từ cấu hình, nhưng một số đến từ chất lượng proxy: proxy treo, proxy hết hạn giữa chừng, hay cổng SOCKS5 bị trỏ nhầm. Trước khi mua proxy cho script Ruby, anh em hỏi rõ cổng HTTP, cách xác thực và giới hạn kết nối đồng thời. ProxyVN có proxy dân cư tĩnh, datacenter và 4G, dùng HTTP hoặc SOCKS5, cấp user và password riêng cho từng proxy.
Câu hỏi thường gặp về lỗi proxy Ruby
Vì sao đặt https_proxy mà Ruby vẫn đi thẳng. Net::HTTP chỉ đọc http_proxy, kể cả cho đích HTTPS. Đặt http_proxy, hoặc đặt cả hai biến cùng giá trị nếu ứng dụng có dùng Faraday, hoặc truyền proxy tường minh trong code.
Vì sao 407 lúc thì là exception, lúc thì là response. Đích HTTPS đi qua lệnh CONNECT, proxy từ chối ở bước đó nên thư viện ném exception. Đích HTTP gửi thẳng request, proxy trả 407 như response bình thường. Code phải xử lý cả hai.
Ruby có dùng được proxy SOCKS5 không. Net::HTTP thì không. Tiền tố socks5 trong biến môi trường bị bỏ qua và Net::HTTP vẫn nói giao thức HTTP. Dùng cổng HTTP của nhà cung cấp cho Ruby.
Tăng open_timeout có hết lỗi proxy treo không. Thường là không. Với proxy nhận kết nối rồi im lặng, lỗi là ReadTimeout ở bước chờ CONNECT, tính theo read_timeout. Đổi sang proxy khác nhanh hơn chờ lâu hơn.
Có nên đặt VERIFY_NONE để hết lỗi chứng chỉ. Không. Lỗi chứng chỉ qua proxy là lỗi của website, phần trong ngoặc cuối thông báo cho biết nguyên nhân. Tắt kiểm tra chứng chỉ khi đi qua proxy của bên thứ ba là bỏ đi lớp bảo vệ cần nhất.
Lỗi proxy Ruby: những điều cần nhớ
Lỗi proxy Ruby đọc theo giai đoạn: Failed to open TCP connection là chưa tới proxy, 407 là sai tài khoản, SSLError là chứng chỉ website. Truyền proxy bằng tham số riêng, đặt http_proxy cho Net::HTTP, tắt max_retries và kiểm tra cả mã trạng thái. Cần proxy HTTP ổn định để bớt lỗi proxy Ruby, liên hệ Proxy.vn - Nhà cung cấp dịch vụ proxy chất lượng hàng đầu Việt Nam.