Я отправляю двоичные данные с клиента (Debian 6.0.3) на сервер (Windows Server 2003). Чтобы обойти большинство брандмауэров, я использую HTTPS POST. Клиент и сервер реализованы с использованием Boost.Asio и OpenSSL. Сначала я реализовал максимально простую версию, и она отлично работала.
Заголовок HTTP:
POST / HTTP/1.1
User-Agent: my custom client v.1
[binary data]
([binary data]
не кодируется base64, если это имеет значение)
Затем на другом клиентском компьютере это не удалось (подключено к тому же серверному компьютеру). Поведение не стабильное. Соединение всегда устанавливается нормально (порт 443). В большинстве случаев я успешно передаю SSL-рукопожатие, но сервер не получает данных (почти никаких данных, иногда действительно принимается пакет или два). Иногда я получаю ошибку рукопожатия SSL «короткое чтение». Иногда я получаю неверные данные.
Клиент подключается к серверу, рукопожатия, отправляет заголовок HTTP POST, а затем бесконечно отправляет двоичные данные, пока не произойдет что-то не так. Для теста я использую сгенерированный пользователем SSL-сертификат.
Код сервера:
namespace ssl = boost::asio::ssl;
ssl::context context(io_service, ssl::context::sslv23);
context.set_options(ssl::context::default_workarounds | ssl::context::no_sslv2);
context.use_certificate_chain_file("server.pem");
context.use_private_key_file("server.pem", boost::asio::ssl::context::pem);
ssl::stream<tcp::socket> socket(io_service, context);
// standard connection accepting
socket.async_handshake(ssl::stream_base::server, ...);
...
boost::asio::async_read_until(socket, POST_header, "\r\n\r\n", ...);
...
Код клиента:
ssl::context context(io_service, ssl::context::sslv23);
context.load_verify_file("server.crt");
socket.reset(new ssl::stream<tcp::socket>(io_service, context));
socket->set_verify_mode(ssl::verify_none);
// standard connection
socket.async_handshake(ssl::stream_base::client, ...);
...
(обработка ошибок опущена вместе с не относящимся к делу кодом)
Как видите, это самое простое SSL-соединение. Что не так? Может быть причина в фаерволе?
Я попробовал простой TCP без SSL через тот же порт 443, все работает нормально.
ИЗМЕНИТЬ:
Пробовал добавлять "Content-Type: application/octet-stream", не помогает.
РЕДАКТИРОВАТЬ 2:
Обычно я получаю заголовок HTTP POST в порядке. Затем я отправляю куски данных как chunk-size(4 bytes)chunk(chunk-size bytes)...
. Сервер получает chunk-size
отлично, но потом ничего. Клиент не уведомляет сервер о проблемах (нет ошибок) и продолжает отправлять данные. Иногда сервер может получить фрагмент или два, иногда он получает недействительные chunk-size
, но чаще всего ничего.
ИЗМЕНИТЬ 3:
Сравнил захваченный трафик на клиенте и сервере, разницы не нашел.
Решение
Я был введен в заблуждение с самого начала с этой проблемой. Сузил его до удивительных подробностей:
Отправка через сокет SSL завершается ошибкой, если я использую мультибуферы Boost.Asio в Boost v.1.48 (самый последний на данный момент). Пример:
// data to send, protocol is [packet size: 4 bytes][packet: packet_size bytes]
std::vector<char> packet = ...;
uint32_t packet_size = packet.size();
// prepare buffers
boost::array<boost::asio::const_buffer, 2> bufs = {{boost::asio::buffer(&packet_size, sizeof(packet_size)), boost::asio::buffer(packet)}};
// send multi buffers by single call
boost::asio::async_write(socket, bufs, ...);
Отправка по отдельности packet_size
и packet
в этом примере решает проблему. Я далек от того, чтобы называть любое подозрительное поведение ошибкой, особенно если оно связано с библиотеками Boost. Но этот действительно выглядит как ошибка. Пробовал на Boost v.1.47 - работает нормально. Пробовал с обычным сокетом TCP (не SSL) - работает нормально. То же самое и в Linux, и в Windows.
Я собираюсь найти любые сообщения об этой проблеме в списке рассылки Asio и сообщу об этом, если ничего не будет найдено.