Merge lp:~thomas-voss/net-cpp/add-streaming into lp:net-cpp

Proposed by Thomas Voß
Status: Merged
Approved by: Marcus Tomlinson
Approved revision: 51
Merged at revision: 48
Proposed branch: lp:~thomas-voss/net-cpp/add-streaming
Merge into: lp:net-cpp
Diff against target: 1051 lines (+802/-28)
11 files modified
CMakeLists.txt (+3/-1)
debian/changelog (+6/-0)
debian/libnet-cpp1.symbols (+4/-0)
include/core/net/http/streaming_client.h (+88/-0)
include/core/net/http/streaming_request.h (+60/-0)
src/CMakeLists.txt (+2/-0)
src/core/net/http/impl/curl/client.cpp (+74/-8)
src/core/net/http/impl/curl/client.h (+24/-16)
src/core/net/http/impl/curl/request.h (+17/-3)
tests/CMakeLists.txt (+16/-0)
tests/http_streaming_client_test.cpp (+508/-0)
To merge this branch: bzr merge lp:~thomas-voss/net-cpp/add-streaming
Reviewer Review Type Date Requested Status
Marcus Tomlinson (community) Approve
PS Jenkins bot continuous-integration Approve
Ubuntu Phablet Team Pending
Review via email: mp+253850@code.launchpad.net

Commit message

Add streaming support to net-cpp in an ABI-stable way.

Description of the change

Add streaming support to net-cpp in an ABI-stable way.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Marcus Tomlinson (marcustomlinson) wrote :

361 +class Client : public core::net::http::StreamingClient

Just noticing a few overriding methods in this class that don't specify "override" in their declarations. i.e. url_escape, timings, run, stop, get, head, post, and pull.

review: Needs Fixing
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Marcus Tomlinson (marcustomlinson) wrote :

