Ngiêm cấm sử đụng PROXY và VPS vào mục đích trái pháp luật bạn sẽ phải chịu toàn bộ trách nhiệm trước pháp luật khi sử dụng dịch vụ của chúng tôi tks!
libcurl proxy bằng C: CURLOPT_PROXY, CURLcode và handle

libcurl proxy bằng C: CURLOPT_PROXY, CURLcode và vòng đời handle

Chuyên viên Kỹ thuật ProxyVN 08-02-2026 Đang tính... phút đọc

libcurl proxy bằng C là cách dùng trực tiếp API C của libcurl để gửi request qua máy trung gian. Ở tầng này, anh em tự quản handle, tự cấp bộ nhớ cho thân response, tự đọc CURLcode và tự dọn dẹp. ProxyVN đối chiếu tài liệu libcurl, viết chương trình C mẫu và kiểm từng hành vi proxy trên libcurl 8.23.0 với proxy HTTP và SOCKS5 tự dựng.

libcurl proxy bằng C và vòng đời của easy handle

Bốn bước của một request

Bốn bước của easy handle

Bốn bước của easy handle

API easy của libcurl xoay quanh một con trỏ CURL gọi là easy handle. Một request đi qua bốn bước: curl_easy_init tạo handle, curl_easy_setopt đặt từng tùy chọn, curl_easy_perform chạy, curl_easy_cleanup giải phóng. Trước tất cả, chương trình gọi curl_global_init một lần, và cuối cùng gọi curl_global_cleanup.

Proxy chỉ là vài tùy chọn trong bước thứ hai. Nhưng vì đây là C, mọi sai sót về bộ nhớ, về kiểu tham số hay về thứ tự gọi đều có thể làm chương trình hỏng theo cách khó dò. Phần lớn bài này vì thế nói về những gì bao quanh tùy chọn proxy.

Với libcurl bằng C, tùy chọn proxy đúng chưa đủ, handle phải được tạo, đặt và dọn đúng thứ tự. Một handle bị dùng lại sai cách có thể gửi request bằng proxy cũ.

Ranh giới với các bài cURL khác

Bài curl proxy là gì nói về lệnh curl với cờ -x. Bài PHP cURL proxy dùng cùng tên tùy chọn CURLOPT_PROXY nhưng trong PHP, nơi bộ nhớ và kiểu dữ liệu do PHP lo. Bài lỗi curl 56 đi sâu một mã lỗi.

Bài này dành cho chương trình C hoặc thư viện C dùng libcurl trực tiếp: phần mềm nhúng, công cụ hệ thống, plugin cho ứng dụng khác. Nó tập trung vào API C: kiểu tham số của setopt, callback ghi dữ liệu, CURLOPT_ERRORBUFFER, curl_easy_getinfo và cách biên dịch.

Phạm vi kiểm thử

Máy lab hôm viết bài không có trình biên dịch C, nên chương trình C trong bài chưa được biên dịch trên máy này. Hành vi của từng tùy chọn proxy được kiểm trên libcurl 8.23.0 qua binding PycURL, vốn gọi đúng curl_easy_setopt với cùng tùy chọn. Proxy thử là chương trình tự dựng, có proxy HTTP cần tài khoản user1 với mật khẩu p@ss#2026 và proxy SOCKS5.

Cách kiểm này cho biết libcurl phản ứng thế nào với từng giá trị tùy chọn. Nó không kiểm lỗi bộ nhớ hay lỗi kiểu trong mã C, phần đó anh em cần tự biên dịch với cảnh báo bật đầy đủ và chạy công cụ kiểm bộ nhớ.

Chuẩn bị môi trường cho libcurl proxy bằng C

Cài thư viện phát triển

Trên Debian, Ubuntu, cài gói libcurl4-openssl-dev để có tiêu đề curl/curl.h và thư viện liên kết. Trên Fedora là libcurl-devel. Trên Windows, có thể dùng vcpkg với gói curl, hoặc bản dựng chính thức từ trang curl. Kiểm bản libcurl bằng curl-config --version hoặc in curl_version() trong chương trình.

gcc -Wall -Wextra -O2 -o proxy_lab proxy_lab.c $(curl-config --cflags --libs)
./proxy_lab

Biên dịch với -Wall -Wextra để trình biên dịch bắt lỗi kiểu tham số của curl_easy_setopt, vốn là hàm có số tham số thay đổi. Truyền int vào chỗ cần long là lỗi hay gặp và không phải lúc nào cũng gây sự cố ngay.

