FETPAPI 0.6.0.0
This project provides C++ classes which facilitate the developement of ETP1.2 clients and servers.
Loading...
Searching...
No Matches
HttpClientSession.h
1/*-----------------------------------------------------------------------
2Licensed to the Apache Software Foundation (ASF) under one
3or more contributor license agreements. See the NOTICE file
4distributed with this work for additional information
5regarding copyright ownership. The ASF licenses this file
6to you under the Apache License, Version 2.0 (the
7"License"; you may not use this file except in compliance
8with the License. You may obtain a copy of the License at
9
10http://www.apache.org/licenses/LICENSE-2.0
11
12Unless required by applicable law or agreed to in writing,
13software distributed under the License is distributed on an
14"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15KIND, either express or implied. See the License for the
16specific language governing permissions and limitations
17under the License.
18-----------------------------------------------------------------------*/
19#pragma once
20
21#include <iostream>
22
23#include <boost/beast/core.hpp>
24#include <boost/beast/http.hpp>
25#include <boost/beast/version.hpp>
26
27#include <boost/version.hpp>
28#if BOOST_VERSION < 107000
29#include <boost/asio/connect.hpp>
30#include <boost/asio/ip/tcp.hpp>
31#else
32#include <boost/asio/strand.hpp>
33#endif
34
35namespace http = boost::beast::http; // from <boost/beast/http.hpp>
36namespace net = boost::asio; // from <boost/asio.hpp>
37using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
38
39namespace ETP_NS
40{
41 // Performs an HTTP GET and stores the response
42 class HttpClientSession : public std::enable_shared_from_this<HttpClientSession>
43 {
44 tcp::resolver resolver_;
45#if BOOST_VERSION < 107000
46 tcp::socket stream_;
47#else
48 beast::tcp_stream stream_;
49#endif
50 boost::beast::flat_buffer buffer_; // (Must persist between reads)
51 http::request<http::empty_body> req_;
52 http::response<http::string_body> res_;
53
54 public:
55 // Resolver and socket require an io_context
56 explicit
57 HttpClientSession(boost::asio::io_context& ioc)
58#if BOOST_VERSION < 107000
59 : resolver_(ioc)
60 , stream_(ioc)
61#else
62 : resolver_(net::make_strand(ioc))
63 , stream_(net::make_strand(ioc))
64#endif
65 {
66 }
67
68 // Start the asynchronous operation
69 void
70 run(
71 const std::string& etpServerHost,
72 uint16_t etpServerPort,
73 const std::string& etpServerTarget,
74 uint32_t version,
75 const std::string& etpServerAuthorization = "",
76 const std::string& proxyHost = "",
77 uint16_t proxyPort = 80,
78 const std::string& proxyAuthorization = "")
79 {
80 // Set up an HTTP GET request message
81 req_.version(version);
82 req_.method(http::verb::get);
83 req_.target(etpServerTarget);
84 req_.set(http::field::host, etpServerHost + ':' + std::to_string(etpServerPort));
85 req_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
86 if (!etpServerAuthorization.empty()) {
87 req_.set(http::field::authorization, etpServerAuthorization);
88 }
89 if (!proxyAuthorization.empty()) {
90 req_.set(http::field::proxy_authorization, proxyAuthorization);
91 }
92
93 // Look up the domain name
94 resolver_.async_resolve(
95 proxyHost.empty() ? etpServerHost : proxyHost,
96 std::to_string(proxyHost.empty() ? etpServerPort : proxyPort),
97 std::bind(
98 &HttpClientSession::on_resolve,
99 shared_from_this(),
100 std::placeholders::_1,
101 std::placeholders::_2));
102 }
103
104 void
105 on_resolve(
106 boost::system::error_code ec,
107 tcp::resolver::results_type results)
108 {
109 if (ec) {
110 std::cerr << "resolve : " << ec.message() << std::endl;
111 return;
112 }
113
114 // Reality check: IPv6 is unlikely to be available yet
115 std::vector<tcp::endpoint> endpoints(results.begin(), results.end());
116 std::stable_partition(endpoints.begin(), endpoints.end(), [](auto entry) {return entry.protocol() == tcp::v4(); });
117
118 // Make the connection on the IP address we get from a lookup
119#if BOOST_VERSION < 107000
120 boost::asio::async_connect(
121 stream_,
122 endpoints.begin(),
123 endpoints.end(),
124 std::bind(
125 &HttpClientSession::on_connect,
126 shared_from_this(),
127 std::placeholders::_1));
128#else
129 stream_.expires_after(std::chrono::seconds(3));
130 stream_.async_connect(
131 endpoints,
132 beast::bind_front_handler(
133 &HttpClientSession::on_connect,
134 shared_from_this()));
135#endif
136 }
137
138#if BOOST_VERSION < 107000
139 void on_connect(boost::system::error_code ec)
140#else
141 void on_connect(boost::beast::error_code ec, tcp::resolver::results_type::endpoint_type)
142#endif
143 {
144 if (ec) {
145 std::cerr << "connect : " << ec.message() << std::endl;
146 return;
147 }
148
149 // Send the HTTP request to the remote host
150 http::async_write(stream_, req_,
151 std::bind(
152 &HttpClientSession::on_write,
153 shared_from_this(),
154 std::placeholders::_1,
155 std::placeholders::_2));
156 }
157
158 void
159 on_write(
160 boost::system::error_code ec,
161 std::size_t bytes_transferred)
162 {
163 boost::ignore_unused(bytes_transferred);
164
165 if (ec) {
166 std::cerr << "write : " << ec.message() << std::endl;
167 return;
168 }
169
170 // Receive the HTTP response
171 http::async_read(stream_, buffer_, res_,
172 std::bind(
173 &HttpClientSession::on_read,
174 shared_from_this(),
175 std::placeholders::_1,
176 std::placeholders::_2));
177 }
178
179 void
180 on_read(
181 boost::system::error_code ec,
182 std::size_t bytes_transferred)
183 {
184 boost::ignore_unused(bytes_transferred);
185
186 if (ec) {
187 std::cerr << "HTTP read : " << ec.message() << std::endl;
188 return;
189 }
190
191 // Gracefully close the socket
192#if BOOST_VERSION < 107000
193 stream_.shutdown(tcp::socket::shutdown_both, ec);
194#else
195 stream_.socket().shutdown(tcp::socket::shutdown_both, ec);
196#endif
197
198 // not_connected happens sometimes so don't bother reporting it.
199 if (ec && ec != boost::system::errc::not_connected) {
200 std::cerr << "shutdown : " << ec.message() << std::endl;
201 return;
202 }
203
204 // If we get here then the connection is closed gracefully
205 }
206
207 const http::response<http::string_body>& getResponse() const {
208 return res_;
209 }
210 };
211}