`
fantaxy025025
  • 浏览: 1309180 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类

Rails源码阅读(八)ActionController::Base_用户请求在rails中的处理流程(3)

 
阅读更多

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里

 

 

====结束====

===           ===

==                ==

=                     =

|                       |

分享到:
评论

相关推荐

    rails 部署 nginx

    当用户请求到达Nginx时,Nginx会将请求转发到内部的Rails应用服务器,服务器处理完请求后再将结果返回给Nginx,由Nginx将结果返回给用户。 6. **优化与性能**: 在部署Rails应用时,还需要考虑性能优化,比如启用...

    关于Rails登录和验证插件http_authentication restful-authentication

    在Rails中实现HTTP基本认证,开发者可以在Controller的before_action中添加代码来检查请求头中的认证信息。 Restful-Authentication则是一个更全面的解决方案,它包含了注册、登录、登出、密码重置等功能。该插件...

    rails2.3.2 ExceptionNotifier 配置

    ExceptionNotifier是Rails的一个非常有用的gem,它允许开发者在应用程序中捕获并通知异常情况,如错误或未处理的异常,这对于监控和调试生产环境中的应用至关重要。 ExceptionNotifier的主要功能是在发生异常时发送...

    关于rails的services层

    它们通常作为一个独立的类,不继承任何Rails特定的基类,比如`ActiveRecord::Base`或`ActionController::Base`。服务对象的主要目的是封装复杂操作,保持控制器简洁,同时提供一个可测试的边界。 创建服务对象的...

    rails 邮件支持

    在Ruby on Rails框架中,邮件支持是一个至关重要的功能,它允许开发者向用户发送电子邮件通知,例如确认注册、重置密码提醒、...了解并熟练掌握这些知识点,将有助于在Rails应用中构建高效且用户体验良好的邮件系统。

    rails 2.3.5开发就业指导中心网站部分记录

    在Rails 2.3.5中,开发者可能使用了MVC(模型-视图-控制器)架构,其中模型负责处理业务逻辑和数据操作,视图用于展示数据,而控制器则作为两者之间的桥梁。数据库方面,Rails通常与ActiveRecord一起使用,这是一个...

    ruby on rails入门基础

    - Controller(控制器)作为模型和视图之间的桥梁,处理用户请求并传递数据。 5. **数据库集成**: - Rails默认使用SQLite,但也可以配置使用MySQL、PostgreSQL等其他数据库。在本例中,`libmySQL.dll`可能是一个...

    Ruby on Rails入门例子

    Controller作为两者之间的桥梁,处理用户请求并协调数据流。这篇教程可能涵盖了如何设置开发环境、创建第一个Rails应用程序、理解路由工作原理、搭建数据库模型以及使用ActiveRecord进行数据库操作等内容。 通过...

    RailsTutorial_DemoApp:Ruby on Rails教程的演示应用程序

    4. **编写业务逻辑**: 在模型文件(如 `app/models/user.rb`)中定义业务规则,在控制器(如 `app/controllers/users_controller.rb`)中处理 HTTP 请求。 5. **构建视图**: 修改视图模板(位于 `app/views` 目录下...

    ruby 与 sybase 连接

    标题中的“ruby 与 sybase 连接”指的是在Ruby编程语言中,如何使用特定的库或适配器与Sybase数据库进行交互。Sybase是一种关系型数据库管理系统,广泛应用于企业级应用。Ruby则是一种面向对象的脚本语言,常用于Web...

    json_api_responders:Rails JSON API应用程序的响应者

    JsonApi响应者 该gem提供了一些使用JSONAPI的便捷方法。... class BaseController &lt; ApplicationController include JsonApiResponders end end end 用法 该gem带有以下两个方法respond_with和respond_with_er

    基于sqlite的ror例子

    在描述中提到的“我回复内容的例子的完整程序”,意味着这个压缩包可能包含了某个用户在博客论坛(如ITEYE)上分享的关于如何在Rails应用中集成SQLite的代码示例。通过提供的博文链接(虽然在这里无法直接访问),...

    Ruby-ParanoiaRails345的actsasparanoid一个重实现

    在 Rails 3、4 和 5 中,Paranoia 提供了与 acts_as_paranoid 类似的功能,但可能有自己独特的优化和改进。 Paranoia 的核心功能是提供一种方法,使得在应用中执行删除操作时,不是物理删除记录,而是设置一个特定...

    使用ROR编写ORACLE WEB应用

    在Rails中,模型类通常继承自`ActiveRecord::Base`,并通过定义属性和方法来表示数据库中的表和字段。控制器负责处理HTTP请求,调用模型进行业务逻辑处理,并渲染视图返回响应。 至于文件名“ForRuby_oracle.~sql”...

    Laravel开发-laravel-envol .zip

    在本文中,我们将深入探讨Laravel框架,特别是与“laravel-envol”相关的开发实践。Laravel是一款基于PHP的开源Web应用框架,以其优雅的语法和强大的功能深受开发者喜爱。让我们一起探索Laravel的核心概念、环境配置...

    Ruby中相等性判断的4种方法

    ”,平常程序中都有使用,但是感觉对其缺乏深入理解,今天读 rails 部分源码的时候拿捏不定其中一个判断的意思,于是趁机深入研究了一番,总算觉得比较清楚了,今天做一下笔记,以作备忘。 “==” 最常见的相等性...

    BootStrop前端框架入门教程详解

    3. **Sass移植版**:适合Rails、Compass或只使用Sass的项目,提供了从Less到Sass的源码转换。 此外,Bootstrap中文网提供了免费的CDN服务,使得开发者无需下载即可直接引用Bootstrap资源,这对于快速构建页面特别...

Global site tag (gtag.js) - Google Analytics