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



146
147
148
# File 'lib/rack/prerender/fetcher.rb', line 146

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

#api_url(url) ⇒ Object



75
76
77
78
79
80
81
# File 'lib/rack/prerender/fetcher.rb', line 75

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

#before_render(env) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/rack/prerender/fetcher.rb', line 37

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



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

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



116
117
118
119
120
121
122
# File 'lib/rack/prerender/fetcher.rb', line 116

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



124
125
126
127
128
129
130
131
132
# File 'lib/rack/prerender/fetcher.rb', line 124

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')
  end
  response
end

#fetch(arg) ⇒ Object

Entry point for manual fetching. Does not run callbacks.



27
28
29
30
31
32
33
34
35
# 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
    raise ArgumentError, "expected a URL, Request or env, got #{arg.class}"
  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 :(



62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/rack/prerender/fetcher.rb', line 62

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



51
52
53
# File 'lib/rack/prerender/fetcher.rb', line 51

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

#fetch_url(url, as: nil) ⇒ Object



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

def fetch_url(url, as: nil)
  uri = URI.parse(api_url(url))
  fetch_api_uri(uri, as: as)
end

#headers(user_agent: nil) ⇒ Object



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

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

#request_url(env) ⇒ Object



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/rack/prerender/fetcher.rb', line 100

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



83
84
85
86
# File 'lib/rack/prerender/fetcher.rb', line 83

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

#tokenObject



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

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