Skip to content

Commit 47e506b

Browse files
committed
Adding option to http_client_config to specify whether or not the request body should be buffered.
This is useful for situations where authentication challenges might occur or autologon is used. This is to help an external customer and is only avaliable on Windows desktop right now.
1 parent f277d66 commit 47e506b

File tree

4 files changed

+70
-55
lines changed

4 files changed

+70
-55
lines changed

Release/include/cpprest/http_client.h

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,12 @@ class http_client_config
9696
#if !defined(__cplusplus_winrt)
9797
, m_validate_certificates(true)
9898
#endif
99-
,m_set_user_nativehandle_options([](native_handle)->void{})
99+
, m_set_user_nativehandle_options([](native_handle)->void{})
100+
#ifdef _MS_WINDOWS
101+
#if !defined(__cplusplus_winrt)
102+
, m_buffer_request(false)
103+
#endif
104+
#endif
100105
{
101106
}
102107

@@ -212,6 +217,30 @@ class http_client_config
212217
}
213218
#endif
214219

220+
#ifdef _MS_WINDOWS
221+
#if !defined(__cplusplus_winrt)
222+
/// <summary>
223+
/// Checks if request data buffering is turned on, the default is off.
224+
/// </summary>
225+
/// <returns>True if buffering is enabled, false otherwise</returns>
226+
bool buffer_request() const
227+
{
228+
return m_buffer_request;
229+
}
230+
231+
/// <summary>
232+
/// Sets the request buffering property.
233+
/// If true, in cases where the request body/stream doesn't support seeking the request data will be buffered.
234+
/// This can help in situations where an authentication challenge might be expected.
235+
/// </summary>
236+
/// <remarks>Please note there is a performance cost due to copying the request data.</remarks>
237+
void set_buffer_request(bool buffer_request)
238+
{
239+
m_buffer_request = buffer_request;
240+
}
241+
#endif
242+
#endif
243+
215244
/// <summary>
216245
/// Sets a callback to enable custom setting of winhttp options
217246
/// </summary>
@@ -231,6 +260,13 @@ class http_client_config
231260
#if !defined(__cplusplus_winrt)
232261
bool m_validate_certificates;
233262
#endif
263+
264+
#ifdef _MS_WINDOWS
265+
#if !defined(__cplusplus_winrt)
266+
bool m_buffer_request;
267+
#endif
268+
#endif
269+
234270
std::function<void(native_handle)> m_set_user_nativehandle_options;
235271

236272
utility::seconds m_timeout;
@@ -244,7 +280,6 @@ class http_client_config
244280
#endif // __cplusplus_winrt
245281
#endif // _MS_WINDOWS
246282

247-
248283
/// <summary>
249284
/// Invokes a user callback to allow for customization of the requst
250285
/// </summary>

Release/include/cpprest/http_client_impl.h

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ class request_context
187187
finish();
188188
}
189189

190-
concurrency::streams::streambuf<uint8_t> _get_readbuffer()
190+
virtual concurrency::streams::streambuf<uint8_t> _get_readbuffer()
191191
{
192192
auto instream = m_request.body();
193193

@@ -200,7 +200,6 @@ class request_context
200200
auto outstream = m_response._get_impl()->outstream();
201201

202202
_ASSERTE((bool)outstream);
203-
204203
return outstream.streambuf();
205204
}
206205

@@ -256,7 +255,7 @@ class _http_client_communicator
256255
// Destructor to clean up any held resources.
257256
virtual ~_http_client_communicator() {}
258257

259-
// Asychronously send a HTTP request and process the response.
258+
// Asynchronously send a HTTP request and process the response.
260259
void async_send_request(std::shared_ptr<request_context> request)
261260
{
262261
if(m_client_config.guarantee_order())
@@ -388,7 +387,7 @@ class _http_client_communicator
388387
}
389388
}
390389

