如何通过HTTPS POST正确发送二进制数据?
我将二进制数据从客户端(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]
([二进制数据]
不是 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 连接。怎么了?原因可能是防火墙吗?
我在同一个 443 端口上尝试了简单的 TCP(不带 SSL),效果很好。
编辑:
尝试添加“Content-Type:application/octet-stream”,但没有帮助。
编辑2:
通常我会很好地收到HTTP POST标头。然后我将数据块发送为 chunk-size(4 bytes)chunk(chunk-size bytes)...
。服务器收到 chunk-size
正常,但随后什么也没有。客户端不会通知服务器问题(没有错误)并继续发送数据。有时服务器可以接收一两个块,有时它接收到无效的块大小,但大多数时候什么也没有。
编辑3:
比较客户端和服务器上捕获的流量,没有发现任何差异。
解决方案
我从一开始就被这个问题误导了。将其缩小到令人惊讶的细节:
如果我在 Boost v.1.48(目前最新的版本)中使用 Boost.Asio 多缓冲区,则通过 SSL 套接字发送会失败。示例:
// 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 邮件列表中查找有关此问题的任何报告,如果没有找到,我将报告它。
I send binary data from client (Debian 6.0.3) to server (Windows Server 2003). To bypass most firewalls I use HTTPS POST. Client and server are implemented using Boost.Asio and OpenSSL. First I implemented the simplest possible version and it worked fine.
HTTP Header:
POST / HTTP/1.1
User-Agent: my custom client v.1
[binary data]
([binary data]
is not base64 encoded if this matters)
Then, on another client machine it failed (connected to the same server machine). The behavior is not stable. Connection always is established fine (port 443). Most time I pass SSL handshake fine but server receive no data (almost no data, sometimes a packet or two are actually received). Sometimes I receive SSL handshake error "short read". Sometimes I receive invalid data.
Client connects to server, handshakes, sends HTTP POST header and then infinitely sends binary data until something wrong hapenned. For test I use custom generated SSL certificate.
Server code:
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", ...);
...
Client code:
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, ...);
...
(error handling is omitted along with not relevant code)
As you can see, it's the simplest possible SSL connection. What is wrong? Can the reason be a firewall?
I tried simple TCP w/o SSL over the same 443 port, this works fine.
EDIT:
Tried adding "Content-Type: application/octet-stream", doesn't help.
EDIT 2:
Usually I receive HTTP POST header fine. Then I send data chunks as chunk-size(4 bytes)chunk(chunk-size bytes)...
. Server receives chunk-size
fine, but then nothing. Client doesn't notify server problems (no errors) and continue to send data. Sometimes server can receive chunk or two, sometimes it receives invalid chunk-size
, but most time just nothing.
EDIT 3:
Compared captured traffic on client and server, didn't find any differences.
Solution
I was misled from the start with this problem. Narrowed it down to surprising details:
Sending over SSL socket fails if I use Boost.Asio multi-buffers in Boost v.1.48 (the most recent one at this moment). Example:
// 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, ...);
Sending separately packet_size
and packet
in this example works around the problem. I'm far from calling any suspicious behavior as a bug, especially if it's related with Boost libraries. But this one really looks like a bug. Tried on Boost v.1.47 - works fine. Tried with usual TCP socket (not SSL one) - works fine. The same on both Linux and Windows.
I'm going to find any reports about this problem in Asio mailing list and will report it if nothing found.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
如果你不需要在Web服务器前面操作,那么你就没有
使用 HTTPS 协议。从防火墙的角度来看 HTTPS 看起来还像
另一个 SSL 连接,它不知道发生了什么。所以如果
您唯一需要的只是传递数据 - 而不是实际的网络服务器,请使用
只是通过 443 端口的 SSL 连接。
因此,只需对 SSL 连接进行故障排除即可,该问题与 HTTP 无关。
如果您想使用 HTTP Web 服务器而不是自定义客户端:
两点:
最简单的是
对于 HTTP/1.1,
Or 注意: 您无法发送无限数据。 HTTP协议需要固定的内容长度
大多数时候,Web 服务器会先加载数据,然后再将其传递给
后端。
因此,您必须按块传输数据。
If you don't have to operate in front of web server, you don't have
to use HTTPS protocol. From the firewall point of view HTTPS looks like yet
another SSL connection and it has no idea what going through. So if the
only thing you need is just to pass the data - not to actual web server, use
just SSL connection over 443 port.
So just troubleshoot your SSL connection the problem has nothing to do with HTTP.
If you want to use HTTP web server and not custom client:
Two points:
The simplest would be
Or for HTTP/1.1
Note: you can't send infinite data. HTTP protocol requires fixed content-lenght
and most of the time web servers would load the data first before passing it to the
backend.
So you will have to transfer data by chunks.
我从一开始就被这个问题误导了。将其缩小到令人惊讶的细节:
如果我在 Boost v.1.48(目前最新的版本)中使用 Boost.Asio 多缓冲区,则通过 SSL 套接字发送会失败。示例:
在此示例中单独发送
packet_size
和packet
可以解决该问题。我绝不会将任何可疑行为称为错误,尤其是与 Boost 库相关的行为。但这看起来确实像一个错误。尝试过 Boost v.1.47 - 工作正常。尝试使用普通的 TCP 套接字(不是 SSL 套接字)- 工作正常。 Linux 和 Windows 上都是一样的。我将在 Asio 邮件列表中查找有关此问题的任何报告,如果没有找到,我将报告它。
I was misled from the start with this problem. Narrowed it down to surprising details:
Sending over SSL socket fails if I use Boost.Asio multi-buffers in Boost v.1.48 (the most recent one at this moment). Example:
Sending separately
packet_size
andpacket
in this example works around the problem. I'm far from calling any suspicious behavior as a bug, especially if it's related with Boost libraries. But this one really looks like a bug. Tried on Boost v.1.47 - works fine. Tried with usual TCP socket (not SSL one) - works fine. The same on both Linux and Windows.I'm going to find any reports about this problem in Asio mailing list and will report it if nothing found.
(编辑:我最初删除了这个,因为我意识到它并没有真正使用 HTTP。在您认为您可能有 MITM 代理并且应该使用正确的 HTTP 的评论之后,我取消删除/编辑。)
是否是二进制数据无论是否,在纯 HTTP 或 SSL/TLS 中,您都需要
Content-Length
标头或使用 分块传输编码。 HTTP 规范的本节。Content-Type
标头也很有用。分块传输编码适用于您不一定知道流的长度的情况。 (发送数据时,您总是需要某种形式的分隔符,只是为了可靠地检测数据何时结束。)
话虽这么说,您应该能够找出您是否位于 MITM 代理后面,该代理会在之上查看应用程序层SSL/TLS(如果您获得的证书不是您的服务器)。如果您仍然与赢得的服务器证书成功握手,则不存在这样的代理。即使是 HTTP 代理也会使用 CONNECT 并中继所有内容,而无需更改 SSL/TLS 连接(因此不会更改顶部的原始伪 HTTP)。
(EDIT: I had originally deleted this because I had realised it wasn't using HTTP really. Following a comment where you think you might have a MITM proxy and should use proper HTTP, I'm undeleting/editing.)
Whether it's binary data or not, in plain HTTP or with SSL/TLS, you'll need a
Content-Length
header or to use chunked transfer encoding. This this section of the HTTP spec. AContent-Type
header would be useful too.Chunked transfer encoding is for when you don't necessarily know the length of the stream. (You always need some form of delimiters when sending data, if only to detect reliably when it ends.)
This being said, you should be able to find out whether you're behind a MITM proxy that looks into the application layer on top of SSL/TLS if you get a certificate that's not your servers. If you do still get a successful handshake with your won server cert, there isn't such a proxy. Even an HTTP proxy would use
CONNECT
and relay everything, without altering the SSL/TLS connection (and thus without altering your original pseudo-HTTP on top).