Классы для работы с HTTP(S) на C++
Пятница, 6. Январь 2012
Раздел: C/C++, Софт, автор: dx
Написал кроссплатформенную библиотеку (набор классов) для работы с HTTP(S) на C++, используя последние концепции программирования. Пока что это, пожалуй, beta, так как тестировал я ее совсем мало.
Для сборки библиотеки потребуется собранный Boost (желательно версии 1.48 или выше). Если планируется включение функционала для работы с SSL (HTTPS), то потребуется еще собранная библиотека OpenSSL. Также в обязательном порядке требуется поддержка компилятором C++11. Сами заголовочные файлы библиотеки не включают ни заголовочные файлы boost, ни OpenSSL.
Для тех, кто захочет использовать библиотеку для написания чего-то жестоко многопоточного (конечно, спамеров, а что же еще подобное пишут с дохера потоками), учтите, что каждый запрос создает 1 дополнительный фоновый поток, т.е. количество потоков, используемых приложением, увеличивается вдвое. Такая особенность работы связана с тем, что в своем ядре библиотека использует boost::asio::io_service для контроля времени выполнения запроса (словом, внутри библиотеки всё происходит асинхронно).
Краткое описание возможностей класса:
[+] GET/POST/прочие запросы с использованием HTTP/1.0
[+] Поддержка HTTPS
[+] Поддержка multipart-POST запросов с возможностью загрузки произвольного количества файлов любого размера
[+] Автоматический менеджмент Cookies
[+] Автоматические переходы по Location-редиректам
[+] Поддержка скачивания больших файлов
[+] Поддержка HTTP/HTTPS/SOCKS5-прокси с авторизацией и без
[+] Возможность добавлять собственные HTTP-заголовки в запрос
[+] Поддержка автоматической обработки параметров (urlencode)
[+] Возможность задать таймаут на любой запрос
[+] Множество вспомогательных функций (urlencode, base64 и т.п.)
Классы потоконебезопасны, организация синхронизации должна производиться на вашей стороне в случае использования одного и того же экземпляра какого-либо класса в нескольких потоках сразу.
Далее я по пунктам приведу краткое описание возможностей библиотеки с примерами.
1. Простой пример HTTP GET-запроса.
#include <iostream> #include <string> #include <simple_http/simple_http.h> #include <simple_http/http_endpoint.h> #include <simple_http/functions.h> int main() { //Создали объект http_sync_connection simple_http::http_sync_connection conn; /* Отправили GET-запрос серверу и получили ответ Поддерживаемые адреса в функции from_string() имеют вид http://kaimi.ru http://kaimi.ru/test/ https://www.google.com/ -> если библиотека собрана с поддержкой SSL http://mail.ru:80/?xxx=123 http://test.ru/qwerty/xxx.php?a=1&b=2&c=3 */ std::string ret = conn.get(simple_http::http_endpoint::from_string("http://localhost/uploader/test.php?xxx=123&zzz=123")); //Получили тело ответа и вывели его в консоль std::cout << simple_http::get_body(ret) << std::endl; return 0; }
Пара строк кода, чтобы совершить GET-запрос. Что может быть проще? Дальнейшие примеры не будут уступать этому по простоте.
На заметку. Метод simple_http::http_endpoint::from_string создает экземпляр класса http_endpoint, который разделяет переданную ему строку URL на следующие независимые элементы:
- HTTP/HTTPS
- HOST/IP address
- порт (если есть)
- URI (если есть)
- параметры запроса (если есть)
Если передать объект класса http_endpoint в метод get, то параметры будут переданы методом GET, а если в post, то, соответственно, методом POST.
2. Запросы с автоматическим менеджментом Cookies.
Для автоматического менеджмента Cookies существует класс cookie_manager.
simple_http::http_sync_connection conn; simple_http::cookie_manager manager; //создали менеджер conn.set_auto_cookie_manager(manager); //прикрепили его к HTTP-классу
Далее могут следовать любые HTTP(S)-запросы, кукисы для них будут автоматически сохраняться менеджером и подставляться, когда это необходимо. Класс менеджера Cookies позволяет получить действительные Cookies для заданного домена, пути и т.д. Также можно добавлять собственные Cookies, они будут использоваться при HTTP(S)-запросах.
Для отключения автоматического менеджмента Cookies используется функция remove_auto_cookie_manager.
Класс cookie_manager имеет множество методов, позволяющих получить определенные Cookies, добавить новые, удалить какие-то определенные или все сразу и т.д. Описание я приводить не буду, так как они имеют достаточно очевидные названия и прототипы.
3. Установка таймаута на запрос.
Для того, чтобы установить максимальное время, которое может выполняться запрос, используется функция set_query_timeout, которая принимает как аргумент максимальное значение времени в секундах. По умолчанию на каждый запрос отводится 60 секунд. Если при запросе используются прокси, то время их отклика включается в этот таймаут.
4. Использование http proxy/socks5 с авторизацией или без для запросов.
Для установки proxy для запросов используются следующие методы класса http_sync_connection:
void set_proxy(const std::string& address, unsigned short port, proxy_type ptype); void set_proxy(const std::string& address, unsigned short port, proxy_type ptype, const std::string& login, const std::string& password);
address - строка адреса прокси (IP-адрес или имя хоста).
port - порт прокси
ptype - тип прокси: proxy_none - убрать прокси, proxy_http - HTTP-прокси, proxy_socks5 - SOCKS5-прокси.
login, password - логин и пароль прокси-сервера.
void clear_proxy();
Позволяет очистить текущий прокси-сервер.
5. Использование автоматических переходов по редиректам.
void enable_auto_redirect_follow(bool follow = true);
Включает/отключает автоматические переходы по редиректам (Location). По умолчанию выключено.
void enable_cross_domain_redirects(bool allow = true);
Включает/отключает переходы по Location на другие домены (например, если встретится редирект с mail.ru на yandex.ru, то при выключенной опции он не будет произведен автоматически). По умолчанию включено.
void set_max_redirect_count(unsigned long count = 10);
Устанавливает максимальное количество автоматических переходов по редиректам. По умолчанию 10.
std::string get_last_redirect_domain() const; std::string get_last_redirect_uri() const; std::string get_last_redirect_line() const; unsigned short get_last_redirect_port() const;
Данные функции позволяют получить информацию о последнем произведенном редиректе: домен, URI, строку адреса редиректа и порт соответственно.
unsigned long get_recent_redirect_count() const;
Позволяет получить количество сделанных авторедиректов во время последнего HTTP(S)-запроса.
Если библиотека собрана без поддержки SSL, и в каком-либо ответе от сервера встречается редирект с HTTP на HTTPS, он произведен не будет, даже если включена опция автоматического перехода по редиректам.
6. Дополнительные заголовки запроса.
Для добавления/удаления дополнительных заголовков в запрос используются следующие функции:
void add_query_header(const std::string& name, const std::string& value); bool remove_query_header(const std::string& name); void clear_query_headers();
Первая добавляет дополнительный заголовок с именем name и значением value.
Вторая удаляет дополнительный заголовок с именем name.
Третья удаляет все дополнительные заголовки.
7. User-agent.
Для установки заголовка User-agent есть удобная функция
void set_user_agent(const std::string& user_agent = "");
Если ей передана пустая строка, заголовок User-agent отсылаться не будет.
8. Отправка запросов без использования http_endpoint.
Иногда может потребоваться отправка запросов без использования данного упрощающего класса (например, если требуется передать HTTP Referer или часть параметров методом GET, а другую часть - методом POST).
Класс http_sync_connection имеет следующие функции:
std::string get(const std::string& uri, const std::string& parameters = "", const std::string& cookies = "", const std::string& referer = ""); std::string post(const std::string& uri, const std::string& parameters = "", const std::string& cookies = "", const std::string& referer = ""); std::string query(const std::string& method, const std::string& uri, const std::string& parameters = "", const std::string& cookies = "", const std::string& referer = ""); std::string get(const http_endpoint& ep, const std::string& cookies = "", const std::string& referer = ""); std::string post(const http_endpoint& ep, const std::string& cookies = "", const std::string& referer = ""); std::string query(const std::string& method, const http_endpoint& ep, const std::string& cookies = "", const std::string& referer = "");
Первые две отправляют на сервер, соответственно, GET и POST запрос. Третья позволяет явно задать тип запроса (например, "GET", "POST", "HEAD").
uri - Service-URI
parameters - параметры, которые необходимо передать на сервер. Функция get передаст их методом GET, а post - методом POST.
cookies - cookie, которые необходимо добавить в запрос. Если включен автоматический менеджмент Cookies, эти кукисы будут добавлены к тем, которые отсылаются менеджером cookies.
referer - HTTP-Referer.
Три последние функции отличаются от первых трех только тем, что позволяют задать домен, порт, URI и параметры запроса с помощью класса http_endpoint.
9. Отправка файлов на сервер (Multipart POST-запросы).
Класс http_sync_connection поддерживает загрузку файлов на сервер с помощью multipart-запросов. Имеется три функции:
std::string multipart_post(const std::string& uri, const std::string& parameters = "", const std::string& cookies = "", const std::string& referer = ""); std::string multipart_post(const std::string& uri, const std::string& multipart_file_parameter_name, multipart_file& file, const std::string& parameters = "", const std::string& cookies = "", const std::string& referer = ""); std::string multipart_post(const std::string& uri, std::map<std::string, multipart_file*>& files, const std::string& parameters = "", const std::string& cookies = "", const std::string& referer = ""); std::string multipart_post(const http_endpoint& ep, const std::string& multipart_file_parameter_name, multipart_file& file, const std::string& cookies = "", const std::string& referer = ""); std::string multipart_post(const http_endpoint& ep, std::map<std::string, multipart_file*>& files, const std::string& cookies = "", const std::string& referer = ""); std::string multipart_post(const http_endpoint& ep, const std::string& cookies = "", const std::string& referer = "");
Их параметры аналогичны вышеописанным get, post, query. Первый вариант функции ничем не отличается от функции post за исключением того, что данные будут отсылаться multipart-запросом.
Второй вариант имеет дополнительные параметры:
multipart_file_parameter_name - имя прикрепляемого к запросу файла
file - сам файл (объект класса multipart_file).
Третий вариант позволяет прикрепить сразу несколько файлов к запросу. Для этого используется параметр files - список файлов с именами.
Три последние функции отличаются от первых трех только тем, что позволяют задать домен, порт, URI и параметры запроса с помощью класса http_endpoint.
Класс multipart_file объявлен в заголовочном файле multipart_file.h и позволяет задать файл либо по его пути (физический файл операционной системы), либо как буфер данных.
Пример загрузки файла на сервер:
int main() { simple_http::http_sync_connection conn; //Создали объект класса multipart_file simple_http::multipart_file file("file.bmp", "C:\\image.bmp"); //Загрузили этот файл на сервер и считали ответ std::cout << conn.multipart_post(simple_http::http_endpoint::from_string("http://localhost/uploader/5.php"), "uploaded_file", file) << std::endl; return 0; }
10. Кеширование DNS-запросов.
void set_connenction_cache(bool enabled = true);
Эта функция позволяет включить или отключить кеширование DNS-запросов. По умолчанию кеширование включено. Если вы делаете много запросов подряд на один и тот же сервер, и данная функция включена, определение IP-адреса хоста будет производиться только один раз при первом запросе.
11. Автоматическая обработка параметров запроса (urlencode).
По умолчанию данная возможность выключена. Для ее включения/отключения используется функция
void enable_auto_urlencode(bool enable = true);
Если данная функция включена, будет производиться автоматическая обработка (urlencode) параметров запросов HTTP(S).
12. Callback при загрузке контента.
Если вы скачиваете большой файл или общаетесь с очень медленным сервером, возможно, есть смысл настроить callback, который будет вызываться при загрузке содержимого страницы.
template<typename Handler> void set_http_load_callback(Handler handler)
Данная функция позволяет установить коллбек, который будет вызываться в процессе загрузки контента с сервера.
void clear_http_load_callback();
Данная функция отключает callback на чтение данных.
void clear_input_after_callback(bool clear = true);
Данная функция устанавливает, должно ли очищаться возвращаемое значение функций get, post, query, multipart_post при использовании коллбека. По умолчанию включено.
void set_callback_min_transfer_amount(unsigned long bytes);
Данная функция позволяет установить минимальное количество байтов, которое должно быть принято с сервера до вызова коллбека. По умолчанию 1.
void set_callback_option(http_callback_option option = get_everything);
Данная функция позволяет настроить поведение коллбека. По умолчанию это get_everything, т.е. из сокета читается весь контент. Доступны также значения get_headers_only (будут считаны только заголовки) и get_body_only (будет считано только тело).
Сам коллбек должен представлять из себя функтор, возвращающий bool. Чтение данных из сокета будет продолжаться до тех пор, пока коллбек возвращает true.
Пример использования коллбека для скачивания большого файла (при этом оперативной памяти будет расходоваться совсем немного):
#include <iostream> #include <string> #include <fstream> #include <boost/bind.hpp> #include <simple_http/simple_http.h> #include <simple_http/http_endpoint.h> #include <simple_http/functions.h> //Функция сохранения скачанной части данных в файл bool filereader(const std::string& s, std::ofstream& file, unsigned long& downloaded) { downloaded += s.length(); //Сохраняем кусок скачанного файла file.write(s.data(), s.length()); //Выводим количество скачанных байтов данных std::cout << "\rDownloaded: " << downloaded; return true; //Возвращаем true - продолжаем чтение } int main() { simple_http::http_sync_connection conn; //Открываем файл на запись std::ofstream file("downloaded_boost.7z", std::ios::out | std::ios::binary | std::ios::trunc); unsigned long downloaded = 0; //Устанавливаем коллбек conn.set_http_load_callback(boost::bind(filereader, _1, boost::ref(file), boost::ref(downloaded))); //Получаем только тело ответа (сам файл, заголовки нам не нужны сейчас) conn.set_callback_option(simple_http::get_body_only); //Читаем пачками минимум по 50 кб, чтобы было быстрее и коллбек реже вызывался conn.set_callback_min_transfer_amount(50000); //Запускаем чтение conn.get(simple_http::http_endpoint::from_string("http://localhost/boost.7z")); //Готово, файл скачался. Функция get в этом случае вернет пустую строку, так как это опция по умолчанию //Это необходимо для экономии оперативной памяти при скачивании больших файлов, да и результат мы уже получили //и сохранили, так что дополнительно получать контент нет смысла. std::cout << std::endl << "Finished!" << std::endl; return 0; }
13. Вспомогательные функции.
Файл utils.h содержит объявления различных вспомогательных функций для перекодирования base64, urlencode и т.д.
Файл functions.h содержит объявления вспомогательных функций для работы с ответами HTTP.
14. Исключения
Файл http_error.h содержит объявление класса, используемого для возбуждения исключений, возникающих при работе классов simple_http. Все примеры приведены без обработки исключений, чтобы сосредоточиться на их сути. Вообще, все вызовы функций и методов классов неймспейса simple_http должны оборачиваться в обработчик исключений. Класс http_error позволяет получить как текст сообщения об ошибке, так и его код (они перечислены внутри класса).
15. Сборка с поддержкой SSL.
Для того, чтобы собрать библиотеку с поддержкой SSL (чтобы работали HTTPS-запросы), необходимо задать директиву препроцессора SSL_SUPPORT. Если поддержка SSL отключена, класс http_sync_connection будет бросать исключение при попытке произвести HTTPS-запрос.
Наконец, последний пример - авторизуемся на Форуме АНТИЧАТ и получаем количество новых приватных сообщений и общее их количество.
#include <iostream> #include <string> #include <boost/regex.hpp> #include <simple_http/simple_http.h> #include <simple_http/http_endpoint.h> #include <simple_http/cookie_manager.h> #include <simple_http/http_error.h> #pragma comment(lib, "simple_http.lib") #pragma comment(lib, "libeay32.lib") #pragma comment(lib, "ssleay32.lib") int main() { std::string login = "ВАШ ЛОГИН"; std::string password = "ВАШ ПАРОЛЬ"; try { simple_http::http_sync_connection conn; //создали экземпляр класса http_sync_connection simple_http::cookie_manager manager; //создали менеджер cookies conn.set_auto_cookie_manager(manager); //прикрепили его к HTTP-классу //Делаем GET-запрос на главную страницу, чтобы получить начальные Cookies conn.get(simple_http::http_endpoint::from_string("https://forum.antichat.ru/index.php")); //Добавляем HTTP-Referer'а conn.add_query_header("Referer", "https://forum.antichat.ru/index.php?"); //Включаем автоматическую обработку параметров (urlencode) conn.enable_auto_urlencode(); //Делаем POST-запрос, чтобы авторизоваться std::string login_page = conn.post( simple_http::http_endpoint::from_string("https://forum.antichat.ru/login.php?do=login&cookieuser=1&forceredirect=1&s=&vb_login_username=" + login + "&vb_login_password=" + password)); //Если авторизация успешна if(login_page.find("Спасибо, что зашли, ") != std::string::npos) { std::cout << "Correct login and password" << std::endl; //Снова получаем главную страницу, будучи авторизованными std::string index_page = conn.get(simple_http::http_endpoint::from_string("https://forum.antichat.ru/index.php")); //Получаем количество ПМов static boost::regex pm_match("ЛИЧНАЯ ПОЧТА</a> \\(<b>(\\d+)</b>/(\\d+)\\)"); boost::smatch what; //И выводим, сколько у нас новых ПМов, и сколько их всего if(boost::regex_search(index_page, what, pm_match)) std::cout << "New PMs: " << what[1].str() << ", total PMs: " << what[2].str() << std::endl; } else { //Иначе говорим, что введен неправильный логин или пароль std::cout << "Incorrect login or password" << std::endl; } } catch(const simple_http::http_error& e) { //Если произошла какая-то ошибка, выведем ее и завершим работу std::cout << "Error: " << e.what() << std::endl; } return 0; }
Вот и всё! Приведенной информации вполне достаточно, чтобы полноценно работать с библиотекой.
Выкладываю исходники библиотеки и последнего примера: simple_http.zip. В комплекте - файл солюшена и проектов Visual Studio 2010. Не забудьте прописать свои пути к boost'у и OpenSSL (если вам потребуется поддержка HTTPS).

and1 :
Собираю дебаг, тест тоже
command line в simple_http:
/I"C:\OpenSSL-Win32\include\openssl" /I"C:\OpenSSL-Win32\include" /ZI /nologo /W3 /WX- /Od /Oy- /D "SSL_SUPPORT" /D "_WIN32_WINNT=0x0501" /D "WIN32" /D "_DEBUG" /D "_LIB" /D "_UNICODE" /D "UNICODE" /Gm /EHsc /RTC1 /MTd /GS /fp:precise /Zc:wchar_t /Zc:forScope /Fp"Debug\simple_http.pch" /Fa"Debug\" /Fo"Debug\" /Fd"Debug\vc100.pdb" /Gd /analyze- /errorReport:queue
[Ответить]
dx:
Февраль 22nd, 2012 at 11:17
Это командная строка компилятора. Ошибки, которые ты написал, выводятся линкером. Покажи командную строку линкера.
[Ответить]
and1 :
Подменю линкера в simple_http нет, есть подменю Librarian!Там командная строка
/OUT:"C:\Users\Администратор\Downloads\simple_http\Debug\simple_http.lib" /NOLOGO
[Ответить]
dx:
Февраль 22nd, 2012 at 16:31
Я говорю о сборке не либы, а твоего проекта, который либу использует блин. Туда надо подключить собранную мою либу.
[Ответить]
Kaimi:
Февраль 22nd, 2012 at 16:48
А я вот либу собрать не могу, выдает: boost::asio::detail::make_read_until_expr_op(AsyncReadStream &,boost::asio::basic_streambuf &,const boost::regex &,ReadHandler)' : could not deduce template argument for 'RegEx'
boost_1_47_0\boost\asio\impl\read_until.hpp(881): error C2783: 'boost::asio::detail::read_until_expr_op
\boost_1_47_0\boost\asio\impl\read_until.hpp(862) : see declaration of 'boost::asio::detail::make_read_until_expr_op',boost::_bi::bind_t>(AsyncReadStream &,boost::asio::basic_streambuf<> &,const boost::regex &,const ReadHandler &)' being compiled...
1> \desktop\simple_http\simple_http\simple_http_socket_worker.h(96) : see reference to function template instantiation 'void boost::asio::async_read_until
ШОДЕЛАТЬ?!
[Ответить]
dx:
Февраль 22nd, 2012 at 17:42
Обновить буст до 1.48.00, блеать, это в нем бага.
and1 :
Кхм... извиняюсь конечно, но до сих пор не могу собрать все в кучу! Вот строка линкера проекта, где хочу использовать либу!
/OUT:"D:\example\boost+openssl\Debug\boost+openssl.exe" /INCREMENTAL /NOLOGO "kernel32.lib" "user32.lib" "gdi32.lib" "winspool.lib" "comdlg32.lib" "advapi32.lib" "shell32.lib" "ole32.lib" "oleaut32.lib" "uuid.lib" "odbc32.lib" "odbccp32.lib" /MANIFEST /ManifestFile:"Debug\boost+openssl.exe.intermediate.manifest" /ALLOWISOLATION /MANIFESTUAC:"level='asInvoker' uiAccess='false'" /DEBUG /PDB:"D:\example\boost+openssl\Debug\boost+openssl.pdb" /SUBSYSTEM:CONSOLE /PGD:"D:\example\boost+openssl\Debug\boost+openssl.pgd" /TLBID:1 /DYNAMICBASE /NXCOMPAT /MACHINE:X86 /ERRORREPORT:QUEUE
Как правильно настроить линкер?
[Ответить]
dx:
Февраль 27th, 2012 at 19:03
Я тут не вижу линкования с simple_http.lib, собсна. Либо явно указать либу надо, либо прописать в исходнике проекта #pragma comment(lib, "simple_http")
[Ответить]
and1 :
Дык, 80 ошибок линкера при #pragma comment(lib,"simple_http.lib")...
[Ответить]
dx:
Февраль 28th, 2012 at 21:52
За это время можно было бы уже 100 раз разобраться. Если нет умения работать с бустом и вообще со студией - лучше за это дело даже не браться, прежде чем научишься что-то делать.
[Ответить]
and1 :
Да я просто не понимаю почему не получается... simple_http::http_sync_connection conn именно с этим проблема!
Менеджер куков свободно создается, буст работает! Может быть из за SSL? Хочу разобраться и понять, с Вашей помощью...
[Ответить]
dx:
Февраль 29th, 2012 at 19:33
Попробуй собрать из буста пример, работающий с SSL. Там есть вроде бы, в примерах для boost::asio.
Да и ты же говоришь, у тебя там 80 ошибок появляется, если слинковаться с моей либой... Значит, не только в конструкторе проблема, еще что-то не так?
[Ответить]
and1 :
Ошибки:
MSVCRTD.lib(ti_inst.obj) \\ на эти либы линковщик ругается
msvcprtd.lib(MSVCP100D.dll) \\ ошибкой errorLNK2005
после чего выдает error LNK1169: one or more multiply defined symbols found
Предупреждение:
warning LNK4098: defaultlib 'MSVCRTD' conflicts with use of other libs; use /NODEFAULTLIB:library
убирал эту либу- вообще беда)
simple_http::http_sync_connection conn; -это не работает
simple_http::cookie_manager manager;- это работает
либу и проект собирал с SSL_SUPPORT
[Ответить]
dx:
Март 1st, 2012 at 14:28
Уже сто раз писал, что ты буст/мою библиотеку/проект примера собираешь с разными настройками рантаймов студии. Повторяю это в очередной раз.
[Ответить]
111одинодинодинодин :
а с яваскриптом через него как-нибудь работать можно?
[Ответить]
dx:
Март 28th, 2012 at 16:11
Не
[Ответить]
KOLANICH :
либкурл не требует никакого буста, уже собран, с минимальными затратами поддерживает потоки (правда когда его юзал последний раз там был глюк в библиотеке, связанный с указателями, сейчас ), создаёт всего один лишний поток, на всё (количество потоков = ваши потоки +1), есть неблокирующийся режим и тд.
зачем изобретать велосипед?
[Ответить]
dx:
Апрель 26th, 2012 at 09:27
Дело в том, что если бы все имели такое мнение, как у тебя, то мы бы сейчас были лишены большого количества замечательных продуктов, а у тебя не было бы выбора, какие программы ставить на компьютер. Зачем было делать винамп, аимп и прочие плейеры, если есть виндовс медиа плеер, который тоже все играет? Ну и т.д.
Немного по теме: либкурл - это сишная библиотека, и плюсовые биндинги надо, во-первых, еще найти/выбрать, а потом еще и собрать, так как они собранными точно не будут поставляться - плюсовое abi в каждом компиляторе сильно отличается, поэтому каждый собирает для себя.
Не спорю, возможностей там больше, но это не значит, что не нужно делать никаких аналогов, свет клином на курле не сошелся.
[Ответить]
йцукен :
А при режиме прокси socks5 он будет работать с socks4? Аналогичный вопрос ещё и про HTTP & HTTPS.
[Ответить]
dx:
Апрель 29th, 2012 at 11:52
Не будет.
[Ответить]
niXman :
проект собирается переезжать на какую-нибудь CVS ?
[Ответить]
dx:
Май 16th, 2012 at 15:53
Пока что вряд ли.
[Ответить]