Merge lp:~thomas-voss/net-cpp/bug-fixing-and-features-landing into lp:net-cpp

Proposed by Thomas Voß
Status: Merged
Merged at revision: 50
Proposed branch: lp:~thomas-voss/net-cpp/bug-fixing-and-features-landing
Merge into: lp:net-cpp
Prerequisite: lp:~gary-wzl77/net-cpp/bug-fixing-and-features
Diff against target: 431 lines (+163/-59)
11 files modified
debian/changelog (+8/-0)
debian/control (+2/-2)
debian/libnet-cpp2.symbols (+1/-1)
include/core/net/http/request.h (+9/-9)
include/core/net/http/streaming_request.h (+0/-12)
src/core/net/http/impl/curl/client.cpp (+17/-0)
src/core/net/http/impl/curl/easy.cpp (+1/-1)
src/core/net/http/impl/curl/multi.cpp (+5/-0)
src/core/net/http/impl/curl/multi.h (+3/-0)
src/core/net/http/impl/curl/request.h (+23/-27)
tests/http_streaming_client_test.cpp (+94/-7)
To merge this branch: bzr merge lp:~thomas-voss/net-cpp/bug-fixing-and-features-landing
Reviewer Review Type Date Requested Status
Gary.Wang (community) Approve
Ubuntu Phablet Team Pending
Review via email: mp+296140@code.launchpad.net

Commit message

* Enable pause/resume of requests.
* Fix LP:#1570686 and LP:#1570687

Description of the change

* Enable pause/resume of requests.
* Fix LP:#1570686 and LP:#1570687

To post a comment you must log in.
Revision history for this message
Thomas Voß (thomas-voss) wrote :

@Gary: I changed a few minor things when compared to your MP:
  - pause/resume on the easy handle is invoked on the downloading/uploading thread now.
  - fixed the test case to talk to a server that is (hopefully) reachable from within our CI labs.
  - some packaging updates to account for the major version bump.
  - moved speed.{limit, duration} into the Request::Configuration struct, picking reasonable values by default.

Revision history for this message
Gary.Wang (gary-wzl77) wrote :

Thanks tvoss for this MP, it looks good to me.
In this MP, I didn't see pause/resume issue anymore.
I think i should put speed.{limit, duration} into Request::Configuration struct in my MP just as what you do in this MP since we already have this struct there.
I really like the implementation of progress bar, it's quite neat and simple.
Thanks again.

P.S I forget we have sth available in CI labs. :)

review: Approve
55. By Thomas Voß

