-
Notifications
You must be signed in to change notification settings - Fork 94
/
asio_http_client.cpp
223 lines (179 loc) · 6.78 KB
/
asio_http_client.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
// This code is forked from
// boost_1_67_0/libs/beast/example/http/client/coro/http_client_coro.cpp
// and modified by xhawk18 to use promise-cpp for better async control.
// Copyright (c) 2018, xhawk18
// at gmail.com
// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/beast
//
//------------------------------------------------------------------------------
//
// Example: HTTP client, asynchronous with promise-cpp
//
//------------------------------------------------------------------------------
#include <boost/version.hpp>
#if BOOST_VERSION < 106600
# error "This program need boost 1.66.0 or higher!"
#endif
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <algorithm>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <memory>
#include <string>
#include <regex>
#include "add_ons/asio/io.hpp"
using namespace promise;
namespace asio = boost::asio;
using tcp = boost::asio::ip::tcp;
namespace http = boost::beast::http;
namespace beast = boost::beast;
//------------------------------------------------------------------------------
//From https://stackoverflow.com/a/11044337
struct Uri
{
public:
std::string QueryString, Path, Protocol, Host, Port;
static Uri Parse(const std::string &uri)
{
Uri result;
typedef std::string::const_iterator iterator_t;
if (uri.length() == 0)
return result;
iterator_t uriEnd = uri.end();
// get query start
iterator_t queryStart = std::find(uri.begin(), uriEnd, '?');
// protocol
iterator_t protocolStart = uri.begin();
iterator_t protocolEnd = std::find(protocolStart, uriEnd, ':'); //"://");
if (protocolEnd != uriEnd)
{
std::string prot = &*(protocolEnd);
if ((prot.length() > 3) && (prot.substr(0, 3) == "://"))
{
result.Protocol = std::string(protocolStart, protocolEnd);
protocolEnd += 3; // ://
}
else
protocolEnd = uri.begin(); // no protocol
}
else
protocolEnd = uri.begin(); // no protocol
// host
iterator_t hostStart = protocolEnd;
iterator_t pathStart = std::find(hostStart, uriEnd, '/'); // get pathStart
iterator_t hostEnd = std::find(protocolEnd,
(pathStart != uriEnd) ? pathStart : queryStart,
L':'); // check for port
result.Host = std::string(hostStart, hostEnd);
// port
if ((hostEnd != uriEnd) && ((&*(hostEnd))[0] == ':')) // we have a port
{
hostEnd++;
iterator_t portEnd = (pathStart != uriEnd) ? pathStart : queryStart;
result.Port = std::string(hostEnd, portEnd);
}
// path
if (pathStart != uriEnd)
result.Path = std::string(pathStart, queryStart);
// query
if (queryStart != uriEnd)
result.QueryString = std::string(queryStart, uri.end());
return result;
} // Parse
}; // uri
// Performs an HTTP GET and prints the response
Promise do_session(
std::string const& host,
std::string const& port,
std::string const& target,
int version,
asio::io_context& ioc) {
struct Session {
tcp::resolver resolver_;
tcp::socket socket_;
beast::flat_buffer buffer_;
http::request<http::empty_body> req_;
http::response<http::string_body> res_;
explicit Session(asio::io_context& ioc)
: resolver_(ioc)
, socket_(ioc) {
}
};
// (Must persist in io_context ioc)
auto session = std::make_shared<Session>(ioc);
// Set up an HTTP GET request message
session->req_.version(version);
session->req_.method(http::verb::get);
session->req_.target(target);
session->req_.set(http::field::host, host);
session->req_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
//<1> Resolve the host
return async_resolve(session->resolver_, host, port)
.then([=](tcp::resolver::results_type &results) {
//<2> Connect to the host
return async_connect(session->socket_, results);
}).then([=]() {
//<3> Write the request
return async_write(session->socket_, session->req_);
}).then([=](size_t bytes_transferred) {
boost::ignore_unused(bytes_transferred);
//<4> Read the response
return async_read(session->socket_, session->buffer_, session->res_);
}).then([=](size_t bytes_transferred) {
boost::ignore_unused(bytes_transferred);
//<5> Write the message to standard out
std::cout << session->res_ << std::endl;
}).then([]() {
//<6> success, return default error_code
return boost::system::error_code();
}, [](const boost::system::error_code err) {
//<6> failed, return the error_code
return err;
}).then([=](boost::system::error_code &err) {
//<7> Gracefully close the socket
std::cout << "shutdown..." << std::endl;
session->socket_.shutdown(tcp::socket::shutdown_both, err);
});
}
Promise download(asio::io_context &ioc, const std::string &url) {
const Uri &uri = Uri::Parse(url);
if (uri.Protocol != "http") {
std::cerr << "Support http protocol only!\n";
return promise::reject();
}
std::string port = (uri.Port.empty() ? "80" : uri.Port);
std::string target = uri.Path + uri.QueryString;
std::cout << target << "\n";
std::cout << port << "\n";
std::cout << uri.Host << "\n";
int http_version = 10;
return do_session(uri.Host, port, target, http_version, ioc);
}
//------------------------------------------------------------------------------
int main(int argc, char** argv) {
// The io_context is required for all I/O
asio::io_context ioc;
// Launch the asynchronous operation
download(ioc, "http://www.163.com/")
.then([&]() {
return download(ioc, "http://baidu.com/");
}).then([&]() {
return download(ioc, "http://qq.com");
}).then([&]() {
return download(ioc, "http://github.com/xhawk18");
});
// Run the I/O service. The call will return when
// the get operation is complete.
ioc.run();
return EXIT_SUCCESS;
}