Ruby on rails 实战圣经:ActionController

Controlling complexity isthe essence of computer programming. — Brian Kernighan

HTTP通讯协议是一种Request-Response(请求-响应)的流程,客户端(通常是浏览器)向服务器送出一个HTTP request封包,然后服务器就响应一个response封包。在上一章中,我们介绍了Rails如何使用路由来分派requestController的其中一个Action。而每个Action的任务就是根据客户端传来的数据与Model互动,然后响应结果给客户端。这一章中我们将仔细介绍负责响应请求的Controller

ApplicationController

默认产生出来的Controller都继承自ApplicationController。因此定义在这里的方法可以被所有Controller取用,你可以在这边定义一些共享的方法。默认的application_controller.rb长的如下:

class ApplicationController < ActionController::Base
     
  protect_from_forgery
     
end
    

其中的protect_from_forgery方法启动了CSRF安全性功能,所有非GETHTTPrequest都必须带有一个Token参数才能存取,Rails会自动在所有窗体中帮你插入Token参数,默认的Layout中也有一行<%= csrf_meta_tag %>卷标可以让JavaScript读取到这个Token。会需要关闭这个功能的时机是,你需要开放API给非浏览器客户端,这时候你会需要取消它:

class ApisController < ApplicationController
     
  skip_before_filter :verify_authenticity_token
     
end
    

CSRF 网络攻击 http://en.wikipedia.org/wiki/Cross-site_request_forgery

注意,请将方法放在protectedprivate之下,如果是public方法,就会变成一个公开的Action可以给浏览器呼叫到

产生ControllerAction

我们在Part1示范过,要产生一个Controller档案,请输入

rails g controller events
    

如此便会产生app/controllers/events_controller.rb,依照RESTful设计的惯例,所有的Controller命名都是复数,而文件名依照惯例都是{name}_controller.rb

一个Action就是Controller里的一个Public方法:

class EventsController < ApplicationController
     
  def show
     
    # ...
     
  end
     
end
    

除了继承自ApplicationController,我们也可以继承更底层的ActionController::Metal,请参考Rails3: 新的 Metal 机制

Action方法中我们要处理request,基本上会做三件事情: 1. 收集request的信息,例如用户传进来的参数 2. 操作Model来做数据的处理 3. 回传response结果,这个动作称作render

Request信息收集

ControllerAction之中,Rails提供了一些方法可以让你得知此request各种信息,包括:

·         action_name 目前的Action名称

·         cookies Cookie下述

·         headers HTTP标头

·         params 包含用户所有传进来的参数Hash,这是最常使用的信息

·         request 各种关于此request的详细信息

·         request_method

·         method

·         delete?, get?, head?,post?, put?

·         xml_http_request? xhr?

·         url

·         protocol, host, port,path query_string

·         domain

·         host_with_port

·         port_string

·         ssl?

·         remote_ip?

·         path_without_extension,path_without_format_and_extension, format_and_extension, relative_path

·         env

·         accepts

·         format

·         mime_type

·         content_type

·         headers

·         body

·         content_length

·         response 代表要回传的内容,会由Rails设定好。通常你会用到的时机是你想加特别的Response Header

·         session Session下述

正确的说,params这个HashActiveSupport::HashWithIndifferentAccess对象,而不是普通的Hash而已。Ruby内建的Hash,用Symbolhash[:foo]和用字符串的hash["foo"]是不一样的,这在混用的时候常常搞错而取不到值,算是常见的臭虫来源。Rails在这里使用的ActiveSupport::HashWithIndifferentAccess对象,无论键是Symbol或字符串,都指涉相同的值,减少麻烦

Render结果

在根据request信息做好数据处理之后,我们接下来就要回传结果给用户。事实上,就算你什么都不处理,Action方法里面空空如也,甚至不定义ActionRails默认也还是会执行render方法。这个render方法会回传默认的Template,依照Rails惯例就是app/views/{controller_name}/{action_name}。如果找不到样板档案的话,会出现Template is missing的错误。

当然,有时候我们会需要自定render,也许是指定不同的Template,也许是不需要Template。这时候有以下参数可以使用:

直接回传结果

·         render :text =>"Hello"直接回传字符串内容,不使用任何样板

·         render :xml =>@event.to_xml回传XML

