Class: HttpSession

Inherits:
Object
  • Object
show all
Defined in:
lib/http_session.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(host, use_ssl, port) ⇒ HttpSession

don’t use new() directly, use singleton get() or use() instead



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/http_session.rb', line 128

def initialize(host, use_ssl, port)
  self.handle = Net::HTTP.new(host, port)
  self.handle.open_timeout    = @@open_timeout     # seems to have no effect?
  self.handle.read_timeout    = @@read_timeout     # seems to have an effect on establishing tcp connection??
  self.handle.close_on_empty_response = true     # seems to have no effect?
  if use_ssl
    self.handle.use_ssl       = true
    # respond_to? check is a ruby-1.9.1-p378 (+/-?) bug workaround
    self.handle.ssl_timeout   = @@ssl_timeout if OpenSSL::SSL::SSLContext.new.respond_to?(:ssl_timeout=)
    self.handle.verify_mode   = @@ssl_verify_mode
    self.handle.ca_file       = @@ssl_ca_file
    self.handle.verify_depth  = @@ssl_verify_depth
  end
  self.cookies = {}
end

Instance Attribute Details

#cookiesObject

storage for cookies, for instance method usage



191
192
193
# File 'lib/http_session.rb', line 191

def cookies
  @cookies
end

#handleObject

storage for open session handle, for instance method usage



125
126
127
# File 'lib/http_session.rb', line 125

def handle
  @handle
end

Class Method Details

.exists?(host, use_ssl = false, port = nil) ⇒ Boolean

check if a session exists yet ot not

Returns:

  • (Boolean)


150
151
152
# File 'lib/http_session.rb', line 150

def self.exists?(host, use_ssl=false, port=nil)
  session_store.has_key?(key(host, use_ssl, port))
end

.get(host, use_ssl = false, port = nil) ⇒ Object

get the session for the given host and port, nil if there isn’t one yet



155
156
157
# File 'lib/http_session.rb', line 155

def self.get(host, use_ssl=false, port=nil)
  session_store[key(host, use_ssl, port)]
end

.get_request_url(url, headers = {}) ⇒ Object

shortcut for parsing a full url and performing simple GET requests returns Net::HTTPResponse, or raises Timeout::Error, SystemCallError, OpenSSL::SSL::SSLError, EOFError, Net::ProtocolError



58
59
60
61
# File 'lib/http_session.rb', line 58

def self.get_request_url(url, headers={})
  parsed = URI.parse(url)
  use(parsed.host, parsed.scheme == 'https', parsed.port).request(parsed.path + (parsed.query.nil? ? '' : "?#{parsed.query}"), headers)
end

.key(host, use_ssl, port) ⇒ Object

