Skip to content

Commit

Permalink
Refactor: context has a transparent usage and "works" on nested
Browse files Browse the repository at this point in the history
environments.

* Refactor ClassHandlerManager::handleRequest to use less nesting
  and easier-to-follow logic
* Removes context information from HttpServerRequest
* Refactor ClassHandlerManager::handleRequest to show a namespaced
  url to its handlers, then they won't be able to know whether
  they're running "inside a context". Great for nesting. The other
  option would be a stack of contexts instead.
  • Loading branch information
vinipsmaker committed Feb 26, 2014
1 parent 375f7a4 commit 76648c6
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 79 deletions.
35 changes: 20 additions & 15 deletions examples/cmake/sample_plugin/plugins/Test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,10 @@ QJsonObject Test::test1(Tufao::HttpServerRequest & request, Tufao::HttpServerRes

void Test::test2(Tufao::HttpServerRequest & request, Tufao::HttpServerResponse & response, QString chapter, int pageNumber)
{
if(!request.context().isEmpty()){
qDebug() << "The context for this plugin is: " << request.context();
}
qDebug() << "I don't know if my view from the environment is the same,"
" because namespaces transparently override what I can see.";
qDebug() << "I think I'm handling this url: " << request.url();

response.writeHead(Tufao::HttpResponseStatus::OK);
response.headers().replace("Content-Type", "text/html; charset=utf-8");
QString html("<html><head><title>Plugin Test</title></head><body>"
Expand All @@ -89,9 +90,10 @@ void Test::test2(Tufao::HttpServerRequest & request, Tufao::HttpServerResponse &

void Test::test3(Tufao::HttpServerRequest & request, Tufao::HttpServerResponse & response)
{
if(!request.context().isEmpty()){
qDebug() << "The context for this plugin is: " << request.context();
}
qDebug() << "I don't know if my view from the environment is the same,"
" because namespaces transparently override what I can see.";
qDebug() << "I think I'm handling this url: " << request.url();

response.writeHead(Tufao::HttpResponseStatus::OK);
response.headers().replace("Content-Type", "text/html; charset=utf-8");
response << "<html><head><title>Plugin Test</title></head><body>"
Expand All @@ -101,9 +103,10 @@ void Test::test3(Tufao::HttpServerRequest & request, Tufao::HttpServerResponse &

void Test::test4(Tufao::HttpServerRequest & request, Tufao::HttpServerResponse & response, QString chapter)
{
if(!request.context().isEmpty()){
qDebug() << "The context for this plugin is: " << request.context();
}
qDebug() << "I don't know if my view from the environment is the same,"
" because namespaces transparently override what I can see.";
qDebug() << "I think I'm handling this url: " << request.url();

response.writeHead(Tufao::HttpResponseStatus::OK);
response.headers().replace("Content-Type", "text/html; charset=utf-8");
QString html("<html><head><title>Plugin Test</title></head><body>"
Expand All @@ -115,9 +118,10 @@ void Test::test4(Tufao::HttpServerRequest & request, Tufao::HttpServerResponse &

void Test::test5(Tufao::HttpServerRequest & request, Tufao::HttpServerResponse & response, QString chapter, QString pageNumber)
{
if(!request.context().isEmpty()){
qDebug() << "The context for this plugin is: " << request.context();
}
qDebug() << "I don't know if my view from the environment is the same,"
" because namespaces transparently override what I can see.";
qDebug() << "I think I'm handling this url: " << request.url();

qDebug() << QString("==> %1,%2").arg(chapter).arg(pageNumber);
response.writeHead(Tufao::HttpResponseStatus::OK);
response.headers().replace("Content-Type", "text/html; charset=utf-8");
Expand All @@ -130,9 +134,10 @@ void Test::test5(Tufao::HttpServerRequest & request, Tufao::HttpServerResponse &

void Test::test6(Tufao::HttpServerRequest & request, Tufao::HttpServerResponse & response, int chapter, int pageNumber)
{
if(!request.context().isEmpty()){
qDebug() << "The context for this plugin is: " << request.context();
}
qDebug() << "I don't know if my view from the environment is the same,"
" because namespaces transparently override what I can see.";
qDebug() << "I think I'm handling this url: " << request.url();

qDebug() << QString("==> %1,%2").arg(chapter).arg(pageNumber);
response.writeHead(Tufao::HttpResponseStatus::OK);
response.headers().replace("Content-Type", "text/html; charset=utf-8");
Expand Down
2 changes: 1 addition & 1 deletion examples/cmake/sample_plugin/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ int main(int argc, char *argv[])
QCoreApplication qtApplication(argc, argv);
Tufao::HttpServer server;

Tufao::ClassHandlerManager classHandler("tufao.Test/1.0", "context");
Tufao::ClassHandlerManager classHandler("tufao.Test/1.0", "/context");

Tufao::HttpServerRequestRouter router{
{QRegularExpression{"^/$"}, Tufao::UrlRewriterHandler::handler(QUrl("/index.html"))},
Expand Down
116 changes: 76 additions & 40 deletions src/classhandlermanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -363,55 +363,91 @@ int ClassHandlerManager::selectMethod(const QString className,
/* ************************************************************************** */
bool ClassHandlerManager::handleRequest(Tufao::HttpServerRequest & request, Tufao::HttpServerResponse & response)
{
bool wasHandled = false;
QStringList pathComponents = request.url().path().split("/", QString::SkipEmptyParts);
/* Apply context and resume request dispatching or abort if request has
different context */
const QUrl originalUrl = request.url();
const QString originalPath = originalUrl.path();

if (!priv->context.isEmpty()) {
if (!(originalPath.size()
&& originalPath.startsWith(priv->context)
&& originalPath[priv->context.size()] == '/')) {
return false;
}
}

const QString namespacedPath = [&originalPath,this]() {
return originalPath.mid(priv->context.size());
}();

/* The user MUST NOT view the original url. This design ease the
implementation of nested handlers.
request.setUrl(namespacedUrl) MUST be called before pass the request to
the user and the previous url (originalUrl) MUST be restored if the
dispatch failed and we'll return the request to HttpServerRequestRouter.
*/
const QUrl namespacedUrl = [&originalUrl,&namespacedPath]() {
QUrl ret = originalUrl;
ret.setPath(namespacedPath);
return ret;
}();

QStringList pathComponents = namespacedPath.split("/", QString::SkipEmptyParts);


//Is the request for our context?
bool useContext = !priv->context.isEmpty();
// There must be at least two path components (class & method), and 3 if a context is specified.
int minimumPathComponents = useContext ? 3 : 2;
// There must be at least two path components (class & method), and 3 if a
// context is specified.
const int minimumPathComponents = 2;
if (pathComponents.length() < minimumPathComponents) {
qWarning() << "Request was dispatched to handler, but too few path components found. The path components are"
qWarning() << "Request was dispatched to handler, but too few path"
" components found. The path components are"
<< pathComponents;
} else if(pathComponents.length() > minimumPathComponents + 16) {
// We also can not have too many arguments; 16 is max, as that is 8 argumetns plus request & response
qWarning() << "Request was dispatched to handler, but too many path components found. The path components are"
return false;
}

if (pathComponents.length() > minimumPathComponents + 16) {
// We also can not have too many arguments; 16 is max, as that is 8
// argumetns plus request & response
qWarning() << "Request was dispatched to handler, but too many path"
" components found. The path components are"
<< pathComponents;
} else {
if(!useContext || priv->context == pathComponents[0]) {
// Add the context to the request
request.setContext(priv->context);

int pathIndex = useContext ? 1 : 0;
QString className = pathComponents[pathIndex++];
QString methodName = pathComponents[pathIndex++];
// We need to have an even number of path components left
if((pathComponents.length() - pathIndex) % 2 == 0) {
// See if we have a class handler with a matching method
if (priv->handlers.contains(className)
&& (priv->handlers[className]->methodNames
.contains(methodName))) {
// Convert the remaining path components into an argument hash
QHash<QString, QString> arguments;
while(pathIndex < pathComponents.length()){
arguments[pathComponents[pathIndex]] = pathComponents[pathIndex + 1];
pathIndex += 2;
}
wasHandled = processRequest(request, response, className, methodName, arguments);
} else {
if (priv->handlers.contains(className)) {
qWarning() << "The class" << className << "has no method named" << methodName;
}
}
} else {
qWarning() << "Can not dispath as an odd number of parameter components were supplied.";
}
return false;
}

int pathIndex = 0;
QString className = pathComponents[pathIndex++];
QString methodName = pathComponents[pathIndex++];
// We need to have an even number of path components left
if ((pathComponents.length() - pathIndex) % 2 != 0) {
qWarning() << "Can not dispath as an odd number of parameter components"
" were supplied.";
return false;
}

// See if we have a class handler with a matching method
if (!(priv->handlers.contains(className)
&& priv->handlers[className]->methodNames.contains(methodName))) {
if (priv->handlers.contains(className)) {
qWarning() << "The class" << className << "has no method named"
<< methodName;
}
}

return wasHandled;
// Convert the remaining path components into an argument hash
QHash<QString, QString> arguments;
while(pathIndex < pathComponents.length()){
arguments[pathComponents[pathIndex]] = pathComponents[pathIndex + 1];
pathIndex += 2;
}

request.setUrl(namespacedUrl);
if (!processRequest(request, response, className, methodName, arguments)) {
request.setUrl(originalUrl);
return false;
}

return true;
}

} // namespace Tufao
10 changes: 0 additions & 10 deletions src/httpserverrequest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -392,14 +392,4 @@ int HttpServerRequest::Priv::on_message_complete(http_parser *parser)
return 0;
}

void HttpServerRequest::setContext(const QString context)
{
priv->context = context;
}

QString HttpServerRequest::context(void) const
{
return priv->context;
}

} // namespace Tufao
12 changes: 0 additions & 12 deletions src/httpserverrequest.h
Original file line number Diff line number Diff line change
Expand Up @@ -261,18 +261,6 @@ class TUFAO_EXPORT HttpServerRequest : public QObject
*/
void setCustomData(const QVariant &data);

/*!
* \brief context the setter for the request context (first path component).
* \param context the context.
* \since 1.1
*/
void setContext(const QString context);
/*!
* \brief context the accessor for the context.
* \return the contet of the request.
*/
QString context(void) const;

signals:
/*!
This signal is emitted when most of the data about the request is
Expand Down
1 change: 0 additions & 1 deletion src/priv/httpserverrequest.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ struct HttpServerRequest::Priv

int timeout;
QTimer timer;
QString context;

static const http_parser_settings httpSettingsInstance;
};
Expand Down

0 comments on commit 76648c6

Please sign in to comment.