1. 程式人生 > >ReactiveX 學習筆記(24)使用 RxCpp + C++ REST SDK 調用 REST API

ReactiveX 學習筆記(24)使用 RxCpp + C++ REST SDK 調用 REST API

x86-64 from space ces 測試 git clone delete let ann

JSON : Placeholder

JSON : Placeholder (https://jsonplaceholder.typicode.com/) 是一個用於測試的 REST API 網站。
以下使用 Task API/Rx.NET + Json.NET 調用該網站的 REST API,獲取字符串以及 JSON 數據。

  • GET /posts/1
  • GET /posts
  • POST /posts
  • PUT /posts/1
  • DELETE /posts/1

所有 GET API 都返回JSON數據,格式(JSON-Schema)如下:

{
  "type":"object",
  "properties": {
    "userId": {"type" : "integer"},
    "id": {"type" : "integer"},
    "title": {"type" : "string"},
    "body": {"type" : "string"}
  }
}

安裝 C++ REST SDK

$ brew install cpprestsdk
$ brew install boost
$ brew install libressl

安裝 "JSON for Modern C++"

$ brew tap nlohmann/json
$ brew install nlohmann_json

下載 RxCpp

$ git clone --recursive https://github.com/ReactiveX/RxCpp.git

創建工程

打開 Xcode,File / New / Project...
在向導的第1頁選 macOS / Command Line Tool

在向導的第2頁語言選 C++,Product Name 填上任意名稱
在向導的第3頁選擇任意文件夾,點擊 Create 創建工程。

配置工程

將 System Header Search Paths 設置為
/usr/local/Cellar/cpprestsdk/2.10.2/include
/usr/local/Cellar/boost/1.67.0_1/include
/usr/local/Cellar/libressl/2.7.4/include
/usr/local/Cellar/nlohmann_json/3.1.2/include
RxCpp安裝文件夾/Rx/v2/src

將 Library Search Paths 設置為
/usr/local/Cellar/cpprestsdk/2.10.2/lib

/usr/local/Cellar/boost/1.67.0_1/lib
/usr/local/Cellar/libressl/2.7.4/lib

將 Other Linker Flags 設置為
-lcpprest -lboost_system -lboost_thread-mt -lboost_chrono-mt -lssl -lcrypto

cpprestsdk: Undefined symbols for architecture x86_64

Post

在工程中添加 post.hpp,內容如下

#ifndef Post_hpp
#define Post_hpp

#include <iostream>
#include <nlohmann/json.hpp>
using nlohmann::json;

struct Post {
    int userId;
    int id;
    std::string title;
    std::string body;
};
void to_json(json& j, const Post& p);
void from_json(const json& j, Post& p);
std::ostream& operator<<(std::ostream& out, const Post& p);

#endif /* Post_hpp */

在工程中添加 post.cpp,內容如下

#include "Post.hpp"
#include <boost/algorithm/string/replace.hpp>
using namespace std;

void to_json(json& j, const Post& p) {
    j = json{{"userId", p.userId}, {"id", p.id}, {"title", p.title}, {"body", p.body}};
}
void from_json(const json& j, Post& p) {
    p.userId = j.at("userId").get<int>();
    p.id = j.at("id").get<int>();
    p.title = j.at("title").get<string>();
    p.body = boost::algorithm::replace_all_copy(j.at("body").get<string>(), "\n", "\\n");
}
std::ostream& operator<<(std::ostream& out, const Post& p) {
    cout << "Post {userId = " << p.userId
        << ", id = " << p.id
        << ", title = \"" << p.title
        << "\", body = \"" << p.body
        << "\"}";
    return out;
}

RestApi

在工程中添加 RestApi.hpp,內容如下

#ifndef RestApi_hpp
#define RestApi_hpp

#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
#include <cpprest/json.h>

using namespace utility;                    // Common utilities like string conversions
//using namespace web;                        // Common features like URIs.
using namespace web::http;                  // Common HTTP functionality
using namespace web::http::client;          // HTTP client features
using namespace concurrency::streams;       // Asynchronous streams

#include "rxcpp/rx.hpp"
namespace Rx {
    using namespace rxcpp;
    using namespace rxcpp::sources;
    using namespace rxcpp::operators;
    using namespace rxcpp::util;
}
using namespace Rx;

#include <nlohmann/json.hpp>
using nlohmann::json;

template<class T>
struct RestApi {
    http_client client;
    
    RestApi(const uri &base_uri) : client(base_uri) {}
    
    observable<string_t> getString(const string_t &path_query_fragment) {
        return observable<>::create<string_t>(
        [&](subscriber<string_t> s){
            client
            .request(methods::GET, path_query_fragment)
            .then([](http_response response) -> pplx::task<string_t> {
                return response.extract_string();
            })
            .then([&](pplx::task<string_t> previousTask) {
                try {
                    string_t const & v = previousTask.get();
                    s.on_next(v);
                    s.on_completed();
                } catch (http_exception const & e) {
                    s.on_error(std::current_exception());
                }
            })
            .wait();
        });
    }
    
    observable<T> getObject(const string_t &path_query_fragment) {
        return observable<>::create<T>(
        [&](subscriber<T> s){
            client
            .request(methods::GET, path_query_fragment)
            .then([](http_response response) -> pplx::task<string_t> {
                return response.extract_string();
            })
            .then([&](pplx::task<string_t> previousTask) {
                try {
                    string_t const & v = previousTask.get();
                    json j = json::parse(v);
                    T t = j;
                    s.on_next(t);
                    s.on_completed();
                } catch (http_exception const & e) {
                    s.on_error(std::current_exception());
                }
            })
            .wait();
        });
    }
    
    observable<T> getArray(const string_t &path_query_fragment) {
        return observable<>::create<T>(
        [&](subscriber<T> s){
            client
            .request(methods::GET, path_query_fragment)
            .then([](http_response response) -> pplx::task<string_t> {
                return response.extract_string();
            })
            .then([&](pplx::task<string_t> previousTask) {
                try {
                    string_t const & v = previousTask.get();
                    json j = json::parse(v);
                    std::vector<T> vec = j;
                    for(const auto& t : vec)
                        s.on_next(t);
                    s.on_completed();
                } catch (http_exception const & e) {
                    s.on_error(std::current_exception());
                }
            })
            .wait();
        });
    }

    observable<string_t> createObject(const string_t& url, const T& obj) {
        return observable<>::create<string_t>(
        [&](subscriber<string_t> s){
            json j = obj;
            client
            .request(methods::POST, url, j.dump(), U("application/json"))
            .then([](http_response response) -> pplx::task<string_t> {
                return response.extract_string();
            })
            .then([&](pplx::task<string_t> previousTask) {
                try {
                    string_t const & v = previousTask.get();
                    s.on_next(v);
                    s.on_completed();
                } catch (http_exception const & e) {
                    s.on_error(std::current_exception());
                }
            })
            .wait();
        });
    }
    
    observable<string_t> updateObject(const string_t& url, const T& obj) {
        return observable<>::create<string_t>(
        [&](subscriber<string_t> s){
            json j = obj;
            client
            .request(methods::PUT, url, j.dump(), U("application/json"))
            .then([](http_response response) -> pplx::task<string_t> {
                return response.extract_string();
            })
            .then([&](pplx::task<string_t> previousTask) {
                try {
                    string_t const & v = previousTask.get();
                    s.on_next(v);
                    s.on_completed();
                } catch (http_exception const & e) {
                    s.on_error(std::current_exception());
                }
            })
            .wait();
        });
    }
    
    observable<string_t> deleteObject(const string_t& url) {
        return observable<>::create<string_t>(
        [&](subscriber<string_t> s){
            client
            .request(methods::DEL, url)
            .then([](http_response response) -> pplx::task<string_t> {
                return response.extract_string();
            })
            .then([&](pplx::task<string_t> previousTask) {
                try {
                    string_t const & v = previousTask.get();
                    s.on_next(v);
                    s.on_completed();
                } catch (http_exception const & e) {
                    s.on_error(std::current_exception());
                }
            })
            .wait();
        });
    }
};

#endif /* RestApi_hpp */

main

將 main.cpp 的內容改為

#include "Post.hpp"
#include "RestApi.hpp"

using namespace std;

int main(int argc, char* argv[])
{
    RestApi<Post> api(U("https://jsonplaceholder.typicode.com/"));
    api.getString(U("posts/1")).subscribe([](const string_t& v){cout << v << endl;});
    api.getObject(U("posts/1")).subscribe([](const Post& v){cout << v << endl;});
    api.getArray(U("posts")).take(2).subscribe([](const Post& v){cout << v << endl;});
    Post o;
    o.id = 0;
    o.userId = 101;
    o.title = U("test title");
    o.body = U("test body");
    api.createObject(U("posts"), o).subscribe([](string_t v){cout << v << endl;});
    api.updateObject(U("posts/1"), o).subscribe([](string_t v){cout << v << endl;});
    api.deleteObject(U("posts/1")).subscribe([](string_t v){cout << v << endl;});

    return 0;
}

輸出結果

{
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
  "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
Post {userId = 1, id = 1, title = "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", body = "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"}
Post {userId = 1, id = 1, title = "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", body = "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"}
Post {userId = 1, id = 2, title = "qui est esse", body = "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"}
{
  "body": "test body",
  "id": 101,
  "title": "test title",
  "userId": 101
}
{
  "body": "test body",
  "id": 1,
  "title": "test title",
  "userId": 101
}
{}

ReactiveX 學習筆記(24)使用 RxCpp + C++ REST SDK 調用 REST API