C++ REST API CROW库使用攻略

2023-11-13  本文已影响0人  FredricZhu

本文是基于A站视频教程<Web Servers and API using C++>的课程练习。因为Heroku云服务平台国内无法访问。将数据库从MongoDB切换成了SQLLite,去除了上云部分。其他部分的内容与课程一致。
选择SQLite是因为之前有SQLite的需求,用过SQLite,不想去折腾其他的数据库了。因为又需要安装驱动之类的,略坑。

下面是相关的源代码,

基镜像Dockerfile文件

FROM gcc:7.3.0

RUN echo "deb http://archive.debian.org/debian stretch main contrib non-free" > /etc/apt/sources.list
RUN apt-get -y update
RUN apt-get -y upgrade
RUN apt-get -y install cmake
RUN apt-get -y install libboost-all-dev=1.62.0.1
RUN apt-get -y install build-essential libtcmalloc-minimal4 && \
    ln -s /usr/lib/libtcmalloc_minimal.so.4 /usr/lib/libtcmalloc_minimal.so

基镜像启动container后, commit.sh

docker commit {container_id} hello_base:latest

工程Dockerfile文件

FROM hello_base:latest
WORKDIR /usr/src/cppweb/parse_path/build
CMD ["./parse_path"]

Build Image, Build Project, Run 命令,cmd.sh

#!/bin/bash
# Build Image 
docker build -t parse_path:latest .

# Build
docker run -d --name cpp_build -v /home/fredric/code/cpp_practice/cppweb:/usr/src/cppweb -ti cppbox:latest bash
docker exec -ti cpp_build bash

# Run
docker run -v /home/fredric/code/cpp_practice/cppweb/:/usr/src/cppweb -p 8080:8080 -e PORT=8080 parse_path:latest

crow_all.h下载地址,
https://github.com/ipkn/crow/releases/tag/v0.1
json.hpp下载地址,
https://github.com/nlohmann/json/releases
SQLite3 相关文件请去官网下载。

CMakeLists.txt

cmake_minimum_required(VERSION 3.7)
project(parse_path)

set(CMAKE_CXX_STANDARD 11)
set(THREADS_PREFER_PTHREAD_FLAG ON)

find_package(Boost COMPONENTS system filesystem REQUIRED)
find_package(Threads)

include_directories(${Boost_INCLUDE_DIRS})
add_executable(parse_path main.cpp sqlite3.c sqllite_op.cc)
target_link_libraries(parse_path ${Boost_LIBRARIES} Threads::Threads dl)

add_executable(data_maker data_maker.cpp sqlite3.c sqllite_op.cc)
target_link_libraries(data_maker ${Boost_LIBRARIES} Threads::Threads dl)

数据构建程序data_maker.cpp

#include <stdio.h>
#include <stdlib.h>
#include <map>
#include <string>
#include <iostream>
#include <boost/filesystem.hpp>
#include "sqllite_op.h"


