我正在使用boost tcp :: iostream(充当HTTP服务器)为使用输入和输出的代码添加HTTPS支持。
我找到了使用boost :: asio :: read / boost :: asio :: write进行SSL输入/输出的示例(并且有一个工作的玩具HTTPS服务器),但没有使用iostreams和<< ; >>运营商。如何将ssl :: stream转换为iostream?
工作代码:
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/foreach.hpp>
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
using namespace boost;
using boost::asio::ip::tcp;
typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_stream;
string HTTPReply(int nStatus, const string& strMsg)
{
string strStatus;
if (nStatus == 200) strStatus = "OK";
else if (nStatus == 400) strStatus = "Bad Request";
else if (nStatus == 404) strStatus = "Not Found";
else if (nStatus == 500) strStatus = "Internal Server Error";
ostringstream s;
s << "HTTP/1.1 " << nStatus << " " << strStatus << "\r\n"
<< "Connection: close\r\n"
<< "Content-Length: " << strMsg.size() << "\r\n"
<< "Content-Type: application/json\r\n"
<< "Date: Sat, 09 Jul 2009 12:04:08 GMT\r\n"
<< "Server: json-rpc/1.0\r\n"
<< "\r\n"
<< strMsg;
return s.str();
}
int main()
{
// Bind to loopback 127.0.0.1 so the socket can only be accessed locally
boost::asio::io_service io_service;
tcp::endpoint endpoint(boost::asio::ip::address_v4::loopback(), 1111);
tcp::acceptor acceptor(io_service, endpoint);
boost::asio::ssl::context context(io_service, boost::asio::ssl::context::sslv23);
context.set_options(
boost::asio::ssl::context::default_workarounds
| boost::asio::ssl::context::no_sslv2);
context.use_certificate_chain_file("server.cert");
context.use_private_key_file("server.pem", boost::asio::ssl::context::pem);
for(;;)
{
// Accept connection
ssl_stream stream(io_service, context);
tcp::endpoint peer_endpoint;
acceptor.accept(stream.lowest_layer(), peer_endpoint);
boost::system::error_code ec;
stream.handshake(boost::asio::ssl::stream_base::server, ec);
if (!ec) {
boost::asio::write(stream, boost::asio::buffer(HTTPReply(200, "Okely-Dokely\n")));
// I really want to write:
// iostream_object << HTTPReply(200, "Okely-Dokely\n") << std::flush;
}
}
}
似乎ssl :: stream_service就是答案,但那是一个死胡同。
使用boost :: iostreams(按照接受的答案建议)是正确的方法;这是我最终得到的工作代码:
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/iostreams/concepts.hpp>
#include <boost/iostreams/stream.hpp>
#include <sstream>
#include <string>
#include <iostream>
using namespace boost::asio;
typedef ssl::stream<ip::tcp::socket> ssl_stream;
//
// IOStream device that speaks SSL but can also speak non-SSL
//
class ssl_iostream_device : public boost::iostreams::device<boost::iostreams::bidirectional> {
public:
ssl_iostream_device(ssl_stream &_stream, bool _use_ssl ) : stream(_stream)
{
use_ssl = _use_ssl;
need_handshake = _use_ssl;
}
void handshake(ssl::stream_base::handshake_type role)
{
if (!need_handshake) return;
need_handshake = false;
stream.handshake(role);
}
std::streamsize read(char* s, std::streamsize n)
{
handshake(ssl::stream_base::server); // HTTPS servers read first
if (use_ssl) return stream.read_some(boost::asio::buffer(s, n));
return stream.next_layer().read_some(boost::asio::buffer(s, n));
}
std::streamsize write(const char* s, std::streamsize n)
{
handshake(ssl::stream_base::client); // HTTPS clients write first
if (use_ssl) return boost::asio::write(stream, boost::asio::buffer(s, n));
return boost::asio::write(stream.next_layer(), boost::asio::buffer(s, n));
}
private:
bool need_handshake;
bool use_ssl;
ssl_stream& stream;
};
std::string HTTPReply(int nStatus, const std::string& strMsg)
{
std::string strStatus;
if (nStatus == 200) strStatus = "OK";
else if (nStatus == 400) strStatus = "Bad Request";
else if (nStatus == 404) strStatus = "Not Found";
else if (nStatus == 500) strStatus = "Internal Server Error";
std::ostringstream s;
s << "HTTP/1.1 " << nStatus << " " << strStatus << "\r\n"
<< "Connection: close\r\n"
<< "Content-Length: " << strMsg.size() << "\r\n"
<< "Content-Type: application/json\r\n"
<< "Date: Sat, 09 Jul 2009 12:04:08 GMT\r\n"
<< "Server: json-rpc/1.0\r\n"
<< "\r\n"
<< strMsg;
return s.str();
}
void handle_request(std::iostream& s)
{
s << HTTPReply(200, "Okely-Dokely\n") << std::flush;
}
int main(int argc, char* argv[])
{
bool use_ssl = (argc <= 1);
// Bind to loopback 127.0.0.1 so the socket can only be accessed locally
io_service io_service;
ip::tcp::endpoint endpoint(ip::address_v4::loopback(), 1111);
ip::tcp::acceptor acceptor(io_service, endpoint);
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.cert");
context.use_private_key_file("server.pem", ssl::context::pem);
for(;;)
{
ip::tcp::endpoint peer_endpoint;
ssl_stream _ssl_stream(io_service, context);
ssl_iostream_device d(_ssl_stream, use_ssl);
boost::iostreams::stream<ssl_iostream_device> ssl_iostream(d);
// Accept connection
acceptor.accept(_ssl_stream.lowest_layer(), peer_endpoint);
std::string method;
std::string path;
ssl_iostream >> method >> path;
handle_request(ssl_iostream);
}
}
答案 0 :(得分:15)
@Guy的建议(使用boost::asio::streambuf
)应该有效,而且它可能是最容易实现的。这种方法的主要缺点是,写入iostream的所有内容都将缓存在内存中直到结束,此时对boost::asio::write()
的调用将立即将缓冲区的全部内容转储到ssl流上。 (我应该注意到,在许多情况下,这种缓冲实际上是可取的,并且在你的情况下,它可能没有任何区别,因为你已经说过它是一个小批量的应用程序)。
如果这只是“一次性”,我可能会使用@ Guy的方法实现它。
话虽如此 - 有很多很好的理由让您可能更倾向于使用允许您使用iostream调用直接写入ssl_stream
的解决方案。如果您发现是这种情况,那么您需要构建自己的包装类,该类扩展std::streambuf
,覆盖overflow()
和sync()
(可能还有其他人根据您的需要)
幸运的是,boost::iostreams
提供了一种相对简单的方法来实现这一点,而无需直接使用std类。您只需构建自己的类来实现相应的Device
合同。在这种情况下,Sink
和boost::iostreams::sink
类被提供作为获取大部分路径的便捷方式。一旦你有了一个新的Sink类来封装写入底层ssl_stream的过程,你所要做的就是创建一个boost::iostreams::stream
来模仿你的新设备类型,然后离开。
它看起来类似于以下内容(此示例改编自here,另见this related stackoverflow post):
//---this should be considered to be "pseudo-code",
//---it has not been tested, and probably won't even compile
//---
#include <boost/iostreams/concepts.hpp>
// other includes omitted for brevity ...
typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_stream;
class ssl_iostream_sink : public sink {
public:
ssl_iostream_sink( ssl_stream *theStream )
{
stream = theStream;
}
std::streamsize write(const char* s, std::streamsize n)
{
// Write up to n characters to the underlying
// data sink into the buffer s, returning the
// number of characters written
boost::asio::write(*stream, boost::asio::buffer(s, n));
}
private:
ssl_stream *stream;
};
现在,您的接受循环可能会更改为如下所示:
for(;;)
{
// Accept connection
ssl_stream stream(io_service, context);
tcp::endpoint peer_endpoint;
acceptor.accept(stream.lowest_layer(), peer_endpoint);
boost::system::error_code ec;
stream.handshake(boost::asio::ssl::stream_base::server, ec);
if (!ec) {
// wrap the ssl stream with iostream
ssl_iostream_sink my_sink(&stream);
boost::iostream::stream<ssl_iostream_sink> iostream_object(my_sink);
// Now it works the way you want...
iostream_object << HTTPReply(200, "Okely-Dokely\n") << std::flush;
}
}
该方法将ssl流挂钩到iostream框架中。所以现在你应该能够在上面的例子中对iostream_object
做任何事情,你通常会做任何其他std::ostream
(比如stdout)。你写给它的东西将被写入幕后的ssl_stream。 Iostreams具有内置缓冲功能,因此内部会发生某种程度的缓冲 - 但这是一件好事 - 它会缓冲直到它累积了一些合理数量的数据,然后它会将它转储到ssl流上,并且回去缓冲。最后的std :: flush,应该强制它将缓冲区清空到ssl_stream。
如果您需要更多地控制内部缓冲(或任何其他高级内容),请查看boost::iostreams
中提供的其他很酷的内容。具体来说,您可以从查看stream_buffer
开始。
答案 1 :(得分:2)
我认为你想要做的是使用流缓冲区(asio :: streambuf)
然后你可以做类似的事情(随后编写的未经测试的代码):
boost::asio::streambuf msg;
std::ostream msg_stream(&msg);
msg_stream << "hello world";
msg_stream.flush();
boost::asio::write(stream, msg);
类似地,您的读/写端可以与std :: istream一起读入流缓冲区,因此您可以使用各种流函数/运算符来处理输入。
另一个注意事项是我认为你应该查看asio教程/示例。完成后,您可能希望将代码更改为异步工作,而不是上面显示的同步示例。
答案 2 :(得分:1)
ssl :: stream可以用boost :: iostreams / bidirectional包装,以模仿与tcp :: iostream类似的行为。在进一步阅读之前冲洗输出似乎是无法避免的。
#include <regex>
#include <string>
#include <iostream>
#include <boost/iostreams/stream.hpp>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
namespace bios = boost::iostreams;
namespace asio = boost::asio;
namespace ssl = boost::asio::ssl;
using std::string;
using boost::asio::ip::tcp;
using boost::system::system_error;
using boost::system::error_code;
int parse_url(const std::string &s,
std::string& proto, std::string& host, std::string& path)
{
std::smatch m;
bool found = regex_search(s, m, std::regex("^(http[s]?)://([^/]*)(.*)$"));
if (m.size() != 4)
return -1;
proto = m[1].str();
host = m[2].str();
path = m[3].str();
return 0;
}
void get_page(std::iostream& s, const string& host, const string& path)
{
s << "GET " << path << " HTTP/1.0\r\n"
<< "Host: " << host << "\r\n"
<< "Accept: */*\r\n"
<< "Connection: close\r\n\r\n" << std::flush;
std::cout << s.rdbuf() << std::endl;;
}
typedef ssl::stream<tcp::socket> ssl_socket;
class ssl_wrapper : public bios::device<bios::bidirectional>
{
ssl_socket& sock;
public:
typedef char char_type;
ssl_wrapper(ssl_socket& sock) : sock(sock) {}
std::streamsize read(char_type* s, std::streamsize n) {
error_code ec;
auto rc = asio::read(sock, asio::buffer(s,n), ec);
return rc;
}
std::streamsize write(const char_type* s, std::streamsize n) {
return asio::write(sock, asio::buffer(s,n));
}
};
int main(int argc, char* argv[])
{
std::string proto, host, path;
if (argc!= 2 || parse_url(argv[1], proto, host, path)!=0)
return EXIT_FAILURE;
try {
if (proto != "https") {
tcp::iostream s(host, proto);
s.expires_from_now(boost::posix_time::seconds(60));
get_page(s, host, path);
} else {
asio::io_service ios;
tcp::resolver resolver(ios);
tcp::resolver::query query(host, "https");
tcp::resolver::iterator endpoint_iterator =
resolver.resolve(query);
ssl::context ctx(ssl::context::sslv23);
ctx.set_default_verify_paths();
ssl_socket socket(ios, ctx);
asio::connect(socket.lowest_layer(), endpoint_iterator);
socket.set_verify_mode(ssl::verify_none);
socket.set_verify_callback(ssl::rfc2818_verification(host));
socket.handshake(ssl_socket::client);
bios::stream<ssl_wrapper> ss(socket);
get_page(ss, host, path);
}
} catch (const std::exception& e) {
std::cout << "Exception: " << e.what() << "\n";
}
}