391-
// Queue used to guarantee ordering of requests, when appliable.
390+
// Queue used to guarantee ordering of requests, when applicable.
392391
std::queue<std::shared_ptr<request_context>> m_requests_queue;
393392
int m_scheduled;
394393
};
@@ -424,7 +423,7 @@ class http_network_handler : public http_pipeline_stage
424423
// Helper function to check to make sure the uri is valid.
425424
void verify_uri(const uri &uri)
426425
{
427-
// Somethings like proper URI schema are verified by the URI class.
426+
// Some things like proper URI schema are verified by the URI class.
428427
// We only need to check certain things specific to HTTP.
429428
if (uri.scheme() != _XPLATSTR("http") && uri.scheme() != _XPLATSTR("https"))
430429
{

Release/src/http/client/http_win7.cpp

Lines changed: 25 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -226,9 +226,14 @@ class winhttp_request_context : public request_context
226226

227227
std::char_traits<uint8_t>::pos_type m_readbuf_pos;
228228

229-
// The read-write buffer used to save the outgoing body data,
230-
// if the authentication challenges failed, resend the body data from this buffer.
231-
std::shared_ptr<streams::producer_consumer_buffer<uint8_t>> m_rwbuf_copy;
229+
// If the user specified that to guarantee data buffering of request data, in case of challenged authentication requests, etc...
230+
// Then if the request stream buffer doesn't support seeking we need to copy the body chunks as it is sent.
231+
concurrency::streams::istream m_readStream;
232+
std::unique_ptr<concurrency::streams::container_buffer<std::vector<uint8_t>>> m_readBufferCopy;
233+
virtual concurrency::streams::streambuf<uint8_t> _get_readbuffer()
234+
{
235+
return m_readStream.streambuf();
236+
}
232237

233238
memory_holder m_body_data;
234239

@@ -261,7 +266,8 @@ class winhttp_request_context : public request_context
261266
m_body_data(),
262267
m_remaining_to_write(0),
263268
m_proxy_authentication_tried(false),
264-
m_server_authentication_tried(false)
269+
m_server_authentication_tried(false),
270+
m_readStream(request.body())
265271
{
266272
}
267273
};
@@ -614,10 +620,10 @@ class winhttp_client : public _http_client_communicator
614620
return;
615621
}
616622

617-
// Initialize the local producer_consumer buffer.
618-
if (winhttp_context->m_bodyType == transfer_encoding_chunked && !winhttp_context->_get_readbuffer().can_seek() && has_credentials(winhttp_context))
623+
// Only need to cache the request body if user specified and the request stream doesn't support seeking.
624+
if (winhttp_context->m_bodyType != no_body && client_config().buffer_request() && !winhttp_context->_get_readbuffer().can_seek())
619625
{
620-
winhttp_context->m_rwbuf_copy = std::make_shared<streams::producer_consumer_buffer<uint8_t>>();
626+
winhttp_context->m_readBufferCopy = ::utility::details::make_unique<::concurrency::streams::container_buffer<std::vector<uint8_t>>>();
621627
}
622628

623629
_start_request_send(winhttp_context, content_length);
@@ -661,7 +667,7 @@ class winhttp_client : public _http_client_communicator
661667
return;
662668
}
663669

