Rails从HTTP Accept header得到客户端需要的response format信息
默认的MIME types见mime_type.rb:
[code]
ALL = Type.new "*/*", :all
TEXT = Type.new "text/plain", :text
HTML = Type.new "text/html", :html, %w( application/xhtml+xml )
JS = Type.new "text/javascript", :js, %w( application/javascript application/x-javascript )
ICS = Type.new "text/calendar", :ics
CSV = Type.new "text/csv", :csv
XML = Type.new "application/xml", :xml, %w( text/xml application/x-xml )
RSS = Type.new "application/rss+xml", :rss
ATOM = Type.new "application/atom+xml", :atom
YAML = Type.new "application/x-yaml", :yaml, %w( text/yaml )
JSON = Type.new "application/json", :json, %w( text/x-json )
SET = [ ALL, TEXT, HTML, JS, ICS, XML, RSS, ATOM, YAML, JSON ]
def register(string, symbol, synonyms = [])
Mime.send :const_set, symbol.to_s.upcase, Type.new(string, symbol, synonyms)
SET << Mime.send(:const_get, symbol.to_s.upcase)
LOOKUP[string] = EXTENSION_LOOKUP[symbol.to_s] = SET.last
end
[/code]
我们可以在environment.rb里注册自己的MIME type:
[code]
Mime::Type.register "image/jpg", :jpg
[/code]
看看源码mime_responds.rb:
[code]
module ActionController
module MimeResponds
def self.included(base)
base.send(:include, ActionController::MimeResponds::InstanceMethods)
end
module InstanceMethods
def respond_to(*types, &block)
raise ArgumentError, "respond_to takes either types or a block, never both" unless types.any? ^ block
block ||= lambda { |responder| types.each { |type| responder.send(type) } }
responder = Responder.new(block.binding)
block.call(responder)
responder.respond
end
end
class Responder
def initialize(block_binding)
@block_binding = block_binding
@mime_type_priority = eval(
"(params[:format] && Mime::EXTENSION_LOOKUP[params[:format]]) ? " +
"[ Mime::EXTENSION_LOOKUP[params[:format]] ] : request.accepts",
block_binding
)
@order = []
@responses = {}
end
def custom(mime_type, &block)
mime_type = mime_type.is_a?(Mime::Type) ? mime_type : Mime::Type.lookup(mime_type.to_s)
@order << mime_type
if block_given?
@responses[mime_type] = Proc.new do
eval "response.content_type = '#{mime_type.to_s}'", @block_binding
block.call
end
else
if source = DEFAULT_BLOCKS[mime_type.to_sym]
@responses[mime_type] = eval(source, @block_binding)
else
raise ActionController::RenderError, "Expected a block but none was given for custom mime handler #{mime_type}"
end
end
end
def method_missing(symbol, &block)
mime_constant = symbol.to_s.upcase
if Mime::SET.include?(Mime.const_get(mime_constant))
custom(Mime.const_get(mime_constant), &block)
else
super
end
end
def respond
for priority in @mime_type_priority
if priority == Mime::ALL
@responses[@order.first].call
return
else
if priority === @order
@responses[priority].call
return
end
end
end
if @order.include?(Mime::ALL)
@responses[Mime::ALL].call
else
eval 'render(:nothing => true, :status => "406 Not Acceptable")', @block_binding
end
end
end
end
end
[/code]
我们看到,respond_to方法调用responder.send(type),如format.js,但是Responder类没有js方法,所以调用了method_missing方法
method_missing调用了custom方法,custom则eval了response.content_type的设置,然后block.call
最后调用responder.respond,@responses[priority].call调用了content_type的设置以及参数给的block
默认的MIME types见mime_type.rb:
[code]
ALL = Type.new "*/*", :all
TEXT = Type.new "text/plain", :text
HTML = Type.new "text/html", :html, %w( application/xhtml+xml )
JS = Type.new "text/javascript", :js, %w( application/javascript application/x-javascript )
ICS = Type.new "text/calendar", :ics
CSV = Type.new "text/csv", :csv
XML = Type.new "application/xml", :xml, %w( text/xml application/x-xml )
RSS = Type.new "application/rss+xml", :rss
ATOM = Type.new "application/atom+xml", :atom
YAML = Type.new "application/x-yaml", :yaml, %w( text/yaml )
JSON = Type.new "application/json", :json, %w( text/x-json )
SET = [ ALL, TEXT, HTML, JS, ICS, XML, RSS, ATOM, YAML, JSON ]
def register(string, symbol, synonyms = [])
Mime.send :const_set, symbol.to_s.upcase, Type.new(string, symbol, synonyms)
SET << Mime.send(:const_get, symbol.to_s.upcase)
LOOKUP[string] = EXTENSION_LOOKUP[symbol.to_s] = SET.last
end
[/code]
我们可以在environment.rb里注册自己的MIME type:
[code]
Mime::Type.register "image/jpg", :jpg
[/code]
看看源码mime_responds.rb:
[code]
module ActionController
module MimeResponds
def self.included(base)
base.send(:include, ActionController::MimeResponds::InstanceMethods)
end
module InstanceMethods
def respond_to(*types, &block)
raise ArgumentError, "respond_to takes either types or a block, never both" unless types.any? ^ block
block ||= lambda { |responder| types.each { |type| responder.send(type) } }
responder = Responder.new(block.binding)
block.call(responder)
responder.respond
end
end
class Responder
def initialize(block_binding)
@block_binding = block_binding
@mime_type_priority = eval(
"(params[:format] && Mime::EXTENSION_LOOKUP[params[:format]]) ? " +
"[ Mime::EXTENSION_LOOKUP[params[:format]] ] : request.accepts",
block_binding
)
@order = []
@responses = {}
end
def custom(mime_type, &block)
mime_type = mime_type.is_a?(Mime::Type) ? mime_type : Mime::Type.lookup(mime_type.to_s)
@order << mime_type
if block_given?
@responses[mime_type] = Proc.new do
eval "response.content_type = '#{mime_type.to_s}'", @block_binding
block.call
end
else
if source = DEFAULT_BLOCKS[mime_type.to_sym]
@responses[mime_type] = eval(source, @block_binding)
else
raise ActionController::RenderError, "Expected a block but none was given for custom mime handler #{mime_type}"
end
end
end
def method_missing(symbol, &block)
mime_constant = symbol.to_s.upcase
if Mime::SET.include?(Mime.const_get(mime_constant))
custom(Mime.const_get(mime_constant), &block)
else
super
end
end
def respond
for priority in @mime_type_priority
if priority == Mime::ALL
@responses[@order.first].call
return
else
if priority === @order
@responses[priority].call
return
end
end
end
if @order.include?(Mime::ALL)
@responses[Mime::ALL].call
else
eval 'render(:nothing => true, :status => "406 Not Acceptable")', @block_binding
end
end
end
end
end
[/code]
我们看到,respond_to方法调用responder.send(type),如format.js,但是Responder类没有js方法,所以调用了method_missing方法
method_missing调用了custom方法,custom则eval了response.content_type的设置,然后block.call
最后调用responder.respond,@responses[priority].call调用了content_type的设置以及参数给的block
本文深入解析Rails中MIME类型的注册及响应机制,介绍如何通过HTTP Accept header获取客户端所需的response format,并详细阐述Rails内部如何处理不同MIME类型的请求。

被折叠的 条评论
为什么被折叠?



