Ну вот взять ув.тов.asfikon-а (он же Eax Melanhovich) и его свежий пост про работу с libcurl из C (вот целиком исходный файл, о котором пойдет речь). Хватило же у человека терпения и прилежания тщательно выписывать очистку ресурсов при обнаружении ошибок в работе с libcurl:
CURLcode res = curl_easy_perform(curl); if(res != CURLE_OK) { fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); writeFuncCtxFree(&ctx); curl_formfree(formFirstItem); curl_slist_free_all(curlHeaders); curl_easy_cleanup(curl); return 1; } |
Но вообще очевидно, что программирование в таком стиле -- это занятие настолько муторное, что нет-нет, да и забудешь где-нибудь код возврата проверить:
curl_easy_setopt(curl, CURLOPT_URL, "http://imageshack.us/upload_api.php"); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ctx); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, curlHeaders); curl_easy_setopt(curl, CURLOPT_HTTPPOST, formFirstItem); |
Поначалу может казаться, что тут все тривиально и ошибкам возникать неоткуда... Но откуда только что берется со временем ;)
Так вот, возвращаясь к основной мысли: в последние лет 5 регулярно замечаю, что многие люди, которые явно умнее меня, пишут код вот в таком вот простом стиле. Без изысков. Без попыток упростить себе жизнь. Ув.тов.asfikon просто хороший пример этого явления -- у него есть блог, где он публикует фрагменты своего кода, поэтому приводить примеры оттуда проще.
И не очень понятно почему так.
Сам я очень давно убедился в том, что не смогу нормально писать в таком стиле. Годах в 1991-1992-ом, когда более-менее серьезно попрограммировал на Pascal и C. Не хватает у меня терпения и внимательности для того, чтобы следить за тем количеством деталей, за которым приходится следить в C. Потому и ушел на C++ как только разобрался, что к чему.
А пример работы с libcurl хорош тем, что он иллюстрирует количество вещей, за которыми нужно следить при работе с C-шным API libcurl-а. Поэтому лет шесть назад, когда довелось втягивать libcurl в тогдашние проекты, дабы не заниматься всей этой низкоуровневой кухней, была написана простенькая C++ная библиотека, практически header-only. Под катом код примера из состава этой библиотеки.
Этот пример был написан где-то лет 5 или 6 назад, еще для C++03. Он демонстрирует основные вещи, связанные с libcurl и его curl_easy-интерфейсом: инициализация libcurl, создание и уничтожение объекта CURL, использование libcurl для escaping-а и unescaping-а строк, установка опций, установка callback-ов для получения HTTP-заголовков от сервера, установка debug-callback-а для трассировки выполняемых libcurl действий. Ну и, конечно же, выполнение HTTP GET и HTTP POST.
Коды ошибок для всех действий, связанных с libcurl контролируются автоматически. В случае возникновения проблем порождаются исключения. Дабы диагностика была более информативной, часть действий выполняется посредством макроса CURL_EASY_SAFE_SETOPT, который нужен для того, чтобы в исключение попадало не только описание ошибки от самой libcurl, но и название опции, при установке которой возникла ошибка. Так, если ошибка возникает в конструкции CURL_EASY_SAFE_SETOPT( handle, CURLOPT_POST, 1 ), то в описании ошибки будет присутствовать название "CURLOPT_POST".
Ну а вот, собственно, и код. Мне интересно, действительно ли такой C++ сложнее для понимания, чем чистый C? Или же, глядя на него "хочется обнять и плакать (с)"? ;)
#include <iostream> #include <stdexcept> #include <sstream> #include <libcurl_tools_1/h/easy.hpp> using namespace libcurl_tools_1; class cerr_debug_functor_t : public easy_debug_functor_t { public : virtual int handle( curl_infotype type, cpp_util_2::string_piece_t what ) { switch( type ) { case CURLINFO_TEXT : std::cerr << "# "; break; case CURLINFO_HEADER_IN : std::cerr << "> "; break; case CURLINFO_HEADER_OUT : std::cerr << "< "; break; }; std::cerr.write( what.data(), what.size() ); return 0; } }; class cout_header_functor_t : public easy_header_functor_t { public : virtual size_t handle( cpp_util_2::string_piece_t what ) { std::cout << "HTTP Header: '" << what.to_string() << "'" << std::endl; return what.size(); } }; void do_escape_unescape_test( const easy_handle_t & handle ) { const std::string source_str = "This is a source string + some symbols: =!@#$%^&*()"; easy_url_escaped_string_t escaped( handle, source_str ); easy_url_unescaped_string_t unescaped( handle, escaped.get() ); std::cout << "URL escape/unescape test:\n" "\tsource: " << source_str << "\n\tescaped: " << escaped.get() << "\n\tunescaped: " << unescaped.get().to_string() << std::endl; } void do_test_get( const easy_handle_t & handle ) { easy_auto_reseter_t option_reseter( handle ); cerr_debug_functor_t debug_functor; easy_setup_debug_functor( handle, debug_functor ); cout_header_functor_t header_functor; easy_setup_header_functor( handle, header_functor ); std::stringstream response_stream; easy_setup_std_ostream_as_writedata( handle, response_stream ); const char * url = "http://www.ya.ru"; CURL_EASY_SAFE_SETOPT( handle, CURLOPT_URL, url ); easy_perform_with_error_buffer( handle ); std::cout << "RESULT is: " << std::endl << response_stream.str() << std::endl; } void do_test_post( const easy_handle_t & handle ) { easy_auto_reseter_t option_reseter( handle ); cerr_debug_functor_t debug_functor; easy_setup_debug_functor( handle, debug_functor ); std::stringstream output_stream( std::ios_base::in | std::ios_base::out | std::ios_base::binary ); std::stringstream response_stream; output_stream << "<message bearer=\"HTTP\" uid=\"CENSORED\">\n" "\t<sn>42</sn>\n" "\t<payload>CENSORED</payload>\n" "</message>\n"; rewindable_istream_as_readdata_t< std::stringstream > readdata( entire_istream( output_stream ) ); readdata.setup_to_curl_handle( handle ); easy_setup_std_ostream_as_writedata( handle, response_stream ); const char * url = "http://localhost:8080/simple_echo.rb"; CURL_EASY_SAFE_SETOPT( handle, CURLOPT_URL, url ); slist_wrapper_t headers; headers.append( "Content-Type: text/xml" ).append( "Connection: Keep-Alive" ); CURL_EASY_SAFE_SETOPT( handle, CURLOPT_HTTPHEADER, headers.head() ); CURL_EASY_SAFE_SETOPT( handle, CURLOPT_POST, 1 ); CURL_EASY_SAFE_SETOPT( handle, CURLOPT_POSTFIELDSIZE, static_cast< curl_off_t >( output_stream.str().size() ) ); easy_perform_with_error_buffer( handle ); std::cout << "RESULT is: " << std::endl << response_stream.str() << std::endl; } int main( int argc, char ** argv ) { try { global_initializer_t curl_initializer; easy_handle_t handle; do_escape_unescape_test( handle ); do_test_get( handle ); do_test_post( handle ); do_test_post( handle ); } catch( const std::exception & x ) { std::cerr << "*** exception: " << x.what() << std::endl; } } |
Еще позволю себе несколько пояснений по коду.
В main-е не зря делается два вызова do_test_post(), это проверка для работы такой штуки, как easy_auto_reseter_t. Можно заметить, что в начале do_test_get и do_test_post объявляется переменная типа easy_auto_reseter_t. Деструктор этой переменной автоматически вызывает curl_easy_reset(), что сбрасывает все изменения, внесенные в конкретный экземпляр CURL внутри функции. Бывает очень полезно, когда один и тот же CURL-объект используется в разных операциях.
При осуществлении HTTP POST используется rewindable_istream_as_readdata_t. Насколько я помню, этот класс появился как следствие обнаружения вот этой проблемы при использовании istream-ов совместно с Keep-Alive. Данный класс позволяет автоматически вернутся к началу отсылаемого на сервер фрагмента, если был обнаружен разрыв старого соединения и создание нового.
Для выполнения GET и POST запросов используется функция easy_perform_with_error_buffer. Эта функция устанавливает CURLOPT_ERRORBUFFER до вызова curl_easy_perform, затем сбрасывает эту опцию. Если же curl_easy_perform завершается неудачно, то при выбрасывании исключения используется содержимое error_buffer-а (там, как правило, оказывается более-менее вменяемое описание причины проблемы от самого libcurl).
Вообще, от работы с libcurl остались самые хорошие воспоминания (но из-за того, что работа с libcurl велась через собственную обертку). Жаль, что libcurl/curl не сделали их автора обеспеченным человеком, что, в прочем, обычное дело для реально полезных вещей. Главная претензия, которая у меня была в свое время к libcurl -- это использование autoconf. Из-за чего под Windows каждую версию библиотеки приходилось дорабатывать напильником (может сейчас что-то поменялось, не знаю).
Вроде как в свое время были какие-то плюсовые обертки над libcurl, но мне что-то они не понравились, проще оказалось сделать свой лисапет :)
Комментариев нет:
Отправить комментарий