http_session 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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