#cpp #http #curl #парсер #веб_сервер
Учусь писать HTTP веб-сервер. Сам сервер есть. Запускаю сервак, посылаю HTTP-запрос
curl -D dumpbin 127.0.0.1:2000. На сервере сохраняю запрос в строковую переменную.
Теперь (и я не знаю, как это сделать) мне надо распарсить весь полученный запрос, составить
ответ и отправить обратно. С отправкой я справлюсь, а вот с парсингом и с составлением
ответа возникли проблемы.
Сказали, что следует использовать libcurl, но я не понимаю, как им можно парсить
готовые строки и можно ли.
Помогите, пожалуйста, примером такого использования этой либы, если он возможен.
Посоветуйте, какие можно использовать библиотеки еще, чтобы решить эту задачу.
//Вид запроса (это то, что вернул мой эхо-сервер при использовании утилиты curl):
request = "GET /index.html HTTP/1.1\r\n"
"Host: 127.0.0.1:5991\r\n"
"User-Agent: curl/7.47.0\r\n"
"Accept: */*\r\n"
"\r\n";
//Вид ответа (это я прочитал в комментах к заданию на степике):
answer = "HTTP/1.0 200 OK\r\n"
"Content-length: %d\r\n"
"Connection: close\r\n"
"Content-Type: text/html\r\n"
"\r\n"
"%s";
Суть в том, что есть данные, которые разделены \r\n и заканчиваются двойным переводом
каретки и строки \r\n\r\n
Ответы
Ответ 1
tl;dr см. конец ответа для реализации парсера заголовков
licurl используют, как правило, для написания клиентов, смотрите FAQ:
5.17 Can I write a server with libcurl?
No. libcurl offers no functions or building blocks to build any kind
of internet protocol server. libcurl is only a client-side library.
For server libraries, you need to continue your search elsewhere but
there exist many good open source ones out there for most protocols
you could possibly want a server for. And there are really good
stand-alone ones that have been tested and proven for many years.
There's no need for you to reinvent them!
В вольном переводе:
5.17 Могу ли я использовать libcurl для написания сервера?
Нет. libcurl не предлагает функций или других строительных блоков для
создания какого либо IP сервера. libcurl это только клиентская
библиотека. Вам нужно продолжить ваши поиски библиотеки для построения
сервера. Существует большое количество таких библиотек с открытым
исходным кодом для большинства протоколов. Также существует большое
кол-во проверенных годами stand-alone приложений. Вам не стоит
изобретать их заново.
Хотя для построения клиента libcurl действительно очень удобная вещь, как упомянул
в своем ответе @Pink Tux. В том числе заголовки вы получаете уже разбитые по одному.
Для построения сервера, как один из вариантов, можно использовать mongoose (на c,
в виде пары .c/.h, добавляемой к вашему проекту, либо одну из многочисленных c++ оберток).
Пример минимального сервера достаточно громоздкий, поэтому приводить здесь не буду.
Можно посмотреть здесь.
Еще один вариант - библиотека cpp-netlib которая изначально планировалась как часть
boost (но потом планы разработчиков поменялись). В простейшем виде http сервер выглядит так:
namespace http = boost::network::http;
struct handler;
typedef http::server http_server;
struct handler {
void operator() (http_server::request const &request,
http_server::response &response) {
response = http_server::response::stock_reply(
http_server::response::ok, "Hello, world!");
}
void log(http_server::string_type const &info) {
std::cerr << "ERROR: " << info << '\n';
}
};
int main(int arg, char * argv[]) {
handler handler_;
http_server::options options(handler_);
http_server server_(
options.address("0.0.0.0")
.port("8000"));
server_.run();
}
Если вам нужно что-то еще более высокоуровневое (с поддержкой шаблонов, json, middlewares),
можно взять crow, которая подключается к вашему проекту одним заголовочным файлом.
По духу это напоминает Python Flask. Пример сервера:
#include "crow_all.h"
int main()
{
crow::SimpleApp app;
CROW_ROUTE(app, "/")([](){
return "Hello world";
});
app.port(18080).multithreaded().run();
}
Если все-таки интересно самому разобрать заголовки, то можно например таким кодом:
std::istringstream rawstream(raw);
std::map headers;
std::string header;
while (std::getline(rawstream, header) && header != "\r") {
std::string::size_type index = header.find(':');
if (index != std::string::npos) {
headers.insert(std::make_pair(
boost::algorithm::trim_copy(header.substr(0, index)),
boost::algorithm::trim_copy(header.substr(index + 1))
));
}
}
В raw типа std::string помещаете строку с вашими заголовками (либо даже целиком запрос
клиента), в headers получаете map из имен/значений заголовков. Используете к примеру так:
std::cout << headers["Content-Type"] << std::endl;
Обратите внимание, что по стандарту имя заголовка может иметь любой регистр. Поэтому
CONTENT-TYPE, content-type и Content-Type это все один заголовок, и для удобства можно
перед добавлением в map привести имя заголовка в нижний регистр:
headers.insert(std::make_pair(
boost::algorithm::to_lower_copy(boost::algorithm::trim_copy(header.substr(0,
index))),
boost::algorithm::trim_copy(header.substr(index + 1))
));
Действующий пример на Ideone.com.
P.S. Для тестирования вместо curl можно использовать postman в котором можно задавать
заголовки, тело страниц (в том числе json), сохранять наборы запросов и т.д.
Ответ 2
libcurl предоставляет возможность задавать обработчик для разных данных. Например,
чтобы "поймать" заголовки ответа, можно воспользоваться следующим подходом:
static size_t header_callback(char *buffer, size_t size,
size_t nitems, void *userdata)
{
/*
* Эта функция будет вызываться на каждый возвращаемый заголовок.
*/
return nitems * size;
}
int main()
{
CURL *curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_URL, "http://yahoo.com/");
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback);
curl_easy_perform(curl);
}
return 0;
}
Подробней - см. в документации по libcurl. Кроме того, для C++ есть обёртки, например,
curlcpp.