em-http-request 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of em-http-request might be problematic. Click here for more details.
- data/.gitignore +2 -0
- data/README.md +4 -2
- data/em-http-request.gemspec +33 -33
- data/examples/fibered-http.rb +51 -47
- data/lib/em-http/client.rb +307 -275
- data/lib/em-http/http_client_options.rb +1 -0
- data/lib/em-http/http_connection.rb +199 -194
- data/lib/em-http/http_connection_options.rb +6 -1
- data/lib/em-http/http_encoding.rb +2 -2
- data/lib/em-http/http_header.rb +4 -0
- data/lib/em-http/http_status_codes.rb +51 -37
- data/lib/em-http/multi.rb +4 -2
- data/lib/em-http/version.rb +5 -5
- data/spec/client_fiber_spec.rb +21 -0
- data/spec/client_spec.rb +734 -669
- data/spec/encoding_spec.rb +11 -2
- data/spec/external_spec.rb +128 -127
- data/spec/helper.rb +2 -2
- data/spec/multi_spec.rb +9 -1
- data/spec/pipelining_spec.rb +66 -38
- data/spec/redirect_spec.rb +55 -3
- data/spec/socksify_proxy_spec.rb +24 -24
- data/spec/stallion.rb +273 -270
- data/spec/stub_server.rb +25 -5
- metadata +34 -31
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -46,9 +46,11 @@ Several higher-order Ruby projects have incorporated em-http and other Ruby HTTP
|
|
46
46
|
|
47
47
|
## Other libraries & applications using EM-HTTP
|
48
48
|
|
49
|
+
- [VMWare CloudFoundry](https://github.com/cloudfoundry) - The open platform-as-a-service project
|
50
|
+
- [PubSubHubbub](https://github.com/igrigorik/PubSubHubbub) - Asynchronous PubSubHubbub ruby client
|
51
|
+
- [em-net-http](https://github.com/jfairbairn/em-net-http) - Monkeypatching Net::HTTP to play ball with EventMachine
|
49
52
|
- [chirpstream](https://github.com/joshbuddy/chirpstream) - EM client for Twitters Chirpstream API
|
50
53
|
- [rsolr-async](https://github.com/mwmitchell/rsolr-async) - An asynchronus connection adapter for RSolr
|
51
|
-
- [PubSubHubbub](https://github.com/igrigorik/PubSubHubbub) - Asynchronous PubSubHubbub ruby client
|
52
54
|
- [Firering](https://github.com/EmmanuelOga/firering) - Eventmachine powered Campfire API
|
53
55
|
- [RDaneel](https://github.com/hasmanydevelopers/RDaneel) - Ruby crawler which respects robots.txt
|
54
56
|
- [em-eventsource](https://github.com/AF83/em-eventsource) - EventSource client for EventMachine
|
@@ -57,4 +59,4 @@ Several higher-order Ruby projects have incorporated em-http and other Ruby HTTP
|
|
57
59
|
|
58
60
|
### License
|
59
61
|
|
60
|
-
(MIT License) - Copyright (c) 2011 Ilya Grigorik
|
62
|
+
(MIT License) - Copyright (c) 2011 Ilya Grigorik
|
data/em-http-request.gemspec
CHANGED
@@ -1,33 +1,33 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
|
-
$:.push File.expand_path("../lib", __FILE__)
|
3
|
-
require "em-http/version"
|
4
|
-
|
5
|
-
Gem::Specification.new do |s|
|
6
|
-
s.name = "em-http-request"
|
7
|
-
s.version = EventMachine::HttpRequest::VERSION
|
8
|
-
|
9
|
-
s.platform = Gem::Platform::RUBY
|
10
|
-
s.authors = ["Ilya Grigorik"]
|
11
|
-
s.email = ["ilya@igvita.com"]
|
12
|
-
s.homepage = "http://github.com/igrigorik/em-http-request"
|
13
|
-
s.summary = "EventMachine based, async HTTP Request client"
|
14
|
-
s.description = s.summary
|
15
|
-
s.rubyforge_project = "em-http-request"
|
16
|
-
|
17
|
-
s.add_dependency "eventmachine", ">= 1.0.0.beta.
|
18
|
-
s.add_dependency "addressable", ">= 2.2.3"
|
19
|
-
s.add_dependency "http_parser.rb", ">= 0.5.
|
20
|
-
s.add_dependency "em-socksify"
|
21
|
-
|
22
|
-
|
23
|
-
s.add_development_dependency "
|
24
|
-
s.add_development_dependency "
|
25
|
-
s.add_development_dependency "
|
26
|
-
s.add_development_dependency "
|
27
|
-
s.add_development_dependency "mongrel", "~> 1.2.0.pre2"
|
28
|
-
|
29
|
-
s.files = `git ls-files`.split("\n")
|
30
|
-
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
31
|
-
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
32
|
-
s.require_paths = ["lib"]
|
33
|
-
end
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "em-http/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "em-http-request"
|
7
|
+
s.version = EventMachine::HttpRequest::VERSION
|
8
|
+
|
9
|
+
s.platform = Gem::Platform::RUBY
|
10
|
+
s.authors = ["Ilya Grigorik"]
|
11
|
+
s.email = ["ilya@igvita.com"]
|
12
|
+
s.homepage = "http://github.com/igrigorik/em-http-request"
|
13
|
+
s.summary = "EventMachine based, async HTTP Request client"
|
14
|
+
s.description = s.summary
|
15
|
+
s.rubyforge_project = "em-http-request"
|
16
|
+
|
17
|
+
s.add_dependency "eventmachine", ">= 1.0.0.beta.4"
|
18
|
+
s.add_dependency "addressable", ">= 2.2.3"
|
19
|
+
s.add_dependency "http_parser.rb", ">= 0.5.3"
|
20
|
+
s.add_dependency "em-socksify"
|
21
|
+
s.add_dependency "cookiejar"
|
22
|
+
|
23
|
+
s.add_development_dependency "rspec"
|
24
|
+
s.add_development_dependency "rake"
|
25
|
+
s.add_development_dependency "rack"
|
26
|
+
s.add_development_dependency "yajl-ruby"
|
27
|
+
s.add_development_dependency "mongrel", "~> 1.2.0.pre2"
|
28
|
+
|
29
|
+
s.files = `git ls-files`.split("\n")
|
30
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
31
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
32
|
+
s.require_paths = ["lib"]
|
33
|
+
end
|
data/examples/fibered-http.rb
CHANGED
@@ -1,47 +1,51 @@
|
|
1
|
-
$: << 'lib' << '../lib'
|
2
|
-
|
3
|
-
require 'eventmachine'
|
4
|
-
require 'em-http'
|
5
|
-
require 'fiber'
|
6
|
-
|
7
|
-
# Using Fibers in Ruby 1.9 to simulate blocking IO / IO scheduling
|
8
|
-
# while using the async EventMachine API's
|
9
|
-
|
10
|
-
def async_fetch(url)
|
11
|
-
f = Fiber.current
|
12
|
-
http = EventMachine::HttpRequest.new(url).get :timeout => 10
|
13
|
-
|
14
|
-
http.callback { f.resume(http) }
|
15
|
-
http.errback { f.resume(http) }
|
16
|
-
|
17
|
-
Fiber.yield
|
18
|
-
|
19
|
-
if http.error
|
20
|
-
p [:HTTP_ERROR, http.error]
|
21
|
-
end
|
22
|
-
|
23
|
-
http
|
24
|
-
end
|
25
|
-
|
26
|
-
EventMachine.run do
|
27
|
-
Fiber.new{
|
28
|
-
|
29
|
-
puts "Setting up HTTP request #1"
|
30
|
-
data = async_fetch('http://0.0.0.0/')
|
31
|
-
puts "Fetched page #1: #{data.response_header.status}"
|
32
|
-
|
33
|
-
puts "Setting up HTTP request #2"
|
34
|
-
data = async_fetch('http://www.yahoo.com/')
|
35
|
-
puts "Fetched page #2: #{data.response_header.status}"
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
#
|
1
|
+
$: << 'lib' << '../lib'
|
2
|
+
|
3
|
+
require 'eventmachine'
|
4
|
+
require 'em-http'
|
5
|
+
require 'fiber'
|
6
|
+
|
7
|
+
# Using Fibers in Ruby 1.9 to simulate blocking IO / IO scheduling
|
8
|
+
# while using the async EventMachine API's
|
9
|
+
|
10
|
+
def async_fetch(url)
|
11
|
+
f = Fiber.current
|
12
|
+
http = EventMachine::HttpRequest.new(url).get :timeout => 10
|
13
|
+
|
14
|
+
http.callback { f.resume(http) }
|
15
|
+
http.errback { f.resume(http) }
|
16
|
+
|
17
|
+
Fiber.yield
|
18
|
+
|
19
|
+
if http.error
|
20
|
+
p [:HTTP_ERROR, http.error]
|
21
|
+
end
|
22
|
+
|
23
|
+
http
|
24
|
+
end
|
25
|
+
|
26
|
+
EventMachine.run do
|
27
|
+
Fiber.new{
|
28
|
+
|
29
|
+
puts "Setting up HTTP request #1"
|
30
|
+
data = async_fetch('http://0.0.0.0/')
|
31
|
+
puts "Fetched page #1: #{data.response_header.status}"
|
32
|
+
|
33
|
+
puts "Setting up HTTP request #2"
|
34
|
+
data = async_fetch('http://www.yahoo.com/')
|
35
|
+
puts "Fetched page #2: #{data.response_header.status}"
|
36
|
+
|
37
|
+
puts "Setting up HTTP request #3"
|
38
|
+
data = async_fetch('http://non-existing.domain/')
|
39
|
+
puts "Fetched page #3: #{data.response_header.status}"
|
40
|
+
|
41
|
+
EventMachine.stop
|
42
|
+
}.resume
|
43
|
+
end
|
44
|
+
|
45
|
+
puts "Done"
|
46
|
+
|
47
|
+
# Setting up HTTP request #1
|
48
|
+
# Fetched page #1: 302
|
49
|
+
# Setting up HTTP request #2
|
50
|
+
# Fetched page #2: 200
|
51
|
+
# Done
|
data/lib/em-http/client.rb
CHANGED
@@ -1,275 +1,307 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
@
|
29
|
-
@
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
@
|
41
|
-
@
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
def
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
end
|
73
|
-
|
74
|
-
def
|
75
|
-
@
|
76
|
-
end
|
77
|
-
|
78
|
-
def
|
79
|
-
@
|
80
|
-
end
|
81
|
-
|
82
|
-
def
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
def
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
def
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
if
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
@
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
if @
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
@
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
@
|
225
|
-
|
226
|
-
#
|
227
|
-
if
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
if
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
1
|
+
require 'cookiejar'
|
2
|
+
|
3
|
+
module EventMachine
|
4
|
+
|
5
|
+
|
6
|
+
class HttpClient
|
7
|
+
include Deferrable
|
8
|
+
include HttpEncoding
|
9
|
+
include HttpStatus
|
10
|
+
|
11
|
+
TRANSFER_ENCODING="TRANSFER_ENCODING"
|
12
|
+
CONTENT_ENCODING="CONTENT_ENCODING"
|
13
|
+
CONTENT_LENGTH="CONTENT_LENGTH"
|
14
|
+
CONTENT_TYPE="CONTENT_TYPE"
|
15
|
+
LAST_MODIFIED="LAST_MODIFIED"
|
16
|
+
KEEP_ALIVE="CONNECTION"
|
17
|
+
SET_COOKIE="SET_COOKIE"
|
18
|
+
LOCATION="LOCATION"
|
19
|
+
HOST="HOST"
|
20
|
+
ETAG="ETAG"
|
21
|
+
|
22
|
+
CRLF="\r\n"
|
23
|
+
|
24
|
+
attr_accessor :state, :response
|
25
|
+
attr_reader :response_header, :error, :content_charset, :req, :cookies
|
26
|
+
|
27
|
+
def initialize(conn, options)
|
28
|
+
@conn = conn
|
29
|
+
@req = options
|
30
|
+
|
31
|
+
@stream = nil
|
32
|
+
@headers = nil
|
33
|
+
@cookies = []
|
34
|
+
@cookiejar = CookieJar.new
|
35
|
+
|
36
|
+
reset!
|
37
|
+
end
|
38
|
+
|
39
|
+
def reset!
|
40
|
+
@response_header = HttpResponseHeader.new
|
41
|
+
@state = :response_header
|
42
|
+
|
43
|
+
@response = ''
|
44
|
+
@error = nil
|
45
|
+
@content_decoder = nil
|
46
|
+
@content_charset = nil
|
47
|
+
end
|
48
|
+
|
49
|
+
def last_effective_url; @req.uri; end
|
50
|
+
def redirects; @req.followed; end
|
51
|
+
def peer; @conn.peer; end
|
52
|
+
|
53
|
+
def connection_completed
|
54
|
+
@state = :response_header
|
55
|
+
|
56
|
+
head, body = build_request, @req.body
|
57
|
+
@conn.middleware.each do |m|
|
58
|
+
head, body = m.request(self, head, body) if m.respond_to?(:request)
|
59
|
+
end
|
60
|
+
|
61
|
+
send_request(head, body)
|
62
|
+
end
|
63
|
+
|
64
|
+
def on_request_complete
|
65
|
+
begin
|
66
|
+
@content_decoder.finalize! if @content_decoder
|
67
|
+
rescue HttpDecoders::DecoderError
|
68
|
+
on_error "Content-decoder error"
|
69
|
+
end
|
70
|
+
|
71
|
+
unbind
|
72
|
+
end
|
73
|
+
|
74
|
+
def continue?
|
75
|
+
@response_header.status == 100 && (@req.method == 'POST' || @req.method == 'PUT')
|
76
|
+
end
|
77
|
+
|
78
|
+
def finished?
|
79
|
+
@state == :finished || (@state == :body && @response_header.content_length.nil?)
|
80
|
+
end
|
81
|
+
|
82
|
+
def redirect?
|
83
|
+
@response_header.location && @req.follow_redirect?
|
84
|
+
end
|
85
|
+
|
86
|
+
def unbind(reason = nil)
|
87
|
+
if finished?
|
88
|
+
if redirect?
|
89
|
+
|
90
|
+
begin
|
91
|
+
@conn.middleware.each do |m|
|
92
|
+
m.response(self) if m.respond_to?(:response)
|
93
|
+
end
|
94
|
+
|
95
|
+
# one of the injected middlewares could have changed
|
96
|
+
# our redirect settings, check if we still want to
|
97
|
+
# follow the location header
|
98
|
+
if redirect?
|
99
|
+
@req.followed += 1
|
100
|
+
|
101
|
+
@cookies.clear
|
102
|
+
@cookies = @cookiejar.get(@response_header.location).map(&:to_s) if @req.pass_cookies
|
103
|
+
@req.set_uri(@response_header.location)
|
104
|
+
@conn.redirect(self)
|
105
|
+
else
|
106
|
+
succeed(self)
|
107
|
+
end
|
108
|
+
|
109
|
+
rescue Exception => e
|
110
|
+
on_error(e.message)
|
111
|
+
end
|
112
|
+
else
|
113
|
+
succeed(self)
|
114
|
+
end
|
115
|
+
|
116
|
+
else
|
117
|
+
on_error(reason)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def on_error(msg = nil)
|
122
|
+
@error = msg
|
123
|
+
fail(self)
|
124
|
+
end
|
125
|
+
alias :close :on_error
|
126
|
+
|
127
|
+
def stream(&blk); @stream = blk; end
|
128
|
+
def headers(&blk); @headers = blk; end
|
129
|
+
|
130
|
+
def normalize_body(body)
|
131
|
+
body.is_a?(Hash) ? form_encode_body(body) : body
|
132
|
+
end
|
133
|
+
|
134
|
+
def build_request
|
135
|
+
head = @req.headers ? munge_header_keys(@req.headers) : {}
|
136
|
+
proxy = @req.proxy
|
137
|
+
|
138
|
+
if @req.http_proxy?
|
139
|
+
head['proxy-authorization'] = @req.proxy[:authorization] if @req.proxy[:authorization]
|
140
|
+
end
|
141
|
+
|
142
|
+
# Set the cookie header if provided
|
143
|
+
if cookie = head['cookie']
|
144
|
+
@cookies << encode_cookie(cookie) if cookie
|
145
|
+
end
|
146
|
+
head['cookie'] = @cookies.compact.uniq.join("; ").squeeze(";") unless @cookies.empty?
|
147
|
+
|
148
|
+
# Set connection close unless keepalive
|
149
|
+
if !@req.keepalive
|
150
|
+
head['connection'] = 'close'
|
151
|
+
end
|
152
|
+
|
153
|
+
# Set the Host header if it hasn't been specified already
|
154
|
+
head['host'] ||= encode_host
|
155
|
+
|
156
|
+
# Set the User-Agent if it hasn't been specified
|
157
|
+
head['user-agent'] ||= "EventMachine HttpClient"
|
158
|
+
|
159
|
+
head
|
160
|
+
end
|
161
|
+
|
162
|
+
def send_request(head, body)
|
163
|
+
body = normalize_body(body)
|
164
|
+
file = @req.file
|
165
|
+
query = @req.query
|
166
|
+
|
167
|
+
# Set the Content-Length if file is given
|
168
|
+
head['content-length'] = File.size(file) if file
|
169
|
+
|
170
|
+
# Set the Content-Length if body is given,
|
171
|
+
# or we're doing an empty post or put
|
172
|
+
if body
|
173
|
+
head['content-length'] = body.bytesize
|
174
|
+
elsif @req.method == 'POST' or @req.method == 'PUT'
|
175
|
+
# wont happen if body is set and we already set content-length above
|
176
|
+
head['content-length'] = 0
|
177
|
+
end
|
178
|
+
|
179
|
+
# Set content-type header if missing and body is a Ruby hash
|
180
|
+
if not head['content-type'] and @req.body.is_a? Hash
|
181
|
+
head['content-type'] = 'application/x-www-form-urlencoded'
|
182
|
+
end
|
183
|
+
|
184
|
+
request_header ||= encode_request(@req.method, @req.uri, query, @conn.connopts.proxy)
|
185
|
+
request_header << encode_headers(head)
|
186
|
+
request_header << CRLF
|
187
|
+
@conn.send_data request_header
|
188
|
+
|
189
|
+
if body
|
190
|
+
@conn.send_data body
|
191
|
+
elsif @req.file
|
192
|
+
@conn.stream_file_data @req.file, :http_chunks => false
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def on_body_data(data)
|
197
|
+
if @content_decoder
|
198
|
+
begin
|
199
|
+
@content_decoder << data
|
200
|
+
rescue HttpDecoders::DecoderError
|
201
|
+
on_error "Content-decoder error"
|
202
|
+
end
|
203
|
+
else
|
204
|
+
on_decoded_body_data(data)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def on_decoded_body_data(data)
|
209
|
+
data.force_encoding @content_charset if @content_charset
|
210
|
+
if @stream
|
211
|
+
@stream.call(data)
|
212
|
+
else
|
213
|
+
@response << data
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def parse_response_header(header, version, status)
|
218
|
+
header.each do |key, val|
|
219
|
+
@response_header[key.upcase.gsub('-','_')] = val
|
220
|
+
end
|
221
|
+
|
222
|
+
@response_header.http_version = version.join('.')
|
223
|
+
@response_header.http_status = status
|
224
|
+
@response_header.http_reason = CODE[status] || 'unknown'
|
225
|
+
|
226
|
+
# invoke headers callback after full parse
|
227
|
+
# if one is specified by the user
|
228
|
+
@headers.call(@response_header) if @headers
|
229
|
+
|
230
|
+
unless @response_header.http_status and @response_header.http_reason
|
231
|
+
@state = :invalid
|
232
|
+
on_error "no HTTP response"
|
233
|
+
return
|
234
|
+
end
|
235
|
+
|
236
|
+
# add set-cookie's to cookie list
|
237
|
+
if @response_header.cookie && @req.pass_cookies
|
238
|
+
[@response_header.cookie].flatten.each {|cookie| @cookiejar.set(cookie, @req.uri)}
|
239
|
+
end
|
240
|
+
|
241
|
+
# correct location header - some servers will incorrectly give a relative URI
|
242
|
+
if @response_header.location
|
243
|
+
begin
|
244
|
+
location = Addressable::URI.parse(@response_header.location)
|
245
|
+
|
246
|
+
if location.relative?
|
247
|
+
location = @req.uri.join(location)
|
248
|
+
@response_header[LOCATION] = location.to_s
|
249
|
+
else
|
250
|
+
# if redirect is to an absolute url, check for correct URI structure
|
251
|
+
raise if location.host.nil?
|
252
|
+
end
|
253
|
+
|
254
|
+
rescue
|
255
|
+
on_error "Location header format error"
|
256
|
+
return
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
# Fire callbacks immediately after recieving header requests
|
261
|
+
# if the request method is HEAD. In case of a redirect, terminate
|
262
|
+
# current connection and reinitialize the process.
|
263
|
+
if @req.method == "HEAD"
|
264
|
+
@state = :finished
|
265
|
+
return
|
266
|
+
end
|
267
|
+
|
268
|
+
if @response_header.chunked_encoding?
|
269
|
+
@state = :chunk_header
|
270
|
+
elsif @response_header.content_length
|
271
|
+
@state = :body
|
272
|
+
else
|
273
|
+
@state = :body
|
274
|
+
end
|
275
|
+
|
276
|
+
if @req.decoding && decoder_class = HttpDecoders.decoder_for_encoding(response_header[CONTENT_ENCODING])
|
277
|
+
begin
|
278
|
+
@content_decoder = decoder_class.new do |s| on_decoded_body_data(s) end
|
279
|
+
rescue HttpDecoders::DecoderError
|
280
|
+
on_error "Content-decoder error"
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
# handle malformed header - Content-Type repetitions.
|
285
|
+
content_type = [response_header[CONTENT_TYPE]].flatten.first
|
286
|
+
|
287
|
+
if String.method_defined?(:force_encoding) && /;\s*charset=\s*(.+?)\s*(;|$)/.match(content_type)
|
288
|
+
@content_charset = Encoding.find($1.gsub(/^\"|\"$/, '')) rescue Encoding.default_external
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
class CookieJar
|
293
|
+
def initialize
|
294
|
+
@jar = ::CookieJar::Jar.new
|
295
|
+
end
|
296
|
+
|
297
|
+
def set string, uri
|
298
|
+
@jar.set_cookie(uri, string) rescue nil # drop invalid cookies
|
299
|
+
end
|
300
|
+
|
301
|
+
def get uri
|
302
|
+
uri = URI.parse(uri) rescue nil
|
303
|
+
uri ? @jar.get_cookies(uri) : []
|
304
|
+
end
|
305
|
+
end # CookieJar
|
306
|
+
end
|
307
|
+
end
|