2#include <ossia/detail/config.hpp>
4#include <ossia/detail/fmt.hpp>
6#include <ossia/detail/parse_relax.hpp>
8#include <boost/asio.hpp>
15using tcp = boost::asio::ip::tcp;
20template <
typename Fun,
typename Err>
21class http_client_request
22 :
public std::enable_shared_from_this<http_client_request<Fun, Err>>
24 fmt::memory_buffer m_request;
27 using std::enable_shared_from_this<http_client_request<Fun, Err>>::shared_from_this;
30 Fun f, Err err, boost::asio::io_context& ctx, std::string_view verb,
31 std::string_view host, std::string_view path,
32 const std::vector<std::pair<std::string, std::string>>& headers = {},
33 std::string_view body = {})
37 , m_err{std::move(err)}
39 m_request.reserve(256 + host.size() + path.size() + body.size());
40 m_response.prepare(Fun::reserve_expect);
43 fmt::format_to(fmt::appender(m_request),
"{} ", verb);
47 fmt::format_to(fmt::appender(m_request),
"{}", c);
49 fmt::format_to(fmt::appender(m_request),
"%20");
51 fmt::format_to(fmt::appender(m_request),
" HTTP/1.1\r\n");
54 fmt::format_to(fmt::appender(m_request),
"Host: {}\r\n", host);
57 bool hasAccept =
false;
58 bool hasConnection =
false;
59 bool hasContentLength =
false;
60 bool hasContentType =
false;
63 for(
const auto& [key, value] : headers)
65 fmt::format_to(fmt::appender(m_request),
"{}: {}\r\n", key, value);
68 else if(key ==
"Connection")
70 else if(key ==
"Content-Length")
71 hasContentLength =
true;
72 else if(key ==
"Content-Type")
73 hasContentType =
true;
78 fmt::format_to(fmt::appender(m_request),
"Accept: */*\r\n");
80 fmt::format_to(fmt::appender(m_request),
"Connection: close\r\n");
86 fmt::appender(m_request),
"Content-Length: {}\r\n", body.size());
89 fmt::appender(m_request),
"Content-Type: application/octet-stream\r\n");
93 fmt::format_to(fmt::appender(m_request),
"\r\n");
95 fmt::format_to(fmt::appender(m_request),
"{}", body);
98 void resolve(
const std::string& server,
const std::string& port)
100 m_resolver.async_resolve(
102 [self = this->shared_from_this()](
103 const boost::system::error_code& err,
104 const tcp::resolver::results_type& endpoints) {
105 self->handle_resolve(err, endpoints);
109 void close() { m_socket.close(); }
113 const boost::system::error_code& err,
114 const tcp::resolver::results_type& endpoints)
118 boost::asio::async_connect(
120 [self = this->shared_from_this()](
121 const boost::system::error_code& err,
auto&&...) {
122 self->handle_connect(err);
128 m_err(*
this, err.message());
132 void handle_connect(
const boost::system::error_code& err)
136 boost::asio::const_buffer request(m_request.data(), m_request.size());
137 boost::asio::async_write(
139 [self = this->shared_from_this()](
140 const boost::system::error_code& err, std::size_t size) {
141 self->handle_write_request(err, size);
147 m_err(*
this, err.message());
151 void handle_write_request(
const boost::system::error_code& err, std::size_t size)
155 boost::asio::async_read_until(
156 m_socket, m_response,
"\r\n",
157 [self = this->shared_from_this()](
158 const boost::system::error_code& err, std::size_t size) {
159 self->handle_read_status_line(err, size);
165 m_err(*
this, err.message());
169 void handle_read_status_line(
const boost::system::error_code& err, std::size_t size)
171 if(!err || err == boost::asio::error::eof)
173 std::istream response_stream(&m_response);
174 std::string http_version;
175 response_stream >> http_version;
176 response_stream >> m_statusCode;
177 std::string status_message;
178 std::getline(response_stream, status_message);
180 if(!response_stream || http_version.substr(0, 5) !=
"HTTP/")
183 m_err(*
this,
"Invalid HTTP response");
188 boost::asio::async_read_until(
189 m_socket, m_response,
"\r\n\r\n",
190 [self = this->shared_from_this()](
191 const boost::system::error_code& err, std::size_t size) {
192 self->handle_read_headers(err, size);
198 m_err(*
this, err.message());
202 void handle_read_headers(
const boost::system::error_code& err, std::size_t size)
204 if(!err || err == boost::asio::error::eof)
206 std::istream response_stream(&m_response);
208 while(std::getline(response_stream, header) && header !=
"\r")
210 if(header.starts_with(
"Content-Length: "))
213 header.begin() + strlen(
"Content-Length: "), header.end());
214 if(
auto num = ossia::parse_relax<int>(sz))
215 m_contentLength = *num;
219 if(m_contentLength == 0)
222 finish_read(boost::asio::error::eof, 0);
224 else if(m_contentLength > 0)
226 if(m_contentLength == (
int)m_response.size())
228 finish_read(boost::asio::error::eof, size);
232 boost::asio::async_read(
233 m_socket, m_response,
234 boost::asio::transfer_exactly(m_contentLength - m_response.size()),
235 [self = this->shared_from_this()](
236 const boost::system::error_code& err, std::size_t size) {
237 self->handle_read_content(err, size);
244 boost::asio::async_read(
245 m_socket, m_response, boost::asio::transfer_all(),
246 [self = this->shared_from_this()](
247 const boost::system::error_code& err, std::size_t size) {
248 self->handle_read_content(err, size);
255 m_err(*
this, err.message());
259 void handle_read_content(
const boost::system::error_code& err, std::size_t size)
261 if(!err || err == boost::asio::error::eof)
262 finish_read(err, size);
266 m_err(*
this, err.message());
270 void finish_read(
const boost::system::error_code& err, std::size_t size)
272 const auto& dat = m_response.data();
273 auto begin = boost::asio::buffers_begin(dat);
274 auto end = boost::asio::buffers_end(dat);
275 auto sz = end - begin;
277 str.reserve(sz + 16);
278 str.assign(begin, end);
279 m_fun(*
this, m_statusCode, str);
283 tcp::resolver m_resolver;
284 tcp::socket m_socket;
285 boost::asio::streambuf m_response;
286 int m_contentLength{-1};
spdlog::logger & logger() noexcept
Where the errors will be logged. Default is stderr.
Definition context.cpp:118