Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
codereading opened this issue May 4, 2012 · 8 comments
Open

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

codereading opened this issue May 4, 2012 · 8 comments

Comments

@codereading
Copy link
Collaborator

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
Copy link

skade commented May 4, 2012

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
Copy link
Member

samnang commented May 4, 2012

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
Copy link

skade commented May 4, 2012

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

@ericgj
Copy link
Member

ericgj commented May 5, 2012

@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
Copy link
Collaborator Author

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

@ankitconsultant
Copy link

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

@abelmartin
Copy link

+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
Copy link

agis commented May 7, 2012

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants