В жизни любой достаточно большой программы наступает момент, когда нужно вывести наружу какой-нибудь API — для плагинов, для интеграции с другими системами, для автоматизации и т.д. Для этого есть много разных технологий, но как-то так исторически сложилось, что сейчас принято делать API в виде REST-сервисов. В принципе, если не гнаться за экономией каждого байта и микросекунды, то в этом есть смысл: HTTP-запрос сделать легко из любого языка, это хорошо работает и локально, и по сети, не нужно сильно глубоко погружаться в недры сетевых протоколов.
Давайте посмотрим, как к уже существующей программе на C++ можно быстренько прикрутить Web API, используя для этого библиотеку POCO.
Задача
Пусть у нас есть вот такая программа:
#include <iostream>
#include <conio.h>
int main(int argc, char** argv)
{
for(;;)
{
std::cout << "Hello world!" << std::endl;
_getch();
}
return 0;
}
Мы хотим дать возможность извне менять выводимый текст. Для этого клиенту будет достаточно сделать запрос вида:
http://host:port/setText?text=NewText
Собираем POCO
- Идём вот сюда и скачиваем последнюю версию Basic Edition for Windows (1.4.6p1 на момент написания этой статьи).
- Разархивируем куда-нибудь.
- Идём туда, куда разархивировали и видим там батники с именами build_vs90.cmd, build_vs100.cmd, build_vs110.cmd. Они собирают библиотеку POCO с помощью разных версий Visual Studio. Просто запустите нужный (студия, само-собой, к этому моменту должна быть уже установлена). Я лично компилировал POCO под VS2008, VS2010 и VS2012 — проблем не возникло ни разу.
Подключаем POCO к своему проекту
- Сделайте в папке своего проекта папку POCO
- В этой папке сделайте три подпапки: include, lib, bin.
- В папку include скопируйте все заголовочные файлы POCO (из папок %POCO%XMLinclude, %POCO%Utilinclude, %POCO%Netinclude и %POCO%Foundationinclude)
- В папку lib скопируйте lib-файлы из %POCO%lib. Для удобства можно разнести их по подпапкам Debug и Release.
- В папку bin скопируйте dll-файлы из %POCO%bin. Их аналогично можно разнести по подпапкам Debug и Release.
- Добавьте путь к папке include в Additional Include Directories, а путь к папке lib — в Additional Library Directories своего проекта. Удобно использовать переменную $(SolutionDir) и относительные пути.
- Добавьте файлы PocoFoundationd.lib, PocoNetd.lib, PocoUtild.lib в Additional Dependencies проекта для Debug-конфигурации, а их версии без буквы «d» в конце — для Release-конфигурации.
- Не забудьте, что dll-файлы из папки bin нужно будет деплоить вместе с вашим приложением. Можете просто их скопировать в папки DebugRelease или сделать шаг в процессе компиляции, где они будут туда копироваться.
В конце топика есть ссылка на настроенный проект на GitHub — можете использовать его как базу.
Используем POCO для создания Web API
Код получается очень простой и понятный. В главном файле создаём объект класса, унаследованного от ServerApplication. Это позволит нам сохранить в главном потоке вывод наших сообщений, одновременно запустив в другом потоке веб-сервер с помощью класса HTTPServer. Для того, чтобы объяснить веб-серверу, как обрабатывать входящие запросы мы сделаем свою фабрику, унаследованную от HTTPRequestHandlerFactory. На каждый запрос она будет создавать новый объект обработчика (класса, унаследованного от HTTPRequestHandler), который уже и будет заниматься разбором входящего запроса, его обработкой и выдачей клиенту результата.
В коде всё выглядит даже проще, чем в описании выше.
#include "MyRequestHandler.h"
int main(int argc, char** argv)
{
MyServerApp app;
return app.run(argc, argv);
}
#pragma once
#include <Poco/Mutex.h>
#include <Poco/Util/ServerApplication.h>
using namespace Poco;
using namespace Poco::Util;
using namespace std;
class MyServerApp : public ServerApplication
{
public:
static string getText();
static void setText(string newText);
protected:
int main(const vector<string> &);
static string text;
static Mutex textLock;
};
#include <iostream>
#include <conio.h>
#include <string>
#include "MyRequestHandler.h"
#include <Poco/Net/HTTPServerRequest.h>
#include <Poco/Net/HTTPRequestHandler.h>
#include <Poco/Net/HTTPServerResponse.h>
#include <Poco/Net/HTTPServer.h>
#include <Poco/Net/HTTPRequestHandlerFactory.h>
#include <Poco/ScopedLock.h>
#include <Poco/URI.h>
#include <Poco/StringTokenizer.h>
using namespace Poco::Net;
string MyServerApp::text = "Hello world!";
Mutex MyServerApp::textLock;
class CMyRequestHandler : public HTTPRequestHandler
{
public:
void handleRequest(HTTPServerRequest &req, HTTPServerResponse &resp)
{
resp.setStatus(HTTPResponse::HTTP_OK);
resp.setContentType("text/html");
ostream& out = resp.send();
URI uri(req.getURI());
if (uri.toString().find("/setText") == 0)
{
StringTokenizer str(uri.getQuery(), "=");
if (str.count() == 2 && str[0] == "text")
{
MyServerApp::setText(str[1]);
out << "ok";
out.flush();
return;
}
}
out << "error";
out.flush();
}
};
class MyRequestHandlerFactory : public HTTPRequestHandlerFactory
{
public:
virtual HTTPRequestHandler* createRequestHandler(const HTTPServerRequest &)
{
return new CMyRequestHandler;
}
};
void MyServerApp::setText(string newText)
{
ScopedLock<Mutex> lock(textLock);
text = newText;
}
string MyServerApp::getText()
{
ScopedLock<Mutex> lock(textLock);
return text;
}
int MyServerApp::main(const vector<string> &)
{
HTTPServer s(new MyRequestHandlerFactory, ServerSocket(8000), new HTTPServerParams);
s.start();
for(;;)
{
cout << MyServerApp::getText() << endl;
_getch();
}
s.stop();
return Application::EXIT_OK;
}
Дополнительные материалы
Всем спасибо, удачи в использовании POCO.
Автор: tangro