Khởi tạo toàn cục

curl_global_init phải gọi một lần trước khi tạo bất kỳ handle nào, và không an toàn khi gọi đồng thời từ nhiều luồng. Đặt nó ở đầu main, trước khi khởi động các luồng. curl_global_cleanup đặt ở cuối main.

#include <curl/curl.h>
#include <stdio.h>
 
int main(void) {
    if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) {
        fprintf(stderr, "Khong khoi tao duoc libcurl\n");
        return 1;
    }
    printf("%s\n", curl_version());
    /* ... cac request ... */
    curl_global_cleanup();
    return 0;
}

In curl_version() khi khởi động cho biết bản libcurl, thư viện TLS và các tính năng như SOCKS có sẵn hay không. Lab của mình in ra libcurl 8.23.0 với OpenSSL 3.6.5 và Schannel.

Bộ đệm cho thân response

libcurl không tự giữ thân response. Nó gọi hàm ghi mỗi khi nhận dữ liệu. Với C, anh em tự viết callback cấp phát và nối bộ nhớ. Đây là mẫu chuẩn theo các ví dụ trong tài liệu libcurl.

#include <stdlib.h>
#include <string.h>
 
struct buf { char *data; size_t len; };
 
static size_t write_cb(char *ptr, size_t size, size_t nmemb, void *userdata) {
    size_t n = size * nmemb;
    struct buf *b = userdata;
    char *p = realloc(b->data, b->len + n + 1);
    if (!p) return 0;
    b->data = p;
    memcpy(b->data + b->len, ptr, n);
    b->len += n;
    b->data[b->len] = '\0';
    return n;
}

Trả về 0 khi realloc thất bại báo cho libcurl dừng, curl_easy_perform sẽ trả CURLE_WRITE_ERROR. Đừng trả số khác n, libcurl coi đó là lỗi ghi.

Đặt libcurl proxy bằng C trên easy handle

CURLOPT_PROXY và tài khoản riêng

Tài liệu CURLOPT_PROXY mô tả tùy chọn nhận chuỗi tên máy proxy, có thể kèm tiền tố giao thức và cổng. Tài khoản nên đặt bằng CURLOPT_PROXYUSERNAME và CURLOPT_PROXYPASSWORD, hai chuỗi riêng không cần mã hóa.

static CURLcode fetch(const char *url, struct buf *out, char *errbuf) {
    CURL *h = curl_easy_init();
    if (!h) return CURLE_FAILED_INIT;
    curl_easy_setopt(h, CURLOPT_URL, url);
    curl_easy_setopt(h, CURLOPT_PROXY, getenv("PROXY_HOSTPORT"));
    curl_easy_setopt(h, CURLOPT_PROXYUSERNAME, getenv("PROXY_USER"));
    curl_easy_setopt(h, CURLOPT_PROXYPASSWORD, getenv("PROXY_PASS"));
    curl_easy_setopt(h, CURLOPT_WRITEFUNCTION, write_cb);
    curl_easy_setopt(h, CURLOPT_WRITEDATA, out);
    curl_easy_setopt(h, CURLOPT_ERRORBUFFER, errbuf);
    curl_easy_setopt(h, CURLOPT_CONNECTTIMEOUT, 5L);
    curl_easy_setopt(h, CURLOPT_TIMEOUT, 20L);
    CURLcode rc = curl_easy_perform(h);
    curl_easy_cleanup(h);
    return rc;
}

Trong lab, đặt PROXYUSERNAME là user1 và PROXYPASSWORD là p@ss#2026 nguyên văn, proxy nhận đúng tài khoản và trả 200. libcurl gửi Basic ngay ở request đầu tiên, không chờ proxy trả 407, nhật ký proxy chỉ có một dòng cho mỗi request.

Đừng mã hóa giá trị của PROXYPASSWORD

Giá trị của CURLOPT_PROXYPASSWORD được dùng nguyên văn. Trong lab, đặt PROXYPASSWORD là p%40ss%232026, tức mật khẩu đã mã hóa phần trăm, proxy trả 407. libcurl không giải mã chuỗi này.

Ngược lại, nếu tài khoản nằm trong chuỗi CURLOPT_PROXY dạng http://user1:p%40ss%[email protected]:8942, libcurl giải mã phần trăm và proxy nhận đúng mật khẩu. Đặt chuỗi nguyên văn http://user1:p@ss#[email protected]:8942 thì libcurl báo CURLE_COULDNT_RESOLVE_PROXY với thông điệp Could not resolve proxy: ss. Quy tắc gọn: tài khoản trong URL thì mã hóa, tài khoản trong tùy chọn riêng thì không.

