Why I built an alternative to plug (Raxx), and why I based it on Rack

tl:dr. This is not an anti plug article, my conclusion is there is reason for both to exist.

Several years ago Jose wrote this article, Why your web framework should not adopt Rack API. It was not the only one, The dark side of the Rack and Websockets dreams.

I am aware of these and was still was curious what a Rack style webserver interface would look like for Elixir, the result was Raxx. Recently, I started building a web-framework on top of Raxx. Here are my thoughts as to “Why Raxx”.

Interested in hearing anyone’s thoughts and opinions.

Edit: See the Library thread on the forum here.

13 Likes

I think rack is simpler. It is easier to do things like modify a request after a subsequent step has run and it is easier to recover from exceptions. Plug has to add it’s own special mechanisms for that, like registering callbacks for after render and exception handlers that can’t be plugs. That said, Plug’s model better supports things like websockets and streaming HTTP. I’d say your assessment is correct.

When I started to build a Plug like interface for processing messages from RabbitMQ, I found myself tripping over the issues I mentioned. Switching to a rack like model was a better fit for the problem because I didn’t have the complex use cases that Plug did.

3 Likes

Found this talk, not sure if it’s up to date but here it is anyway:

Personally I think exploring new ideas is always a good thing, choices generally drive innovation.

6 Likes

I’m all for alternatives, but can you really argue that Plug.Conn and the plugs that manipulate it don’t promote simplicity? I don’t see anything that is simpler about this and none of the points seem to not apply to Plug.

Logically, if I needed a barebones system, I could just do it in my cowboy handler, like so, but ultimately it’s extremely seldom a great idea to start at that level.

The part about purity seems like an odd one to make in Elixir, which really doesn’t limit you in terms of this. Despite this, we know how to do things even with the looming possibility of using impure functions.

Why should I start with Raxx, given the fact that, for minimalism, I could start with only cowboy, and why should I use it instead of Plug? I’ve read the rationale previously posted and I’ve watched the lightning talk, but I’m still not seeing what it’s doing in addition to the cowboy bit and a few response helper functions.

3 Likes

Yep that is me talking. It’s pretty up to date, the principles are constant and because it is based rack it has been pretty stable from the start.

I have been experimenting with an upgrade mechanism to support chunked replys. (Loosely based on the second articled I referenced), that is far more in flux and requires a bit more work but then I hope to write about that

3 Likes

The problem with this approach is that you need to sit with the response in memory while you undo the stack. If you have a pipeline with 10 plugs, it is most likely that only one or two need to modify the response. In Plug you would register callbacks when you need it. In the Rack model, you need to hold all 10 middleware in the stack.

I would also argue that the use of Plug is simpler for the majority of cases. You can’t get simpler than a single function that receives the connection and returns the connection. As an example, in Phoenix you can use plugs both as “callbacks” in your controller and in your pipelines. However, in Ruby, almost all frameworks and libraries, such as Rails and Sinatra, end-up introducing a new lightweight abstraction that resembles Plug, such as callbacks or filters at the controller and application level.

However, I definitely agree the Plug model is more complex than the Rack model in the scenarios you mentioned. Part of the problem here, especially in regards to exception handling, was that the issue was discovered as we moved forward due to the interplay between Plug and immutability. I believe it would be less confusing if those issues could have been articulated up front.

That’s not saying Plug is the ideal interface for everything but for HTTP the list of pros fairly outweigh the cons (IMO :slight_smile:), especially with HTTP 2 being available and playing even more on the strengths of streaming, data push, multiple responses, etc.

13 Likes

Here’s an excellent writeup on the “response in memory” aspect from Big Nerd Ranch as well

3 Likes

@josevalim thanks a bunch for taking the time to reply when you have already expressed your points well elsewhere.

Maybe simplicity is in the eyes of the beholder, so I won’t add more comments around that.

I am however curious about the topic of middleware. Raxx doesn’t yet define anything like a builder etc, for stacking middleware. For my own purposes I have used defoverridable to modify application modules. e.g. this module to translate HEAD requests to GET requests https://github.com/CrowdHailer/Tokumei/blob/master/app/lib/tokumei/head.ex.
Does this method have the same drawbacks with unrolling the stack?

3 Likes

Nice article. :slight_smile:

I believe the conclusion to this is that working with a response in memory is really quite efficient, also that these advantages are exposed by EEx. If so it is certainly possible for either approach to benefit from this.

1 Like

Yes. https://github.com/CrowdHailer/Tokumei/blob/master/app/lib/tokumei/head.ex#L18

But to be fair a large stack is much less of an issue though in the Erlang VM.

2 Likes

Cheers. Thinking out lout I guess it might be possible to create some kind of middleware convention to help here.

Stacking a new middleware will only overwrite handle_request (or handle_response) if the middleware module implements a transform_request or transform_response. The middleware builder can inspect each module using Module.defines? and use defoverridable sparingly.

Sounds interesting enough to be worth an experiment.

1 Like

My largest issues with rack is it’s design is based on assumptions prior to HTTP 2 spec being developed and released - if you can develop an effective and performance http2 implementation in raxx then amazing!

Otherwise my concern is that other less experienced developers may take the advantages you offer without understanding that the trade offs and then find themselves caught in a corner of deprecated design technology (rack)

2 Likes

I think this is a very valid concern. To be honest I don’t see any reason why a pure request -> response function should not be able to be passed to a HTTP2 server. Or maybe it could return a list of responses as promises. request -> [response, promises...] and the server knows that if the original request was http1.1 to discard the promises.

I am working on integrating Raxx and chatterbox, no firm conclusions yet but results coming soon, I hope.

1 Like

I’m so glad to see the work you are doing on Raxx - Great write-up at the github page.

Perhaps these articles would give you some food for thought regarding the request/response cycle.
https://www.speedshop.co/2016/01/07/what-http2-means-for-ruby-developers.html which links to https://www.igvita.com/2012/01/18/building-a-modern-web-stack-for-the-realtime-web/ and https://github.com/tenderlove/the_metal/issues/5

I’m still in the middle of trying to digest the spec myself! :blush:

2 Likes

Thanks, writing docs is not a strength of mine so they tool a while.

there were certainly some interesting discussions in ruby about upgrading rack, i.e. the_metal. The dark side link I originally posted linked to this proposed spec which provides more callbacks as an upgrade mechanism. This is probably the closest to the way I’m thinking at the moment. I think the extra callbacks vs a direct connection object are a similar abstraction to the way a GenServer gives you callbacks instead of using send and receive on a process.

1 Like

Thank you for your attention.

1 Like

I still think that simplicity is use-case specific, However there is more to a plug than I think may users realise. Typical Elixir Phoenix Plug.Conn struct contents, in an easy-to-read format. By Jamon Holmgren. · GitHub

1 Like

Further to this conversation I am ready to share the outline of how I intend to handle streaming as part of a pure interface.

This implementation for chunked responses is here, (currently only built in one webserver but I am in the process of extracting). It is promising but I look forward to using it more concertedly in the future. Feedback welcome.

1 Like

As I recently made my HTTP/2 server available I can now return to the topic of what a streaming interface could look like.

Rather than develop it in Raxx, I am going to directly work on the interface in Ace. I hope that reducing the layers of indirection will allow me to iterate to a good solution quicker. Once a nice solution is found the idea would be to extract it too Raxx and have Ace as just one implementation.

I have opened this issue for what a streaming API could look like with an examples for simple cases, streaming up and streaming down.

1 Like

We starting using Ace/Raxx in production today to handle some webhooks, so far its working great.

Thanks man, keep up the good work!

1 Like