·         render :json =>@event.to_json回传JSON格式(再加上:callback就会是JSONP)

·         render :nothing => true空空如

指定Template

·         :template指定Template

·         :action指定使用该ActionTemplate(注意到只是使用它的Template,而不会执行该Action内的程序)

·         :file指定Template的档名全

其他参数

·         :status设定HTTP status,默认是200,也就是正常。其他常用代码包括401权限不足、404找不到页面、500服务器错误等

·         :layout可以指定这个ActionLayout,设成false即关掉Layout

补充一提,在特定情况你想把render的结果存成一个字符串,例如拿到局部样板Partials成为一个字符串,这时候可以改使用render_to_string :partial => "foobar"

Redirect

如果Action不要render任何结果,而是要用户转向到别页,可以使用redirect_to

·         redirect_to :action =>"show", :id => @event

·         redirect_to :back回到上一页

注意,一个Action中只能有一个render或一个redirect_to。不然你会得到一个DoubleRenderError例外错误

串流Sending data

如果需要回传二进制Binary数据,有两个方法可以使用:

send_data(data,options={})回传二进制字符串,接受以下参数:

·         data是二进制的字符

·         :filename用户储存下来的文件

·         :type默认是application/octet-stream

·         :disposition inlineattachment

·         :status默认是200

send_file(file_location,options={})回传一个档案,接受以下参数:

·         file_location 是档案路径和档

·         :filename用户储存下来的文件

·         :type默认是application/octet-stream

·         :disposition inlineattachment

·         :status默认是200

·         :buffer_size stream的暂存空间,默认4096 bytes

·         :stream默认是false,会先将整个档案先加载内存,如果档案非常大可能造成问题

Rails支持X-Sendfile Header可以将传档的任务委派给网页服务器处理,降低Rails服务器的负担

respond_to

我们在第六章RESTful应用程序中曾经示范过用法,respond_to可以用来响应不同的数据格式。Rails内建支持格式包括有:html, :text, :js, :css, :ics, :csv, :xml, :rss, :atom,:yaml, :json等。如果需要扩充,可以编辑config/initializers/mime_types.rb这个档案。

如果你想要设定一个else的情况,你可以用:any

respond_to do |format|
     
  format.html
     
  format.xml { render :xml => @event.to_xml }
     
  format.any { render :text => "WTF" }
     
end
    

Sessions

HTTP是一种无状态的通讯协议,为了能够让浏览器能够在跨request之间记住信息,Rails提供了Session功能,像是记住登入的状态、记住用户购物车的内容等等,都是用Session实现出来的。

要操作Session,直接操作session这个Hash变量即可。例如:

session[:cart] = Cart.new
    

只要是可以被串行化的对象,都可以放进session之中。当然,你不会想放太大的数据进去,这样每次request读取时会降低服务器的性能。

Session storage

Rails默认采用Cookies session storage来储存Session数据,它是将Session数据透过config/initializers/secret_token编码后放到浏览器的Cookie之中,最大的好处是对服务器的性能负担很低,缺点是大小最多4Kb,以及数据还是可以透过反编码后看出来,只是无法进行修改。因此安全性较低,不适合存放机密数据。

除了Cookies session storageRails也支持其他方式,你可以修改config/initializers/session_store.rb

·         :active_record_store使用数据库来储存

·         :mem_cache_store使用Memcached快取系统来储

一般来说使用默认的Cookies sessionstorage即可,如果对安全性较高要求,可以使用数据库。如果希望兼顾性能,可以考虑使用Memcached

采用:active_record_store的话,必须产生sessions数据表:

$ rake db:sessions:create
     
$ rake db:migrate
    

Cookies

除了Session,我们也可以直接操作底层的Cookie,以下是一些使用范例:

# Sets a simple session cookie.
     
cookies[:user_name] = "david"
     

   
     
   
# Sets a cookie that expires in 1 hour.
     
cookies[:login] = { :value => "XJ-122", :expires => 1.hour.from_now }
     

   
     
   
# Example for deleting:
     
cookies.delete :user_name
     

   
     
   
cookies[:key] = {
     
   :value => 'a yummy cookie',
     
   :expires => 1.year.from_now,
     
   :domain => 'domain.com'
     
}
     

   
     
   
cookies.delete(:key, :domain => 'domain.com')
    