Kiểu tham số của setopt

curl_easy_setopt là hàm có số tham số thay đổi, kiểu tham số thứ ba phụ thuộc tùy chọn. Tùy chọn số như CURLOPT_CONNECTTIMEOUT, CURLOPT_HTTPPROXYTUNNEL cần long, nên viết 5L, 1L. Tùy chọn chuỗi cần char con trỏ. Tùy chọn hàm cần con trỏ hàm đúng chữ ký.

libcurl sao chép chuỗi truyền vào setopt cho hầu hết tùy chọn chuỗi từ bản 7.17.0, nên có thể giải phóng chuỗi gốc sau khi gọi. Với getenv, chuỗi thuộc về môi trường, không giải phóng. Nếu getenv trả NULL, truyền NULL vào CURLOPT_PROXY nghĩa là dùng mặc định, tức đọc biến môi trường proxy.

Đọc biến môi trường khi không đặt CURLOPT_PROXY

Khi không đặt CURLOPT_PROXY, libcurl đọc biến môi trường. Tài liệu ghi http_proxy chỉ đọc bản chữ thường vì lý do bảo mật, https_proxy, all_proxy và no_proxy đọc cả hai bản. Trong lab trên Windows, biến môi trường không phân biệt hoa thường nên không thấy khác biệt, nhưng trên Linux, HTTP_PROXY chữ hoa sẽ bị bỏ qua cho URL http.

Muốn một handle bắt buộc đi thẳng dù môi trường có biến proxy, đặt CURLOPT_PROXY là chuỗi rỗng. Tài liệu ghi chuỗi rỗng tắt hẳn proxy, kể cả proxy từ biến môi trường.

libcurl proxy bằng C với HTTPS và đường hầm

CONNECT cho URL https

Với URL https qua proxy HTTP, libcurl mở đường hầm bằng lệnh CONNECT rồi bắt tay TLS với website bên trong đường hầm. Trong lab, https://example.com qua proxy có tài khoản trả 200, nhật ký proxy ghi CONNECT example.com:443 có tài khoản.

CURLINFO_HTTP_CONNECTCODE trả mã proxy đáp cho lệnh CONNECT. Với URL http không đường hầm, mã này là 0. Với đường hầm thành công, mã là 200. Đây là cách tách chặng proxy khỏi chặng website trong cùng một request.

long connect_code = 0, http_code = 0;
curl_easy_getinfo(h, CURLINFO_HTTP_CONNECTCODE, &connect_code);
curl_easy_getinfo(h, CURLINFO_RESPONSE_CODE, &http_code);

Lưu ý gọi getinfo trước curl_easy_cleanup. Trong hàm fetch ở trên, cần di chuyển getinfo lên trước cleanup và trả mã ra ngoài qua tham số con trỏ.

Sai tài khoản ở hai nơi

Tài khoản trong URL thì mã hóa

Tài khoản trong URL thì mã hóa

Kết quả lab quan trọng nhất về lỗi: cùng một tài khoản sai, URL http và URL https báo lỗi ở hai nơi khác nhau. Với URL https, curl_easy_perform trả CURLE_COULDNT_CONNECT với thông điệp CONNECT tunnel failed, response 407, và CURLINFO_HTTP_CONNECTCODE là 407. Với URL http, curl_easy_perform trả CURLE_OK, CURLINFO_RESPONSE_CODE là 407, thân rỗng.

Chương trình chỉ kiểm CURLcode sẽ coi request http bị proxy từ chối là thành công. Luôn kiểm cả CURLcode lẫn mã HTTP. Bài sửa lỗi 407 Proxy Authentication Required liệt kê nguyên nhân phía nhà cung cấp.

CURLOPT_HTTPPROXYTUNNEL

Đặt CURLOPT_HTTPPROXYTUNNEL bằng 1L buộc libcurl mở đường hầm kể cả với URL http. Trong lab, URL http://shop.test với tùy chọn này cho mã CONNECT 200, nhật ký ghi CONNECT shop.test:8941. Dùng khi proxy chỉ cho phép CONNECT hoặc khi không muốn proxy đọc nội dung request http.

Một số proxy công ty chặn CONNECT tới cổng khác 443. Khi bật đường hầm mà nhận mã CONNECT 403 hoặc lỗi nhận dữ liệu, kiểm chính sách cổng của proxy trước.

libcurl proxy bằng C với SOCKS5

