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 +5 -0
- data/.rvmrc +1 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +27 -0
- data/LICENSE +19 -0
- data/README.rdoc +72 -0
- data/Rakefile +148 -0
- data/VERSION +1 -0
- data/http_session.gemspec +75 -0
- data/lib/http_session.rb +206 -0
- data/share/ca/cacert.pem +3987 -0
- data/test/helper.rb +84 -0
- data/test/ssl/README.rdoc +32 -0
- data/test/ssl/ca.crt +34 -0
- data/test/ssl/ca.key +54 -0
- data/test/ssl/server.crt +31 -0
- data/test/ssl/server.csr +27 -0
- data/test/ssl/server.key +51 -0
- data/test/test_http_session.rb +292 -0
- metadata +149 -0
data/.document
ADDED
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
|
+
|
data/lib/http_session.rb
ADDED
@@ -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
|