Skip to content

Commit 0790995

Browse files
author
Ognjen Sobajic
committed
SSL for Linux. Mostly http_linux.cpp changes. One test which fetches a json list from youtube.
1 parent f364f32 commit 0790995

File tree

3 files changed

+149
-31
lines changed

3 files changed

+149
-31
lines changed

Release/include/cpprest/http_client_impl.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ using namespace concurrency;
4444
#else
4545
#include <boost/asio.hpp>
4646
#include <boost/bind.hpp>
47+
#include <boost/asio/ssl.hpp>
4748
#include <boost/algorithm/string.hpp>
4849
#include <pplx/threadpool.h>
4950
#endif

Release/src/http/client/http_linux.cpp

Lines changed: 116 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,14 @@ namespace web { namespace http
5757
}
5858

5959
std::unique_ptr<tcp::socket> m_socket;
60+
std::unique_ptr<boost::asio::ssl::stream<tcp::socket>> p_ssl_stream;
61+
6062
uri m_what;
6163
size_t m_known_size;
6264
size_t m_current_size;
6365
bool m_needChunked;
6466
bool m_timedout;
67+
bool m_ssl;
6568
boost::asio::streambuf m_request_buf;
6669
boost::asio::streambuf m_response_buf;
6770
std::unique_ptr<boost::asio::deadline_timer> m_timer;
@@ -74,18 +77,23 @@ namespace web { namespace http
7477
m_timer.reset();
7578
}
7679

77-
if (m_socket)
80+
if (!m_ssl && m_socket)
7881
{
7982
boost::system::error_code ignore;
8083
m_socket->shutdown(tcp::socket::shutdown_both, ignore);
8184
m_socket->close();
8285
m_socket.reset();
8386
}
87+
88+
if (m_ssl && p_ssl_stream)
89+
{
90+
p_ssl_stream->shutdown();
91+
}
8492
}
8593

