Как правильно отправлять двоичные данные через HTTPS POST? - PullRequest
13 голосов
/ 03 февраля 2012

Я отправляю двоичные данные с клиента (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, все работает нормально.

EDIT:

Попытка добавления "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 и сообщу, если ничего не найдено.

Ответы [ 3 ]

7 голосов
/ 03 февраля 2012

Если вам не нужно работать перед веб-сервером, вам не нужно использовать протокол HTTPS.С точки зрения брандмауэра HTTPS выглядит как еще одно SSL-соединение, и он понятия не имеет, что происходит.Поэтому, если вам нужно только передать данные, а не реальный веб-сервер, используйте только SSL-соединение через порт 443.

Так что просто устраните неполадки в вашем SSL-соединении, проблема не имеет никакого отношения к HTTP.


Если вы хотите использовать веб-сервер HTTP, а не пользовательский клиент:

Две точки:

  1. Необходимо указать длину содержимого.
  2. Если вы используете HTTP / 1.1, вам нужно указать заголовок хоста.

Самым простым будет

POST /url HTTP/1.0
User-Agent: my custom client v.1
Content-Type: application/octet-stream
Content-Length: NNN

Actual Content

Или для HTTP / 1.1

POST /url HTTP/1.1
Host: www.example.com
User-Agent: my custom client v.1
Content-Type: application/octet-stream
Content-Length: NNN

Actual Content

Примечание: вы не можете отправлять бесконечные данные.Протокол HTTP требует фиксированной длины содержимого, и большую часть времени веб-серверы сначала загружают данные перед передачей их бэкэнду.

Таким образом, вам придется передавать данные порциями.

4 голосов
/ 15 февраля 2012

Я был сбит с толку с самого начала с этой проблемой. Сузили до удивительных деталей:

Отправка через сокет 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 и сообщу, если ничего не найдено.

1 голос
/ 03 февраля 2012

(РЕДАКТИРОВАТЬ: Первоначально я удалил это, потому что я понял, что он действительно не использует HTTP. После комментария, где вы думаете, что у вас может быть прокси MITM и должен использовать правильный HTTP, я удаляю / редактирую.)

POST / HTTP/1.1
User-Agent: my custom client v.1

[binary data]

Будь то двоичные данные или нет, в простом HTTP или с SSL / TLS, вам понадобится заголовок Content-Length или для использования кодированной передачи chunked .Это этот раздел спецификации HTTP.Заголовок Content-Type также был бы полезен.

Кодировка передачи по частям предназначена для случаев, когда вы не обязательно знаете длину потока.(При отправке данных вам всегда нужна какая-либо форма разделителей, хотя бы для надежного обнаружения, когда они заканчиваются.)

При этом вы должны быть в состоянии выяснить, находитесь ли вы за прокси-сервером MITM, который просматриваетприкладной уровень поверх SSL / TLS, если вы получаете сертификат, который не является вашим сервером.Если вы все еще получаете успешное рукопожатие с вашим выигранным сертификатом сервера, такой прокси не существует.Даже HTTP-прокси будет использовать CONNECT и ретранслировать все, не изменяя соединение SSL / TLS (и, следовательно, не изменяя свой исходный псевдо-HTTP сверху).

...