--
You received this message because you are subscribed to the Google Groups "Ring" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ring-clojure...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
However, Readable/Writable ByteChannels are not really asynchronous. SelectableChannels allow for multiplexing, so it's at least not one thread per connection, but at the cost of significant implementation complexity. Every server implementation would need a thread set aside to run the selection loop, and for streaming requests some sort of execution model outside of that loop would be needed to handle the reads. In other words, every Ring-CPS implementation would asymptotically approach Netty. Given that efficient interaction with WritableByteChannels also requires selectors, this probably is also true of any attempt to extend ChannelWritable over core.async, etc.
So while I think the CPS approach is adequate, pretty much everything relating to the bodies of the requests and responses needs some more thought, and definitely one or more reference implementations before the spec is finalized.
--
But I would like to ask the following of James and Zach. Why keep the complexity of the ring call model? Ring handlers combine the modification of a context with the calling of a new context, but for what purpose?
Yes, I think I'd favor protocols that resemble the read and write halves of http://docs.oracle.com/javase/7/docs/api/java/nio/channels/AsynchronousByteChannel.html. Note that we can't just use AsynchronousByteChannel, since it's only a way of representing sockets, not arbitrary flows of bytes in a process. Note that you'd probably want synchronous AND asynchronous read/write methods, and some way for the stream object to indicate which is native for it. Otherwise, you'd end up pushing every "asynchronous" read from an InputStream onto another thread so it doesn't block, rather than just spinning up a thread to do a series of synchronous reads. So something like https://gist.github.com/ztellman/2e2a266076f34e3f5892.
The reason for a buffer on reads is to allow buffer reuse.
--
You received this message because you are subscribed to the Google Groups "Ring" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ring-clojure...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
"I'm somewhat leery about abstractions that seek to improve upon the runtime environment, because it tends to lead to a situation where you need to reimplement at least part of the language's toolchain. "This is true of CPS as well, things like exceptions and stack traces will be quite unusable in CPS as well, and proper handling of them will need to be re-implemented in a continuation style.
Have you considered the fact that this would allow for spec-compliant
HTTP clients in both Clojure and ClojureScript ?
I've in fact been using the exact design you're proposing in a number
of client-side middlewares and it's worked very well for us.
My contention was that all the existing client libraries are very
"complected"; they re-implement the same functionality in ad-hoc,
subtly incompatible ways, and most don't provide clean extension
points like middlewares do. Were they to adopt this spec, most of
their features could be packaged as compatible middlewares, and it
would leave them as simple adapters in Ring parlance.
Hi all,
Sorry for being so late to this thread, someone just linked me to it today.
The system Timothy Baldridge described above is effectively how the interceptor chain works. On Pedestal Master, the interceptor chain is a separate module (pedestal.interceptor) -- I definitely invite you to explore it, see how it integrates into your existing work, and weigh the trade-offs against a purely CPS approach. While I don't care for the CPS approach (I don't think the trade-offs shake out as a net positive when compared against alternatives), I do share Zach's view that CPS is the logical async equivalent of Ring's current model.
I've had to tackle the container-specific NIO/AIO challenges in Pedestal. All response types are programmed against two protocols (one for blocking IO bodies, another for non-blocking bodies). I'm more than happy to talk through the challenges, approaches we tried and reviewed, and discuss what we finally ended up upon.
At this point, Pedestal really is just a set of separate libraries, all programmed against protocols/interfaces. If you see something you like, please steal liberally :) If you have questions, or want to openly work through design decisions and trade-offs, I'm definitely open to participating.
Cheers,
Paul
I'm aware of Pedestal's interceptors. My understanding is that their functionality is equivalent CPS-style Ring middleware, in that there are functions for entering, leaving and raising an error.
CPS-style functions are backward compatible with Ring's current design, which is the main draw to me.
Interceptors are arguably more transparent, but if I understand right, not significantly so in isolation. An map containing three opaque functions doesn't reveal much more than a single opaque function.
The key advantage of interceptors that I can see is that they are held in a persistent queue, whereas middleware are typically composed. But there's no reason why we can't place middleware in a queue and compose them later.
Am I missing something, or is that an accurate assessment?
Interceptors are arguably more transparent, but if I understand right, not significantly so in isolation. An map containing three opaque functions doesn't reveal much more than a single opaque function.
The functions themselves are opaque, but what you get is a first-class shared/established container, on which you can attach other properties (ie: the "interceptor"). You can hold the three functions together as a logical "slice" in the handling/operating pipeline.
The key advantage of interceptors that I can see is that they are held in a persistent queue, whereas middleware are typically composed. But there's no reason why we can't place middleware in a queue and compose them later.
Regarding error handling and composition, I think you eventually have to make this jump (or something similar to it), or accept the standard CPS debugging story. Much of Pedestal's core capabilities come directly from the interceptor chain's queue -- it's how one can have dynamic and rule-based interceptors, how you can easily trace and inject in the handling of a request, and how Pedestal can treat async-debugging as if it were synchronous (with a chain, you can pattern match on "steps" and errors within those steps). It's also how we can run execution of steps backwards, which you'd lose in CPS -- this is going to put more strain on developers to correctly compose the independent, but related pieces of the CPS handling within Ring.
All-in-all an accurate assessment, though I think the fallout of the design decisions might give you some ideas or new angles to consider for Ring. One example, I can envision a model where Ring's exposed API is in a CPS style, but is then composed and executed on the interceptor chain under the hood.
I'm looking forward to what shakes out!
Presumably the functions are passed the interceptor they belong to, so they can read these additional keys?
Interesting. Why would you want to execute the steps backward?
All-in-all an accurate assessment, though I think the fallout of the design decisions might give you some ideas or new angles to consider for Ring. One example, I can envision a model where Ring's exposed API is in a CPS style, but is then composed and executed on the interceptor chain under the hood.The thought had struck me as well. There are a lot of things I like about the interceptor model, but I also get the impression I don't fully appreciate the advantages of its design.
Presumably the functions are passed the interceptor they belong to, so they can read these additional keys?
This is the motivating factor behind all functions taking only a single argument -- the context. The context holds the interceptor queue, which is how other interceptor functions can dynamically change (or instrument) the processing of a request. Interceptors are just records with :enter, :leave, :error, and :name -- a single interceptor represents that one logical slice. An interceptor could always look at itself in the queue.
Interesting. Why would you want to execute the steps backward?
Once you process the chain in one direction (:enter), you can then just turn around and process it in the other direction (:leave), and you get a similar effect to middleware, but completely decoupled. Related, you could also just run the chain in a single direction to process streaming data (SSE, WebSockets, etc.), while still leaving all the error-handling properties of the interceptor chain intact.
How often does an interceptor need to change the interceptor queue? It seems like it could make things more confusing if the sequence of interceptors can change on the fly.
What are the practical advantages of decoupling?I mean, the author of a middleware function always has the option of decoupling the "request" and "response" parts, so the only time I can think of that running the "enter" or "leave" part of an interceptor in isolation would be to do something the original author didn't intend.
Thanks for answering my questions, by the way.
It occurs more often than one might think, but it rarely is done in an ad-hoc, unpredictable fashion. Let's consider two (related) use-cases. Keep in mind, absolutely everything in Pedestal is an interceptor, interceptors are placed on the queue (the interceptor chain), and the chain is executed linearly.
One may want a different set of interceptors for different routes within their application. They might even want a different set of interceptors for a single route -- this is common when you want that endpoint to perform an upgrade-request (SSE or WebSockets), and you sometimes see it where one, single route offers a different output encoding (common with the rise of demand-driven APIs).
But we need not limit ourselves to just dispatching this kind of behavior on URL alone. Consider something like Liberator. You could write a single interceptor that inspects many aspects of a request and queues up the corresponding interceptors to handle that request and correctly format the response. This is the case anytime you'd write a rules-based interceptor (which is common when attempting to model your API to embody business rules).
When enter and leave are separate, one might elect to only run the chain in a single direction, which allows for a chain to handle streams of data. This allows an interceptor chain to be used for SSE, WebSockets, and other streaming message workloads.
Totally unrelated, but Pedestal's router is also a separate module now, if you'd like to pull it into Compojure. It routes in effectively constant-time in the optimized case, and O(log N) in the general case. It operates on the Ring spec for a request, so it should work for everyone.
I typically have different middleware wrap different sets of routes in Ring applications using the wrap-routes Compojure macro that applies the middleware after a particular route has been matched. Consider the following example:
Ring middleware can be wrapped at any point in the request handling process. There's nothing that prevents middleware functions from invoking additional middleware for a specific set of routes based on whatever runtime logic is required.
I don't think Pedestal's router is compatible with the way Compojure works. Routes in Compojure are functions that when passed a request map, either return a response or nil, indicating the route didn't match. So there's no overall routing table, each route operates in isolation.
My understanding of Pedestal's router is that its efficiency comes from having the entire routing table available to it. If it only knew about one route, my guess is that it would be no more efficient than a regular expression. Would you say this is an accurate assessment?
My understanding of Pedestal's router is that its efficiency comes from having the entire routing table available to it. If it only knew about one route, my guess is that it would be no more efficient than a regular expression. Would you say this is an accurate assessment?
I was under the impression that Compojure now has a "compile routes" step. Is that just to turn routes into Records for efficient lookup? Presumably you have a collection of all the routes in some sequential container, could you not pass that sequence to the Pedestal router and package the result in a middleware?