module Hanami module Action # Ensures to not send body or headers for HEAD requests and/or for status # codes that doesn't allow them. # # @since 0.3.2 # # @see http://www.ietf.org/rfc/rfc2616.txt module Head # Status codes that by RFC must not include a message body # # @since 0.3.2 # @api private HTTP_STATUSES_WITHOUT_BODY = Set.new((100..199).to_a << 204 << 205 << 304).freeze # Entity headers allowed in blank body responses, according to # RFC 2616 - Section 10 (HTTP 1.1). # # "The response MAY include new or updated metainformation in the form # of entity-headers". # # @since 0.4.0 # @api private # # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.5 # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html ENTITY_HEADERS = { 'Allow' => true, 'Content-Encoding' => true, 'Content-Language' => true, 'Content-Location' => true, 'Content-MD5' => true, 'Content-Range' => true, 'Expires' => true, 'Last-Modified' => true, 'extension-header' => true }.freeze # Ensures to not send body or headers for HEAD requests and/or for status # codes that doesn't allow them. # # @since 0.3.2 # @api private # # @see Hanami::Action#finish def finish super if _requires_no_body? @_body = nil @headers.reject! {|header,_| !keep_response_header?(header) } end end protected # @since 0.3.2 # @api private def _requires_no_body? HTTP_STATUSES_WITHOUT_BODY.include?(@_status) || head? end private # According to RFC 2616, when a response MUST have an empty body, it only # allows Entity Headers. # # For instance, a 204 doesn't allow Content-Type or any # other custom header. # # This restriction is enforced by Hanami::Action::Head#finish. # # However, there are cases that demand to bypass this rule to set meta # informations via headers. # # An example is a DELETE request for a JSON API application. # It returns a 204 but still wants to specify the rate limit # quota via X-Rate-Limit. # # @since 0.5.0 # # @see Hanami::Action::HEAD#finish # # @example # require 'hanami/controller' # # module Books # class Destroy # include Hanami::Action # # def call(params) # # ... # self.headers.merge!( # 'Last-Modified' => 'Fri, 27 Nov 2015 13:32:36 GMT', # 'X-Rate-Limit' => '4000', # 'Content-Type' => 'application/json', # 'X-No-Pass' => 'true' # ) # # self.status = 204 # end # # private # # def keep_response_header?(header) # super || header == 'X-Rate-Limit' # end # end # end # # # Only the following headers will be sent: # # * Last-Modified - because we used `super' in the method that respects the HTTP RFC # # * X-Rate-Limit - because we explicitely allow it # # # Both Content-Type and X-No-Pass are removed because they're not allowed def keep_response_header?(header) ENTITY_HEADERS.include?(header) end end end end