Web server structure: Rack
Max Lapshin
max.lapshin@REDACTED
Sat Jan 9 18:50:37 CET 2010
Hi, I've promised to talk about better structure of web stack. I want
to tell about Ruby on Rails experience.
Basically Rails application is built according to MVC paradigm: Model,
View, Controller. Perhaps everyone here have
heard about it.
Most interesting here will be speak about structure of View and
Controller layers. Web application violates pure MVC structure,
because in web application controller must present object of Model
layer to template engine.
So, after years of development, Rails came to the concept of Rack
infrastructure.
After parsing HTTP headers request is represented as hash table with
http headers. Then this request is passed
through configured chain of filters, that is ended with some handler.
Let's look at an example config:
use ActionController::Failsafe # this is global exception handler,
that captures all errors and show 500 in
use ActionController::PageCache # tries to find whole page cached
map "/advert" do # enables mapping all requests to
/advert to special handler.
run FastAdvertHandler
end
map "/" do # all other requests are
moving to this section
use ActionController::Session::CookieStore, , {:secret=>"some_key",
:session_key=>"_rails_session"} # loads session from cookie
use ActionController::ParamsParser # decode all params and
parses query string
use Rack::Head # discard body is HTTP HEAD
use Rails::Router # checkout what
controller and action will handle request
run Rails::Dispatcher
end
Each filter can store required information in mutable request, which
is hash. Lets look at code of such filter:
module Rack
module Session
class Cookie
def initialize(app, options = {})
@app = app
@key = options[:key] || "rack.session" # Variables,
starting from @ in ruby are just object instance variables
@secret = options[:secret]
end
def call(env) # this method is by interface, requred method
of each handler
load_session(env) # this method take
status, headers, body = @app.call(env) # this line is
by interface required part of calling further request processing.
commit_session(env, status, headers, body)
end
def load_session(env)
env["rack.session"] = load_session_from_cookie(env[@key]) #
here we take cookie, named the same as our key and decode it
end
def commit_session
... # this method packs session to cookie back.
end
end
end
end
It is very important, that each filter can control, when to call
subfilters up to last request handler.
Second important thing, that there is defined convention, how to name
request keys: env["rack.session"] is for session,
env["request.error"] is for backtrace of request exceptions.
Let's look, how would it be possible to repeat this structure in Erlang:
-module(rack_server).
-export([rack_config/0]).
rack_config() ->
[
controller_failsafe, % name of module
controller_cookiestore:new("secretstring", "_example_session"),
...
].
Here is sketch:
http://github.com/maxlapshin/erack/blob/master/src/example_server.erl
so can look controller_failsafe module:
-module(controller_failsafe).
-export([call/2]).
-behaviour(rack_handler).
call(App, Request) ->
try App:call(Request) of
Reply -> Reply
catch
error:notfound -> [404, [{"Content-Type", "text/plain"}], "404 Not found"];
_:_ -> [500, [{"Content-Type", "text/plain"}], "500 Server
error"++lists:flatten(io_lib:format("~n~p~n",
[erlang:get_stacktrace()]))]
end.
Concept of Rack handlers became a glue idea for most ruby frameworks,
making useless many of them, because they appeared to be just a one
rack config with some set of handlers =)
What do I want? To discuss this api and promote its implementation in
erlang web servers. Also it would be great to extract implementation
of the whole controller layer
out of Zotonic CMS into reusable components. I'm willing to reuse its
code in my applications, but it is impossible, because business logic
and infrastructure logic are in one repository.
More information about the erlang-questions
mailing list