664-
// Capure the current read position of the stream.
670+
// Capture the current read position of the stream.
665671
auto rbuf = winhttp_context->_get_readbuffer();
666672
if ( !_check_streambuf(winhttp_context, rbuf, _XPLATSTR("Input stream is not open")) )
667673
{
@@ -708,19 +714,10 @@ class winhttp_client : public _http_client_communicator
708714
try
709715
{
710716
bytes_read = op.get();
711-
if (!p_request_context->_get_readbuffer().can_seek() && has_credentials(p_request_context)
712-
&& !p_request_context->m_server_authentication_tried && !p_request_context->m_proxy_authentication_tried)
717+
// If the read buffer for copying exists then write to it.
718+
if (p_request_context->m_readBufferCopy)
713719
{
714-
// If the user buffer is not seekable, the credentials are provided and the authentication challenges are not tried,
715-
// copy the user buffer to a local producer_consumer buffer for resending if authentication failed.
716-
try
717-
{
718-
p_request_context->m_rwbuf_copy->putn(&p_request_context->m_body_data.get()[http::details::chunked_encoding::data_offset], bytes_read).wait();
719-
}
720-
catch (...)
721-
{
722-
p_request_context->report_exception(std::current_exception());
723-
}
720+
p_request_context->m_readBufferCopy->putn(&p_request_context->m_body_data.get()[http::details::chunked_encoding::data_offset], bytes_read).wait();
724721
}
725722
}
726723
catch (...)
@@ -737,18 +734,11 @@ class winhttp_client : public _http_client_communicator
737734
if (bytes_read == 0)
738735
{
739736
p_request_context->m_bodyType = no_body;
740-
if (!p_request_context->_get_readbuffer().can_seek() && has_credentials(p_request_context)
741-
&& !p_request_context->m_server_authentication_tried && !p_request_context->m_proxy_authentication_tried)
737+
if (p_request_context->m_readBufferCopy)
742738
{
743-
try
744-
{
745-
p_request_context->m_rwbuf_copy->close(std::ios_base::out).wait();
746-
747-
}
748-
catch (...)
749-
{
750-
p_request_context->report_exception(std::current_exception());
751-
}
739+
// Move the saved buffer into the read buffer.
740+
p_request_context->m_readStream = concurrency::streams::container_stream<std::vector<uint8_t>>::open_istream(std::move(p_request_context->m_readBufferCopy->collection()));
741+
p_request_context->m_readBufferCopy.reset();
752742
}
753743
}
754744

@@ -764,24 +754,11 @@ class winhttp_client : public _http_client_communicator
764754
}
765755
};
766756

767-
if ((!p_request_context->m_server_authentication_tried && !p_request_context->m_proxy_authentication_tried) || p_request_context->_get_readbuffer().can_seek())
768-
{
769-
if (!_check_streambuf(p_request_context, p_request_context->_get_readbuffer(), _XPLATSTR("Input stream is not open")))
770-
{
771-
return;
772-
}
773-
774-
p_request_context->_get_readbuffer().getn(&p_request_context->m_body_data.get()[http::details::chunked_encoding::data_offset], chunk_size).then(after_read);
775-
}
776-
else
757+
if (!_check_streambuf(p_request_context, p_request_context->_get_readbuffer(), _XPLATSTR("Input stream is not open")))
777758
{
778-
if (!_check_streambuf(p_request_context, *p_request_context->m_rwbuf_copy, _XPLATSTR("Locally copied input stream is not open")))
779-
{
780-
return;
781-
}
782-
// Resending the request after authentication failed, we need to read the body data from the copied buffer.
783-
p_request_context->m_rwbuf_copy->getn(&p_request_context->m_body_data.get()[http::details::chunked_encoding::data_offset], chunk_size).then(after_read);
759+
return;
784760
}
761+
p_request_context->_get_readbuffer().getn(&p_request_context->m_body_data.get()[http::details::chunked_encoding::data_offset], chunk_size).then(after_read);
785762
}
786763

787764
static void _multiple_segment_write_data(_In_ winhttp_request_context * p_request_context)
@@ -1333,7 +1310,7 @@ class winhttp_client : public _http_client_communicator
13331310
http_network_handler::http_network_handler(uri base_uri, http_client_config client_config) :
13341311
m_http_client_impl(std::make_shared<details::winhttp_client>(std::move(base_uri), std::move(client_config)))
13351312
{
1336-
}
1313+
}
13371314

13381315
pplx::task<http_response> http_network_handler::propagate(http_request request)
13391316
{

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,9 @@ TEST_FIXTURE(uri_address, auth_producer_comsumer_buffer)
490490
msg.set_body(buf.create_istream());
491491

492492
http_client_config config;
493+
VERIFY_IS_FALSE(config.buffer_request());
494+
config.set_buffer_request(true);
495+
VERIFY_IS_TRUE(config.buffer_request());
493496
config.set_credentials(credentials(U("USERNAME"), U("PASSWORD")));
494497

495498
http_client client(m_uri, config);
@@ -553,6 +556,7 @@ TEST_FIXTURE(uri_address, auth_producer_comsumer_buffer_fail)
553556
msg.set_body(buf.create_istream());
554557

555558
http_client_config config;
559+
config.set_buffer_request(true);
556560
config.set_credentials(credentials(U("USERNAME"), U("PASSWORD")));
557561

558562
http_client client(m_uri, config);

0 commit comments

Comments
 (0)