1.避免进程提前终止
由于任务的启动是异步的,所以任务的执行和主线程的执行是并行的,如果不加任何的控制,那么当主线程执行完所有操作以后直接退出,并且导致整个进程的终止。 WFFacilities::WaitGroup 可以根据情况阻塞线程或者恢复运行,可以用来控制主线程的运行情况。
#include <workflow/WFFacilities.h>
static WFFacilities::WaitGroup wait_group(1);
//wait_group的参数用来表示有多少个任务还没完成
void sig_handler(int signo){
wait_group.done();
//done方法表示完成了一个任务
}
int main(){
signal(SIGINT, sig_handler);
wait_group.wait();
//wait方法当存在至少一个任务未完成时,线程阻塞。
return 0;
}
2.http的客户端基本流程
利用workflow来实现一个http客户端。
实现一个http客户端的基本流程:
- 使用工厂函数,根据任务类型HTTP,创建一个任务对象;
- 设置任务的属性;
- 为任务绑定一个回调函数;
- 启动任务
在workflow当中,所有任务对象都是使用工厂函数来创建的。在创建任务的时候,还可以设置一些属性,比如要连接的服务端的url、最大重定向次数、连接失败的时候的重试次数和用户的回调函数(没有回调函数则传入nullptr)。
class WFTaskFactory
{
public:
static WFHttpTask *create_http_task(const std::string& url,//要连接的服务端的url
int redirect_max,//最大重定向次数
int retry_max,//连接失败的时候的重试次数
http_callback_t callback);//回调函数
};
在创建任务对象之后,启动任务对象之前,可以用访问任务对象的方法去修改任务的属性。
using WFHttpTask = WFNetworkTask<protocol::HttpRequest,
protocol::HttpResponse>;
REQ *get_req();//获取指向请求的指针
void set_callback(std::function<void (WFNetworkTask<REQ, RESP> *)> cb);//设置回调函数
关于HTTP的请求和响应,实际会存在更多相关的接口。
class HttpMessage : public ProtocolMessage
{
public:
const char *get_http_version() const;
bool set_http_version(const char *version);
bool add_header_pair(const char *name, const char *value);
bool set_header_pair(const char *name, const char *value);
bool get_parsed_body(const void **body, size_t *size) const;
/* Output body is for sending. Want to transfer a message received, maybe:
* msg->get_parsed_body(&body, &size);
* msg->append_output_body_nocopy(body, size); */
bool append_output_body(const void *buf, size_t size);
bool append_output_body_nocopy(const void *buf, size_t size);
void clear_output_body();
size_t get_output_body_size() const;
//上述接口都有std::string版本
//...
};
class HttpRequest : public HttpMessage
{
public:
const char *get_method() const;
const char *get_request_uri() const;
bool set_method(const char *method);
bool set_request_uri(const char *uri);
//上述接口都有std::string版本
//...
};
class HttpResponse : public HttpMessage
{
public:
const char *get_status_code() const;
const char *get_reason_phrase() const;
bool set_status_code(const char *code);
bool set_reason_phrase(const char *phrase);
/* Tell the parser, it is a HEAD response. */
void parse_zero_body();
//上述接口都有std::string版本
//...
};
调用start方法可以异步启动任务。需要值得特别注意的是,只有客户端才可以调用start方法。通过观察得知,start方法的底层逻辑就是根据本任务对象创建一个序列,其中本任务是序列当中的第一个任务,随后启动该任务。
/* start(), dismiss() are for client tasks only. */
void start()
{
assert(!series_of(this));
Workflow::start_series_work(this, nullptr);
}
3.回调函数的设计
当任务的基本工作完成之后,就会执行用户设置的回调函数,在回调函数当中,可以获取本次任务的执行情况。
针对http任务,回调函数在执行过程中可以获取本次任务的执行状态和失败的原因。
template<class REQ, class RESP>
class WFNetworkTask : public CommRequest
{
public:
// ...
int get_state() const { return this->state; }
int get_error() const { return this->error; }
// ...
}
下面是使用状态码和错误码的例子。当http基本工作执行正常的时候,此时状态码为
WFT_STATE_SUCCESS ,当出现系统错误的时候,此时状态码为 WFT_STATE_SYS_ERROR ,可以使用strerror 获取报错信息。当出现url错误的使用,此时状态码为 WFT_STATE_DNS_ERROR ,可以使用gai_strerror 获取报错信息。
#include "unixHeader.h"
#include <workflow/HttpMessage.h>
#include <workflow/HttpUtil.h>
#include <workflow/WFTaskFactory.h>
#include <workflow/WFFacilities.h>
static WFFacilities::WaitGroup wait_group(1);
void sig_handler(int signo){
wait_group.done();
}
void callback(WFHttpTask *httpTask){
int state = httpTask->get_state();
int error = httpTask->get_error();
switch (state){
case WFT_STATE_SYS_ERROR:
fprintf(stderr, "system error: %s\n", strerror(error));
break;
case WFT_STATE_DNS_ERROR:
fprintf(stderr, "DNS error: %s\n", gai_strerror(error));
break;
case WFT_STATE_SUCCESS:
break;
}
if (state != WFT_STATE_SUCCESS){
fprintf(stderr, "Failed. Press Ctrl-C to exit.\n");
return;
}
fprintf(stderr, "success\n");
wait_group.done();
}
int main(int argc, char *argv[]){
std::string url = "http://";
url.append(argv[1]);
signal(SIGINT, sig_handler);
auto httpTask = WFTaskFactory::create_http_task(url, 0, 0, callback);
protocol::HttpRequest *req = httpTask->get_req();
req->add_header_pair("Accept", "*/*");
req->add_header_pair("User-Agent", "TestAgent");
req->add_header_pair("Connection", "close");
httpTask->start();
wait_group.wait();
return 0;
}
在使用回调函数的时候,还可以获取http请求报文和响应报文的内容。
template<class REQ, class RESP>
class WFNetworkTask : public CommRequest
{
// ...
public:
REQ *get_req() { return &this->req; }
RESP *get_resp() { return &this->resp; }
// ...
}
//其中http任务的实例化版本
//REQ -> protocol::HttpRequest
//RESP -> protocol::HttpResponse
下面是样例代码:
void callback(WFHttpTask *task){
protocol::HttpRequest *req = task->get_req();
protocol::HttpResponse *resp = task->get_resp();
// ...
fprintf(stderr, "%s %s %s\r\n", req->get_method(),
req->get_http_version(),
req->get_request_uri());
// ...
fprintf(stderr, "%s %s %s\r\n", resp->get_http_version(),
resp->get_status_code(),
resp->get_reason_phrase());
// ...
}
对于首部字段,workflow提供 protocol::HttpHeaderCursor 类型作为遍历所有首部字段的迭代器。next 方法负责找到下一对首部字段键值对,倘若已经解析完成,就会返回 false 。 find 会根据首部字段的键,找到对应的值,值得注意的是, find 方法会修改迭代器的位置。
class HttpHeaderCursor{
//...
public:
bool next(std::string& name, std::string& value);
bool find(const std::string& name, std::string& value);
void rewind();
//...
};
下面是样例:
void callback(WFHttpTask *task){
protocol::HttpRequest *req = task->get_req();
protocol::HttpResponse *resp = task->get_resp();
//...
std::string name;
std::string value;
// ....
// 遍历请求报文的首部字段
protocol::HttpHeaderCursor req_cursor(req);
while (req_cursor.next(name, value)){
fprintf(stderr, "%s: %s\r\n", name.c_str(), value.c_str());
}
fprintf(stderr, "\r\n");
// 遍历响应报文的首部字段
protocol::HttpHeaderCursor resp_cursor(resp);
while (resp_cursor.next(name, value)){
fprintf(stderr, "%s: %s\r\n", name.c_str(), value.c_str());
}
fprintf(stderr, "\r\n");
//...
}
对于http报文的报文体,可以使用 get_parsed_body 方法获取报文的内容,需要注意的是它的用法。
//...
// 首先需要定义一个指针变量,该指针的基类型是const void
const void *body;
size_t body_len;
// 将指针变量的地址传入get_parsed_body方法中,指针变量将要指向报文体
resp->get_parsed_body(&body, &body_len);
fwrite(body, 1, body_len, stdout);
fflush(stdout);
//...