Sei sulla pagina 1di 21

A Quick Introduction to Rack

Whats Rack?
In the words of the author of Rack Christian Neukirchen: Rack aims to provide a minimal API for connecting web servers supporting Ruby (like W !rick" #ongrel etc$% and Ruby web frameworks (like Rails" &inatra etc$%$ Web frameworks such as &inatra are built on top of Rack or have a Rack interface for allowing web application servers to connect to them$ 'he premise of Rack is simple it (ust allows you to easily deal with )''P re*uests$ )''P is a simple protocol: it basically describes the activity of a client sending a )''P re*uest to a server and the server returning a )''P response$ !oth )''P re*uest and )''P response in turn have very similar structures$ A )''P re*uest is a triplet consisting of a method and resource pair" a set of headers and an optional body while a )''P response is in triplet consisting of a response code" a set of headers and an optional body$ Rack maps closely to this$ A Rack application is a Ruby ob(ect that has a call method" which has a single argument" the environment" (corresponding to a )''P re*uest% and returns an array of + elements" status" headers and body (corresponding to a )''P response%$ Rack includes handlers that connect Rack to all these web application servers (W !rick" #ongrel etc$%$ Rack includes adapters that connect Rack to various web frameworks (&inatra" Rails etc$%$ !etween the server and the framework" Rack can be customi,ed to your applications needs using middleware$ 'he fundamental idea behind Rack middleware is come between the calling client and the server" process the )''P re*uest before sending it to the server" and processing the )''P response before returning it to the client$

A Rack App

Documentation
http:--rack$rubyforge$org-doc-

Installing Rack
/ote that I have Ruby .$0$1 installed on a Windows bo2 and all the programs in this article have been tested using that$ 3et4s check if we already have rack with us$ 5pen a command window and type:
irb --simple-prompt >> require 'rack' => true >>

6es" rack4s already there on your machine$ If rack4s not there you will get an error like:
LoadError: no such file to load -- rack

6ou can install rack by opening a new command window and typing:
gem install rack

A quick visit to Rubys roc ob!ect


