codereading / rack

a modular Ruby webserver interface
http://rack.rubyforge.org/
Other
18 stars 2 forks source link

Where does Rack sit in relation to client, server and app? #7

Open codereading opened 12 years ago

codereading commented 12 years ago

My original impression was that it sat inbetween the server (apache) and app (i.e. rails app). I envisioned the relationship chain to be like this

client (browser) ---request --> server (apache) ----> rack -----> app (rails)

and the response would be in the same order but in reverse.

But I just read the following form the rubylearning rack tutorial http://gallery.mailchimp.com/e49655551a5bb47498310c7de/files/RackIntro.pdf

Remember: The fundamental idea behind Rack middleware is - come between the calling client and the server, process the HTTP request before sending it to the server, and processing the HTTP response before returning it to the client.

Is that right?

skade commented 12 years ago

Rack are two different things: the SPEC and the lib. The spec describes how the environment hash looks like and how the mandatory parts behave. The library implements the specification and some patterns to work with the environment (middlewares and the builder to stack together middlewares). You could use Rack without middlewares and you would still follow the spec, as long as your framework returns the right stuff.

Now, about the flow. You forgot one part, which is the handler. The server does some server-specific stuff with the HTTP request and will give it to you in whichever form the server implements. This can be a string on stdin, a hash (if your server is running in the same process like thin) or something different. All this does not conform to the SPEC. So, there is a Handler in place, which is specific for each webserver and ensures that all this is turned into a spec-conforming environment hash. It also makes sure that the returned response will be given to the server in the representation that the server wants. So the flow is as follows:

client (browser) ---> request --> server (apache) ----> rack (handler) -----> app (middlewares+framework) ---> rack(handler) ---> server --> response --> client

The app can have multiple middleware stacks, it can have forking conditions, etc.. Rack doesn't care. It just wants a response back after calling the app.

Padrino for example has at least 2 middleware stacks (One in front of the whole application stack, one in front of each application class) that can all be freely manipulated - but Rack couldn't care less about that :).

samnang commented 12 years ago

So when I read RackIntro by rubylearning, I see Rack includes adapters that connect Rack to various web frameworks (Sinatra, Rails etc.), so where is it inside the flow? Where does it define, I can't find the files?

skade commented 12 years ago

Hm? Sinatra has no adapter to rack. It is a Rack application itself, as is Rails nowadays.

ericgj commented 12 years ago

@samnang :

(1) https://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L742 An instance of Sinatra::Base is the basic Rack app that gets put at the end of the pipeline, note it's just a plain class that def call(env) and def initialize(app). *

You can either use the command-line rackup to run it (in which case Rack sets up the handler and launches the server), or configure the middleware and run it "self-hosted" with the use and run! methods (in which case Sinatra does it, using Rack's handlers). It's interesting to compare Rack::Server with Sinatra::Base.run!.

(2) https://github.com/rails/rails/blob/master/railties/lib/rails/commands/server.rb#L6

Looks like Rails does it a bit differently, subclassing Rack::Server, which builds the app 'internally' instead of through a rackup shell command. But the basic app which is sent in to this class to be wrapped up with middleware, (I can't find where at the moment), is your Application subclass, Application < Engine, and it's in Engine that you find #call, which ultimately seems to delegate to a ActionDispatch::Routing::RouteSet object:

https://github.com/rails/rails/blob/master/railties/lib/rails/engine.rb#L478

Which drops down to Journey: https://github.com/rails/journey/blob/master/lib/journey/router.rb#L53

Maybe others with more experience with Rails can fill in the details.

The point being, as skade says, when you peel away all the layers of the onion, Sinatra and Rails apps are both plain old ruby classes that conform to rack's simple interface -- they don't need an adapter.

*EDIT: Note def initialize(app) is technically only needed for making middleware from Sinatra::Base.

codereading commented 12 years ago

Ahh thank you everyone for your input. Skade that confirms my initial impression. The wording in the rubylearning article is a bit confusing.

ankitconsultant commented 12 years ago

Thanks Skade for the nice explanations. Inline with my initial understanding and clarified little doubts in the back of my mind.

abelmartin commented 12 years ago

+3 to @codereading's question & +3 to @skade's explanation & diagram.

My mental picture of rack was exactly what @codereading described. I'm happy to be set straight. :)

agis commented 12 years ago

Very straightforward explanation. Finally I get it, thanks @skade.