Client::url_escape should also be marked override, but no biggy. Looks good.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2014-07-30 15:16:19 +0000
3+++ CMakeLists.txt 2015-04-01 06:23:51 +0000
4@@ -27,7 +27,7 @@
5 set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined")
6
7 set(NET_CPP_VERSION_MAJOR 1)
8-set(NET_CPP_VERSION_MINOR 1)
9+set(NET_CPP_VERSION_MINOR 2)
10 set(NET_CPP_VERSION_PATCH 0)
11
12 include(CTest)
13@@ -36,6 +36,8 @@
14 include/
15 )
16
17+file(GLOB_RECURSE NET_CPP_INTERFACE_HEADERS include/*.h)
18+
19 add_subdirectory(doc)
20 add_subdirectory(data)
21 add_subdirectory(include)
22
23=== modified file 'debian/changelog'
24--- debian/changelog 2015-03-05 12:08:09 +0000
25+++ debian/changelog 2015-04-01 06:23:51 +0000
26@@ -1,3 +1,9 @@
27+net-cpp (1.2.0) UNRELEASED; urgency=medium
28+
29+ * Introduce a streaming http interface.
30+
31+ -- Thomas Voß <thomas.voss@canonical.com> Wed, 01 Apr 2015 08:22:32 +0200
32+
33 net-cpp (1.1.0+15.04.20150305-0ubuntu1) vivid; urgency=medium
34
35 [ thomas-voss ]
36
37=== modified file 'debian/libnet-cpp1.symbols'
38--- debian/libnet-cpp1.symbols 2014-12-04 20:54:26 +0000
39+++ debian/libnet-cpp1.symbols 2015-04-01 06:23:51 +0000
40@@ -1,5 +1,6 @@
41 libnet-cpp.so.1 libnet-cpp1 #MINVER#
42 (c++)"core::net::http::make_client()@Base" 0.0.1+14.10.20140611
43+ (c++)"core::net::http::make_streaming_client()@Base" 1.1.0+15.04.20150305-0ubuntu1
44 (c++)"core::net::make_uri(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::vector<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::basic_string<char, std::char_traits<char>, std::allocator<char> > > > const&, std::vector<std::pair<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::basic_string<char, std::char_traits<char>, std::allocator<char> > > > > const&)@Base" 1.1.0+14.10.20140804
45 (c++)"core::net::http::Client::uri_to_string(core::net::Uri const&) const@Base" 1.1.0+14.10.20140804
46 (c++)"core::net::http::Client::Errors::HttpMethodNotSupported::HttpMethodNotSupported(core::net::http::Method, core::Location const&)@Base" 0.0.1+14.10.20140611
47@@ -41,3 +42,6 @@
48 (c++)"vtable for core::net::http::Header@Base" 0.0.1+14.10.20140611
49 (c++)"vtable for core::net::http::Request::Errors::AlreadyActive@Base" 0.0.1+14.10.20140611
50 (c++)"vtable for core::net::http::Request@Base" 0.0.1+14.10.20140611
51+ (c++)"typeinfo for core::net::http::StreamingRequest@Base" 1.1.0+15.04.20150305-0ubuntu1
52+ (c++)"typeinfo name for core::net::http::StreamingRequest@Base" 1.1.0+15.04.20150305-0ubuntu1
53+ (c++)"vtable for core::net::http::StreamingRequest@Base" 1.1.0+15.04.20150305-0ubuntu1
54\ No newline at end of file
55
56=== added file 'include/core/net/http/streaming_client.h'
57--- include/core/net/http/streaming_client.h 1970-01-01 00:00:00 +0000
58+++ include/core/net/http/streaming_client.h 2015-04-01 06:23:51 +0000
59@@ -0,0 +1,88 @@
60+/*
61+ * Copyright © 2013 Canonical Ltd.
62+ *
63+ * This program is free software: you can redistribute it and/or modify it
64+ * under the terms of the GNU Lesser General Public License version 3,
65+ * as published by the Free Software Foundation.
66+ *
67+ * This program is distributed in the hope that it will be useful,
68+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
69+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
70+ * GNU Lesser General Public License for more details.
71+ *
72+ * You should have received a copy of the GNU Lesser General Public License
73+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
74+ *
75+ * Authored by: Thomas Voß <thomas.voss@canonical.com>
76+ */
77+#ifndef CORE_NET_HTTP_STREAMING_CLIENT_H_
78+#define CORE_NET_HTTP_STREAMING_CLIENT_H_
79+
80+#include <core/net/http/client.h>
81+
82+#include <core/net/http/streaming_request.h>
83+
84+namespace core
85+{
86+namespace net
87+{
88+namespace http
89+{
90+class StreamingClient : public Client
91+{
92+public:
93+
94+ virtual ~StreamingClient() = default;
95+
96+ /**
97+ * @brief streaming_get is a convenience method for issueing a GET request for the given URI.
98+ * @throw Errors::HttpMethodNotSupported if the underlying implementation does not support the provided HTTP method.
99+ * @param configuration The configuration to issue a get request for.
100+ * @return An executable instance of class Request.
101+ */
102+ virtual std::shared_ptr<StreamingRequest> streaming_get(const Request::Configuration& configuration) = 0;
103+
104+ /**
105+ * @brief streaming_head is a convenience method for issueing a HEAD request for the given URI.
106+ * @throw Errors::HttpMethodNotSupported if the underlying implementation does not support the provided HTTP method.
107+ * @param configuration The configuration to issue a get request for.
108+ * @return An executable instance of class Request.
109+ */
110+ virtual std::shared_ptr<StreamingRequest> streaming_head(const Request::Configuration& configuration) = 0;
111+
112+ /**
113+ * @brief streaming_put is a convenience method for issuing a PUT request for the given URI.
114+ * @throw Errors::HttpMethodNotSupported if the underlying implementation does not support the provided HTTP method.
115+ * @param configuration The configuration to issue a get request for.
116+ * @param payload The data to be transmitted as part of the PUT request.
117+ * @param size Size of the payload data in bytes.
118+ * @return An executable instance of class Request.
119+ */
120+ virtual std::shared_ptr<StreamingRequest> streaming_put(const Request::Configuration& configuration, std::istream& payload, std::size_t size) = 0;
121+
122+ /**
123+ * @brief streaming_post is a convenience method for issuing a POST request for the given URI.
124+ * @throw Errors::HttpMethodNotSupported if the underlying implementation does not support the provided HTTP method.
125+ * @param configuration The configuration to issue a get request for.
126+ * @param payload The data to be transmitted as part of the POST request.
127+ * @param type The content-type of the data.
128+ * @return An executable instance of class Request.
129+ */
130+ virtual std::shared_ptr<StreamingRequest> streaming_post(const Request::Configuration& configuration, const std::string& payload, const std::string& type) = 0;
131+
132+ /**
133+ * @brief streaming_post_form is a convenience method for issuing a POST request for the given URI, with url-encoded payload.
134+ * @throw Errors::HttpMethodNotSupported if the underlying implementation does not support the provided HTTP method.
135+ * @param configuration The configuration to issue a get request for.
136+ * @param values Key-value pairs to be added to the payload in url-encoded format.
137+ * @return An executable instance of class Request.
138+ */
139+ virtual std::shared_ptr<StreamingRequest> streaming_post_form(const Request::Configuration& configuration, const std::map<std::string, std::string>& values) = 0;
140+};
141+
142+/** @brief Dispatches to the default implementation and returns a streaming client instance. */
143+CORE_NET_DLL_PUBLIC std::shared_ptr<StreamingClient> make_streaming_client();
144+}
145+}
146+}
147+#endif // CORE_NET_HTTP_STREAMING_CLIENT_H_
148
149=== added file 'include/core/net/http/streaming_request.h'
150--- include/core/net/http/streaming_request.h 1970-01-01 00:00:00 +0000
151+++ include/core/net/http/streaming_request.h 2015-04-01 06:23:51 +0000
152@@ -0,0 +1,60 @@
153+/*
154+ * Copyright © 2013 Canonical Ltd.
155+ *
156+ * This program is free software: you can redistribute it and/or modify it
157+ * under the terms of the GNU Lesser General Public License version 3,
158+ * as published by the Free Software Foundation.
159+ *
160+ * This program is distributed in the hope that it will be useful,
161+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
162+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
163+ * GNU Lesser General Public License for more details.
164+ *
165+ * You should have received a copy of the GNU Lesser General Public License
166+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
167+ *
168+ * Authored by: Thomas Voß <thomas.voss@canonical.com>
169+ */
170+#ifndef CORE_NET_HTTP_STREAMING_REQUEST_H_
171+#define CORE_NET_HTTP_STREAMING_REQUEST_H_
172+
173+#include <core/net/http/request.h>
174+
175+namespace core
176+{
177+namespace net
178+{
179+namespace http
180+{
181+/**
182+ * @brief The StreamingRequest class encapsulates a request for a web resource,
183+ * streaming data to the receiver as it receives in addition to accumulating all incoming data.
184+ */
185+class CORE_NET_DLL_PUBLIC StreamingRequest : public Request
186+{
187+public:
188+
189+ /** DataHandler is invoked when a new chunk of data arrives from the server. */
190+ typedef std::function<void(const std::string&)> DataHandler;
191+
192+ /**
193+ * @brief Synchronously executes the request.
194+ * @throw core::net::http::Error in case of http-related errors.
195+ * @throw core::net::Error in case of network-related errors.
196+ * @return The response to the request.
197+ */
198+ virtual Response execute(const ProgressHandler& ph, const DataHandler& dh) = 0;
199+
200+ /**
201+ * @brief Asynchronously executes the request, reporting errors, progress and completion to the given handlers.
202+ * @param handler The handlers to called for events happening during execution of the request.
203+ * @param dh The data handler receiving chunks of data while executing the request.
204+ * @return The response to the request.
205+ */
206+ virtual void async_execute(const Handler& handler, const DataHandler& dh) = 0;
207+};
208+}
209+}
210+}
211+
212+#endif // CORE_NET_HTTP_STREAMING_REQUEST_H_
213
214=== modified file 'src/CMakeLists.txt'
215--- src/CMakeLists.txt 2014-06-10 14:19:14 +0000
216+++ src/CMakeLists.txt 2015-04-01 06:23:51 +0000
217@@ -22,6 +22,8 @@
218 add_library(
219 net-cpp SHARED
220
221+ ${NET_CPP_INTERFACE_HEADERS}
222+
223 core/location.cpp
224
225 core/net/error.cpp
226
227=== modified file 'src/core/net/http/impl/curl/client.cpp'
228--- src/core/net/http/impl/curl/client.cpp 2014-06-10 14:19:14 +0000
229+++ src/core/net/http/impl/curl/client.cpp 2015-04-01 06:23:51 +0000
230@@ -20,6 +20,7 @@
231 #include "curl.h"
232 #include "request.h"
233
234+#include <core/net/http/content_type.h>
235 #include <core/net/http/method.h>
236
237 #include <boost/archive/iterators/base64_from_binary.hpp>
238@@ -107,7 +108,7 @@
239 multi.stop();
240 }
241
242-std::shared_ptr<http::Request> http::impl::curl::Client::head(const http::Request::Configuration& configuration)
243+std::shared_ptr<http::impl::curl::Request> http::impl::curl::Client::head_impl(const http::Request::Configuration& configuration)
244 {
245 ::curl::easy::Handle handle;
246 handle.method(http::Method::head)
247@@ -126,10 +127,10 @@
248 handle.http_credentials(credentials.username, credentials.password);
249 }
250
251- return std::shared_ptr<http::Request>{new http::impl::curl::Request{multi, handle}};
252+ return std::shared_ptr<http::impl::curl::Request>{new http::impl::curl::Request{multi, handle}};
253 }
254
255-std::shared_ptr<http::Request> http::impl::curl::Client::get(const http::Request::Configuration& configuration)
256+std::shared_ptr<http::impl::curl::Request> http::impl::curl::Client::get_impl(const http::Request::Configuration& configuration)
257 {
258 ::curl::easy::Handle handle;
259 handle.method(http::Method::get)
260@@ -147,10 +148,10 @@
261 handle.http_credentials(credentials.username, credentials.password);
262 }
263
264- return std::shared_ptr<http::Request>{new http::impl::curl::Request{multi, handle}};
265+ return std::shared_ptr<http::impl::curl::Request>{new http::impl::curl::Request{multi, handle}};
266 }
267
268-std::shared_ptr<http::Request> http::impl::curl::Client::post(
269+std::shared_ptr<http::impl::curl::Request> http::impl::curl::Client::post_impl(
270 const Request::Configuration& configuration,
271 const std::string& payload,
272 const std::string& ct)
273@@ -172,10 +173,10 @@
274 handle.http_credentials(credentials.username, credentials.password);
275 }
276
277- return std::shared_ptr<http::Request>{new http::impl::curl::Request{multi, handle}};
278+ return std::shared_ptr<http::impl::curl::Request>{new http::impl::curl::Request{multi, handle}};
279 }
280
281-std::shared_ptr<http::Request> http::impl::curl::Client::put(
282+std::shared_ptr<http::impl::curl::Request> http::impl::curl::Client::put_impl(
283 const Request::Configuration& configuration,
284 std::istream& payload,
285 std::size_t size)
286@@ -201,10 +202,75 @@
287 handle.http_credentials(credentials.username, credentials.password);
288 }
289
290- return std::shared_ptr<http::Request>{new http::impl::curl::Request{multi, handle}};
291+ return std::shared_ptr<http::impl::curl::Request>{new http::impl::curl::Request{multi, handle}};
292+}
293+
294+std::shared_ptr<http::StreamingRequest> http::impl::curl::Client::streaming_get(const http::Request::Configuration& configuration)
295+{
296+ return get_impl(configuration);
297+}
298+
299+std::shared_ptr<http::StreamingRequest> http::impl::curl::Client::streaming_head(const http::Request::Configuration& configuration)
300+{
301+ return head_impl(configuration);
302+}
303+
304+std::shared_ptr<http::StreamingRequest> http::impl::curl::Client::streaming_put(const http::Request::Configuration& configuration, std::istream& payload, std::size_t size)
305+{
306+ return put_impl(configuration, payload, size);
307+}
308+
309+std::shared_ptr<http::StreamingRequest> http::impl::curl::Client::streaming_post(const http::Request::Configuration& configuration, const std::string& payload, const std::string& type)
310+{
311+ return post_impl(configuration, payload, type);
312+}
313+
314+std::shared_ptr<http::StreamingRequest> http::impl::curl::Client::streaming_post_form(const http::Request::Configuration& configuration, const std::map<std::string, std::string>& values)
315+{
316+ std::stringstream ss;
317+ bool first{true};
318+
319+ for (const auto& pair : values)
320+ {
321+ ss << (first ? "" : "&") << url_escape(pair.first) << "=" << url_escape(pair.second);
322+ first = false;
323+ }
324+
325+ return post_impl(configuration, ss.str(), http::ContentType::x_www_form_urlencoded);
326+}
327+
328+std::shared_ptr<http::Request> http::impl::curl::Client::head(const http::Request::Configuration& configuration)
329+{
330+ return head_impl(configuration);
331+}
332+
333+std::shared_ptr<http::Request> http::impl::curl::Client::get(const http::Request::Configuration& configuration)
334+{
335+ return get_impl(configuration);
336+}
337+
338+std::shared_ptr<http::Request> http::impl::curl::Client::post(
339+ const Request::Configuration& configuration,
340+ const std::string& payload,
341+ const std::string& ct)
342+{
343+ return post_impl(configuration, payload, ct);
344+}
345+
346+std::shared_ptr<http::Request> http::impl::curl::Client::put(
347+ const Request::Configuration& configuration,
348+ std::istream& payload,
349+ std::size_t size)
350+{
351+ return put_impl(configuration, payload, size);
352 }
353
354 std::shared_ptr<http::Client> http::make_client()
355 {
356 return std::make_shared<http::impl::curl::Client>();
357 }
358+
359+std::shared_ptr<http::StreamingClient> http::make_streaming_client()
360+{
361+ return std::make_shared<http::impl::curl::Client>();
362+}
363
364=== modified file 'src/core/net/http/impl/curl/client.h'
365--- src/core/net/http/impl/curl/client.h 2014-06-10 14:19:14 +0000
366+++ src/core/net/http/impl/curl/client.h 2015-04-01 06:23:51 +0000
367@@ -18,9 +18,10 @@
368 #ifndef CORE_NET_HTTP_IMPL_CURL_CLIENT_H_
369 #define CORE_NET_HTTP_IMPL_CURL_CLIENT_H_
370
371-#include <core/net/http/client.h>
372+#include <core/net/http/streaming_client.h>
373
374 #include "curl.h"
375+#include "request.h"
376
377 namespace core
378 {
379@@ -32,7 +33,7 @@
380 {
381 namespace curl
382 {
383-class Client : public core::net::http::Client
384+class Client : public core::net::http::StreamingClient
385 {
386 public:
387 Client();
388@@ -45,23 +46,30 @@
389
390 std::string base64_decode(const std::string& s) const override;
391
392- core::net::http::Client::Timings timings();
393-
394- void run();
395-
396- void stop();
397-
398- std::shared_ptr<Request> get(const Request::Configuration& configuration);
399-
400- std::shared_ptr<Request> head(const Request::Configuration& configuration);
401-
402- std::shared_ptr<Request> post(const Request::Configuration& configuration, const std::string&, const std::string&);
403-
404- std::shared_ptr<Request> put(const Request::Configuration& configuration, std::istream& payload, std::size_t size);
405+ core::net::http::Client::Timings timings() override;
406+
407+ void run() override;
408+
409+ void stop() override;
410+
411+ std::shared_ptr<http::Request> get(const Request::Configuration& configuration) override;
412+ std::shared_ptr<http::Request> head(const Request::Configuration& configuration) override;
413+ std::shared_ptr<http::Request> post(const Request::Configuration& configuration, const std::string&, const std::string&) override;
414+ std::shared_ptr<http::Request> put(const Request::Configuration& configuration, std::istream& payload, std::size_t size) override;
415+
416+ std::shared_ptr<http::StreamingRequest> streaming_get(const Request::Configuration& configuration) override;
417+ std::shared_ptr<http::StreamingRequest> streaming_head(const Request::Configuration& configuration) override;
418+ std::shared_ptr<http::StreamingRequest> streaming_put(const Request::Configuration& configuration, std::istream& payload, std::size_t size) override;
419+ std::shared_ptr<http::StreamingRequest> streaming_post(const Request::Configuration& configuration, const std::string& payload, const std::string& type) override;
420+ std::shared_ptr<http::StreamingRequest> streaming_post_form(const Request::Configuration& configuration, const std::map<std::string, std::string>& values) override;
421
422 private:
423- ::curl::multi::Handle multi;
424+ std::shared_ptr<curl::Request> get_impl(const Request::Configuration& configuration);
425+ std::shared_ptr<curl::Request> head_impl(const Request::Configuration& configuration);
426+ std::shared_ptr<curl::Request> post_impl(const Request::Configuration& configuration, const std::string&, const std::string&);
427+ std::shared_ptr<curl::Request> put_impl(const Request::Configuration& configuration, std::istream& payload, std::size_t size);
428
429+ ::curl::multi::Handle multi;
430 };
431 }
432 }
433
434=== modified file 'src/core/net/http/impl/curl/request.h'
435--- src/core/net/http/impl/curl/request.h 2015-01-23 06:23:31 +0000
436+++ src/core/net/http/impl/curl/request.h 2015-04-01 06:23:51 +0000
437@@ -18,7 +18,7 @@
438 #ifndef CORE_NET_HTTP_IMPL_CURL_REQUEST_H_
439 #define CORE_NET_HTTP_IMPL_CURL_REQUEST_H_
440
441-#include <core/net/http/request.h>
442+#include <core/net/http/streaming_request.h>
443
444 #include <core/net/http/error.h>
445 #include <core/net/http/response.h>
446@@ -80,7 +80,7 @@
447 std::atomic<core::net::http::Request::State>& state;
448 };
449
450-class Request : public core::net::http::Request,
451+class Request : public core::net::http::StreamingRequest,
452 public std::enable_shared_from_this<Request>
453 {
454 public:
455@@ -118,6 +118,11 @@
456
457 Response execute(const Request::ProgressHandler& ph)
458 {
459+ return execute(ph, [](const std::string&){});
460+ }
461+
462+ Response execute(const Request::ProgressHandler& ph, const StreamingRequest::DataHandler& dh)
463+ {
464 if (atomic_state.load() != core::net::http::Request::State::ready)
465 throw core::net::http::Request::Errors::AlreadyActive{CORE_FROM_HERE()};
466
467@@ -149,6 +154,8 @@
468 easy.on_write_data(
469 [&](char* data, std::size_t size, std::size_t nmemb)
470 {
471+ // Report out to the data handler prior to accumulating data.
472+ dh(std::string{data, size * nmemb});
473 context.body.write(data, size * nmemb);
474 return size * nmemb;
475 });
476@@ -183,6 +190,11 @@
477
478 void async_execute(const Request::Handler& handler)
479 {
480+ async_execute(handler, [](const std::string&){});
481+ }
482+
483+ void async_execute(const Request::Handler& handler, const StreamingRequest::DataHandler& dh)
484+ {
485 if (atomic_state.load() != core::net::http::Request::State::ready)
486 throw core::net::http::Request::Errors::AlreadyActive{CORE_FROM_HERE()};
487
488@@ -235,8 +247,10 @@
489 }
490
491 easy.on_write_data(
492- [context](char* data, std::size_t size, std::size_t nmemb)
493+ [context, dh](char* data, std::size_t size, std::size_t nmemb)
494 {
495+ // Report out to the data handler prior to accumulating data.
496+ dh(std::string{data, size * nmemb});
497 context->body.write(data, size * nmemb);
498 return size * nmemb;
499 });
500
501=== modified file 'tests/CMakeLists.txt'
502--- tests/CMakeLists.txt 2014-05-22 11:50:55 +0000
503+++ tests/CMakeLists.txt 2015-04-01 06:23:51 +0000
504@@ -57,6 +57,11 @@
505 )
506
507 add_executable(
508+ http_streaming_client_test
509+ http_streaming_client_test.cpp
510+)
511+
512+add_executable(
513 http_client_load_test
514 http_client_load_test.cpp
515 )
516@@ -82,6 +87,16 @@
517 )
518
519 target_link_libraries(
520+ http_streaming_client_test
521+
522+ net-cpp
523+
524+ ${GMOCK_BOTH_LIBRARIES}
525+ ${JSON_CPP_LDFLAGS}
526+ ${PROCESS_CPP_LDFLAGS}
527+)
528+
529+target_link_libraries(
530 http_client_load_test
531
532 net-cpp
533@@ -93,4 +108,5 @@
534
535 add_test(header_test ${CMAKE_CURRENT_BINARY_DIR}/header_test)
536 add_test(http_client_test ${CMAKE_CURRENT_BINARY_DIR}/http_client_test)
537+add_test(http_streaming_client_test ${CMAKE_CURRENT_BINARY_DIR}/http_streaming_client_test)
538 add_test(http_client_load_test ${CMAKE_CURRENT_BINARY_DIR}/http_client_load_test)
539
540=== added file 'tests/http_streaming_client_test.cpp'
541--- tests/http_streaming_client_test.cpp 1970-01-01 00:00:00 +0000
542+++ tests/http_streaming_client_test.cpp 2015-04-01 06:23:51 +0000
543@@ -0,0 +1,508 @@
544+/*
545+ * Copyright © 2013 Canonical Ltd.
546+ *
547+ * This program is free software: you can redistribute it and/or modify it
548+ * under the terms of the GNU Lesser General Public License version 3,
549+ * as published by the Free Software Foundation.
550+ *
551+ * This program is distributed in the hope that it will be useful,
552+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
553+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
554+ * GNU Lesser General Public License for more details.
555+ *
556+ * You should have received a copy of the GNU Lesser General Public License
557+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
558+ *
559+ * Authored by: Thomas Voß <thomas.voss@canonical.com>
560+ */
561+
562+#include <core/net/error.h>
563+#include <core/net/uri.h>
564+#include <core/net/http/streaming_client.h>
565+#include <core/net/http/content_type.h>
566+#include <core/net/http/request.h>
567+#include <core/net/http/response.h>
568+
569+#include "httpbin.h"
570+
571+#include <gmock/gmock.h>
572+#include <gtest/gtest.h>
573+
574+#include <json/json.h>
575+
576+#include <future>
577+#include <memory>
578+
579+namespace http = core::net::http;
580+namespace json = Json;
581+namespace net = core::net;
582+
583+namespace
584+{
585+class MockDataHandler : public std::enable_shared_from_this<MockDataHandler>
586+{
587+public:
588+ // We are enabling shared from this, thus forcing creation of
589+ // managed instances here.
590+ static std::shared_ptr<MockDataHandler> create()
591+ {
592+ return std::shared_ptr<MockDataHandler>(new MockDataHandler);
593+ }
594+
595+ MOCK_METHOD1(on_new_data, void(const std::string&));
596+
597+ http::StreamingRequest::DataHandler to_data_handler()
598+ {
599+ auto thiz = shared_from_this();
600+ return [thiz](const std::string& s)
601+ {
602+ thiz->on_new_data(s);
603+ };
604+ }
605+
606+private:
607+ MockDataHandler() = default;
608+};
609+
610+auto default_progress_reporter = [](const http::Request::Progress& progress)
611+{
612+ if (progress.download.current > 0. && progress.download.total > 0.)
613+ std::cout << "Download progress: " << progress.download.current / progress.download.total << std::endl;
614+ if (progress.upload.current > 0. && progress.upload.total > 0.)
615+ std::cout << "Upload progress: " << progress.upload.current / progress.upload.total << std::endl;
616+
617+ return http::Request::Progress::Next::continue_operation;
618+};
619+
620+bool init()
621+{
622+ static httpbin::Instance instance;
623+ return true;
624+}
625+
626+static const bool is_initialized = init();
627+}
628+
629+TEST(StreamingStreamingHttpClient, head_request_for_existing_resource_succeeds)
630+{
631+ using namespace ::testing;
632+
633+ // We obtain a default client instance, dispatching to the default implementation.
634+ auto client = http::make_streaming_client();
635+
636+ // Url pointing to the resource we would like to access via http.
637+ auto url = std::string(httpbin::host) + httpbin::resources::get();
638+
639+ // The client mostly acts as a factory for http requests.
640+ auto request = client->streaming_head(http::Request::Configuration::from_uri_as_string(url));
641+
642+ // Our mocked data handler.
643+ auto dh = MockDataHandler::create(); EXPECT_CALL(*dh, on_new_data(_)).Times(AtLeast(1));
644+
645+ // We finally execute the query synchronously and story the response.
646+ auto response = request->execute(default_progress_reporter, dh->to_data_handler());
647+
648+ // We expect the query to complete successfully
649+ EXPECT_EQ(core::net::http::Status::ok, response.status);
650+}
651+
652+TEST(StreamingHttpClient, get_request_for_existing_resource_succeeds)
653+{
654+ using namespace ::testing;
655+
656+ // We obtain a default client instance, dispatching to the default implementation.
657+ auto client = http::make_streaming_client();
658+
659+ // Url pointing to the resource we would like to access via http.
660+ auto url = std::string(httpbin::host) + httpbin::resources::get();
661+
662+ // The client mostly acts as a factory for http requests.
663+ auto request = client->streaming_get(http::Request::Configuration::from_uri_as_string(url));
664+
665+ // Our mocked data handler.
666+ auto dh = MockDataHandler::create(); EXPECT_CALL(*dh, on_new_data(_)).Times(AtLeast(1));
667+
668+ // All endpoint data on httpbin.org is JSON encoded.
669+ json::Value root;
670+ json::Reader reader;
671+
672+ // We finally execute the query synchronously and story the response.
673+ auto response = request->execute(default_progress_reporter, dh->to_data_handler());
674+
675+ // We expect the query to complete successfully
676+ EXPECT_EQ(core::net::http::Status::ok, response.status);
677+ // Parsing the body of the response as JSON should succeed.
678+ EXPECT_TRUE(reader.parse(response.body, root));
679+ // The url field of the payload should equal the original url we requested.
680+ EXPECT_EQ(url, root["url"].asString());
681+}
682+
683+TEST(StreamingHttpClient, get_request_with_custom_headers_for_existing_resource_succeeds)
684+{
685+ using namespace ::testing;
686+
687+ // We obtain a default client instance, dispatching to the default implementation.
688+ auto client = http::make_streaming_client();
689+
690+ // Url pointing to the resource we would like to access via http.
691+ auto url = std::string(httpbin::host) + httpbin::resources::headers();
692+
693+ // The client mostly acts as a factory for http requests.
694+ auto configuration = http::Request::Configuration::from_uri_as_string(url);
695+ configuration.header.set("Test1", "42");
696+ configuration.header.set("Test2", "43");
697+
698+ auto request = client->streaming_get(configuration);
699+
700+ // Our mocked data handler.
701+ auto dh = MockDataHandler::create(); EXPECT_CALL(*dh, on_new_data(_)).Times(AtLeast(1));
702+
703+ // All endpoint data on httpbin.org is JSON encoded.
704+ json::Value root;
705+ json::Reader reader;
706+
707+ // We finally execute the query synchronously and story the response.
708+ auto response = request->execute(default_progress_reporter, dh->to_data_handler());
709+
710+ // We expect the query to complete successfully
711+ EXPECT_EQ(core::net::http::Status::ok, response.status);
712+
713+ // Parsing the body of the response as JSON should succeed.
714+ EXPECT_TRUE(reader.parse(response.body, root));
715+
716+ auto headers = root["headers"];
717+
718+ EXPECT_EQ("42", headers["Test1"].asString());
719+ EXPECT_EQ("43", headers["Test2"].asString());
720+}
721+
722+TEST(StreamingHttpClient, empty_header_values_are_handled_correctly)
723+{
724+ using namespace ::testing;
725+
726+ // We obtain a default client instance, dispatching to the default implementation.
727+ auto client = http::make_streaming_client();
728+
729+ // Url pointing to the resource we would like to access via http.
730+ auto url = std::string(httpbin::host) + httpbin::resources::headers();
731+
732+ // The client mostly acts as a factory for http requests.
733+ auto configuration = http::Request::Configuration::from_uri_as_string(url);
734+ configuration.header.set("Empty", std::string{});
735+
736+ auto request = client->streaming_get(configuration);
737+
738+ // Our mocked data handler.
739+ auto dh = MockDataHandler::create(); EXPECT_CALL(*dh, on_new_data(_)).Times(AtLeast(1));
740+
741+ // All endpoint data on httpbin.org is JSON encoded.
742+ json::Value root;
743+ json::Reader reader;
744+
745+ // We finally execute the query synchronously and story the response.
746+ auto response = request->execute(default_progress_reporter, dh->to_data_handler());
747+
748+ // We expect the query to complete successfully
749+ EXPECT_EQ(core::net::http::Status::ok, response.status);
750+
751+ // Parsing the body of the response as JSON should succeed.
752+ EXPECT_TRUE(reader.parse(response.body, root));
753+
754+ auto headers = root["headers"];
755+ EXPECT_EQ(std::string{}, headers["Empty"].asString());
756+}
757+
758+TEST(StreamingHttpClient, get_request_for_existing_resource_guarded_by_basic_auth_succeeds)
759+{
760+ using namespace ::testing;
761+
762+ // We obtain a default client instance, dispatching to the default implementation.
763+ auto client = http::make_streaming_client();
764+
765+ // Url pointing to the resource we would like to access via http.
766+ auto url = std::string(httpbin::host) + httpbin::resources::basic_auth();
767+
768+ // The client mostly acts as a factory for http requests.
769+ auto configuration = http::Request::Configuration::from_uri_as_string(url);
770+ configuration.authentication_handler.for_http = [](const std::string&)
771+ {
772+ return http::Request::Credentials{"user", "passwd"};
773+ };
774+ auto request = client->streaming_get(configuration);
775+
776+ // Our mocked data handler.
777+ auto dh = MockDataHandler::create(); EXPECT_CALL(*dh, on_new_data(_)).Times(AtLeast(1));
778+
779+ // All endpoint data on httpbin.org is JSON encoded.
780+ json::Value root;
781+ json::Reader reader;
782+
783+ // We finally execute the query synchronously and story the response.
784+ auto response = request->execute(default_progress_reporter, dh->to_data_handler());
785+
786+ // We expect the query to complete successfully
787+ EXPECT_EQ(core::net::http::Status::ok, response.status);
788+ // Parsing the body of the response as JSON should succeed.
789+ EXPECT_TRUE(reader.parse(response.body, root));
790+ // We expect authentication to work.
791+ EXPECT_TRUE(root["authenticated"].asBool());
792+ // With the correct user id
793+ EXPECT_EQ("user", root["user"].asString());
794+}
795+
796+// Digest auth is broken on httpbin.org. It even fails in the browser after the first successful access.
797+TEST(StreamingHttpClient, DISABLED_get_request_for_existing_resource_guarded_by_digest_auth_succeeds)
798+{
799+ using namespace ::testing;
800+
801+ // We obtain a default client instance, dispatching to the default implementation.
802+ auto client = http::make_streaming_client();
803+
804+ // Url pointing to the resource we would like to access via http.
805+ auto url = std::string(httpbin::host) + httpbin::resources::digest_auth();
806+
807+ // The client mostly acts as a factory for http requests.
808+ auto configuration = http::Request::Configuration::from_uri_as_string(url);
809+ configuration.authentication_handler.for_http = [](const std::string&)
810+ {
811+ return http::Request::Credentials{"user", "passwd"};
812+ };
813+ auto request = client->streaming_get(configuration);
814+
815+ // Our mocked data handler.
816+ auto dh = MockDataHandler::create(); EXPECT_CALL(*dh, on_new_data(_)).Times(AtLeast(1));
817+
818+ // All endpoint data on httpbin.org is JSON encoded.
819+ json::Value root;
820+ json::Reader reader;
821+
822+ // We finally execute the query synchronously and story the response.
823+ auto response = request->execute(default_progress_reporter, dh->to_data_handler());
824+
825+ // We expect the query to complete successfully
826+ EXPECT_EQ(core::net::http::Status::ok, response.status);
827+ // Parsing the body of the response as JSON should succeed.
828+ EXPECT_TRUE(reader.parse(response.body, root));
829+ // We expect authentication to work.
830+ EXPECT_TRUE(root["authenticated"].asBool());
831+ // With the correct user id
832+ EXPECT_EQ("user", root["user"].asString());
833+}
834+
835+TEST(StreamingHttpClient, async_get_request_for_existing_resource_succeeds)
836+{
837+ using namespace ::testing;
838+
839+ // We obtain a default client instance, dispatching to the default implementation.
840+ auto client = http::make_streaming_client();
841+
842+ // Execute the client
843+ std::thread worker{[client]() { client->run(); }};
844+
845+ // Url pointing to the resource we would like to access via http.
846+ auto url = std::string(httpbin::host) + httpbin::resources::get();
847+
848+ // The client mostly acts as a factory for http requests.
849+ auto request = client->streaming_get(http::Request::Configuration::from_uri_as_string(url));
850+
851+ // Our mocked data handler.
852+ auto dh = MockDataHandler::create(); EXPECT_CALL(*dh, on_new_data(_)).Times(AtLeast(1));
853+
854+ std::promise<core::net::http::Response> promise;
855+ auto future = promise.get_future();
856+
857+ // We finally execute the query asynchronously.
858+ request->async_execute(
859+ http::Request::Handler()
860+ .on_progress(default_progress_reporter)
861+ .on_response([&](const core::net::http::Response& response)
862+ {
863+ promise.set_value(response);
864+ })
865+ .on_error([&](const core::net::Error& e)
866+ {
867+ promise.set_exception(std::make_exception_ptr(e));
868+ }),
869+ dh->to_data_handler());
870+
871+ auto response = future.get();
872+
873+ // All endpoint data on httpbin.org is JSON encoded.
874+ json::Value root;
875+ json::Reader reader;
876+
877+ // We expect the query to complete successfully
878+ EXPECT_EQ(core::net::http::Status::ok, response.status);
879+ // Parsing the body of the response as JSON should succeed.
880+ EXPECT_TRUE(reader.parse(response.body, root));
881+ // The url field of the payload should equal the original url we requested.
882+ EXPECT_EQ(url, root["url"].asString());
883+
884+ client->stop();
885+
886+ // We shut down our worker thread
887+ if (worker.joinable())
888+ worker.join();
889+}
890+
891+TEST(StreamingHttpClient, async_get_request_for_existing_resource_guarded_by_basic_authentication_succeeds)
892+{
893+ using namespace ::testing;
894+
895+ // We obtain a default client instance, dispatching to the default implementation.
896+ auto client = http::make_streaming_client();
897+
898+ // Execute the client
899+ std::thread worker{[client]() { client->run(); }};
900+
901+ // Url pointing to the resource we would like to access via http.
902+ auto url = std::string(httpbin::host) + httpbin::resources::basic_auth();
903+
904+ // The client mostly acts as a factory for http requests.
905+ auto configuration = http::Request::Configuration::from_uri_as_string(url);
906+
907+ configuration.authentication_handler.for_http = [](const std::string&)
908+ {
909+ return http::Request::Credentials{"user", "passwd"};
910+ };
911+
912+ auto request = client->streaming_get(configuration);
913+
914+ // Our mocked data handler.
915+ auto dh = MockDataHandler::create(); EXPECT_CALL(*dh, on_new_data(_)).Times(AtLeast(1));
916+
917+ // All endpoint data on httpbin.org is JSON encoded.
918+ json::Value root;
919+ json::Reader reader;
920+
921+ std::promise<core::net::http::Response> promise;
922+ auto future = promise.get_future();
923+
924+ // We finally execute the query asynchronously.
925+ request->async_execute(
926+ http::Request::Handler()
927+ .on_progress(default_progress_reporter)
928+ .on_response([&](const core::net::http::Response& response)
929+ {
930+ promise.set_value(response);
931+ client->stop();
932+ })
933+ .on_error([&](const core::net::Error& e)
934+ {
935+ promise.set_exception(std::make_exception_ptr(e));
936+ client->stop();
937+ }),
938+ dh->to_data_handler());
939+
940+ // And wait here for the response to arrive.
941+ auto response = future.get();
942+
943+ // We shut down our worker thread
944+ if (worker.joinable())
945+ worker.join();
946+
947+ // We expect the query to complete successfully
948+ EXPECT_EQ(core::net::http::Status::ok, response.status);
949+ // Parsing the body of the response as JSON should succeed.
950+ EXPECT_TRUE(reader.parse(response.body, root));
951+ // We expect authentication to work.
952+ EXPECT_TRUE(root["authenticated"].asBool());
953+ // With the correct user id
954+ EXPECT_EQ("user", root["user"].asString());
955+}
956+
957+TEST(StreamingHttpClient, post_request_for_existing_resource_succeeds)
958+{
959+ using namespace ::testing;
960+
961+ // We obtain a default client instance, dispatching to the default implementation.
962+ auto client = http::make_streaming_client();
963+
964+ // Url pointing to the resource we would like to access via http.
965+ auto url = std::string(httpbin::host) + httpbin::resources::post();
966+
967+ std::string payload = "{ 'test': 'test' }";
968+
969+ // The client mostly acts as a factory for http requests.
970+ auto request = client->streaming_post(http::Request::Configuration::from_uri_as_string(url),
971+ payload,
972+ core::net::http::ContentType::json);
973+
974+ // Our mocked data handler.
975+ auto dh = MockDataHandler::create(); EXPECT_CALL(*dh, on_new_data(_)).Times(AtLeast(1));
976+
977+ // All endpoint data on httpbin.org is JSON encoded.
978+ json::Value root;
979+ json::Reader reader;
980+
981+ // We finally execute the query synchronously and story the response.
982+ auto response = request->execute(default_progress_reporter, dh->to_data_handler());
983+
984+ // We expect the query to complete successfully
985+ EXPECT_EQ(core::net::http::Status::ok, response.status);
986+ // Parsing the body of the response as JSON should succeed.
987+ EXPECT_TRUE(reader.parse(response.body, root));
988+ // The url field of the payload should equal the original url we requested.
989+ EXPECT_EQ(payload, root["data"].asString());
990+}
991+
992+TEST(StreamingHttpClient, post_form_request_for_existing_resource_succeeds)
993+{
994+ using namespace ::testing;
995+
996+ // We obtain a default client instance, dispatching to the default implementation.
997+ auto client = http::make_streaming_client();
998+
999+ // Url pointing to the resource we would like to access via http.
1000+ auto url = std::string(httpbin::host) + httpbin::resources::post();
1001+
1002+ std::map<std::string, std::string> values
1003+ {
1004+ {"test", "test"}
1005+ };
1006+
1007+ // The client mostly acts as a factory for http requests.
1008+ auto request = client->streaming_post_form(http::Request::Configuration::from_uri_as_string(url),
1009+ values);
1010+
1011+ // Our mocked data handler.
1012+ auto dh = MockDataHandler::create(); EXPECT_CALL(*dh, on_new_data(_)).Times(AtLeast(1));
1013+
1014+ // We finally execute the query synchronously and store the response.
1015+ auto response = request->execute(default_progress_reporter, dh->to_data_handler());
1016+
1017+ // All endpoint data on httpbin.org is JSON encoded.
1018+ json::Value root;
1019+ json::Reader reader;
1020+
1021+ EXPECT_EQ(core::net::http::Status::ok, response.status);
1022+ EXPECT_TRUE(reader.parse(response.body, root));
1023+ EXPECT_EQ("test", root["form"]["test"].asString());
1024+}
1025+
1026+TEST(StreamingHttpClient, put_request_for_existing_resource_succeeds)
1027+{
1028+ using namespace ::testing;
1029+
1030+ auto client = http::make_streaming_client();
1031+ auto url = std::string(httpbin::host) + httpbin::resources::put();
1032+
1033+ const std::string value{"{ 'test': 'test' }"};
1034+ std::stringstream payload(value);
1035+
1036+ auto request = client->streaming_put(http::Request::Configuration::from_uri_as_string(url),
1037+ payload,
1038+ value.size());
1039+
1040+ // Our mocked data handler.
1041+ auto dh = MockDataHandler::create(); EXPECT_CALL(*dh, on_new_data(_)).Times(AtLeast(1));
1042+
1043+ json::Value root;
1044+ json::Reader reader;
1045+
1046+ auto response = request->execute(default_progress_reporter, dh->to_data_handler());
1047+
1048+ EXPECT_EQ(core::net::http::Status::ok, response.status);
1049+ EXPECT_TRUE(reader.parse(response.body, root));
1050+ EXPECT_EQ(payload.str(), root["data"].asString());
1051+}

Subscribers

People subscribed via source and target branches