0
0
Fork 0
mirror of https://github.com/matrix-construct/construct synced 2024-06-29 15:28:20 +02:00

ircd: Minor reorg client/resource relationship.

This commit is contained in:
Jason Volk 2017-12-24 00:34:11 -07:00
parent ae8a8e736a
commit c20a2927d0
3 changed files with 170 additions and 168 deletions

View file

@ -25,6 +25,8 @@
namespace ircd
{
struct resource;
bool handle_request(client &client, parse::capstan &pc);
}
struct ircd::resource

View file

@ -201,6 +201,64 @@ ircd::write(client &client,
return base;
}
void
ircd::async_recv_next(std::shared_ptr<client> client)
{
async_recv_next(std::move(client), milliseconds(-1));
}
///
/// This function is the basis for the client's request loop. We still use
/// an asynchronous pattern until there is activity on the socket (a request)
/// in which case the switch to synchronous mode is made by jumping into an
/// ircd::context drawn from the request pool. When the request is finished,
/// the client exits back into asynchronous mode until the next request is
/// received and rinse and repeat.
//
/// This sequence exists to avoid any possible c10k-style limitation imposed by
/// dedicating a context and its stack space to the lifetime of a connection.
/// This is similar to the thread-per-request pattern before async was in vogue.
//
/// Developers: Pay close attention to the comments to know exactly where you
/// are and what you can do at any given point in this sequence.
///
void
ircd::async_recv_next(std::shared_ptr<client> client,
const milliseconds &timeout)
{
assert(bool(client));
assert(bool(client->sock));
// This call returns immediately so we no longer block the current context and
// its stack while waiting for activity on idle connections between requests.
auto &sock(*client->sock);
sock(timeout, [client(std::move(client)), timeout](const net::error_code &ec)
noexcept
{
// Right here this handler is executing on the main stack (not in any
// ircd::context).
if(!handle_ec(*client, ec))
return;
// This call returns immediately because we can never block the main stack outside
// of the ircd::context system. The context the closure ends up getting is the next
// available from the request pool, which may not be available immediately so this
// handler might be queued for some time after this call returns.
request([ec, client(std::move(client)), timeout]
{
// Right here this handler is executing on an ircd::context with its own
// stack dedicated to the lifetime of this request. If client::main()
// returns true, we bring the client back into async mode to wait for
// the next request.
if(client->main())
async_recv_next(std::move(client), timeout);
else
disconnect(*client, net::dc::SSL_NOTIFY_YIELD);
});
});
}
//
// client
//
@ -237,12 +295,6 @@ catch(const std::exception &e)
return;
}
namespace ircd
{
void handle_request(client &client, parse::capstan &pc, const http::request::head &head);
bool handle_request(client &client, parse::capstan &pc);
}
/// Main client loop.
///
/// This function parses requests off the socket in a loop until there are no
@ -276,6 +328,17 @@ noexcept try
parse::buffer pb{buffer};
parse::capstan pc{pb, read_closure(*this)}; do
{
request_timer = ircd::timer{};
const socket::scope_timeout timeout
{
*sock, request_timeout, [client(shared_from(*this))]
(const net::error_code &ec)
{
if(!ec)
disconnect(*client, net::dc::SSL_NOTIFY_YIELD);
}
};
if(!handle_request(*this, pc))
return false;
@ -358,106 +421,6 @@ catch(const std::exception &e)
#endif
}
/// Handle a single request within the client main() loop.
///
/// This function returns false if the main() loop should exit
/// and thus disconnect the client. It should return true in most
/// cases even for lightly erroneous requests that won't affect
/// the next requests on the tape.
///
/// This function is timed. The timeout will prevent a client from
/// sending a partial request and leave us waiting for the rest.
/// As of right now this timeout extends to our handling of the
/// request too.
bool
ircd::handle_request(client &client,
parse::capstan &pc)
try
{
client.request_timer = ircd::timer{};
const socket::scope_timeout timeout
{
*client.sock, request_timeout, [client(shared_from(client))]
(const net::error_code &ec)
{
if(!ec)
disconnect(*client, net::dc::SSL_NOTIFY_YIELD);
}
};
bool ret{true};
http::request
{
pc, nullptr, [&client, &pc, &ret]
(const auto &head)
{
handle_request(client, pc, head);
ret = !iequals(head.connection, "close"s);
}
};
return ret;
}
catch(const http::error &e)
{
log::debug("client[%s] HTTP %s in %ld$us %s",
string(remote(client)),
e.what(),
client.request_timer.at<microseconds>().count(),
e.content);
http::response
{
e.code, e.content, write_closure(client)
};
switch(e.code)
{
case http::BAD_REQUEST:
case http::REQUEST_TIMEOUT:
return false;
case http::INTERNAL_SERVER_ERROR:
throw;
default:
return true;
}
}
catch(const std::exception &e)
{
log::error("client[%s]: in %ld$us: %s",
string(remote(client)),
client.request_timer.at<microseconds>().count(),
e.what());
http::response
{
http::INTERNAL_SERVER_ERROR, e.what(), write_closure(client)
};
throw;
}
void
ircd::handle_request(client &client,
parse::capstan &pc,
const http::request::head &head)
{
log::debug("client[%s] HTTP %s `%s' (content-length: %zu)",
string(remote(client)),
head.method,
head.path,
head.content_length);
auto &resource
{
ircd::resource::find(head.path)
};
resource(client, pc, head);
}
std::shared_ptr<ircd::client>
ircd::add_client(std::shared_ptr<socket> s)
{
@ -516,64 +479,6 @@ ircd::disconnect(client &client,
disconnect(*client.sock, type);
}
void
ircd::async_recv_next(std::shared_ptr<client> client)
{
async_recv_next(std::move(client), milliseconds(-1));
}
///
/// This function is the basis for the client's request loop. We still use
/// an asynchronous pattern until there is activity on the socket (a request)
/// in which case the switch to synchronous mode is made by jumping into an
/// ircd::context drawn from the request pool. When the request is finished,
/// the client exits back into asynchronous mode until the next request is
/// received and rinse and repeat.
//
/// This sequence exists to avoid any possible c10k-style limitation imposed by
/// dedicating a context and its stack space to the lifetime of a connection.
/// This is similar to the thread-per-request pattern before async was in vogue.
//
/// Developers: Pay close attention to the comments to know exactly where you
/// are and what you can do at any given point in this sequence.
///
void
ircd::async_recv_next(std::shared_ptr<client> client,
const milliseconds &timeout)
{
assert(bool(client));
assert(bool(client->sock));
// This call returns immediately so we no longer block the current context and
// its stack while waiting for activity on idle connections between requests.
auto &sock(*client->sock);
sock(timeout, [client(std::move(client)), timeout](const net::error_code &ec)
noexcept
{
// Right here this handler is executing on the main stack (not in any
// ircd::context).
if(!handle_ec(*client, ec))
return;
// This call returns immediately because we can never block the main stack outside
// of the ircd::context system. The context the closure ends up getting is the next
// available from the request pool, which may not be available immediately so this
// handler might be queued for some time after this call returns.
request([ec, client(std::move(client)), timeout]
{
// Right here this handler is executing on an ircd::context with its own
// stack dedicated to the lifetime of this request. If client::main()
// returns true, we bring the client back into async mode to wait for
// the next request.
if(client->main())
async_recv_next(std::move(client), timeout);
else
disconnect(*client, net::dc::SSL_NOTIFY_YIELD);
});
});
}
namespace ircd
{
static bool handle_ec_success(client &);

View file

@ -21,14 +21,104 @@
#include <ircd/m/m.h>
namespace ircd {
namespace ircd
{
void handle_request(client &client, parse::capstan &pc, const http::request::head &head);
}
IRCD_INIT_PRIORITY(STD_CONTAINER)
decltype(resource::resources)
resource::resources
decltype(ircd::resource::resources)
ircd::resource::resources
{};
} // namespace ircd
/// Handle a single request within the client main() loop.
///
/// This function returns false if the main() loop should exit
/// and thus disconnect the client. It should return true in most
/// cases even for lightly erroneous requests that won't affect
/// the next requests on the tape.
///
/// This function is timed. The timeout will prevent a client from
/// sending a partial request and leave us waiting for the rest.
/// As of right now this timeout extends to our handling of the
/// request too.
bool
ircd::handle_request(client &client,
parse::capstan &pc)
try
{
bool ret{true};
http::request
{
pc, nullptr, [&client, &pc, &ret]
(const auto &head)
{
handle_request(client, pc, head);
ret = !iequals(head.connection, "close"s);
}
};
return ret;
}
catch(const http::error &e)
{
log::debug("client[%s] HTTP %s in %ld$us %s",
string(remote(client)),
e.what(),
client.request_timer.at<microseconds>().count(),
e.content);
resource::response
{
client, e.content, "text/html; charset=utf8", e.code
};
switch(e.code)
{
case http::BAD_REQUEST:
case http::REQUEST_TIMEOUT:
return false;
case http::INTERNAL_SERVER_ERROR:
throw;
default:
return true;
}
}
catch(const std::exception &e)
{
log::error("client[%s]: in %ld$us: %s",
string(remote(client)),
client.request_timer.at<microseconds>().count(),
e.what());
resource::response
{
client, e.what(), {}, http::INTERNAL_SERVER_ERROR
};
throw;
}
void
ircd::handle_request(client &client,
parse::capstan &pc,
const http::request::head &head)
{
log::debug("client[%s] HTTP %s `%s' (content-length: %zu)",
string(remote(client)),
head.method,
head.path,
head.content_length);
auto &resource
{
ircd::resource::find(head.path)
};
resource(client, pc, head);
}
ircd::resource &
ircd::resource::find(string_view path)
@ -70,6 +160,10 @@ ircd::resource::find(string_view path)
return *it->second;
}
//
// resource
//
ircd::resource::resource(const string_view &path)
:resource
{
@ -278,6 +372,7 @@ try
}
catch(const std::out_of_range &e)
{
//TODO: This will have to respond with an Accept header listing methods.
throw http::error
{
http::METHOD_NOT_ALLOWED