void create_db_structure(SQLiteOp& sqlite_op) {
     /* Create SQL statement */
    std::string create_table_sql = "CREATE TABLE CONTACT("  \
         "id INT PRIMARY KEY     NOT NULL," \
         "first_name           TEXT    NOT NULL," \
         "last_name         TEXT    NOT NULL," \
         "email           TEXT    NOT NULL," \
         "phone           TEXT    NOT NULL," \
         "photo           TEXT    NOT NULL);";
    sqlite_op.ExecUpdate(create_table_sql);
    
    /* Create SQL statement */
    std::string insert_data_sql = "INSERT INTO CONTACT (id,first_name,last_name,email,phone,photo) "  \
         "VALUES (1, 'Fredric', 'Zhu', 'Fredric2010@outlook.com', '17366885637', 'https://img2.baidu.com/it/u=1574304958,174721775&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=665'); " \
        
         "INSERT INTO CONTACT (id,first_name,last_name,email,phone,photo) "  \
         "VALUES (2, 'BoFeng', 'Tan', 'bofeng111@gmail.com', '16311112222', 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fsafe-img.xhscdn.com%2Fbw1%2Fe167bf5b-dffc-413a-837b-ce107dc9e24d%3FimageView2%2F2%2Fw%2F1080%2Fformat%2Fjpg&refer=http%3A%2F%2Fsafe-img.xhscdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1702520553&t=0505a58fa0554700d57a8767bbc968d6'); " \
        
        "INSERT INTO CONTACT (id,first_name,last_name,email,phone,photo) "  \
         "VALUES (3, 'Lily', 'Hu', 'lilyhu123@qq.com', '16513122328', 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fsafe-img.xhscdn.com%2Fbw1%2F5737cad3-5f86-46c2-84af-67be0b3b7ca7%3FimageView2%2F2%2Fw%2F1080%2Fformat%2Fjpg&refer=http%3A%2F%2Fsafe-img.xhscdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1702520553&t=59bbd0e5ad58abb7e202e4342525a247'); " \
        
        "INSERT INTO CONTACT (id,first_name,last_name,email,phone,photo) "  \
         "VALUES (4, 'Fei', 'Zhang', 'feizhang222@163.com', '15713122326', 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fsafe-img.xhscdn.com%2Fbw1%2F8f544e0c-b753-4b51-ba8a-f08e727d0f56%3FimageView2%2F2%2Fw%2F1080%2Fformat%2Fjpg&refer=http%3A%2F%2Fsafe-img.xhscdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1702520553&t=bde67166ed212413ac87d139bd422cd5'); " \
        
        "INSERT INTO CONTACT (id,first_name,last_name,email,phone,photo) "  \
         "VALUES (5, 'Li', 'Liu', 'LiLiu333@outlook.com', '15813122328', 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fsafe-img.xhscdn.com%2Fbw1%2F3d27156b-5157-4ca1-92a3-1d471aa449f4%3FimageView2%2F2%2Fw%2F1080%2Fformat%2Fjpg&refer=http%3A%2F%2Fsafe-img.xhscdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1702520553&t=55ca224fb02d33256c16d099fa6f4bd5'); " \
        
    
        "INSERT INTO CONTACT (id,first_name,last_name,email,phone,photo) "  \
         "VALUES (6, 'Fei', 'Yue', 'FeiYue222@outlook.com', '15713142329', 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fsafe-img.xhscdn.com%2Fbw1%2Fc8dbb82e-2d57-406d-89ab-4f0dbc7b01d6%3FimageView2%2F2%2Fw%2F1080%2Fformat%2Fjpg&refer=http%3A%2F%2Fsafe-img.xhscdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1702520556&t=48199667ec06c05ad0b4cc2cb569e2df'); " \
        
        "INSERT INTO CONTACT (id,first_name,last_name,email,phone,photo) "  \
         "VALUES (7, 'Jie', 'Wu', 'JieWu333@163.com', '15913152327', 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fsafe-img.xhscdn.com%2Fbw1%2Fbc45b1ae-3876-40c9-ac79-f4f97577e779%3FimageView2%2F2%2Fw%2F1080%2Fformat%2Fjpg&refer=http%3A%2F%2Fsafe-img.xhscdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1702520556&t=78ce12b4808d5c9583a3d69b621047bf'); " \
        
        "INSERT INTO CONTACT (id,first_name,last_name,email,phone,photo) "  \
         "VALUES (8, 'JianZhong', 'Han', 'jianzhonghan@gmail.com', '15623253328', 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fsafe-img.xhscdn.com%2Fbw1%2Fe6016b13-4d73-4d56-bd50-8174c5a2b921%3FimageView2%2F2%2Fw%2F1080%2Fformat%2Fjpg&refer=http%3A%2F%2Fsafe-img.xhscdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1702520556&t=5957eab53850d7a6bc60e388c4326293'); " \
        
        "INSERT INTO CONTACT (id,first_name,last_name,email,phone,photo) "  \
         "VALUES (9, 'Ming', 'Li', 'mingli111@163.com', '15323263428', 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fsafe-img.xhscdn.com%2Fbw1%2F71129792-05f5-4da1-8cce-cfe96f6145f0%3FimageView2%2F2%2Fw%2F1080%2Fformat%2Fjpg&refer=http%3A%2F%2Fsafe-img.xhscdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1702521087&t=672f6dc320f8dd0ff1e8c52c22497fa4'); " \
        
        "INSERT INTO CONTACT (id,first_name,last_name,email,phone,photo) "  \
         "VALUES (10, 'San', 'Zhang', 'sanzhang222@gmail.com', '15923273429', 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fsafe-img.xhscdn.com%2Fbw1%2F3f0cd4ca-9101-41aa-a8f3-5d27b880625d%3FimageView2%2F2%2Fw%2F1080%2Fformat%2Fjpg&refer=http%3A%2F%2Fsafe-img.xhscdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1702521087&t=84688271c47e4a8af966422680b5e9b6'); " \


        "INSERT INTO CONTACT (id,first_name,last_name,email,phone,photo) "  \
         "VALUES (11, 'Wu', 'Wang', 'wuwang555@163.com', '15723293428', 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fsafe-img.xhscdn.com%2Fbw1%2F2d4476dc-f191-4a42-bfdc-7cc37d24f4bc%3FimageView2%2F2%2Fw%2F1080%2Fformat%2Fjpg&refer=http%3A%2F%2Fsafe-img.xhscdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1702521087&t=05d5b48b730ae4013dbef7807104e4cf'); " \

        "INSERT INTO CONTACT (id,first_name,last_name,email,phone,photo) "  \
         "VALUES (12, 'Liu', 'Zhao', 'liuzhao@gmail.com', '15223293626', 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fsafe-img.xhscdn.com%2Fbw1%2F983f97bf-a2dc-4696-b74c-2b14ca355c57%3FimageView2%2F2%2Fw%2F1080%2Fformat%2Fjpg&refer=http%3A%2F%2Fsafe-img.xhscdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1702521470&t=ccacc11910461a941ecf20a18f97922c'); ";
        
    sqlite_op.ExecUpdate(insert_data_sql);
}

