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

new(app) click to toggle source
   # File lib/rack/contrib/jsonp.rb
26 def initialize(app)
27   @app = app
28 end

Public Instance Methods

call(env) click to toggle source

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

bad_request(body = "Bad Request") click to toggle source
    # 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
has_callback?(request) click to toggle source
   # 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
is_json?(headers) click to toggle source
   # File lib/rack/contrib/jsonp.rb
68 def is_json?(headers)
69   headers.key?('Content-Type') && headers['Content-Type'].include?('application/json')
70 end
pad(callback, response) click to toggle source

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
valid_callback?(callback) click to toggle source

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