Преамбула. Необходимо было через libcurl делать HTTP POST запросы и получать ответы от HTTP-сервера. Тело POST-запроса подготавливалось в std::stringstream. Для того, чтобы отдать его содержимое библиотеке libcurl я сделал специальную вспомогательную функцию, приблизительно такого вида:
size_t std_istream_read_function( void * to, size_t size, size_t nmemb, void * raw_stream_pointer ) { std::istream * from = reinterpret_cast< std::istream * >( raw_stream_pointer ); if( !from ) return CURL_READFUNC_ABORT; if( !(*from) ) if( from->eof() ) return 0; else return CURL_READFUNC_ABORT; const size_t capacity = size * nmemb; from->read( reinterpret_cast< char * >( to ), capacity ); return from->gcount(); } |
А затем регистрировал данную функцию в качестве параметра CURLOPT_READFUNCTION (а в качестве CURLOPT_READDATA передавал указатель на stringstream). Соответственно, когда libcurl устанавливал соединение, проходил HTTP-аутентификацию и отсылал на удаленную сторону HTTP-заголовки, он вызывал мою функцию и получал от меня тело POST-а.
Теперь амбула :)
Все это дело красиво работало. Но не всегда. Часть запросов почему-то завершалась неудачно. После чего спецы, которые обслуживали HTTP-сервер, стали жаловаться, что в некоторых случаях от нас приходят только HTTP-заголовки, но нет тела POST-а.
Разбирательство показало, что в этих случаях происходит следующее:
- для взаимодействия с HTTP-сервером используются Keep-Alive соединения;
- 1, 2, 3,…, (i-1)-ый обмен с HTTP-сервером проходят без сучка и задоринки;
- между (i-1)-м и i-м запросами проходит несколько десятков секунд;
- при выполнении i-го запроса libcurl передает HTTP-заголовки и тело POST, после чего libcurl:
- пишет, что текущее соединение с сервером оказалось разорвано;
- предупреждает о том, что попытается восстановить соединение;
- сообщает, что соединение восстановлено;
- отсылает HTTP-заголовки;
- некоторое время ничего не делает;
- получает ответ от удаленной стороны.
Т.е., действительно, в таких случаях от нас тело POST-а не уходило повторно. По сообщениям libcurl ситуация выглядит приблизительно так:
# Re-using existing connection! (#0) with host YYY
# Connected to YYY (zzz.zzz.zzz.zzz) port 8080 (#0)
< POST /bla-bla-bla HTTP/1.1
< Content-Type: …
< Content-Length:…
<
< <?xml>…</xml>
# Connection died, retryinga fresh connect
# Closing connection #0
# Issue another request to this URL: 'http://YYY:8080/bla-bla-bla'
# About to connect() to YYY port 8080 (#0)
# Trying 193.41.60.77...
# connected
# Connected to YYY (zzz.zzz.zzz.zzz) port 8080 (#0)
< POST /bla-bla-bla HTTP/1.1
< Content-Type: …
< Content-Length:…
# HTTP 1.0, assume close after body
> HTTP/1.0 200 OK
> Server: Apache-Coyote/1.1
> Content-Type: text/xml;charset=ISO-8859-1
> Content-Length: 152
…
Т.е. соединение висело без дела достаточно времени, удаленная сторона его прибила (или оно просто само умерло из-за каких-то сетевых неприятностей). При попытке переиспользования соединения libcurl это диагностировал и попытался восстановить соединение и передать данные заново. Только почему-то не передал.
А не передал по простой причине – моя функция при новом обращении к ней от libcurl-а ничего больше не отдала.
Ларчик, как водится, открывался просто – во время первого обращения к std_istream_read_function был достигнут конец входного потока (у объекта stringstream был установлен eofbit). А посему последующие обращения сразу же обламывались.
Вот такая вот неожиданная для меня особенность работы libcurl. И, что обидно, нигде не замечал в документации описания того, что libcurl может обращаться к ней повторно для того, чтобы перечитать поток заново с самого начала.
В процессе разбирательства с libcurl выяснилось, что в нее в версии 7.18.0 была добавлена специальная опция CURLOPT_SEEKFUNCTION, которая специально для таких вещей предназначена – для перехода на конкретную позицию передаваемых на HTTP-сервер данных. Но, как показали эксперименты, для POST-запросов она не используется. Поскольку предназначена для операций upload (т.е. HTTP PUT-запросов) и для случаев с разрывами keep-alive соединений она не задействуется.
Посему пришлось вносить в свою функцию несколько строк:
if( !(*from) ) if( from->eof() ) { // Для того, чтобы можно было начать читать поток заново, // если это потребуется из-за разрыва keep-alive соединения. from->clear(); from->seekg( 0 ); return 0; } else return CURL_READFUNC_ABORT; |
После чего все заработало.
PS. Логгирования мало не бывает! :)