Tiền tố và CURLOPT_PROXYTYPE

Có hai cách chọn SOCKS: tiền tố trong chuỗi CURLOPT_PROXY như socks5h://, hoặc đặt CURLOPT_PROXYTYPE với hằng như CURLPROXY_SOCKS5_HOSTNAME. Tiền tố trong chuỗi được ưu tiên nếu có, và dễ đọc hơn khi cấu hình đến từ tệp hay biến môi trường.

curl_easy_setopt(h, CURLOPT_PROXY, "socks5h://127.0.0.1:1080");
curl_easy_setopt(h, CURLOPT_PROXYUSERNAME, getenv("PROXY_USER"));
curl_easy_setopt(h, CURLOPT_PROXYPASSWORD, getenv("PROXY_PASS"));

CURLPROXY_SOCKS5 và CURLPROXY_SOCKS5_HOSTNAME

Trong lab, socks5:// với tên chỉ phân giải được bên trong proxy trả CURLE_COULDNT_RESOLVE_HOST, không có kết nối nào tới proxy SOCKS. socks5h:// trả 200, nhật ký SOCKS ghi CONNECT tới tên miền với kiểu địa chỉ domain. Hằng CURLPROXY_SOCKS5 tương ứng socks5, CURLPROXY_SOCKS5_HOSTNAME tương ứng socks5h.

Với crawler, dùng socks5h hoặc CURLPROXY_SOCKS5_HOSTNAME để proxy phân giải tên miền, tránh rò truy vấn DNS ra nhà mạng của máy. Bài DNS leak khi dùng proxy giải thích rủi ro này.

Tài khoản SOCKS

CURLOPT_PROXYUSERNAME và CURLOPT_PROXYPASSWORD dùng cho cả SOCKS5. Trong lab, proxy SOCKS nhận đúng user1 với mật khẩu có ký tự đặc biệt khi đặt nguyên văn. Quy tắc không mã hóa giá trị của hai tùy chọn này áp dụng y như proxy HTTP.

Bỏ qua libcurl proxy bằng C với CURLOPT_NOPROXY

Danh sách host đi thẳng

CURLOPT_NOPROXY nhận chuỗi danh sách host cách nhau bằng dấu phẩy. Host khớp danh sách sẽ đi thẳng dù CURLOPT_PROXY đã đặt. Giá trị * bỏ qua proxy cho mọi host. Trong lab, đặt CURLOPT_NOPROXY là shop.test, request tới shop.test đi thẳng và báo không phân giải được host, đúng như mong đợi.

curl_easy_setopt(h, CURLOPT_NOPROXY, "localhost,127.0.0.1,.corp.example");

Bài no_proxy là gì giải thích quy ước viết danh sách. Với libcurl, tên bắt đầu bằng dấu chấm khớp mọi tên miền con.

Đè biến no_proxy của môi trường

Khi đặt CURLOPT_NOPROXY, giá trị này thay cho biến no_proxy của môi trường. Đặt chuỗi rỗng nghĩa là không bỏ qua host nào. Với chương trình chạy trên nhiều máy, đặt rõ CURLOPT_NOPROXY giúp hành vi không phụ thuộc cấu hình từng máy.

Đọc CURLcode khi dùng libcurl proxy bằng C

curl_easy_strerror và CURLOPT_ERRORBUFFER

curl_easy_perform trả CURLcode. curl_easy_strerror đổi mã thành chuỗi mô tả chung. CURLOPT_ERRORBUFFER cho thông điệp chi tiết hơn, gồm cả tên máy, cổng và mã proxy. Bộ đệm phải có kích thước ít nhất CURL_ERROR_SIZE và tồn tại suốt thời gian handle được dùng.

char errbuf[CURL_ERROR_SIZE] = "";
struct buf out = {0};
CURLcode rc = fetch("https://api.example.com/items", &out, errbuf);
if (rc != CURLE_OK) {
    fprintf(stderr, "Loi %d %s: %s\n", rc, curl_easy_strerror(rc), errbuf[0] ? errbuf : "");
}
free(out.data);

Trong lab, thông điệp chi tiết cho proxy chết là Failed to connect to 127.0.0.1:8999 over proxy 127.0.0.1 kèm số mili giây. Chữ over proxy cho biết libcurl đang cố tới proxy, không phải website.

Bảng CURLcode và mã HTTP

CURLE_OK chưa phải thành công

CURLE_OK chưa phải thành công

