I've been playing around with some changes to ACCEPTOR and friends that introduce the new classes SOCKET-CONNECTOR and SSL-SOCKET-CONNECTOR. The purpose of these changes are to take the specialized socket handling behavior out of the acceptor and into another class, such that subclassing acceptor isn't required to, say, use SSL streams.
As a motivating example, consider the following:
(hunchentoot:start (make-instance 'hunchentoot:easy-acceptor :port 4243 :socket-connector (make-instance 'hunchentoot::ssl-socket-connector :ssl-certificate-file (asdf:system-relative-pathname :hunchentoot "ssl/certificate.pem") :ssl-privatekey-file (asdf:system-relative-pathname :hunchentoot "ssl/key.pem"))))
(hunchentoot:define-easy-handler (say-yo :uri "/yo") (name) (setf (hunchentoot:content-type*) "text/plain") (format nil "Hey~@[ ~A~]!" name))
Without these changes, one would have to subclass both easy-acceptor and ssl-acceptor in order to have an SSL-using easy acceptor. Comments/suggestions greatly appreciated.
The changes can be found in the git repo at:
https://github.com/slyrus/hunchentoot/tree/socket-connector
and in the attached patch.
Cyrus
Hi Cyrus,
without having looked at your patches thoroughly, a question: Why does it make sense to have an extra socket-connector class? I'd put the connection type specific functionality into acceptor, and just add the dispatcher class that you've proposed, although I would like to call it request-handler. It may make sense to add an umbrella "server" class that holds one or more acceptors.
The goal that I see is:
- It should be possible to have one server serve multiple ports, e.g. SSL and non-SLL, but share one object that the application can attach things to. - It should be possible to have multiple request handler instances. They should be attached to a server and be tried in a well-defined order to handle incoming requests. Hunchentoot would provide two request handler classes, easy-handlers and file-handlers.
Thoughts? -Hans
On Mon, Apr 18, 2011 at 12:13 AM, Cyrus Harmon ch-tbnl@bobobeach.com wrote:
I've been playing around with some changes to ACCEPTOR and friends that introduce the new classes SOCKET-CONNECTOR and SSL-SOCKET-CONNECTOR. The purpose of these changes are to take the specialized socket handling behavior out of the acceptor and into another class, such that subclassing acceptor isn't required to, say, use SSL streams.
As a motivating example, consider the following:
(hunchentoot:start (make-instance 'hunchentoot:easy-acceptor :port 4243 :socket-connector (make-instance 'hunchentoot::ssl-socket-connector :ssl-certificate-file (asdf:system-relative-pathname :hunchentoot "ssl/certificate.pem") :ssl-privatekey-file (asdf:system-relative-pathname :hunchentoot "ssl/key.pem"))))
(hunchentoot:define-easy-handler (say-yo :uri "/yo") (name) (setf (hunchentoot:content-type*) "text/plain") (format nil "Hey~@[ ~A~]!" name))
Without these changes, one would have to subclass both easy-acceptor and ssl-acceptor in order to have an SSL-using easy acceptor. Comments/suggestions greatly appreciated.
The changes can be found in the git repo at:
https://github.com/slyrus/hunchentoot/tree/socket-connector
and in the attached patch.
Cyrus
tbnl-devel site list tbnl-devel@common-lisp.net http://common-lisp.net/mailman/listinfo/tbnl-devel
Hi Hans,
Well, that's a good question. I think the idea is that someone could both extend ACCEPTOR (and TASKMASTER) (a fancy epoll-using ACCEPTOR perhaps?) and still have said ACCEPTOR-subclass useable with SSL without subclassing. I like the idea of composing in the SSL-based socket functionality at runtime without subclassing -- at least I liked it enough to try out the approach. I'm not certain this is the best approach, but it looked reasonable enough to try it out and it seems to work. If the consensus is that this isn't a good approach, I can live with that.
As for the DISPATCHER / REQUEST-HANDLER, sure I'm happy to see it renamed to REQUEST-HANDLER.
I think a SERVER class that holds one or more acceptors would be a fine thing. I seem to end up coding something like that by hand when I need it (the ability to do so should remain, of course) but it would be nice to have a standard way to wrap those servers as I think that's a pretty common use case.
And, yes, I think a way of associating multiple request handlers with acceptors would be a good thing. I guess the question is should the ACCEPTOR do that or should we delegate to something like a DISPATCHER which would call each REQUEST-HANDLER?
Thanks for taking a look at my patches.
Cyrus
On Apr 17, 2011, at 10:48 PM, Hans Hübner wrote:
Hi Cyrus,
without having looked at your patches thoroughly, a question: Why does it make sense to have an extra socket-connector class? I'd put the connection type specific functionality into acceptor, and just add the dispatcher class that you've proposed, although I would like to call it request-handler. It may make sense to add an umbrella "server" class that holds one or more acceptors.
The goal that I see is:
- It should be possible to have one server serve multiple ports, e.g.
SSL and non-SLL, but share one object that the application can attach things to.
- It should be possible to have multiple request handler instances.
They should be attached to a server and be tried in a well-defined order to handle incoming requests. Hunchentoot would provide two request handler classes, easy-handlers and file-handlers.
Thoughts? -Hans
On Mon, Apr 18, 2011 at 12:13 AM, Cyrus Harmon ch-tbnl@bobobeach.com wrote:
I've been playing around with some changes to ACCEPTOR and friends that introduce the new classes SOCKET-CONNECTOR and SSL-SOCKET-CONNECTOR. The purpose of these changes are to take the specialized socket handling behavior out of the acceptor and into another class, such that subclassing acceptor isn't required to, say, use SSL streams.
As a motivating example, consider the following:
(hunchentoot:start (make-instance 'hunchentoot:easy-acceptor :port 4243 :socket-connector (make-instance 'hunchentoot::ssl-socket-connector :ssl-certificate-file (asdf:system-relative-pathname :hunchentoot "ssl/certificate.pem") :ssl-privatekey-file (asdf:system-relative-pathname :hunchentoot "ssl/key.pem"))))
(hunchentoot:define-easy-handler (say-yo :uri "/yo") (name) (setf (hunchentoot:content-type*) "text/plain") (format nil "Hey~@[ ~A~]!" name))
Without these changes, one would have to subclass both easy-acceptor and ssl-acceptor in order to have an SSL-using easy acceptor. Comments/suggestions greatly appreciated.
The changes can be found in the git repo at:
https://github.com/slyrus/hunchentoot/tree/socket-connector
and in the attached patch.
Cyrus
tbnl-devel site list tbnl-devel@common-lisp.net http://common-lisp.net/mailman/listinfo/tbnl-devel
tbnl-devel site list tbnl-devel@common-lisp.net http://common-lisp.net/mailman/listinfo/tbnl-devel
On Mon, Apr 18, 2011 at 4:56 PM, Cyrus Harmon ch-tbnl@bobobeach.com wrote:
I think the idea is that someone could both extend ACCEPTOR (and TASKMASTER) (a fancy epoll-using ACCEPTOR perhaps?) and still have said ACCEPTOR-subclass useable with SSL without subclassing. I like the idea of composing in the SSL-based socket functionality at runtime without subclassing -- at least I liked it enough to try out the approach. I'm not certain this is the best approach, but it looked reasonable enough to try it out and it seems to work. If the consensus is that this isn't a good approach, I can live with that.
At this point, I'd prefer not to add new classes unless they really fulfill a need. I have been pondering whether/if Hunchentoot can be converted to an event oriented web server somehow, but that would require much more than separating out the connector functionality. The real issue, I think, is that the underlying streams are not event oriented. The flow of control would need to be basically reverted, and that is something that I'm not up to doing. Maybe using Hunchentoot as a start is a bad idea anyway. Anyway, I'd like to keep things as simple as possible.
I think a SERVER class that holds one or more acceptors would be a fine thing. I seem to end up coding something like that by hand when I need it (the ability to do so should remain, of course) but it would be nice to have a standard way to wrap those servers as I think that's a pretty common use case.
The task of a SERVER class would be to read a configuration specification and instantiate the required objects that make up the server. In the simple case, this would be one ACCEPTOR, one TASKMASTER and one REQUEST-HANDLER. In more complex cases, more ACCEPTOR instances would be present (to listen on multiple ports) or more REQUEST-HANDLERS would exist to, say, serve multiple virtual hosts.
I will need a few more weeks until I can really put effort into reviewing the patches and implement the new mechanisms. I also hope that the SSL problems are sorted out until then, as the upcoming release should certainly support SSL.
-Hans
Hi Hans and the rest of the hunchentoot folks,
Sorry for the silence on this, but I'm curious to see if anything has happened in the last few months that changes things. The main problem I had with the hunchentoot changes from late last year/early this year where that my hunchentoot-vhost no long worked, and it wasn't clear how to cleanly make things work. I thought the socket-connector and dispatcher patches solved the problem in an appropriate manner.
I've been away from this for quite some time, but I, once again, find myself needing to run multiple, in apache parlance, virtual hosts, on a single pair or 80 and 443 ports. I'm happy to abandon my hunchentoot-vhost approach if there's a better option, but I'd hate to have to recode that stuff for no good reason. Any suggestions?
thanks,
Cyrus
On Apr 20, 2011, at 1:01 AM, Hans Hübner wrote:
On Mon, Apr 18, 2011 at 4:56 PM, Cyrus Harmon ch-tbnl@bobobeach.com wrote:
I think the idea is that someone could both extend ACCEPTOR (and TASKMASTER) (a fancy epoll-using ACCEPTOR perhaps?) and still have said ACCEPTOR-subclass useable with SSL without subclassing. I like the idea of composing in the SSL-based socket functionality at runtime without subclassing -- at least I liked it enough to try out the approach. I'm not certain this is the best approach, but it looked reasonable enough to try it out and it seems to work. If the consensus is that this isn't a good approach, I can live with that.
At this point, I'd prefer not to add new classes unless they really fulfill a need. I have been pondering whether/if Hunchentoot can be converted to an event oriented web server somehow, but that would require much more than separating out the connector functionality. The real issue, I think, is that the underlying streams are not event oriented. The flow of control would need to be basically reverted, and that is something that I'm not up to doing. Maybe using Hunchentoot as a start is a bad idea anyway. Anyway, I'd like to keep things as simple as possible.
I think a SERVER class that holds one or more acceptors would be a fine thing. I seem to end up coding something like that by hand when I need it (the ability to do so should remain, of course) but it would be nice to have a standard way to wrap those servers as I think that's a pretty common use case.
The task of a SERVER class would be to read a configuration specification and instantiate the required objects that make up the server. In the simple case, this would be one ACCEPTOR, one TASKMASTER and one REQUEST-HANDLER. In more complex cases, more ACCEPTOR instances would be present (to listen on multiple ports) or more REQUEST-HANDLERS would exist to, say, serve multiple virtual hosts.
I will need a few more weeks until I can really put effort into reviewing the patches and implement the new mechanisms. I also hope that the SSL problems are sorted out until then, as the upcoming release should certainly support SSL.
-Hans
tbnl-devel site list tbnl-devel@common-lisp.net http://common-lisp.net/mailman/listinfo/tbnl-devel
Hi Cyrus,
Quoth Cyrus Harmon ch-tbnl@bobobeach.com:
I'm happy to abandon my hunchentoot-vhost approach if there's a better option, but I'd hate to have to recode that stuff for no good reason. Any suggestions?
At one point I considered hunchentoot-vhost but decided against it. It seemed quite complex (at least to me).
Instead, I run hunchentoot behind lighttpd. hunchentoot has acceptors listening on various localhost ports and lighttpd is configured to send page requests to one of those localhost ports depending on which host is identified in the URL.
And that's it! "One host per port". It works like a dream.
lighttpd's conf file needs a section that reads like this:
--8<---------------cut here---------------start------------->8--- # proxy handling server.modules += ( "mod_proxy" )
$SERVER["socket"] == "123.456.78.90:80" { proxy.balance = "fair" $HTTP["host"] =~ "^example.com" { proxy.server = ("" => (("host" => "127.0.0.1", "port" => 49154))) } $HTTP["host"] =~ "^foobar.org" { proxy.server = ("" => (("host" => "127.0.0.1", "port" => 49155))) } } --8<---------------cut here---------------end--------------->8---
I'm sure it's just as easy using Apache, but I'm afraid I've never used it so I can't say precisely how it's done.
Hope this helps,
Seb
On Thu, Sep 22, 2011 at 8:48 AM, Sebastian Tennant sebyte@smolny.plus.com wrote:
At one point I considered hunchentoot-vhost but decided against it. It seemed quite complex (at least to me).
I am using squid as a frontend to pretty much the same effect (I use it for SSL processing, too).
Sadly, I have not made progress regarding the changing the architecture of Hunchentoot in ways that would decouple request handling from from accepting. What would really be needed is a request routing concept that is orthogonal to request handling. Virtual host handling would then become part of the request routing.
Even though easy handlers are currently defined as acceptor subclass, I do not think that this is the best way to implement a framework on top of Hunchentoot. I refactored easy handlers that way because it was the quickest way to make them optional, as I wanted to get the easy handler code out of the common code path used for all handlers.
I don't have current plans to make larger architectural changes myself, though. If you, the Hunchentoot user base, have needs that require architectural microchanges within the current class layout, I'll gladly review pull requests on github and also make them part of the main repository. I am also open to larger efforts if anyone has the time to do that. This can happen on a github fork first and moved to the edicl repository once there is sufficient interest and movement.
For the long-promised, still-undated upcoming release of Hunchentoot, my focus is to stabilize the feature set that exists right now.
-Hans
Quoth Hans Hübner hans.huebner@gmail.com:
On Thu, Sep 22, 2011 at 8:48 AM, Sebastian Tennant sebyte@smolny.plus.com wrote:
At one point I considered hunchentoot-vhost but decided against it. It seemed quite complex (at least to me).
I am using squid as a frontend to pretty much the same effect (I use it for SSL processing, too).
Cool. I've never tried squid but I'll definitely have a look at it now, if I ever find time!
Sadly, I have not made progress regarding the changing the architecture of Hunchentoot in ways that would decouple request handling from accepting. What would really be needed is a request routing concept that is orthogonal to request handling. Virtual host handling would then become part of the request routing.
IMHO, the fact that there's only a single *dispatch-table* is 'the big thing' that needs changing.
Adding a dispatch-table slot (with default value *dispatch-table*) to the acceptor class would probably suffice, though it's probably not that simple.
My workaround uses the acceptor :name slot to store the name of the dispatch table for that acceptor) and I then define a modified request dispatcher function that binds *dispatch-table* accordingly:
;;; per-acceptor dispatch tables (defun choosing-list-request-dispatcher (request) (let ((*dispatch-table* (eval (acceptor-name *acceptor*)))) (loop for dispatcher in *dispatch-table* for action = (funcall dispatcher request) when action return (funcall action) finally (setf (return-code *reply*) +http-not-found+))))
;;; initalise dispatch tables (defvar *exmaple-dot-com-dispatch-table* '(default-dispatcher)) (defvar *foobar-dot-org-dispatch-table* '(default-dispatcher))
;;; acceptors (defvar example-dot-com (make-instance 'acceptor :name '*exmaple-dot-com-dispatch-table* :address "127.0.0.1" :port 49154 :request-dispatcher 'choosing-list-request-dispatcher))
(defvar foobar-dot-org (make-instance 'acceptor :name '*foobar-dot-org-dispatch-table* :address "127.0.0.1" :port 49155 :request-dispatcher 'choosing-list-request-dispatcher))
Even though easy handlers are currently defined as acceptor subclass, I do not think that this is the best way to implement a framework on top of Hunchentoot. I refactored easy handlers that way because it was the quickest way to make them optional, as I wanted to get the easy handler code out of the common code path used for all handlers.
I found easy handlers too difficult to use :) so I've never used them and therefore can't really comment, although getting their code out of the common code path sounds like a sensible thing to be doing.
If you [...] require architectural microchanges within the current class layout, I'll gladly review pull requests
Noted.
on github and also make them part of the main repository.
Sorry... isn't 'hunchentoot on github' and 'the main repository' one and the same thing?
I am also open to larger efforts if anyone has the time to do that. This can happen on a github fork first and moved to the edicl repository once there is sufficient interest and movement.
Noted.
For the long-promised, still-undated upcoming release of Hunchentoot, my focus is to stabilize the feature set that exists right now.
Might it not be a good idea to make an announcement with subject line 'Hunchentoot frozen pending imminent release - bugfixes welcome' or words to that effect?
Seb
On Thu, Sep 22, 2011 at 10:03 PM, Sebastian Tennant sebyte@smolny.plus.com wrote:
Quoth Hans Hübner hans.huebner@gmail.com:
IMHO, the fact that there's only a single *dispatch-table* is 'the big thing' that needs changing.
Adding a dispatch-table slot (with default value *dispatch-table*) to the acceptor class would probably suffice, though it's probably not that simple.
I can agree to that, but subclassing acceptors is not really modular (i.e. it is as right or wrong as the easy-acceptor subclass). I think it would make sense to have a dispatcher class. Every acceptor would have an dispatchers slot that contained a list of dispatcher instances which would be consulted, one after another, to route the request.
That way, it would be possible to share dispatcher instances between acceptors. For example, a web site could have a dispatcher for the anonymous, sessionless front-end functionality that would be used by the http port. The same dispatcher instance would be on the dispatchers list of the https acceptor, and in addition there would be a dispatcher instance for the, say, content management system on that acceptors dispatchers list. Also, the dispatcher class would be responsible for implementing virtual hosts, i.e. there would be one dispatcher for every virtual host supported.
Now this sounds nice and easy, but it adds to the number of instances that need to be created in order to start Hunchentoot. Starting Hunchentoot in any but the most basic standard configurations is already a chore, requiring various make-instance invocations and initargs. If we move forward in this direction, I think we need to improve how Hunchentoot is configured and started, too. Basically, this would be a declarative syntax that contained the various classes and instiation arguments.
on github and also make them part of the main repository.
Sorry... isn't 'hunchentoot on github' and 'the main repository' one and the same thing?
"on github" means "in any fork", "the main repository" means edicl/hunchentoot.
Might it not be a good idea to make an announcement with subject line 'Hunchentoot frozen pending imminent release - bugfixes welcome' or words to that effect?
Yes. As soon as the freeze point is reached, that will happen. Before that, here is what I'd like to do:
- Conceptionalize dispatching (in the way described above or a different modular way) - Implement declarative configuration - Integrate Hunchensocket - Set up default file system with documentation and nice-looking error pages
Anything else? Let me know. In order for this to actually happen, discussion and collaboration is needed.
-Hans
Quoth Hans Hübner hans.huebner@gmail.com:
On Thu, Sep 22, 2011 at 10:03 PM, Sebastian Tennant sebyte@smolny.plus.com wrote:
Quoth Hans Hübner hans.huebner@gmail.com:
IMHO, the fact that there's only a single *dispatch-table* is 'the big thing' that needs changing.
Adding a dispatch-table slot (with default value *dispatch-table*) to the acceptor class would probably suffice, though it's probably not that simple.
I can agree to that, but subclassing acceptors is not really modular (i.e. it is as right or wrong as the easy-acceptor subclass).
I was talking about adding a dispatch-table slot to the existing acceptor class. I was not talking about subclassing. My understanding of object oriented programming paradigms is limited so I can't say I fully understand why subclassing is 'not really modular', however simply adding a dispatch-table slot to the acceptor class would circumvent that problem.
I think it would make sense to have a dispatcher class. Every acceptor would have a dispatchers slot that contained a list of dispatcher instances which would be consulted, one after another, to route the request.
I'm sure this could be made to work well but it undoubtedly adds another layer of complexity.
I drew this visual model for myself when I was learning about hunchentoot:
+--------------------------+ +-----------<| Client |<---------------+ | +--------------------------+ | HTTP request HTML | | +---------------------------------------------------+ | | | req. obj. | | ACCEPTOR | PROCESS-REQUEST -------------> request dispatcher | ===> request handler +---------------------------------------------------+ | ^ | ^ req.| | req.| | obj.| NIL obj.| request handler | | | | *DISPATCH-TABLE* (dispatch function#1 dispatch function#2 ... dispatch function#N)
It would help me a lot if you could draw a similar model which incorporates dispatch tables and dispatch functions, as well as your proposed dispatcher object.
That way, it would be possible to share dispatcher instances between acceptors. For example, a web site could have a dispatcher for the anonymous, sessionless front-end functionality that would be used by the http port. The same dispatcher instance would be on the dispatchers list of the https acceptor, and in addition there would be a dispatcher instance for the, say, content management system on that acceptors dispatchers list.
Sharing dispatcher instances sounds good in theory but I'm not sure how useful it would be in practice (based on my own experience).
Also, the dispatcher class would be responsible for implementing virtual hosts, i.e. there would be one dispatcher for every virtual host supported.
Each virtual host already has its own acceptor so why does each virtual host need its own dispatcher object as well?
Now this sounds nice and easy,
Does it? :)
but it adds to the number of instances that need to be created in order to start Hunchentoot. Starting Hunchentoot in any but the most basic standard configurations is already a chore, requiring various make-instance invocations and initargs. If we move forward in this direction, I think we need to improve how Hunchentoot is configured and started, too. Basically, this would be a declarative syntax that contained the various classes and instantiation arguments.
It seems to me the solution is becoming more complicated than the problem we are trying to solve, namely improved support for virtual hosts.
My solution is simple, and backwards compatible:
(defun list-request-dispatcher (request) (let ((dispatch-table (if (fboundp 'acceptor-dispatch-table)) (eval (acceptor-dispatch-table *acceptor*)) *dispatch-table*))) (loop for dispatcher in dispatch-table for action = (funcall dispatcher request) when action return (funcall action) finally (setf (return-code *reply*) +http-not-found+)))))
where acceptor-dispatch-table is a reader method defined on an additional slot to the acceptor class; dispatch-table, with default value '*dispatch-table*.
"on github" means "in any fork", "the main repository" means edicl/hunchentoot.
OK, I see.
Yes. As soon as the freeze point is reached, that will happen. Before that, here is what I'd like to do:
- Conceptionalize dispatching [...]
- Implement declarative configuration
This seems to me like a lot of work and it also seems quite likely that you will have to do most (if not all) of it. And will these changes be backwards compatible? I suugest these be deferred to a subsequent release, one with a more significant version number bump; 1.2.0 say?
- Integrate Hunchensocket
I hadn't heard of hunchensocket until now, but I've no doubt it should be integrated. How easy or hard do you think this will be?
- Set up default file system with documentation and nice-looking error pages
I'm not sure what you mean by a default file system. Do you mean a dispatch function which matches a unique path:
/hunchendocs/manual.html
for example, which is added to *dispatch-table* by default?
As for 'nice-looking' error pages, don't forget that one person's nice is another person's ugly.
Sebastian