8694
void cancel(const boost::system::error_code& ec)
8795
{
88-
if (!ec)
96+
if (!ec)
8997
{
9098
m_timedout = true;
9199
auto sock = m_socket.get();
@@ -113,7 +121,6 @@ namespace web { namespace http
113121
}
114122
};
115123

116-
117124
struct client
118125
{
119126
client(boost::asio::io_service& io_service, size_t chunk_size)
@@ -124,9 +131,19 @@ namespace web { namespace http
124131
void send_request(linux_request_context* ctx, int timeout)
125132
{
126133
auto what = ctx->m_what;
127-
ctx->m_socket.reset(new tcp::socket(m_io_service));
128-
129134
auto resource = what.resource().to_string();
135+
136+
ctx->m_ssl = what.scheme() == "https";
137+
138+
if (ctx->m_ssl)
139+
{
140+
boost::asio::ssl::context context(boost::asio::ssl::context::sslv23);
141+
context.set_verify_mode(boost::asio::ssl::context::verify_none);
142+
ctx->p_ssl_stream.reset(new boost::asio::ssl::stream<boost::asio::ip::tcp::socket>(m_io_service, context));
143+
}
144+
else
145+
ctx->m_socket.reset(new tcp::socket(m_io_service));
146+
130147
if (resource == "") resource = "/";
131148

132149
auto method = ctx->m_request.method();
@@ -139,15 +156,12 @@ namespace web { namespace http
139156
auto host = what.host();
140157
std::ostream request_stream(&ctx->m_request_buf);
141158

142-
request_stream << method << " "
143-
<< resource << " "
144-
<< "HTTP/1.1" << CRLF
145-
<< "Host: " << host;
146-
147-
if (what.port() != 0 && what.port() != 80)
148-
request_stream << ":" << what.port();
159+
request_stream << method << " " << resource << " " << "HTTP/1.1" << CRLF << "Host: " << host;
149160

150-
request_stream << CRLF;
161+
int port = what.port();
162+
if (port == 0)
163+
port = (ctx->m_ssl ? 443 : 80);
164+
request_stream << ":" << port << CRLF;
151165

152166
// Check user specified transfer-encoding
153167
std::string transferencoding;
@@ -169,7 +183,8 @@ namespace web { namespace http
169183
else
170184
{
171185
has_body = false;
172-
ctx->m_request.headers()[header_names::content_length] = U("0");
186+
if (!ctx->m_ssl)
187+
ctx->m_request.headers()[header_names::content_length] = U("0");
173188
}
174189
}
175190

@@ -180,16 +195,24 @@ namespace web { namespace http
180195

181196
request_stream << flatten_http_headers(ctx->m_request.headers());
182197

183-
request_stream << "Connection: close" << CRLF; // so we can just read to EOF
198+
if (!ctx->m_ssl)
199+
request_stream << "Connection: close" << CRLF; // so we can just read to EOF
200+
184201
request_stream << CRLF;
185202

186-
tcp::resolver::query query(host, utility::conversions::print_string(what.port() == 0 ? 80 : what.port()));
203+
tcp::resolver::query query(host, utility::conversions::print_string(port));
187204

188205
ctx->m_timer.reset(new boost::asio::deadline_timer(m_io_service));
189206
ctx->m_timer->expires_from_now(boost::posix_time::milliseconds(timeout));
190207
ctx->m_timer->async_wait(boost::bind(&linux_request_context::cancel, ctx, boost::asio::placeholders::error));
191208

192-
m_resolver.async_resolve(query, boost::bind(&client::handle_resolve, this, boost::asio::placeholders::error, boost::asio::placeholders::iterator, ctx));
209+
if (ctx->m_ssl)
210+
{
211+
boost::asio::ip::tcp::resolver::iterator iter = m_resolver.resolve(query);
212+
boost::asio::async_connect(ctx->p_ssl_stream->lowest_layer(), iter, boost::bind(&client::handle_connect, this, boost::asio::placeholders::error, boost::asio::placeholders::iterator, ctx));
213+
}
214+
else
215+
m_resolver.async_resolve(query, boost::bind(&client::handle_resolve, this, boost::asio::placeholders::error, boost::asio::placeholders::iterator, ctx));
193216
}
194217

195218
private:
@@ -223,28 +246,55 @@ namespace web { namespace http
223246
else
224247
{
225248
auto endpoint = *endpoints;
226-
ctx->m_socket->async_connect(endpoint, boost::bind(&client::handle_connect, this, boost::asio::placeholders::error, ++endpoints, ctx));
249+
if (ctx->m_ssl)
250+
{
251+
boost::asio::ip::tcp::resolver::iterator endpoint_iterator;
252+
boost::asio::async_connect((*(ctx->p_ssl_stream)).lowest_layer(), endpoint_iterator, boost::bind(&client::handle_connect, this, boost::asio::placeholders::error, ++endpoints, ctx));
253+
}
254+
else
255+
ctx->m_socket->async_connect(endpoint, boost::bind(&client::handle_connect, this, boost::asio::placeholders::error, ++endpoints, ctx));
227256
}
228257
}
229258

230259
void handle_connect(const boost::system::error_code& ec, tcp::resolver::iterator endpoints, linux_request_context* ctx)
231260
{
232261
if (!ec)
233262
{
234-
boost::asio::async_write(*ctx->m_socket, ctx->m_request_buf, boost::bind(&client::handle_write_request, this, boost::asio::placeholders::error, ctx));
263+
if (ctx->m_ssl)
264+
ctx->p_ssl_stream->async_handshake(boost::asio::ssl::stream_base::client, boost::bind(&client::handle_handshake, this, boost::asio::placeholders::error, ctx));
265+
else
266+
boost::asio::async_write(*ctx->m_socket, ctx->m_request_buf, boost::bind(&client::handle_write_request, this, boost::asio::placeholders::error, ctx));
235267
}
236268
else if (endpoints == tcp::resolver::iterator())
237269
{
238270
ctx->report_error("Failed to connect to any resolved endpoint", ec);
239271
}
240272
else
241273
{
274+
if (ctx->m_ssl)
275+
{
276+
ctx->report_error("SSL Failed to connect to any resolved endpoint", ec);
277+
return;
278+
}
279+
242280
boost::system::error_code ignore;
243281
ctx->m_socket->shutdown(tcp::socket::shutdown_both, ignore);
244282
ctx->m_socket->close();
245283
ctx->m_socket.reset(new tcp::socket(m_io_service));
246284
auto endpoint = *endpoints;
247285
ctx->m_socket->async_connect(endpoint, boost::bind(&client::handle_connect, this, boost::asio::placeholders::error, ++endpoints, ctx));
286+
}
287+
}
288+
289+
void handle_handshake(const boost::system::error_code& ec, linux_request_context* ctx)
290+
{
291+
if (!ec)
292+
{
293+
boost::asio::async_write(*ctx->p_ssl_stream, ctx->m_request_buf, boost::bind(&client::handle_write_request, this, boost::asio::placeholders::error, ctx));
294+
}
295+
else
296+
{
297+
std::cout << "Error code in handle_handshake is " << ec << std::endl;
248298
}
249299
}
250300

@@ -275,8 +325,12 @@ namespace web { namespace http
275325
ctx->m_request_buf.consume(offset);
276326
ctx->m_current_size += readSize;
277327
ctx->m_uploaded += (size64_t)readSize;
278-
boost::asio::async_write(*ctx->m_socket, ctx->m_request_buf,
279-
boost::bind(readSize != 0 ? &client::handle_write_chunked_body : &client::handle_write_body, this, boost::asio::placeholders::error, ctx));
328+
if (ctx->m_ssl)
329+
boost::asio::async_write(*ctx->p_ssl_stream, ctx->m_request_buf,
330+
boost::bind(readSize != 0 ? &client::handle_write_chunked_body : &client::handle_write_body, this, boost::asio::placeholders::error, ctx));
331+
else
332+
boost::asio::async_write(*ctx->m_socket, ctx->m_request_buf,
333+
boost::bind(readSize != 0 ? &client::handle_write_chunked_body : &client::handle_write_body, this, boost::asio::placeholders::error, ctx));
280334
});
281335
}
282336

@@ -308,6 +362,10 @@ namespace web { namespace http
308362
ctx->m_uploaded += (size64_t)actualSize;
309363
ctx->m_current_size += actualSize;
310364
ctx->m_request_buf.commit(actualSize);
365+
if (ctx->m_ssl)
366+
boost::asio::async_write(*ctx->p_ssl_stream, ctx->m_request_buf,
367+
boost::bind(&client::handle_write_large_body, this, boost::asio::placeholders::error, ctx));
368+
else
311369
boost::asio::async_write(*ctx->m_socket, ctx->m_request_buf,
312370
boost::bind(&client::handle_write_large_body, this, boost::asio::placeholders::error, ctx));
313371
});
@@ -339,8 +397,12 @@ namespace web { namespace http
339397
{
340398
(*progress)(message_direction::upload, ctx->m_uploaded);
341399
}
342-
343-
// Read until the end of entire headers
400+
401+
// Read until the end of entire headers
402+
if (ctx->m_ssl)
403+
boost::asio::async_read_until(*ctx->p_ssl_stream, ctx->m_response_buf, CRLF+CRLF,
404+
boost::bind(&client::handle_status_line, this, boost::asio::placeholders::error, ctx));
405+
else
344406
boost::asio::async_read_until(*ctx->m_socket, ctx->m_response_buf, CRLF+CRLF,
345407
boost::bind(&client::handle_status_line, this, boost::asio::placeholders::error, ctx));
346408
}
@@ -410,6 +472,7 @@ namespace web { namespace http
410472

411473
ctx->m_known_size = 0;
412474
ctx->m_response.headers().match(header_names::content_length, ctx->m_known_size);
475+
413476
// note: need to check for 'chunked' here as well, azure storage sends both
414477
// transfer-encoding:chunked and content-length:0 (although HTTP says not to)
415478
if (ctx->m_request.method() == U("HEAD") || (!ctx->m_needChunked && ctx->m_known_size == 0))
@@ -430,18 +493,34 @@ namespace web { namespace http
430493
async_read_until_buffersize(std::min(ctx->m_known_size, m_chunksize),
431494
boost::bind(&client::handle_read_content, this, boost::asio::placeholders::error, ctx), ctx);
432495
else
433-
boost::asio::async_read_until(*ctx->m_socket, ctx->m_response_buf, CRLF,
434-
boost::bind(&client::handle_chunk_header, this, boost::asio::placeholders::error, ctx));
496+
{
497+
if (ctx->m_ssl)
498+
boost::asio::async_read_until(*ctx->p_ssl_stream, ctx->m_response_buf, CRLF,
499+
boost::bind(&client::handle_chunk_header, this, boost::asio::placeholders::error, ctx));
500+
else
501+
boost::asio::async_read_until(*ctx->m_socket, ctx->m_response_buf, CRLF,
502+
boost::bind(&client::handle_chunk_header, this, boost::asio::placeholders::error, ctx));
503+
}
435504
}
436505
}
437506

438507
template <typename ReadHandler>
439508
void async_read_until_buffersize(size_t size, ReadHandler handler, linux_request_context* ctx)
440509
{
441-
if (ctx->m_response_buf.size() >= size)
442-
boost::asio::async_read(*ctx->m_socket, ctx->m_response_buf, boost::asio::transfer_at_least(0), handler);
510+
if (ctx->m_ssl)
511+
{
512+
if (ctx->m_response_buf.size() >= size)
513+
boost::asio::async_read(*ctx->p_ssl_stream, ctx->m_response_buf, boost::asio::transfer_at_least(0), handler);
514+
else
515+
boost::asio::async_read(*ctx->p_ssl_stream, ctx->m_response_buf, boost::asio::transfer_at_least(size - ctx->m_response_buf.size()), handler);
516+
}
443517
else
444-
boost::asio::async_read(*ctx->m_socket, ctx->m_response_buf, boost::asio::transfer_at_least(size - ctx->m_response_buf.size()), handler);
518+
{
519+
if (ctx->m_response_buf.size() >= size)
520+
boost::asio::async_read(*ctx->m_socket, ctx->m_response_buf, boost::asio::transfer_at_least(0), handler);
521+
else
522+
boost::asio::async_read(*ctx->m_socket, ctx->m_response_buf, boost::asio::transfer_at_least(size - ctx->m_response_buf.size()), handler);
523+
}
445524
}
446525

447526
void handle_chunk_header(const boost::system::error_code& ec, linux_request_context* ctx)
@@ -452,7 +531,7 @@ namespace web { namespace http
452531
std::string line;
453532
std::getline(response_stream, line);
454533

455-
std::istringstream octetLine(line);
534+
std::istringstream octetLine(line);
456535
int octets = 0;
457536
octetLine >> std::hex >> octets;
458537

@@ -509,8 +588,13 @@ namespace web { namespace http
509588
return;
510589
}
511590
ctx->m_response_buf.consume(to_read + CRLF.size()); // consume crlf
512-
boost::asio::async_read_until(*ctx->m_socket, ctx->m_response_buf, CRLF,
513-
boost::bind(&client::handle_chunk_header, this, boost::asio::placeholders::error, ctx));
591+
592+
if (ctx->m_ssl)
593+
boost::asio::async_read_until(*ctx->p_ssl_stream, ctx->m_response_buf, CRLF,
594+
boost::bind(&client::handle_chunk_header, this, boost::asio::placeholders::error, ctx));
595+
else
596+
boost::asio::async_read_until(*ctx->m_socket, ctx->m_response_buf, CRLF,
597+
boost::bind(&client::handle_chunk_header, this, boost::asio::placeholders::error, ctx));
514598
});
515599
}
516600
}
@@ -575,6 +659,7 @@ namespace web { namespace http
575659
}
576660
}
577661
};
662+
578663
class linux_client : public _http_client_communicator
579664
{
580665
private:

Release/tests/Functional/http/client/outside_tests.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,38 @@ TEST_FIXTURE(uri_address, reading_google_stream,
9797
VERIFY_ARE_EQUAL(strcmp((const char *)chars, "<!doctype html><html itemscope=\"itemscope\" itemtype=\"http://schema.org/WebPage\">"), 0);
9898
}
9999

100+
TEST_FIXTURE(uri_address, outside_ssl_json,
101+
"Ignore", "Manual")
102+
{
103+
using namespace utility;
104+
using namespace web;
105+
using namespace web::http;
106+
using namespace web::http::client;
107+
using namespace concurrency::streams;
108+
109+
// Create an HTTP request.
110+
uri_builder playlistUri("https://www.googleapis.com/youtube/v3/playlistItems?");
111+
playlistUri.append_query("part","snippet");
112+
playlistUri.append_query("playlistId", "UUF1hMUVwlrvlVMjUGOZExgg");
113+
playlistUri.append_query("key","AIzaSyAviHxf_y0SzNoAq3iKqvWVE4KQ0yylsnk");
114+
web::http::client::http_client playlistClient(playlistUri.to_uri());
115+
116+
playlistClient.request(methods::GET).then([=](http_response playlistResponse) -> pplx::task<json::value>
117+
{
118+
return playlistResponse.extract_json();
119+
}).then([=](json::value jsonArray)
120+
{
121+
int count = 0;
122+
for(const auto& i : jsonArray[U("items")])
123+
{
124+
auto name = i.second[U("snippet")][U("title")].to_string();
125+
std::cout << name.c_str() << std::endl;
126+
count++;
127+
}
128+
VERIFY_ARE_EQUAL(3, count);
129+
}).wait();
130+
}
131+
100132
} // SUITE(outside_tests)
101133

102134
}}}}

0 commit comments

Comments
 (0)