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