int main(int argc, char* argv[]) {
    std::string db_name {"../test.db"};
    bool should_create_db {false};
    if(!boost::filesystem::exists(db_name)) {
        should_create_db = true;
    }

    SQLiteOp sqlite_op {db_name};
    sqlite_op.Open();
    
    if(should_create_db) {
        std::cout << "db not exists\n";
        create_db_structure(sqlite_op);
    }
    sqlite_op.Close();
    return EXIT_SUCCESS;
}

sqlite_op.h

#ifndef _FREDRIC_SQLITE_OP_H_
#define _FREDRIC_SQLITE_OP_H_

#include <string>
#include <map>
#include <vector>
#include "sqlite3.h"

using ResultMap = std::vector<std::map<std::string, std::string>>;

struct SQLiteOp {
public:
    SQLiteOp(std::string const& db_name_);
    ~SQLiteOp();
    void Open();
    void Close();
    bool ExecUpdate(std::string const& updateSql);
    ResultMap Query(std::string const& querySql);
private:
    std::string db_name;
    sqlite3 *db;
    bool is_closed {true};
};

#endif

sqlite_op.cc

#include "sqllite_op.h"
#include <iostream>

SQLiteOp::SQLiteOp(std::string const& db_name_): db_name(db_name_) {}

void SQLiteOp::Open() {
    char *zErrMsg = 0;
    int rc;
    char *sql;

    /* Open database */
    rc = sqlite3_open(db_name.data(), &db);
    if( rc ){
        std::cerr << "Can't open database: " << sqlite3_errmsg(db) << "\n"; 
        exit(0);
    }else{
        is_closed = false;
        std::cout << "Opened database successfully\n";
    }
}

void SQLiteOp::Close() {
    std::cout << "Close DB\n";
    sqlite3_close(db);
    is_closed = true;
}

