I've spent today implementing a new type of Handler.
I like Tufao so far, although I've only been playing with it a couple of days. But I found the handlers very lacking, and not intuitive.
Not intuitive, because lots of links are hard to find for documentation, and who likes regular expressions?
Lacking, because other than the built in Handlers for rewriting, serving static content, and 404, they don't do much. [...]
So I wrote a very automatic, fined-grained, small, dispatch system, based on ReST. I hope I didn't miss something somewhere, where this is already possible. :) I am using the Qt meta object system.
This new handler is called a ClassHandler. These are Qt plugins, and can be either statically linked, or dynamically linked. There is a ClassHandlerManager that not only handles the subclasses of ClassHandler, but is also the Hander that is used by the HttpServerRequestRouter. It is wired up like all other Handlers.
ClassHandlerManager classHandler;
classHandler.init();
Tufao::HttpServerRequestRouter router{
{QRegularExpression{"^/$"}, Tufao::UrlRewriterHandler::handler(QUrl("/index.html"))},
{QRegularExpression{""}, Tufao::HttpFileServer::handler("static")},
{QRegularExpression{""}, classHandler},
{QRegularExpression{""}, Tufao::NotFoundHandler::handler()}
};
When ClassHandlerManager::handleRequest() is called, it determines if a class has been registered that can handle the request. Subclasses of ClassHandler are registered automatically, either at compile time for static plugins, or at startup for dynamic ones. No acton is required on the developers side, other than implementing the Qt interface, ClassHandlerInterface. That's it.
In the sublcasses of ClassHandler, slots are used. A slot will automatically be called based on the URL. The subclass can have an arbitrary number of slots.
For example, if the URL is:
/server/settings
then the ClassHandlerManager will automatically see if there is a class Server (technically, this is the QObject::name, but, can be the class name), and if there is, will call the settings method on the instance.
/server/temp/20
would call the temp(20) method on the server instance, as long as the class defined a slot temp(int) method. If not, the request is not handled, and the request is passed on down the router, like any other request, and any other handler. [...]
If there is more general interest in this, I'll fork and integrate my code, and create a pull request. I'm sure a better C++ coder than me could make improvements; it's not perfect, and I'm still tweeking it, as I use it myself.
Thoughts?
-- Vinícius dos Santos Oliveira https://about.me/vinipsmaker |
Em Sex, 2013-12-06 às 12:09 -0800, tre...@silverfieldstech.com escreveu:Well, so can I assume the problem with the documentation are difficult to find links? It seems reasonable, given you're *only* fast when you already know the name of the class you want to use.
I've spent today implementing a new type of Handler.
I like Tufao so far, although I've only been playing with it a couple of days. But I found the handlers very lacking, and not intuitive.
Not intuitive, because lots of links are hard to find for documentation, and who likes regular expressions?
I could create some pages implementing the concept of knowledge map (same as Khan Academy). What do you think? The only problem is *how* should we categorize/create the map. Feedback appreciated.
Lacking, because other than the built in Handlers for rewriting, serving static content, and 404, they don't do much. [...]
So I wrote a very automatic, fined-grained, small, dispatch system, based on ReST. I hope I didn't miss something somewhere, where this is already possible. :) I am using the Qt meta object system.
This new handler is called a ClassHandler. These are Qt plugins, and can be either statically linked, or dynamically linked. There is a ClassHandlerManager that not only handles the subclasses of ClassHandler, but is also the Hander that is used by the HttpServerRequestRouter. It is wired up like all other Handlers.
ClassHandlerManager classHandler;
classHandler.init();
Tufao::HttpServerRequestRouter router{
{QRegularExpression{"^/$"}, Tufao::UrlRewriterHandler::handler(QUrl("/index.html"))},
{QRegularExpression{""}, Tufao::HttpFileServer::handler("static")},
{QRegularExpression{""}, classHandler},
{QRegularExpression{""}, Tufao::NotFoundHandler::handler()}
};
My thoughts:
- Why not do the introspection on *any* QObject subclass? Why only ClassHandler subclass?
- What/why is the init method?
- Can we add a prefix for classHandler? e.g. if we choose the prefix "plugin/", then the handler will prepend "plugin/" to its normal handling mechanism.
I like the idea of automatic detection, but can we have both? Not actually both, but automatic and semi-automatic. I mean, at register-time, we could say our plugin is related to the string "forum" and on the ClassHandler instance, we could choose the prefix "forum/". It would greatly improve application deploying and decoupling and etc...When ClassHandlerManager::handleRequest() is called, it determines if a class has been registered that can handle the request. Subclasses of ClassHandler are registered automatically, either at compile time for static plugins, or at startup for dynamic ones. No acton is required on the developers side, other than implementing the Qt interface, ClassHandlerInterface. That's it.
Not that hard to do.
In the sublcasses of ClassHandler, slots are used. A slot will automatically be called based on the URL. The subclass can have an arbitrary number of slots.would call the temp(20) method on the server instance, as long as the class defined a slot temp(int) method. If not, the request is not handled, and the request is passed on down the router, like any other request, and any other handler. [...]
For example, if the URL is:
/server/settings
then the ClassHandlerManager will automatically see if there is a class Server (technically, this is the QObject::name, but, can be the class name), and if there is, will call the settings method on the instance.
/server/temp/20
It's perfect for single and zero argument functions, but I think we should use query strings or something else for multi-argument functions.
But it can be released as is. Support for multi-argument functions can be released in later versions (and we'd have more feedback and ideas).
If you keep in a private branch, you'll have to maintain the set of patches for yourself (new Tufão release, more work to you to backport the new features ...).
If there is more general interest in this, I'll fork and integrate my code, and create a pull request. I'm sure a better C++ coder than me could make improvements; it's not perfect, and I'm still tweeking it, as I use it myself.
There are a lot of reasons to not consider any QObject; an application can have hundreds or thousands of such classes, that have nothing to do with responding to HTTP requests. And it's not only subclasses of ClassHandler, it's any class that implements the interface. I just chose to have an abstract class implement the interface. An interface makes perfect sense, as that is what an interface is for; it says that a class is intended to be used for a particular reason or in a particular fashion.
As for the init(), that's mainly due to the fact that C++ does not separate memory allocation and instance initialization, the way, say, Objective-C does. And as you should never call a method from a constructor, the init method allows for the initialization before use. In this case, the init method iterates over plugins, and builds a dispatch table for faster run-time performance.
By prefix, do you mean in the URL? That would be a really bad idea. Something accessing a URI doesn't need to know if the implementation is a plugin or not. If I misunderstood, please clarify.
When ClassHandlerManager::handleRequest() is called, it determines if a class has been registered that can handle the request. Subclasses of ClassHandler are registered automatically, either at compile time for static plugins, or at startup for dynamic ones. No acton is required on the developers side, other than implementing the Qt interface, ClassHandlerInterface. That's it.
I like the idea of automatic detection, but can we have both? Not actually both, but automatic and semi-automatic. I mean, at register-time, we could say our plugin is related to the string "forum" and on the ClassHandler instance, we could choose the prefix "forum/". It would greatly improve application deploying and decoupling and etc...
Not that hard to do.
I do not understand what you mean here. If you had a class named Forum, then any URL starting with /forum would be dispatched to that class. That happens without intervention reuqired
No, it's actually perfect regardless of the number of arguments. As I'm trying to follow ReST, something like:
/catalog/parts/chapter/3/page/2
would result in a call to Catalog::parts(int chapter, int page) as content(3,2)
Em Seg, 2013-12-09 às 12:55 -0500, Reaves, Timothy escreveu:My understanding about your solution is growing and I think it's a contribution worth enough to have upstream.There are a lot of reasons to not consider any QObject; an application can have hundreds or thousands of such classes, that have nothing to do with responding to HTTP requests. And it's not only subclasses of ClassHandler, it's any class that implements the interface. I just chose to have an abstract class implement the interface. An interface makes perfect sense, as that is what an interface is for; it says that a class is intended to be used for a particular reason or in a particular fashion.
Currently Tufão acts more like a library and less like a framework. When you use Tufão, you guide Tufão tools to work together. Your solution would add the other option too, where Tufão would call user code (freeing the user from the responsibility of piecing and communicating different components).
If I understood correctly, your solution will navigate through all subclasses of ClassHandler at runtime and register them as handlers. If this is the case, then I agree with you. It's a bad idea to consider any QObject.
When you create a class you are abstracting something. The something can be a resource and what you care about is the resource, not memory. Memory is also a resource, but not the only type of resource. RAII free us from the responsibility of think about every exit point to put a free-function call. More than that, RAII allow us to abstract other kind of resources not possible in other languages like mutexes and is the most robust way to exception-safe programming.
As for the init(), that's mainly due to the fact that C++ does not separate memory allocation and instance initialization, the way, say, Objective-C does. And as you should never call a method from a constructor, the init method allows for the initialization before use. In this case, the init method iterates over plugins, and builds a dispatch table for faster run-time performance.
A separate init function will complicate the life of the user, because now he has not only to call another function that won't bring him any benefit, but also he has to track a third possible state: the invalid/non-initialized state object.
But... the init function could benefit the implementer, letting him abstract common initialization tasks. If this is the case, just make the method private and call it from the constructors.
Maybe the game will change with transactional memory research. Until there we can continue to use RAII.
Your proposed solution is a bit intrusive and require changes to previous components to make them work against the new approach. If the user doesn't have control over all components, he can't update the source to match the new approach, but you avoid this problem with the ClassHandlerManager request handler.
By prefix, do you mean in the URL? That would be a really bad idea. Something accessing a URI doesn't need to know if the implementation is a plugin or not. If I misunderstood, please clarify.
When ClassHandlerManager::handleRequest() is called, it determines if a class has been registered that can handle the request. Subclasses of ClassHandler are registered automatically, either at compile time for static plugins, or at startup for dynamic ones. No acton is required on the developers side, other than implementing the Qt interface, ClassHandlerInterface. That's it.
I like the idea of automatic detection, but can we have both? Not actually both, but automatic and semi-automatic. I mean, at register-time, we could say our plugin is related to the string "forum" and on the ClassHandler instance, we could choose the prefix "forum/". It would greatly improve application deploying and decoupling and etc...
Not that hard to do.
I do not understand what you mean here. If you had a class named Forum, then any URL starting with /forum would be dispatched to that class. That happens without intervention reuqired
I see this as an opportunity to make Tufão smart enough to handle "embedded apps". Imagine if we have multiple ClassHandlerManagers. Can we register ClassHandler-inherited handlers to different ClassHandlerManagers? Maybe we can use a root fallback ClassHandlerManager handler and some string-identified ClassHandlerManager handlers. They still would be decoupled and would only interact at runtime.
My overly bad explained suggestions quoted above were related about this change.
Imagine the user wants to use an embedded app to provide a forum to its site. Maybe he wants to put this app under the "/forum" url or maybe he wants to put it under the "/boards" url, but he must have control to avoid collision without the need to change the imported components. This is what I meant with the "url-prefixing".
And what if the user wants to import a wiki app/component and a forum app/component to embed on its site? The components shouldn't register to the same ClassHandlerManager, because each one will "prefix" to different paths to the beginning of the url. If the user doesn't identify a ClassHandlerManager, then the wiki should just use the root ClassHandlerManager (the one without a id string associated, always present).
Thoughts?
While working at a large internet search company, the main code base could not be tested, as the programmers there did not make this separation, and their object constructors acquired so many resources that testing was impossible, as these resources where not available to the test harnesses, and lots so exceptions where thrown.
This is a fundamental approach to software development, and people will either agree or disagree, just as some poor programmers disagree when I say a conditional body should always be enclosed in brackets, even when that body is a single line. As a general statement, I fundamentally believe a constructor should never do more than assignment; no use of 'new' and no use of the de-refernce operators. Because when you do, and a test fails, you do not know if the test failed because the logic was wrong, or some resource was unavailable. That's a bad test. So other than using an init() method, how do you separate the two concerns?
Now, if that is the only thing that would preclude merging of my code, I'd change it.
Are you saying you think a user should be able to specify context and over-ride what the pluging author wants? Or to specify a context if the plugin author did not?
Yes, this is do-able easily with dynamic plugins. Not with static plugins. It can be done with static plugins differently though, requiring the developer to not only use Q_PLUGIN_METADATA but also Q_CLASSINFO. This is because QPluginLoader isn't used for static plugins.
So should be figure all plugins dynamic? Or support both methods?
When you get a chance, have a look at:
https://github.com/treaves/tufao
When you get a chance, have a look at:
https://github.com/treaves/tufao
I've spent today implementing a new type of Handler. [...]
- Replace the entry.endsWith test by QLibrary::isLibrary
- Use CMake to detect platform features. See this: http://www.cmake.org/pipermail/cmake/2007-June/014688.html
[...] If you want me to go ahead with the change for isLibrary(), and merge in your changes, I will, but, it seems to be less work for you to simply update that code.
I see on the main README that you merged this for 1.2, but in the source, all plugin code was removed. There is not comment to explain this. Is this a permanent thing?
Is there any way I might help out? I'd like to continue my involvement with the project, and now that I've started a new job, I hope to have some time.