-
Notifications
You must be signed in to change notification settings - Fork 2
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
Once again, where to start? #6
Comments
One place to start is from the place you'd typically interact with Rack in a web application -- which is to say, the |
I have started a thread in google group about this topic, and I share my thought there: https://groups.google.com/forum/#!msg/codereading/40PYinWmMa0/a-1P--HHZcIJ I'm not sure should we keep discussion here or in google group? |
Actually, not so many of them are connected. Because most Rack middlewares are single-file affairs but live under the I would start with: |
Samsang thanks for posting that topic. It answers a question that im sure many people here in the group are wondering. Perhaps copying it over here is a better home for it as it is is rack specific. But I think we do need a topic in the main google group for a general "How do i start". How about just editing your google group post a bit such as the title to "How do I start?" I really wish we could have all our conversation in one place with the ability to group conversations around projects and have the features github provides. It feels a bit disjointed but I guess for now we will just have to put up with it. Thanks again for the great post. |
Hi Jesse, Ill try to explain how I start. Im writing this for everyone though so forgive me for my dummy style prose but it might come in handy for someone down the line. Regarding where to start I usually create a very very simple example app that utilizes the gem's core feature and ignores the fancy parts. Using that, I'll look for an entry point and try to follow the code right to the end. That's usually enough for me to orientate myself in the code without getting lost in features. And after that I can start to explore some of the more interesting use cases by expanding the example code. With Rack it's core feature is allowing you to return a response to a simple request. The fancy parts would be its ability to stack middleware together. I guess the simplest of apps to use as an example for code reading would be a hello world app, an app which given a request to "localhost:3000" simply displays hello world. In rack you could achieve this by passing it a proc such as this
Thats an array including a status of 200, empty header hash (you could put "content-type" => "text/plain" etc here) and the content hello world. That's the response but how about actually creating the rack app. Well there are a few ways you could create a simple hello world app and I'll let everyone read up on that here http://gallery.mailchimp.com/e49655551a5bb47498310c7de/files/RackIntro.pdf. But a common way rack apps are built is to make use of rackup. Rackup comes with rack and is a dsl. It's job is to make it easy to configure rack middleware together. It also understands your environment so can automatically pick and run a server for you i.e. WEBrick etc. Theres no middleware for helloworld but the later reason is good enough to use this approach. And since there's little code to write and it's common practice its probably good for code reading purposes. Rackup expects a config file by the name of config.ru and you simple pass your proc to rackup#run
then in your console
check your broswer at the relevant port and bam hello world. So now for code reading. Looking at the code I'd say the entry point would be the run command. I'd go looking in the source code for that. I'd that see what happens to the proc and how that ends up getting displayed back in the browser. Of course another entry point would be trying to figure out the first point rack receives the request from the server, tracing that through untill you reach the run method above. One way you could do that would be (besides just scouring the source code) to use pry or ruby-debugger ( for 1.9.3 i think you need it's cousin debugger ). You could put a break point at some high point in the source code, make the request in your browser and execution would stop and you can step through finding your way. One common need for a rack app would be the ability to return different content depending on the request url. If you look at this very simple app https://github.com/rack/rack/wiki/Rack-app-with-uri-and-HTTP-specific-responses demonstrating this you'll see rack provides some helper methods to query the request. That might be worth looking at, it might not be the most interesting of code reading but you'll find helpers in there you could use in your own apps. Lastly comes the juicy part. I havent even got to here yet but Rack is famous for the way it allows middleware to be stacked. I think it utilizes the decorator pattern to do this. This is actually one of the things im most interested in studying. Id create some middleware of my own, configure it in config.ru and again using pry or debugger figure out how it all comes together. Hope this is useful. If you're still stumped feel free to ask more questions! |
@codereading I clone my topic from google group. So here what I started with:
That's a good idea, but I think we should create a new post with that topic, so it's not tight coupling with feature project in it. |
Very helpful info, however I find myself stuck at this point:
That's the entry point you were talking about, however I don't know how to proceed after this. I understand what this method does (assigns to the instance variable |
@agis, it's a little tricky to follow since rack gives you various entry points into building an app. Did you follow the path from bin/rackup? You'll see that the place that the @use.reverse.inject(app) { |a,e| e[a] } Keeping in mind that for simple cases, this can be reduced to @use.reverse.inject(@run) { |a,e| e[a] } It takes a while to puzzle out how this line works to create the rack pipeline, but IMO it's fundamental to understanding the rest of the codebase, & how middleware work. |
Hm I see. If I'm getting it right, But I'm not sure about what the inject is doing. I think it's inserting the It's confusing :P |
https://github.com/rack/rack/blob/master/lib/rack/builder.rb#L77-83 |
Is this the general way rackbuilder works then. Those few lines with inject and use seem to play a massive role If we have define three middlewares A,B and C like so
and define our rackbuilder like so
Then when a request for "posts/index" comes from the client rack will eventually call rackbuilder#call rackbuilder#use will create an array of procs. Each proc will be responsible for instantiating the middleware defined above in the config.ru file. I.e
again working backwards, #use is used here in #to_app
on the first iteration app will be the main app i.e. the rails app. Looking at the procs in array above, the middleware class "C" is then instantiated with the railsapp.
This results in a chain of middlewares getting instantiated. Each middleware will have an attribute @app that is set to an instance of the middleware above it in the list of use statements in config.ru. The last middleware A will be the last result of the inject method and is returned to the calling method. This calling method is rackbuilder#call which immediately calls A's own call method. Assuming the request is for a posts/index action The rails app resturns We return to C's call method where it cusomizes the returned response with its own message. B and A do the same resulting in a final response of Is that about right? |
The logic is kinda complex isn't it? So the order in which the middlewares are called through #use is important and matters. But they're ran after the main app (the one that is called by For example, we have a Rails app and I'm using OmniAuth which is another Rack middleware. So if we were going to implement this in a config.ru, it would look like this:
So the RailsApp would be instatiated first and then the OmniAuth middleware would follow, providing its functionality. And if we were going to add another middleware like this:
then the OmniAuth would be instatiated after the new middleware. Is that right? |
I've done some more research and I now believe that the middlewares are run first and the app is the last that runs. Based on this talk: http://confreaks.com/videos/49-mwrc2009-in-a-world-of-middleware-who-needs-monolithic-applications |
Middlewares can run before and after an application. It all depends on where your middleware does its work: class MyMiddleware
def call(env)
do_stuff_before(env)
status, headers, body = @app.call(env) # call next middleware
do_stuff_after(status, headers, body, env)
end
end Imagine the Middleware stack as a linked list: each middleware knows the next one, but nothing else. Control is passed by calling the next middleware. At the end, control is passed back when the call stack is unwound. As long as every call to There are some subtleties to all this. First of all, The second one is that Middlewares and the application are singletons: you class MyApp
def self.call(env) #class method!
new(env).call!
end
def call!(env) # use call! to announce that this should only be called once
#do_your_work
end
end For middlewares, which get constructed at stack build time to ensure that class Middleware
def call(env)
self.clone.call!(env)
end
def call!(env) # be aware that env is still not copied
@dirty = something(env)
end
end Hope that clears things up a little. Regards, |
I'm feeling grateful that you're around. Thank you. |
There are so many different source files in the lib/rack/ folder and I feel like a lot of them are interconnected. Does anyone have a good strategy for where to start and how to move through them?
Thanks!
The text was updated successfully, but these errors were encountered: