http_session 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm --create ruby-1.9.2-p180@http_session
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source "http://rubygems.org"
2
+
3
+ # No dependencies required to use this gem
4
+
5
+ # Dependencies to develop this gem
6
+ group :development do
7
+ gem "rake", "= 0.8.7"
8
+ gem "shoulda", ">= 0"
9
+ gem "bundler", "~> 1.0.0"
10
+ gem "jeweler", "~> 1.6.2"
11
+ gem "rcov", ">= 0", :platforms => :ruby_18
12
+ gem 'simplecov', '>= 0.4.0', :require => false, :platforms => :ruby_19
13
+ gem "rdoc", ">= 0", :platforms => :ruby_19
14
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,27 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ git (1.2.5)
5
+ jeweler (1.6.2)
6
+ bundler (~> 1.0)
7
+ git (>= 1.2.5)
8
+ rake
9
+ rake (0.8.7)
10
+ rcov (0.9.9)
11
+ rdoc (3.6.1)
12
+ shoulda (2.11.3)
13
+ simplecov (0.4.2)
14
+ simplecov-html (~> 0.4.4)
15
+ simplecov-html (0.4.5)
16
+
17
+ PLATFORMS
18
+ ruby
19
+
20
+ DEPENDENCIES
21
+ bundler (~> 1.0.0)
22
+ jeweler (~> 1.6.2)
23
+ rake (= 0.8.7)
24
+ rcov
25
+ rdoc
26
+ shoulda
27
+ simplecov (>= 0.4.0)
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (C) 2011 by David Burry
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,72 @@
1
+
2
+ = HttpSession
3
+
4
+ A useful yet still extremely light-weight web client built on top of Ruby <tt>Net::HTTP</tt>. Keeps certain information internally in a session for each host/port used. Great for simple web page scraping or web service API usage.
5
+
6
+ == Dependencies
7
+
8
+ * just Ruby 1.8.6 - 1.9.2
9
+ * no third party gem dependencies!
10
+
11
+ == Features
12
+
13
+ * Extremely light-weight at just 100-200 lines of code
14
+ * Supports GET and POST requests, including POST parameter data
15
+ * Supports SSL connections in a natural way, and performs certificate verification using the same CA bundle as Mozilla
16
+ * Automatically uses KeepAlives if the server supports them
17
+ * Automatically remembers Cookies for each session (though currently ignores host/path/expires/secure options)
18
+ * Automatically follows Redirects, up to a certain limit
19
+ * Automatically retries again if certain possibly transient errors happen, up to a limit. Useful, for example, when the KeepAlive limit is reached and the server hangs up on you, it just retries and keeps going without missing a beat.
20
+ * Supports additional headers for whatever simple additional HTTP features you may need.
21
+
22
+ == Usage
23
+
24
+ Normal example usage, parses url string to determine its behavior:
25
+
26
+ response_object = HttpSession.get_request_url(url_string)
27
+ response_object = HttpSession.get_request_url(url_string, headers_hash)
28
+ response_object = HttpSession.post_request_url(url_string, post_params_hash)
29
+ response_object = HttpSession.post_request_url(url_string, post_params_hash, headers_hash)
30
+
31
+ Lower level examples, if your url is already broken down into pieces:
32
+
33
+ session_object = HttpSession.use(host_string, use_ssl_boolean, port_integer)
34
+ response_object = session_object.request(uri_string, headers_hash, get_or_post_symbol, post_params_hash)
35
+
36
+ session_object = HttpSession.use(host_string) # defaults are: false, 80 (or 443 if use_ssl is true)
37
+ response_object = session_object.request # defaults are: '/', {}, :get, {}
38
+
39
+ All the above return:
40
+ * a <tt>Net::HTTPResponse</tt> object in all its glory
41
+
42
+ Or raise any of the following:
43
+ * <tt>Timeout::Error</tt>, <tt>Errno::*</tt> (<tt>SystemCallError</tt> subclass), <tt>OpenSSL::SSL::SSLError</tt>, or <tt>EOFError</tt> - for various connection-related issues
44
+ * <tt>Net::HTTP*Error/Exception</tt> (<tt>Net::ProtocolError</tt> subclass) - for HTTP response errors
45
+
46
+ Notes:
47
+ * You do not need to check the response object for HTTP errors like <tt>404 "Not Found"</tt> or others, they are raised automatically, and can be caught when needed. In my opinion this is better than how <tt>Net::HTTP</tt> behaves.
48
+ * A common pattern is to feed the <tt>response_object.body</tt> string into Nokogiri[http://nokogiri.org/] for further processing.
49
+
50
+ == Contributing
51
+
52
+ If you think you found a bug or want a feature, get involved at http://github.com/dburry/http_session/issues If you'd then like to contribute a patch, use Github's wonderful fork and pull request features. Keep in mind that one of the main goals of this library is to remain light-weight, so changes done in this spirit are most likely to be included.
53
+
54
+ To set up a full development environment:
55
+ * <tt>git clone</tt> the repository,
56
+ * have RVM[https://rvm.beginrescueend.com/] and Bundler[http://gembundler.com/] installed,
57
+ * then cd into your repo (follow any RVM prompts if this is your first time using that),
58
+ * and run <tt>bundle install</tt> to pull in all the rest of the development dependencies.
59
+ * After that point, <tt>rake -T</tt> should be fairly self-explanatory.
60
+ * Check out the <tt>rake -T rubies</tt> tasks for some neat multi-ruby-version setup and testing.
61
+
62
+ Note: If <tt>rake -T</tt> doesn't show much or gives you warnings about missing libraries, then perhaps you did not install RVM, or run <tt>bundle install</tt> correctly, or you do not have the right Ruby version or gemset installed/selected with RVM. You can either correct this the normal way, or (if you have RVM) run the <tt>rake rubies:setup</tt> task, which makes sure several different versions of Ruby are intalled and sets up a nicely configured gemset in each for you. You can then use RVM to switch between them for manually testing or running anything you want in any version (although the <tt>.rvmrc</tt> chooses Ruby 1.9.2 for you currently).
63
+
64
+ == Alternatives
65
+
66
+ Light-weight libraries are great resource savers when you only need limited features. But they're not for every situation. Here are some other great alternatives:
67
+
68
+ * Mechanize[http://mechanize.rubyforge.org/] - Much more fully-featured web page scraping library.
69
+
70
+ == License
71
+
72
+ This library is distributed under the MIT license. Please see the LICENSE file.
data/Rakefile ADDED
@@ -0,0 +1,148 @@
1
+ # encoding: utf-8
2
+
3
+ # like printing `rvm current` only it actually works in `rvm version,version,version rake some:task`
4
+ puts "\n*** Using #{File.basename(ENV['GEM_HOME'])}" if ENV.has_key?('GEM_HOME')
5
+
6
+ # for some odd reason, requiring bundler causes rake and jeweler to do recursive requires on ruby 1.9.2...
7
+ # therefore the following is all commented out for now...
8
+ # bundler is still used in development, just use it on the command line to make a clean rvm gemset (or use rubies:* tasks)
9
+ # instead of this enforcing the gems at rake run time
10
+
11
+ # require 'rubygems'
12
+ # require 'bundler'
13
+ # begin
14
+ # Bundler.require(:default, :development)
15
+ # rescue Bundler::BundlerError => e
16
+ # $stderr.puts e.message
17
+ # $stderr.puts "Run `bundle install` to install missing gems"
18
+ # exit e.status_code
19
+ # end
20
+ require 'rake'
21
+
22
+
23
+ begin
24
+ require 'jeweler'
25
+ rescue LoadError
26
+ puts 'WARNING: missing jeweler library, some tasks are not available'
27
+ else
28
+ Jeweler::Tasks.new do |gem|
29
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
30
+ gem.name = "http_session"
31
+ gem.homepage = "http://github.com/dburry/http_session"
32
+ gem.license = "MIT"
33
+ gem.summary = %Q{A useful yet still extremely light-weight web client built on top of Ruby Net::HTTP}
34
+ gem.email = "dburry@falcon"
35
+ gem.authors = ["David Burry"]
36
+ # dependencies defined in Gemfile
37
+ end
38
+ Jeweler::RubygemsDotOrgTasks.new
39
+ end
40
+
41
+ begin
42
+ require 'rake/testtask'
43
+ rescue LoadError
44
+ puts 'WARNING: missing test library, some tasks are not available'
45
+ else
46
+ task :default => :test
47
+ Rake::TestTask.new(:test) do |test|
48
+ test.libs << 'lib' << 'test'
49
+ test.pattern = 'test/**/test_*.rb'
50
+ end
51
+ end
52
+
53
+ if RUBY_VERSION =~ /^1\.8\./
54
+ begin
55
+ require 'rcov/rcovtask'
56
+ rescue LoadError
57
+ puts 'WARNING: missing rcov library, some tasks are not available'
58
+ else
59
+ Rcov::RcovTask.new do |test|
60
+ test.libs << 'test'
61
+ test.pattern = 'test/**/test_*.rb'
62
+ end
63
+ end
64
+ end
65
+ if RUBY_VERSION =~ /^1\.9\./
66
+ begin
67
+ require 'simplecov'
68
+ rescue LoadError
69
+ puts 'WARNING: missing simplecov library, some tasks are not available'
70
+ else
71
+ desc 'Analyze code coverage with tests'
72
+ task :coverage => 'coverage:clobber' do
73
+ ENV['USE_SIMPLECOV'] = :true.to_s
74
+ Rake::Task['test'].invoke
75
+ # see top of test helper for the rest of this task, which is triggered with the environment var....
76
+ end
77
+ namespace :coverage do
78
+ desc 'Remove output directory generated by simplecov'
79
+ task :clobber do
80
+ FileUtils.rm_rf(File.expand_path('../coverage', __FILE__))
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ if RUBY_VERSION =~ /^1\.9/
87
+ begin
88
+ require 'rdoc/task'
89
+ rescue LoadError
90
+ puts 'WARNING: missing rdoc library, some tasks are not available'
91
+ else
92
+ RDoc::Task.new do |rdoc|
93
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
94
+ rdoc.rdoc_dir = 'rdoc'
95
+ rdoc.title = "http_session #{version}"
96
+ rdoc.rdoc_files.include('README*')
97
+ rdoc.rdoc_files.include('lib/**/*.rb')
98
+ end
99
+ end
100
+ end
101
+
102
+ if `which rvm`.empty?
103
+ puts 'WARNING: missing rvm executable, some tasks are not available'
104
+ else
105
+ task :rubies => 'rubies:list'
106
+ namespace :rubies do
107
+ @@ruby_versions = [
108
+ # edit this list to test on more rubies...
109
+ # though beware that some development dependencies might be troublesome on some versions...
110
+ 'ruby-1.9.2-p180',
111
+ 'ruby-1.9.1-p378',
112
+ 'ruby-1.8.7-p330',
113
+ 'ruby-1.8.6-p399'
114
+ ]
115
+ def installed_ruby_versions
116
+ current_rubies = `rvm list`
117
+ ruby_version_gemsets_string = @@ruby_versions.select { |r| current_rubies =~ /\b#{r}\b/ }
118
+ end
119
+ def missing_ruby_versions
120
+ current_rubies = `rvm list`
121
+ ruby_version_gemsets_string = @@ruby_versions.select { |r| current_rubies !~ /\b#{r}\b/ }
122
+ end
123
+ desc "List all the versions of Ruby these tasks use"
124
+ task :list do
125
+ puts 'The following Ruby versions are used in testing:'
126
+ @@ruby_versions.each { |r| puts r }
127
+ end
128
+ desc "Setup multiple versions of Ruby for testing, and populate an RVM gemset for each"
129
+ task :setup do
130
+ missing = missing_ruby_versions
131
+ system "rvm install #{missing.join(',')}" unless missing.empty?
132
+ ruby_version_gemsets_string = @@ruby_versions.collect { |r| r + '@http_session' }.join(',')
133
+ system "rvm --create #{ruby_version_gemsets_string} gem install bundler"
134
+ system "rvm #{ruby_version_gemsets_string} exec bundle install"
135
+ end
136
+ desc "Remove RVM gemsets (leave the Ruby versions installed though)"
137
+ task :cleanup do
138
+ installed_ruby_versions.each { |r| system "rvm --force #{r} gemset delete http_session" }
139
+ end
140
+ desc "Run tests on multiple versions of Ruby, using RVM"
141
+ task :test do
142
+ installed = installed_ruby_versions
143
+ puts "WARNING: some rubies are missing from RVM, run `rake rubies:setup` to install them" if installed != @@ruby_versions
144
+ ruby_version_gemsets_string = installed.collect { |r| r + '@http_session' }.join(',')
145
+ system "rvm #{ruby_version_gemsets_string} exec rake test"
146
+ end
147
+ end
148
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,75 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{http_session}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = [%q{David Burry}]
12
+ s.date = %q{2011-06-25}
13
+ s.email = %q{dburry@falcon}
14
+ s.extra_rdoc_files = [
15
+ "LICENSE",
16
+ "README.rdoc"
17
+ ]
18
+ s.files = [
19
+ ".document",
20
+ ".rvmrc",
21
+ "Gemfile",
22
+ "Gemfile.lock",
23
+ "LICENSE",
24
+ "README.rdoc",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "http_session.gemspec",
28
+ "lib/http_session.rb",
29
+ "share/ca/cacert.pem",
30
+ "test/helper.rb",
31
+ "test/ssl/README.rdoc",
32
+ "test/ssl/ca.crt",
33
+ "test/ssl/ca.key",
34
+ "test/ssl/server.crt",
35
+ "test/ssl/server.csr",
36
+ "test/ssl/server.key",
37
+ "test/test_http_session.rb"
38
+ ]
39
+ s.homepage = %q{http://github.com/dburry/http_session}
40
+ s.licenses = [%q{MIT}]
41
+ s.require_paths = [%q{lib}]
42
+ s.rubygems_version = %q{1.8.5}
43
+ s.summary = %q{A useful yet still extremely light-weight web client built on top of Ruby Net::HTTP}
44
+
45
+ if s.respond_to? :specification_version then
46
+ s.specification_version = 3
47
+
48
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
49
+ s.add_development_dependency(%q<rake>, ["= 0.8.7"])
50
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
51
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
52
+ s.add_development_dependency(%q<jeweler>, ["~> 1.6.2"])
53
+ s.add_development_dependency(%q<rcov>, [">= 0"])
54
+ s.add_development_dependency(%q<simplecov>, [">= 0.4.0"])
55
+ s.add_development_dependency(%q<rdoc>, [">= 0"])
56
+ else
57
+ s.add_dependency(%q<rake>, ["= 0.8.7"])
58
+ s.add_dependency(%q<shoulda>, [">= 0"])
59
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
60
+ s.add_dependency(%q<jeweler>, ["~> 1.6.2"])
61
+ s.add_dependency(%q<rcov>, [">= 0"])
62
+ s.add_dependency(%q<simplecov>, [">= 0.4.0"])
63
+ s.add_dependency(%q<rdoc>, [">= 0"])
64
+ end
65
+ else
66
+ s.add_dependency(%q<rake>, ["= 0.8.7"])
67
+ s.add_dependency(%q<shoulda>, [">= 0"])
68
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
69
+ s.add_dependency(%q<jeweler>, ["~> 1.6.2"])
70
+ s.add_dependency(%q<rcov>, [">= 0"])
71
+ s.add_dependency(%q<simplecov>, [">= 0.4.0"])
72
+ s.add_dependency(%q<rdoc>, [">= 0"])
73
+ end
74
+ end
75
+
@@ -0,0 +1,206 @@
1
+
2
+ require 'net/http'
3
+ require 'net/https'
4
+
5
+ class HttpSession
6
+
7
+ # it really sux that plain ruby doesn't have cattr_accessor so we have to do 30 lines of repeating ourselves!
8
+ def self.open_timeout=(v)
9
+ @@open_timeout = v
10
+ end
11
+ def self.read_timeout=(v)
12
+ @@read_timeout = v
13
+ end
14
+ def self.redirect_limit=(v)
15
+ @@redirect_limit = v
16
+ end
17
+ def self.retry_limit=(v)
18
+ @@retry_limit = v
19
+ end
20
+ def self.ssl_timeout=(v)
21
+ @@ssl_timeout = v
22
+ end
23
+ def self.ssl_verify_mode=(v)
24
+ @@ssl_verify_mode = v
25
+ end
26
+ def self.ssl_ca_file=(v)
27
+ @@ssl_ca_file = v
28
+ end
29
+ def self.ssl_verify_depth=(v)
30
+ @@ssl_verify_depth = v
31
+ end
32
+
33
+ # timeout for opening tcp/ip connection, in seconds
34
+ self.open_timeout = 4
35
+ # timeout for reading bytes from tcp/ip connection, in seconds
36
+ self.read_timeout = 8
37
+ # number of redirects to follow before throwing an error
38
+ self.redirect_limit = 10
39
+ # number of times to retry, on connection errors
40
+ self.retry_limit = 1
41
+ # ssl timeout
42
+ self.ssl_timeout = 2
43
+ # kind of ssl certificate verification (should be OpenSSL::SSL::VERIFY_NONE or OpenSSL::SSL::VERIFY_PEER)
44
+ self.ssl_verify_mode = OpenSSL::SSL::VERIFY_PEER
45
+ # when ssl certificate verification is used, where is the certificate authority file
46
+ # you can get one from curl here: http://curl.haxx.se/ca/cacert.pem
47
+ self.ssl_ca_file = ::File.expand_path('../../share/ca/cacert.pem', __FILE__)
48
+ # ssl verify depth
49
+ self.ssl_verify_depth = 5
50
+
51
+
52
+ #
53
+ # Request handling
54
+ #
55
+
56
+ # shortcut for parsing a full url and performing simple GET requests
57
+ # returns Net::HTTPResponse, or raises Timeout::Error, SystemCallError, OpenSSL::SSL::SSLError, EOFError, Net::ProtocolError
58
+ def self.get_request_url(url, headers={})
59
+ parsed = URI.parse(url)
60
+ use(parsed.host, parsed.scheme == 'https', parsed.port).request(parsed.path + (parsed.query.nil? ? '' : "?#{parsed.query}"), headers)
61
+ end
62
+
63
+ # shortcut for parsing a full url and performing simple POST requests
64
+ # returns Net::HTTPResponse, or raises Timeout::Error, SystemCallError, OpenSSL::SSL::SSLError, EOFError, Net::ProtocolError
65
+ def self.post_request_url(url, params, headers={})
66
+ parsed = URI.parse(url)
67
+ use(parsed.host, parsed.scheme == 'https', parsed.port).request(parsed.path + (parsed.query.nil? ? '' : "?#{parsed.query}"), headers, :post, params)
68
+ end
69
+
70
+ # internally handle GET and POST requests (recursively for redirects and retries)
71
+ # returns Net::HTTPResponse, or raises Timeout::Error, SystemCallError, OpenSSL::SSL::SSLError, EOFError, Net::ProtocolError
72
+ def request(uri='/', headers={}, type=:get, post_params={}, redirect_limit=@@redirect_limit, retry_limit=@@retry_limit)
73
+ req = case type
74
+ when :get
75
+ Net::HTTP::Get.new(uri)
76
+ when :post
77
+ Net::HTTP::Post.new(uri)
78
+ else
79
+ raise ArgumentError, "bad type: #{type}"
80
+ end
81
+ headers.each { |k, v| req[k] = v } unless headers.empty?
82
+ req['Cookie'] = cookie_string if cookies?
83
+ req.set_form_data(post_params) if type == :post
84
+
85
+ begin
86
+ handle.start unless handle.started? # may raise Timeout::Error or OpenSSL::SSL::SSLError
87
+ response = handle.request(req) # may raise Errno::* (subclasses of SystemCallError) or EOFError
88
+ rescue Timeout::Error, SystemCallError, EOFError
89
+ handle.finish if handle.started?
90
+ raise if retry_limit == 0
91
+ request(uri, headers, type, post_params, redirect_limit, retry_limit - 1)
92
+
93
+ else
94
+ add_cookies response
95
+ if response.kind_of?(Net::HTTPRedirection)
96
+ raise Net::HTTPError.new('Redirection limit exceeded', response) if redirect_limit == 0
97
+ loc = URI.parse(response['location'])
98
+ if loc.scheme && loc.host && loc.port
99
+ self.class.use(loc.host, loc.scheme == 'https', loc.port).request(loc.path + (loc.query.nil? ? '' : "?#{loc.query}"), headers, :get, {}, redirect_limit - 1)
100
+ else
101
+ # only really bad web servers would ever send this... since it's not technically a valid value!
102
+ request(loc.path + (loc.query.nil? ? '' : "?#{loc.query}"), headers, :get, {}, redirect_limit - 1)
103
+ end
104
+ else
105
+ response.error! unless response.kind_of?(Net::HTTPOK) # raises Net::HTTP*Error/Exception (subclasses of Net::ProtocolError)
106
+ raise Net::HTTPError.new('Document has no body', response) if response.body.nil? || response.body == ''
107
+ response
108
+ end
109
+ end
110
+ end
111
+
112
+
113
+ #
114
+ # Session initialization and basic handling
115
+ #
116
+
117
+ # place to store references to all currently-known session instances, for singleton method usage
118
+ @@sessions = {}
119
+
120
+ # storage for open session handle, for instance method usage
121
+ attr_accessor :handle
122
+
123
+ # don't use new() directly, use singleton get() or use() instead
124
+ def initialize(host, use_ssl, port)
125
+ self.handle = Net::HTTP.new(host, port)
126
+ self.handle.open_timeout = @@open_timeout # seems to have no effect?
127
+ self.handle.read_timeout = @@read_timeout # seems to have an effect on establishing tcp connection??
128
+ self.handle.close_on_empty_response = true # seems to have no effect?
129
+ if use_ssl
130
+ self.handle.use_ssl = true
131
+ # respond_to? check is a ruby-1.9.1-p378 (+/-?) bug workaround
132
+ self.handle.ssl_timeout = @@ssl_timeout if OpenSSL::SSL::SSLContext.new.respond_to?(:ssl_timeout=)
133
+ self.handle.verify_mode = @@ssl_verify_mode
134
+ self.handle.ca_file = @@ssl_ca_file
135
+ self.handle.verify_depth = @@ssl_verify_depth
136
+ end
137
+ self.cookies = {}
138
+ end
139
+
140
+ # just our own internal session key... (it looks like: "scheme://host:port")
141
+ def self.key(host, use_ssl, port)
142
+ "#{use_ssl ? 'https' : 'http'}://#{host}:#{port_or_default(port, use_ssl)}"
143
+ end
144
+
145
+ # check if a session exists yet ot not
146
+ def self.exists?(host, use_ssl=false, port=nil)
147
+ @@sessions.has_key?(key(host, use_ssl, port))
148
+ end
149
+
150
+ # get the session for the given host and port, nil if there isn't one yet
151
+ def self.get(host, use_ssl=false, port=nil)
152
+ @@sessions[key(host, use_ssl, port)]
153
+ end
154
+
155
+ # get the session for the given host and port, creating a new one if it doesn't exist
156
+ def self.use(host, use_ssl=false, port=nil)
157
+ key = key(host, use_ssl, port)
158
+ @@sessions.has_key?(key) ? @@sessions[key] : (@@sessions[key] = new(host, use_ssl, port_or_default(port, use_ssl)))
159
+ end
160
+
161
+ # done with this session, close and reset it
162
+ # but it still exists in the session storage in a dormant/empty state, so next request would easily reopen it
163
+ def close
164
+ handle.finish if handle.started?
165
+ self.cookies = {}
166
+ end
167
+
168
+ # delete session from session storage (you should probably call close on it too, and set all references to nil so it gets garbage collected)
169
+ def delete
170
+ key = self.class.key(handle.address, handle.use_ssl?, handle.port)
171
+ @@sessions.delete(key) if @@sessions.has_key?(key)
172
+ end
173
+
174
+ # return the given port, or defaults for ssl setting if it's nil
175
+ def self.port_or_default(port, use_ssl)
176
+ port.nil? ? (use_ssl ? Net::HTTP.https_default_port : Net::HTTP.http_default_port) : port
177
+ end
178
+
179
+
180
+ #
181
+ # Cookie handling
182
+ #
183
+
184
+ # storage for cookies, for instance method usage
185
+ attr_accessor :cookies
186
+
187
+ # check if a session has any cookies or not
188
+ def cookies?
189
+ cookies.length > 0
190
+ end
191
+
192
+ # return all current cookies in a comma-delimited name=value string format
193
+ def cookie_string
194
+ cookies.collect { |name, val| "#{name}=#{val}" }.join(', ')
195
+ end
196
+
197
+ # store the cookies from the given response into the session (ignores all host/path/expires/secure/etc cookie options!)
198
+ def add_cookies(response)
199
+ return unless response.key?('set-cookie')
200
+ response.get_fields('set-cookie').each do |cookie|
201
+ (key, val) = cookie.split('; ')[0].split('=', 2)
202
+ cookies[key] = val
203
+ end
204
+ end
205
+
206
+ end