bool SQLiteOp::ExecUpdate(std::string const& updateSql) {
    char *zErrMsg = 0;
    int rc = sqlite3_exec(db, updateSql.data(), nullptr, 0, &zErrMsg);
    if( rc != SQLITE_OK ){
        std::cerr << "SQL error: " << zErrMsg << "\n";
        sqlite3_free(zErrMsg);
        return false;
    }else{
        std::cout << "Records created successfully\n";
        return true;
    }
}

ResultMap SQLiteOp::Query(std::string const& querySql) {
    ResultMap results;
    char *zErrMsg = 0;
    /* Execute SQL statement */
    int rc = sqlite3_exec(db, querySql.data(), [](void *data, int argc, char **argv, char **azColName) {
            int i;
            auto& results_ = *((ResultMap*)(data));
            std::map<std::string, std::string> result;
            for(i=0; i<argc; i++){
                if(argv[i]) {
                    result[azColName[i]] =  argv[i];
                } else {
                    result[azColName[i]] = "NULL";
                }
            }
            results_.emplace_back(std::move(result));
            return 0;
    }, (void*)&results, &zErrMsg);

    if( rc != SQLITE_OK ){
        std::cerr << "SQL error: " << zErrMsg << "\n";
        sqlite3_free(zErrMsg);
    }else{
        std::cout << "Operation done successfully\n";
    }
    return results;
}

SQLiteOp::~SQLiteOp() {
    if(!is_closed) {
        std::cout << "Close DB\n";
        sqlite3_close(db);
    }
}

main.cpp

#include "crow_all.h"
#include "sqllite_op.h"
#include "json.hpp"
#include <fstream>
#include <sstream>
#include <string>

using ordered_json = nlohmann::ordered_json;

void send_file(crow::response& res, std::string file_name, std::string content_type) {
    std::ifstream in("../public/" + file_name, std::ifstream::in);
        if(in) {
            std::ostringstream contents;
            contents << in.rdbuf();
            in.close();
            res.set_header("Content-Type", content_type);
            res.write(contents.str());
        } else {
            res.code = 404;
            res.write("Not found");
        }
        res.end();   
}

void send_html(crow::response& res, std::string file_name) {
    send_file(res, file_name + ".html", "text/html");
}


void send_image(crow::response& res, std::string file_name) {
    send_file(res, "images/" + file_name, "image/jpeg");
}

void send_script(crow::response& res, std::string file_name) {
    send_file(res, "scripts/" + file_name, "text/javascript");
}

void send_style(crow::response& res, std::string file_name) {
    send_file(res, "styles/" + file_name, "text/css");
}

void get_view(crow::response& res, std::string const& file_name, crow::mustache::context& ctx ) {
    res.set_header("Content-Type", "text/html");
    auto text = crow::mustache::load("../public/" + file_name + ".html").render(ctx);
    res.write(text);
    res.end();
}

void not_found(crow::response& res, std::string const& message) {
    res.code = 404;
    res.write(message + ": not found");
    res.end();
}

