class Rack::JSONP
A Rack
middleware for providing JSON-P support.
Full credit to Flinn Mueller (actsasflinn.com/) for this contribution.
Constants
- U2029
- VALID_CALLBACK
Public Class Methods
# File lib/rack/contrib/jsonp.rb 26 def initialize(app) 27 @app = app 28 end
Public Instance Methods
Proxies the request to the application, stripping out the JSON-P callback method and padding the response with the appropriate callback format if the returned body is application/json
Changes nothing if no callback
param is specified.
# File lib/rack/contrib/jsonp.rb 36 def call(env) 37 request = Rack::Request.new(env) 38 39 status, headers, response = @app.call(env) 40 41 if STATUS_WITH_NO_ENTITY_BODY.include?(status) 42 return status, headers, response 43 end 44 45 headers = HeaderHash.new(headers) 46 47 if is_json?(headers) && has_callback?(request) 48 callback = request.params['callback'] 49 return bad_request unless valid_callback?(callback) 50 51 response = pad(callback, response) 52 53 # No longer json, its javascript! 54 headers['Content-Type'] = headers['Content-Type'].gsub('json', 'javascript') 55 56 # Set new Content-Length, if it was set before we mutated the response body 57 if headers['Content-Length'] 58 length = response.map(&:bytesize).reduce(0, :+) 59 headers['Content-Length'] = length.to_s 60 end 61 end 62 63 [status, headers, response] 64 end
Private Instance Methods
# File lib/rack/contrib/jsonp.rb 110 def bad_request(body = "Bad Request") 111 [ 400, { 'Content-Type' => 'text/plain', 'Content-Length' => body.bytesize.to_s }, [body] ] 112 end
# File lib/rack/contrib/jsonp.rb 72 def has_callback?(request) 73 request.params.include?('callback') and not request.params['callback'].to_s.empty? 74 end
# File lib/rack/contrib/jsonp.rb 68 def is_json?(headers) 69 headers.key?('Content-Type') && headers['Content-Type'].include?('application/json') 70 end
Pads the response with the appropriate callback format according to the JSON-P spec/requirements.
The Rack
response spec indicates that it should be enumerable. The method of combining all of the data into a single string makes sense since JSON is returned as a full string.
# File lib/rack/contrib/jsonp.rb 92 def pad(callback, response) 93 body = response.to_enum.map do |s| 94 # U+2028 and U+2029 are allowed inside strings in JSON (as all literal 95 # Unicode characters) but JavaScript defines them as newline 96 # seperators. Because no literal newlines are allowed in a string, this 97 # causes a ParseError in the browser. We work around this issue by 98 # replacing them with the escaped version. This should be safe because 99 # according to the JSON spec, these characters are *only* valid inside 100 # a string and should therefore not be present any other places. 101 s.gsub(U2028, '\u2028').gsub(U2029, '\u2029') 102 end.join 103 104 # https://github.com/rack/rack-contrib/issues/46 105 response.close if response.respond_to?(:close) 106 107 ["/**/#{callback}(#{body})"] 108 end
See: stackoverflow.com/questions/1661197/valid-characters-for-javascript-variable-names
NOTE: Supports dots (.) since callbacks are often in objects:
# File lib/rack/contrib/jsonp.rb 81 def valid_callback?(callback) 82 callback =~ VALID_CALLBACK 83 end