Skip to content

Commit 2888eeb

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 2888eeb

File tree

4 files changed

+85
-75
lines changed

4 files changed

+85
-75
lines changed

Release/include/cpprest/http_client.h

Lines changed: 38 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,31 @@ 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+
/// <param name="buffer_request">True to turn on buffer, false otherwise.</param>
237+
/// <remarks>Please note there is a performance cost due to copying the request data.</remarks>
238+
void set_buffer_request(bool buffer_request)
239+
{
240+
m_buffer_request = buffer_request;
241+
}
242+
#endif
243+
#endif
244+
215245
/// <summary>
216246
/// Sets a callback to enable custom setting of winhttp options
217247
/// </summary>
@@ -231,6 +261,13 @@ class http_client_config
231261
#if !defined(__cplusplus_winrt)
232262
bool m_validate_certificates;
233263
#endif
264+
265+
#ifdef _MS_WINDOWS
266+
#if !defined(__cplusplus_winrt)
267+
bool m_buffer_request;
268+
#endif
269+
#endif
270+
234271
std::function<void(native_handle)> m_set_user_nativehandle_options;
235272

236273
utility::seconds m_timeout;
@@ -244,7 +281,6 @@ class http_client_config
244281
#endif // __cplusplus_winrt
245282
#endif // _MS_WINDOWS
246283

247-
248284
/// <summary>
249285
/// Invokes a user callback to allow for customization of the requst
250286
/// </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: 39 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -224,11 +224,16 @@ class winhttp_request_context : public request_context
224224

225225
size64_t m_remaining_to_write;
226226

227-
std::char_traits<uint8_t>::pos_type m_readbuf_pos;
227+
std::char_traits<uint8_t>::pos_type m_startingPosition;
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

@@ -257,11 +262,12 @@ class winhttp_request_context : public request_context
257262
: request_context(client, request),
258263
m_request_handle(nullptr),
259264
m_bodyType(no_body),
260-
m_readbuf_pos(0),
265+
m_startingPosition(std::char_traits<uint8_t>::eof()),
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,14 +667,16 @@ 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
{
668674
return;
669675
}
670676

671-
winhttp_context->m_readbuf_pos = rbuf.getpos(std::ios_base::in);
677+
// Record starting position incase request is challenged for authorization
678+
// and needs to seek back to where reading is started from.
679+
winhttp_context->m_startingPosition = rbuf.getpos(std::ios_base::in);
672680

673681
// If we find ourselves here, we either don't know how large the message
674682
// body is, or it is larger than our threshold.
@@ -708,19 +716,12 @@ class winhttp_client : public _http_client_communicator
708716
try
709717
{
710718
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)
719+
// If the read buffer for copying exists then write to it.
720+
if (p_request_context->m_readBufferCopy)
713721
{
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-
}
722+
// We have raw memory here writing to a memory stream so it is safe to wait
723+
// since it will always be non-blocking.
724+
p_request_context->m_readBufferCopy->putn(&p_request_context->m_body_data.get()[http::details::chunked_encoding::data_offset], bytes_read).wait();
724725
}
725726
}
726727
catch (...)
@@ -737,18 +738,11 @@ class winhttp_client : public _http_client_communicator
737738
if (bytes_read == 0)
738739
{
739740
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)
741+
if (p_request_context->m_readBufferCopy)
742742
{
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-
}
743+
// Move the saved buffer into the read buffer, which now supports seeking.
744+
p_request_context->m_readStream = concurrency::streams::container_stream<std::vector<uint8_t>>::open_istream(std::move(p_request_context->m_readBufferCopy->collection()));
745+
p_request_context->m_readBufferCopy.reset();
752746
}
753747
}
754748

@@ -764,24 +758,11 @@ class winhttp_client : public _http_client_communicator
764758
}
765759
};
766760

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
761+
if (!_check_streambuf(p_request_context, p_request_context->_get_readbuffer(), _XPLATSTR("Input stream is not open")))
777762
{
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);
763+
return;
784764
}
765+
p_request_context->_get_readbuffer().getn(&p_request_context->m_body_data.get()[http::details::chunked_encoding::data_offset], chunk_size).then(after_read);
785766
}
786767

787768
static void _multiple_segment_write_data(_In_ winhttp_request_context * p_request_context)
@@ -887,10 +868,6 @@ class winhttp_client : public _http_client_communicator
887868
_ASSERTE(response.status_code() == status_codes::Unauthorized || response.status_code() == status_codes::ProxyAuthRequired
888869
|| error == ERROR_WINHTTP_RESEND_REQUEST);
889870

890-
// If the application set a stream for the request body, we can only resend if the input stream supports
891-
// seeking and we are also successful in seeking to the position we started at when the original request
892-
// was sent.
893-
894871
bool got_credentials = false;
895872
BOOL results;
896873
DWORD dwSupportedSchemes;
@@ -900,22 +877,16 @@ class winhttp_client : public _http_client_communicator
900877
string_t username;
901878
string_t password;
902879

903-
if (request.body())
880+
// Check if the saved read position is valid
881+
auto rdpos = p_request_context->m_startingPosition;
882+
if (rdpos != static_cast<std::char_traits<uint8_t>::pos_type>(std::char_traits<uint8_t>::eof()))
904883
{
905-
// Valid request stream => msg has a body that needs to be resend
884+
auto rbuf = p_request_context->_get_readbuffer();
906885

907-
auto rdpos = p_request_context->m_readbuf_pos;
908-
909-
// Check if the saved read position is valid
910-
if (rdpos != (std::char_traits<uint8_t>::pos_type) - 1)
886+
// Try to seek back to the saved read position
887+
if (rbuf.seekpos(rdpos, std::ios::ios_base::in) != rdpos)
911888
{
912-
auto rbuf = p_request_context->_get_readbuffer();
913-
914-
if (rbuf.is_open())
915-
{
916-
// Try to seek back to the saved read position
917-
rbuf.seekpos(rdpos, std::ios::ios_base::in);
918-
}
889+
return false;
919890
}
920891
}
921892

@@ -1333,7 +1304,7 @@ class winhttp_client : public _http_client_communicator
13331304
http_network_handler::http_network_handler(uri base_uri, http_client_config client_config) :
13341305
m_http_client_impl(std::make_shared<details::winhttp_client>(std::move(base_uri), std::move(client_config)))
13351306
{
1336-
}
1307+
}
13371308

13381309
pplx::task<http_response> http_network_handler::propagate(http_request request)
13391310
{

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)