boost::asio的http client应用笔记
1 踩过的坑
1.1 io_service
boost::asio::io_service::run()
会一直运行到没有任务为止,假设中途调用stop()
。则全部等待中的任务会立马运行。要在停止的时候抛弃全部的任务,解决方式是用run_one()
。即
while (keep_running)
io_service_.run_one();
keep_running是个bool值。要stop io_service的时候直接置false就可以。
1.2 deadline_timer
在调用async_wait()
后。不管调用deadline_timer::cancel()
还是这个deadline_timer都析构掉。handler
都会被触发。
当然。这个在文档是有写的。规避野指针的办法有两个,一是传入的handler
是shared_ptr,二是再封装一层。后者适用于handler
的生命周期无法由自身控制的情况,演示样例代码请看http client一节的TimerHolder类。
1.3 async*
这个事实上和deadline_timer::asyn::wait()
差点儿相同。async_read
、async_read_until
等带async_前缀的函数,仅仅要中途被停止(比如调用ip::tcp::socket::close()
),Handler
都会被运行并传入一个代表aborted的boost::system::error_code。
1.4 ip::tcp::socket
- 官方的example过于简单。反而迷惑人了。HTTP协议说来也算复杂,比如chunked encoding还得自己解析。
- 从ip::tcp::resolver得到的可能是多个IP,假设把返回的迭代器交给
async_connect
,那么非常可能出错,应为IP里可能有不合理的地址。比方可能返回的是全0的地址。解决的方法參考http client代码的DoResolveAndConnect()
函数。 - socket的read可能会读到额外的数据,这个文档里有写。
2. http client的应用
封装成了C++类。这是单线程的实现(io_service是同一线程下run的)。同步地调用socket函数并用deadline_timer来异步返回数据会更easy控制。
不细说了。请看代码。
注:URL、HttpRequest、HttpResponse等类未列出源代码,请自行实现相应函数。
// header
#include <assert.h>
#include <string>
#include "boost/asio.hpp"
#include "boost/make_shared.hpp"
#include "boost/thread.hpp"
class HttpTransaction {
public:
// The Delegate implementation MAY delete HttpTransaction during Delegate
// method is invoked.
// Possible flow: ("*" means zero or more times.)
// 1. OnResponseReceived() -> OnDataReceived()* -> OnFinished().
// 2. OnError().
// 3. OnResponseReceived() -> OnError().
// 4. OnResponseReceived() -> OnDataReceived()* -> OnError().
class Delegate {
public:
virtual ~Delegate() {}
virtual void OnResponseReceived(HttpTransaction* transaction,
const HttpResponse& response) = 0;
virtual void OnDataReceived(HttpTransaction* transaction,
const char* data,
size_t length) = 0;
virtual void OnFinished(HttpTransaction* transaction) = 0;
virtual void OnError(HttpTransaction* transaction, int error_code) = 0;
};
explicit HttpTransaction(boost::asio::io_service& io);
~HttpTransaction();
void Start();
void Cancel();
const HttpRequest* request() const {
return http_request_;
}
void set_request(const HttpRequest* request) {
http_request_ = request;
}
Delegate* delegate() const { return delegate_; }
void set_delegate(Delegate* delegate) {
delegate_ = delegate;
}
private:
enum State {
STATE_NONE,
STATE_CONNECT,
STATE_SEND_REQUEST,
STATE_READ_HEADER,
STATE_READ_BODY,
STATE_READ_CHUNK_SIZE,
STATE_READ_CHUNK_DATA,
STATE_READ_UNTIL_EOF,
STATE_CALL_ON_FINISHED,
};
void DoLoop();
bool DoResolveAndConnect();
bool DoSendRequest();
bool DoReadHeader();
bool DoReadChunkSize();
bool DoReadChunkData();
bool DoReadBody();
bool DoReadUntilEof();
bool DoCallOnFinished();
void CallOnDataReceived(size_t size, size_t additional_consume_size = 0);
const HttpRequest* http_request_;
Delegate* delegate_;
HttpResponse* http_response_;
boost::asio::ip::tcp::resolver resolver_;
boost::asio::ip::tcp::socket socket_;
boost::shared_ptr<boost::asio::streambuf> stream_buf_;
size_t pending_read_size_;
State next_state_;
bool started_;
class TimerHolder;
boost::shared_ptr<TimerHolder> timer_holder_;
};
#####################################################################
// implementation
#include <string.h>
#include <assert.h>
#include <algorithm>
#include "boost/algorithm/hex.hpp"
#include "boost/bind.hpp"
#include "http_transaction.h"
#include "http_request.h"
#include "http_response.h"
#include "util/url.h"
#include "util/logging.h"
// TimerHolder is needed because the callback is invoked even the timer
// cancelled or deleted.
class HttpTransaction::TimerHolder {
public:
TimerHolder(HttpTransaction* trans,
boost::asio::io_service& io) // NOLINT
: trans_(trans),
timer_(io) {}
void Schedule() {
timer_.expires_from_now(boost::posix_time::microseconds(0));
timer_.async_wait(boost::bind(&TimerHolder::OnTimer, trans_->timer_holder_,
boost::asio::placeholders::error));
}
void OnTransactionCancelled() {
trans_ = NULL;
timer_.cancel();
}
private:
void OnTimer(const boost::system::error_code& err) {
if (!err && trans_) {
trans_->DoLoop();
}
}
HttpTransaction* trans_;
boost::asio::deadline_timer timer_;
};
HttpTransaction::HttpTransaction(boost::asio::io_service& io)
: http_request_(NULL),
delegate_(NULL),
http_response_(NULL),
resolver_(io),
socket_(io),
stream_buf_(new boost::asio::streambuf),
pending_read_size_(0),
next_state_(STATE_NONE),
started_(false),
timer_holder_(new TimerHolder(this, io)) {
}
HttpTransaction::~HttpTransaction() {
Cancel();
}
void HttpTransaction::Start() {
assert(!started_);
started_ = true;
next_state_ = STATE_CONNECT;
timer_holder_->Schedule();
}
void HttpTransaction::Cancel() {
next_state_ = STATE_NONE;
timer_holder_->OnTransactionCancelled();
socket_.close();
if (http_response_) {
delete http_response_;
http_response_ = NULL;
}
}
void HttpTransaction::DoLoop() {
bool rv = false;
do {
State state = next_state_;
next_state_ = STATE_NONE;
switch (state) {
case STATE_CONNECT:
rv = DoResolveAndConnect();
break;
case STATE_SEND_REQUEST:
rv = DoSendRequest();
break;
case STATE_READ_HEADER:
rv = DoReadHeader();
break;
case STATE_READ_BODY:
rv = DoReadBody();
break;
case STATE_READ_UNTIL_EOF:
rv = DoReadUntilEof();
break;
case STATE_READ_CHUNK_SIZE:
rv = DoReadChunkSize();
break;
case STATE_READ_CHUNK_DATA:
rv = DoReadChunkData();
break;
case STATE_CALL_ON_FINISHED:
rv = DoCallOnFinished();
break;
default:
assert(0);
break;
}
} while (rv);
}
bool HttpTransaction::DoResolveAndConnect() {
URL url(http_request_->url());
// TODO(liuhx): if url is ip address.
boost::asio::ip::tcp::resolver::query query(
url.host(), url.port() == 0 ? url.protocol() : url.port_string());
boost::system::error_code err;
boost::asio::ip::tcp::resolver::iterator it = resolver_.resolve(query, err);
boost::asio::ip::tcp::resolver::iterator end; // Default is end.
if (err || it == end) {
LOG_DEBUG(kLogTagHttpTrans, "resolve error:%s",
err.message().c_str());
delegate_->OnError(this, err.value());
return false;
}
do {
LOG_INFO(kLogTagHttpTrans, "dns result:%s",
it->endpoint().address().to_string().c_str());
// "unspecified" means address is "0.0.0.0". It may appear on some machines
// running Apache. Please google it for more detail.
if (!it->endpoint().address().is_unspecified()) {
socket_.close();
LOG_INFO(kLogTagHttpTrans, "connecting:%s",
it->endpoint().address().to_string().c_str());
socket_.connect(*it, err);
if (!err)
break;
}
++it;
} while (it != end);
if (err) {
LOG_DEBUG(kLogTagHttpTrans, "connect error:%s",
err.message().c_str());
delegate_->OnError(this, err.value());
return false;
}
next_state_ = STATE_SEND_REQUEST;
return true;
}
bool HttpTransaction::DoSendRequest() {
URL url(http_request_->url());
std::ostream request_stream(stream_buf_.get());
request_stream << http_request_->method() << " " << url.path()
<< " HTTP/1.1\r\n";
if (!http_request_->HasHeader("Host")) {
request_stream << "Host: " << url.host() << "\r\n";
}
if (!http_request_->HasHeader("Connection")) {
request_stream << "Connection: keep-alive\r\n";
}
const char* name;
const char* value;
void* iter = NULL;
while (http_request_->EnumerateHeaderLines(&iter, &name, &value))
request_stream << name << ": " << value << "\r\n";
size_t size = 0;
if (http_request_->body(&value, &size)) {
if (!http_request_->HasHeader("Content-Length")) {
request_stream << "Content-Length: " << size << "\r\n";
}
request_stream << "\r\n";
request_stream.write(value, size);
} else {
request_stream << "\r\n";
}
boost::system::error_code err;
// boost::asio::write() consumes |stream_buf_|, no need to do it ourselves.
boost::asio::write(socket_, *stream_buf_, err);
if (err) {
LOG_DEBUG(kLogTagHttpTrans, "send request error:%s",
err.message().c_str());
delegate_->OnError(this, err.value());
} else {
next_state_ = STATE_READ_HEADER;
timer_holder_->Schedule();
}
return false;
}
bool HttpTransaction::DoReadHeader() {
boost::system::error_code err;
boost::asio::read_until(socket_, *stream_buf_, "\r\n\r\n", err);
if (err) {
LOG_DEBUG(kLogTagHttpTrans, "read header error:%s",
err.message().c_str());
delegate_->OnError(this, err.value());
return false;
}
size_t size = stream_buf_->size();
const char* data = boost::asio::buffer_cast<const char*>(stream_buf_->data());
size_t pos = std::string(data, size).find("\r\n\r\n");
if (pos == std::string::npos) {
LOG_DEBUG(kLogTagHttpTrans,
"Can not find header end. Maybe TCP data Out-of-Order");
delegate_->OnError(this, 1234);
return false;
}
http_response_ = new HttpResponse(http_request_->url(), data, pos);
stream_buf_->consume(pos + 4); // Skip 4 = "\r\n\r\n".
if (http_response_->status_code() < 100) {
LOG_DEBUG(kLogTagHttpTrans, "Header invalid");
delegate_->OnError(this, 2345);
return false;
}
// TODO(liuhx): Body may be available even status code is not 200.For
// example, some website may display a web page while replying with 404.
if (http_response_->status_code() != 200) {
next_state_ = STATE_CALL_ON_FINISHED;
} else {
const char* content_length = http_response_->GetHeader("content-length");
if (content_length) {
pending_read_size_ = atoi(content_length);
next_state_ = STATE_READ_BODY;
} else {
const char* encoding = http_response_->GetHeader("Transfer-Encoding");
bool is_chunk = encoding && (std::string(encoding).find("chunked") !=
std::string::npos);
if (is_chunk) {
next_state_ = STATE_READ_CHUNK_SIZE;
} else {
next_state_ = STATE_READ_UNTIL_EOF;
}
}
}
timer_holder_->Schedule();
delegate_->OnResponseReceived(this, *http_response_);
return false;
}
bool HttpTransaction::DoReadBody() {
// If content-length exists, the connection may be keep-alive. We MUST keep
// counting |pending_read_size_| instead of reading until EOF.
if (pending_read_size_ == 0) {
delegate_->OnFinished(this);
return false;
}
while (true) {
// boost may read addtional data beyond the condition in STATE_READ_HEADER,
// pass left data first if exists.
size_t size = stream_buf_->size();
if (size) {
next_state_ = STATE_READ_BODY;
timer_holder_->Schedule();
// TODO(liuhx): assert -> OnError
assert(pending_read_size_ >= size);
pending_read_size_ -= size;
CallOnDataReceived(size);
break;
} else {
boost::system::error_code err;
boost::asio::read(socket_, *stream_buf_,
boost::asio::transfer_at_least(1), err);
if (err) {
LOG_DEBUG(kLogTagHttpTrans, "read body error:%s",
err.message().c_str());
delegate_->OnError(this, err.value());
break;
}
}
}
return false;
}
// About chunked encoding, refer to http://tools.ietf.org/html/rfc2616#page-25
bool HttpTransaction::DoReadChunkSize() {
while (true) {
// boost may read addtional data beyond the condition, find "\r\n" first.
size_t size = stream_buf_->size();
if (size) {
const char* data =
boost::asio::buffer_cast<const char*>(stream_buf_->data());
size_t index = std::string(data, size).find("\r\n");
if (index != std::string::npos) {
pending_read_size_ = static_cast<size_t>(strtol(data, NULL, 16));
stream_buf_->consume(index + 2); // Skip +2 = "\r\n"
if (pending_read_size_ == 0) {
delegate_->OnFinished(this);
return false;
}
break;
}
}
boost::system::error_code err;
boost::asio::read_until(socket_, *stream_buf_, "\r\n", err);
if (err) {
LOG_DEBUG(kLogTagHttpTrans, "read chunk size error:%s",
err.message().c_str());
delegate_->OnError(this, err.value());
return false;
}
}
next_state_ = STATE_READ_CHUNK_DATA;
return true;
}
bool HttpTransaction::DoReadChunkData() {
while (true) {
size_t size = stream_buf_->size();
if (size) {
bool reach_end = size >= pending_read_size_;
size_t data_size = reach_end ?
pending_read_size_ : size;
pending_read_size_ -= data_size;
next_state_ = reach_end ?
STATE_READ_CHUNK_SIZE : STATE_READ_CHUNK_DATA;
timer_holder_->Schedule();
CallOnDataReceived(data_size, reach_end ?
2 : 0); // Skip 2 = "\r\n".
break;
} else {
boost::system::error_code err;
boost::asio::read_until(socket_, *stream_buf_, "\r\n", err);
if (err) {
LOG_DEBUG(kLogTagHttpTrans, "read chunk data error:%s",
err.message().c_str());
delegate_->OnError(this, err.value());
break;
}
}
}
return false;
}
bool HttpTransaction::DoReadUntilEof() {
while (true) {
size_t size = stream_buf_->size();
if (size) {
next_state_ = STATE_READ_UNTIL_EOF;
timer_holder_->Schedule();
const char* data =
boost::asio::buffer_cast<const char*>(stream_buf_->data());
boost::shared_ptr<boost::asio::streambuf> buf = stream_buf_;
delegate_->OnDataReceived(this, data, size);
buf->consume(size);
break;
} else {
boost::system::error_code err;
boost::asio::read(socket_, *stream_buf_,
boost::asio::transfer_at_least(1), err);
if (err) {
if (err == boost::asio::error::eof) {
delegate_->OnFinished(this);
} else {
LOG_DEBUG(kLogTagHttpTrans, "%s", err.message().c_str());
delegate_->OnError(this, err.value());
}
break;
}
}
}
return false;
}
bool HttpTransaction::DoCallOnFinished() {
delegate_->OnFinished(this);
return false;
}
void HttpTransaction::CallOnDataReceived(size_t size,
size_t additional_consume_size) {
const char* data = boost::asio::buffer_cast<const char*>(stream_buf_->data());
// Because Delegate may delete HttpTransaction during callback, we MUST NOT
// access member variable after callback method, instead, use the |buf|.
boost::shared_ptr<boost::asio::streambuf> buf = stream_buf_;
delegate_->OnDataReceived(this, data, size);
buf->consume(size + additional_consume_size);
}
boost::asio的http client应用笔记的更多相关文章
- boost------asio库的使用2(Boost程序库完全开发指南)读书笔记
网络通信 asio库支持TCP.UDP.ICMP通信协议,它在名字空间boost::asio::ip里提供了大量的网络通信方面的函数和类,很好地封装了原始的Berkeley Socket Api,展现 ...
- 使用boost.asio实现网络通讯
#include <boost/asio.hpp> #define USING_SSL //是否加密 #ifdef USING_SSL #include <boost/asio/ss ...
- boost asio异步读写网络聊天程序client 实例具体解释
boost官方文档中聊天程序实例解说 数据包格式chat_message.hpp <pre name="code" class="cpp">< ...
- boost asio one client one thread
总结了一个简单的boost asio的tcp服务器端与客户端通信流程.模型是一个client对应一个线程.先做一个记录,后续再对此进行优化. 环境:VS2017 + Boost 1.67 serve ...
- boost asio io_service学习笔记
构造函数 构造函数的主要动作就是调用CreateIoCompletionPort创建了一个初始iocp. Dispatch和post的区别 Post一定是PostQueuedCompletionSta ...
- c++ boost asio库初学习
前些日子研究了一个c++的一个socket库,留下范例代码给以后自己参考. 同步server: // asio_server.cpp : コンソール アプリケーションのエントリ ポイントを定義します. ...
- BOOST.Asio——Tutorial
=================================版权声明================================= 版权声明:原创文章 谢绝转载 啥说的,鄙视那些无视版权随 ...
- BOOST.Asio——Overview
=================================版权声明================================= 版权声明:原创文章 谢绝转载 啥说的,鄙视那些无视版权随 ...
- boost asio sync
Service: #include<boost/asio.hpp> #include<boost/thread.hpp> #include<iostream> #i ...
随机推荐
- linux常用命令之lsof 、netstat、ipcs、ldd
一.lsof lsof(list open files)是一个列出当前系统打开文件的工具.在linux环境下,任何事物都以文件的形式存在,通过文件不仅仅可以访问常规数据,还可以访问网络连接和硬件.每行 ...
- Linux下的一个高速跳转到上N层文件夹的简单方法
编辑文件 vim .bashrc (使改动在当前用户下有效) 或者 vim /etc/profile (须要在root用户下运行,使改动在全部用户下有效) 在文件结尾加入别名 alias cd1=' ...
- Oracle442个应用场景-----------Oracle数据库物理结构
-------------------------Oracle数据库物理结构------------------------------- Oracle数据库物理结构 oracle的数据,实际 ...
- Linux - 用 Konstruct 安装 KDE 3.x
make-kde3.x Linux - 用 Konstruct 安装 KDE 3.x 找个截图工具 khtml2png (http://khtml2png.sourceforge.net/), 现在都 ...
- thinkphp图片处理
thinkphp图片处理 一.总结 1.参考手册:参考手册上面啥都有,只是这样业务逻辑不明显,所以看视频会很好,但是如果用编程的灵性(设计),那么其实会更加高效,但是看视频更快而且没那么枯燥,更高效把 ...
- IE中实现placeholder
简介:IE本身不支持Placeholder这种先进的特性,但是我们又必须且仅仅支持IE,所以网上找了一个支持placeholder的方法 考虑版权,以及知识产权原因,只放链接: http://blog ...
- Maven用法
Maven 的使用教程 1.什么是 Maven? Maven项目对象模型(POM),可以通过一小段描述信息来管理项目的构建,报告和文档的软件项目管理工具. 2.下载 Maven? ①.官网下载 ...
- 【Thinkphp学习】TP3.2.3在PHP5.5环境下运行非常慢
在做项目时遇到了一个瓶颈问题:老项目迁移到PHP5.5环境后打开网页很卡很慢. 服务器环境为:apache+php5.5.38+mysql,使用框架为Thinkphp3.2.3. 经过多方面排查找到了 ...
- Keil 编译环境之在线仿真调试问题
一.问题现象: 这几天刚开始上手STM32,使用Keil 环境进行编程,然后使用ULINK2进行在线仿真,在按键处理函数程序中设置断点,却发现按了按键程序没有停在设置的断点,程序正常运行,如下图所示, ...
- CISP/CISA 每日一题 二
CISA 观察和测试用户操作程序 1.职责分离:确保没人具有执行多于一个下列处理过程的能力:启动.授权.验证或分发 2.输入授权:可以通过在输入文件上的书面授权或唯一口令的使用来获得证据 3.平衡:验 ...