Skip to content

Commit 0292c7c

Browse files
tboundy-trimblegigaplex
authored andcommitted
Refactor the response handling in http_server_httpsys to fix some issues where error replies didn't get sent, and cyclic references weren't cleaned up
1 parent 1e487cc commit 0292c7c

File tree

3 files changed

+79
-41
lines changed

3 files changed

+79
-41
lines changed

Release/include/cpprest/details/http_server_httpsys.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ struct windows_request_context : http::details::_http_server_context
9999
// Dispatch request to the provided http_listener.
100100
void dispatch_request_to_listener(_In_ web::http::experimental::listener::details::http_listener_impl *pListener);
101101

102+
// Initialise the response task callbacks
103+
void do_response(bool bad_request);
104+
102105
// Read in a portion of the request body.
103106
void read_request_body_chunk();
104107

Release/src/http/listener/http_listener_msg.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ pplx::task<void> details::_http_request::_reply_impl(http_response response)
4747
{
4848
// Add a task-based continuation so no exceptions thrown from the task go 'unobserved'.
4949
response._set_server_context(std::move(m_server_context));
50-
response_completed = experimental::details::http_server_api::server_api()->respond(response);
50+
response_completed = server_api->respond(response);
5151
response_completed.then([](pplx::task<void> t)
5252
{
5353
try { t.wait(); } catch(...) {}

Release/src/http/listener/http_server_httpsys.cpp

Lines changed: 75 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,12 @@ windows_request_context::~windows_request_context()
494494
// the lock then setting of the event has completed.
495495
std::unique_lock<std::mutex> lock(m_responseCompletedLock);
496496

497+
// Add a task-based continuation so no exceptions thrown from the task go 'unobserved'.
498+
pplx::create_task(m_response_completed).then([](pplx::task<void> t)
499+
{
500+
try { t.wait(); } catch(...) {}
501+
});
502+
497503
auto *pServer = static_cast<http_windows_server *>(http_server_api::server_api());
498504
if(--pServer->m_numOutstandingRequests == 0)
499505
{
@@ -530,6 +536,7 @@ void windows_request_context::async_process_request(HTTP_REQUEST_ID request_id,
530536
{
531537
CancelThreadpoolIo(pServer->m_threadpool_io);
532538
m_msg.reply(status_codes::InternalError);
539+
do_response(true);
533540
}
534541
}
535542

@@ -541,6 +548,7 @@ void windows_request_context::read_headers_io_completion(DWORD error_code, DWORD
541548
if(error_code != NO_ERROR)
542549
{
543550
m_msg.reply(status_codes::InternalError);
551+
do_response(true);
544552
}
545553
else
546554
{
@@ -574,6 +582,7 @@ void windows_request_context::read_headers_io_completion(DWORD error_code, DWORD
574582
else
575583
{
576584
m_msg.reply(status_codes::BadRequest, badRequestMsg);
585+
do_response(true);
577586
}
578587
}
579588
}
@@ -614,6 +623,7 @@ void windows_request_context::read_request_body_chunk()
614623
else
615624
{
616625
m_msg._get_impl()->_complete(0, std::make_exception_ptr(http_exception(error_code)));
626+
m_msg._reply_if_not_already(status_codes::InternalError);
617627
}
618628
}
619629
}
@@ -639,6 +649,7 @@ void windows_request_context::read_body_io_completion(DWORD error_code, DWORD by
639649
{
640650
request_body_buf.commit(0);
641651
m_msg._get_impl()->_complete(0, std::make_exception_ptr(http_exception(error_code)));
652+
m_msg._reply_if_not_already(status_codes::InternalError);
642653
}
643654
}
644655

@@ -649,46 +660,7 @@ void windows_request_context::dispatch_request_to_listener(_In_ web::http::exper
649660
// Save http_request copy to dispatch to user's handler in case content_ready() completes before.
650661
http_request request = m_msg;
651662

652-
// Wait until the content download finished before replying.
653-
request.content_ready().then([=](pplx::task<http_request> requestBody)
654-
{
655-
// If an exception occurred while processing the body then there is no reason
656-
// to even try sending the response, just re-surface the same exception.
657-
try
658-
{
659-
requestBody.wait();
660-
}
661-
catch (...)
662-
{
663-
m_msg = http_request();
664-
cancel_request(std::current_exception());
665-
return;
666-
}
667-
668-
// At this point the user entirely controls the lifetime of the http_request.
669-
m_msg = http_request();
670-
671-
request.get_response().then([this](pplx::task<http::http_response> responseTask)
672-
{
673-
// Don't let an exception from sending the response bring down the server.
674-
try
675-
{
676-
m_response = responseTask.get();
677-
}
678-
catch (const pplx::task_canceled &)
679-
{
680-
// This means the user didn't respond to the request, allowing the
681-
// http_request instance to be destroyed. There is nothing to do then
682-
// so don't send a response.
683-
return;
684-
}
685-
catch (...)
686-
{
687-
m_response = http::http_response(status_codes::InternalError);
688-
}
689-
async_process_response();
690-
});
691-
});
663+
do_response(false);
692664

693665
// Look up the lock for the http_listener.
694666
auto *pServer = static_cast<http_windows_server *>(http_server_api::server_api());
@@ -721,6 +693,69 @@ void windows_request_context::dispatch_request_to_listener(_In_ web::http::exper
721693
}
722694
}
723695

696+
void windows_request_context::do_response(bool bad_request)
697+
{
698+
// Use a proxy event so we're not causing a circular reference between the http_request and the response task
699+
pplx::task_completion_event<void> proxy_content_ready;
700+
701+
auto content_ready_task = m_msg.content_ready();
702+
auto get_response_task = m_msg.get_response();
703+
704+
content_ready_task.then([this, proxy_content_ready](pplx::task<http_request> requestBody)
705+
{
706+
// If an exception occurred while processing the body then there is no reason
707+
// to even try sending the response, just re-surface the same exception.
708+
try
709+
{
710+
requestBody.wait();
711+
}
712+
catch (...)
713+
{
714+
m_msg = http_request();
715+
proxy_content_ready.set_exception(std::current_exception());
716+
cancel_request(std::current_exception());
717+
return;
718+
}
719+
720+
// At this point the user entirely controls the lifetime of the http_request.
721+
m_msg = http_request();
722+
proxy_content_ready.set();
723+
});
724+
725+
get_response_task.then([this, bad_request, proxy_content_ready](pplx::task<http::http_response> responseTask)
726+
{
727+
// Don't let an exception from sending the response bring down the server.
728+
try
729+
{
730+
m_response = responseTask.get();
731+
}
732+
catch (const pplx::task_canceled &)
733+
{
734+
// This means the user didn't respond to the request, allowing the
735+
// http_request instance to be destroyed. There is nothing to do then
736+
// so don't send a response.
737+
return;
738+
}
739+
catch (...)
740+
{
741+
m_response = http::http_response(status_codes::InternalError);
742+
}
743+
744+
if (bad_request)
745+
{
746+
async_process_response();
747+
}
748+
else
749+
{
750+
// Wait until the content download finished before replying.
751+
pplx::create_task(proxy_content_ready).then([this](pplx::task<void> requestBody)
752+
{
753+
async_process_response();
754+
});
755+
}
756+
});
757+
}
758+
724759
void windows_request_context::async_process_response()
725760
{
726761
auto *pServer = static_cast<http_windows_server *>(http_server_api::server_api());

0 commit comments

Comments
 (0)