Exposing any Ruby object over the Web
Can someone explain how the following Ruby code works? (taken from gist: 675667)
require 'rubygems'
require 'rack'
class Object
def webapp
class << self
define_method :call do |env|
func, *attrs = env['PATH_INFO'].split('/').reject(&:empty?)
[200, {}, [send(func, *attrs).to_s]]
end
end
self
end
end
Rack::Handler::Mongrel.run [].webapp, :Port => 9292
# ^^^^^^^^^^^
# | (x)
# ROFLSCALE DB ---/
#
If we run it开发者_如何学C, we can access it over the Web:
GET http://localhost:9292/push/1 -> 1
GET http://localhost:9292/push/2 -> 12
GET http://localhost:9292/push/3 -> 123
GET http://localhost:9292/to_a -> 123
GET http://localhost:9292/pop -> 3
GET http://localhost:9292/shift -> 1
Of course, we can run something like:
GET http://localhost:9292/instance_eval/exec("rm -rf /")
Anyways... how does it work? Can you walk me through the code step-by-step?
The class Object
is the base class for all objects in Ruby. A new method webapp
is defined on this, which makes it callable for all objects.
When calling webapp
, the method self.define_method
is called on the objects class (But only for that particular object - That's called a meta-class, by the way). This defines a new method call
for its instance (Eg. the object).
This new call
method takes env
as argument and splits PATH_INFO
by forward-slashes and stores in an array. The first element is then assigned to a variable func
, and the remainder to a variable attrs
. Then the magic method send
is invoked, which basically calls a method by the name of the variable func
. It then returns an array, consisting of a status-code (200), an empty hash and the output of the method-call.
On the final line, a new array instance is created ([]
is shorthand for Array.new
). Then the method webapp
is called on it, which enriches it with the call
method, as explained above. webapp
conveniently returns self
. So you can pass it directly to Rack::Handler::Mongrel.run
, which will launch a web server (Mongrel is the web server - Rack is an abstraction-layer that gives different web servers a uniform interface). The server will pass requests to the call
method, and interpret the return value to send a response back.
Not sure how familiar you are with ruby - let me know what doesn't make sense.
Lines 1 and 2 import the libraries needed to start a webserver.
Then on line 4, the base class Object
is reopened to add a new method to it - you can do this anywhere you like in ruby. All classes inherit from Object
, so whatever methods you define here can be called on any object.
Line 5 begins defining a new method for all objects, called webapp
.
Line 6 and 7 define a method call
for each instance of Object
that is instantiated. I think this is basically equivalent to using self.call
to define the method.
Lines 8 takes the relative part of the URL (eg push/1
or to_a
) and splits it into two parts, (ie func
and a variable length string of attrs
).
Line 9 then returns a raw HTTP response, comprised of the status code (200
), headers (empty in this case: {}
), and body ([send(func, *attrs).to_s])
The body is comprised of the response of the object to the method func
, with the arguments attrs
. So if the URL requested was push/1
, the method push
would be called with the argument 1
. This is equivalent to [push(1).to_s]
. The to_s
converts the value that is returned by the method to a string.
Finally, on line 16, a webserver is started on port 9292. The object that the webapp
method is called on is an empty array - you could use a String
(ie ''
), or a Hash
(ie {}
) or a Proc
(ie Proc.new
) or any ruby class you like.
You can then manipulate the object by hitting URLs of the form func/attribute1/attribute2/attribute3...
, and the object will call the method func
with the string of arguments attribute1, attribute2, attribute3...
.
Interesting code!
精彩评论