因为数据是存放在用户浏览器,所以如果需要保护不能让用户乱改,Rails也提供了Signed方法:

cookies.signed[:user_preference] = @current_user.preferences
    

另外,如果是尽可能永远留在用户浏览器的数据,可以使用Permanent方法:

cookies.permanent[:remember_me] = [current_user.id, current_user.salt]
    

两者也可以加在一起用:

cookies.permanent.signed[:remember_me] = [current_user.id, current_user.salt]
    

Flash讯息

我们在Part1示范过用Flash来传递讯息。它的用处在于redirect时,能够从这一个request传递文字讯息到下一个request,例如从createAction传递「成功建立」的讯息到show Action

flash是一个Hash,其中的键你可以自定,常用:notice:warning:error等。例如我们在第一个Action中设定它:

def create
     
  @event = Event.create(params[:event])
     
  flash[:notice] = "成功建立"
     
  redirect_to :action => :show
     
end
    

那么在下一个Action中,我们就可以在Template中读取到这个讯息,通常我们会放在Layout中:

<p><%= flash[:notice] %></p>
    

使用过一次之后,Rails就会自动清除flash

另外,有时候你等不及到下一个Action,就想让Template在同一个Action中读取到flash值,这时候你可以写成:

flash.now[:notice] = "foobar"
    

Filters

可将Controller中重复的程序抽出来,有三种方法可以定义在进入Action之前、之中或之后执行特定方法,分别是before_filterafter_filteraround_filter,其中before_filter最为常用。这三个方法可以接受Code block、一个Symbol方法名称或是一个对象(Rails会呼叫此对象的filter方法)

before_filter

before_filter最常用于准备跨Action共享的数据,或是用户权力验证等等:

class EventsControler < ApplicationController
     
  before_filter :find_event, :only => :show
     

   
     
   
  def show
     
  end
     

   
     
   
  protected
     

   
     
   
  def find_event
     
    @event = Event.find(params[:id])
     
  end
     

   
     
   
end
    

每一个都可以搭配:only:except参数。

around_filter

# app/controllers/benchmark_filter.rb
     
class BenchmarkFilter
     
    def self.filter(controller)
     
     timer = Time.now
     
     Rails.logger.debug "---#{controller.controller_name} #{controller.action_name}"
     
     yield # 這裡讓出來執行Action動作
    
     elapsed_time = Time.now - timer
     
     Rails.logger.debug "---#{controller.controller_name} #{controller.action_name} finished in %0.2f" % elapsed_time
     
    end
     
end
     

   
     
   
# app/controller/events_controller.rb
     
class EventsControler < ApplicationController
     
    around_filter BenchmarkFilter
     
end
    

Filter的顺序

当有多个Filter时,Rails是由上往下依序执行的。如果需要加到第一个执行,可以使用prepend_before_filter方法,同理也有prepend_after_filterprepend_around_filter

如果需要取消从父类别继承过来的Filter,可以使用skip_before_filter :filter_method_name方法,同理也有skip_after_filterskip_around_filter

rescue_from

rescue_from可以在Controller中宣告救回例外,例如:

class ApplicationController < ActionController::Base
     

   
     
   
    rescue_from ActiveRecord::RecordNotFound, :with => :show_not_found
     
    rescue_from ActiveRecord::RecordInvalid, :with => :show_error
     

   
     
   
    protected
     

   
     
   
    def show_not_found
     
        # render something
     
    end
     

   
     
   
    def show_error
     
        # render something
     
    end
     

   
     
   
end
    

HTTP Basic Authenticate

Rails内建支持HTTP Basic Authenticate,可以很简单实现出认证功能:

class PostsController < ApplicationController
     
    before_filter :authenticate
     

   
     
   
    protected
     

   
     
   
    def authenticate
     
     authenticate_or_request_with_http_basic do |username, password|
     
       username == "foo" && password == "bar"
     
     end
     
    end
     
end
    

或是这样写:

class PostsController < ApplicationController
     
    http_basic_authenticate_with :name => "foo", :password => "bar"
     
end
    

更多在线资源

·         Action ControllerOverview http://guides.rubyonrails.org/action_controller_overview.html

·         Layouts and Renderingin Rails http://guides.rubyonrails.org/layouts_and_rendering.html

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 成长之路 设计师:Amelia_0503 返回首页