Remember the proc ob(ect from Ruby7 Blocks are not objects" but they can be converted into ob(ects of class Proc$ 'his can be done by calling the lambda method of the class Object$ A block created with lambda acts like a Ruby method$ 'he class Proc has a method call that invokes the block$ In irb type:
>> m !rack!proc = lambda "puts '#ack $ntro'% => &'Proc:()*fc+(,-./irb0:1/lambda0> >> & method call in2okes the block 3> m !rack!proc4call #ack $ntro => nil >>

A sim le Rack a

" my#rack# roc

As mentioned earlier" our simple Rack application is a Ruby ob(ect (not a class% that responds to call and takes e2actly one argument" the environment$ 'he environment must be a true instance of 5ash$ 'he app should return an Array of e2actly three values: the status code (it must be greater than or e*ual to .88%" the headers (must be a hash%" and the body (the body commonly is an Array of &trings" the application instance itself" or a 9ile:like ob(ect$ 'he body must respond to method each and must only yield 6tring values$% 3et us create our new proc ob(ect$ 'ype:
>> m !rack!proc = lambda " 7en27 81((9 "%9 8:5ello4 ;he time is &";ime4no<%:== % => &'Proc:()*f>c,?-./irb0:?/lambda0> >>

/ow we can call the proc ob(ect my;rack;proc with the call method$ 'ype:
>> m !rack!proc4call/"%0 => 81((9 "%9 8:5ello4 ;he time is 1(**-*(-1> (+:*-:?@ A(?,(:== >>

my#rack# roc is our single line Rack application$ In the above e2ample" we have used an empty hash for headers$ Instead" let4s have something in the header as follows:
>> m !rack!proc = lambda " 7en27 81((9 ":Bontent-; pe: => :te)tCplain:%9 8:5ello4 ;he time is &";ime4no<%:== % => &'Proc:()*f>c,?-./irb0:?/lambda0> >>

/ow we can call the proc ob(ect my;rack;proc with the call method$ 'ype:
>> m !rack!proc4call/"%0 => 81((9 ":Bontent-; pe: => :te)tCplain:%9 8:5ello4 ;he time is 1(**-*(-1> (+:*-:?@ A(?,(:== >>

We can run our Rack application (my#rack# roc% with any of the Rack handlers$ 'o look at the Rack handlers available" in irb type:
>> #ack::5andler4constants => 8:BD$9 :EastBD$9 :Fongrel9 :E2entedFongrel9 :6<iftipliedFongrel9 :GEHrick9 :L6G69 :6BD$9 :;hin= >>

'o get a handler for say W$%rick (the default W$%rick" web application server" that comes along with Ruby%" type:
>> #ack::5andler::GEHrick => #ack::5andler::GEHrick >>

All of these handlers have a common method called run to run all the Rack based applications$
>> #ack::5andler::GEHrick4run m !rack!proc 81(**-*(-1> *(:((:>?= $IEO GEHrick *4,4* 81(**-*(-1> *(:((:>?= $IEO rub *4+41 /1(**-(J-(+0 8i,-@-ming<,1= 81(**-*(-1> *(:((:>?= $IEO GEHrick::5;;P6er2er&start: pid=*J-- port=-(

5pen a browser window and type the url: http:--localhostIn your browser window" you should see a string" something like this:
5ello4 ;he time is 1(**-*(-1> *(:(1:1( A(?,(

Note: If you already have something running at port <8" you can run this app at a different port" say 0<=>$ 'ype:
>> #ack::5andler::GEHrick4run m !rack!proc9 :Port => +-J@ 81(**-*(-1> **:,1:1*= $IEO GEHrick *4,4* 81(**-*(-1> **:,1:1*= $IEO rub *4+41 /1(**-(J-(+0 8i,-@-ming<,1= 81(**-*(-1> **:,1:1*= $IEO GEHrick::5;;P6er2er&start: pid=>-( port=+-J@

5pen a browser window and type the url: http:--localhost:0<=>In your browser window" you should see a string" something like this:
5ello4 ;he time is 1(**-*(-1> *(:(1:1( A(?,(

Another Rack a

" my#method

A Rack app need not be a lambda? it could be a method$ 'ype:


>> >> >> => def m !method en2 81((9 "%9 8:method called:== end nil

We declare a method my;method that takes an argument env$ 'he method returns three values$ /e2t type:
>> #ack::5andler::GEHrick4run method/:m !method0 81(**-*(-1> *>:,1:(?= $IEO GEHrick *4,4* 81(**-*(-1> *>:,1:(?= $IEO rub *4+41 /1(**-(J-(+0 8i,-@-ming<,1= 81(**-*(-1> *>:,1:(?= $IEO GEHrick::5;;P6er2er&start: pid=*@>> port=-(

5pen a browser window and type the url: http:--localhostIn your browser window" you should see something like this:
method called

ob(ects are created by Object&method$ 'hey are associated with a particular ob(ect (not (ust with a class%$ 'hey may be used to invoke the method within the ob(ect$ 'he Fethod4call method invokes the method with the specified arguments" returning the method4s return value$
Fethod

Press Ctrl&C in irb to stop the W$%rick server$

'sing racku
'he rack gem comes with a bunch of useful stuff to make life easier for a rack application developer$ rackup is one of them$

rackup is a useful tool for running Rack applications$ rackup automatically figures out the environment it is run in" and runs your application as 9astABI" ABI" or standalone with #ongrel or W !rick all from the same configuration$ 'o use rackup" you4ll need to supply it with a rackup config file$ !y convention" you should use $ru e2tension for a rackup config file$ &upply it a run Rack5b(ect and you4re ready to go:
K rackup config4ru

!y default" rackup will start a server on port 0101$ 'o view rackup help" open a command window and type:
K rackup --help

3et us create a config$ru file that contains the following:


run lambda " 7en27 81((9 ":Bontent-; pe: => :te)tCplain:%9 8:5ello4 ;he time is &";ime4no<%:== %

'his file contains run" which can be called on anything that responds to a 4call$ 'o run our rack app" in the same folder that contains config$ru" type:
K rackup config4ru 81(**-*(-1> *?:*-:(,= $IEO GEHrick *4,4* 81(**-*(-1> *?:*-:(,= $IEO rub *4+41 /1(**-(J-(+0 8i,-@-ming<,1= 81(**-*(-1> *?:*-:(,= $IEO GEHrick::5;;P6er2er&start: pid=,,(> port=+1+1

5pen a browser window and type the url: http:--localhost:0101In your browser window" you should see something like this:
5ello4 ;he time is 1(**-*(-1> *?:*-:*( A(?,(

Press Ctrl&C to stop the W$%rick server$ /ow let4s move our application from the config$ru file to my;app$rb file as follows:
& m !app4rb class F Lpp def call en2 81((9 ":Bontent-; pe: => :te)tChtml:%9 8:5ello #ack Participants:== end end

Also" our config$ru will change to:


require '4Cm !app' run F Lpp4ne<

'o run our rack app" in the same folder that contains config$ru" type:

rackup config4ru 81(**-*(-1? (@:*-:*@= $IEO 81(**-*(-1? (@:*-:*@= $IEO 81(**-*(-1? (@:*-:*@= $IEO

GEHrick *4,4* rub *4+41 /1(**-(J-(+0 8i,-@-ming<,1= GEHrick::5;;P6er2er&start: pid=111> port=+1+1

5pen a browser window and type the url: http:--localhost:0101In your browser window" you should see something like this:
5ello #ack Participants

Press Ctrl&C to stop the W$%rick server$

De loying (ure Rack A

s to )eroku

We shall now download and install Bit$ 'he precompiled packages are available here: http:--git$or$c,&elect the relevant package for your operating system$ Bit still has some issues on the Windows platform but for normal usage the msysgit package shouldn4t let you down$ Download and install it from the url: http:--code$google$com-p-msysgit-downloads-list &elect the current version available$ Install by running the E installer$ When asked" it is recommended for Windows users to use the FGse Bit !ash onlyH option$ Please ensure that you are connected to the internet and then create an account on )eroku (obviously do this only once% if you don4t have one http:--heroku$com-signup$ 5pen a new command window to install the )eroku gem file$ 'ype:
gem install heroku

/e2t" create a new folder" say rackheroku$ Assuming that you have Bit installed" open a !ash shell in that folder$ 6ou now need to identify yourself to Bit (you need to do this only once%$ With the bash shell still open type in the following:
git config --global user4name :Mour name here: git config --global user4email :Mour email id:

Bit does not allow accented characters in user name$ 'his will set the info stored when you commit to a Bit repository$ Bit has now been set up$ 'he first step in using Bit is to create your &&) Iey$ 'his will be used to secure communications between your machine and other machines" and to identify your source code changes$ (If you already have an &&) key for other reasons" you can use it here" there is nothing Bit:specific about this$%

>

'o create our ssh key" open a new command window and type:
K ssh-ke gen -B :username.email4com: -t rsa

(with your own email address" of course%$ Accept the default key file location$ When prompted for a passphrase" make one up and enter it$ If you feel confident that your own machine is secure" you can use a blank passphrase" for more convenience and less security$ /ote where it told you it stored the file$ 5n my Windows bo2" it was stored in FA:JDocuments and &ettingsJAJ$sshJH$ #emori,e your passphrase carefully$ If you forget it" you will /5' be able to recover it$ 5pen the public file id;rsa$pub with a te2t editor$ 'he te2t in there is your Fpublic &&) keyH$ Gpload your public key (do it only once%:
K heroku ke s:add

6ou4ll be prompted for your username and password the first time you run a heroku command? they4ll be saved on K-$heroku-credentials so you won4t be prompted on future runs$ It will also upload your public key to allow you to push and pull code$ In order for )eroku to know what to do with your Rack app" create a config$ru (ru stands for Rack up% in the rackheroku folder$ 'he contents are:
require '4Cm !app' run F Lpp4ne<

Also" copy the my;app$rb file to the rackheroku folder$ It4s contents are:
& m !app4rb class F Lpp def call en2 81((9 ":Bontent-; pe: => :te)tChtml:%9 8:5ello #ack Participants:== end end

In the already open command window" we will install bundler$ 'ype:


gem install bundler

Alose the command window$ /e2t" we will install the re*uired gems (if any% via bundler$ In the already open Bit !ash shell for folder rackheroku type:
K bundle init Griting ne< Demfile to c:CrackherokuCDemfile

dit the created Bemfile with your preferred te2t editor to let it look like this:
source :http:CCrub gems4org: gem 'rack'

/ow we need to tell !undler to check if we4re missing the gems our application depends on" if so" tell it to install them$ In your open !ash shell type:
K bundle check ;he Demfile's dependencies are satisfied

9inally in the open !ash shell" type:


K bundle install

'his will ensure all gems specified on Bemfile" together with their dependencies" are available for your application$ Running Fbundle installH will also generate a FBemfile$lockH file$ 'he Bemfile$lock ensures that your deployed versions of gems on )eroku match the version installed locally on your development machine$ /e2t we set up our local app to use Bit$ 'ype:
K git init K git add 4 K git commit -m :#ack app first commit:

3et4s create our Rack app on )eroku$ 'ype:


K heroku create Breating quiet-<inter-,J>*444 done9 stack is bamboo-mri-*4+41 http:CCquiet-<inter-,J>*4heroku4comC 7 git.heroku4com:quiet-<inter-,J>*4git Dit remote heroku added

'he app has been created and two GR3s are provided$ 5ne is for the web face of your new app i$e$ http:--*uiet:winter:+=@.$heroku$com- If you visit that GR3 now" you4ll see a standard welcome page" until you push your application up$ 'he other one is for the Bit repository that you will push your code to$ /ormally you would need to add this as a git remote? the Fheroku createH command has done this for you automatically$ Do note that the output from the create command will be different for each one of you$ /ow let us deploy our code to )eroku$ 'ype:
git push heroku master

At this stage we can rename our app to rackheroku$ 'ype:


K heroku rename rackheroku http:CCrackheroku4heroku4comC 7 git.heroku4com:rackheroku4git Dit remote heroku updated

5ur app is now deployed to )eroku$ 5pen a new browser window and type http:--rackheroku$heroku$comIn the browser window" you should see:
5ello #ack Participants from across the globe

<

'sing Rack middle*are


We mentioned earlier that between the server and the framework" Rack can be customi,ed to your applications needs using middleware$ 'he fundamental idea behind Rack middleware is come between the calling client and the server" process the )''P re*uest before sending it to the server" and processing the )''P response before returning it to the client$ Rackup also has a use method that accepts a middleware$ 3et us use one of Rack4s built:in middleware$ 6ou must have observed that every time we make a change to our config$ru or my;app$rb files" we need to restart the W !rick server$ 'o avoid this" let4s use one of Rack4s built:in middleware #ack::#eloader$ dit config$ru to have:
require '4Cm !app' use #ack::#eloader run F Lpp4ne<

In the same folder" we have the my;app$rb file$ It4s contents are:
& m !app4rb class F Lpp def call en2 81((9 ":Bontent-; pe: => :te)tChtml:%9 8:5ello #ack Participants from across the globe:== end end

'o run our rack app" in the same folder that contains config$ru" type:
K rackup config4ru

5pen a browser window and type the url: http:--localhost:0101-$ In your browser window" you should see something like this:
5ello #ack Participants from across the globe

#odify my;app$rb and instead of F)ello Rack Participants from across the globeH type F)ello Rack Participants from across the worldL Refresh the browser window and you should see:
5ello #ack Participants from across the <orldN

'here was no need to re:start the W !rick server$ 6ou can now press Ctrl&C to stop the W$%rick server$

Writing our o*n +iddle*are

9or all practical purposes" a middleware is a rack application that wraps up an inner application$ 5ur middleware is going to do something very simple$ We will append some te2t to the body being sent by our inner application$ 3et us write our own middleware$ We will create a middleware class called F #ackFiddle<are$ )ere4s the skeleton program:
class F #ackFiddle<are def initialiOe/appl0 .appl = appl end def call/en20 end end

In the code above" to get the original body" we initiali,e the class F #ackFiddle<are by passing in the inner application (appl%$ /e2t we need to call this appl:
def call/en20 status9 headers9 bod application end = .appl4call/en20 & <e no< call the inner

In the above code" we now have access to the original body" which we can now modify$ Rack does not guarantee you that the body would be a string$ It could be an ob(ect too$ )owever" we are guaranteed that if we call 4each on the body" everything it returns will be a string$ 3et4s use this in our call method:
def call/en20 status9 headers9 bod = .appl4call/en20 append!s = :444 greetings from #ub LearningNN: 8status9 headers9 bod '' append!s= end

)ere4s our completed middleware class:


class F #ackFiddle<are def initialiOe/appl0 .appl = appl end def call/en20 status9 headers9 bod = .appl4call/en20 append!s = :444 greetings from #ub LearningNN: 8status9 headers9 bod '' append!s= end end

5ur my;app$rb file remains the same$ It4s contents are:


& m !app4rb class F Lpp def call/en20

.8

81((9 ":Bontent-; pe: => :te)tChtml:%9 8:5ello #ack Participants from across the globe:== end end

dit config$ru to have:


require '4Cm !app' require '4Cm rackmiddle<are' use #ack::#eloader use F #ackFiddle<are run F Lpp4ne<

'o run our rack app" in the same folder that contains config$ru" type:
K rackup config4ru

5pen a browser window and type the url: http:--localhost:0101-$ In your browser window" you should see something like this:
5ello #ack Participants from across the globe444 greetings from #ub LearningNN

Rack and ,inatra


3et us see how to use Rack with a &inatra app$ Areate a folder racksinatra$ /e2t" let us install &inatra:
gem install sinatra

)ere4s a trivial &inatra program m !sinatra4rb in the folder racksinatra:


require 'sinatra' get 'C' do 'Gelcome to all' end

&inatra can use Rack middleware$ 5ur trivial middleware class will intercept the re*uest from the user" calculate and display the response time on the console and pass on the unaltered re*uest to the &inatra app$ In the folder racksinatra write the middleware class namely rackmiddle<are4rb$
class #ackFiddle<are def initialiOe/appl0 .appl = appl end def call/en20 start = ;ime4no< status9 headers9 bod = .appl4call/en20 & call our 6inatra app stop = ;ime4no< puts :#esponse ;ime: &"stop-start%: & displa on console 8status9 headers9 bod = end end

..

In order for )eroku to know what to do with our &inatra app" create a config$ru in the folder racksinatra:
require :4Cm !sinatra: run 6inatra::Lpplication

#odify the &inatra app to use our middleware:


& m !sinatra4rb require 'sinatra' require '4Crackmiddle<are' use #ackFiddle<are get 'C' do 'Gelcome to all' end

/ow open a Bit !ash shell for folder racksinatra and type:
K bundle init

dit the created Bemfile with your preferred te2t editor to let it look like this:
source :http:CCrub gems4org: gem 'sinatra'

In your open !ash shell type:


K bundle check

9inally in the open !ash shell" type:


K bundle install

&et up your local app to use Bit$ 'ype:


K git init K git add 4 K git commit -m :6inatra#ack app:

Areate the app on )eroku$ In the open bash shell" type:


K heroku create

5n my machine it showed:
Breating <arm-<inter-@J-?444 done9 stack is bamboo-mri-*4+41 http:CC<arm-<inter-@J-?4heroku4comC 7 git.heroku4com:<arm-<inter-@J-?4git Dit remote heroku added

3et us rename the app to racksinatra:


K heroku rename racksinatra

9inally" let us push our app to )eroku: .1

K git push heroku master

'he app is now running on )eroku$ 6ou can take a look at it" in your browser type: http:--racksinatra$heroku$com-$ 'o check whether the MMM Response 'ime MMM: has been displayed on the )eroku console" type:
K heroku logs -s app -n ?

where -s app shows the output from your app and -n ? retrieves C log lines$ 9or me it showed:
38,@m1(**-*(-1@;(?:>(:(@A((:(( app8<eb4*=:38(m PPP #esponse ;ime PPP: (4(((1+J,-?

'hat4s it$ I hope you liked this introductory article on Rack$ )appy RackingLL &atish 'alim is an author and founder of Ruby3earning$com and Ruby3earning$org where over +C888 participants have learnt Ruby programming from across the globe$

.+

Rack from the Beginning


Nuly .@" 18.1 O rack tutorials Rack is the )''P interface for Ruby$ Rack defines a standard interface for interacting with )''P and connecting web servers$ Rack makes it easy to write )''P facing applications in Ruby$ Rack applications are shockingly simple$ 'here is the code that accepts a re*uest and code serves the response$ Rack defines the interface between the two$

Dead Simple Rack Applications


Rack applications are ob(ects that respond to call$ 'hey must return a PtripletP$ A triplet contains the status code" headers" and body$ )ereQs an e2ample class that shows Phello world$P
class 5elloGorld def response 81((9 "%9 '5ello Gorld'= end end

'his class is not a Rack application$ It demonstrates what a triplet looks like$ 'he first element is the )''P response code$ 'he second is a hash of headers$ 'he third is an enumerable ob(ect representing the body$ We can use our hello world class to create a simple rack app$ We know that we need to create an ob(ect that responds to call$ call takes one argument: the rack environment$ WeQll come back to the en2 later$
class 5elloGorldLpp def self4call/en20 5ello<Gorld4ne<4response end end

IQve made a simple class that implements call$ It returns the response from the 5elloGorld class$ /ow we need to put this online$ We have implemented one side of the wall$ /ow we need write the other side$ Rack includes a 6er2er class$ 'his is the simplest way to serve rack applications$ It includes daemoni,ation and things like that$ It works but itQs not meant from production applicationsL 3etQs create a simple ruby script to serve 5elloGorldLpp
& hello!<orld4rb require 'rack' require 'rackCser2er' class 5elloGorld def response 81((9 "%9 '5ello Gorld'= end end class 5elloGorldLpp def self4call/en20 5elloGorld4ne<4response end end

.@

#ack::6er2er4start :app => 5elloGorldLpp

)ereQs what happens when you run this script:


K rub hello!<orld4rb >> ;hin <eb ser2er /2*4>4* codename Bhromeo0 >> Fa)imum connections set to *(1> >> Listening on (4(4(4(:-(-(9 B;#LAB to stop

&imply open http:CClocalhost:-(-( and youQll see P)ello WorldP in the browser$ ItQs not fancy but you (ust wrote your first rack appL We didnQt write our own server and thatQs ok$ #atter of fact" thatQs fantastic$ 5dds are you will never need to write your own server$ 'here are plenty of servers to choose from: 'hin" Gnicorn" Rainbows" Boliath" Puma" and Passenger$ 6ou donQt want to write those$ 6ou want to write applications$ 'hatQs what we wrote$

Env
I skipped over over what en2 is in the previous section$ 'hatQs because we didnQt need it yet$ 'he en2 is a 5ash that meets the rack spec$ 6ou can read the spec here$ It defines incoming information$ 5utgoing data must be triplets$ 'he en2 gives you access to incoming headers" host info" *uery string and other common information$ 'he en2 is passed to the application which decides what to do$ 5ur 5elloGorldLpp didnQt care about it$ 3etQs update our 5elloGorldLpp to interact with incoming information$
class 5elloGorldLpp def self4call/en20 81((9 "%9 :5ello Gorld4 Mou said: &"en28'QRE#M!6;#$ID'=%:= end end #ack::6er2er4start :app => 5elloGorldLpp

/ow visit http:CClocalhost:-(-(3message=foo and youQll see PmessageRfooP on the page$ If your more curious about en2 you can do this:
class En2$nspector def self4call/en20 81((9 "%9 en24inspect= end end #ack::6er2er4start :app => En2$nspector

)ereQs the tl?dr of what basic en2 looks like$ ItQs (ust a standard )ash instance$
" :6E#SE#!6OE;GL#E:=>:thin *4>4* codename Bhromeo:9 :6E#SE#!ILFE:=>:localhost:9 :rack4input:=>&'6tring$O:()((Jfa*bce(,+f->9 :rack42ersion:=>8*9 (=9 :rack4errors:=>&'$O:'6;TE##>>9 :rack4multithread:=>false9

.C

:rack4multiprocess:=>false9 :rack4run!once:=>false9 :#EQRE6;!FE;5OT:=>:DE;:9 :#EQRE6;!PL;5:=>:Cfa2icon4ico:9 :PL;5!$IEO:=>:Cfa2icon4ico:9 :#EQRE6;!R#$:=>:Cfa2icon4ico:9 :5;;P!SE#6$OI:=>:5;;PC*4*:9 :5;;P!5O6;:=>:localhost:-(-(:9 :5;;P!BOIIEB;$OI:=>:keep-ali2e:9 :5;;P!LBBEP;:=>:PCP:9 :5;;P!R6E#!LDEI;:=> :FoOillaC?4( /FacintoshU $ntel Fac O6 V *(!J!>0 LppleGebWitC?,@4** /W5;FL9 like Decko0 BhromeC1(4(4**,14>J 6afariC?,@4**:9 :5;;P!LBBEP;!EIBOT$ID:=>:gOip9deflate9sdch:9 :5;;P!LBBEP;!LLIDRLDE:=>:en-R69enUq=(4-:9 :5;;P!LBBEP;!B5L#6E;:=>:$6O---?+-*9utf--Uq=(4J9PUq=(4,:9 :5;;P!BOOW$E:=> :!gauges!unique! ear=*U !gauges!unique!month=*:9 :DL;EGLM!$I;E#ELBE:=>:BD$C*41:9 :6E#SE#!PO#;:=>:-(-(:9 :QRE#M!6;#$ID:=>::9 :6E#SE#!P#O;OBOL:=>:5;;PC*4*:9 :rack4url!scheme:=>:http:9 :6B#$P;!ILFE:=>::9 :#EFO;E!LTT#:=>:*1J4(4(4*:9 :as nc4callback:=>&'Fethod: ;hin::Bonnection&post!process>9 :as nc4close:=>&'E2entFachine::TefaultTeferrable:()((Jfa*bce,?b-%

6ou may have noticed that the en2 doesnQt do any fancy parsing$ 'he *uery string wasnQt a hash$ It was the string$ 'he en2 is raw data$ I like this design principle a lot$ Rack is very simple to understand and use$ If you wanted you could only work with hashes and triplets$ )owever thatQs (ust tedious$ Aomple2 applications need abstractions$ nter #ack::#equest and #ack::#esponse$

Abstractions
is an abstraction around the en2 hash$ It provides access to thinks like cookies" P5&' paramters" and other common things$ It removes boiler plate code$ )ereQs an e2ample$
#ack::#equest class 5elloGorldLpp def self4call/en20 request = #ack::#equest4ne< en2 request4params & contains the union of DE; and PO6; params request4)hr3 & requested <ith LXLV require4bod & the incoming request $O stream if request4params8'message'= 81((9 "%9 request4params8'message'== else 81((9 "%9 '6a something to meN'= end end end

is simply a pro2y for the en2 hash$ 'he underlying en2 hash is modified so keep that in mind$
#ack::#equest

.>

is an abstraction around generating response triplets$ It simplifies access to headers" cookies" and the body$ )ereQs an e2ample:
#ack::#esponse class 5elloGorldLpp def self4call/en20 response = #ack::#esponse4ne< response4<rite '5ello Gorld' & <rite some content to the bod response4bod = 8'5ello Gorld'= & or set it directl response8'V-Bustom-5eader'= = 'foo' response4set!cookie 'bar'9 'baO' response4status = 1(1 response4finish & return the generated triplet end end

'hese are basic abstractions$ 'hey donQt re*uire much e2planation$ 6ou can learn more about them by reading the documentation$ /ow that we have some basic abstractions we can start to make more comple2 applications$ ItQs hard to make an application when all the logic is contained in one class$ Applications are always composed of different classes$ ach class has a single responsibility$ 'his is the &RP (&ingle Responsibility Principle%$ 'hese discrete chunks are called PmiddlewareP$

Middleware
Rack applications are simply ob(ects that respond to call$ We can do whatever we want inside call" for instance we can delegate to another class$ )ereQs an e2ample:
class ParamsParser def initialiOe/app0 .app = app end def call/en20 request = #ack::#equest4ne< en2 en28'params'= = request4params app4call en2 end end class 5elloGorldLpp def self4call/en20 parser = ParamsParser4ne< self en2 = parser4call en2 & en28'params'= is no< set to a hash for all the input paramters 81((9 "%9 en28'params'=4inspect= end end

I admit this e2ample is *uite contrived$ 6ou want not do this in practice$ 'he point is to illustrate that you can manipulate env (or response%$ 6ou can create a middleware stack as deep as you like$ ach middleware simply calls the ne2t one and returns its value$ 'his is an e2ample of the builder pattern$ Aomposing Rack applications is so common (and re*uired%

.=

that Rack includes a class to make this easy$ !efore we move to the ne2t step" letQs define what a middleware looks like:
class Fiddle<are def initialiOe/app0 .app = app end & ;his is a :null: middl<are because it simpl calls the ne)t one4 & Ge can manipulate the input before calling the ne)t middle<are & or manipulate the response before returning up the chain4 def call/en20 .app4call en2 end end

Composing Rack Apps from Middleware


creates up a middleware stack$ ach ob(ect calls the ne2t one and returns its return value$ Rack contains a bunch of handy middlewares$ 'hey have one for caching and encodings$ 3etQs increase the 5elloGorldLppQs performance$
#ack::Huilder & this returns an app that responds to call cascading do<n the list of & middle<ares4 ;echnicall there is no difference bet<een :use: and & :run:4 :run: is just there to illustrate that it's the end of the & chain and it does the <ork4 app = #ack::Huilder4ne< do use #ack::Etag & Ldd an E;ag use #ack::BonditionalDet & 6upport Baching use #ack::Teflator & DYip run 5elloGorldLpp & 6a 5ello end #ack::6er2er4start :app => app app

has a call method that generates this call tree:

#ack::Etag #ack::BonditionalDet #ack::Teflator 5elloGorldLpp

IQm not going to cover what those middlewares do because thatQs not important$ 'his is an e2ample of how you can build up functionality in applications$ #iddlewares are very powerful$ 6ou can add manipulate incoming data before hitting the ne2t one or modify the response from an e2isting one$ 3etQs create some for practice$
class EnsureXson#esponse def initialiOe/app0 .app = app end & 6et the 'Lccept' header to 'applicationCjson' no matter <hat4 & 5opefull the ne)t middle<are respects the accept header :0 def call/en20 en28'5;;P!LBBEP;'= = 'applicationCjson' .app4call en2

.<

end end class ;imer def initialiOe/app0 .app = app end def call/en20 before = ;ime4no< status9 headers9 bod

= .app4call en2

headers8'V-;iming'= = /;ime4no< - before04to!i4to!s 8status9 headers9 bod = end end

/ow we can use those middlewares in our app$


app = use use run end #ack::Huilder4ne< do ;imer & put the timer at the top so it captures e2er thing belo< it EnsureXson#esponse 5elloGorldLpp

#ack::6er2er4start :app => app

WeQve (ust written our own middeware and learned how to generate a runnable application with a middleware stack$ 'his is how rack apps are written in practice$ /ow onto the final piece of the pu,,le: config4ru

Rackup
6ou may have seen the rackup command referenced before$ ItQs provided by the rack gem$ It provides a simple way to start a web process using one of the rack servers installed on the system$ It looks for config4ru by default$ config4ru defines what ruby code the server should call$ ItQs wrapped in a #ack::Huilder as shown before$ )ereQs all the work weQve done up to now in config4ru
& config4ru & 5elloGorldLpp defintion & EnsureXson#esponse defintion & ;imer definition use ;imer use EnsureXson#esponse run 5elloGorldLpp

/ow navigate into the correct directory and run: rackup and youQll see:
K rackup >> ;hin <eb ser2er /2*4>4* codename Bhromeo0 >> Fa)imum connections set to *(1> >> Listening on (4(4(4(:-(-(9 B;#LAB to stop

.0

Rackup will prefer better servers like 'hin over We!rick$ 'hereQs nothing super fancy going on here$ 'he code inside config4ru is evaluated and built using a #ack::Huilder which generates an API compliant ob(ect$ 'he ob(ect is passed to the rack server ('hin% in this case$ 'hin puts the app online$

Rails & Rack


Rails +S is fully Rack compliant$ A Rails + application is more comple2 Rack app$ It has a comple2 middleware stack$ 'he dispatcher is the final middlware$ 'he dispatcher reads the routing table and calls the correct controller and method$ )ereQs the stock middleware stack used in production:
use #ack::Bache use LctionTispatch::6tatic use #ack::Lock use &'Lcti2e6upport::Bache::6trateg ::LocalBache::Fiddle<are:()((JfceJJf1*@+(> use #ack::#untime use #ack::FethodO2erride use LctionTispatch::#equest$d use #ails::#ack::Logger use LctionTispatch::6ho<E)ceptions use LctionTispatch::TebugE)ceptions use LctionTispatch::#emote$p use LctionTispatch::Ballbacks use Lcti2e#ecord::BonnectionLdapters::BonnectionFanagement use Lcti2e#ecord::Quer Bache use LctionTispatch::Bookies use LctionTispatch::6ession::Bookie6tore use LctionTispatch::Elash use LctionTispatch::ParamsParser use LctionTispatch::5ead use #ack::BonditionalDet use #ack::E;ag use LctionTispatch::Hest6tandards6upport run MourLpp::Lpplication4routes

'he middlewares are not declared e2plicitly in config4ru$ Rails applications create their own middleware chains from different configuration files$ 'he application instance delegates call to the middleware chain$ )ereQs an e2ample config4ru for a rails app:
& ;his file is used b #ack-based ser2ers to start the application4 !!E$LE!!0

require ::Eile4e)pand!path/'44CconfigCen2ironment'9 run E)ample::Lpplication

6ou know that E)ample::Lpplication must have a call method$ )ereQs the implementation of this method from +$1 stable:
& #ails::Lpplication9 #ails::Lpplication ' #ails::Engine def call/en20 en28:O#$D$ILL!ERLLPL;5:= = build!original!fullpath/en20 super/en20 end & #ails::Engine

18

& the super class method def call/en20 app4call/en24mergeN/en2!config00 end & app method in the super class def app .app 77= begin config4middle<are = config4middle<are4merge!into/default!middle<are!stack0 config4middle<are4build/endpoint0 end end

1.

Potrebbero piacerti anche