Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial attempt at win32 http_listener response refactor #16

Merged
merged 8 commits into from
Dec 7, 2015
3 changes: 3 additions & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ Cisco Systems
Gergely Lukacsy (glukacsy)

thomasschaub

Trimble
Tim Boundy (gigaplex)
8 changes: 8 additions & 0 deletions Release/include/cpprest/details/http_server_httpsys.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@ struct windows_request_context : http::details::_http_server_context
// Dispatch request to the provided http_listener.
void dispatch_request_to_listener(_In_ web::http::experimental::listener::details::http_listener_impl *pListener);

enum ShouldWaitForBody
{
WaitForBody,
DontWaitForBody
};
// Initialise the response task callbacks. If the body has been requested, we should wait for it to avoid race conditions.
void init_response_callbacks(ShouldWaitForBody shouldWait);

// Read in a portion of the request body.
void read_request_body_chunk();

Expand Down
2 changes: 1 addition & 1 deletion Release/src/http/listener/http_listener_msg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ pplx::task<void> details::_http_request::_reply_impl(http_response response)
{
// Add a task-based continuation so no exceptions thrown from the task go 'unobserved'.
response._set_server_context(std::move(m_server_context));
response_completed = experimental::details::http_server_api::server_api()->respond(response);
response_completed = server_api->respond(response);
response_completed.then([](pplx::task<void> t)
{
try { t.wait(); } catch(...) {}
Expand Down
147 changes: 100 additions & 47 deletions Release/src/http/listener/http_server_httpsys.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -465,13 +465,7 @@ void http_windows_server::receive_requests()
pplx::task<void> http_windows_server::respond(http::http_response response)
{
windows_request_context * p_context = static_cast<windows_request_context *>(response._get_server_context());
return pplx::create_task(p_context->m_response_completed).then([p_context](::pplx::task<void> t)
{
// After response is sent, break circular reference between http_response and the request context.
// Otherwise http_listener::close() can hang.
p_context->m_response._get_impl()->_set_server_context(nullptr);
t.get();
});
return pplx::create_task(p_context->m_response_completed);
}

windows_request_context::windows_request_context()
Expand All @@ -494,6 +488,12 @@ windows_request_context::~windows_request_context()
// the lock then setting of the event has completed.
std::unique_lock<std::mutex> lock(m_responseCompletedLock);

// Add a task-based continuation so no exceptions thrown from the task go 'unobserved'.
pplx::create_task(m_response_completed).then([](pplx::task<void> t)
{
try { t.wait(); } catch(...) {}
});

auto *pServer = static_cast<http_windows_server *>(http_server_api::server_api());
if(--pServer->m_numOutstandingRequests == 0)
{
Expand Down Expand Up @@ -530,6 +530,7 @@ void windows_request_context::async_process_request(HTTP_REQUEST_ID request_id,
{
CancelThreadpoolIo(pServer->m_threadpool_io);
m_msg.reply(status_codes::InternalError);
init_response_callbacks(DontWaitForBody);
}
}

Expand All @@ -541,6 +542,7 @@ void windows_request_context::read_headers_io_completion(DWORD error_code, DWORD
if(error_code != NO_ERROR)
{
m_msg.reply(status_codes::InternalError);
init_response_callbacks(DontWaitForBody);
}
else
{
Expand Down Expand Up @@ -574,6 +576,9 @@ void windows_request_context::read_headers_io_completion(DWORD error_code, DWORD
else
{
m_msg.reply(status_codes::BadRequest, badRequestMsg);

// Even though we have a bad request, we should wait for the body otherwise we risk racing over m_overlapped
init_response_callbacks(WaitForBody);
}
}
}
Expand Down Expand Up @@ -649,46 +654,7 @@ void windows_request_context::dispatch_request_to_listener(_In_ web::http::exper
// Save http_request copy to dispatch to user's handler in case content_ready() completes before.
http_request request = m_msg;

// Wait until the content download finished before replying.
request.content_ready().then([=](pplx::task<http_request> requestBody)
{
// If an exception occurred while processing the body then there is no reason
// to even try sending the response, just re-surface the same exception.
try
{
requestBody.wait();
}
catch (...)
{
m_msg = http_request();
cancel_request(std::current_exception());
return;
}

// At this point the user entirely controls the lifetime of the http_request.
m_msg = http_request();

request.get_response().then([this](pplx::task<http::http_response> responseTask)
{
// Don't let an exception from sending the response bring down the server.
try
{
m_response = responseTask.get();
}
catch (const pplx::task_canceled &)
{
// This means the user didn't respond to the request, allowing the
// http_request instance to be destroyed. There is nothing to do then
// so don't send a response.
return;
}
catch (...)
{
m_response = http::http_response(status_codes::InternalError);
}
async_process_response();
});
});
init_response_callbacks(WaitForBody);

// Look up the lock for the http_listener.
auto *pServer = static_cast<http_windows_server *>(http_server_api::server_api());
Expand Down Expand Up @@ -721,6 +687,93 @@ void windows_request_context::dispatch_request_to_listener(_In_ web::http::exper
}
}

void windows_request_context::init_response_callbacks(ShouldWaitForBody shouldWait)
{
// Use a proxy event so we're not causing a circular reference between the http_request and the response task
pplx::task_completion_event<void> proxy_content_ready;

auto content_ready_task = m_msg.content_ready();
auto get_response_task = m_msg.get_response();

content_ready_task.then([this, proxy_content_ready](pplx::task<http_request> requestBody)
{
// If an exception occurred while processing the body then there is no reason
// to even try sending the response, just re-surface the same exception.
try
{
requestBody.wait();
}
catch (...)
{
// Copy the request reference in case it's the last
http_request request = m_msg;
m_msg = http_request();
proxy_content_ready.set_exception(std::current_exception());
cancel_request(std::current_exception());
return;
}

// At this point the user entirely controls the lifetime of the http_request.
m_msg = http_request();
proxy_content_ready.set();
});

get_response_task.then([this, proxy_content_ready](pplx::task<http::http_response> responseTask)
{
// Don't let an exception from sending the response bring down the server.
try
{
m_response = responseTask.get();
}
catch (const pplx::task_canceled &)
{
// This means the user didn't respond to the request, allowing the
// http_request instance to be destroyed. There is nothing to do then
// so don't send a response.
// Avoid unobserved exception handler
pplx::create_task(proxy_content_ready).then([this](pplx::task<void> t)
{
try { t.wait(); } catch(...) {}
});
return;
}
catch (...)
{
// Should never get here, if we do there's a chance that a circular reference will cause leaks,
// or worse, undefined behaviour as we don't know who owns 'this' anymore
_ASSERTE(false);
m_response = http::http_response(status_codes::InternalError);
}

pplx::create_task(m_response_completed).then([this](::pplx::task<void> t)
{
// After response is sent, break circular reference between http_response and the request context.
// Otherwise http_listener::close() can hang.
m_response._get_impl()->_set_server_context(nullptr);
});

// Wait until the content download finished before replying because m_overlapped is reused,
// and we don't want to delete 'this' if the body is still downloading
pplx::create_task(proxy_content_ready).then([this](pplx::task<void> t)
{
try
{
t.wait();
async_process_response();
}
catch (...)
{
}
}).wait();
});

if (shouldWait == DontWaitForBody)
{
// Fake a body completion so the content_ready() task doesn't keep the http_request alive forever
m_msg._get_impl()->_complete(0);
}
}

void windows_request_context::async_process_response()
{
auto *pServer = static_cast<http_windows_server *>(http_server_api::server_api());
Expand Down