Trong bài viết này, tôi sẽ nói về cách tôi tạo một mẫu (cookiecutter) và thiết lập môi trường để viết dịch vụ API REST trong C++ bằng cách sử dụng docker/docker-compose và trình quản lý gói conan.
Trong cuộc thi hackathon tiếp theo, nơi tôi tham gia với tư cách là nhà phát triển phụ trợ, câu hỏi đặt ra là nên sử dụng gì để viết microservice tiếp theo. Tất cả những gì được viết cho đến nay đều do tôi và tôi viết.
Vì vậy, chúng tôi phải đối mặt với nhiệm vụ viết một dịch vụ tải cao, nhiệm vụ chính của nó là xử lý trước dữ liệu đến nó và ghi nó vào cơ sở dữ liệu. Và sau một lần nghỉ giải lao khác, một người bạn đã gợi ý rằng tôi, với tư cách là một nhà phát triển C++, hãy viết dịch vụ này bằng cách sử dụng các chuyên gia. Lập luận rằng điều này sẽ nhanh hơn, hiệu quả hơn và nói chung, ban giám khảo sẽ rất vui với cách chúng tôi biết cách quản lý tài nguyên của nhóm. Tôi trả lời rằng tôi chưa bao giờ làm những việc như vậy trong C++ và có thể dễ dàng dành hơn 20 giờ còn lại để tìm kiếm, biên soạn và liên kết các thư viện phù hợp. Nói một cách đơn giản, tôi đã hết hồn. Đó là điều chúng tôi đã quyết định và bình tĩnh hoàn thành mọi thứ bằng Python.
Bây giờ, trong thời gian buộc phải tự cách ly, tôi quyết định tìm cách viết dịch vụ bằng C++. Điều đầu tiên cần làm là quyết định một thư viện phù hợp. Sự lựa chọn của tôi rơi vào
conanfile.txt
[yêu cầu]poco/1.9.3
libpq/11.5
và bằng một lệnh đơn giản "cài đặt conan." cài đặt các thư viện cần thiết. Đương nhiên, cũng cần phải thực hiện những thay đổi đối với
CMakeLists.txt
include(build/conanbuildinfo.cmake)
conan_basic_setup()
target_link_libraries(<target_name> ${CONAN_LIBS})
Sau đó, tôi bắt đầu tìm kiếm một thư viện để làm việc với PostgreSQL, vì đó là thư viện mà tôi có ít kinh nghiệm làm việc và nó cũng là thư viện mà các dịch vụ Python của chúng tôi tương tác. Và bạn có biết tôi đã học được gì không? Nó ở POCO! Nhưng conan không biết rằng nó nằm trong POCO và không biết cách xây dựng nó, có một tệp cấu hình lỗi thời trong kho (tôi đã viết về lỗi này cho những người tạo ra POCO). Điều này có nghĩa là bạn sẽ phải tìm một thư viện khác.
Và rồi sự lựa chọn của tôi rơi vào một thư viện ít phổ biến hơn
Bước tiếp theo là viết một mẫu dịch vụ có thể xử lý các yêu cầu.
Chúng ta phải kế thừa lớp TemplateServerApp từ Poco::Util::ServerApplication và ghi đè phương thức chính.
Ứng dụng máy chủ mẫu
#pragma once
#include <string>
#include <vector>
#include <Poco/Util/ServerApplication.h>
class TemplateServerApp : public Poco::Util::ServerApplication
{
protected:
int main(const std::vector<std::string> &);
};
int TemplateServerApp::main(const vector<string> &)
{
HTTPServerParams* pParams = new HTTPServerParams;
pParams->setMaxQueued(100);
pParams->setMaxThreads(16);
HTTPServer s(new TemplateRequestHandlerFactory, ServerSocket(8000), pParams);
s.start();
cerr << "Server started" << endl;
waitForTerminationRequest(); // wait for CTRL-C or kill
cerr << "Shutting down..." << endl;
s.stop();
return Application::EXIT_OK;
}
Trong phương thức chính chúng ta phải đặt các tham số: cổng, số lượng luồng và kích thước hàng đợi. Và quan trọng nhất, bạn phải chỉ định một trình xử lý cho các yêu cầu gửi đến. Điều này được thực hiện bằng cách tạo ra một nhà máy
MẫuYêu cầuHandlerFactory
class TemplateRequestHandlerFactory : public HTTPRequestHandlerFactory
{
public:
virtual HTTPRequestHandler* createRequestHandler(const HTTPServerRequest & request)
{
return new TemplateServerAppHandler;
}
};
Trong trường hợp của tôi, nó chỉ đơn giản tạo ra cùng một trình xử lý mọi lúc - FileServerAppHandler. Đây là nơi chúng ta có thể đặt logic kinh doanh của mình.
MẫuServerAppHandler
class TemplateServerAppHandler : public HTTPRequestHandler
{
public:
void handleRequest(HTTPServerRequest &req, HTTPServerResponse &resp)
{
URI uri(req.getURI());
string method = req.getMethod();
cerr << "URI: " << uri.toString() << endl;
cerr << "Method: " << req.getMethod() << endl;
StringTokenizer tokenizer(uri.getPath(), "/", StringTokenizer::TOK_TRIM);
HTMLForm form(req,req.stream());
if(!method.compare("POST"))
{
cerr << "POST" << endl;
}
else if(!method.compare("PUT"))
{
cerr << "PUT" << endl;
}
else if(!method.compare("DELETE"))
{
cerr << "DELETE" << endl;
}
resp.setStatus(HTTPResponse::HTTP_OK);
resp.setContentType("application/json");
ostream& out = resp.send();
out << "{"hello":"heh"}" << endl;
out.flush();
}
};
Tôi cũng đã tạo một mẫu lớp để làm việc với PostgreSQL. Để thực hiện SQL đơn giản, chẳng hạn như tạo bảng, có một phương thức Thực thiSQL(). Đối với các truy vấn hoặc truy xuất dữ liệu phức tạp hơn, bạn sẽ phải có kết nối qua Nhận kết nối() và sử dụng API libpg. (Có lẽ sau này tôi sẽ sửa chữa sự bất công này).
Cơ sở dữ liệu
#pragma once
#include <memory>
#include <mutex>
#include <libpq-fe.h>
class Database
{
public:
Database();
std::shared_ptr<PGconn> GetConnection() const;
bool ExecuteSQL(const std::string& sql);
private:
void establish_connection();
void LoadEnvVariables();
std::string m_dbhost;
int m_dbport;
std::string m_dbname;
std::string m_dbuser;
std::string m_dbpass;
std::shared_ptr<PGconn> m_connection;
};
Tất cả các tham số kết nối tới cơ sở dữ liệu đều được lấy từ môi trường nên bạn cũng cần tạo và cấu hình file .env
.NS
DATABASE_NAME=template
DATABASE_USER=user
DATABASE_PASSWORD=password
DATABASE_HOST=postgres
DATABASE_PORT=5432
Bạn có thể xem tất cả mã tại
Và bây giờ đến giai đoạn cuối cùng của việc viết dockerfile và docker-compose.yml. Thành thật mà nói, việc này tốn phần lớn thời gian, không chỉ vì tôi là một người mới, vì tôi cần phải xây dựng lại thư viện mỗi lần, mà còn vì những cạm bẫy của conan. Ví dụ: để conan tải xuống, cài đặt và xây dựng các phụ thuộc cần thiết, việc tải xuống “conan install .” là chưa đủ, nó còn cần phải truyền tham số -s trình biên dịch.libcxx=libstdc++11, nếu không thì bạn có nguy cơ gặp phải một loạt lỗi ở giai đoạn liên kết ứng dụng của mình. Tôi đã mắc lỗi này trong vài giờ và tôi hy vọng bài viết này sẽ giúp những người khác giải quyết vấn đề này trong thời gian ngắn hơn.
Tiếp theo, sau khi viết docker-compose.yml, theo lời khuyên của bạn tôi, tôi đã thêm hỗ trợ
Tôi hy vọng mẫu này sẽ giúp ích cho những người mới bắt đầu trên con đường khó khăn trong việc phát triển các ứng dụng API REST bằng một ngôn ngữ tuyệt vời và mạnh mẽ nhưng vụng về như C++.
Ngoài ra, tôi thực sự khuyên bạn nên đọc ở đây
Nguồn: www.habr.com