- 什么是MimeResponds
MimeResponds主要是为了支持Web Service请求:
比如 /people/list.html 返回一个html格式的响应
比如 /people/list.xml 返回xml格式的响应
Rails determines the desired response format from the HTTP Accept header submitted by the client.
也可以自己增加MINE Type
- def index
- @people = Person.find(:all)
- respond_to do |format|
- format.html
- format.xml { render :xml => @people.to_xml }
- end
- end
Mime::Type.register "image/jpg", :jpg
这个方法我们后面再分析分析
- 实现
- def respond_to(*types, &block)
- raise ArgumentError, "respond_to takes either types or a block, never both" unless types.any? ^ block // 参数错误只允许传types或者block
- // 如果传入types那么构建block
- block ||= lambda { |responder| types.each { |type| responder.send(type) } }
- responder = Responder.new(self)
- //调用responder的type指名的方法 实际上先调用method_missing
- block.call(responder)
- responder.respond
- end
为了了解清楚实际的代码,先看看register方法:
- Mime::Type.register "*/*", :all
- Mime::Type.register "text/plain", :text, [], %w(txt)
- Mime::Type.register "text/html", :html, %w( application/xhtml+xml ), %w( xhtml )
- Mime::Type.register "text/javascript", :js, %w( application/javascript application/x-javascript )
- Mime::Type.register "text/css", :css
- Mime::Type.register "text/calendar", :ics
- Mime::Type.register "text/csv", :csv
- Mime::Type.register "application/xml", :xml, %w( text/xml application/x-xml )
- Mime::Type.register "application/rss+xml", :rss
- Mime::Type.register "application/atom+xml", :atom
- Mime::Type.register "application/x-yaml", :yaml, %w( text/yaml )
- Mime::Type.register "multipart/form-data", :multipart_form
- Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form
- # http://www.ietf.org/rfc/rfc4627.txt
- Mime::Type.register "application/json", :json, %w( text/x-json )
Mime::Type.register 方法的实现:
- def register(string, symbol, mime_type_synonyms = [], extension_synonyms = [], skip_lookup = false)
- Mime.instance_eval { const_set symbol.to_s.upcase, Type.new(string, symbol, mime_type_synonyms) }
- #动态的把symbol(:html :text :js等转化为常量)
- # HTML = Type.new("text/html", :html, %w( application/xhtml+xml ))
- SET << Mime.const_get(symbol.to_s.upcase)
- # 把这个常量放入SET中
- ([string] + mime_type_synonyms).each { |string| LOOKUP[string] = SET.last } unless skip_lookup
- #所有mine_types都设置到lookup中
- ([symbol.to_s] + extension_synonyms).each { |ext| EXTENSION_LOOKUP[ext] = SET.last }
- # 所有扩展都设置到扩展中
- end
- def lookup(string)
- LOOKUP[string]
- end
- def lookup_by_extension(extension)
- EXTENSION_LOOKUP[extension]
- end
Mine::Type实体的对象方法:
- def initialize(string, symbol = nil, synonyms = [])
- @symbol, @synonyms = symbol, synonyms
- @string = string
- end
- def to_s
- @string
- end
- def to_str
- to_s
- end
- def to_sym
- @symbol || @string.to_sym
- end
- def ===(list)
- if list.is_a?(Array)
- (@synonyms + [ self ]).any? { |synonym| list.include?(synonym) }
- else
- super
- end
- end
- def ==(mime_type)
- return false if mime_type.blank?
- (@synonyms + [ self ]).any? do |synonym|
- synonym.to_s == mime_type.to_s || synonym.to_sym == mime_type.to_sym
- end
- end
- # Returns true if Action Pack should check requests using this Mime Type for possible request forgery. See
- # ActionController::RequestForgerProtection.
- def verify_request?
- !@@unverifiable_types.include?(to_sym)
- end
- def html?
- @@html_types.include?(to_sym) || @string =~ /html/
- end
- private
- def method_missing(method, *args)
- if method.to_s =~ /(/w+)/?$/
- $1.downcase.to_sym == to_sym
- else
- super
- end
- end
实现
- class Responder #:nodoc:
- def initialize(controller)
- @controller = controller
- @request = controller.request
- @response = controller.response
- @mime_type_priority = Array(Mime::Type.lookup_by_extension(@request.parameters[:format]) || @request.accepts) // 根据@request的参数:format就是扩展来查询,如果没有就是在accepts方法里获取
- @order = []
- @responses = {}
- end
- def custom(mime_type, &block)
- #如果是Mine::Type那么直接返回
- #如果不是,那么lookup table中去查找
- mime_type = mime_type.is_a?(Mime::Type) ? mime_type : Mime::Type.lookup(mime_type.to_s)
- @order << mime_type
- # 设置一个Proc等待调用
- @responses[mime_type] ||= Proc.new do
- @response.template.template_format = mime_type.to_sym #返回symbol作为格式
- @response.content_type = mime_type.to_s #返回to_s就是类似"text/html"这段
- block_given? ? block.call : @controller.send(:render, :action => @controller.action_name)
- #如果没有设置block直接调用render方法
- # 例如 format.html
- #如果设置那么执行block
- # 例如:format.xml { render :xml => @people.to_xml }
- end
- end
- def any(*args, &block)
- if args.any?
- args.each { |type| send(type, &block) }
- else
- custom(@mime_type_priority.first, &block) //取优先级第一个的mine_type
- end
- end
- def method_missing(symbol, &block)
- mime_constant = symbol.to_s.upcase
- if Mime::SET.include?(Mime.const_get(mime_constant))
- #如果在SET中包含了这个常量就调用custom才处理这个常量
- 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 @responses[priority]
- @responses[priority].call
- return # mime type match found, be happy and return
- end
- end
- end
- if @order.include?(Mime::ALL)
- @responses[Mime::ALL].call
- else
- @controller.send :head, :not_acceptable
- end
- end
- end
其中Request的accepts方法如下:
- def accepts
- @accepts ||=
- if @env['HTTP_ACCEPT'].to_s.strip.empty?
- [ content_type, Mime::ALL ].compact # make sure content_type being nil is not included
- else
- Mime::Type.parse(@env['HTTP_ACCEPT'])
- end
- end