Rails源码阅读(八)ActionController::Base_用户请求在rails中的处理流程(3)
执行流程从路由找到真正要执行的XXXController后,会执行super方法,即ActionController::Base.process方法
class << self
# ActionController::Base.process代码:
# Factory for the standard create, process loop where the controller is discarded after processing.
def process(request, response) #:nodoc:
new.process(request, response)
end
end
这个方法的细微之处在于:new.process
new是去新建一个当前Controller的实例,之后执行这个实例的process方法。
这里有个重要的结论:每个请求都会new一个Controller的实例。这个结论将实质上影响以后的设计和扩展。
例如,每个请求的action中的实例变量,只在当前请求中存在,多个请求不会存在实例变量共享的问题。
对于熟练java人,尤其要注意这一点,与servlet的多线程区别很大。
XXXController的实例方法以及详细分析:
# Extracts the action_name from the request parameters and performs that action.
def process(request, response, method = :perform_action, *arguments) #:nodoc:
response.request = request #在response里可以直接使用request
initialize_template_class(response) #初始化模板相关信息,后面会详细看
assign_shortcuts(request, response) #定义了很多实例变量,作用就是shotcuts了
initialize_current_url #一句内容:@url = UrlRewriter.new(request, params.clone)
assign_names #@action_name = (params['action'] || 'index') #应该叫assign_action_name
log_processing #记日志
send(method, *arguments) #执行实例的perform_action方法,后面详细看
send_response #返回结果,下面会详细看
ensure
process_cleanup
end
# 初始化模板相关信息
def initialize_template_class(response)
response.template = ActionView::Base.new(self.class.view_paths, {}, self)
response.template.helpers.send :include, self.class.master_helper_module
response.redirected_to = nil
@performed_render = @performed_redirect = false
end
这里也是用到了ActionController::Response的方法,这个好理解,因为response的主要任务就是组装字符串。
response初始化的template是个ActionView::Base的实例,到这里就与ActionView联系起来了。
response.template.helpers.send :include, self.class.master_helper_module
这里???暂时没有找到??? 猜测是把controller里用helper声明的方法加入了helpers里[补充:这个的确如猜测的一样,详细以后再分析,代码在action_controller/helpers.rb里。这个master_helper_module是虚拟出来的中间代理对象,目的就是把很多方法放入其中,之后再混入view的helper中,以使得这些方法在view中直接使用]
# shorcuts
def assign_shortcuts(request, response)
@_request, @_params = request, request.parameters
@_response = response
@_response.session = request.session
@_session = @_response.session
@template = @_response.template
@_headers = @_response.headers
end
这里就是为了简单使用,把一些常用量放在了实例变量里。
好处:好用,而不需要每次都xxx.yyy.zzz,直接用@zzz就好了
坏处:几乎没有,因为每次请求都new一个controller,用完就回收了
# 进入action的执行
经过了这么长的流程,终于到了执行action了,即执行perform_action
def perform_action
if action_methods.include?(action_name)
send(action_name)
default_render unless performed?
elsif respond_to? :method_missing
method_missing action_name
default_render unless performed?
else
begin
default_render
rescue ActionView::MissingTemplate => e
# Was the implicit template missing, or was it another template?
if e.path == default_template_name
raise UnknownAction, "No action responded to #{action_name}. Actions: #{action_methods.sort.to_sentence(:locale => :en)}", caller
else
raise e
end
end
end
end
这么长的代码,大部分都在处理特殊情况。
真正的核心是找到action,执行action,执行action时,我们有时候写render,有时候不写。
如果不写,则调用default_render(从if条件可以推测,render中一定会设置这个实例变量的)
#代码很短,直接交给了render去处理所有情况。
#这也挺好的,最怕的是有很多特例,写在很多地方,改的人就入雷区了。
def default_render #:nodoc:
render
end
# render的代码真长,做的事情也很复杂,看看注释也这么长
主要做的事请,生成要response的body,即生成字符串:
a:找到layout
一般就用默认的了;
可以直接传递layout参数,比如传false就可以不用layout了,等
b:根据render的参数不同,调用不同的view
比如json格式,xml格式,等
c:erb渲染
简单来说是:@template.render()
@template是ActionView::Base的实例
d:其他的细节,暂略作下回分析
归纳一下Controller里面render做的事情:不论是直接返回(例如:text),还是调用View的render来返回erb页面的结果,最终得到的都是text,把这个text字符串存入body。body在response的时候给客户端看。
代码:
def render_for_text(text = nil, status = nil, append_response = false)
#略一部分
if append_response
response.body ||= ''
response.body << text.to_s #就是这里!
else
response.body = case text #这里
when Proc then text
when nil then " " # Safari doesn't pass the headers of the return if the response is zero length
else text.to_s
end
end
end
# 返回结果用send_response
#send_response的代码
def send_response
response.prepare!
response
end
(1):这里的细节,需要去看ActionController::Response的代码,大致的是在设置response的控制信息,例如语言,编码,类型,cookie等。
#大致的工作都是在设置response的控制信息,例如语言,编码,类型,cookie等
def prepare!
assign_default_content_type_and_charset!
handle_conditional_get!
set_content_length!
convert_content_type!
convert_language!
convert_cookies!
end
(2):send_response的返回结果就是response本身,这个好像不符合Rack的标准返回结果是:
[200, {"Content-Type" => "text/html"}, "Hello Rack!"]
回到请求的入口处:app.call(env).to_a
从这里应该推测出,response应该有to_a方法,返回符合rack标准的接口。
找了找Response里没有,应该去看看其super-class,果然是:
class Response < Rack::Response
在Rack::Response中定义了to_a方法,代码如下:
def finish(&block)
@block = block
if [204, 304].include?(status.to_i)
header.delete "Content-Type"
[status.to_i, header.to_hash, []]
else
[status.to_i, header.to_hash, self]
end
end
alias to_a finish # For *response
# render_to_string的介绍
直接使用render方法,render的返回值即response.body
但是在controller里直接得到body,有些麻烦:必须确保显式调用了render之后才可以
如何解决,用的方法是先调用render,之后清除render的残留物:render完成标志+body值+assigns的值+等【思考下这里:这个方法提前知道来render做了哪些改变当前环境的事情,设计的不好!】
# Renders according to the same rules as <tt>render</tt>, but returns the result in a string instead
# of sending it as the response body to the browser.
def render_to_string(options = nil, &block) #:doc:
render(options, &block)
ensure
response.content_type = nil
erase_render_results
reset_variables_added_to_assigns
end
这个方法很好用,尤其是在处理js和ajax 的时候,不再需要在controller里面写太多拼凑字符串了,直接用erb模板,就如同写view一样;再例如,需要动态的定制页面生成等给编辑使用,这个时候手写erb不划算。
总结:
#1 介绍了XXXController的process方法做了哪些操作以及涉及到的步骤:
new一个当前Controller的实例,调用实例的process方法,
process执行perform_action方法,
perform_action用反射(action名字是路由来的)调用了当前实例的action方法(我们真正编码实现的地方)
之后render,也就是根据页面的得到字符串,状态等信息,response给http的用户
#2 erb页面内容的生成是由@template.render做的。@template是ActionView::Base的实例
这里调用的接力棒到了action_view里
====结束====
=== ===
== ==
= =
| |