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
=== modified file 'CMakeLists.txt'
--- CMakeLists.txt 2014-07-30 15:16:19 +0000
+++ CMakeLists.txt 2015-04-01 06:23:51 +0000
@@ -27,7 +27,7 @@
27set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined")27set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined")
2828
29set(NET_CPP_VERSION_MAJOR 1)29set(NET_CPP_VERSION_MAJOR 1)
30set(NET_CPP_VERSION_MINOR 1)30set(NET_CPP_VERSION_MINOR 2)
31set(NET_CPP_VERSION_PATCH 0)31set(NET_CPP_VERSION_PATCH 0)
3232
33include(CTest)33include(CTest)
@@ -36,6 +36,8 @@
36 include/36 include/
37)37)
3838
39file(GLOB_RECURSE NET_CPP_INTERFACE_HEADERS include/*.h)
40
39add_subdirectory(doc)41add_subdirectory(doc)
40add_subdirectory(data)42add_subdirectory(data)
41add_subdirectory(include)43add_subdirectory(include)
4244
=== modified file 'debian/changelog'
--- debian/changelog 2015-03-05 12:08:09 +0000
+++ debian/changelog 2015-04-01 06:23:51 +0000
@@ -1,3 +1,9 @@
1net-cpp (1.2.0) UNRELEASED; urgency=medium
2
3 * Introduce a streaming http interface.
4
5 -- Thomas Voß <thomas.voss@canonical.com> Wed, 01 Apr 2015 08:22:32 +0200
6
1net-cpp (1.1.0+15.04.20150305-0ubuntu1) vivid; urgency=medium7net-cpp (1.1.0+15.04.20150305-0ubuntu1) vivid; urgency=medium
28
3 [ thomas-voss ]9 [ thomas-voss ]
410
=== modified file 'debian/libnet-cpp1.symbols'
--- debian/libnet-cpp1.symbols 2014-12-04 20:54:26 +0000
+++ debian/libnet-cpp1.symbols 2015-04-01 06:23:51 +0000
@@ -1,5 +1,6 @@
1libnet-cpp.so.1 libnet-cpp1 #MINVER#1libnet-cpp.so.1 libnet-cpp1 #MINVER#
2 (c++)"core::net::http::make_client()@Base" 0.0.1+14.10.201406112 (c++)"core::net::http::make_client()@Base" 0.0.1+14.10.20140611
3 (c++)"core::net::http::make_streaming_client()@Base" 1.1.0+15.04.20150305-0ubuntu1
3 (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.201408044 (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
4 (c++)"core::net::http::Client::uri_to_string(core::net::Uri const&) const@Base" 1.1.0+14.10.201408045 (c++)"core::net::http::Client::uri_to_string(core::net::Uri const&) const@Base" 1.1.0+14.10.20140804
5 (c++)"core::net::http::Client::Errors::HttpMethodNotSupported::HttpMethodNotSupported(core::net::http::Method, core::Location const&)@Base" 0.0.1+14.10.201406116 (c++)"core::net::http::Client::Errors::HttpMethodNotSupported::HttpMethodNotSupported(core::net::http::Method, core::Location const&)@Base" 0.0.1+14.10.20140611
@@ -41,3 +42,6 @@
41 (c++)"vtable for core::net::http::Header@Base" 0.0.1+14.10.2014061142 (c++)"vtable for core::net::http::Header@Base" 0.0.1+14.10.20140611
42 (c++)"vtable for core::net::http::Request::Errors::AlreadyActive@Base" 0.0.1+14.10.2014061143 (c++)"vtable for core::net::http::Request::Errors::AlreadyActive@Base" 0.0.1+14.10.20140611
43 (c++)"vtable for core::net::http::Request@Base" 0.0.1+14.10.2014061144 (c++)"vtable for core::net::http::Request@Base" 0.0.1+14.10.20140611
45 (c++)"typeinfo for core::net::http::StreamingRequest@Base" 1.1.0+15.04.20150305-0ubuntu1
46 (c++)"typeinfo name for core::net::http::StreamingRequest@Base" 1.1.0+15.04.20150305-0ubuntu1
47 (c++)"vtable for core::net::http::StreamingRequest@Base" 1.1.0+15.04.20150305-0ubuntu1
44\ No newline at end of file48\ No newline at end of file
4549
=== added file 'include/core/net/http/streaming_client.h'
--- include/core/net/http/streaming_client.h 1970-01-01 00:00:00 +0000
+++ include/core/net/http/streaming_client.h 2015-04-01 06:23:51 +0000
@@ -0,0 +1,88 @@
1/*
2 * Copyright © 2013 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License version 3,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Thomas Voß <thomas.voss@canonical.com>
17 */
18#ifndef CORE_NET_HTTP_STREAMING_CLIENT_H_
19#define CORE_NET_HTTP_STREAMING_CLIENT_H_
20
21#include <core/net/http/client.h>
22
23#include <core/net/http/streaming_request.h>
24
25namespace core
26{
27namespace net
28{
29namespace http
30{
31class StreamingClient : public Client
32{
33public:
34
35 virtual ~StreamingClient() = default;
36
37 /**
38 * @brief streaming_get is a convenience method for issueing a GET request for the given URI.
39 * @throw Errors::HttpMethodNotSupported if the underlying implementation does not support the provided HTTP method.
40 * @param configuration The configuration to issue a get request for.
41 * @return An executable instance of class Request.
42 */
43 virtual std::shared_ptr<StreamingRequest> streaming_get(const Request::Configuration& configuration) = 0;
44
45 /**
46 * @brief streaming_head is a convenience method for issueing a HEAD request for the given URI.
47 * @throw Errors::HttpMethodNotSupported if the underlying implementation does not support the provided HTTP method.
48 * @param configuration The configuration to issue a get request for.
49 * @return An executable instance of class Request.
50 */
51 virtual std::shared_ptr<StreamingRequest> streaming_head(const Request::Configuration& configuration) = 0;
52
53 /**
54 * @brief streaming_put is a convenience method for issuing a PUT request for the given URI.
55 * @throw Errors::HttpMethodNotSupported if the underlying implementation does not support the provided HTTP method.
56 * @param configuration The configuration to issue a get request for.
57 * @param payload The data to be transmitted as part of the PUT request.
58 * @param size Size of the payload data in bytes.
59 * @return An executable instance of class Request.
60 */
61 virtual std::shared_ptr<StreamingRequest> streaming_put(const Request::Configuration& configuration, std::istream& payload, std::size_t size) = 0;
62
63 /**
64 * @brief streaming_post is a convenience method for issuing a POST request for the given URI.
65 * @throw Errors::HttpMethodNotSupported if the underlying implementation does not support the provided HTTP method.
66 * @param configuration The configuration to issue a get request for.
67 * @param payload The data to be transmitted as part of the POST request.
68 * @param type The content-type of the data.
69 * @return An executable instance of class Request.
70 */
71 virtual std::shared_ptr<StreamingRequest> streaming_post(const Request::Configuration& configuration, const std::string& payload, const std::string& type) = 0;
72
73 /**
74 * @brief streaming_post_form is a convenience method for issuing a POST request for the given URI, with url-encoded payload.
75 * @throw Errors::HttpMethodNotSupported if the underlying implementation does not support the provided HTTP method.
76 * @param configuration The configuration to issue a get request for.
77 * @param values Key-value pairs to be added to the payload in url-encoded format.
78 * @return An executable instance of class Request.
79 */
80 virtual std::shared_ptr<StreamingRequest> streaming_post_form(const Request::Configuration& configuration, const std::map<std::string, std::string>& values) = 0;
81};
82
83/** @brief Dispatches to the default implementation and returns a streaming client instance. */
84CORE_NET_DLL_PUBLIC std::shared_ptr<StreamingClient> make_streaming_client();
85}
86}
87}
88#endif // CORE_NET_HTTP_STREAMING_CLIENT_H_
089
=== added file 'include/core/net/http/streaming_request.h'
--- include/core/net/http/streaming_request.h 1970-01-01 00:00:00 +0000
+++ include/core/net/http/streaming_request.h 2015-04-01 06:23:51 +0000
@@ -0,0 +1,60 @@
1/*
2 * Copyright © 2013 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License version 3,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Thomas Voß <thomas.voss@canonical.com>
17 */
18#ifndef CORE_NET_HTTP_STREAMING_REQUEST_H_
19#define CORE_NET_HTTP_STREAMING_REQUEST_H_
20
21#include <core/net/http/request.h>
22
23namespace core
24{
25namespace net
26{
27namespace http
28{
29/**
30 * @brief The StreamingRequest class encapsulates a request for a web resource,
31 * streaming data to the receiver as it receives in addition to accumulating all incoming data.
32 */
33class CORE_NET_DLL_PUBLIC StreamingRequest : public Request
34{
35public:
36
37 /** DataHandler is invoked when a new chunk of data arrives from the server. */
38 typedef std::function<void(const std::string&)> DataHandler;
39
40 /**
41 * @brief Synchronously executes the request.
42 * @throw core::net::http::Error in case of http-related errors.
43 * @throw core::net::Error in case of network-related errors.
44 * @return The response to the request.
45 */
46 virtual Response execute(const ProgressHandler& ph, const DataHandler& dh) = 0;
47
48 /**
49 * @brief Asynchronously executes the request, reporting errors, progress and completion to the given handlers.
50 * @param handler The handlers to called for events happening during execution of the request.
51 * @param dh The data handler receiving chunks of data while executing the request.
52 * @return The response to the request.
53 */
54 virtual void async_execute(const Handler& handler, const DataHandler& dh) = 0;
55};
56}
57}
58}
59
60#endif // CORE_NET_HTTP_STREAMING_REQUEST_H_
061
=== modified file 'src/CMakeLists.txt'
--- src/CMakeLists.txt 2014-06-10 14:19:14 +0000
+++ src/CMakeLists.txt 2015-04-01 06:23:51 +0000
@@ -22,6 +22,8 @@
22add_library(22add_library(
23 net-cpp SHARED23 net-cpp SHARED
2424
25 ${NET_CPP_INTERFACE_HEADERS}
26
25 core/location.cpp27 core/location.cpp
2628
27 core/net/error.cpp29 core/net/error.cpp
2830
=== modified file 'src/core/net/http/impl/curl/client.cpp'
--- src/core/net/http/impl/curl/client.cpp 2014-06-10 14:19:14 +0000
+++ src/core/net/http/impl/curl/client.cpp 2015-04-01 06:23:51 +0000
@@ -20,6 +20,7 @@
20#include "curl.h"20#include "curl.h"
21#include "request.h"21#include "request.h"
2222
23#include <core/net/http/content_type.h>
23#include <core/net/http/method.h>24#include <core/net/http/method.h>
2425
25#include <boost/archive/iterators/base64_from_binary.hpp>26#include <boost/archive/iterators/base64_from_binary.hpp>
@@ -107,7 +108,7 @@
107 multi.stop();108 multi.stop();
108}109}
109110
110std::shared_ptr<http::Request> http::impl::curl::Client::head(const http::Request::Configuration& configuration)111std::shared_ptr<http::impl::curl::Request> http::impl::curl::Client::head_impl(const http::Request::Configuration& configuration)
111{112{
112 ::curl::easy::Handle handle;113 ::curl::easy::Handle handle;
113 handle.method(http::Method::head)114 handle.method(http::Method::head)
@@ -126,10 +127,10 @@
126 handle.http_credentials(credentials.username, credentials.password);127 handle.http_credentials(credentials.username, credentials.password);
127 }128 }
128129
129 return std::shared_ptr<http::Request>{new http::impl::curl::Request{multi, handle}};130 return std::shared_ptr<http::impl::curl::Request>{new http::impl::curl::Request{multi, handle}};
130}131}
131132
132std::shared_ptr<http::Request> http::impl::curl::Client::get(const http::Request::Configuration& configuration)133std::shared_ptr<http::impl::curl::Request> http::impl::curl::Client::get_impl(const http::Request::Configuration& configuration)
133{134{
134 ::curl::easy::Handle handle;135 ::curl::easy::Handle handle;
135 handle.method(http::Method::get)136 handle.method(http::Method::get)
@@ -147,10 +148,10 @@
147 handle.http_credentials(credentials.username, credentials.password);148 handle.http_credentials(credentials.username, credentials.password);
148 }149 }
149150
150 return std::shared_ptr<http::Request>{new http::impl::curl::Request{multi, handle}};151 return std::shared_ptr<http::impl::curl::Request>{new http::impl::curl::Request{multi, handle}};
151}152}
152153
153std::shared_ptr<http::Request> http::impl::curl::Client::post(154std::shared_ptr<http::impl::curl::Request> http::impl::curl::Client::post_impl(
154 const Request::Configuration& configuration,155 const Request::Configuration& configuration,
155 const std::string& payload,156 const std::string& payload,
156 const std::string& ct)157 const std::string& ct)
@@ -172,10 +173,10 @@
172 handle.http_credentials(credentials.username, credentials.password);173 handle.http_credentials(credentials.username, credentials.password);
173 }174 }
174175
175 return std::shared_ptr<http::Request>{new http::impl::curl::Request{multi, handle}};176 return std::shared_ptr<http::impl::curl::Request>{new http::impl::curl::Request{multi, handle}};
176}177}
177178
178std::shared_ptr<http::Request> http::impl::curl::Client::put(179std::shared_ptr<http::impl::curl::Request> http::impl::curl::Client::put_impl(
179 const Request::Configuration& configuration,180 const Request::Configuration& configuration,
180 std::istream& payload,181 std::istream& payload,
181 std::size_t size)182 std::size_t size)
@@ -201,10 +202,75 @@
201 handle.http_credentials(credentials.username, credentials.password);202 handle.http_credentials(credentials.username, credentials.password);
202 }203 }
203204
204 return std::shared_ptr<http::Request>{new http::impl::curl::Request{multi, handle}};205 return std::shared_ptr<http::impl::curl::Request>{new http::impl::curl::Request{multi, handle}};
206}
207
208std::shared_ptr<http::StreamingRequest> http::impl::curl::Client::streaming_get(const http::Request::Configuration& configuration)
209{
210 return get_impl(configuration);
211}
212
213std::shared_ptr<http::StreamingRequest> http::impl::curl::Client::streaming_head(const http::Request::Configuration& configuration)
214{
215 return head_impl(configuration);
216}
217
218std::shared_ptr<http::StreamingRequest> http::impl::curl::Client::streaming_put(const http::Request::Configuration& configuration, std::istream& payload, std::size_t size)
219{
220 return put_impl(configuration, payload, size);
221}
222
223std::shared_ptr<http::StreamingRequest> http::impl::curl::Client::streaming_post(const http::Request::Configuration& configuration, const std::string& payload, const std::string& type)
224{
225 return post_impl(configuration, payload, type);
226}
227
228std::shared_ptr<http::StreamingRequest> http::impl::curl::Client::streaming_post_form(const http::Request::Configuration& configuration, const std::map<std::string, std::string>& values)
229{
230 std::stringstream ss;
231 bool first{true};
232
233 for (const auto& pair : values)
234 {
235 ss << (first ? "" : "&") << url_escape(pair.first) << "=" << url_escape(pair.second);
236 first = false;
237 }
238
239 return post_impl(configuration, ss.str(), http::ContentType::x_www_form_urlencoded);
240}
241
242std::shared_ptr<http::Request> http::impl::curl::Client::head(const http::Request::Configuration& configuration)
243{
244 return head_impl(configuration);
245}
246
247std::shared_ptr<http::Request> http::impl::curl::Client::get(const http::Request::Configuration& configuration)
248{
249 return get_impl(configuration);
250}
251
252std::shared_ptr<http::Request> http::impl::curl::Client::post(
253 const Request::Configuration& configuration,
254 const std::string& payload,
255 const std::string& ct)
256{
257 return post_impl(configuration, payload, ct);
258}
259
260std::shared_ptr<http::Request> http::impl::curl::Client::put(
261 const Request::Configuration& configuration,
262 std::istream& payload,
263 std::size_t size)
264{
265 return put_impl(configuration, payload, size);
205}266}
206267
207std::shared_ptr<http::Client> http::make_client()268std::shared_ptr<http::Client> http::make_client()
208{269{
209 return std::make_shared<http::impl::curl::Client>();270 return std::make_shared<http::impl::curl::Client>();
210}271}
272
273std::shared_ptr<http::StreamingClient> http::make_streaming_client()
274{
275 return std::make_shared<http::impl::curl::Client>();
276}
211277
=== modified file 'src/core/net/http/impl/curl/client.h'
--- src/core/net/http/impl/curl/client.h 2014-06-10 14:19:14 +0000
+++ src/core/net/http/impl/curl/client.h 2015-04-01 06:23:51 +0000
@@ -18,9 +18,10 @@
18#ifndef CORE_NET_HTTP_IMPL_CURL_CLIENT_H_18#ifndef CORE_NET_HTTP_IMPL_CURL_CLIENT_H_
19#define CORE_NET_HTTP_IMPL_CURL_CLIENT_H_19#define CORE_NET_HTTP_IMPL_CURL_CLIENT_H_
2020
21#include <core/net/http/client.h>21#include <core/net/http/streaming_client.h>
2222
23#include "curl.h"23#include "curl.h"
24#include "request.h"
2425
25namespace core26namespace core
26{27{
@@ -32,7 +33,7 @@
32{33{
33namespace curl34namespace curl
34{35{
35class Client : public core::net::http::Client36class Client : public core::net::http::StreamingClient
36{37{
37public:38public:
38 Client();39 Client();
@@ -45,23 +46,30 @@
4546
46 std::string base64_decode(const std::string& s) const override;47 std::string base64_decode(const std::string& s) const override;
4748
48 core::net::http::Client::Timings timings();49 core::net::http::Client::Timings timings() override;
4950
50 void run();51 void run() override;
5152
52 void stop();53 void stop() override;
5354
54 std::shared_ptr<Request> get(const Request::Configuration& configuration);55 std::shared_ptr<http::Request> get(const Request::Configuration& configuration) override;
5556 std::shared_ptr<http::Request> head(const Request::Configuration& configuration) override;
56 std::shared_ptr<Request> head(const Request::Configuration& configuration);57 std::shared_ptr<http::Request> post(const Request::Configuration& configuration, const std::string&, const std::string&) override;
5758 std::shared_ptr<http::Request> put(const Request::Configuration& configuration, std::istream& payload, std::size_t size) override;
58 std::shared_ptr<Request> post(const Request::Configuration& configuration, const std::string&, const std::string&);59
5960 std::shared_ptr<http::StreamingRequest> streaming_get(const Request::Configuration& configuration) override;
60 std::shared_ptr<Request> put(const Request::Configuration& configuration, std::istream& payload, std::size_t size);61 std::shared_ptr<http::StreamingRequest> streaming_head(const Request::Configuration& configuration) override;
62 std::shared_ptr<http::StreamingRequest> streaming_put(const Request::Configuration& configuration, std::istream& payload, std::size_t size) override;
63 std::shared_ptr<http::StreamingRequest> streaming_post(const Request::Configuration& configuration, const std::string& payload, const std::string& type) override;
64 std::shared_ptr<http::StreamingRequest> streaming_post_form(const Request::Configuration& configuration, const std::map<std::string, std::string>& values) override;
6165
62private:66private:
63 ::curl::multi::Handle multi;67 std::shared_ptr<curl::Request> get_impl(const Request::Configuration& configuration);
68 std::shared_ptr<curl::Request> head_impl(const Request::Configuration& configuration);
69 std::shared_ptr<curl::Request> post_impl(const Request::Configuration& configuration, const std::string&, const std::string&);
70 std::shared_ptr<curl::Request> put_impl(const Request::Configuration& configuration, std::istream& payload, std::size_t size);
6471
72 ::curl::multi::Handle multi;
65};73};
66}74}
67}75}
6876
=== modified file 'src/core/net/http/impl/curl/request.h'
--- src/core/net/http/impl/curl/request.h 2015-01-23 06:23:31 +0000
+++ src/core/net/http/impl/curl/request.h 2015-04-01 06:23:51 +0000
@@ -18,7 +18,7 @@
18#ifndef CORE_NET_HTTP_IMPL_CURL_REQUEST_H_18#ifndef CORE_NET_HTTP_IMPL_CURL_REQUEST_H_
19#define CORE_NET_HTTP_IMPL_CURL_REQUEST_H_19#define CORE_NET_HTTP_IMPL_CURL_REQUEST_H_
2020
21#include <core/net/http/request.h>21#include <core/net/http/streaming_request.h>
2222
23#include <core/net/http/error.h>23#include <core/net/http/error.h>
24#include <core/net/http/response.h>24#include <core/net/http/response.h>
@@ -80,7 +80,7 @@
80 std::atomic<core::net::http::Request::State>& state;80 std::atomic<core::net::http::Request::State>& state;
81};81};
8282
83class Request : public core::net::http::Request,83class Request : public core::net::http::StreamingRequest,
84 public std::enable_shared_from_this<Request>84 public std::enable_shared_from_this<Request>
85{85{
86public:86public:
@@ -118,6 +118,11 @@
118118
119 Response execute(const Request::ProgressHandler& ph)119 Response execute(const Request::ProgressHandler& ph)
120 {120 {
121 return execute(ph, [](const std::string&){});
122 }
123
124 Response execute(const Request::ProgressHandler& ph, const StreamingRequest::DataHandler& dh)
125 {
121 if (atomic_state.load() != core::net::http::Request::State::ready)126 if (atomic_state.load() != core::net::http::Request::State::ready)
122 throw core::net::http::Request::Errors::AlreadyActive{CORE_FROM_HERE()};127 throw core::net::http::Request::Errors::AlreadyActive{CORE_FROM_HERE()};
123128
@@ -149,6 +154,8 @@
149 easy.on_write_data(154 easy.on_write_data(
150 [&](char* data, std::size_t size, std::size_t nmemb)155 [&](char* data, std::size_t size, std::size_t nmemb)
151 {156 {
157 // Report out to the data handler prior to accumulating data.
158 dh(std::string{data, size * nmemb});
152 context.body.write(data, size * nmemb);159 context.body.write(data, size * nmemb);
153 return size * nmemb;160 return size * nmemb;
154 });161 });
@@ -183,6 +190,11 @@
183190
184 void async_execute(const Request::Handler& handler)191 void async_execute(const Request::Handler& handler)
185 {192 {
193 async_execute(handler, [](const std::string&){});
194 }
195
196 void async_execute(const Request::Handler& handler, const StreamingRequest::DataHandler& dh)
197 {
186 if (atomic_state.load() != core::net::http::Request::State::ready)198 if (atomic_state.load() != core::net::http::Request::State::ready)
187 throw core::net::http::Request::Errors::AlreadyActive{CORE_FROM_HERE()};199 throw core::net::http::Request::Errors::AlreadyActive{CORE_FROM_HERE()};
188200
@@ -235,8 +247,10 @@
235 }247 }
236248
237 easy.on_write_data(249 easy.on_write_data(
238 [context](char* data, std::size_t size, std::size_t nmemb)250 [context, dh](char* data, std::size_t size, std::size_t nmemb)
239 {251 {
252 // Report out to the data handler prior to accumulating data.
253 dh(std::string{data, size * nmemb});
240 context->body.write(data, size * nmemb);254 context->body.write(data, size * nmemb);
241 return size * nmemb;255 return size * nmemb;
242 });256 });
243257
=== modified file 'tests/CMakeLists.txt'
--- tests/CMakeLists.txt 2014-05-22 11:50:55 +0000
+++ tests/CMakeLists.txt 2015-04-01 06:23:51 +0000
@@ -57,6 +57,11 @@
57)57)
5858
59add_executable(59add_executable(
60 http_streaming_client_test
61 http_streaming_client_test.cpp
62)
63
64add_executable(
60 http_client_load_test65 http_client_load_test
61 http_client_load_test.cpp66 http_client_load_test.cpp
62)67)
@@ -82,6 +87,16 @@
82)87)
8388
84target_link_libraries(89target_link_libraries(
90 http_streaming_client_test
91
92 net-cpp
93
94 ${GMOCK_BOTH_LIBRARIES}
95 ${JSON_CPP_LDFLAGS}
96 ${PROCESS_CPP_LDFLAGS}
97)
98
99target_link_libraries(
85 http_client_load_test100 http_client_load_test
86101
87 net-cpp102 net-cpp
@@ -93,4 +108,5 @@
93108
94add_test(header_test ${CMAKE_CURRENT_BINARY_DIR}/header_test)109add_test(header_test ${CMAKE_CURRENT_BINARY_DIR}/header_test)
95add_test(http_client_test ${CMAKE_CURRENT_BINARY_DIR}/http_client_test)110add_test(http_client_test ${CMAKE_CURRENT_BINARY_DIR}/http_client_test)
111add_test(http_streaming_client_test ${CMAKE_CURRENT_BINARY_DIR}/http_streaming_client_test)
96add_test(http_client_load_test ${CMAKE_CURRENT_BINARY_DIR}/http_client_load_test)112add_test(http_client_load_test ${CMAKE_CURRENT_BINARY_DIR}/http_client_load_test)
97113
=== added file 'tests/http_streaming_client_test.cpp'
--- tests/http_streaming_client_test.cpp 1970-01-01 00:00:00 +0000
+++ tests/http_streaming_client_test.cpp 2015-04-01 06:23:51 +0000
@@ -0,0 +1,508 @@
1/*
2 * Copyright © 2013 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License version 3,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Thomas Voß <thomas.voss@canonical.com>
17 */
18
19#include <core/net/error.h>
20#include <core/net/uri.h>
21#include <core/net/http/streaming_client.h>
22#include <core/net/http/content_type.h>
23#include <core/net/http/request.h>
24#include <core/net/http/response.h>
25
26#include "httpbin.h"
27
28#include <gmock/gmock.h>
29#include <gtest/gtest.h>
30
31#include <json/json.h>
32
33#include <future>
34#include <memory>
35
36namespace http = core::net::http;
37namespace json = Json;
38namespace net = core::net;
39
40namespace
41{
42class MockDataHandler : public std::enable_shared_from_this<MockDataHandler>
43{
44public:
45 // We are enabling shared from this, thus forcing creation of
46 // managed instances here.
47 static std::shared_ptr<MockDataHandler> create()
48 {
49 return std::shared_ptr<MockDataHandler>(new MockDataHandler);
50 }
51
52 MOCK_METHOD1(on_new_data, void(const std::string&));
53
54 http::StreamingRequest::DataHandler to_data_handler()
55 {
56 auto thiz = shared_from_this();
57 return [thiz](const std::string& s)
58 {
59 thiz->on_new_data(s);
60 };
61 }
62
63private:
64 MockDataHandler() = default;
65};
66
67auto default_progress_reporter = [](const http::Request::Progress& progress)
68{
69 if (progress.download.current > 0. && progress.download.total > 0.)
70 std::cout << "Download progress: " << progress.download.current / progress.download.total << std::endl;
71 if (progress.upload.current > 0. && progress.upload.total > 0.)
72 std::cout << "Upload progress: " << progress.upload.current / progress.upload.total << std::endl;
73
74 return http::Request::Progress::Next::continue_operation;
75};
76
77bool init()
78{
79 static httpbin::Instance instance;
80 return true;
81}
82
83static const bool is_initialized = init();
84}
85
86TEST(StreamingStreamingHttpClient, head_request_for_existing_resource_succeeds)
87{
88 using namespace ::testing;
89
90 // We obtain a default client instance, dispatching to the default implementation.
91 auto client = http::make_streaming_client();
92
93 // Url pointing to the resource we would like to access via http.
94 auto url = std::string(httpbin::host) + httpbin::resources::get();
95
96 // The client mostly acts as a factory for http requests.
97 auto request = client->streaming_head(http::Request::Configuration::from_uri_as_string(url));
98
99 // Our mocked data handler.
100 auto dh = MockDataHandler::create(); EXPECT_CALL(*dh, on_new_data(_)).Times(AtLeast(1));
101
102 // We finally execute the query synchronously and story the response.
103 auto response = request->execute(default_progress_reporter, dh->to_data_handler());
104
105 // We expect the query to complete successfully
106 EXPECT_EQ(core::net::http::Status::ok, response.status);
107}
108
109TEST(StreamingHttpClient, get_request_for_existing_resource_succeeds)
110{
111 using namespace ::testing;
112
113 // We obtain a default client instance, dispatching to the default implementation.
114 auto client = http::make_streaming_client();
115
116 // Url pointing to the resource we would like to access via http.
117 auto url = std::string(httpbin::host) + httpbin::resources::get();
118
119 // The client mostly acts as a factory for http requests.
120 auto request = client->streaming_get(http::Request::Configuration::from_uri_as_string(url));
121
122 // Our mocked data handler.
123 auto dh = MockDataHandler::create(); EXPECT_CALL(*dh, on_new_data(_)).Times(AtLeast(1));
124
125 // All endpoint data on httpbin.org is JSON encoded.
126 json::Value root;
127 json::Reader reader;
128
129 // We finally execute the query synchronously and story the response.
130 auto response = request->execute(default_progress_reporter, dh->to_data_handler());
131
132 // We expect the query to complete successfully
133 EXPECT_EQ(core::net::http::Status::ok, response.status);
134 // Parsing the body of the response as JSON should succeed.
135 EXPECT_TRUE(reader.parse(response.body, root));
136 // The url field of the payload should equal the original url we requested.
137 EXPECT_EQ(url, root["url"].asString());
138}
139
140TEST(StreamingHttpClient, get_request_with_custom_headers_for_existing_resource_succeeds)
141{
142 using namespace ::testing;
143
144 // We obtain a default client instance, dispatching to the default implementation.
145 auto client = http::make_streaming_client();
146
147 // Url pointing to the resource we would like to access via http.
148 auto url = std::string(httpbin::host) + httpbin::resources::headers();
149
150 // The client mostly acts as a factory for http requests.
151 auto configuration = http::Request::Configuration::from_uri_as_string(url);
152 configuration.header.set("Test1", "42");
153 configuration.header.set("Test2", "43");
154
155 auto request = client->streaming_get(configuration);
156
157 // Our mocked data handler.
158 auto dh = MockDataHandler::create(); EXPECT_CALL(*dh, on_new_data(_)).Times(AtLeast(1));
159
160 // All endpoint data on httpbin.org is JSON encoded.
161 json::Value root;
162 json::Reader reader;
163
164 // We finally execute the query synchronously and story the response.
165 auto response = request->execute(default_progress_reporter, dh->to_data_handler());
166
167 // We expect the query to complete successfully
168 EXPECT_EQ(core::net::http::Status::ok, response.status);
169
170 // Parsing the body of the response as JSON should succeed.
171 EXPECT_TRUE(reader.parse(response.body, root));
172
173 auto headers = root["headers"];
174
175 EXPECT_EQ("42", headers["Test1"].asString());
176 EXPECT_EQ("43", headers["Test2"].asString());
177}
178
179TEST(StreamingHttpClient, empty_header_values_are_handled_correctly)
180{
181 using namespace ::testing;
182
183 // We obtain a default client instance, dispatching to the default implementation.
184 auto client = http::make_streaming_client();
185
186 // Url pointing to the resource we would like to access via http.
187 auto url = std::string(httpbin::host) + httpbin::resources::headers();
188
189 // The client mostly acts as a factory for http requests.
190 auto configuration = http::Request::Configuration::from_uri_as_string(url);
191 configuration.header.set("Empty", std::string{});
192
193 auto request = client->streaming_get(configuration);
194
195 // Our mocked data handler.
196 auto dh = MockDataHandler::create(); EXPECT_CALL(*dh, on_new_data(_)).Times(AtLeast(1));
197
198 // All endpoint data on httpbin.org is JSON encoded.
199 json::Value root;
200 json::Reader reader;
201
202 // We finally execute the query synchronously and story the response.
203 auto response = request->execute(default_progress_reporter, dh->to_data_handler());
204
205 // We expect the query to complete successfully
206 EXPECT_EQ(core::net::http::Status::ok, response.status);
207
208 // Parsing the body of the response as JSON should succeed.
209 EXPECT_TRUE(reader.parse(response.body, root));
210
211 auto headers = root["headers"];
212 EXPECT_EQ(std::string{}, headers["Empty"].asString());
213}
214
215TEST(StreamingHttpClient, get_request_for_existing_resource_guarded_by_basic_auth_succeeds)
216{
217 using namespace ::testing;
218
219 // We obtain a default client instance, dispatching to the default implementation.
220 auto client = http::make_streaming_client();
221
222 // Url pointing to the resource we would like to access via http.
223 auto url = std::string(httpbin::host) + httpbin::resources::basic_auth();
224
225 // The client mostly acts as a factory for http requests.
226 auto configuration = http::Request::Configuration::from_uri_as_string(url);
227 configuration.authentication_handler.for_http = [](const std::string&)
228 {
229 return http::Request::Credentials{"user", "passwd"};
230 };
231 auto request = client->streaming_get(configuration);
232
233 // Our mocked data handler.
234 auto dh = MockDataHandler::create(); EXPECT_CALL(*dh, on_new_data(_)).Times(AtLeast(1));
235
236 // All endpoint data on httpbin.org is JSON encoded.
237 json::Value root;
238 json::Reader reader;
239
240 // We finally execute the query synchronously and story the response.
241 auto response = request->execute(default_progress_reporter, dh->to_data_handler());
242
243 // We expect the query to complete successfully
244 EXPECT_EQ(core::net::http::Status::ok, response.status);
245 // Parsing the body of the response as JSON should succeed.
246 EXPECT_TRUE(reader.parse(response.body, root));
247 // We expect authentication to work.
248 EXPECT_TRUE(root["authenticated"].asBool());
249 // With the correct user id
250 EXPECT_EQ("user", root["user"].asString());
251}
252
253// Digest auth is broken on httpbin.org. It even fails in the browser after the first successful access.
254TEST(StreamingHttpClient, DISABLED_get_request_for_existing_resource_guarded_by_digest_auth_succeeds)
255{
256 using namespace ::testing;
257
258 // We obtain a default client instance, dispatching to the default implementation.
259 auto client = http::make_streaming_client();
260
261 // Url pointing to the resource we would like to access via http.
262 auto url = std::string(httpbin::host) + httpbin::resources::digest_auth();
263
264 // The client mostly acts as a factory for http requests.
265 auto configuration = http::Request::Configuration::from_uri_as_string(url);
266 configuration.authentication_handler.for_http = [](const std::string&)
267 {
268 return http::Request::Credentials{"user", "passwd"};
269 };
270 auto request = client->streaming_get(configuration);
271
272 // Our mocked data handler.
273 auto dh = MockDataHandler::create(); EXPECT_CALL(*dh, on_new_data(_)).Times(AtLeast(1));
274
275 // All endpoint data on httpbin.org is JSON encoded.
276 json::Value root;
277 json::Reader reader;
278
279 // We finally execute the query synchronously and story the response.
280 auto response = request->execute(default_progress_reporter, dh->to_data_handler());
281
282 // We expect the query to complete successfully
283 EXPECT_EQ(core::net::http::Status::ok, response.status);
284 // Parsing the body of the response as JSON should succeed.
285 EXPECT_TRUE(reader.parse(response.body, root));
286 // We expect authentication to work.
287 EXPECT_TRUE(root["authenticated"].asBool());
288 // With the correct user id
289 EXPECT_EQ("user", root["user"].asString());
290}
291
292TEST(StreamingHttpClient, async_get_request_for_existing_resource_succeeds)
293{
294 using namespace ::testing;
295
296 // We obtain a default client instance, dispatching to the default implementation.
297 auto client = http::make_streaming_client();
298
299 // Execute the client
300 std::thread worker{[client]() { client->run(); }};
301
302 // Url pointing to the resource we would like to access via http.
303 auto url = std::string(httpbin::host) + httpbin::resources::get();
304
305 // The client mostly acts as a factory for http requests.
306 auto request = client->streaming_get(http::Request::Configuration::from_uri_as_string(url));
307
308 // Our mocked data handler.
309 auto dh = MockDataHandler::create(); EXPECT_CALL(*dh, on_new_data(_)).Times(AtLeast(1));
310
311 std::promise<core::net::http::Response> promise;
312 auto future = promise.get_future();
313
314 // We finally execute the query asynchronously.
315 request->async_execute(
316 http::Request::Handler()
317 .on_progress(default_progress_reporter)
318 .on_response([&](const core::net::http::Response& response)
319 {
320 promise.set_value(response);
321 })
322 .on_error([&](const core::net::Error& e)
323 {
324 promise.set_exception(std::make_exception_ptr(e));
325 }),
326 dh->to_data_handler());
327
328 auto response = future.get();
329
330 // All endpoint data on httpbin.org is JSON encoded.
331 json::Value root;
332 json::Reader reader;
333
334 // We expect the query to complete successfully
335 EXPECT_EQ(core::net::http::Status::ok, response.status);
336 // Parsing the body of the response as JSON should succeed.
337 EXPECT_TRUE(reader.parse(response.body, root));
338 // The url field of the payload should equal the original url we requested.
339 EXPECT_EQ(url, root["url"].asString());
340
341 client->stop();
342
343 // We shut down our worker thread
344 if (worker.joinable())
345 worker.join();
346}
347
348TEST(StreamingHttpClient, async_get_request_for_existing_resource_guarded_by_basic_authentication_succeeds)
349{
350 using namespace ::testing;
351
352 // We obtain a default client instance, dispatching to the default implementation.
353 auto client = http::make_streaming_client();
354
355 // Execute the client
356 std::thread worker{[client]() { client->run(); }};
357
358 // Url pointing to the resource we would like to access via http.
359 auto url = std::string(httpbin::host) + httpbin::resources::basic_auth();
360
361 // The client mostly acts as a factory for http requests.
362 auto configuration = http::Request::Configuration::from_uri_as_string(url);
363
364 configuration.authentication_handler.for_http = [](const std::string&)
365 {
366 return http::Request::Credentials{"user", "passwd"};
367 };
368
369 auto request = client->streaming_get(configuration);
370
371 // Our mocked data handler.
372 auto dh = MockDataHandler::create(); EXPECT_CALL(*dh, on_new_data(_)).Times(AtLeast(1));
373
374 // All endpoint data on httpbin.org is JSON encoded.
375 json::Value root;
376 json::Reader reader;
377
378 std::promise<core::net::http::Response> promise;
379 auto future = promise.get_future();
380
381 // We finally execute the query asynchronously.
382 request->async_execute(
383 http::Request::Handler()
384 .on_progress(default_progress_reporter)
385 .on_response([&](const core::net::http::Response& response)
386 {
387 promise.set_value(response);
388 client->stop();
389 })
390 .on_error([&](const core::net::Error& e)
391 {
392 promise.set_exception(std::make_exception_ptr(e));
393 client->stop();
394 }),
395 dh->to_data_handler());
396
397 // And wait here for the response to arrive.
398 auto response = future.get();
399
400 // We shut down our worker thread
401 if (worker.joinable())
402 worker.join();
403
404 // We expect the query to complete successfully
405 EXPECT_EQ(core::net::http::Status::ok, response.status);
406 // Parsing the body of the response as JSON should succeed.
407 EXPECT_TRUE(reader.parse(response.body, root));
408 // We expect authentication to work.
409 EXPECT_TRUE(root["authenticated"].asBool());
410 // With the correct user id
411 EXPECT_EQ("user", root["user"].asString());
412}
413
414TEST(StreamingHttpClient, post_request_for_existing_resource_succeeds)
415{
416 using namespace ::testing;
417
418 // We obtain a default client instance, dispatching to the default implementation.
419 auto client = http::make_streaming_client();
420
421 // Url pointing to the resource we would like to access via http.
422 auto url = std::string(httpbin::host) + httpbin::resources::post();
423
424 std::string payload = "{ 'test': 'test' }";
425
426 // The client mostly acts as a factory for http requests.
427 auto request = client->streaming_post(http::Request::Configuration::from_uri_as_string(url),
428 payload,
429 core::net::http::ContentType::json);
430
431 // Our mocked data handler.
432 auto dh = MockDataHandler::create(); EXPECT_CALL(*dh, on_new_data(_)).Times(AtLeast(1));
433
434 // All endpoint data on httpbin.org is JSON encoded.
435 json::Value root;
436 json::Reader reader;
437
438 // We finally execute the query synchronously and story the response.
439 auto response = request->execute(default_progress_reporter, dh->to_data_handler());
440
441 // We expect the query to complete successfully
442 EXPECT_EQ(core::net::http::Status::ok, response.status);
443 // Parsing the body of the response as JSON should succeed.
444 EXPECT_TRUE(reader.parse(response.body, root));
445 // The url field of the payload should equal the original url we requested.
446 EXPECT_EQ(payload, root["data"].asString());
447}
448
449TEST(StreamingHttpClient, post_form_request_for_existing_resource_succeeds)
450{
451 using namespace ::testing;
452
453 // We obtain a default client instance, dispatching to the default implementation.
454 auto client = http::make_streaming_client();
455
456 // Url pointing to the resource we would like to access via http.
457 auto url = std::string(httpbin::host) + httpbin::resources::post();
458
459 std::map<std::string, std::string> values
460 {
461 {"test", "test"}
462 };
463
464 // The client mostly acts as a factory for http requests.
465 auto request = client->streaming_post_form(http::Request::Configuration::from_uri_as_string(url),
466 values);
467
468 // Our mocked data handler.
469 auto dh = MockDataHandler::create(); EXPECT_CALL(*dh, on_new_data(_)).Times(AtLeast(1));
470
471 // We finally execute the query synchronously and store the response.
472 auto response = request->execute(default_progress_reporter, dh->to_data_handler());
473
474 // All endpoint data on httpbin.org is JSON encoded.
475 json::Value root;
476 json::Reader reader;
477
478 EXPECT_EQ(core::net::http::Status::ok, response.status);
479 EXPECT_TRUE(reader.parse(response.body, root));
480 EXPECT_EQ("test", root["form"]["test"].asString());
481}
482
483TEST(StreamingHttpClient, put_request_for_existing_resource_succeeds)
484{
485 using namespace ::testing;
486
487 auto client = http::make_streaming_client();
488 auto url = std::string(httpbin::host) + httpbin::resources::put();
489
490 const std::string value{"{ 'test': 'test' }"};
491 std::stringstream payload(value);
492
493 auto request = client->streaming_put(http::Request::Configuration::from_uri_as_string(url),
494 payload,
495 value.size());
496
497 // Our mocked data handler.
498 auto dh = MockDataHandler::create(); EXPECT_CALL(*dh, on_new_data(_)).Times(AtLeast(1));
499
500 json::Value root;
501 json::Reader reader;
502
503 auto response = request->execute(default_progress_reporter, dh->to_data_handler());
504
505 EXPECT_EQ(core::net::http::Status::ok, response.status);
506 EXPECT_TRUE(reader.parse(response.body, root));
507 EXPECT_EQ(payload.str(), root["data"].asString());
508}

Subscribers

People subscribed via source and target branches