int main(int argc, char* argv[]) {
    crow::SimpleApp app;
    // mustache需要这一句,没有set_base, mustache不工作
    crow::mustache::set_base(".");
    std::string db_name {"../test.db"};

    CROW_ROUTE(app, "/styles/<string>") (
        [](crow::request const& req, crow::response& res, std::string file_name) {
            send_style(res, file_name);
        }
    );

    CROW_ROUTE(app, "/scripts/<string>") (
        [](crow::request const& req, crow::response& res, std::string file_name) {
            send_script(res, file_name);
        }
    );

    CROW_ROUTE(app, "/images/<string>") (
        [](crow::request const& req, crow::response& res, std::string file_name) {
            send_image(res, file_name);
        }
    );

    CROW_ROUTE(app, "/") (
        [](crow::request const& req, crow::response& res) {
            send_html(res, "index");
        }
    );

    CROW_ROUTE(app, "/contacts")(
        [&](crow::request const& req, crow::response& res) {
            SQLiteOp sqlite_op(db_name);
            std::string query_str = "SELECT * FROM CONTACT LIMIT 4 OFFSET 4";
            sqlite_op.Open();
            auto results = sqlite_op.Query(query_str);
            crow::json::wvalue dto;
            std::vector<crow::json::rvalue> contacts;
            contacts.reserve(4);
            for(auto const& result: results) {
                ordered_json js_res;
                for(auto const& entry: result) {
                    js_res[entry.first] = entry.second;
                }
                contacts.push_back(crow::json::load(js_res.dump()));
            }
            dto["contacts"] = contacts;
            get_view(res, "contacts", dto);
        }
    );

    CROW_ROUTE(app, "/contact/<string>")(
        [&](crow::request const& req, crow::response& res, std::string email) {
            SQLiteOp sqlite_op(db_name);
            std::string query_str = "SELECT * FROM CONTACT where email='" + email + "'";
            sqlite_op.Open();
            auto results = sqlite_op.Query(query_str);
            crow::json::wvalue dto;
            std::vector<crow::json::rvalue> contacts;
            contacts.reserve(4);
            for(auto const& result: results) {
                ordered_json js_res;
                for(auto const& entry: result) {
                    js_res[entry.first] = entry.second;
                }
                contacts.push_back(crow::json::load(js_res.dump()));
            }
            dto["contact"] = contacts;
            get_view(res, "contact", dto);
        }
    );

    CROW_ROUTE(app, "/contact/<string>/<string>")(
        [&](crow::request const& req, crow::response& res, std::string first_name, std::string last_name) {
            SQLiteOp sqlite_op(db_name);
            std::string query_str = "SELECT * FROM CONTACT where first_name='" + first_name + "' and last_name='" + last_name + "'";
            std::cout << query_str << std::endl;
            sqlite_op.Open();
            auto results = sqlite_op.Query(query_str);
            if(results.size() == 0) {
                not_found(res, "Contact");
                return;
            }

            crow::json::wvalue dto;
            std::vector<crow::json::rvalue> contacts;
            contacts.reserve(4);
            for(auto const& result: results) {
                ordered_json js_res;
                for(auto const& entry: result) {
                    js_res[entry.first] = entry.second;
                }
                contacts.push_back(crow::json::load(js_res.dump()));
            }
            dto["contact"] = contacts;
            get_view(res, "contact", dto);
        }
    );

    CROW_ROUTE(app, "/add/<int>/<int>")(
        [](crow::request const& req, crow::response& res, int a, int b) {
            res.set_header("Content-Type", "text/plain");
            std::ostringstream os;
            os << "Integer: " << a << " + " << b << " = " << a + b << "\n";
            res.write(os.str());
            res.end();
        }
    );

    CROW_ROUTE(app, "/add/<double>/<double>")(
        [](crow::request const& req, crow::response& res, double a, double b) {
            res.set_header("Content-Type", "text/plain");
            std::ostringstream os;
            os << "Double: " << a << " + " << b << " = " << a + b << "\n";
            res.write(os.str());
            res.end();
        }
    );

    CROW_ROUTE(app, "/add/<string>/<string>")(
        [](crow::request const& req, crow::response& res, std::string a, std::string b) {
            res.set_header("Content-Type", "text/plain");
            std::ostringstream os;
            os << "String: " << a << " + " << b << " = " << a + b << "\n";
            res.write(os.str());
            res.end();
        }
    );

    char* port = getenv("PORT");
    uint16_t i_port = static_cast<uint16_t>(port != nullptr? std::stoi(port): 18080);
    std::cout << "PORT = " << i_port << std::endl;
    app.port(i_port).multithreaded().run();
    return EXIT_SUCCESS;
}

在docker镜像中Build程序后,启动控制台,效果如下


image.png

Contacts页面


image.png

Email查询


image.png

FirstName, LastName查询


image.png

加法


image.png
上一篇 下一篇

猜你喜欢

热点阅读