em-http-request 0.2.11 → 0.2.12
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of em-http-request might be problematic. Click here for more details.
- data/Changelog.md +27 -0
- data/README.md +157 -157
- data/Rakefile +110 -110
- data/VERSION +1 -1
- data/em-http-request.gemspec +5 -4
- data/lib/em-http/client.rb +27 -12
- data/lib/em-http/http_options.rb +33 -33
- data/spec/request_spec.rb +76 -14
- metadata +9 -4
data/Changelog.md
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## 0.2.12 / 2010-09-12
|
4
|
+
|
5
|
+
- added headers callback (http.headers {|h| p h})
|
6
|
+
- added .close method on client obj to terminate session (accepts message)
|
7
|
+
|
8
|
+
- bugfix: report 0 for response status on 1.9 on timeouts
|
9
|
+
- bugfix: handle bad Location host redirects
|
10
|
+
- bugfix: reset host override on connect
|
11
|
+
|
12
|
+
## 0.2.11 / 2010-08-16
|
13
|
+
|
14
|
+
- all URIs are now normalized prior to dispatch (and on redirect)
|
15
|
+
- default to direct proxy (instead of CONNECT handshake) - better performance
|
16
|
+
- specify :proxy => {:tunnel => true} if you need to force CONNECT route
|
17
|
+
- MultiRequest accepts block syntax for dispatching parallel requests (see specs)
|
18
|
+
- MockHttpRequest accepts block syntax (see Mock wiki page)
|
19
|
+
|
20
|
+
|
21
|
+
- bugfix: nullbyte frame for websockets
|
22
|
+
- bugfix: set @uri on DNS resolve failure
|
23
|
+
- bugfix: handle bad hosts in absolute redirects
|
24
|
+
- bugfix: reset seen content on redirects (doh!)
|
25
|
+
- bugfix: invalid multibyte escape in websocket regex (1.9.2+)
|
26
|
+
- bugfix: report etag and last_modified headers correctly
|
27
|
+
|
data/README.md
CHANGED
@@ -1,157 +1,157 @@
|
|
1
|
-
EM-HTTP-Request
|
2
|
-
===============
|
3
|
-
|
4
|
-
Asynchronous HTTP client for Ruby, based on EventMachine runtime.
|
5
|
-
|
6
|
-
- Ragel HTTP parser for speed & performance
|
7
|
-
- Simple interface for single & parallel requests via deferred callbacks
|
8
|
-
- Automatic gzip & deflate decoding
|
9
|
-
- Basic-Auth & OAuth support
|
10
|
-
- Custom timeout support
|
11
|
-
- Stream response processing
|
12
|
-
- Proxy support (with SSL Tunneling)
|
13
|
-
- Auto-follow 3xx redirects with custom max depth
|
14
|
-
- Bi-directional communication with web-socket services
|
15
|
-
- [Native mocking support](http://wiki.github.com/igrigorik/em-http-request/mocking-httprequest) and through [Webmock](http://github.com/bblimke/webmock)
|
16
|
-
|
17
|
-
Getting started
|
18
|
-
---------------
|
19
|
-
|
20
|
-
gem install em-http-request
|
21
|
-
irb:0> require 'em-http'
|
22
|
-
|
23
|
-
Or checkout [screencast / demo](http://everburning.com/news/eventmachine-screencast-em-http-request/) of using EM-HTTP-Request.
|
24
|
-
|
25
|
-
Libraries & Applications using em-http
|
26
|
-
--------------------------------------
|
27
|
-
|
28
|
-
- [chirpstream](http://github.com/joshbuddy/chirpstream) - EM client for Twitters Chirpstream API
|
29
|
-
- [RDaneel](http://github.com/hasmanydevelopers/RDaneel) - Ruby crawler which respects robots.txt
|
30
|
-
- [rsolr-async](http://github.com/mwmitchell/rsolr-async) - An asynchronus connection adapter for RSolr
|
31
|
-
- [PubSubHubbub](http://github.com/igrigorik/PubSubHubbub) - Asynchronous PubSubHubbub ruby client
|
32
|
-
- and many others.. drop me a link if you want yours included!
|
33
|
-
|
34
|
-
Simple client example
|
35
|
-
---------------------
|
36
|
-
|
37
|
-
EventMachine.run {
|
38
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1/').get :query => {'keyname' => 'value'}, :timeout => 10
|
39
|
-
|
40
|
-
http.callback {
|
41
|
-
p http.response_header.status
|
42
|
-
p http.response_header
|
43
|
-
p http.response
|
44
|
-
|
45
|
-
EventMachine.stop
|
46
|
-
}
|
47
|
-
}
|
48
|
-
|
49
|
-
Multi-request example
|
50
|
-
---------------------
|
51
|
-
|
52
|
-
Fire and wait for multiple requests to complete via the MultiRequest interface.
|
53
|
-
|
54
|
-
EventMachine.run {
|
55
|
-
multi = EventMachine::MultiRequest.new
|
56
|
-
|
57
|
-
# add multiple requests to the multi-handler
|
58
|
-
multi.add(EventMachine::HttpRequest.new('http://www.google.com/').get)
|
59
|
-
multi.add(EventMachine::HttpRequest.new('http://www.yahoo.com/').get)
|
60
|
-
|
61
|
-
multi.callback {
|
62
|
-
p multi.responses[:succeeded]
|
63
|
-
p multi.responses[:failed]
|
64
|
-
|
65
|
-
EventMachine.stop
|
66
|
-
}
|
67
|
-
}
|
68
|
-
|
69
|
-
Basic-Auth example
|
70
|
-
------------------
|
71
|
-
|
72
|
-
Full basic author support. For OAuth, check examples/oauth-tweet.rb file.
|
73
|
-
|
74
|
-
EventMachine.run {
|
75
|
-
http = EventMachine::HttpRequest.new('http://www.website.com/').get :head => {'authorization' => ['user', 'pass']}
|
76
|
-
|
77
|
-
http.errback { failed }
|
78
|
-
http.callback {
|
79
|
-
p http.response_header
|
80
|
-
EventMachine.stop
|
81
|
-
}
|
82
|
-
}
|
83
|
-
|
84
|
-
|
85
|
-
POSTing data example
|
86
|
-
--------------------
|
87
|
-
|
88
|
-
EventMachine.run {
|
89
|
-
http1 = EventMachine::HttpRequest.new('http://www.website.com/').post :body => {"key1" => 1, "key2" => [2,3]}
|
90
|
-
http2 = EventMachine::HttpRequest.new('http://www.website.com/').post :body => "some data"
|
91
|
-
|
92
|
-
# ...
|
93
|
-
}
|
94
|
-
|
95
|
-
Streaming body processing
|
96
|
-
-------------------------
|
97
|
-
|
98
|
-
Allows you to consume an HTTP stream of content in real-time. Each time a new piece of content is pushed
|
99
|
-
to the client, it is passed to the stream callback for you to operate on.
|
100
|
-
|
101
|
-
EventMachine.run {
|
102
|
-
http = EventMachine::HttpRequest.new('http://www.website.com/').get
|
103
|
-
http.stream { |chunk| print chunk }
|
104
|
-
}
|
105
|
-
|
106
|
-
Streaming files from disk
|
107
|
-
-------------------------
|
108
|
-
Allows you to efficiently stream a (large) file from disk via EventMachine's FileStream interface.
|
109
|
-
|
110
|
-
EventMachine.run {
|
111
|
-
http = EventMachine::HttpRequest.new('http://www.website.com/').post :file => 'largefile.txt'
|
112
|
-
http.callback { |chunk| puts "Upload finished!" }
|
113
|
-
}
|
114
|
-
|
115
|
-
Proxy example
|
116
|
-
-------------
|
117
|
-
|
118
|
-
Full transparent proxy support with support for SSL tunneling.
|
119
|
-
|
120
|
-
EventMachine.run {
|
121
|
-
http = EventMachine::HttpRequest.new('http://www.website.com/').get :proxy => {
|
122
|
-
:host => 'www.myproxy.com',
|
123
|
-
:port => 8080,
|
124
|
-
:authorization => ['username', 'password'] # authorization is optional
|
125
|
-
}
|
126
|
-
|
127
|
-
Auto-follow 3xx redirects
|
128
|
-
-------------------------
|
129
|
-
|
130
|
-
Specify the max depth of redirects to follow, default is 0.
|
131
|
-
|
132
|
-
EventMachine.run {
|
133
|
-
http = EventMachine::HttpRequest.new('http://www.google.com/').get :redirects => 1
|
134
|
-
http.callback { p http.last_effective_url }
|
135
|
-
}
|
136
|
-
|
137
|
-
WebSocket example
|
138
|
-
-----------------
|
139
|
-
|
140
|
-
[Bi-directional communication with WebSockets](http://www.igvita.com/2009/12/22/ruby-websockets-tcp-for-the-browser/): simply pass in a ws:// resource and the client will negotiate the connection upgrade for you. On successful handshake the callback is invoked, and any incoming messages will be passed to the stream callback. The client can also send data to the server at will by calling the "send" method!
|
141
|
-
|
142
|
-
EventMachine.run {
|
143
|
-
http = EventMachine::HttpRequest.new("ws://yourservice.com/websocket").get :timeout => 0
|
144
|
-
|
145
|
-
http.errback { puts "oops" }
|
146
|
-
http.callback {
|
147
|
-
puts "WebSocket connected!"
|
148
|
-
http.send("Hello client")
|
149
|
-
}
|
150
|
-
|
151
|
-
http.stream { |msg|
|
152
|
-
puts "Recieved: #{msg}"
|
153
|
-
http.send "Pong: #{msg}"
|
154
|
-
}
|
155
|
-
|
156
|
-
http.disconnect { puts "oops, dropped connection?" }
|
157
|
-
}
|
1
|
+
EM-HTTP-Request
|
2
|
+
===============
|
3
|
+
|
4
|
+
Asynchronous HTTP client for Ruby, based on EventMachine runtime.
|
5
|
+
|
6
|
+
- Ragel HTTP parser for speed & performance
|
7
|
+
- Simple interface for single & parallel requests via deferred callbacks
|
8
|
+
- Automatic gzip & deflate decoding
|
9
|
+
- Basic-Auth & OAuth support
|
10
|
+
- Custom timeout support
|
11
|
+
- Stream response processing
|
12
|
+
- Proxy support (with SSL Tunneling)
|
13
|
+
- Auto-follow 3xx redirects with custom max depth
|
14
|
+
- Bi-directional communication with web-socket services
|
15
|
+
- [Native mocking support](http://wiki.github.com/igrigorik/em-http-request/mocking-httprequest) and through [Webmock](http://github.com/bblimke/webmock)
|
16
|
+
|
17
|
+
Getting started
|
18
|
+
---------------
|
19
|
+
|
20
|
+
gem install em-http-request
|
21
|
+
irb:0> require 'em-http'
|
22
|
+
|
23
|
+
Or checkout [screencast / demo](http://everburning.com/news/eventmachine-screencast-em-http-request/) of using EM-HTTP-Request.
|
24
|
+
|
25
|
+
Libraries & Applications using em-http
|
26
|
+
--------------------------------------
|
27
|
+
|
28
|
+
- [chirpstream](http://github.com/joshbuddy/chirpstream) - EM client for Twitters Chirpstream API
|
29
|
+
- [RDaneel](http://github.com/hasmanydevelopers/RDaneel) - Ruby crawler which respects robots.txt
|
30
|
+
- [rsolr-async](http://github.com/mwmitchell/rsolr-async) - An asynchronus connection adapter for RSolr
|
31
|
+
- [PubSubHubbub](http://github.com/igrigorik/PubSubHubbub) - Asynchronous PubSubHubbub ruby client
|
32
|
+
- and many others.. drop me a link if you want yours included!
|
33
|
+
|
34
|
+
Simple client example
|
35
|
+
---------------------
|
36
|
+
|
37
|
+
EventMachine.run {
|
38
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1/').get :query => {'keyname' => 'value'}, :timeout => 10
|
39
|
+
|
40
|
+
http.callback {
|
41
|
+
p http.response_header.status
|
42
|
+
p http.response_header
|
43
|
+
p http.response
|
44
|
+
|
45
|
+
EventMachine.stop
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
49
|
+
Multi-request example
|
50
|
+
---------------------
|
51
|
+
|
52
|
+
Fire and wait for multiple requests to complete via the MultiRequest interface.
|
53
|
+
|
54
|
+
EventMachine.run {
|
55
|
+
multi = EventMachine::MultiRequest.new
|
56
|
+
|
57
|
+
# add multiple requests to the multi-handler
|
58
|
+
multi.add(EventMachine::HttpRequest.new('http://www.google.com/').get)
|
59
|
+
multi.add(EventMachine::HttpRequest.new('http://www.yahoo.com/').get)
|
60
|
+
|
61
|
+
multi.callback {
|
62
|
+
p multi.responses[:succeeded]
|
63
|
+
p multi.responses[:failed]
|
64
|
+
|
65
|
+
EventMachine.stop
|
66
|
+
}
|
67
|
+
}
|
68
|
+
|
69
|
+
Basic-Auth example
|
70
|
+
------------------
|
71
|
+
|
72
|
+
Full basic author support. For OAuth, check examples/oauth-tweet.rb file.
|
73
|
+
|
74
|
+
EventMachine.run {
|
75
|
+
http = EventMachine::HttpRequest.new('http://www.website.com/').get :head => {'authorization' => ['user', 'pass']}
|
76
|
+
|
77
|
+
http.errback { failed }
|
78
|
+
http.callback {
|
79
|
+
p http.response_header
|
80
|
+
EventMachine.stop
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
|
85
|
+
POSTing data example
|
86
|
+
--------------------
|
87
|
+
|
88
|
+
EventMachine.run {
|
89
|
+
http1 = EventMachine::HttpRequest.new('http://www.website.com/').post :body => {"key1" => 1, "key2" => [2,3]}
|
90
|
+
http2 = EventMachine::HttpRequest.new('http://www.website.com/').post :body => "some data"
|
91
|
+
|
92
|
+
# ...
|
93
|
+
}
|
94
|
+
|
95
|
+
Streaming body processing
|
96
|
+
-------------------------
|
97
|
+
|
98
|
+
Allows you to consume an HTTP stream of content in real-time. Each time a new piece of content is pushed
|
99
|
+
to the client, it is passed to the stream callback for you to operate on.
|
100
|
+
|
101
|
+
EventMachine.run {
|
102
|
+
http = EventMachine::HttpRequest.new('http://www.website.com/').get
|
103
|
+
http.stream { |chunk| print chunk }
|
104
|
+
}
|
105
|
+
|
106
|
+
Streaming files from disk
|
107
|
+
-------------------------
|
108
|
+
Allows you to efficiently stream a (large) file from disk via EventMachine's FileStream interface.
|
109
|
+
|
110
|
+
EventMachine.run {
|
111
|
+
http = EventMachine::HttpRequest.new('http://www.website.com/').post :file => 'largefile.txt'
|
112
|
+
http.callback { |chunk| puts "Upload finished!" }
|
113
|
+
}
|
114
|
+
|
115
|
+
Proxy example
|
116
|
+
-------------
|
117
|
+
|
118
|
+
Full transparent proxy support with support for SSL tunneling.
|
119
|
+
|
120
|
+
EventMachine.run {
|
121
|
+
http = EventMachine::HttpRequest.new('http://www.website.com/').get :proxy => {
|
122
|
+
:host => 'www.myproxy.com',
|
123
|
+
:port => 8080,
|
124
|
+
:authorization => ['username', 'password'] # authorization is optional
|
125
|
+
}
|
126
|
+
|
127
|
+
Auto-follow 3xx redirects
|
128
|
+
-------------------------
|
129
|
+
|
130
|
+
Specify the max depth of redirects to follow, default is 0.
|
131
|
+
|
132
|
+
EventMachine.run {
|
133
|
+
http = EventMachine::HttpRequest.new('http://www.google.com/').get :redirects => 1
|
134
|
+
http.callback { p http.last_effective_url }
|
135
|
+
}
|
136
|
+
|
137
|
+
WebSocket example
|
138
|
+
-----------------
|
139
|
+
|
140
|
+
[Bi-directional communication with WebSockets](http://www.igvita.com/2009/12/22/ruby-websockets-tcp-for-the-browser/): simply pass in a ws:// resource and the client will negotiate the connection upgrade for you. On successful handshake the callback is invoked, and any incoming messages will be passed to the stream callback. The client can also send data to the server at will by calling the "send" method!
|
141
|
+
|
142
|
+
EventMachine.run {
|
143
|
+
http = EventMachine::HttpRequest.new("ws://yourservice.com/websocket").get :timeout => 0
|
144
|
+
|
145
|
+
http.errback { puts "oops" }
|
146
|
+
http.callback {
|
147
|
+
puts "WebSocket connected!"
|
148
|
+
http.send("Hello client")
|
149
|
+
}
|
150
|
+
|
151
|
+
http.stream { |msg|
|
152
|
+
puts "Recieved: #{msg}"
|
153
|
+
http.send "Pong: #{msg}"
|
154
|
+
}
|
155
|
+
|
156
|
+
http.disconnect { puts "oops, dropped connection?" }
|
157
|
+
}
|
data/Rakefile
CHANGED
@@ -1,110 +1,110 @@
|
|
1
|
-
require 'rake'
|
2
|
-
require 'rake/clean'
|
3
|
-
require 'rake/rdoctask'
|
4
|
-
require 'rake/gempackagetask'
|
5
|
-
require 'fileutils'
|
6
|
-
include FileUtils
|
7
|
-
|
8
|
-
# copied from EventMachine.
|
9
|
-
MAKE = ENV['MAKE'] || if RUBY_PLATFORM =~ /mswin/ # mingw uses make.
|
10
|
-
'nmake'
|
11
|
-
else
|
12
|
-
'make'
|
13
|
-
end
|
14
|
-
|
15
|
-
# Default Rake task is compile
|
16
|
-
task :default => :compile
|
17
|
-
|
18
|
-
# RDoc
|
19
|
-
Rake::RDocTask.new(:rdoc) do |task|
|
20
|
-
task.rdoc_dir = 'doc'
|
21
|
-
task.title = 'EventMachine::HttpRequest'
|
22
|
-
task.options = %w(--title HttpRequest --main README --line-numbers)
|
23
|
-
task.rdoc_files.include(['lib/**/*.rb'])
|
24
|
-
task.rdoc_files.include(['README', 'LICENSE'])
|
25
|
-
end
|
26
|
-
|
27
|
-
# Rebuild parser Ragel
|
28
|
-
task :ragel do
|
29
|
-
Dir.chdir "ext/http11_client" do
|
30
|
-
target = "http11_parser.c"
|
31
|
-
File.unlink target if File.exist? target
|
32
|
-
sh "ragel http11_parser.rl | rlgen-cd -G2 -o #{target}"
|
33
|
-
raise "Failed to build C source" unless File.exist? target
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
require 'spec'
|
38
|
-
require 'spec/rake/spectask'
|
39
|
-
Spec::Rake::SpecTask.new(:spec) do |t|
|
40
|
-
t.spec_opts ||= []
|
41
|
-
t.spec_opts << "--options" << "spec/spec.opts"
|
42
|
-
t.spec_files = FileList['spec/**/*_spec.rb']
|
43
|
-
end
|
44
|
-
|
45
|
-
def make(makedir)
|
46
|
-
Dir.chdir(makedir) { sh MAKE }
|
47
|
-
end
|
48
|
-
|
49
|
-
def extconf(dir)
|
50
|
-
Dir.chdir(dir) { ruby "extconf.rb" }
|
51
|
-
end
|
52
|
-
|
53
|
-
def setup_extension(dir, extension)
|
54
|
-
ext = "ext/#{dir}"
|
55
|
-
ext_so = "#{ext}/#{extension}.#{Config::
|
56
|
-
ext_files = FileList[
|
57
|
-
"#{ext}/*.c",
|
58
|
-
"#{ext}/*.h",
|
59
|
-
"#{ext}/extconf.rb",
|
60
|
-
"#{ext}/Makefile",
|
61
|
-
"lib"
|
62
|
-
]
|
63
|
-
|
64
|
-
task "lib" do
|
65
|
-
directory "lib"
|
66
|
-
end
|
67
|
-
|
68
|
-
desc "Builds just the #{extension} extension"
|
69
|
-
|
70
|
-
mf = (extension + '_makefile').to_sym
|
71
|
-
|
72
|
-
task mf do |t|
|
73
|
-
extconf "#{ext}"
|
74
|
-
end
|
75
|
-
|
76
|
-
task extension.to_sym => [mf] do
|
77
|
-
make "#{ext}"
|
78
|
-
cp ext_so, "lib"
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
setup_extension("buffer", "em_buffer")
|
83
|
-
setup_extension("http11_client", "http11_client")
|
84
|
-
|
85
|
-
task :compile => [:em_buffer, :http11_client]
|
86
|
-
|
87
|
-
CLEAN.include ['build/*', '**/*.o', '**/*.so', '**/*.a', '**/*.log', 'pkg']
|
88
|
-
CLEAN.include ['ext/buffer/Makefile', 'lib/em_buffer.*', 'lib/http11_client.*']
|
89
|
-
|
90
|
-
begin
|
91
|
-
require 'jeweler'
|
92
|
-
Jeweler::Tasks.new do |gemspec|
|
93
|
-
gemspec.name = "em-http-request"
|
94
|
-
gemspec.summary = "EventMachine based, async HTTP Request interface"
|
95
|
-
gemspec.description = gemspec.summary
|
96
|
-
gemspec.email = "ilya@igvita.com"
|
97
|
-
gemspec.homepage = "http://github.com/igrigorik/em-http-request"
|
98
|
-
gemspec.authors = ["Ilya Grigorik"]
|
99
|
-
gemspec.required_ruby_version = ">= 1.8.6"
|
100
|
-
gemspec.extensions = ["ext/buffer/extconf.rb" , "ext/http11_client/extconf.rb"]
|
101
|
-
gemspec.add_dependency('eventmachine', '>= 0.12.9')
|
102
|
-
gemspec.add_dependency('addressable', '>= 2.0.0')
|
103
|
-
gemspec.rubyforge_project = "em-http-request"
|
104
|
-
gemspec.files = FileList[`git ls-files`.split]
|
105
|
-
end
|
106
|
-
|
107
|
-
Jeweler::GemcutterTasks.new
|
108
|
-
rescue LoadError
|
109
|
-
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
110
|
-
end
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/clean'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
require 'rake/gempackagetask'
|
5
|
+
require 'fileutils'
|
6
|
+
include FileUtils
|
7
|
+
|
8
|
+
# copied from EventMachine.
|
9
|
+
MAKE = ENV['MAKE'] || if RUBY_PLATFORM =~ /mswin/ # mingw uses make.
|
10
|
+
'nmake'
|
11
|
+
else
|
12
|
+
'make'
|
13
|
+
end
|
14
|
+
|
15
|
+
# Default Rake task is compile
|
16
|
+
task :default => :compile
|
17
|
+
|
18
|
+
# RDoc
|
19
|
+
Rake::RDocTask.new(:rdoc) do |task|
|
20
|
+
task.rdoc_dir = 'doc'
|
21
|
+
task.title = 'EventMachine::HttpRequest'
|
22
|
+
task.options = %w(--title HttpRequest --main README --line-numbers)
|
23
|
+
task.rdoc_files.include(['lib/**/*.rb'])
|
24
|
+
task.rdoc_files.include(['README', 'LICENSE'])
|
25
|
+
end
|
26
|
+
|
27
|
+
# Rebuild parser Ragel
|
28
|
+
task :ragel do
|
29
|
+
Dir.chdir "ext/http11_client" do
|
30
|
+
target = "http11_parser.c"
|
31
|
+
File.unlink target if File.exist? target
|
32
|
+
sh "ragel http11_parser.rl | rlgen-cd -G2 -o #{target}"
|
33
|
+
raise "Failed to build C source" unless File.exist? target
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
require 'spec'
|
38
|
+
require 'spec/rake/spectask'
|
39
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
40
|
+
t.spec_opts ||= []
|
41
|
+
t.spec_opts << "--options" << "spec/spec.opts"
|
42
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
43
|
+
end
|
44
|
+
|
45
|
+
def make(makedir)
|
46
|
+
Dir.chdir(makedir) { sh MAKE }
|
47
|
+
end
|
48
|
+
|
49
|
+
def extconf(dir)
|
50
|
+
Dir.chdir(dir) { ruby "extconf.rb" }
|
51
|
+
end
|
52
|
+
|
53
|
+
def setup_extension(dir, extension)
|
54
|
+
ext = "ext/#{dir}"
|
55
|
+
ext_so = "#{ext}/#{extension}.#{Config::MAKEFILE_CONFIG['DLEXT']}"
|
56
|
+
ext_files = FileList[
|
57
|
+
"#{ext}/*.c",
|
58
|
+
"#{ext}/*.h",
|
59
|
+
"#{ext}/extconf.rb",
|
60
|
+
"#{ext}/Makefile",
|
61
|
+
"lib"
|
62
|
+
]
|
63
|
+
|
64
|
+
task "lib" do
|
65
|
+
directory "lib"
|
66
|
+
end
|
67
|
+
|
68
|
+
desc "Builds just the #{extension} extension"
|
69
|
+
|
70
|
+
mf = (extension + '_makefile').to_sym
|
71
|
+
|
72
|
+
task mf do |t|
|
73
|
+
extconf "#{ext}"
|
74
|
+
end
|
75
|
+
|
76
|
+
task extension.to_sym => [mf] do
|
77
|
+
make "#{ext}"
|
78
|
+
cp ext_so, "lib"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
setup_extension("buffer", "em_buffer")
|
83
|
+
setup_extension("http11_client", "http11_client")
|
84
|
+
|
85
|
+
task :compile => [:em_buffer, :http11_client]
|
86
|
+
|
87
|
+
CLEAN.include ['build/*', '**/*.o', '**/*.so', '**/*.a', '**/*.log', 'pkg']
|
88
|
+
CLEAN.include ['ext/buffer/Makefile', 'lib/em_buffer.*', 'lib/http11_client.*']
|
89
|
+
|
90
|
+
begin
|
91
|
+
require 'jeweler'
|
92
|
+
Jeweler::Tasks.new do |gemspec|
|
93
|
+
gemspec.name = "em-http-request"
|
94
|
+
gemspec.summary = "EventMachine based, async HTTP Request interface"
|
95
|
+
gemspec.description = gemspec.summary
|
96
|
+
gemspec.email = "ilya@igvita.com"
|
97
|
+
gemspec.homepage = "http://github.com/igrigorik/em-http-request"
|
98
|
+
gemspec.authors = ["Ilya Grigorik"]
|
99
|
+
gemspec.required_ruby_version = ">= 1.8.6"
|
100
|
+
gemspec.extensions = ["ext/buffer/extconf.rb" , "ext/http11_client/extconf.rb"]
|
101
|
+
gemspec.add_dependency('eventmachine', '>= 0.12.9')
|
102
|
+
gemspec.add_dependency('addressable', '>= 2.0.0')
|
103
|
+
gemspec.rubyforge_project = "em-http-request"
|
104
|
+
gemspec.files = FileList[`git ls-files`.split]
|
105
|
+
end
|
106
|
+
|
107
|
+
Jeweler::GemcutterTasks.new
|
108
|
+
rescue LoadError
|
109
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
110
|
+
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.2.
|
1
|
+
0.2.12
|
data/em-http-request.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{em-http-request}
|
8
|
-
s.version = "0.2.
|
8
|
+
s.version = "0.2.12"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Ilya Grigorik"]
|
12
|
-
s.date = %q{2010-
|
12
|
+
s.date = %q{2010-09-12}
|
13
13
|
s.description = %q{EventMachine based, async HTTP Request interface}
|
14
14
|
s.email = %q{ilya@igvita.com}
|
15
15
|
s.extensions = ["ext/buffer/extconf.rb", "ext/http11_client/extconf.rb"]
|
@@ -19,6 +19,7 @@ Gem::Specification.new do |s|
|
|
19
19
|
]
|
20
20
|
s.files = [
|
21
21
|
".gitignore",
|
22
|
+
"Changelog.md",
|
22
23
|
"LICENSE",
|
23
24
|
"README.md",
|
24
25
|
"Rakefile",
|
@@ -62,7 +63,7 @@ Gem::Specification.new do |s|
|
|
62
63
|
s.require_paths = ["lib"]
|
63
64
|
s.required_ruby_version = Gem::Requirement.new(">= 1.8.6")
|
64
65
|
s.rubyforge_project = %q{em-http-request}
|
65
|
-
s.rubygems_version = %q{1.3.
|
66
|
+
s.rubygems_version = %q{1.3.7}
|
66
67
|
s.summary = %q{EventMachine based, async HTTP Request interface}
|
67
68
|
s.test_files = [
|
68
69
|
"spec/hash_spec.rb",
|
@@ -83,7 +84,7 @@ Gem::Specification.new do |s|
|
|
83
84
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
84
85
|
s.specification_version = 3
|
85
86
|
|
86
|
-
if Gem::Version.new(Gem::
|
87
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
87
88
|
s.add_runtime_dependency(%q<eventmachine>, [">= 0.12.9"])
|
88
89
|
s.add_runtime_dependency(%q<addressable>, [">= 2.0.0"])
|
89
90
|
else
|
data/lib/em-http/client.rb
CHANGED
@@ -32,7 +32,7 @@ module EventMachine
|
|
32
32
|
|
33
33
|
# HTTP response status as an integer
|
34
34
|
def status
|
35
|
-
Integer(http_status) rescue
|
35
|
+
Integer(http_status) rescue 0
|
36
36
|
end
|
37
37
|
|
38
38
|
# Length of content as an integer, or nil if chunked/unspecified
|
@@ -265,6 +265,7 @@ module EventMachine
|
|
265
265
|
# fail the connection directly
|
266
266
|
dns_error == true ? fail(self) : unbind
|
267
267
|
end
|
268
|
+
alias :close :on_error
|
268
269
|
|
269
270
|
# assign a stream processing block
|
270
271
|
def stream(&blk)
|
@@ -276,6 +277,11 @@ module EventMachine
|
|
276
277
|
@disconnect = blk
|
277
278
|
end
|
278
279
|
|
280
|
+
# assign a headers parse callback
|
281
|
+
def headers(&blk)
|
282
|
+
@headers = blk
|
283
|
+
end
|
284
|
+
|
279
285
|
# raw data push from the client (WebSocket) should
|
280
286
|
# only be invoked after handshake, otherwise it will
|
281
287
|
# inject data into the header exchange
|
@@ -403,20 +409,24 @@ module EventMachine
|
|
403
409
|
|
404
410
|
def unbind
|
405
411
|
if (@state == :finished) && (@last_effective_url != @uri) && (@redirects < @options[:redirects])
|
406
|
-
|
407
|
-
|
412
|
+
begin
|
413
|
+
# update uri to redirect location if we're allowed to traverse deeper
|
414
|
+
@uri = @last_effective_url
|
408
415
|
|
409
|
-
|
410
|
-
|
416
|
+
# keep track of the depth of requests we made in this session
|
417
|
+
@redirects += 1
|
411
418
|
|
412
|
-
|
413
|
-
|
414
|
-
|
419
|
+
# swap current connection and reassign current handler
|
420
|
+
req = HttpOptions.new(@method, @uri, @options)
|
421
|
+
reconnect(req.host, req.port)
|
415
422
|
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
423
|
+
@response_header = HttpResponseHeader.new
|
424
|
+
@state = :response_header
|
425
|
+
@response = ''
|
426
|
+
@data.clear
|
427
|
+
rescue EventMachine::ConnectionError => e
|
428
|
+
on_error(e.message, true)
|
429
|
+
end
|
420
430
|
else
|
421
431
|
if @state == :finished || (@state == :body && @bytes_remaining.nil?)
|
422
432
|
succeed(self)
|
@@ -479,6 +489,10 @@ module EventMachine
|
|
479
489
|
def parse_response_header
|
480
490
|
return false unless parse_header(@response_header)
|
481
491
|
|
492
|
+
# invoke headers callback after full parse if one
|
493
|
+
# is specified by the user
|
494
|
+
@headers.call(@response_header) if @headers
|
495
|
+
|
482
496
|
unless @response_header.http_status and @response_header.http_reason
|
483
497
|
@state = :invalid
|
484
498
|
on_error "no HTTP response"
|
@@ -674,3 +688,4 @@ module EventMachine
|
|
674
688
|
end
|
675
689
|
|
676
690
|
end
|
691
|
+
|
data/lib/em-http/http_options.rb
CHANGED
@@ -1,34 +1,34 @@
|
|
1
|
-
class HttpOptions
|
2
|
-
attr_reader :uri, :method, :host, :port, :options
|
3
|
-
|
4
|
-
def initialize(method, uri, options)
|
5
|
-
uri.normalize!
|
6
|
-
|
7
|
-
@options = options
|
8
|
-
@method = method.to_s.upcase
|
9
|
-
@uri = uri
|
10
|
-
|
11
|
-
if proxy = options[:proxy]
|
12
|
-
@host = proxy[:host]
|
13
|
-
@port = proxy[:port]
|
14
|
-
else
|
15
|
-
# optional host for cases where you may have
|
16
|
-
# pre-resolved the host, or you need an override
|
17
|
-
@host = options
|
18
|
-
@port = uri.port
|
19
|
-
end
|
20
|
-
|
21
|
-
@options[:timeout] ||= 10 # default connect & inactivity timeouts
|
22
|
-
@options[:redirects] ||= 0 # default number of redirects to follow
|
23
|
-
|
24
|
-
# Make sure the ports are set as Addressable::URI doesn't
|
25
|
-
# set the port if it isn't there
|
26
|
-
if uri.scheme == "https"
|
27
|
-
@uri.port ||= 443
|
28
|
-
@port ||= 443
|
29
|
-
else
|
30
|
-
@uri.port ||= 80
|
31
|
-
@port ||= 80
|
32
|
-
end
|
33
|
-
end
|
1
|
+
class HttpOptions
|
2
|
+
attr_reader :uri, :method, :host, :port, :options
|
3
|
+
|
4
|
+
def initialize(method, uri, options)
|
5
|
+
uri.normalize!
|
6
|
+
|
7
|
+
@options = options
|
8
|
+
@method = method.to_s.upcase
|
9
|
+
@uri = uri
|
10
|
+
|
11
|
+
if proxy = options[:proxy]
|
12
|
+
@host = proxy[:host]
|
13
|
+
@port = proxy[:port]
|
14
|
+
else
|
15
|
+
# optional host for cases where you may have
|
16
|
+
# pre-resolved the host, or you need an override
|
17
|
+
@host = options.delete(:host) || uri.host
|
18
|
+
@port = uri.port
|
19
|
+
end
|
20
|
+
|
21
|
+
@options[:timeout] ||= 10 # default connect & inactivity timeouts
|
22
|
+
@options[:redirects] ||= 0 # default number of redirects to follow
|
23
|
+
|
24
|
+
# Make sure the ports are set as Addressable::URI doesn't
|
25
|
+
# set the port if it isn't there
|
26
|
+
if uri.scheme == "https"
|
27
|
+
@uri.port ||= 443
|
28
|
+
@port ||= 443
|
29
|
+
else
|
30
|
+
@uri.port ||= 80
|
31
|
+
@port ||= 80
|
32
|
+
end
|
33
|
+
end
|
34
34
|
end
|
data/spec/request_spec.rb
CHANGED
@@ -68,17 +68,37 @@ describe EventMachine::HttpRequest do
|
|
68
68
|
}
|
69
69
|
end
|
70
70
|
|
71
|
-
|
72
|
-
EventMachine.run {
|
73
|
-
http = EventMachine::HttpRequest.new('http://google.com:8080/').get :host => '127.0.0.1'
|
71
|
+
context "host override" do
|
74
72
|
|
75
|
-
|
76
|
-
|
77
|
-
http.
|
78
|
-
|
79
|
-
|
73
|
+
it "should accept optional host" do
|
74
|
+
EventMachine.run {
|
75
|
+
http = EventMachine::HttpRequest.new('http://google.com:8080/').get :host => '127.0.0.1'
|
76
|
+
|
77
|
+
http.errback { failed }
|
78
|
+
http.callback {
|
79
|
+
http.response_header.status.should == 200
|
80
|
+
http.response.should match(/Hello/)
|
81
|
+
EventMachine.stop
|
82
|
+
}
|
80
83
|
}
|
81
|
-
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should reset host on redirect" do
|
87
|
+
EventMachine.run {
|
88
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/redirect').get :redirects => 1, :host => '127.0.0.1'
|
89
|
+
|
90
|
+
http.errback { failed }
|
91
|
+
http.callback {
|
92
|
+
http.response_header.status.should == 200
|
93
|
+
http.response_header["CONTENT_ENCODING"].should == "gzip"
|
94
|
+
http.response.should == "compressed"
|
95
|
+
http.last_effective_url.to_s.should == 'http://127.0.0.1:8080/gzip'
|
96
|
+
http.redirects.should == 1
|
97
|
+
|
98
|
+
EM.stop
|
99
|
+
}
|
100
|
+
}
|
101
|
+
end
|
82
102
|
end
|
83
103
|
|
84
104
|
it "should perform successfull GET with a URI passed as argument" do
|
@@ -246,7 +266,7 @@ describe EventMachine::HttpRequest do
|
|
246
266
|
EventMachine.run {
|
247
267
|
|
248
268
|
# digg.com uses chunked encoding
|
249
|
-
http = EventMachine::HttpRequest.new('http://digg.com/').get
|
269
|
+
http = EventMachine::HttpRequest.new('http://digg.com/news').get
|
250
270
|
|
251
271
|
http.errback { failed }
|
252
272
|
http.callback {
|
@@ -435,12 +455,11 @@ describe EventMachine::HttpRequest do
|
|
435
455
|
end
|
436
456
|
|
437
457
|
it "should fail gracefully on an invalid host in Location header" do
|
438
|
-
pending "validate tld's?"
|
439
458
|
EventMachine.run {
|
440
459
|
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/redirect/badhost').get :redirects => 1
|
441
460
|
http.callback { failed }
|
442
461
|
http.errback {
|
443
|
-
http.error.should == '
|
462
|
+
http.error.should == 'unable to resolve server address'
|
444
463
|
EM.stop
|
445
464
|
}
|
446
465
|
}
|
@@ -464,6 +483,49 @@ describe EventMachine::HttpRequest do
|
|
464
483
|
}
|
465
484
|
end
|
466
485
|
|
486
|
+
context "optional header callback" do
|
487
|
+
it "should optionally pass the response headers" do
|
488
|
+
EventMachine.run {
|
489
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get
|
490
|
+
|
491
|
+
http.errback { failed }
|
492
|
+
http.headers { |hash|
|
493
|
+
hash.should be_an_kind_of Hash
|
494
|
+
hash.should include 'CONNECTION'
|
495
|
+
hash.should include 'CONTENT_LENGTH'
|
496
|
+
}
|
497
|
+
|
498
|
+
http.callback {
|
499
|
+
http.response_header.status.should == 200
|
500
|
+
http.response.should match(/Hello/)
|
501
|
+
EventMachine.stop
|
502
|
+
}
|
503
|
+
}
|
504
|
+
end
|
505
|
+
|
506
|
+
it "should allow to terminate current connection from header callback" do
|
507
|
+
EventMachine.run {
|
508
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get
|
509
|
+
|
510
|
+
http.callback { failed }
|
511
|
+
http.headers { |hash|
|
512
|
+
hash.should be_an_kind_of Hash
|
513
|
+
hash.should include 'CONNECTION'
|
514
|
+
hash.should include 'CONTENT_LENGTH'
|
515
|
+
|
516
|
+
http.close('header callback terminated connection')
|
517
|
+
}
|
518
|
+
|
519
|
+
http.errback { |e|
|
520
|
+
http.response_header.status.should == 200
|
521
|
+
http.error.should == 'header callback terminated connection'
|
522
|
+
http.response.should == ''
|
523
|
+
EventMachine.stop
|
524
|
+
}
|
525
|
+
}
|
526
|
+
end
|
527
|
+
end
|
528
|
+
|
467
529
|
it "should optionally pass the deflate-encoded response body progressively" do
|
468
530
|
EventMachine.run {
|
469
531
|
body = ''
|
@@ -657,8 +719,8 @@ describe EventMachine::HttpRequest do
|
|
657
719
|
http = EventMachine::MockHttpRequest.new('http://www.google.ca/').get
|
658
720
|
http.errback { fail }
|
659
721
|
http.callback {
|
660
|
-
c1 = "PREF=ID=
|
661
|
-
c2 = "NID=
|
722
|
+
c1 = "PREF=ID=11955ae9690fd292:TM=1281823106:LM=1281823106:S=wHdloFqGQ_OLSE92; expires=Mon, 13-Aug-2012 21:58:26 GMT; path=/; domain=.google.ca"
|
723
|
+
c2 = "NID=37=USTdOsxOSMbLjphkJ3S5Ueua3Yc23COXuK_pbztcHx7JoyhomwQySrvebCf3_u8eyrBiLWssVzaZcEOiKGEJbNdy8lRhnq_mfrdz693LaMjNPh__ccW4sgn1ZO6nQltE; expires=Sun, 13-Feb-2011 21:58:26 GMT; path=/; domain=.google.ca; HttpOnly"
|
662
724
|
http.response_header.cookie.should == [c1, c2]
|
663
725
|
|
664
726
|
EventMachine.stop
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 2
|
8
|
-
-
|
9
|
-
version: 0.2.
|
8
|
+
- 12
|
9
|
+
version: 0.2.12
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Ilya Grigorik
|
@@ -14,13 +14,14 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-
|
17
|
+
date: 2010-09-12 00:00:00 -04:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: eventmachine
|
22
22
|
prerelease: false
|
23
23
|
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
24
25
|
requirements:
|
25
26
|
- - ">="
|
26
27
|
- !ruby/object:Gem::Version
|
@@ -35,6 +36,7 @@ dependencies:
|
|
35
36
|
name: addressable
|
36
37
|
prerelease: false
|
37
38
|
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
38
40
|
requirements:
|
39
41
|
- - ">="
|
40
42
|
- !ruby/object:Gem::Version
|
@@ -57,6 +59,7 @@ extra_rdoc_files:
|
|
57
59
|
- README.md
|
58
60
|
files:
|
59
61
|
- .gitignore
|
62
|
+
- Changelog.md
|
60
63
|
- LICENSE
|
61
64
|
- README.md
|
62
65
|
- Rakefile
|
@@ -104,6 +107,7 @@ rdoc_options:
|
|
104
107
|
require_paths:
|
105
108
|
- lib
|
106
109
|
required_ruby_version: !ruby/object:Gem::Requirement
|
110
|
+
none: false
|
107
111
|
requirements:
|
108
112
|
- - ">="
|
109
113
|
- !ruby/object:Gem::Version
|
@@ -113,6 +117,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
113
117
|
- 6
|
114
118
|
version: 1.8.6
|
115
119
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
120
|
+
none: false
|
116
121
|
requirements:
|
117
122
|
- - ">="
|
118
123
|
- !ruby/object:Gem::Version
|
@@ -122,7 +127,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
122
127
|
requirements: []
|
123
128
|
|
124
129
|
rubyforge_project: em-http-request
|
125
|
-
rubygems_version: 1.3.
|
130
|
+
rubygems_version: 1.3.7
|
126
131
|
signing_key:
|
127
132
|
specification_version: 3
|
128
133
|
summary: EventMachine based, async HTTP Request interface
|