Bảng dưới gom các ca đã kiểm trên libcurl 8.23.0. Tên mã theo danh sách lỗi của libcurl.

Bảng: Bảng CURLcode và mã HTTP

Ca

CURLcode

Mã HTTP hoặc CONNECT

Không proxy, tên chỉ có trong proxy

CURLE_COULDNT_RESOLVE_HOST (6)

Không có

Mật khẩu chưa mã hóa trong URL proxy

CURLE_COULDNT_RESOLVE_PROXY (5)

Không có

Mật khẩu đã mã hóa đặt vào PROXYPASSWORD

CURLE_OK

Response 407

Sai tài khoản, URL http

CURLE_OK

Response 407

Sai tài khoản, URL https

CURLE_COULDNT_CONNECT (7)

CONNECT 407

Proxy không mở cổng

CURLE_COULDNT_CONNECT (7)

Không có

Website chậm hơn CURLOPT_TIMEOUT

CURLE_OPERATION_TIMEDOUT (28)

Không có

Website trả 503

CURLE_OK

Response 503

Website trả 503, CURLOPT_FAILONERROR

CURLE_HTTP_RETURNED_ERROR (22)

Response 503

socks5 với tên chỉ có trong proxy

CURLE_COULDNT_RESOLVE_HOST (6)

Không có

Dòng thứ ba và thứ tư cho thấy vì sao không được chỉ kiểm CURLcode. Cả hai đều là CURLE_OK nhưng request đã thất bại ở proxy.

Với libcurl bằng C, CURLE_OK chỉ nghĩa là trao đổi hoàn tất. Luôn đọc CURLINFO_RESPONSE_CODE và CURLINFO_HTTP_CONNECTCODE trước khi coi request là thành công.

Timeout, redirect và POST qua libcurl proxy bằng C

Timeout

CURLOPT_CONNECTTIMEOUT giới hạn bước kết nối, khi có proxy là kết nối tới proxy. CURLOPT_TIMEOUT giới hạn cả request. Mặc định libcurl không giới hạn thời gian tổng, nên luôn đặt khi đi qua proxy. Trong lab, trang chờ 5 giây với timeout 2 giây trả CURLE_OPERATION_TIMEDOUT, thông điệp ghi đã chờ khoảng 2014 mili giây và nhận 0 byte.

Với tải tệp lớn, dùng CURLOPT_LOW_SPEED_LIMIT và CURLOPT_LOW_SPEED_TIME để cắt khi tốc độ quá thấp trong một khoảng thời gian, thay vì đặt CURLOPT_TIMEOUT quá dài. Cách này hợp với proxy dân cư có tốc độ không đều.

Redirect

Mặc định libcurl không theo redirect. Trong lab, trang trả 302 cho CURLE_OK với mã HTTP 302 và thân rỗng. Đặt CURLOPT_FOLLOWLOCATION bằng 1L, libcurl theo sang trang đích, trả 200. Nhật ký proxy ghi cả hai request, request sau có tài khoản proxy.

Đặt CURLOPT_MAXREDIRS để giới hạn số lần theo, tránh vòng lặp. Với redirect sang host khác, libcurl mặc định không gửi tài khoản website sang host mới, nhưng tài khoản proxy dùng cho mọi chặng.

POST

CURLOPT_POSTFIELDS với chuỗi JSON và tiêu đề Content-Type qua curl_slist đi qua proxy bình thường. Trong lab, website nhận đúng thân {"sku":"A1"}. Nhớ giải phóng curl_slist bằng curl_slist_free_all sau khi cleanup handle.

struct curl_slist *hdrs = curl_slist_append(NULL, "Content-Type: application/json");
curl_easy_setopt(h, CURLOPT_HTTPHEADER, hdrs);
curl_easy_setopt(h, CURLOPT_POSTFIELDS, "{\"sku\":\"A1\"}");
CURLcode rc = curl_easy_perform(h);
curl_easy_cleanup(h);
curl_slist_free_all(hdrs);

libcurl không tự thử lại. Với POST tạo dữ liệu, không thử lại sau timeout. Với GET, tự viết vòng thử lại cho mã 7, 28 và 56, có khoảng nghỉ tăng dần.

Chứng chỉ khi dùng libcurl proxy bằng C

CURLOPT_CAINFO và CURLOPT_PROXY_CAINFO

libcurl tách hai bộ chứng chỉ. CURLOPT_CAINFO là bó chứng chỉ để kiểm website. CURLOPT_PROXY_CAINFO là bó để kiểm chính proxy khi chuỗi CURLOPT_PROXY bắt đầu bằng https://, tức kết nối tới proxy bằng TLS. Với proxy HTTP thường, chỉ CURLOPT_CAINFO có ý nghĩa.

