Class: Rack::Prerender::Fetcher

Inherits:
Object
  • Object
show all
Defined in:
lib/rack/prerender/fetcher.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Fetcher

Returns a new instance of Fetcher.



8
9
10
# File 'lib/rack/prerender/fetcher.rb', line 8

def initialize(options = {})
  @options = options
end

Instance Attribute Details

#envObject (readonly)

Returns the value of attribute env.



6
7
8
# File 'lib/rack/prerender/fetcher.rb', line 6

def env
  @env
end

#optionsObject (readonly)

Returns the value of attribute options.



6
7
8
# File 'lib/rack/prerender/fetcher.rb', line 6

def options
  @options
end

Instance Method Details

#after_render(env, response) ⇒ Object



159
160
161
# File 'lib/rack/prerender/fetcher.rb', line 159

def after_render(env, response)
  (callback = options[:after_render]) && callback.call(env, response)
end

#api_url(url) ⇒ Object



87
88
89
90
91
92
93
# File 'lib/rack/prerender/fetcher.rb', line 87

def api_url(url)
  if service_url.match?(/[=\/]$/)
    "#{service_url}#{url}"
  else
    "#{service_url}/#{url}"
  end
end

#before_render(env) ⇒ Object



43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/rack/prerender/fetcher.rb', line 43

def before_render(env)
  return unless callback = options[:before_render]

  cached_render = callback.call(env)

  if cached_render && cached_render.is_a?(String)
    Rack::Response.new(cached_render, 200, { 'Content-Type' => 'text/html; charset=utf-8' })
  elsif cached_render && cached_render.is_a?(Rack::Response)
    cached_render
  else
    nil
  end
end

#build_rack_response_from_prerender(prerendered_response) ⇒ Object



147
148
149
150
151
152
153
154
155
156
157
# File 'lib/rack/prerender/fetcher.rb', line 147

def build_rack_response_from_prerender(prerendered_response)
  response = Rack::Response.new(
    prerendered_response.body,
    prerendered_response.code,
    prerendered_response,
  )
  if callback = options[:build_rack_response_from_prerender]
    callback.call(response, prerendered_response)
  end
  response
end

#call(env) ⇒ Object

Entry point for automatic fetching via the middleware.



13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/rack/prerender/fetcher.rb', line 13

def call(env)
  cached_response = before_render(env)
  return cached_response.finish if cached_response

  if prerendered_response = fetch_env(env)
    response = build_rack_response_from_prerender(prerendered_response)
    after_render(env, prerendered_response)
    return response.finish
  end

  nil
end

#configure_protocol(env, protocol) ⇒ Object



128
129
130
131
132
133
134
# File 'lib/rack/prerender/fetcher.rb', line 128

def configure_protocol(env, protocol)
  return unless protocol == 'http' || protocol == 'https'

  env['rack.url_scheme'] = protocol
  env['HTTPS']           = protocol == 'https'
  env['SERVER_PORT']     = protocol == 'https' ? 443 : 80
end

#decompress(response) ⇒ Object



136
137
138
139
140
141
142
143
144
145
# File 'lib/rack/prerender/fetcher.rb', line 136

def decompress(response)
  if response['Content-Encoding'] == 'gzip'
    response.body =
      Zlib::GzipReader.wrap(StringIO.new(response.body), &:read)
    response['Content-Length'] = response.body.bytesize
    response.delete('Content-Encoding')
    response.delete('Transfer-Encoding')
  end
  response
end

#fetch(arg) ⇒ Object

Entry point for manual fetching. Does not run callbacks.



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/rack/prerender/fetcher.rb', line 27

def fetch(arg)
  case arg
  when String, Symbol, URI then fetch_url(arg.to_s)
  when Hash                then fetch_env(arg)
  when Rack::Request       then fetch_env(arg.env)
  else
    if defined?(ActiveModel::Naming) && ActiveModel::Naming === arg.class
      record_url = Rails.application.routes.url_helpers.url_for(arg)
      fetch_url(record_url)
    else
      raise ArgumentError,
            "expected URL, Request, env or record, got #{arg.class}"
    end
  end
end

#fetch_api_uri(uri, as: nil) ⇒ Object

This is just horrible, but replacing net/http would break compatibility because the response object is leaked to several callbacks :(



74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/rack/prerender/fetcher.rb', line 74

def fetch_api_uri(uri, as: nil)
  req = Net::HTTP::Get.new(uri.request_uri, headers(user_agent: as))
  if options[:basic_auth]
    req.basic_auth(ENV['PRERENDER_USERNAME'], ENV['PRERENDER_PASSWORD'])
  end
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true if uri.scheme == 'https'
  response = http.request(req)
  decompress(response)
rescue
  nil
end

#fetch_env(env) ⇒ Object



57
58
59
# File 'lib/rack/prerender/fetcher.rb', line 57

def fetch_env(env)
  fetch_url(request_url(env), as: env['HTTP_USER_AGENT'])
end

#fetch_url(url, as: nil) ⇒ Object



61
62
63
64
# File 'lib/rack/prerender/fetcher.rb', line 61

def fetch_url(url, as: nil)
  uri = try_uri_parse(api_url(url))
  uri && fetch_api_uri(uri, as: as)
end

#headers(user_agent: nil) ⇒ Object



100
101
102
103
104
105
106
# File 'lib/rack/prerender/fetcher.rb', line 100

def headers(user_agent: nil)
  {
    'Accept-Encoding'   => 'gzip',
    'User-Agent'        => user_agent,
    'X-Prerender-Token' => token,
  }.compact
end

#request_url(env) ⇒ Object



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/rack/prerender/fetcher.rb', line 112

def request_url(env)
  if env['CF-VISITOR'] && protocol = env['CF-VISITOR'][/"scheme":"(http|https)"/, 1]
    configure_protocol(env, protocol)
  end

  if env['X-FORWARDED-PROTO'] && protocol = env["X-FORWARDED-PROTO"].split(',')[0]
    configure_protocol(env, protocol)
  end

  if protocol = options[:protocol]
    configure_protocol(env, protocol)
  end

  Rack::Request.new(env).url
end

#service_urlObject



95
96
97
98
# File 'lib/rack/prerender/fetcher.rb', line 95

def service_url
  options[:prerender_service_url] || ENV['PRERENDER_SERVICE_URL'] ||
    'http://service.prerender.io'
end

#tokenObject



108
109
110
# File 'lib/rack/prerender/fetcher.rb', line 108

def token
  options[:prerender_token] || ENV['PRERENDER_TOKEN']
end

#try_uri_parse(url) ⇒ Object



66
67
68
69
70
# File 'lib/rack/prerender/fetcher.rb', line 66

def try_uri_parse(url)
  URI.parse(url)
rescue URI::InvalidURIError
  nil
end