/* * Copyright (C) 2016-2019 Savoir-faire Linux Inc. * Author: Vsevolod Ivanov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "http.h" namespace http { // connection Connection::Connection(const uint16_t id, asio::ip::tcp::socket socket): id_(id), socket_(std::move(socket)) {} Connection::~Connection(){ close(); } uint16_t Connection::id(){ return id_; } void Connection::start(asio::ip::tcp::resolver::iterator &r_iter){ asio::connect(socket_, r_iter); } bool Connection::is_open(){ return socket_.is_open(); } std::string Connection::read(std::error_code& ec){ std::string response; asio::read(socket_, asio::dynamic_buffer(response), ec); return response; } void Connection::write(std::string request, std::error_code& ec){ asio::write(socket_, asio::dynamic_buffer(request), ec); } void Connection::close(){ socket_.close(); } // client Client::Client(std::string ip, std::uint16_t port): resolver_(ctx_){ addr_ = asio::ip::address::from_string(ip); port_ = port; } asio::io_context& Client::context(){ return ctx_; } asio::ip::tcp::resolver::query Client::query(){ return asio::ip::tcp::resolver::query { addr_.is_v4() ? asio::ip::tcp::v4() : asio::ip::tcp::v6(), addr_.to_string(), std::to_string(port_)}; } std::string Client::create_request(const restinio::http_request_header_t header, const restinio::http_header_fields_t header_fields, const restinio::http_connection_header_t connection, const std::string body){ std::stringstream request; request << restinio::method_to_string(header.method()) << " " << header.request_target() << " " << "HTTP/" << header.http_major() << "." << header.http_minor() << "\r\n"; for (auto header_field: header_fields) request << header_field.name() << ": " << header_field.value() << "\r\n"; std::string conn_str; switch (connection){ case restinio::http_connection_header_t::keep_alive: conn_str = "keep-alive"; break; case restinio::http_connection_header_t::close: conn_str = "close"; break; case restinio::http_connection_header_t::upgrade: throw std::invalid_argument("upgrade"); break; } request << "Connection: " << conn_str << "\r\n"; if (!body.empty()){ request << "Content-Length: " << body.size() << "\r\n\r\n"; request << body; } request << "\r\n"; return request.str(); } void Client::post_request(std::string request, http_parser_settings parser_s, const ResponseCallback respcb){ // invoke the given handler and return immediately asio::post(ctx_, [this, request, parser_s, respcb](){ this->async_request(request, parser_s, respcb); // execute at most one handler, it ensures that same func call // with different callback gets the priority on the io_context ctx_.run_one(); }); } void Client::async_request(std::string request, http_parser_settings parser_s, const ResponseCallback respcb){ using namespace asio::ip; auto conn = std::make_shared(connId_, std::move(tcp::socket{ctx_})); printf("[connection:%i] created\n", conn->id()); connId_++; // resolve sometime in future resolver_.async_resolve(query(), [=](std::error_code ec, tcp::resolver::results_type res){ if (ec or res.empty()){ printf("[connection:%i] error resolving\n", conn->id()); conn->close(); return; } for (auto da = res.begin(); da != res.end(); ++da){ printf("[connection:%i] resolved host=%s service=%s\n", conn->id(), da->host_name().c_str(), da->service_name().c_str()); conn->start(da); break; } if (!conn->is_open()){ printf("[connection:%i] error closed connection\n", conn->id()); return; } // send request printf("[connection:%i] request write\n", conn->id()); conn->write(request, ec); if (ec and ec != asio::error::eof){ printf("[connection:%i] error: %s\n", conn->id(), ec.message().c_str()); return; } // read response printf("[connection:%i] response read\n", conn->id()); auto data = conn->read(ec); if (ec and ec != asio::error::eof){ printf("[connection:%i] error: %s\n", conn->id(), ec.message().c_str()); return; } if (respcb) respcb(data.c_str()); // parse the request http_parser parser; http_parser_settings_init(const_cast(&parser_s)); http_parser_init(&parser, HTTP_RESPONSE); http_parser_execute(&parser, &parser_s, data.c_str(), data.size()); if (HPE_OK != parser.http_errno && HPE_PAUSED != parser.http_errno){ auto err = HTTP_PARSER_ERRNO(&parser); printf("[connection:%i] error parsing: %s\n", conn->id(), http_errno_name(err)); } }); } }