Sau proxy kiểm tra TLS của công ty, website trả chứng chỉ do CA nội bộ ký. Nếu bó chứng chỉ mặc định không có CA đó, curl_easy_perform trả CURLE_PEER_FAILED_VERIFICATION. Đặt CURLOPT_CAINFO trỏ tới tệp PEM có CA nội bộ, hoặc trên Windows dùng bản libcurl dựng với Schannel để đọc kho chứng chỉ hệ điều hành.

curl_easy_setopt(h, CURLOPT_CAINFO, "/etc/ssl/certs/corp-bundle.pem");

Không tắt kiểm tra

Đặt CURLOPT_SSL_VERIFYPEER bằng 0L làm mọi kết nối HTTPS của handle chấp nhận chứng chỉ bất kỳ. Ai đứng giữa đường cũng có thể đọc và sửa dữ liệu. Với chương trình chạy trên thiết bị nhúng gửi dữ liệu về máy chủ, đây là lỗ hổng nghiêm trọng, không phải cách chữa lỗi chứng chỉ.

Nếu thiết bị không có kho chứng chỉ, đóng gói bó chứng chỉ cùng phần mềm và cập nhật bó này theo chu kỳ. libcurl cũng có CURLOPT_CAINFO_BLOB để nạp bó chứng chỉ từ bộ nhớ, tiện cho thiết bị không có hệ thống tệp đầy đủ.

Đo thời gian từng chặng của libcurl proxy bằng C

Các mốc CURLINFO

Sau curl_easy_perform, curl_easy_getinfo trả các mốc thời gian tính từ lúc bắt đầu, dạng curl_off_t micro giây với các tùy chọn có hậu tố _T. CURLINFO_NAMELOOKUP_TIME_T là lúc phân giải xong, CURLINFO_CONNECT_TIME_T là lúc kết nối xong, CURLINFO_APPCONNECT_TIME_T là lúc bắt tay TLS xong, CURLINFO_STARTTRANSFER_TIME_T là lúc nhận byte đầu, CURLINFO_TOTAL_TIME_T là lúc kết thúc.

curl_off_t conn = 0, tls = 0, first = 0, total = 0;
curl_easy_getinfo(h, CURLINFO_CONNECT_TIME_T, &conn);
curl_easy_getinfo(h, CURLINFO_APPCONNECT_TIME_T, &tls);
curl_easy_getinfo(h, CURLINFO_STARTTRANSFER_TIME_T, &first);
curl_easy_getinfo(h, CURLINFO_TOTAL_TIME_T, &total);
printf("ket noi %ld us, tls %ld us, byte dau %ld us, tong %ld us\n",
       (long)conn, (long)tls, (long)first, (long)total);

Khi đi qua proxy, mốc kết nối là kết nối tới proxy. Với URL https qua đường hầm, mốc TLS gồm cả thời gian proxy mở đường hầm tới website. Khoảng cách lớn giữa hai mốc này thường do chặng proxy tới website chậm.

Dùng số liệu để chọn proxy

Ghi bốn mốc này cùng tên proxy và mã HTTP vào log cho mỗi request. Sau một ngày, tính trung vị theo từng proxy. Proxy có mốc kết nối cao nằm xa hoặc quá tải, proxy có mốc TLS cao đi đường dài tới website. Số liệu này giúp chọn vùng proxy gần website đích hơn là đoán.

Viết hàm kiểm kết quả dùng chung

Vì có ba loại mã cần kiểm, mình khuyên viết một hàm nhận handle và CURLcode, trả về một trong bốn trạng thái: thành công, lỗi proxy, lỗi website, lỗi mạng. Lỗi proxy gồm CONNECT 407, mã HTTP 407 và CURLE_COULDNT_RESOLVE_PROXY. Lỗi website gồm mã HTTP từ 400 trở lên khác 407. Còn lại là lỗi mạng.

Mọi chỗ gọi curl_easy_perform đều dùng hàm này. Nhờ vậy cách phân loại lỗi nhất quán trong cả chương trình, log dễ đọc và dễ thống kê. Khi tỷ lệ lỗi proxy tăng, đội vận hành biết ngay cần liên hệ nhà cung cấp proxy, không mất thời gian xem website.

Dùng lại handle và nhiều luồng với libcurl proxy bằng C

curl_easy_reset