just our own internal session key… (it looks like: “scheme://host:port”)



145
146
147
# File 'lib/http_session.rb', line 145

def self.key(host, use_ssl, port)
  "#{use_ssl ? 'https' : 'http'}://#{host}:#{port_or_default(port, use_ssl)}"
end

.open_timeout=(v) ⇒ Object

it really sux that plain ruby doesn’t have cattr_accessor so we have to do 30 lines of repeating ourselves!



8
9
10
# File 'lib/http_session.rb', line 8

def self.open_timeout=(v)
  @@open_timeout = v
end

.port_or_default(port, use_ssl) ⇒ Object

return the given port, or defaults for ssl setting if it’s nil



181
182
183
# File 'lib/http_session.rb', line 181

def self.port_or_default(port, use_ssl)
  port.nil? ? (use_ssl ? Net::HTTP.https_default_port : Net::HTTP.http_default_port) : port
end

.post_request_url(url, params, headers = {}) ⇒ Object

shortcut for parsing a full url and performing simple POST requests returns Net::HTTPResponse, or raises Timeout::Error, SystemCallError, OpenSSL::SSL::SSLError, EOFError, Net::ProtocolError



65
66
67
68
# File 'lib/http_session.rb', line 65

def self.post_request_url(url, params, headers={})
  parsed = URI.parse(url)
  use(parsed.host, parsed.scheme == 'https', parsed.port).request(parsed.path + (parsed.query.nil? ? '' : "?#{parsed.query}"), headers, :post, params)
end

.read_timeout=(v) ⇒ Object



11
12
13
# File 'lib/http_session.rb', line 11

def self.read_timeout=(v)
  @@read_timeout = v
end

.redirect_limit=(v) ⇒ Object



14
15
16
# File 'lib/http_session.rb', line 14

def self.redirect_limit=(v)
  @@redirect_limit = v
end

.retry_limit=(v) ⇒ Object



17
18
19
# File 'lib/http_session.rb', line 17

def self.retry_limit=(v)
  @@retry_limit = v
end

.session_storeObject

Simplest thread-safe pooling mechanism is to make a separate session store for every thread. Each thread makes its own connection(s), the way you’d expect that way. Just don’t try to share http_session instances between separate threads, and it will work fine.



120
121
122
# File 'lib/http_session.rb', line 120

def self.session_store
  Thread.current['http_session_sessions'] ||= {}
end

.ssl_ca_file=(v) ⇒ Object



26
27
28
# File 'lib/http_session.rb', line 26

def self.ssl_ca_file=(v)
  @@ssl_ca_file = v
end

.ssl_timeout=(v) ⇒ Object



20
21
22
# File 'lib/http_session.rb', line 20

def self.ssl_timeout=(v)
  @@ssl_timeout = v
end

.ssl_verify_depth=(v) ⇒ Object



29
30
31
# File 'lib/http_session.rb', line 29

def self.ssl_verify_depth=(v)
  @@ssl_verify_depth = v
end

.ssl_verify_mode=(v) ⇒ Object



23
24
25
# File 'lib/http_session.rb', line 23

def self.ssl_verify_mode=(v)
  @@ssl_verify_mode = v
end

.use(host, use_ssl = false, port = nil) ⇒ Object

get the session for the given host and port, creating a new one if it doesn’t exist



160
161
162
163
164
# File 'lib/http_session.rb', line 160

def self.use(host, use_ssl=false, port=nil)
  exists?(host, use_ssl, port) ?
    get(host, use_ssl, port) :
    (session_store[key(host, use_ssl, port)] = new(host, use_ssl, port_or_default(port, use_ssl)))
end

Instance Method Details

#add_cookies(response) ⇒ Object

store the cookies from the given response into the session (ignores all host/path/expires/secure/etc cookie options!)



204
205
206
207
208
209
210
# File 'lib/http_session.rb', line 204

def add_cookies(response)
  return unless response.key?('set-cookie')
  response.get_fields('set-cookie').each do |cookie|
    (key, val) = cookie.split('; ')[0].split('=', 2)
    cookies[key] = val
  end
end

#closeObject

done with this session, close and reset it but it still exists in the session storage in a dormant/empty state, so next request would easily reopen it



168
169
170
171
# File 'lib/http_session.rb', line 168

def close
  handle.finish if handle.started?
  self.cookies = {}
end

return all current cookies in a comma-delimited name=value string format



199
200
201
# File 'lib/http_session.rb', line 199

def cookie_string
  cookies.collect { |name, val| "#{name}=#{val}" }.join(', ')
end

#cookies?Boolean

check if a session has any cookies or not

Returns:

  • (Boolean)


194
195
196
# File 'lib/http_session.rb', line 194

def cookies?
  cookies.length > 0
end

#deleteObject

delete session from session storage (you should probably call close on it too, and set all references to nil so it gets garbage collected)



174
175
176
177
178
# File 'lib/http_session.rb', line 174

def delete
  if self.class.exists?(handle.address, handle.use_ssl?, handle.port)
    self.class.session_store.delete(self.class.key(handle.address, handle.use_ssl?, handle.port))
  end
end

#request(uri = '/', headers = {}, type = :get, post_params = {}, redirect_limit = @@redirect_limit, retry_limit = @@retry_limit) ⇒ Object

internally handle GET and POST requests (recursively for redirects and retries) returns Net::HTTPResponse, or raises Timeout::Error, SystemCallError, OpenSSL::SSL::SSLError, EOFError, Net::ProtocolError



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/http_session.rb', line 72

def request(uri='/', headers={}, type=:get, post_params={}, redirect_limit=@@redirect_limit, retry_limit=@@retry_limit)
  req = case type
    when :get
      Net::HTTP::Get.new(uri)
    when :post
      Net::HTTP::Post.new(uri)
    else
      raise ArgumentError, "bad type: #{type}"
  end
  headers.each { |k, v| req[k] = v } unless headers.empty?
  req['Cookie'] = cookie_string if cookies?
  req.set_form_data(post_params) if type == :post
  
  begin
    handle.start unless handle.started? # may raise Timeout::Error or OpenSSL::SSL::SSLError
    response = handle.request(req) # may raise Errno::* (subclasses of SystemCallError) or EOFError
  rescue Timeout::Error, SystemCallError, EOFError
    handle.finish if handle.started?
    raise if retry_limit == 0
    request(uri, headers, type, post_params, redirect_limit, retry_limit - 1)
    
  else
    add_cookies response
    if response.kind_of?(Net::HTTPRedirection)
      raise Net::HTTPError.new('Redirection limit exceeded', response)  if redirect_limit == 0
      loc = URI.parse(response['location'])
      if loc.scheme && loc.host && loc.port
        self.class.use(loc.host, loc.scheme == 'https', loc.port).request(loc.path + (loc.query.nil? ? '' : "?#{loc.query}"), headers, :get, {}, redirect_limit - 1)
      else
        # only really bad web servers would ever send this... since it's not technically a valid value!
        request(loc.path + (loc.query.nil? ? '' : "?#{loc.query}"), headers, :get, {}, redirect_limit - 1)
      end
    else
      response.error! unless response.kind_of?(Net::HTTPOK) # raises Net::HTTP*Error/Exception (subclasses of Net::ProtocolError)
      raise Net::HTTPError.new('Document has no body', response) if response.body.nil? || response.body == ''
      response
    end
  end
end