HTTP通訊協議是1種Request-Response (要求-回應)的流程,客戶端(通常是閱讀器)向伺服器送出1個HTTP request封包,然后伺服器就回應1個response封包。在上1章中,我們介紹了Rails如何使用路由來分派request到Controller的其中1個Action。而每一個Action的任務就是根據客戶端傳來的資料與Model互動,然后回應結果給客戶端。這1章中我們將仔細介紹負責回應要求的Controller。
透過rails g controller
指令產生出來的controller都會繼承自ApplicationController
。因此定義在這里的方法可以被所有Controller取用,你可以在這邊定義1些共用的方法。預設的application_controller.rb長的以下:
class ApplicationController < ActionController::Base
protect_from_forgery
end
其中的protect_from_forgery
方法啟動了CSRF安全性功能,所有非GET的HTTP
request都必須帶有1個Token參數才能存取,Rails會自動在所有表單中幫你插入Token參數,預設的Layout中也有1行<%
= csrf_meta_tag %>
標簽可讓JavaScript讀取到這個Token。
但是當需要開放API給非閱讀器客戶端時,例如手機端或第3方利用的回呼(webhook),這時候候我們會需要關閉這個功能,例如:
class ApisController < ApplicationController
skip_before_action :verify_authenticity_token # 全部ApisController 關閉檢查
end
我們在Part1示范過,要產生1個Controller檔案,請輸入
rails g controller events
如此便會產生app/controllers/events_controller.rb,依照RESTful設計的慣例,所有的Controller命名都是復數,而檔案名稱依照慣例都是{name}_controller.rb。
1個Action就是Controller里的1個Public方法:
class EventsController < ApplicationController
def show
# ...
end
end
在Action方法中我們要處理request,基本上會做3件事情: 1.搜集request的資訊,例如使用者傳進來的參數2.操作Model來做資料的處理3.回傳response結果,這個動作稱作render
在Controller的Action當中,Rails提供了1些方法可讓你得知此request各種資訊,包括:
在根據request資訊做好資料處理以后,我們接下來就要回傳結果給用戶。事實上,就算你甚么都不處理,Action方法里面空空如也,乃至不定義Action,Rails預設也還是會履行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,例如render
:template => "index"
或可以省略成render "index"
,如果是不同Controller的Template再加上Controller名稱,例如render
"events/index"
。:action
指定同1個Controller中另外一個Action的Template (注意到只是使用它的Template,而不會履行該Action內的程式):status
設定HTTP
status,預設是200,也就是正常。其他經常使用代碼包括401權限不足、404找不到頁面、500伺服器毛病等。:layout
可以指定這個Action的Layout,設成false即關掉Layout
補充1提,在特定情況你想把render
的結果存成1個字串,例如拿到局部樣板Partials成為1個字串,這時候候可以改使用render_to_string
:partial => "foobar"
如果Action不要render任何結果,而是要使用者轉向到別頁,可使用redirect_to
redirect_to events_url
redirect_to :back
回到上1頁。如果需要回傳2進位Binary資料,有兩個方法可使用:
send_data(data, options={})
回傳2進位字串,接受以下參數:
data
參數是2進位的字串::filename
使用者貯存下來的檔案名稱:type
預設是application/octet-stream:disposition
inline或attachment:status
預設是200
send_file(file_location, options={})
回傳1個檔案,接受以下參數:
file_location
是檔案路徑和檔名::filename
使用者貯存下來的檔案名稱:type
預設是application/octet-stream:disposition
inline或attachment:status
預設是200不過實務上我們很少在上線環境上直接用Rails來推送靜態檔案,由于大檔的傳輸時間會浪費寶貴的Rails運算資源。我們會改用X-Sendfile Header將傳檔的任務委派給網頁伺服器(例如Apache或Nginx )處理,來下降Rails伺服器的負擔。或是搭配第3方云貯存服務例如AWS S3將傳檔的任務外包出去。
我們在第6章RESTful利用程式中曾示范過用法,respond_to
可以用來回應不同的資料格式。Rails內建支援格式包括有:html,
:text, :js, :css, :ics, :csv, :xml, :rss, :atom, :yaml, :json
等。如果需要擴充,可以編輯config/initializers/mime_types.rb這個檔案。
如果你想要設定1個else的情況,你可以用:any
:
respond_to do |format|
format.html
format.xml { render :xml => @event.to_xml }
format.any { render :text => "WTF" }
end
另外,Rails也支援單行的簡單寫法:
respond_to :html, :json, :js
這樣其實就是:
respond_to do |format|
format.html
format.json
format.js
end
HTTP是1種無狀態的通訊協議,為了能夠讓閱讀器能夠在跨request之間記住資訊,Rails提供了Session功能,像是記住登入的狀態、記住使用者購物車的內容等等,都是用Session實作出來的。
要操作Session,直接操作session
這個Hash變數便可。例如:
session[:cart_id] = @cart.id
Rails預設采取Cookies
session storage來貯存Session資料,它是將Session資料透過config/secrets.yml的secret_key_base
編碼后放到閱讀器的Cookie當中,最大的好處是對伺服器的效能負擔很低,缺點是大小最多4Kb,和資料還是可以透過反編碼后看出來,只是沒法進行修改。因此安全性較低,不合適寄存機密資料。
除??Cookies session storage,Rails也支援其他方式,你可以修改config/initializers/session_store.rb:
:active_record_store
使用資料庫來貯存:mem_cache_store
使用Memcached快取系統來貯存,合適高流量的網站1般來講使用預設的Cookies session storage便可,如果對安全性較高要求,可使用資料庫。如果希望統籌效能,可以斟酌使用Memcached。
采取:active_record_store
的話,必須安裝activerecord-session_store
gem,然后產生sessions資料表:
$ rails g active_record:session_migration
$ rake db:migrate
除??Session,我們也能夠直接操作底層的