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程序后,启动控制台,效果如下

Contacts页面

Email查询

FirstName, LastName查询

加法
