Client now uses asynchronous asio read/write calls
This commit is contained in:
parent
743785b563
commit
2f32a2b52f
3 changed files with 180 additions and 98 deletions
225
client_http.hpp
225
client_http.hpp
|
|
@ -65,18 +65,28 @@ namespace SimpleWeb {
|
||||||
write_stream << "Content-Length: " << content.size() << "\r\n";
|
write_stream << "Content-Length: " << content.size() << "\r\n";
|
||||||
write_stream << "\r\n";
|
write_stream << "\r\n";
|
||||||
|
|
||||||
try {
|
connect();
|
||||||
connect();
|
|
||||||
|
boost::asio::async_write(*socket, write_buffer,
|
||||||
boost::asio::write(*socket, write_buffer);
|
[this, &content](const boost::system::error_code &ec, size_t /*bytes_transferred*/) {
|
||||||
if(content.size()>0)
|
if(!ec) {
|
||||||
boost::asio::write(*socket, boost::asio::buffer(content.data(), content.size()));
|
if(!content.empty()) {
|
||||||
|
boost::asio::async_write(*socket, boost::asio::buffer(content.data(), content.size()),
|
||||||
}
|
[this](const boost::system::error_code &ec, size_t /*bytes_transferred*/) {
|
||||||
catch(const std::exception& e) {
|
if(ec) {
|
||||||
socket_error=true;
|
socket=nullptr;
|
||||||
throw std::invalid_argument(e.what());
|
throw boost::system::system_error(ec);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
socket=nullptr;
|
||||||
|
throw boost::system::system_error(ec);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
io_service.reset();
|
||||||
|
io_service.run();
|
||||||
|
|
||||||
return request_read();
|
return request_read();
|
||||||
}
|
}
|
||||||
|
|
@ -104,15 +114,15 @@ namespace SimpleWeb {
|
||||||
if(content_length>0)
|
if(content_length>0)
|
||||||
write_stream << content.rdbuf();
|
write_stream << content.rdbuf();
|
||||||
|
|
||||||
try {
|
boost::asio::async_write(*socket, write_buffer,
|
||||||
connect();
|
[this](const boost::system::error_code &ec, size_t /*bytes_transferred*/) {
|
||||||
|
if(ec) {
|
||||||
boost::asio::write(*socket, write_buffer);
|
socket=nullptr;
|
||||||
}
|
throw boost::system::system_error(ec);
|
||||||
catch(const std::exception& e) {
|
}
|
||||||
socket_error=true;
|
});
|
||||||
throw std::invalid_argument(e.what());
|
io_service.reset();
|
||||||
}
|
io_service.run();
|
||||||
|
|
||||||
return request_read();
|
return request_read();
|
||||||
}
|
}
|
||||||
|
|
@ -122,14 +132,12 @@ namespace SimpleWeb {
|
||||||
boost::asio::ip::tcp::endpoint endpoint;
|
boost::asio::ip::tcp::endpoint endpoint;
|
||||||
boost::asio::ip::tcp::resolver resolver;
|
boost::asio::ip::tcp::resolver resolver;
|
||||||
|
|
||||||
std::shared_ptr<socket_type> socket;
|
std::unique_ptr<socket_type> socket;
|
||||||
bool socket_error;
|
|
||||||
|
|
||||||
std::string host;
|
std::string host;
|
||||||
unsigned short port;
|
unsigned short port;
|
||||||
|
|
||||||
ClientBase(const std::string& host_port, unsigned short default_port) :
|
ClientBase(const std::string& host_port, unsigned short default_port) : resolver(io_service) {
|
||||||
resolver(io_service), socket_error(false) {
|
|
||||||
size_t host_end=host_port.find(':');
|
size_t host_end=host_port.find(':');
|
||||||
if(host_end==std::string::npos) {
|
if(host_end==std::string::npos) {
|
||||||
host=host_port;
|
host=host_port;
|
||||||
|
|
@ -145,9 +153,9 @@ namespace SimpleWeb {
|
||||||
|
|
||||||
virtual void connect()=0;
|
virtual void connect()=0;
|
||||||
|
|
||||||
void parse_response_header(const std::shared_ptr<Response> &response, std::istream& stream) const {
|
void parse_response_header(const std::shared_ptr<Response> &response) const {
|
||||||
std::string line;
|
std::string line;
|
||||||
getline(stream, line);
|
getline(response->content, line);
|
||||||
size_t version_end=line.find(' ');
|
size_t version_end=line.find(' ');
|
||||||
if(version_end!=std::string::npos) {
|
if(version_end!=std::string::npos) {
|
||||||
if(5<line.size())
|
if(5<line.size())
|
||||||
|
|
@ -155,7 +163,7 @@ namespace SimpleWeb {
|
||||||
if((version_end+1)<line.size())
|
if((version_end+1)<line.size())
|
||||||
response->status_code=line.substr(version_end+1, line.size()-(version_end+1)-1);
|
response->status_code=line.substr(version_end+1, line.size()-(version_end+1)-1);
|
||||||
|
|
||||||
getline(stream, line);
|
getline(response->content, line);
|
||||||
size_t param_end;
|
size_t param_end;
|
||||||
while((param_end=line.find(':'))!=std::string::npos) {
|
while((param_end=line.find(':'))!=std::string::npos) {
|
||||||
size_t value_start=param_end+1;
|
size_t value_start=param_end+1;
|
||||||
|
|
@ -166,7 +174,7 @@ namespace SimpleWeb {
|
||||||
response->header.insert(std::make_pair(line.substr(0, param_end), line.substr(value_start, line.size()-value_start-1)));
|
response->header.insert(std::make_pair(line.substr(0, param_end), line.substr(value_start, line.size()-value_start-1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
getline(stream, line);
|
getline(response->content, line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -174,61 +182,95 @@ namespace SimpleWeb {
|
||||||
std::shared_ptr<Response> request_read() {
|
std::shared_ptr<Response> request_read() {
|
||||||
std::shared_ptr<Response> response(new Response());
|
std::shared_ptr<Response> response(new Response());
|
||||||
|
|
||||||
try {
|
boost::asio::streambuf chunked_streambuf;
|
||||||
size_t bytes_transferred = boost::asio::read_until(*socket, response->content_buffer, "\r\n\r\n");
|
|
||||||
|
boost::asio::async_read_until(*socket, response->content_buffer, "\r\n\r\n",
|
||||||
size_t num_additional_bytes=response->content_buffer.size()-bytes_transferred;
|
[this, &response, &chunked_streambuf](const boost::system::error_code& ec, size_t bytes_transferred) {
|
||||||
|
if(!ec) {
|
||||||
parse_response_header(response, response->content);
|
size_t num_additional_bytes=response->content_buffer.size()-bytes_transferred;
|
||||||
|
|
||||||
auto header_it=response->header.find("Content-Length");
|
parse_response_header(response);
|
||||||
if(header_it!=response->header.end()) {
|
|
||||||
auto content_length=stoull(header_it->second);
|
auto header_it=response->header.find("Content-Length");
|
||||||
if(content_length>num_additional_bytes) {
|
if(header_it!=response->header.end()) {
|
||||||
boost::asio::read(*socket, response->content_buffer,
|
auto content_length=stoull(header_it->second);
|
||||||
boost::asio::transfer_exactly(content_length-num_additional_bytes));
|
if(content_length>num_additional_bytes) {
|
||||||
|
boost::asio::async_read(*socket, response->content_buffer,
|
||||||
|
boost::asio::transfer_exactly(content_length-num_additional_bytes),
|
||||||
|
[this](const boost::system::error_code& ec, size_t /*bytes_transferred*/) {
|
||||||
|
if(ec) {
|
||||||
|
socket=nullptr;
|
||||||
|
throw boost::system::system_error(ec);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if((header_it=response->header.find("Transfer-Encoding"))!=response->header.end() && header_it->second=="chunked") {
|
||||||
|
request_read_chunked(response, chunked_streambuf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if((header_it=response->header.find("Transfer-Encoding"))!=response->header.end() && header_it->second=="chunked") {
|
else {
|
||||||
boost::asio::streambuf streambuf;
|
socket=nullptr;
|
||||||
std::ostream content(&streambuf);
|
throw boost::system::system_error(ec);
|
||||||
|
}
|
||||||
std::streamsize length;
|
});
|
||||||
std::string buffer;
|
io_service.reset();
|
||||||
do {
|
io_service.run();
|
||||||
size_t bytes_transferred = boost::asio::read_until(*socket, response->content_buffer, "\r\n");
|
|
||||||
std::string line;
|
|
||||||
getline(response->content, line);
|
|
||||||
bytes_transferred-=line.size()+1;
|
|
||||||
line.pop_back();
|
|
||||||
length=stol(line, 0, 16);
|
|
||||||
|
|
||||||
auto num_additional_bytes=static_cast<std::streamsize>(response->content_buffer.size()-bytes_transferred);
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
void request_read_chunked(const std::shared_ptr<Response> &response, boost::asio::streambuf &streambuf) {
|
||||||
|
boost::asio::async_read_until(*socket, response->content_buffer, "\r\n",
|
||||||
|
[this, &response, &streambuf](const boost::system::error_code& ec, size_t bytes_transferred) {
|
||||||
|
if(!ec) {
|
||||||
|
std::string line;
|
||||||
|
getline(response->content, line);
|
||||||
|
bytes_transferred-=line.size()+1;
|
||||||
|
line.pop_back();
|
||||||
|
std::streamsize length=stol(line, 0, 16);
|
||||||
|
|
||||||
if((2+length)>num_additional_bytes) {
|
auto num_additional_bytes=static_cast<std::streamsize>(response->content_buffer.size()-bytes_transferred);
|
||||||
boost::asio::read(*socket, response->content_buffer,
|
|
||||||
boost::asio::transfer_exactly(2+length-num_additional_bytes));
|
auto post_process=[this, &response, &streambuf, length] {
|
||||||
}
|
std::ostream stream(&streambuf);
|
||||||
|
std::vector<char> buffer(static_cast<size_t>(length));
|
||||||
buffer.resize(static_cast<size_t>(length));
|
|
||||||
response->content.read(&buffer[0], length);
|
response->content.read(&buffer[0], length);
|
||||||
content.write(&buffer[0], length);
|
stream.write(&buffer[0], length);
|
||||||
|
|
||||||
//Remove "\r\n"
|
//Remove "\r\n"
|
||||||
response->content.get();
|
response->content.get();
|
||||||
response->content.get();
|
response->content.get();
|
||||||
} while(length>0);
|
|
||||||
|
if(length>0)
|
||||||
|
request_read_chunked(response, streambuf);
|
||||||
|
else {
|
||||||
|
std::ostream response_stream(&response->content_buffer);
|
||||||
|
response_stream << stream.rdbuf();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
std::ostream response_content_output_stream(&response->content_buffer);
|
if((2+length)>num_additional_bytes) {
|
||||||
response_content_output_stream << content.rdbuf();
|
boost::asio::async_read(*socket, response->content_buffer,
|
||||||
|
boost::asio::transfer_exactly(2+length-num_additional_bytes),
|
||||||
|
[this, post_process](const boost::system::error_code& ec, size_t /*bytes_transferred*/) {
|
||||||
|
if(!ec) {
|
||||||
|
post_process();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
socket=nullptr;
|
||||||
|
throw boost::system::system_error(ec);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
post_process();
|
||||||
}
|
}
|
||||||
}
|
else {
|
||||||
catch(const std::exception& e) {
|
socket=nullptr;
|
||||||
socket_error=true;
|
throw boost::system::system_error(ec);
|
||||||
throw std::invalid_argument(e.what());
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -240,20 +282,37 @@ namespace SimpleWeb {
|
||||||
template<>
|
template<>
|
||||||
class Client<HTTP> : public ClientBase<HTTP> {
|
class Client<HTTP> : public ClientBase<HTTP> {
|
||||||
public:
|
public:
|
||||||
Client(const std::string& server_port_path) : ClientBase<HTTP>::ClientBase(server_port_path, 80) {
|
Client(const std::string& server_port_path) : ClientBase<HTTP>::ClientBase(server_port_path, 80) {}
|
||||||
socket=std::make_shared<HTTP>(io_service);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void connect() {
|
void connect() {
|
||||||
if(socket_error || !socket->is_open()) {
|
if(!socket || !socket->is_open()) {
|
||||||
boost::asio::ip::tcp::resolver::query query(host, std::to_string(port));
|
boost::asio::ip::tcp::resolver::query query(host, std::to_string(port));
|
||||||
boost::asio::connect(*socket, resolver.resolve(query));
|
|
||||||
|
|
||||||
boost::asio::ip::tcp::no_delay option(true);
|
resolver.async_resolve(query, [this](const boost::system::error_code &ec,
|
||||||
socket->set_option(option);
|
boost::asio::ip::tcp::resolver::iterator it){
|
||||||
|
if(!ec) {
|
||||||
socket_error=false;
|
socket=std::unique_ptr<HTTP>(new HTTP(io_service));
|
||||||
|
|
||||||
|
boost::asio::async_connect(*socket, it, [this]
|
||||||
|
(const boost::system::error_code &ec, boost::asio::ip::tcp::resolver::iterator /*it*/){
|
||||||
|
if(!ec) {
|
||||||
|
boost::asio::ip::tcp::no_delay option(true);
|
||||||
|
socket->set_option(option);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
socket=nullptr;
|
||||||
|
throw boost::system::system_error(ec);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
socket=nullptr;
|
||||||
|
throw boost::system::system_error(ec);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
io_service.reset();
|
||||||
|
io_service.run();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -29,23 +29,46 @@ namespace SimpleWeb {
|
||||||
if(verify_file.size()>0)
|
if(verify_file.size()>0)
|
||||||
context.load_verify_file(verify_file);
|
context.load_verify_file(verify_file);
|
||||||
|
|
||||||
socket=std::make_shared<HTTPS>(io_service, context);
|
socket=std::unique_ptr<HTTPS>(new HTTPS(io_service, context));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
boost::asio::ssl::context context;
|
boost::asio::ssl::context context;
|
||||||
|
|
||||||
void connect() {
|
void connect() {
|
||||||
if(socket_error || !socket->lowest_layer().is_open()) {
|
if(!socket || !socket->lowest_layer().is_open()) {
|
||||||
boost::asio::ip::tcp::resolver::query query(host, std::to_string(port));
|
boost::asio::ip::tcp::resolver::query query(host, std::to_string(port));
|
||||||
boost::asio::connect(socket->lowest_layer(), resolver.resolve(query));
|
|
||||||
|
|
||||||
boost::asio::ip::tcp::no_delay option(true);
|
resolver.async_resolve(query, [this]
|
||||||
socket->lowest_layer().set_option(option);
|
(const boost::system::error_code &ec, boost::asio::ip::tcp::resolver::iterator it){
|
||||||
|
if(!ec) {
|
||||||
socket->handshake(boost::asio::ssl::stream_base::client);
|
boost::asio::async_connect(socket->lowest_layer(), it, [this]
|
||||||
|
(const boost::system::error_code &ec, boost::asio::ip::tcp::resolver::iterator /*it*/){
|
||||||
socket_error=false;
|
if(!ec) {
|
||||||
|
boost::asio::ip::tcp::no_delay option(true);
|
||||||
|
socket->lowest_layer().set_option(option);
|
||||||
|
|
||||||
|
socket->async_handshake(boost::asio::ssl::stream_base::client,
|
||||||
|
[this](const boost::system::error_code& ec) {
|
||||||
|
if(ec) {
|
||||||
|
socket=nullptr;
|
||||||
|
throw boost::system::system_error(ec);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
socket=nullptr;
|
||||||
|
throw boost::system::system_error(ec);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
socket=nullptr;
|
||||||
|
throw boost::system::system_error(ec);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
io_service.reset();
|
||||||
|
io_service.run();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -71,13 +71,13 @@ public:
|
||||||
bool parse_response_header_test() {
|
bool parse_response_header_test() {
|
||||||
std::shared_ptr<Response> response(new Response());
|
std::shared_ptr<Response> response(new Response());
|
||||||
|
|
||||||
stringstream ss;
|
ostream stream(&response->content_buffer);
|
||||||
ss << "HTTP/1.1 200 OK\r\n";
|
stream << "HTTP/1.1 200 OK\r\n";
|
||||||
ss << "TestHeader: test\r\n";
|
stream << "TestHeader: test\r\n";
|
||||||
ss << "TestHeader2:test2\r\n";
|
stream << "TestHeader2:test2\r\n";
|
||||||
ss << "\r\n";
|
stream << "\r\n";
|
||||||
|
|
||||||
parse_response_header(response, ss);
|
parse_response_header(response);
|
||||||
|
|
||||||
if(response->http_version!="1.1")
|
if(response->http_version!="1.1")
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue