Skip to content

Commit

Permalink
add fv::body_json & fv::body_kvs
Browse files Browse the repository at this point in the history
  • Loading branch information
fawdlstty committed May 20, 2022
1 parent 31a8b9e commit e520627
Show file tree
Hide file tree
Showing 9 changed files with 152 additions and 35 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ In addition to providing network functions, the library also provides a variety

[Github Online Document](docs/)

## Why libfv
## Description

Compared with other network libraries, libfv's biggest advantage is that it supports pure asynchronous development mode. C++ is a very generational language for developers, mainly because C++20 introduced the asynchronous coroutine syntax, and the support for libraries has been slow to catch up, leaving asynchronous development options limited. libfv is one of the options of the new C++ asynchronous coroutine network development framework, which can make asynchronous coroutine development more pleasant from the library level.

## Scene
## Why libfv

The older HTTP libraries of C++ have two main implementations. The first is synchronous HTTP network access, such as code like this:

Expand All @@ -41,7 +41,9 @@ HttpGet ("https://t.cn", [] (Response _r) {
});
```
This way to solve the threading problem, that is, dozens or hundreds of requests can be launched at the same time, only a small amount or a thread on the line, HTTP library internal implementation of the request internal management, after receiving the response to the request, call the callback function, so as to achieve efficient processing of the request. The problem with this approach is that if we need to forward the content of the request to the next request, this can cause a callback hell problem, such as code like this:
This way to solve the threading problem, that is, dozens or hundreds of requests can be launched at the same time, only a small amount or a thread on the line, HTTP library internal implementation of the request internal management, after receiving the response to the request, call the callback function, so as to achieve efficient processing of the request.
The problem with this approach is that if we need to forward the content of the request to the next request, this can cause a callback hell problem, such as code like this:
```cpp
// pseudocode
Expand Down
8 changes: 5 additions & 3 deletions README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ libfv 是 C++20 纯头文件网络库,支持 TCP/SSL/Http/websocket 服务器

[Github 在线文档](docs/)

## 选择 libfv 的理由
## 项目描述

libfv 相对于其他网络库来说,最大的优势是支持纯异步的开发方式。C++是一个非常有年代感的语言,对于开发者来说,主要体现在,C++20出了异步协程语法后,支持的库迟迟没能跟上,使得异步开发选择范围很少。libfv 为新C++的异步协程网络开发框架的选项之一,可以从库的层面使得异步协程开发更为轻松愉快。

## 场景展示
## 选择 libfv 的理由

C++ 较老的 HTTP 库有两种主要的实现方式,第一种是同步 HTTP 网络访问,比如这样的代码:

Expand All @@ -41,7 +41,9 @@ HttpGet ("https://t.cn", [] (Response _r) {
});
```
这种方式解决了线程问题,也就是,几十、几百个请求可以同时发起,只需要极少量或者一个线程就行,HTTP 库内部实现了请求的内部管理,在收到请求的回复后,调用回调函数,从而实现请求的高效处理。但这种方式有个问题,假如我们需要根据请求结果内容转给下一个请求,这会带来一个回调地狱问题,比如这样的代码:
这种方式解决了线程问题,也就是,几十、几百个请求可以同时发起,只需要极少量或者一个线程就行,HTTP 库内部实现了请求的内部管理,在收到请求的回复后,调用回调函数,从而实现请求的高效处理。
但这种方式有个问题,假如我们需要根据请求结果内容转给下一个请求,这会带来一个回调地狱问题,比如这样的代码:
```cpp
// 伪代码
Expand Down
6 changes: 6 additions & 0 deletions docs/en_us/1_HttpClient.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ fv::Response _r2 = co_await fv::Post ("https://t.cn", fv::body_kv ("a", "aaa"),
// Commit file
fv::Response _r = co_await fv::Post ("https://t.cn", fv::body_file ("a", "filename.txt", "content..."));

// Send HttpPost request with Key-Value pair data
fv::Response _r = co_await fv::Post ("https://t.cn", fv::body_kvs ("a=b&c=d"));

// Send HttpPost request with json data
fv::Response _r = co_await fv::Post ("https://t.cn", fv::body_json ("{\"a\":\"b\"}"));

// Send HttpPost request with raw data
fv::Response _r = co_await fv::Post ("https://t.cn", fv::body_raw ("application/octet-stream", "aaa"));
```
Expand Down
7 changes: 7 additions & 0 deletions docs/zh_hans/1_HttpClient.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,15 @@ fv::Response _r2 = co_await fv::Post ("https://t.cn", fv::body_kv ("a", "aaa"),
// 提交文件
fv::Response _r = co_await fv::Post ("https://t.cn", fv::body_file ("a", "filename.txt", "content..."));

// 发送 HttpPost 请求并提交 Key-Value Pair 内容
fv::Response _r = co_await fv::Post ("https://t.cn", fv::body_kvs ("a=b&c=d"));

// 发送 HttpPost 请求并提交 json 内容
fv::Response _r = co_await fv::Post ("https://t.cn", fv::body_json ("{\"a\":\"b\"}"));

// 发送 HttpPost 请求并提交原始内容
fv::Response _r = co_await fv::Post ("https://t.cn", fv::body_raw ("application/octet-stream", "aaa"));

```
可在请求时指定 HTTP 头:
Expand Down
10 changes: 10 additions & 0 deletions include/fv/base.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ struct body_file {
std::string Name, FileName, FileContent;
body_file (std::string _name, std::string _filename, std::string _content): Name (_name), FileName (_filename), FileContent (_content) {}
};
struct body_kvs {
std::string Content;
body_kvs (std::string _content): Content (_content) {}
};
struct body_json {
std::string Content;
body_json (std::string _content): Content (_content) {}
};
struct body_raw {
std::string ContentType, Content;
body_raw (std::string _content_type, std::string _content): ContentType (_content_type), Content (_content) {}
Expand All @@ -67,6 +75,8 @@ concept TFormOption = std::is_same<T, timeout>::value || std::is_same<T, server>
std::is_same<T, header>::value || std::is_same<T, authorization>::value || std::is_same<T, connection>::value ||
std::is_same<T, content_type>::value || std::is_same<T, referer>::value || std::is_same<T, user_agent>::value ||
std::is_same<T, url_kv>::value || std::is_same<T, body_kv>::value || std::is_same<T, body_file>::value;
template<typename T>
concept TBodyOption = std::is_same<T, body_kvs>::value || std::is_same<T, body_json>::value || std::is_same<T, body_raw>::value;
}


Expand Down
20 changes: 20 additions & 0 deletions include/fv/conn.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,26 @@ struct SslConn: public IConn {



struct SslConn2: public IConn {
Ssl::stream<Tcp::socket> SslSocket;
int64_t Id = -1;

SslConn2 (Ssl::stream<Tcp::socket> _sock);

virtual ~SslConn2 () { Cancel (); Close (); }
Task<void> Connect (std::string _host, std::string _port) override { throw Exception ("Cannot use connect from SslConn2 class"); }
Task<void> Reconnect () override { throw Exception ("Cannot use reconnect from SslConn2 class"); }
bool IsConnect () override { return SslSocket.next_layer ().is_open (); }
void Close () override;
Task<void> Send (char *_data, size_t _size) override;
void Cancel () override { SslSocket.next_layer ().cancel (); }

protected:
Task<size_t> RecvImpl (char *_data, size_t _size) override;
};



struct WsConn: public std::enable_shared_from_this<WsConn> {
std::shared_ptr<IConn> Parent;
bool IsClient = true;
Expand Down
33 changes: 33 additions & 0 deletions include/fv/conn_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,39 @@ inline void SslConn::Cancel () {



inline SslConn2::SslConn2 (Ssl::stream<Tcp::socket> _sock): SslSocket (std::move (_sock)) {
if (Config::NoDelay)
SslSocket.next_layer ().set_option (Tcp::no_delay { true });
}

inline void SslConn2::Close () {
if (SslSocket.next_layer ().is_open ()) {
SslSocket.next_layer ().shutdown (SocketBase::shutdown_both);
SslSocket.next_layer ().close ();
}
}

inline Task<void> SslConn2::Send (char *_data, size_t _size) {
if (!SslSocket.next_layer ().is_open ())
throw Exception ("Cannot send data to a closed connection.");
size_t _sended = 0;
while (_sended < _size) {
size_t _tmp_send = co_await SslSocket.async_write_some (asio::buffer (&_data [_sended], _size - _sended), UseAwaitable);
if (_tmp_send == 0)
throw Exception ("Connection temp closed.");
_sended += _tmp_send;
}
}

inline Task<size_t> SslConn2::RecvImpl (char *_data, size_t _size) {
if (!SslSocket.next_layer ().is_open ())
co_return 0;
size_t _count = co_await SslSocket.async_read_some (asio::buffer (_data, _size), UseAwaitable);
co_return _count;
}



inline WsConn::WsConn (std::shared_ptr<IConn> _parent, bool _is_client): Parent (_parent), IsClient (_is_client) {
fv::Tasks::RunAsync ([_wptr = std::weak_ptr (shared_from_this ())] () -> Task<void> {
while (true) {
Expand Down
93 changes: 65 additions & 28 deletions include/fv/session.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,33 @@

namespace fv {
template<TFormOption _Op1>
inline void _OptionApply (Request &_r, _Op1 _op) { throw Exception ("Unsupported dest type template instance"); }
template<> inline void _OptionApply (Request &_r, timeout _t) { _r.Timeout = _t.m_exp; }
template<> inline void _OptionApply (Request &_r, server _s) { _r.Server = _s.m_ip; }
template<> inline void _OptionApply (Request &_r, header _hh) { _r.Headers [_hh.m_key] = _hh.m_value; }
template<> inline void _OptionApply (Request &_r, authorization _auth) { _r.Headers [_auth.m_key] = _auth.m_value; }
template<> inline void _OptionApply (Request &_r, connection _co) { _r.Headers [_co.m_key] = _co.m_value; }
template<> inline void _OptionApply (Request &_r, content_type _ct) { _r.Headers [_ct.m_key] = _ct.m_value; }
template<> inline void _OptionApply (Request &_r, referer _re) { _r.Headers [_re.m_key] = _re.m_value; }
template<> inline void _OptionApply (Request &_r, user_agent _ua) { _r.Headers [_ua.m_key] = _ua.m_value; }
template<> inline void _OptionApply (Request &_r, url_kv _pd) { _r.QueryItems.push_back (_pd); }
template<> inline void _OptionApply (Request &_r, body_kv _pd) { _r.ContentItems.push_back (_pd); }
template<> inline void _OptionApply (Request &_r, body_file _pf) { _r.ContentItems.push_back (_pf); }
inline void _OptionApply (Request &_r, _Op1 &_op) { throw Exception ("Unsupported dest type template instance"); }
template<> inline void _OptionApply (Request &_r, timeout &_t) { _r.Timeout = _t.m_exp; }
template<> inline void _OptionApply (Request &_r, server &_s) { _r.Server = _s.m_ip; }
template<> inline void _OptionApply (Request &_r, header &_hh) { _r.Headers [_hh.m_key] = _hh.m_value; }
template<> inline void _OptionApply (Request &_r, authorization &_auth) { _r.Headers [_auth.m_key] = _auth.m_value; }
template<> inline void _OptionApply (Request &_r, connection &_co) { _r.Headers [_co.m_key] = _co.m_value; }
template<> inline void _OptionApply (Request &_r, content_type &_ct) { _r.Headers [_ct.m_key] = _ct.m_value; }
template<> inline void _OptionApply (Request &_r, referer &_re) { _r.Headers [_re.m_key] = _re.m_value; }
template<> inline void _OptionApply (Request &_r, user_agent &_ua) { _r.Headers [_ua.m_key] = _ua.m_value; }
template<> inline void _OptionApply (Request &_r, url_kv &_pd) { _r.QueryItems.push_back (_pd); }
template<> inline void _OptionApply (Request &_r, body_kv &_pd) { _r.ContentItems.push_back (_pd); }
template<> inline void _OptionApply (Request &_r, body_file &_pf) { _r.ContentItems.push_back (_pf); }

template<TBodyOption _Op1>
inline void _OptionApplyBody (Request &_r, _Op1 &_op) { throw Exception ("Unsupported dest type template instance"); }
template<> inline void _OptionApplyBody (Request &_r, body_kvs &_body) {
_r.Headers ["Content-Type"] = "application/x-www-form-urlencoded";
_r.Content = _body.Content;
}
template<> inline void _OptionApplyBody (Request &_r, body_json &_body) {
_r.Headers ["Content-Type"] = "application/json";
_r.Content = _body.Content;
}
template<> inline void _OptionApplyBody (Request &_r, body_raw &_body) {
_r.Headers ["Content-Type"] = _body.ContentType;
_r.Content = _body.Content;
}

template<TFormOption _Op1>
inline void _OptionApplys (Request &_r, _Op1 _op1) { _OptionApply (_r, _op1); }
Expand Down Expand Up @@ -145,11 +160,16 @@ struct Session {
_OptionApplys (_r, _ops...);
co_return co_await DoMethod (_r);
}
template<TOption ..._Ops>
Task<Response> Post (std::string _url, body_raw _data, _Ops ..._ops) {
template<TBodyOption _Body>
Task<Response> Post (std::string _url, _Body _body) {
Request _r { _url, MethodType::Post };
_r.Headers ["Content-Type"] = _data.ContentType;
_r.Content = _data.Content;
_OptionApplyBody (_r, _body);
co_return co_await DoMethod (_r);
}
template<TBodyOption _Body, TOption ..._Ops>
Task<Response> Post (std::string _url, _Body _body, _Ops ..._ops) {
Request _r { _url, MethodType::Post };
_OptionApplyBody (_r, _body);
_OptionApplys (_r, _ops...);
co_return co_await DoMethod (_r);
}
Expand All @@ -160,11 +180,16 @@ struct Session {
_OptionApplys (_r, _ops...);
co_return co_await DoMethod (_r);
}
template<TOption ..._Ops>
Task<Response> Put (std::string _url, body_raw _data, _Ops ..._ops) {
template<TBodyOption _Body>
Task<Response> Put (std::string _url, _Body _body) {
Request _r { _url, MethodType::Put };
_OptionApplyBody (_r, _body);
co_return co_await DoMethod (_r);
}
template<TBodyOption _Body, TOption ..._Ops>
Task<Response> Put (std::string _url, _Body _body, _Ops ..._ops) {
Request _r { _url, MethodType::Put };
_r.Headers ["Content-Type"] = _data.ContentType;
_r.Content = _data.Content;
_OptionApplyBody (_r, _body);
_OptionApplys (_r, _ops...);
co_return co_await DoMethod (_r);
}
Expand Down Expand Up @@ -226,12 +251,18 @@ inline Task<Response> Post (std::string _url, _Ops ..._ops) {
_OptionApplys (_r, _ops...);
co_return co_await _sess.DoMethod (_r);
}
template<TOption ..._Ops>
inline Task<Response> Post (std::string _url, body_raw _data, _Ops ..._ops) {
template<TBodyOption _Body>
inline Task<Response> Post (std::string _url, _Body _body) {
Request _r { _url, MethodType::Post };
Session _sess = co_await Session::FromUrl (_url, _r.Server);
_OptionApplyBody (_r, _body);
co_return co_await _sess.DoMethod (_r);
}
template<TBodyOption _Body, TOption ..._Ops>
inline Task<Response> Post (std::string _url, _Body _body, _Ops ..._ops) {
Request _r { _url, MethodType::Post };
Session _sess = co_await Session::FromUrl (_url, _r.Server);
_r.Headers ["Content-Type"] = _data.ContentType;
_r.Content = _data.Content;
_OptionApplyBody (_r, _body);
_OptionApplys (_r, _ops...);
co_return co_await _sess.DoMethod (_r);
}
Expand All @@ -243,12 +274,18 @@ inline Task<Response> Put (std::string _url, _Ops ..._ops) {
_OptionApplys (_r, _ops...);
co_return co_await _sess.DoMethod (_r);
}
template<TOption ..._Ops>
inline Task<Response> Put (std::string _url, body_raw _data, _Ops ..._ops) {
template<TBodyOption _Body>
inline Task<Response> Put (std::string _url, _Body _body) {
Request _r { _url, MethodType::Put };
Session _sess = co_await Session::FromUrl (_url, _r.Server);
_OptionApplyBody (_r, _body);
co_return co_await _sess.DoMethod (_r);
}
template<TBodyOption _Body, TOption ..._Ops>
inline Task<Response> Put (std::string _url, _Body _body, _Ops ..._ops) {
Request _r { _url, MethodType::Put };
Session _sess = co_await Session::FromUrl (_url, _r.Server);
_r.Headers ["Content-Type"] = _data.ContentType;
_r.Content = _data.Content;
_OptionApplyBody (_r, _body);
_OptionApplys (_r, _ops...);
co_return co_await _sess.DoMethod (_r);
}
Expand Down
2 changes: 1 addition & 1 deletion libfv_test/libfv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Task<void> test_client () {
fv::Session _sess = co_await fv::Session::FromUrl ("https://www.fawdlstty.com");
fv::Response _r = co_await _sess.Get ("https://www.fawdlstty.com");
std::cout << _r.Content.size () << '\n';
_r = co_await _sess.Get ("https://www.fawdlstty.com");
_r = co_await _sess.Post ("https://www.fawdlstty.com", fv::body_json ("{\"a\":\"b\"}"));
std::cout << _r.Content.size () << '\n';
//
std::this_thread::sleep_for (std::chrono::seconds (10));
Expand Down

0 comments on commit e520627

Please sign in to comment.