Skip to content

Commit 29ec303

Browse files
committed
Implement request_storage_base.
This commit implements an untested (but contains a building test) request_storage_base that aims to hide the details of a segmented data structure used to hold the request data. The intention is that the storage base will be used if large amounts of data will be stored in an efficient manner in memory. This design also allows the request implementation to ignore the fact that there's an underlying storage implementation and just use a provided body chunk writer. This allows people to add a function that pulls the data from whatever source and adds it to the request only when necessary. This is an evolutionary step forward moving from the inside out. The intention is that we're able to build higher level logic to be able to not rely on using the storage base in special circumstances.
1 parent 4578dfd commit 29ec303

File tree

6 files changed

+184
-29
lines changed

6 files changed

+184
-29
lines changed

boost/network/protocol/http/request/request.hpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ struct request : request_base {
5353
virtual void set_method(std::string const & method);
5454
virtual void set_status(std::string const & status);
5555
virtual void set_status_message(std::string const & status_message);
56-
virtual void set_body_stream(shared_ptr<body_stream> stream);
56+
virtual void set_body_writer(function<void(char*, size_t)> writer);
5757
virtual void set_uri(std::string const &uri);
5858
virtual void set_uri(network::uri::uri const &uri);
5959

@@ -63,7 +63,8 @@ struct request : request_base {
6363
virtual void get_method(std::string & method) const;
6464
virtual void get_status(std::string & status) const;
6565
virtual void get_status_message(std::string & status_message) const;
66-
virtual void get_body_stream(body_stream & output_stream) const;
66+
virtual void get_body(function<void(char*, size_t)> chunk_reader) const;
67+
virtual void get_body(std::string const & body) const;
6768

6869
virtual ~request();
6970
private:

boost/network/protocol/http/request/request.ipp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ struct request_pimpl {
2121
};
2222

2323
request::~request() {
24-
// Do nothing here.
24+
// do nothing here
2525
}
2626

2727
request::request(std::string const & url)

boost/network/protocol/http/request/request_base.hpp

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
#include <boost/network/message/message_base.hpp>
1515
#include <boost/network/uri/uri.hpp>
16-
#include <boost/iostreams/categories.hpp>
1716

1817
namespace boost { namespace network { namespace http {
1918

@@ -22,29 +21,27 @@ struct body_source : iostreams::source {
2221
virtual ~body_source();
2322
};
2423

24+
struct request_storage_base_pimpl;
25+
2526
struct request_storage_base {
26-
typedef iostreams::stream<body_source> body_stream;
2727
protected:
28-
// TODO Implement a segmented storage base which efficiently supports
29-
// efficient memory usage that makes sense for HTTP payload. The idea is
30-
// to expose the internal (segmented) storage to the client implementation
31-
// so that raw buffers of formatted HTTP request data (which may or may not
32-
// support delegated streams from user input -- i.e. for the body contents)
33-
// can be efficiently sent out to the wire. This also implies that all
34-
// thread-safety guarantees are handled by the storage base as well.
35-
request_storage_base(size_t hint = BOOST_NETWORK_BUFFER_CHUNK);
36-
virtual void set_status_line(std::string const &status_line);
37-
virtual void append_header(std::string const &name, std::string const &value);
28+
request_storage_base(size_t chunk_size = BOOST_NETWORK_BUFFER_CHUNK);
29+
virtual void append(char const *data, size_t size);
30+
virtual size_t read(char *destination, size_t offset, size_t size);
31+
virtual void flatten(std::string &destination);
32+
virtual void clear();
3833
virtual ~request_storage_base();
34+
35+
private:
36+
request_storage_base_pimpl *pimpl_;
3937
};
4038

4139
struct request_base : message_base, request_storage_base {
42-
using request_storage_base::body_stream;
4340
// Setters
4441
virtual void set_method(std::string const & method) = 0;
4542
virtual void set_status(std::string const & status) = 0;
4643
virtual void set_status_message(std::string const & status_message) = 0;
47-
virtual void set_body_stream(shared_ptr<body_stream> stream) = 0;
44+
virtual void set_body_writer(function<void(char*, size_t)> writer) = 0;
4845
virtual void set_uri(std::string const &uri) = 0;
4946
virtual void set_uri(network::uri::uri const &uri) = 0;
5047

@@ -54,7 +51,8 @@ struct request_base : message_base, request_storage_base {
5451
virtual void get_method(std::string & method) const = 0;
5552
virtual void get_status(std::string & status) const = 0;
5653
virtual void get_status_message(std::string & status_message) const = 0;
57-
virtual void get_body_stream(body_stream & output_stream) const = 0;
54+
virtual void get_body(function<void(char*, size_t)> chunk_reader) const = 0;
55+
virtual void get_body(std::string const & body) const = 0;
5856
virtual ~request_base() = 0;
5957
};
6058

boost/network/protocol/http/request/request_base.ipp

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,124 @@
88
// http://www.boost.org/LICENSE_1_0.txt)
99

1010
#include <boost/network/protocol/http/request/request_base.hpp>
11+
#include <boost/thread/mutex.hpp>
12+
#include <cstring>
1113

1214
namespace boost { namespace network { namespace http {
1315

1416
request_base::~request_base() {
1517
// default implementation, only required for linking.
1618
}
1719

20+
struct request_storage_base_pimpl {
21+
request_storage_base_pimpl(size_t chunk_size);
22+
void append(char const *data, size_t size);
23+
size_t read(char *destination, size_t offset, size_t size);
24+
void flatten(std::string &destination);
25+
void clear();
26+
request_storage_base_pimpl clone() const;
27+
~request_storage_base_pimpl();
28+
29+
private:
30+
size_t chunk_size_;
31+
typedef std::vector<std::pair<char *, size_t> > chunks_vector;
32+
chunks_vector chunks_;
33+
mutex chunk_mutex;
34+
};
35+
36+
request_storage_base::request_storage_base(size_t chunk_size)
37+
: pimpl_(new (std::nothrow) request_storage_base_pimpl(chunk_size))
38+
{}
39+
1840
request_storage_base::~request_storage_base() {
19-
// default implementation, only required for linking.
41+
delete pimpl_;
42+
}
43+
44+
void request_storage_base::append(char const *data, size_t size) {
45+
pimpl_->append(data, size);
46+
}
47+
48+
size_t request_storage_base::read(char *destination, size_t offset, size_t size) {
49+
return pimpl_->read(destination, offset, size);
50+
}
51+
52+
void request_storage_base::flatten(std::string &destination) {
53+
pimpl_->flatten(destination);
54+
}
55+
56+
void request_storage_base::clear() {
57+
pimpl_->clear();
58+
}
59+
60+
request_storage_base_pimpl::request_storage_base_pimpl(size_t chunk_size)
61+
: chunk_size_(chunk_size)
62+
, chunks_()
63+
{
64+
// do nothing here.
65+
}
66+
67+
void request_storage_base_pimpl::append(char const *data, size_t size) {
68+
if (chunks_.empty()) {
69+
chunks_.push_back(std::make_pair(
70+
new (std::nothrow) char[chunk_size_], 0));
71+
}
72+
std::pair<char *, size_t> *chunk = &chunks_.back();
73+
size_t remaining = chunk_size_ - chunk->second;
74+
while (remaining < size) {
75+
std::memcpy(chunk->first + chunk->second, data, size - remaining);
76+
chunk->second += size - remaining;
77+
data += remaining;
78+
size -= remaining;
79+
chunks_.push_back(std::make_pair(
80+
new (std::nothrow) char[chunk_size_], 0));
81+
chunk = &chunks_.back();
82+
remaining = chunk_size_ - chunk->second;
83+
}
84+
if (size > 0) {
85+
std::memcpy(chunk->first + chunk->second, data, size);
86+
chunk->second += size;
87+
}
2088
}
21-
89+
90+
size_t request_storage_base_pimpl::read(char *destination, size_t offset, size_t size) {
91+
if (chunks_.empty()) return 0;
92+
// First we find which chunk we're going to read from using the provided
93+
// offset and some arithmetic to determine the correct one.
94+
size_t chunk_index = offset / chunk_size_;
95+
96+
// Then we start copying up to size data either until we've reached the end
97+
// or we're
98+
size_t chunks_count = chunks_.size();
99+
size_t read_count = 0;
100+
while (size > 0 && chunk_index < chunks_.size()) {
101+
size_t bytes_to_read = std::min(chunks_[chunk_index].second, size);
102+
std::memcpy(destination + read_count, chunks_[chunk_index].first, bytes_to_read);
103+
read_count += bytes_to_read;
104+
size -= bytes_to_read;
105+
++chunk_index;
106+
}
107+
return read_count;
108+
}
109+
110+
void request_storage_base_pimpl::flatten(std::string &destination) {
111+
chunks_vector::const_iterator chunk_iterator = chunks_.begin();
112+
for (; chunk_iterator != chunks_.end(); ++chunk_iterator) {
113+
destination.append(chunk_iterator->first, chunk_iterator->second);
114+
}
115+
}
116+
117+
void request_storage_base_pimpl::clear() {
118+
chunks_vector::const_iterator chunk_iterator = chunks_.begin();
119+
for (; chunk_iterator != chunks_.end(); ++chunk_iterator) {
120+
delete [] chunk_iterator->first;
121+
}
122+
chunks_vector().swap(chunks_);
123+
}
124+
125+
request_storage_base_pimpl::~request_storage_base_pimpl() {
126+
clear();
127+
}
128+
22129
} /* http */
23130

24131
} /* network */

libs/network/test/http/CMakeLists.txt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ if (OPENSSL_FOUND)
1212
endif()
1313

1414
if (Boost_FOUND)
15+
# These are the internal (simple) tests.
16+
add_executable(cpp-netlib-http-request_base_test request_base_test.cpp)
17+
target_link_libraries(cpp-netlib-http-request_base_test
18+
${Boost_LIBRARIES}
19+
cppnetlib-message
20+
cppnetlib-http-message)
21+
add_test(cpp-netlib-http-request_base_test
22+
${CPP-NETLIB_BINARY_DIR}/tests/cpp-netlib-http-request_base_test)
23+
1524
set ( TESTS
1625
client_constructor_test
1726
client_get_test
@@ -25,15 +34,6 @@ if (Boost_FOUND)
2534
PROPERTIES COMPILE_FLAGS "-Wall")
2635
endif()
2736
add_executable(cpp-netlib-http-${test} ${test}.cpp)
28-
add_dependencies(cpp-netlib-http-${test}
29-
cppnetlib-constants
30-
cppnetlib-uri
31-
cppnetlib-message
32-
cppnetlib-message-wrappers
33-
cppnetlib-message-directives
34-
cppnetlib-http-message
35-
cppnetlib-http-client
36-
cppnetlib-http-client-connections)
3737
target_link_libraries(cpp-netlib-http-${test}
3838
${Boost_LIBRARIES}
3939
${CMAKE_THREAD_LIBS_INIT}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright 2012 Dean Michael Berris <dberris@google.com>.
2+
// Copyright 2012 Google, Inc.
3+
// Distributed under the Boost Software License, Version 1.0.
4+
// (See accompanying file LICENSE_1_0.txt or copy at
5+
// http://www.boost.org/LICENSE_1_0.txt)
6+
7+
#define BOOST_TEST_MODULE HTTP Request Storage Base Test
8+
#include <boost/network/protocol/http/request/request_base.hpp>
9+
#include <boost/test/unit_test.hpp>
10+
11+
namespace http = boost::network::http;
12+
13+
// In this test we make sure that the implementation of the default request
14+
// storage base actually doesn't have bugs and works as advertised. Although we
15+
// don't intend to expose this interface to users, we use the test as a sanity
16+
// check on the internals of the implementation.
17+
struct request_test : http::request_storage_base {
18+
typedef http::request_storage_base base_type;
19+
20+
// Expose the protected functions so that we can test them.
21+
using base_type::append;
22+
using base_type::read;
23+
using base_type::flatten;
24+
using base_type::clear;
25+
26+
request_test(size_t chunk_size)
27+
: base_type(chunk_size)
28+
{}
29+
30+
~request_test() {
31+
// do nothing here.
32+
}
33+
};
34+
35+
BOOST_AUTO_TEST_CASE(request_storage_flow) {
36+
// Use a few byte chunks just to make it manageable.
37+
request_test simple(64);
38+
static char data[] =
39+
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus vitae ante sed nunc dapibus convallis in at neque. Vestibulum sed congue nunc. Sed tempus lorem non dui ultrices porttitor porta ligula venenatis. Sed a orci gravida tellus condimentum laoreet. Vivamus pulvinar, tortor eu adipiscing tempus, dolor urna tincidunt enim, id pretium eros ante quis dui. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In hac habitasse platea dictumst. Maecenas mattis metus.";
40+
simple.append(data, sizeof(data));
41+
char output[sizeof(data)];
42+
size_t bytes_read = simple.read(output, 0, sizeof(data));
43+
BOOST_CHECK_EQUAL(bytes_read, sizeof(data));
44+
std::string flattened;
45+
simple.flatten(flattened);
46+
BOOST_CHECK_EQUAL(flattened, std::string(output, sizeof(data)));
47+
BOOST_CHECK_EQUAL(std::string(data, sizeof(data)), std::string(output, sizeof(data)));
48+
simple.clear();
49+
}

0 commit comments

Comments
 (0)