Tạo handle mới cho mỗi request tốn bắt tay lại với proxy. Dùng lại handle giúp giữ kết nối. curl_easy_reset xóa mọi tùy chọn về mặc định nhưng giữ kết nối sống, bộ nhớ đệm DNS và phiên TLS. Sau reset, phải đặt lại CURLOPT_PROXY và tài khoản, nếu không request sau sẽ đọc biến môi trường hoặc đi thẳng.

Khi cần IP mới từ proxy xoay đổi theo tên người dùng, kết nối giữ sẵn có thể được dùng lại với tài khoản cũ. Đặt CURLOPT_FORBID_REUSE hoặc tạo handle mới cho phiên mới.

Một handle cho mỗi luồng

Easy handle không được dùng đồng thời từ nhiều luồng. Mỗi luồng tạo handle riêng. Với nhiều request song song trong một luồng, dùng API multi: tạo nhiều easy handle, thêm vào multi handle bằng curl_multi_add_handle, rồi chạy vòng curl_multi_perform và curl_multi_poll.

Với API multi, lỗi của từng request đọc qua curl_multi_info_read, không trả từ hàm perform. Giới hạn số request đồng thời bằng hoặc thấp hơn hạn mức luồng của gói proxy. Mình chưa chạy API multi trong lab.

Chia sẻ bộ đệm giữa các handle

API share cho phép nhiều easy handle dùng chung bộ đệm DNS, cookie hay phiên TLS. Khi đi qua proxy với socks5h hay HTTP, DNS của website do proxy phân giải, nên chia sẻ DNS chủ yếu giúp cho tên máy proxy. Với nhiều luồng, API share cần hàm khóa riêng, đọc kỹ tài liệu trước khi dùng.

Chạy đối chứng libcurl proxy bằng C

Bộ ca tối thiểu

Sau khi biên dịch, chạy chương trình với proxy cục bộ có nhật ký và kiểm từng ca sau.

  • Không CURLOPT_PROXY, không biến môi trường: request đi thẳng.
  • CURLOPT_PROXY với tài khoản riêng: nhật ký có tài khoản, mã 200.
  • Tài khoản đã mã hóa trong URL proxy: mã 200.
  • Sai tài khoản với URL http: CURLE_OK, mã 407.
  • Sai tài khoản với URL https: CURLE_COULDNT_CONNECT, CONNECT 407.
  • socks5h: nhật ký SOCKS ghi tên miền.
  • CURLOPT_NOPROXY: nhật ký không có dòng cho host đó.
  • Proxy chết: CURLE_COULDNT_CONNECT trong khoảng CONNECTTIMEOUT.
  • Kiểm IP ra: gọi dịch vụ trả IP, so với IP proxy mong đợi.

Chạy thêm chương trình dưới Valgrind hoặc AddressSanitizer với các ca lỗi. Đường lỗi là nơi hay quên free bộ đệm hay curl_slist.

Ghi kết quả

Ghi bản libcurl, thư viện TLS, hệ điều hành và kết quả từng ca. Hành vi biến môi trường khác giữa Windows và Linux, nên chạy trên đúng nền tảng sẽ triển khai.

Đưa libcurl proxy bằng C vào sản phẩm

Gom cấu hình mạng vào một hàm

Viết một hàm apply_proxy nhận handle và cấu hình, đặt CURLOPT_PROXY, tài khoản, CURLOPT_NOPROXY, timeout. Mọi nơi tạo handle đều gọi hàm này. Khi đổi proxy, chỉ sửa cấu hình, không sửa từng chỗ gọi.

Không in tài khoản ra log. CURLOPT_VERBOSE in chi tiết trao đổi và có thể in tiêu đề Proxy-Authorization dạng base64. Nếu cần log chi tiết, dùng CURLOPT_DEBUGFUNCTION để lọc dòng chứa tiêu đề này. Xóa mật khẩu khỏi bộ nhớ sau khi dùng nếu chương trình có yêu cầu bảo mật cao.

Kiểm IP ra khi khởi động

Thêm một bước kiểm IP khi chương trình khởi động: gọi một dịch vụ trả IP công khai qua đúng cấu hình proxy, so với IP của máy. Nếu trùng, hoặc mã HTTP khác 200, dừng chương trình với mã lỗi riêng và thông báo rõ ràng. Với thiết bị nhúng, gửi trạng thái này về hệ thống giám sát thay vì chạy tiếp bằng IP thật.