Make sure that exceptions while pausing/resuming an easy handle are not propagating.
Flush output stream for progress bar.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/changelog'
2--- debian/changelog 2015-04-15 12:34:49 +0000
3+++ debian/changelog 2016-06-01 08:27:01 +0000
4@@ -1,3 +1,11 @@
5+net-cpp (2.0.0) UNRELEASED; urgency=medium
6+
7+ [Gary Wang]
8+ * Enable pause/resume of requests.
9+ * Fix LP:#1570686 and LP:#1570687
10+
11+ -- Thomas Voß <thomas.voss@canonical.com> Tue, 31 May 2016 22:49:32 +0200
12+
13 net-cpp (1.2.0+15.04.20150415.2-0ubuntu1) vivid; urgency=medium
14
15 [ Thomas Voß ]
16
17=== modified file 'debian/control'
18--- debian/control 2015-02-25 10:27:29 +0000
19+++ debian/control 2016-06-01 08:27:01 +0000
20@@ -29,7 +29,7 @@
21 Vcs-Bzr: https://code.launchpad.net/~phablet-team/net-cpp/trunk
22 Vcs-Browser: https://bazaar.launchpad.net/~phablet-team/net-cpp/trunk/files
23
24-Package: libnet-cpp1
25+Package: libnet-cpp2
26 Architecture: any
27 Multi-Arch: same
28 Pre-Depends: ${misc:Pre-Depends},
29@@ -45,7 +45,7 @@
30 Architecture: any
31 Multi-Arch: same
32 Pre-Depends: ${misc:Pre-Depends},
33-Depends: libnet-cpp1 (= ${binary:Version}),
34+Depends: libnet-cpp2 (= ${binary:Version}),
35 ${misc:Depends},
36 Description: C++11 library for networking purposes - runtime library
37 Net-Cpp is a simple and straightforward networking library for C++11.
38
39=== renamed file 'debian/libnet-cpp1.install' => 'debian/libnet-cpp2.install'
40=== renamed file 'debian/libnet-cpp1.symbols' => 'debian/libnet-cpp2.symbols'
41--- debian/libnet-cpp1.symbols 2015-03-23 16:24:27 +0000
42+++ debian/libnet-cpp2.symbols 2016-06-01 08:27:01 +0000
43@@ -1,4 +1,4 @@
44-libnet-cpp.so.1 libnet-cpp1 #MINVER#
45+libnet-cpp.so.2 libnet-cpp2 #MINVER#
46 (c++)"core::net::http::make_client()@Base" 0.0.1+14.10.20140611
47 (c++)"core::net::http::make_streaming_client()@Base" 1.1.0+15.04.20150305-0ubuntu1
48 (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
49
50=== modified file 'include/core/net/http/request.h'
51--- include/core/net/http/request.h 2016-06-01 08:27:01 +0000
52+++ include/core/net/http/request.h 2016-06-01 08:27:01 +0000
53@@ -213,6 +213,13 @@
54 /** Invoked for querying user credentials to authenticate proxy accesses. */
55 AuthenicationHandler for_proxy;
56 } authentication_handler;
57+
58+ /** Encapsulates thresholds for minimum transfer speed in [kB/s] for duration seconds. */
59+ struct
60+ {
61+ std::uint64_t limit{1};
62+ std::chrono::seconds duration{std::chrono::seconds{30}};
63+ } speed;
64 };
65
66 Request(const Request&) = delete;
67@@ -249,7 +256,8 @@
68 virtual void async_execute(const Handler& handler) = 0;
69
70 /**
71- * @brief Pause the request
72+ * @brief Pause the request with options for aborting the request.
73+ * The request will be aborted if transfer speed falls below \a limit in [bytes/second] for \a time seconds.
74 * @throw core::net::http::Error in case of http-related errors.
75 */
76 virtual void pause() = 0;
77@@ -272,14 +280,6 @@
78 */
79 virtual std::string url_unescape(const std::string& s) = 0;
80
81- /**
82- * @brief Sets options for aborting the request.
83- * The request will be aborted if transfer speed belows \a limit bytes per second for \a time seconds
84- * @param limit The transfer speed in seconds.
85- * @param time waiting period(seconds) to abort the request.
86- */
87- virtual void abort_request_if(std::uint64_t limit, const std::chrono::seconds& time) = 0;
88-
89 protected:
90 /** @cond */
91 Request() = default;
92
93=== modified file 'include/core/net/http/streaming_request.h'
94--- include/core/net/http/streaming_request.h 2016-06-01 08:27:01 +0000
95+++ include/core/net/http/streaming_request.h 2016-06-01 08:27:01 +0000
96@@ -53,18 +53,6 @@
97 * @return The response to the request.
98 */
99 virtual void async_execute(const Handler& handler, const DataHandler& dh) = 0;
100-
101- /**
102- * @brief Pause the request
103- * @throw core::net::http::Error in case of http-related errors.
104- */
105- virtual void pause() = 0;
106-
107- /**
108- * @brief Resume the request
109- * @throw core::net::http::Error in case of http-related errors.
110- */
111- virtual void resume() = 0;
112 };
113 }
114 }
115
116=== modified file 'src/core/net/http/impl/curl/client.cpp'
117--- src/core/net/http/impl/curl/client.cpp 2016-06-01 08:27:01 +0000
118+++ src/core/net/http/impl/curl/client.cpp 2016-06-01 08:27:01 +0000
119@@ -36,6 +36,11 @@
120 namespace
121 {
122 const std::string BASE64_PADDING[] = { "", "==", "=" };
123+template<typename To, typename From>
124+To cast_without_overflow(From from)
125+{
126+ return static_cast<To>(std::min<From>(std::numeric_limits<To>::max(), from));
127+}
128 }
129
130 http::impl::curl::Client::Client()
131@@ -121,6 +126,8 @@
132 configuration.ssl.verify_host ? ::curl::easy::enable_ssl_host_verification : ::curl::easy::disable);
133 handle.set_option(::curl::Option::ssl_verify_peer,
134 configuration.ssl.verify_peer ? ::curl::easy::enable : ::curl::easy::disable);
135+ handle.set_option(::curl::Option::low_speed_limit, cast_without_overflow<long>(configuration.speed.limit));
136+ handle.set_option(::curl::Option::low_speed_time, cast_without_overflow<long>(configuration.speed.duration.count()));
137
138 if (configuration.authentication_handler.for_http)
139 {
140@@ -142,6 +149,8 @@
141 configuration.ssl.verify_host ? ::curl::easy::enable_ssl_host_verification : ::curl::easy::disable);
142 handle.set_option(::curl::Option::ssl_verify_peer,
143 configuration.ssl.verify_peer ? ::curl::easy::enable : ::curl::easy::disable);
144+ handle.set_option(::curl::Option::low_speed_limit, cast_without_overflow<long>(configuration.speed.limit));
145+ handle.set_option(::curl::Option::low_speed_time, cast_without_overflow<long>(configuration.speed.duration.count()));
146
147 if (configuration.authentication_handler.for_http)
148 {
149@@ -167,6 +176,8 @@
150 configuration.ssl.verify_host ? ::curl::easy::enable_ssl_host_verification : ::curl::easy::disable);
151 handle.set_option(::curl::Option::ssl_verify_peer,
152 configuration.ssl.verify_peer ? ::curl::easy::enable : ::curl::easy::disable);
153+ handle.set_option(::curl::Option::low_speed_limit, cast_without_overflow<long>(configuration.speed.limit));
154+ handle.set_option(::curl::Option::low_speed_time, cast_without_overflow<long>(configuration.speed.duration.count()));
155
156 if (configuration.authentication_handler.for_http)
157 {
158@@ -200,6 +211,8 @@
159 configuration.ssl.verify_host ? ::curl::easy::enable_ssl_host_verification : ::curl::easy::disable);
160 handle.set_option(::curl::Option::ssl_verify_peer,
161 configuration.ssl.verify_peer ? ::curl::easy::enable : ::curl::easy::disable);
162+ handle.set_option(::curl::Option::low_speed_limit, cast_without_overflow<long>(configuration.speed.limit));
163+ handle.set_option(::curl::Option::low_speed_time, cast_without_overflow<long>(configuration.speed.duration.count()));
164
165 if (configuration.authentication_handler.for_http)
166 {
167@@ -231,6 +244,8 @@
168 configuration.ssl.verify_host ? ::curl::easy::enable_ssl_host_verification : ::curl::easy::disable);
169 handle.set_option(::curl::Option::ssl_verify_peer,
170 configuration.ssl.verify_peer ? ::curl::easy::enable : ::curl::easy::disable);
171+ handle.set_option(::curl::Option::low_speed_limit, cast_without_overflow<long>(configuration.speed.limit));
172+ handle.set_option(::curl::Option::low_speed_time, cast_without_overflow<long>(configuration.speed.duration.count()));
173
174 if (configuration.authentication_handler.for_http)
175 {
176@@ -252,6 +267,8 @@
177 configuration.ssl.verify_host ? ::curl::easy::enable_ssl_host_verification : ::curl::easy::disable);
178 handle.set_option(::curl::Option::ssl_verify_peer,
179 configuration.ssl.verify_peer ? ::curl::easy::enable : ::curl::easy::disable);
180+ handle.set_option(::curl::Option::low_speed_limit, cast_without_overflow<long>(configuration.speed.limit));
181+ handle.set_option(::curl::Option::low_speed_time, cast_without_overflow<long>(configuration.speed.duration.count()));
182
183 if (configuration.authentication_handler.for_http)
184 {
185
186=== modified file 'src/core/net/http/impl/curl/easy.cpp'
187--- src/core/net/http/impl/curl/easy.cpp 2016-06-01 08:27:01 +0000
188+++ src/core/net/http/impl/curl/easy.cpp 2016-06-01 08:27:01 +0000
189@@ -451,7 +451,7 @@
190 void easy::Handle::resume()
191 {
192 if (!d) throw easy::Handle::HandleHasBeenAbandoned{};
193- throw_if_not<curl::Code::ok>(easy::native::pause(native(), CURLPAUSE_RECV_CONT), [this]() { return std::string{d->error};});
194+ throw_if_not<curl::Code::ok>(easy::native::pause(native(), CURLPAUSE_CONT), [this]() { return std::string{d->error};});
195 }
196
197 // URL escapes the given input string.
198
199=== modified file 'src/core/net/http/impl/curl/multi.cpp'
200--- src/core/net/http/impl/curl/multi.cpp 2015-02-25 10:26:38 +0000
201+++ src/core/net/http/impl/curl/multi.cpp 2016-06-01 08:27:01 +0000
202@@ -285,6 +285,11 @@
203 return result;
204 }
205
206+void multi::Handle::dispatch(const std::function<void ()> &task)
207+{
208+ d->dispatcher.post(task);
209+}
210+
211 void multi::Handle::run()
212 {
213 d->dispatcher.run();
214
215=== modified file 'src/core/net/http/impl/curl/multi.h'
216--- src/core/net/http/impl/curl/multi.h 2014-03-13 20:50:36 +0000
217+++ src/core/net/http/impl/curl/multi.h 2016-06-01 08:27:01 +0000
218@@ -140,6 +140,9 @@
219 // Queries statistics about the timing information of the last transfers.
220 core::net::http::Client::Timings timings();
221
222+ // Dispatch dispatches task on the underlying reactor.
223+ void dispatch(const std::function<void()>& task);
224+
225 // Executes the underlying dispatcher executing the curl multi instance.
226 // Can be called multiple times for thread-pool use-cases.
227 void run();
228
229=== modified file 'src/core/net/http/impl/curl/request.h'
230--- src/core/net/http/impl/curl/request.h 2016-06-01 08:27:01 +0000
231+++ src/core/net/http/impl/curl/request.h 2016-06-01 08:27:01 +0000
232@@ -242,7 +242,6 @@
233 case Request::Progress::Next::abort_operation: result = 1; break;
234 case Request::Progress::Next::continue_operation: result = 0; break;
235 }
236-
237 return result;
238 });
239 }
240@@ -275,26 +274,32 @@
241 }
242
243 void pause()
244- {
245- try
246- {
247- easy.pause();
248- } catch(const std::system_error& se)
249- {
250- throw core::net::http::Error(se.what(), CORE_FROM_HERE());
251- }
252+ {
253+ auto copy = easy;
254+ multi.dispatch([copy]() mutable
255+ {
256+ try
257+ {
258+ copy.pause();
259+ }
260+ catch(...) {}
261+ });
262+
263 }
264
265 void resume()
266- {
267- try
268- {
269- easy.resume();
270- } catch(const std::system_error& se)
271- {
272- throw core::net::http::Error(se.what(), CORE_FROM_HERE());
273- }
274- }
275+ {
276+ auto copy = easy;
277+ multi.dispatch([copy]() mutable
278+ {
279+ try
280+ {
281+ copy.resume();
282+ }
283+ catch(...) {}
284+
285+ });
286+ }
287
288 std::string url_escape(const std::string& s)
289 {
290@@ -306,15 +311,6 @@
291 return easy.unescape(s);
292 }
293
294- void abort_request_if(std::uint64_t limit, const std::chrono::seconds& time)
295- {
296- if (atomic_state.load() != core::net::http::Request::State::ready)
297- throw core::net::http::Request::Errors::AlreadyActive{CORE_FROM_HERE()};
298-
299- easy.set_option(::curl::Option::low_speed_limit, limit);
300- easy.set_option(::curl::Option::low_speed_time, time.count());
301- }
302-
303 private:
304 std::atomic<core::net::http::Request::State> atomic_state;
305 ::curl::multi::Handle multi;
306
307=== modified file 'tests/http_streaming_client_test.cpp'
308--- tests/http_streaming_client_test.cpp 2016-06-01 08:27:01 +0000
309+++ tests/http_streaming_client_test.cpp 2016-06-01 08:27:01 +0000
310@@ -35,6 +35,8 @@
311 #include <memory>
312
313 #include <fstream>
314+#include <iomanip>
315+#include <iostream>
316
317 namespace http = core::net::http;
318 namespace json = Json;
319@@ -67,16 +69,43 @@
320 MockDataHandler() = default;
321 };
322
323-auto default_progress_reporter = [](const http::Request::Progress& progress)
324-{
325- if (progress.download.current > 0. && progress.download.total > 0.)
326- std::cout << "Download progress: " << progress.download.current / progress.download.total << std::endl;
327- if (progress.upload.current > 0. && progress.upload.total > 0.)
328- std::cout << "Upload progress: " << progress.upload.current / progress.upload.total << std::endl;
329-
330+class ProgressBar
331+{
332+public:
333+ ProgressBar(std::uint32_t width)
334+ : width{width}, out{std::cout}
335+ {
336+ }
337+
338+ ~ProgressBar()
339+ {
340+ out << std::endl;
341+ }
342+
343+ void update(double percentage)
344+ {
345+ struct CursorState
346+ {
347+ CursorState(std::ostream& out) : out{out} { out << "\33[?25l"; }
348+ ~CursorState() { out << "\33[?25h"; }
349+ std::ostream& out;
350+ } cs{out};
351+
352+ out << "\r" << "[" << std::setw(width) << std::left << std::setfill(' ') << std::string(percentage * width, '=') << "] " << std::setw(5) << std::fixed << std::setprecision(2) << percentage * 100 << " %" << std::flush;
353+ }
354+
355+private:
356+ std::uint32_t width;
357+ std::ostream& out;
358+};
359+
360+auto silent_progress_reporter = [](const http::Request::Progress&)
361+{
362 return http::Request::Progress::Next::continue_operation;
363 };
364
365+auto default_progress_reporter = silent_progress_reporter;
366+
367 bool init()
368 {
369 static httpbin::Instance instance;
370@@ -594,3 +623,61 @@
371 EXPECT_EQ(url, root["url"].asString());
372 }
373
374+TEST(StreamingHttpClient, request_can_be_paused_and_resumed)
375+{
376+ using namespace ::testing;
377+ // We obtain a default client instance, dispatching to the default implementation.
378+ auto client = http::make_streaming_client();
379+
380+ // Execute the client
381+ std::thread worker{[client]() { client->run(); }};
382+
383+ auto url = "http://archive.ubuntu.com/ubuntu/dists/xenial/main/installer-amd64/current/images/netboot/mini.iso";
384+
385+ // The client mostly acts as a factory for http requests.
386+ auto request = client->streaming_get(http::Request::Configuration::from_uri_as_string(url));
387+
388+ // Our mocked data handler.
389+ auto dh = MockDataHandler::create(); EXPECT_CALL(*dh, on_new_data(_)).Times(AtLeast(1));
390+
391+ std::promise<core::net::http::Response> promise;
392+ auto future = promise.get_future();
393+
394+ ProgressBar pb{80};
395+
396+ // We finally execute the query asynchronously.
397+ request->async_execute(http::Request::Handler()
398+ .on_progress([&pb](const http::Request::Progress& progress)
399+ {
400+ if (progress.download.current > 0. && progress.download.total > 0.)
401+ pb.update(progress.download.current / static_cast<double>(progress.download.total));
402+ return http::Request::Progress::Next::continue_operation;
403+ })
404+ .on_response([&](const core::net::http::Response& response)
405+ {
406+ promise.set_value(response);
407+ })
408+ .on_error([&](const core::net::Error& e)
409+ {
410+ promise.set_exception(std::make_exception_ptr(e));
411+ }),
412+ dh->to_data_handler());
413+
414+ std::this_thread::sleep_for(std::chrono::seconds(2));
415+
416+ request->pause();
417+ request->resume();
418+
419+ try
420+ {
421+ // This might very well throw.
422+ EXPECT_EQ(core::net::http::Status::ok, future.get().status);
423+ } catch (const std::exception& e) { FAIL() << e.what(); }
424+
425+ client->stop();
426+
427+ // We shut down our worker thread
428+ if (worker.joinable())
429+ worker.join();
430+}
431+

Subscribers

People subscribed via source and target branches