http2 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +39 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/http2.gemspec +69 -0
- data/include/errors.rb +8 -0
- data/include/response.rb +67 -0
- data/include/utils.rb +40 -0
- data/lib/http2.rb +609 -0
- data/spec/http2_spec.rb +93 -0
- data/spec/spec_helper.rb +12 -0
- metadata +139 -0
data/.document
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
gem "knjrbfw"
|
7
|
+
|
8
|
+
# Add dependencies to develop your gem here.
|
9
|
+
# Include everything needed to run rake, tests, features, etc.
|
10
|
+
group :development do
|
11
|
+
gem "rspec", "~> 2.8.0"
|
12
|
+
gem "rdoc", "~> 3.12"
|
13
|
+
gem "bundler", ">= 1.0.0"
|
14
|
+
gem "jeweler", "~> 1.8.4"
|
15
|
+
gem "rcov", ">= 0"
|
16
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
diff-lcs (1.1.3)
|
5
|
+
git (1.2.5)
|
6
|
+
jeweler (1.8.4)
|
7
|
+
bundler (~> 1.0)
|
8
|
+
git (>= 1.2.5)
|
9
|
+
rake
|
10
|
+
rdoc
|
11
|
+
json (1.7.3)
|
12
|
+
knjrbfw (0.0.55)
|
13
|
+
tsafe
|
14
|
+
wref
|
15
|
+
rake (0.9.2.2)
|
16
|
+
rcov (0.9.11)
|
17
|
+
rdoc (3.12)
|
18
|
+
json (~> 1.4)
|
19
|
+
rspec (2.8.0)
|
20
|
+
rspec-core (~> 2.8.0)
|
21
|
+
rspec-expectations (~> 2.8.0)
|
22
|
+
rspec-mocks (~> 2.8.0)
|
23
|
+
rspec-core (2.8.0)
|
24
|
+
rspec-expectations (2.8.0)
|
25
|
+
diff-lcs (~> 1.1.2)
|
26
|
+
rspec-mocks (2.8.0)
|
27
|
+
tsafe (0.0.1)
|
28
|
+
wref (0.0.4)
|
29
|
+
|
30
|
+
PLATFORMS
|
31
|
+
ruby
|
32
|
+
|
33
|
+
DEPENDENCIES
|
34
|
+
bundler (>= 1.0.0)
|
35
|
+
jeweler (~> 1.8.4)
|
36
|
+
knjrbfw
|
37
|
+
rcov
|
38
|
+
rdoc (~> 3.12)
|
39
|
+
rspec (~> 2.8.0)
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Kasper Johansen
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
= http2
|
2
|
+
|
3
|
+
Description goes here.
|
4
|
+
|
5
|
+
== Contributing to http2
|
6
|
+
|
7
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
8
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
9
|
+
* Fork the project.
|
10
|
+
* Start a feature/bugfix branch.
|
11
|
+
* Commit and push until you are happy with your contribution.
|
12
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
13
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
14
|
+
|
15
|
+
== Copyright
|
16
|
+
|
17
|
+
Copyright (c) 2012 Kasper Johansen. See LICENSE.txt for
|
18
|
+
further details.
|
19
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "http2"
|
18
|
+
gem.homepage = "http://github.com/kaspernj/http2"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{A lightweight framework for doing http-connections in Ruby. Supports cookies, keep-alive, compressing and much more.}
|
21
|
+
gem.description = %Q{A lightweight framework for doing http-connections in Ruby. Supports cookies, keep-alive, compressing and much more.}
|
22
|
+
gem.email = "k@spernj.org"
|
23
|
+
gem.authors = ["Kasper Johansen"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rspec/core'
|
29
|
+
require 'rspec/core/rake_task'
|
30
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
31
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
32
|
+
end
|
33
|
+
|
34
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
35
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
36
|
+
spec.rcov = true
|
37
|
+
end
|
38
|
+
|
39
|
+
task :default => :spec
|
40
|
+
|
41
|
+
require 'rdoc/task'
|
42
|
+
Rake::RDocTask.new do |rdoc|
|
43
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
44
|
+
|
45
|
+
rdoc.rdoc_dir = 'rdoc'
|
46
|
+
rdoc.title = "http2 #{version}"
|
47
|
+
rdoc.rdoc_files.include('README*')
|
48
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
49
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.0
|
data/http2.gemspec
ADDED
@@ -0,0 +1,69 @@
|
|
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{http2}
|
8
|
+
s.version = "0.0.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Kasper Johansen"]
|
12
|
+
s.date = %q{2012-07-12}
|
13
|
+
s.description = %q{A lightweight framework for doing http-connections in Ruby. Supports cookies, keep-alive, compressing and much more.}
|
14
|
+
s.email = %q{k@spernj.org}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".rspec",
|
22
|
+
"Gemfile",
|
23
|
+
"Gemfile.lock",
|
24
|
+
"LICENSE.txt",
|
25
|
+
"README.rdoc",
|
26
|
+
"Rakefile",
|
27
|
+
"VERSION",
|
28
|
+
"http2.gemspec",
|
29
|
+
"include/errors.rb",
|
30
|
+
"include/response.rb",
|
31
|
+
"include/utils.rb",
|
32
|
+
"lib/http2.rb",
|
33
|
+
"spec/http2_spec.rb",
|
34
|
+
"spec/spec_helper.rb"
|
35
|
+
]
|
36
|
+
s.homepage = %q{http://github.com/kaspernj/http2}
|
37
|
+
s.licenses = ["MIT"]
|
38
|
+
s.require_paths = ["lib"]
|
39
|
+
s.rubygems_version = %q{1.6.2}
|
40
|
+
s.summary = %q{A lightweight framework for doing http-connections in Ruby. Supports cookies, keep-alive, compressing and much more.}
|
41
|
+
|
42
|
+
if s.respond_to? :specification_version then
|
43
|
+
s.specification_version = 3
|
44
|
+
|
45
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
46
|
+
s.add_runtime_dependency(%q<knjrbfw>, [">= 0"])
|
47
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.8.0"])
|
48
|
+
s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
|
49
|
+
s.add_development_dependency(%q<bundler>, [">= 1.0.0"])
|
50
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.8.4"])
|
51
|
+
s.add_development_dependency(%q<rcov>, [">= 0"])
|
52
|
+
else
|
53
|
+
s.add_dependency(%q<knjrbfw>, [">= 0"])
|
54
|
+
s.add_dependency(%q<rspec>, ["~> 2.8.0"])
|
55
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
56
|
+
s.add_dependency(%q<bundler>, [">= 1.0.0"])
|
57
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
|
58
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
59
|
+
end
|
60
|
+
else
|
61
|
+
s.add_dependency(%q<knjrbfw>, [">= 0"])
|
62
|
+
s.add_dependency(%q<rspec>, ["~> 2.8.0"])
|
63
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
64
|
+
s.add_dependency(%q<bundler>, [">= 1.0.0"])
|
65
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
|
66
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
data/include/errors.rb
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
#This class holds various classes for error-handeling.
|
2
|
+
class Http2::Errors
|
3
|
+
#Raised when trying to access something you dont have access to.
|
4
|
+
class Noaccess < RuntimeError; end
|
5
|
+
|
6
|
+
#Raised when an internal error occurs on the servers side.
|
7
|
+
class Internalserver < RuntimeError; end
|
8
|
+
end
|
data/include/response.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
#This object will be returned as the response for each request.
|
2
|
+
class Http2::Response
|
3
|
+
attr_reader :args
|
4
|
+
|
5
|
+
#This method should not be called manually.
|
6
|
+
def initialize(args = {})
|
7
|
+
@args = args
|
8
|
+
@args[:headers] = {} if !@args.key?(:headers)
|
9
|
+
@args[:body] = "" if !@args.key?(:body)
|
10
|
+
end
|
11
|
+
|
12
|
+
#Returns headers given from the host for the result.
|
13
|
+
#===Examples
|
14
|
+
# headers_hash = res.headers
|
15
|
+
def headers
|
16
|
+
return @args[:headers]
|
17
|
+
end
|
18
|
+
|
19
|
+
#Returns a certain header by name or false if not found.
|
20
|
+
#===Examples
|
21
|
+
# val = res.header("content-type")
|
22
|
+
def header(key)
|
23
|
+
return false if !@args[:headers].key?(key)
|
24
|
+
return @args[:headers][key].first.to_s
|
25
|
+
end
|
26
|
+
|
27
|
+
#Returns true if a header of the given string exists.
|
28
|
+
#===Examples
|
29
|
+
# print "No content-type was given." if !http.header?("content-type")
|
30
|
+
def header?(key)
|
31
|
+
return true if @args[:headers].key?(key) and @args[:headers][key].first.to_s.length > 0
|
32
|
+
return false
|
33
|
+
end
|
34
|
+
|
35
|
+
#Returns the code of the result (200, 404, 500 etc).
|
36
|
+
#===Examples
|
37
|
+
# print "An internal error occurred." if res.code.to_i == 500
|
38
|
+
def code
|
39
|
+
return @args[:code]
|
40
|
+
end
|
41
|
+
|
42
|
+
#Returns the HTTP-version of the result.
|
43
|
+
#===Examples
|
44
|
+
# print "We are using HTTP 1.1 and should support keep-alive." if res.http_version.to_s == "1.1"
|
45
|
+
def http_version
|
46
|
+
return @args[:http_version]
|
47
|
+
end
|
48
|
+
|
49
|
+
#Returns the complete body of the result as a string.
|
50
|
+
#===Examples
|
51
|
+
# print "Looks like we caught the end of it as well?" if res.body.to_s.downcase.index("</html>") != nil
|
52
|
+
def body
|
53
|
+
return @args[:body]
|
54
|
+
end
|
55
|
+
|
56
|
+
#Returns the charset of the result.
|
57
|
+
def charset
|
58
|
+
return @args[:charset]
|
59
|
+
end
|
60
|
+
|
61
|
+
#Returns the content-type of the result as a string.
|
62
|
+
#===Examples
|
63
|
+
# print "This body can be printed - its just plain text!" if http.contenttype == "text/plain"
|
64
|
+
def contenttype
|
65
|
+
return @args[:contenttype]
|
66
|
+
end
|
67
|
+
end
|
data/include/utils.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
#This class holds various methods for encoding, decoding and parsing of HTTP-related stuff.
|
2
|
+
class Http2::Utils
|
3
|
+
#URL-encodes a string.
|
4
|
+
def self.urlenc(string)
|
5
|
+
#Thanks to CGI framework
|
6
|
+
string.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/) do
|
7
|
+
'%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
|
8
|
+
end.tr(' ', '+')
|
9
|
+
end
|
10
|
+
|
11
|
+
#URL-decodes a string.
|
12
|
+
def self.urldec(string)
|
13
|
+
#Thanks to CGI framework
|
14
|
+
str = string.to_s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/) do
|
15
|
+
[$1.delete('%')].pack('H*')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
#Parses a cookies-string and returns them in an array.
|
20
|
+
def self.parse_set_cookies(str)
|
21
|
+
str = String.new(str.to_s)
|
22
|
+
return [] if str.length <= 0
|
23
|
+
args = {}
|
24
|
+
cookie_start_regex = /^(.+?)=(.*?)(;\s*|$)/
|
25
|
+
|
26
|
+
match = str.match(cookie_start_regex)
|
27
|
+
raise "Could not match cookie: '#{str}'." if !match
|
28
|
+
str.gsub!(cookie_start_regex, "")
|
29
|
+
|
30
|
+
args["name"] = self.urldec(match[1].to_s)
|
31
|
+
args["value"] = self.urldec(match[2].to_s)
|
32
|
+
|
33
|
+
while match = str.match(/(.+?)=(.*?)(;\s*|$)/)
|
34
|
+
str = str.gsub(match[0], "")
|
35
|
+
args[match[1].to_s.downcase] = match[2].to_s
|
36
|
+
end
|
37
|
+
|
38
|
+
return [args]
|
39
|
+
end
|
40
|
+
end
|
data/lib/http2.rb
ADDED
@@ -0,0 +1,609 @@
|
|
1
|
+
#This class tries to emulate a browser in Ruby without any visual stuff. Remember cookies, keep sessions alive, reset connections according to keep-alive rules and more.
|
2
|
+
#===Examples
|
3
|
+
# Http2.new(:host => "www.somedomain.com", :port => 80, :ssl => false, :debug => false) do |http|
|
4
|
+
# res = http.get("index.rhtml?show=some_page")
|
5
|
+
# html = res.body
|
6
|
+
# print html
|
7
|
+
#
|
8
|
+
# res = res.post("index.rhtml?choice=login", {"username" => "John Doe", "password" => 123})
|
9
|
+
# print res.body
|
10
|
+
# print "#{res.headers}"
|
11
|
+
# end
|
12
|
+
class Http2
|
13
|
+
#Autoloader for subclasses.
|
14
|
+
def self.const_missing(name)
|
15
|
+
require "#{File.dirname(__FILE__)}/../include/#{name.to_s.downcase}.rb"
|
16
|
+
return Http2.const_get(name)
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :cookies, :args
|
20
|
+
|
21
|
+
def initialize(args = {})
|
22
|
+
args = {:host => args} if args.is_a?(String)
|
23
|
+
raise "Arguments wasnt a hash." if !args.is_a?(Hash)
|
24
|
+
|
25
|
+
@args = args
|
26
|
+
@cookies = {}
|
27
|
+
@debug = @args[:debug]
|
28
|
+
|
29
|
+
require "monitor"
|
30
|
+
@mutex = Monitor.new
|
31
|
+
|
32
|
+
if !@args[:port]
|
33
|
+
if @args[:ssl]
|
34
|
+
@args[:port] = 443
|
35
|
+
else
|
36
|
+
@args[:port] = 80
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
if @args[:nl]
|
41
|
+
@nl = @args[:nl]
|
42
|
+
else
|
43
|
+
@nl = "\r\n"
|
44
|
+
end
|
45
|
+
|
46
|
+
if @args[:user_agent]
|
47
|
+
@uagent = @args[:user_agent]
|
48
|
+
else
|
49
|
+
@uagent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"
|
50
|
+
end
|
51
|
+
|
52
|
+
raise "No host was given." if !@args[:host]
|
53
|
+
self.reconnect
|
54
|
+
|
55
|
+
if block_given?
|
56
|
+
begin
|
57
|
+
yield(self)
|
58
|
+
ensure
|
59
|
+
self.destroy
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
#Returns boolean based on the if the object is connected and the socket is working.
|
65
|
+
#===Examples
|
66
|
+
# print "Socket is working." if http.socket_working?
|
67
|
+
def socket_working?
|
68
|
+
return false if !@sock or @sock.closed?
|
69
|
+
|
70
|
+
if @keepalive_timeout and @request_last
|
71
|
+
between = Time.now.to_i - @request_last.to_i
|
72
|
+
if between >= @keepalive_timeout
|
73
|
+
print "Http2: We are over the keepalive-wait - returning false for socket_working?.\n" if @debug
|
74
|
+
return false
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
return true
|
79
|
+
end
|
80
|
+
|
81
|
+
#Destroys the object unsetting all variables and closing all sockets.
|
82
|
+
#===Examples
|
83
|
+
# http.destroy
|
84
|
+
def destroy
|
85
|
+
@args = nil
|
86
|
+
@cookies = nil
|
87
|
+
@debug = nil
|
88
|
+
@mutex = nil
|
89
|
+
@uagent = nil
|
90
|
+
@keepalive_timeout = nil
|
91
|
+
@request_last = nil
|
92
|
+
|
93
|
+
@sock.close if @sock and !@sock.closed?
|
94
|
+
@sock = nil
|
95
|
+
|
96
|
+
@sock_plain.close if @sock_plain and !@sock_plain.closed?
|
97
|
+
@sock_plain = nil
|
98
|
+
|
99
|
+
@sock_ssl.close if @sock_ssl and !@sock_ssl.closed?
|
100
|
+
@sock_ssl = nil
|
101
|
+
end
|
102
|
+
|
103
|
+
#Reconnects to the host.
|
104
|
+
def reconnect
|
105
|
+
require "socket"
|
106
|
+
print "Http2: Reconnect.\n" if @debug
|
107
|
+
|
108
|
+
#Reset variables.
|
109
|
+
@keepalive_max = nil
|
110
|
+
@keepalive_timeout = nil
|
111
|
+
@connection = nil
|
112
|
+
@contenttype = nil
|
113
|
+
@charset = nil
|
114
|
+
|
115
|
+
#Open connection.
|
116
|
+
if @args[:proxy]
|
117
|
+
print "Http2: Initializing proxy stuff.\n" if @debug
|
118
|
+
@sock_plain = TCPSocket.new(@args[:proxy][:host], @args[:proxy][:port])
|
119
|
+
@sock = @sock_plain
|
120
|
+
|
121
|
+
@sock.write("CONNECT #{@args[:host]}:#{@args[:port]} HTTP/1.0#{@nl}")
|
122
|
+
@sock.write("User-Agent: #{@uagent}#{@nl}")
|
123
|
+
|
124
|
+
if @args[:proxy][:user] and @args[:proxy][:passwd]
|
125
|
+
credential = ["#{@args[:proxy][:user]}:#{@args[:proxy][:passwd]}"].pack("m")
|
126
|
+
credential.delete!("\r\n")
|
127
|
+
@sock.write("Proxy-Authorization: Basic #{credential}#{@nl}")
|
128
|
+
end
|
129
|
+
|
130
|
+
@sock.write(@nl)
|
131
|
+
|
132
|
+
res = @sock.gets
|
133
|
+
raise res if res.to_s.downcase != "http/1.0 200 connection established#{@nl}"
|
134
|
+
|
135
|
+
res_empty = @sock.gets
|
136
|
+
raise "Empty res wasnt empty." if res_empty != @nl
|
137
|
+
else
|
138
|
+
print "Http2: Opening socket connection to '#{@args[:host]}:#{@args[:port]}'.\n" if @debug
|
139
|
+
@sock_plain = TCPSocket.new(@args[:host], @args[:port].to_i)
|
140
|
+
end
|
141
|
+
|
142
|
+
if @args[:ssl]
|
143
|
+
print "Http2: Initializing SSL.\n" if @debug
|
144
|
+
require "openssl"
|
145
|
+
|
146
|
+
ssl_context = OpenSSL::SSL::SSLContext.new
|
147
|
+
#ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
148
|
+
|
149
|
+
@sock_ssl = OpenSSL::SSL::SSLSocket.new(@sock_plain, ssl_context)
|
150
|
+
@sock_ssl.sync_close = true
|
151
|
+
@sock_ssl.connect
|
152
|
+
|
153
|
+
@sock = @sock_ssl
|
154
|
+
else
|
155
|
+
@sock = @sock_plain
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
#Forces various stuff into arguments like URL.
|
160
|
+
def parse_args(*args)
|
161
|
+
if args.length == 1 and args.first.is_a?(String)
|
162
|
+
return {:url => args.first}
|
163
|
+
end
|
164
|
+
|
165
|
+
return args[0]
|
166
|
+
end
|
167
|
+
|
168
|
+
#Returns a result-object based on the arguments.
|
169
|
+
#===Examples
|
170
|
+
# res = http.get("somepage.html")
|
171
|
+
# print res.body #=> <String>-object containing the HTML gotten.
|
172
|
+
def get(args)
|
173
|
+
args = parse_args(args)
|
174
|
+
|
175
|
+
header_str = "GET /#{args[:url]} HTTP/1.1#{@nl}"
|
176
|
+
header_str << self.header_str(self.default_headers(args), args)
|
177
|
+
header_str << "#{@nl}"
|
178
|
+
|
179
|
+
@mutex.synchronize do
|
180
|
+
print "Http2: Writing headers.\n" if @debug
|
181
|
+
print "Header str: #{header_str}\n" if @debug
|
182
|
+
self.write(header_str)
|
183
|
+
|
184
|
+
print "Http2: Reading response.\n" if @debug
|
185
|
+
resp = self.read_response(args)
|
186
|
+
|
187
|
+
print "Http2: Done with get request.\n" if @debug
|
188
|
+
return resp
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
#Tries to write a string to the socket. If it fails it reconnects and tries again.
|
193
|
+
def write(str)
|
194
|
+
#Reset variables.
|
195
|
+
@length = nil
|
196
|
+
@encoding = nil
|
197
|
+
self.reconnect if !self.socket_working?
|
198
|
+
|
199
|
+
begin
|
200
|
+
raise Errno::EPIPE, "The socket is closed." if !@sock or @sock.closed?
|
201
|
+
@sock.puts(str)
|
202
|
+
rescue Errno::EPIPE #this can also be thrown by puts.
|
203
|
+
self.reconnect
|
204
|
+
@sock.puts(str)
|
205
|
+
end
|
206
|
+
|
207
|
+
@request_last = Time.now
|
208
|
+
end
|
209
|
+
|
210
|
+
#Returns the default headers for a request.
|
211
|
+
#===Examples
|
212
|
+
# headers_hash = http.default_headers
|
213
|
+
# print "#{headers_hash}"
|
214
|
+
def default_headers(args = {})
|
215
|
+
return args[:default_headers] if args[:default_headers]
|
216
|
+
|
217
|
+
headers = {
|
218
|
+
"Host" => @args[:host],
|
219
|
+
"Connection" => "Keep-Alive",
|
220
|
+
"User-Agent" => @uagent
|
221
|
+
}
|
222
|
+
|
223
|
+
if !@args.key?(:encoding_gzip) or @args[:encoding_gzip]
|
224
|
+
headers["Accept-Encoding"] = "gzip"
|
225
|
+
else
|
226
|
+
#headers["Accept-Encoding"] = "none"
|
227
|
+
end
|
228
|
+
|
229
|
+
if @args[:basic_auth]
|
230
|
+
headers["Authorization"] = "Basic #{Base64.encode64("#{@args[:basic_auth][:user]}:#{@args[:basic_auth][:passwd]}")}"
|
231
|
+
end
|
232
|
+
|
233
|
+
return headers
|
234
|
+
end
|
235
|
+
|
236
|
+
#This is used to convert a hash to valid post-data recursivly.
|
237
|
+
def self.post_convert_data(pdata, args = nil)
|
238
|
+
praw = ""
|
239
|
+
|
240
|
+
if pdata.is_a?(Hash)
|
241
|
+
pdata.each do |key, val|
|
242
|
+
praw << "&" if praw != ""
|
243
|
+
|
244
|
+
if args and args[:orig_key]
|
245
|
+
key = "#{args[:orig_key]}[#{key}]"
|
246
|
+
end
|
247
|
+
|
248
|
+
if val.is_a?(Hash) or val.is_a?(Array)
|
249
|
+
praw << self.post_convert_data(val, {:orig_key => key})
|
250
|
+
else
|
251
|
+
praw << "#{Http2::Utils.urlenc(key)}=#{Http2::Utils.urlenc(Http2.post_convert_data(val))}"
|
252
|
+
end
|
253
|
+
end
|
254
|
+
elsif pdata.is_a?(Array)
|
255
|
+
count = 0
|
256
|
+
pdata.each do |val|
|
257
|
+
if args and args[:orig_key]
|
258
|
+
key = "#{args[:orig_key]}[#{count}]"
|
259
|
+
else
|
260
|
+
key = count
|
261
|
+
end
|
262
|
+
|
263
|
+
if val.is_a?(Hash) or val.is_a?(Array)
|
264
|
+
praw << self.post_convert_data(val, {:orig_key => key})
|
265
|
+
else
|
266
|
+
praw << "#{Http2::Utils.urlenc(key)}=#{Http2::Utils.urlenc(Http2.post_convert_data(val))}"
|
267
|
+
end
|
268
|
+
|
269
|
+
count += 1
|
270
|
+
end
|
271
|
+
else
|
272
|
+
return pdata.to_s
|
273
|
+
end
|
274
|
+
|
275
|
+
return praw
|
276
|
+
end
|
277
|
+
|
278
|
+
#Posts to a certain page.
|
279
|
+
#===Examples
|
280
|
+
# res = http.post("login.php", {"username" => "John Doe", "password" => 123)
|
281
|
+
def post(args)
|
282
|
+
args = self.parse_args(args)
|
283
|
+
|
284
|
+
@mutex.synchronize do
|
285
|
+
print "Doing post.\n" if @debug
|
286
|
+
|
287
|
+
praw = Http2.post_convert_data(args[:post])
|
288
|
+
|
289
|
+
header_str = "POST /#{args[:url]} HTTP/1.1#{@nl}"
|
290
|
+
header_str << self.header_str(self.default_headers(args).merge("Content-Type" => "application/x-www-form-urlencoded", "Content-Length" => praw.length), args)
|
291
|
+
header_str << "#{@nl}"
|
292
|
+
header_str << praw
|
293
|
+
|
294
|
+
print "Header str: #{header_str}\n" if @debug
|
295
|
+
|
296
|
+
self.write(header_str)
|
297
|
+
return self.read_response(args)
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
#Posts to a certain page using the multipart-method.
|
302
|
+
#===Examples
|
303
|
+
# res = http.post_multipart("upload.php", {"normal_value" => 123, "file" => Tempfile.new(?)})
|
304
|
+
def post_multipart(*args)
|
305
|
+
args = self.parse_args(*args)
|
306
|
+
|
307
|
+
#Generate random string.
|
308
|
+
boundary = rand(36**50).to_s(36)
|
309
|
+
|
310
|
+
#Use tempfile to store contents to avoid eating memory if posting something really big.
|
311
|
+
require "tempfile"
|
312
|
+
|
313
|
+
praw = Tempfile.open("http2_post_multipart_tmp_#{boundary}")
|
314
|
+
args[:post].each do |key, val|
|
315
|
+
praw << "--#{boundary}#{@nl}"
|
316
|
+
|
317
|
+
if val.class.name == "Tempfile" and val.respond_to?("original_filename")
|
318
|
+
praw << "Content-Disposition: form-data; name=\"#{key}\"; filename=\"#{val.original_filename}\";#{@nl}"
|
319
|
+
praw << "Content-Length: #{val.to_s.bytesize}#{@nl}"
|
320
|
+
elsif val.is_a?(Hash) and val[:filename]
|
321
|
+
praw << "Content-Disposition: form-data; name=\"#{key}\"; filename=\"#{val[:filename]}\";#{@nl}"
|
322
|
+
|
323
|
+
if val[:content]
|
324
|
+
praw << "Content-Length: #{val[:content].to_s.bytesize}#{@nl}"
|
325
|
+
elsif val[:fpath]
|
326
|
+
praw << "Content-Length: #{File.size(val[:fpath])}#{@nl}"
|
327
|
+
else
|
328
|
+
raise "Could not figure out where to get content from."
|
329
|
+
end
|
330
|
+
else
|
331
|
+
praw << "Content-Disposition: form-data; name=\"#{key}\";#{@nl}"
|
332
|
+
praw << "Content-Length: #{val.to_s.bytesize}#{@nl}"
|
333
|
+
end
|
334
|
+
|
335
|
+
praw << "Content-Type: text/plain#{@nl}"
|
336
|
+
praw << @nl
|
337
|
+
|
338
|
+
if val.is_a?(StringIO)
|
339
|
+
praw << val.read
|
340
|
+
elsif val.is_a?(Hash) and val[:content]
|
341
|
+
praw << val[:content].to_s
|
342
|
+
elsif val.is_a?(Hash) and val[:fpath]
|
343
|
+
File.open(val[:fpath], "r") do |fp|
|
344
|
+
begin
|
345
|
+
while data = fp.sysread(4096)
|
346
|
+
praw << data
|
347
|
+
end
|
348
|
+
rescue EOFError
|
349
|
+
#ignore.
|
350
|
+
end
|
351
|
+
end
|
352
|
+
else
|
353
|
+
praw << val.to_s
|
354
|
+
end
|
355
|
+
|
356
|
+
praw << @nl
|
357
|
+
end
|
358
|
+
|
359
|
+
praw << "--#{boundary}--"
|
360
|
+
praw.close(false)
|
361
|
+
|
362
|
+
|
363
|
+
#Generate header-string containing 'praw'-variable.
|
364
|
+
header_str = "POST /#{args[:url]} HTTP/1.1#{@nl}"
|
365
|
+
header_str << self.header_str(self.default_headers(args).merge(
|
366
|
+
"Content-Type" => "multipart/form-data; boundary=#{boundary}",
|
367
|
+
"Content-Length" => praw.size
|
368
|
+
), args)
|
369
|
+
header_str << @nl
|
370
|
+
|
371
|
+
|
372
|
+
#Debug.
|
373
|
+
print "Headerstr: #{header_str}\n" if @debug
|
374
|
+
|
375
|
+
|
376
|
+
#Write and return.
|
377
|
+
@mutex.synchronize do
|
378
|
+
self.write(header_str)
|
379
|
+
File.open(praw.path, "r") do |fp|
|
380
|
+
begin
|
381
|
+
while data = fp.sysread(4096)
|
382
|
+
@sock.write(data)
|
383
|
+
end
|
384
|
+
rescue EOFError
|
385
|
+
#ignore.
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
return self.read_response(args)
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
#Returns a header-string which normally would be used for a request in the given state.
|
394
|
+
def header_str(headers_hash, args = {})
|
395
|
+
if @cookies.length > 0 and (!args.key?(:cookies) or args[:cookies])
|
396
|
+
cstr = ""
|
397
|
+
|
398
|
+
first = true
|
399
|
+
@cookies.each do |cookie_name, cookie_data|
|
400
|
+
cstr << "; " if !first
|
401
|
+
first = false if first
|
402
|
+
|
403
|
+
if cookie_data.is_a?(Hash)
|
404
|
+
cstr << "#{Http2::Utils.urlenc(cookie_data["name"])}=#{Http2::Utils.urlenc(cookie_data["value"])}"
|
405
|
+
else
|
406
|
+
cstr << "#{Http2::Utils.urlenc(cookie_name)}=#{Http2::Utils.urlenc(cookie_data)}"
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
headers_hash["Cookie"] = cstr
|
411
|
+
end
|
412
|
+
|
413
|
+
headers_str = ""
|
414
|
+
headers_hash.each do |key, val|
|
415
|
+
headers_str << "#{key}: #{val}#{@nl}"
|
416
|
+
end
|
417
|
+
|
418
|
+
return headers_str
|
419
|
+
end
|
420
|
+
|
421
|
+
def on_content_call(args, line)
|
422
|
+
args[:on_content].call(line) if args.key?(:on_content)
|
423
|
+
end
|
424
|
+
|
425
|
+
#Reads the response after posting headers and data.
|
426
|
+
#===Examples
|
427
|
+
# res = http.read_response
|
428
|
+
def read_response(args = {})
|
429
|
+
@mode = "headers"
|
430
|
+
@resp = Http2::Response.new
|
431
|
+
|
432
|
+
loop do
|
433
|
+
begin
|
434
|
+
if @length and @length > 0 and @mode == "body"
|
435
|
+
line = @sock.read(@length)
|
436
|
+
else
|
437
|
+
line = @sock.gets
|
438
|
+
end
|
439
|
+
|
440
|
+
print "<#{@mode}>: '#{line}'\n" if @debug
|
441
|
+
rescue Errno::ECONNRESET
|
442
|
+
print "Http2: The connection was reset while reading - breaking gently...\n" if @debug
|
443
|
+
@sock = nil
|
444
|
+
break
|
445
|
+
end
|
446
|
+
|
447
|
+
break if line.to_s == ""
|
448
|
+
|
449
|
+
if @mode == "headers" and line == @nl
|
450
|
+
print "Changing mode to body!\n" if @debug
|
451
|
+
break if @length == 0
|
452
|
+
@mode = "body"
|
453
|
+
next
|
454
|
+
end
|
455
|
+
|
456
|
+
if @mode == "headers"
|
457
|
+
self.parse_header(line, args)
|
458
|
+
elsif @mode == "body"
|
459
|
+
self.on_content_call(args, "\r\n")
|
460
|
+
stat = self.parse_body(line, args)
|
461
|
+
break if stat == "break"
|
462
|
+
next if stat == "next"
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
466
|
+
|
467
|
+
#Check if we should reconnect based on keep-alive-max.
|
468
|
+
if @keepalive_max == 1 or @connection == "close"
|
469
|
+
@sock.close if !@sock.closed?
|
470
|
+
@sock = nil
|
471
|
+
end
|
472
|
+
|
473
|
+
|
474
|
+
#Check if the content is gzip-encoded - if so: decode it!
|
475
|
+
if @encoding == "gzip"
|
476
|
+
require "zlib"
|
477
|
+
require "iconv"
|
478
|
+
io = StringIO.new(@resp.args[:body])
|
479
|
+
gz = Zlib::GzipReader.new(io)
|
480
|
+
untrusted_str = gz.read
|
481
|
+
ic = Iconv.new("UTF-8//IGNORE", "UTF-8")
|
482
|
+
valid_string = ic.iconv(untrusted_str + " ")[0..-2]
|
483
|
+
@resp.args[:body] = valid_string
|
484
|
+
end
|
485
|
+
|
486
|
+
|
487
|
+
#Release variables.
|
488
|
+
resp = @resp
|
489
|
+
@resp = nil
|
490
|
+
@mode = nil
|
491
|
+
|
492
|
+
raise "No status-code was received from the server.\n\nHeaders:\n#{resp.headers}\n\nBody:\n#{resp.args[:body]}" if !resp.args[:code]
|
493
|
+
|
494
|
+
if resp.args[:code].to_s == "302" and resp.header?("location") and (!@args.key?(:follow_redirects) or @args[:follow_redirects])
|
495
|
+
require "uri"
|
496
|
+
uri = URI.parse(resp.header("location"))
|
497
|
+
url = uri.path
|
498
|
+
url << "?#{uri.query}" if uri.query.to_s.length > 0
|
499
|
+
|
500
|
+
args = {:host => uri.host}
|
501
|
+
args[:ssl] = true if uri.scheme == "https"
|
502
|
+
args[:port] = uri.port if uri.port
|
503
|
+
|
504
|
+
print "Redirecting from location-header to '#{url}'.\n" if @debug
|
505
|
+
|
506
|
+
if !args[:host] or args[:host] == @args[:host]
|
507
|
+
return self.get(url)
|
508
|
+
else
|
509
|
+
http = Http2.new(args)
|
510
|
+
return http.get(url)
|
511
|
+
end
|
512
|
+
elsif resp.args[:code].to_s == "500"
|
513
|
+
raise Http2::Errors::Internalserver
|
514
|
+
elsif resp.args[:code].to_s == "403"
|
515
|
+
raise Http2::Errors::Noaccess
|
516
|
+
else
|
517
|
+
return resp
|
518
|
+
end
|
519
|
+
end
|
520
|
+
|
521
|
+
#Parse a header-line and saves it on the object.
|
522
|
+
#===Examples
|
523
|
+
# http.parse_header("Content-Type: text/html\r\n")
|
524
|
+
def parse_header(line, args = {})
|
525
|
+
if match = line.match(/^(.+?):\s*(.+)#{@nl}$/)
|
526
|
+
key = match[1].to_s.downcase
|
527
|
+
|
528
|
+
if key == "set-cookie"
|
529
|
+
Http2::Utils.parse_set_cookies(match[2]).each do |cookie_data|
|
530
|
+
@cookies[cookie_data["name"]] = cookie_data
|
531
|
+
end
|
532
|
+
elsif key == "keep-alive"
|
533
|
+
if ka_max = match[2].to_s.match(/max=(\d+)/)
|
534
|
+
@keepalive_max = ka_max[1].to_i
|
535
|
+
print "Http2: Keepalive-max set to: '#{@keepalive_max}'.\n" if @debug
|
536
|
+
end
|
537
|
+
|
538
|
+
if ka_timeout = match[2].to_s.match(/timeout=(\d+)/)
|
539
|
+
@keepalive_timeout = ka_timeout[1].to_i
|
540
|
+
print "Http2: Keepalive-timeout set to: '#{@keepalive_timeout}'.\n" if @debug
|
541
|
+
end
|
542
|
+
elsif key == "connection"
|
543
|
+
@connection = match[2].to_s.downcase
|
544
|
+
elsif key == "content-encoding"
|
545
|
+
@encoding = match[2].to_s.downcase
|
546
|
+
elsif key == "content-length"
|
547
|
+
@length = match[2].to_i
|
548
|
+
elsif key == "content-type"
|
549
|
+
ctype = match[2].to_s
|
550
|
+
if match_charset = ctype.match(/\s*;\s*charset=(.+)/i)
|
551
|
+
@charset = match_charset[1].downcase
|
552
|
+
@resp.args[:charset] = @charset
|
553
|
+
ctype.gsub!(match_charset[0], "")
|
554
|
+
end
|
555
|
+
|
556
|
+
@ctype = ctype
|
557
|
+
@resp.args[:contenttype] = @ctype
|
558
|
+
end
|
559
|
+
|
560
|
+
if key != "transfer-encoding" and key != "content-length" and key != "connection" and key != "keep-alive"
|
561
|
+
self.on_content_call(args, line)
|
562
|
+
end
|
563
|
+
|
564
|
+
@resp.headers[key] = [] if !@resp.headers.key?(key)
|
565
|
+
@resp.headers[key] << match[2]
|
566
|
+
elsif match = line.match(/^HTTP\/([\d\.]+)\s+(\d+)\s+(.+)$/)
|
567
|
+
@resp.args[:code] = match[2]
|
568
|
+
@resp.args[:http_version] = match[1]
|
569
|
+
else
|
570
|
+
raise "Could not understand header string: '#{line}'.\n\n#{@sock.read(409600)}"
|
571
|
+
end
|
572
|
+
end
|
573
|
+
|
574
|
+
#Parses the body based on given headers and saves it to the result-object.
|
575
|
+
# http.parse_body(str)
|
576
|
+
def parse_body(line, args)
|
577
|
+
if @resp.args[:http_version] = "1.1"
|
578
|
+
return "break" if @length == 0
|
579
|
+
|
580
|
+
if @resp.header("transfer-encoding").to_s.downcase == "chunked"
|
581
|
+
len = line.strip.hex
|
582
|
+
|
583
|
+
if len > 0
|
584
|
+
read = @sock.read(len)
|
585
|
+
return "break" if read == "" or read == @nl
|
586
|
+
@resp.args[:body] << read
|
587
|
+
self.on_content_call(args, read)
|
588
|
+
end
|
589
|
+
|
590
|
+
nl = @sock.gets
|
591
|
+
if len == 0
|
592
|
+
if nl == @nl
|
593
|
+
return "break"
|
594
|
+
else
|
595
|
+
raise "Dont know what to do :'-("
|
596
|
+
end
|
597
|
+
end
|
598
|
+
|
599
|
+
raise "Should have read newline but didnt: '#{nl}'." if nl != @nl
|
600
|
+
else
|
601
|
+
@resp.args[:body] << line.to_s
|
602
|
+
self.on_content_call(args, line)
|
603
|
+
return "break" if @resp.header?("content-length") and @resp.args[:body].length >= @resp.header("content-length").to_i
|
604
|
+
end
|
605
|
+
else
|
606
|
+
raise "Dont know how to read HTTP version: '#{@resp.args[:http_version]}'."
|
607
|
+
end
|
608
|
+
end
|
609
|
+
end
|
data/spec/http2_spec.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "Http2" do
|
4
|
+
it "should be able to recursively parse post-data-hashes." do
|
5
|
+
res = Http2.post_convert_data(
|
6
|
+
"test1" => "test2"
|
7
|
+
)
|
8
|
+
raise "Expected 'test1=test2' but got: '#{res}'." if res != "test1=test2"
|
9
|
+
|
10
|
+
res = Http2.post_convert_data(
|
11
|
+
"test1" => [1, 2, 3]
|
12
|
+
)
|
13
|
+
raise "Expected 'test1%5B0%5D=1test1%5B1%5D=2test1%5B2%5D=3' but got: '#{res}'." if res != "test1%5B0%5D=1test1%5B1%5D=2test1%5B2%5D=3"
|
14
|
+
|
15
|
+
res = Http2.post_convert_data(
|
16
|
+
"test1" => {
|
17
|
+
"order" => {
|
18
|
+
[:Bnet_profile, "profile_id"] => 5
|
19
|
+
}
|
20
|
+
}
|
21
|
+
)
|
22
|
+
raise "Expected 'test1%5Border%5D%5B%5B%3ABnet_profile%2C+%22profile_id%22%5D%5D=5' but got: '#{res}'." if res != "test1%5Border%5D%5B%5B%3ABnet_profile%2C+%22profile_id%22%5D%5D=5"
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should be able to do normal post-requests." do
|
26
|
+
require "json"
|
27
|
+
|
28
|
+
#Test posting keep-alive and advanced post-data.
|
29
|
+
Http2.new(:host => "www.partyworm.dk") do |http|
|
30
|
+
0.upto(5) do
|
31
|
+
resp = http.get("multipart_test.php")
|
32
|
+
|
33
|
+
resp = http.post(:url => "multipart_test.php?choice=post-test", :post => {
|
34
|
+
"val1" => "test1",
|
35
|
+
"val2" => "test2",
|
36
|
+
"val3" => [
|
37
|
+
"test3"
|
38
|
+
],
|
39
|
+
"val4" => {
|
40
|
+
"val5" => "test5"
|
41
|
+
},
|
42
|
+
"val6" => {
|
43
|
+
"val7" => [
|
44
|
+
{
|
45
|
+
"val8" => "test8"
|
46
|
+
}
|
47
|
+
]
|
48
|
+
}
|
49
|
+
})
|
50
|
+
res = JSON.parse(resp.body)
|
51
|
+
|
52
|
+
raise "Expected 'res' to be a hash." if !res.is_a?(Hash)
|
53
|
+
raise "Error 1" if res["val1"] != "test1"
|
54
|
+
raise "Error 2" if res["val2"] != "test2"
|
55
|
+
raise "Error 3" if !res["val3"] or res["val3"][0] != "test3"
|
56
|
+
raise "Error 4" if res["val4"]["val5"] != "test5"
|
57
|
+
raise "Error 5" if res["val6"]["val7"][0]["val8"] != "test8"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should be able to do multipart-requests and keep-alive when using multipart." do
|
63
|
+
Http2.new(:host => "www.partyworm.dk", :follow_redirects => false) do |http|
|
64
|
+
0.upto(5) do
|
65
|
+
resp = http.post_multipart(:url => "multipart_test.php", :post => {
|
66
|
+
"test_var" => "true"
|
67
|
+
})
|
68
|
+
|
69
|
+
if resp.body != "multipart-test-test_var=true"
|
70
|
+
raise "Expected body to be 'test_var=true' but it wasnt: '#{resp.body}'."
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
it "it should be able to handle keep-alive correctly" do
|
77
|
+
urls = [
|
78
|
+
"?show=users_search",
|
79
|
+
"?show=users_online",
|
80
|
+
"?show=drinksdb",
|
81
|
+
"?show=forum&fid=9&tid=1917&page=0"
|
82
|
+
]
|
83
|
+
urls = ["robots.txt"]
|
84
|
+
|
85
|
+
http = Http2.new(:host => "www.partyworm.dk", :debug => false)
|
86
|
+
0.upto(105) do |count|
|
87
|
+
url = urls[rand(urls.size)]
|
88
|
+
#print "Doing request #{count} of 200 (#{url}).\n"
|
89
|
+
#res = http.get(url)
|
90
|
+
#raise "Body was empty." if res.body.to_s.length <= 0
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rspec'
|
4
|
+
require 'http2'
|
5
|
+
|
6
|
+
# Requires supporting files with custom matchers and macros, etc,
|
7
|
+
# in ./support/ and its subdirectories.
|
8
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
|
12
|
+
end
|
metadata
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: http2
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Kasper Johansen
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2012-07-12 00:00:00 +02:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: knjrbfw
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
type: :runtime
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ~>
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 2.8.0
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: rdoc
|
40
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "3.12"
|
46
|
+
type: :development
|
47
|
+
prerelease: false
|
48
|
+
version_requirements: *id003
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: bundler
|
51
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 1.0.0
|
57
|
+
type: :development
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: *id004
|
60
|
+
- !ruby/object:Gem::Dependency
|
61
|
+
name: jeweler
|
62
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ~>
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 1.8.4
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: *id005
|
71
|
+
- !ruby/object:Gem::Dependency
|
72
|
+
name: rcov
|
73
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: "0"
|
79
|
+
type: :development
|
80
|
+
prerelease: false
|
81
|
+
version_requirements: *id006
|
82
|
+
description: A lightweight framework for doing http-connections in Ruby. Supports cookies, keep-alive, compressing and much more.
|
83
|
+
email: k@spernj.org
|
84
|
+
executables: []
|
85
|
+
|
86
|
+
extensions: []
|
87
|
+
|
88
|
+
extra_rdoc_files:
|
89
|
+
- LICENSE.txt
|
90
|
+
- README.rdoc
|
91
|
+
files:
|
92
|
+
- .document
|
93
|
+
- .rspec
|
94
|
+
- Gemfile
|
95
|
+
- Gemfile.lock
|
96
|
+
- LICENSE.txt
|
97
|
+
- README.rdoc
|
98
|
+
- Rakefile
|
99
|
+
- VERSION
|
100
|
+
- http2.gemspec
|
101
|
+
- include/errors.rb
|
102
|
+
- include/response.rb
|
103
|
+
- include/utils.rb
|
104
|
+
- lib/http2.rb
|
105
|
+
- spec/http2_spec.rb
|
106
|
+
- spec/spec_helper.rb
|
107
|
+
has_rdoc: true
|
108
|
+
homepage: http://github.com/kaspernj/http2
|
109
|
+
licenses:
|
110
|
+
- MIT
|
111
|
+
post_install_message:
|
112
|
+
rdoc_options: []
|
113
|
+
|
114
|
+
require_paths:
|
115
|
+
- lib
|
116
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
117
|
+
none: false
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
hash: -2095464149702075721
|
122
|
+
segments:
|
123
|
+
- 0
|
124
|
+
version: "0"
|
125
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
126
|
+
none: false
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: "0"
|
131
|
+
requirements: []
|
132
|
+
|
133
|
+
rubyforge_project:
|
134
|
+
rubygems_version: 1.6.2
|
135
|
+
signing_key:
|
136
|
+
specification_version: 3
|
137
|
+
summary: A lightweight framework for doing http-connections in Ruby. Supports cookies, keep-alive, compressing and much more.
|
138
|
+
test_files: []
|
139
|
+
|