Dùng URL https cho bước kiểm này. Như bảng ở trên, sai tài khoản với URL https cho CURLcode khác CURLE_OK, dễ bắt hơn trường hợp URL http trả CURLE_OK kèm mã 407. Ghi IP ra vào log khởi động để đối chiếu khi có sự cố.

Chọn loại proxy

Chương trình C dùng libcurl thường chạy trên thiết bị nhúng, máy chủ hay công cụ hệ thống. Với thiết bị gọi API cần IP cố định, proxy tĩnh phù hợp. Với công cụ tải dữ liệu nhiều trang, proxy xoay chia tải. Proxy xác thực theo IP giúp không phải lưu mật khẩu trên thiết bị.

ProxyVN có proxy dân cư tĩnh, datacenter và proxy xoay, hỗ trợ HTTP và SOCKS5, cấp tài khoản user và mật khẩu. Nếu cần proxy cho chương trình C, anh em có thể mua proxy uy tín tại trang chủ ProxyVN rồi chạy bộ ca kiểm ở trên.

Câu hỏi thường gặp về libcurl proxy bằng C

CURLOPT_PROXYPASSWORD có cần mã hóa phần trăm không. Không. Giá trị dùng nguyên văn. Mã hóa chỉ cần khi tài khoản nằm trong chuỗi CURLOPT_PROXY.

Vì sao HTTP_PROXY chữ hoa không có tác dụng trên Linux. libcurl chỉ đọc http_proxy chữ thường cho URL http.

Làm sao biết lỗi ở proxy hay website. Đọc CURLINFO_HTTP_CONNECTCODE cho chặng proxy với https, CURLINFO_RESPONSE_CODE cho website, và CURLOPT_ERRORBUFFER cho chi tiết.

Có dùng chung handle giữa các luồng được không. Không. Mỗi luồng một handle, hoặc dùng API multi trong một luồng.

Sau curl_easy_reset có phải đặt lại proxy không. Có. reset xóa mọi tùy chọn về mặc định.

socks5 hay socks5h. socks5h để proxy phân giải tên miền.

Đặt proxy cho từng request khác nhau thế nào. Mỗi easy handle có CURLOPT_PROXY riêng. Với nhiều proxy, tạo handle riêng cho mỗi proxy, hoặc đặt lại CURLOPT_PROXY trước mỗi perform trên cùng handle.

libcurl có hỗ trợ proxy HTTPS không. Có, với tiền tố https:// trong CURLOPT_PROXY khi bản libcurl dựng với thư viện TLS hỗ trợ. Khi đó cần CURLOPT_PROXY_CAINFO nếu proxy dùng chứng chỉ riêng.

Có cần gọi curl_global_init nếu chỉ có một luồng không. Nên gọi. curl_easy_init sẽ tự gọi nếu chưa có, nhưng việc đó không an toàn với nhiều luồng và khó kiểm soát thời điểm.

Checklist libcurl proxy bằng C

Checklist libcurl proxy bằng C

Checklist libcurl proxy bằng C

Trước khi phát hành

  • curl_global_init một lần trước khi tạo luồng.
  • Tài khoản qua PROXYUSERNAME, PROXYPASSWORD, không mã hóa.
  • Kiểm CURLcode, mã HTTP và mã CONNECT.
  • CURLOPT_ERRORBUFFER cho mọi handle.
  • socks5h cho SOCKS5.
  • CURLOPT_NOPROXY rõ ràng.
  • CONNECTTIMEOUT và TIMEOUT cho mọi request.
  • Đặt lại proxy sau curl_easy_reset.
  • Giải phóng bộ đệm và curl_slist trên mọi đường lỗi.

Giới hạn của bài

Chương trình C chưa được biên dịch trên máy lab vì không có trình biên dịch. Hành vi tùy chọn được kiểm trên libcurl 8.23.0 qua PycURL với proxy tự dựng. Chưa thử API multi, API share, proxy HTTPS, NTLM và chưa thử trên Linux để thấy khác biệt biến môi trường.

libcurl proxy bằng C và những điều cần nhớ

libcurl proxy bằng C đặt bằng CURLOPT_PROXY và hai tùy chọn tài khoản riêng không mã hóa. CURLE_OK chưa phải thành công, phải đọc mã HTTP và mã CONNECT. Dùng socks5h, đặt timeout, dọn bộ nhớ trên mọi đường lỗi. Cần proxy ổn định cho libcurl proxy bằng C, liên hệ Proxy.vn - Nhà cung cấp dịch vụ proxy chất lượng hàng đầu Việt Nam.

TIN TỨC LIÊN QUAN