em-http-request-samesite 1.1.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gemtest +0 -0
- data/.gitignore +9 -0
- data/.rspec +0 -0
- data/.travis.yml +7 -0
- data/Changelog.md +68 -0
- data/Gemfile +14 -0
- data/README.md +63 -0
- data/Rakefile +10 -0
- data/benchmarks/clients.rb +170 -0
- data/benchmarks/em-excon.rb +87 -0
- data/benchmarks/em-profile.gif +0 -0
- data/benchmarks/em-profile.txt +65 -0
- data/benchmarks/server.rb +48 -0
- data/em-http-request.gemspec +32 -0
- data/examples/.gitignore +1 -0
- data/examples/digest_auth/client.rb +25 -0
- data/examples/digest_auth/server.rb +28 -0
- data/examples/fetch.rb +30 -0
- data/examples/fibered-http.rb +51 -0
- data/examples/multi.rb +25 -0
- data/examples/oauth-tweet.rb +35 -0
- data/examples/socks5.rb +23 -0
- data/lib/em-http-request.rb +1 -0
- data/lib/em-http.rb +20 -0
- data/lib/em-http/client.rb +341 -0
- data/lib/em-http/core_ext/bytesize.rb +6 -0
- data/lib/em-http/decoders.rb +252 -0
- data/lib/em-http/http_client_options.rb +49 -0
- data/lib/em-http/http_connection.rb +321 -0
- data/lib/em-http/http_connection_options.rb +70 -0
- data/lib/em-http/http_encoding.rb +149 -0
- data/lib/em-http/http_header.rb +83 -0
- data/lib/em-http/http_status_codes.rb +57 -0
- data/lib/em-http/middleware/digest_auth.rb +112 -0
- data/lib/em-http/middleware/json_response.rb +15 -0
- data/lib/em-http/middleware/oauth.rb +40 -0
- data/lib/em-http/middleware/oauth2.rb +28 -0
- data/lib/em-http/multi.rb +57 -0
- data/lib/em-http/request.rb +23 -0
- data/lib/em-http/version.rb +5 -0
- data/lib/em/io_streamer.rb +49 -0
- data/spec/client_fiber_spec.rb +23 -0
- data/spec/client_spec.rb +1000 -0
- data/spec/digest_auth_spec.rb +48 -0
- data/spec/dns_spec.rb +41 -0
- data/spec/encoding_spec.rb +49 -0
- data/spec/external_spec.rb +150 -0
- data/spec/fixtures/google.ca +16 -0
- data/spec/fixtures/gzip-sample.gz +0 -0
- data/spec/gzip_spec.rb +91 -0
- data/spec/helper.rb +31 -0
- data/spec/http_proxy_spec.rb +268 -0
- data/spec/middleware/oauth2_spec.rb +15 -0
- data/spec/middleware_spec.rb +143 -0
- data/spec/multi_spec.rb +104 -0
- data/spec/pipelining_spec.rb +66 -0
- data/spec/redirect_spec.rb +430 -0
- data/spec/socksify_proxy_spec.rb +60 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/ssl_spec.rb +71 -0
- data/spec/stallion.rb +334 -0
- data/spec/stub_server.rb +45 -0
- metadata +265 -0
@@ -0,0 +1,252 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
##
|
5
|
+
# Provides a unified callback interface to decompression libraries.
|
6
|
+
module EventMachine::HttpDecoders
|
7
|
+
|
8
|
+
class DecoderError < StandardError
|
9
|
+
end
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def accepted_encodings
|
13
|
+
DECODERS.inject([]) { |r, d| r + d.encoding_names }
|
14
|
+
end
|
15
|
+
|
16
|
+
def decoder_for_encoding(encoding)
|
17
|
+
DECODERS.each { |d|
|
18
|
+
return d if d.encoding_names.include? encoding
|
19
|
+
}
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Base
|
25
|
+
def self.encoding_names
|
26
|
+
name = to_s.split('::').last.downcase
|
27
|
+
[name]
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# chunk_callback:: [Block] To handle a decompressed chunk
|
32
|
+
def initialize(&chunk_callback)
|
33
|
+
@chunk_callback = chunk_callback
|
34
|
+
end
|
35
|
+
|
36
|
+
def <<(compressed)
|
37
|
+
return unless compressed && compressed.size > 0
|
38
|
+
|
39
|
+
decompressed = decompress(compressed)
|
40
|
+
receive_decompressed decompressed
|
41
|
+
end
|
42
|
+
|
43
|
+
def finalize!
|
44
|
+
decompressed = finalize
|
45
|
+
receive_decompressed decompressed
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def receive_decompressed(decompressed)
|
51
|
+
if decompressed && decompressed.size > 0
|
52
|
+
@chunk_callback.call(decompressed)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
|
58
|
+
##
|
59
|
+
# Must return a part of decompressed
|
60
|
+
def decompress(compressed)
|
61
|
+
nil
|
62
|
+
end
|
63
|
+
|
64
|
+
##
|
65
|
+
# May return last part
|
66
|
+
def finalize
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class Deflate < Base
|
72
|
+
def decompress(compressed)
|
73
|
+
begin
|
74
|
+
@zstream ||= Zlib::Inflate.new(-Zlib::MAX_WBITS)
|
75
|
+
@zstream.inflate(compressed)
|
76
|
+
rescue Zlib::Error
|
77
|
+
raise DecoderError
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def finalize
|
82
|
+
return nil unless @zstream
|
83
|
+
|
84
|
+
begin
|
85
|
+
r = @zstream.inflate(nil)
|
86
|
+
@zstream.close
|
87
|
+
r
|
88
|
+
rescue Zlib::Error
|
89
|
+
raise DecoderError
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
##
|
95
|
+
# Partial implementation of RFC 1952 to extract the deflate stream from a gzip file
|
96
|
+
class GZipHeader
|
97
|
+
def initialize
|
98
|
+
@state = :begin
|
99
|
+
@data = ""
|
100
|
+
@pos = 0
|
101
|
+
end
|
102
|
+
|
103
|
+
def finished?
|
104
|
+
@state == :finish
|
105
|
+
end
|
106
|
+
|
107
|
+
def read(n, buffer)
|
108
|
+
if (@pos + n) <= @data.size
|
109
|
+
buffer << @data[@pos..(@pos + n - 1)]
|
110
|
+
@pos += n
|
111
|
+
return true
|
112
|
+
else
|
113
|
+
return false
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def readbyte
|
118
|
+
if (@pos + 1) <= @data.size
|
119
|
+
@pos += 1
|
120
|
+
@data.getbyte(@pos - 1)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def eof?
|
125
|
+
@pos >= @data.size
|
126
|
+
end
|
127
|
+
|
128
|
+
def extract_stream(compressed)
|
129
|
+
@data << compressed
|
130
|
+
|
131
|
+
while !eof? && !finished?
|
132
|
+
buffer = ""
|
133
|
+
|
134
|
+
case @state
|
135
|
+
when :begin
|
136
|
+
break if !read(10, buffer)
|
137
|
+
|
138
|
+
if buffer.getbyte(0) != 0x1f || buffer.getbyte(1) != 0x8b
|
139
|
+
raise DecoderError.new("magic header not found")
|
140
|
+
end
|
141
|
+
|
142
|
+
if buffer.getbyte(2) != 0x08
|
143
|
+
raise DecoderError.new("unknown compression method")
|
144
|
+
end
|
145
|
+
|
146
|
+
@flags = buffer.getbyte(3)
|
147
|
+
if (@flags & 0xe0).nonzero?
|
148
|
+
raise DecoderError.new("unknown header flags set")
|
149
|
+
end
|
150
|
+
|
151
|
+
# We don't care about these values, I'm leaving the code for reference
|
152
|
+
# @time = buffer[4..7].unpack("V")[0] # little-endian uint32
|
153
|
+
# @extra_flags = buffer.getbyte(8)
|
154
|
+
# @os = buffer.getbyte(9)
|
155
|
+
|
156
|
+
@state = :extra_length
|
157
|
+
|
158
|
+
when :extra_length
|
159
|
+
if (@flags & 0x04).nonzero?
|
160
|
+
break if !read(2, buffer)
|
161
|
+
@extra_length = buffer.unpack("v")[0] # little-endian uint16
|
162
|
+
@state = :extra
|
163
|
+
else
|
164
|
+
@state = :extra
|
165
|
+
end
|
166
|
+
|
167
|
+
when :extra
|
168
|
+
if (@flags & 0x04).nonzero?
|
169
|
+
break if read(@extra_length, buffer)
|
170
|
+
@state = :name
|
171
|
+
else
|
172
|
+
@state = :name
|
173
|
+
end
|
174
|
+
|
175
|
+
when :name
|
176
|
+
if (@flags & 0x08).nonzero?
|
177
|
+
while !(buffer = readbyte).nil?
|
178
|
+
if buffer == 0
|
179
|
+
@state = :comment
|
180
|
+
break
|
181
|
+
end
|
182
|
+
end
|
183
|
+
else
|
184
|
+
@state = :comment
|
185
|
+
end
|
186
|
+
|
187
|
+
when :comment
|
188
|
+
if (@flags & 0x10).nonzero?
|
189
|
+
while !(buffer = readbyte).nil?
|
190
|
+
if buffer == 0
|
191
|
+
@state = :hcrc
|
192
|
+
break
|
193
|
+
end
|
194
|
+
end
|
195
|
+
else
|
196
|
+
@state = :hcrc
|
197
|
+
end
|
198
|
+
|
199
|
+
when :hcrc
|
200
|
+
if (@flags & 0x02).nonzero?
|
201
|
+
break if !read(2, buffer)
|
202
|
+
@state = :finish
|
203
|
+
else
|
204
|
+
@state = :finish
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
if finished?
|
210
|
+
compressed[(@pos - (@data.length - compressed.length))..-1]
|
211
|
+
else
|
212
|
+
""
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
class GZip < Base
|
218
|
+
def self.encoding_names
|
219
|
+
%w(gzip compressed)
|
220
|
+
end
|
221
|
+
|
222
|
+
def decompress(compressed)
|
223
|
+
@header ||= GZipHeader.new
|
224
|
+
if !@header.finished?
|
225
|
+
compressed = @header.extract_stream(compressed)
|
226
|
+
end
|
227
|
+
|
228
|
+
@zstream ||= Zlib::Inflate.new(-Zlib::MAX_WBITS)
|
229
|
+
@zstream.inflate(compressed)
|
230
|
+
rescue Zlib::Error
|
231
|
+
raise DecoderError
|
232
|
+
end
|
233
|
+
|
234
|
+
def finalize
|
235
|
+
if @zstream
|
236
|
+
if !@zstream.finished?
|
237
|
+
r = @zstream.finish
|
238
|
+
end
|
239
|
+
@zstream.close
|
240
|
+
r
|
241
|
+
else
|
242
|
+
nil
|
243
|
+
end
|
244
|
+
rescue Zlib::Error
|
245
|
+
raise DecoderError
|
246
|
+
end
|
247
|
+
|
248
|
+
end
|
249
|
+
|
250
|
+
DECODERS = [Deflate, GZip]
|
251
|
+
|
252
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class HttpClientOptions
|
2
|
+
attr_reader :uri, :method, :host, :port
|
3
|
+
attr_reader :headers, :file, :body, :query, :path
|
4
|
+
attr_reader :keepalive, :pass_cookies, :decoding, :compressed
|
5
|
+
|
6
|
+
attr_accessor :followed, :redirects
|
7
|
+
|
8
|
+
def initialize(uri, options, method)
|
9
|
+
@keepalive = options[:keepalive] || false # default to single request per connection
|
10
|
+
@redirects = options[:redirects] ||= 0 # default number of redirects to follow
|
11
|
+
@followed = options[:followed] ||= 0 # keep track of number of followed requests
|
12
|
+
|
13
|
+
@method = method.to_s.upcase
|
14
|
+
@headers = options[:head] || {}
|
15
|
+
|
16
|
+
@file = options[:file]
|
17
|
+
@body = options[:body]
|
18
|
+
|
19
|
+
@pass_cookies = options.fetch(:pass_cookies, true) # pass cookies between redirects
|
20
|
+
@decoding = options.fetch(:decoding, true) # auto-decode compressed response
|
21
|
+
@compressed = options.fetch(:compressed, true) # auto-negotiated compressed response
|
22
|
+
|
23
|
+
set_uri(uri, options[:path], options[:query])
|
24
|
+
end
|
25
|
+
|
26
|
+
def follow_redirect?; @followed < @redirects; end
|
27
|
+
def ssl?; @uri.scheme == "https" || @uri.port == 443; end
|
28
|
+
def no_body?; @method == "HEAD"; end
|
29
|
+
|
30
|
+
def set_uri(uri, path = nil, query = nil)
|
31
|
+
uri = uri.kind_of?(Addressable::URI) ? uri : Addressable::URI::parse(uri.to_s)
|
32
|
+
uri.path = path if path
|
33
|
+
uri.path = '/' if uri.path.empty?
|
34
|
+
|
35
|
+
@uri = uri
|
36
|
+
@path = uri.path
|
37
|
+
@host = uri.hostname
|
38
|
+
@port = uri.port
|
39
|
+
@query = query
|
40
|
+
|
41
|
+
# Make sure the ports are set as Addressable::URI doesn't
|
42
|
+
# set the port if it isn't there
|
43
|
+
if @port.nil?
|
44
|
+
@port = @uri.scheme == "https" ? 443 : 80
|
45
|
+
end
|
46
|
+
|
47
|
+
uri
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,321 @@
|
|
1
|
+
require 'em/io_streamer'
|
2
|
+
|
3
|
+
module EventMachine
|
4
|
+
|
5
|
+
module HTTPMethods
|
6
|
+
def get options = {}, &blk; setup_request(:get, options, &blk); end
|
7
|
+
def head options = {}, &blk; setup_request(:head, options, &blk); end
|
8
|
+
def delete options = {}, &blk; setup_request(:delete, options, &blk); end
|
9
|
+
def put options = {}, &blk; setup_request(:put, options, &blk); end
|
10
|
+
def post options = {}, &blk; setup_request(:post, options, &blk); end
|
11
|
+
def patch options = {}, &blk; setup_request(:patch, options, &blk); end
|
12
|
+
def options options = {}, &blk; setup_request(:options, options, &blk); end
|
13
|
+
end
|
14
|
+
|
15
|
+
class HttpStubConnection < Connection
|
16
|
+
include Deferrable
|
17
|
+
attr_reader :parent
|
18
|
+
|
19
|
+
def parent=(p)
|
20
|
+
@parent = p
|
21
|
+
@parent.conn = self
|
22
|
+
end
|
23
|
+
|
24
|
+
def receive_data(data)
|
25
|
+
begin
|
26
|
+
@parent.receive_data data
|
27
|
+
rescue EventMachine::Connectify::CONNECTError => e
|
28
|
+
@parent.close(e.message)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def connection_completed
|
33
|
+
@parent.connection_completed
|
34
|
+
end
|
35
|
+
|
36
|
+
def unbind(reason=nil)
|
37
|
+
@parent.unbind(reason)
|
38
|
+
end
|
39
|
+
|
40
|
+
# TLS verification support, original implementation by Mislav Marohnić
|
41
|
+
# https://github.com/lostisland/faraday/blob/63cf47c95b573539f047c729bd9ad67560bc83ff/lib/faraday/adapter/em_http_ssl_patch.rb
|
42
|
+
def ssl_verify_peer(cert_string)
|
43
|
+
cert = nil
|
44
|
+
begin
|
45
|
+
cert = OpenSSL::X509::Certificate.new(cert_string)
|
46
|
+
rescue OpenSSL::X509::CertificateError
|
47
|
+
return false
|
48
|
+
end
|
49
|
+
|
50
|
+
@last_seen_cert = cert
|
51
|
+
|
52
|
+
if certificate_store.verify(@last_seen_cert)
|
53
|
+
begin
|
54
|
+
certificate_store.add_cert(@last_seen_cert)
|
55
|
+
rescue OpenSSL::X509::StoreError => e
|
56
|
+
raise e unless e.message == 'cert already in hash table'
|
57
|
+
end
|
58
|
+
true
|
59
|
+
else
|
60
|
+
raise OpenSSL::SSL::SSLError.new(%(unable to verify the server certificate for "#{host}"))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def ssl_handshake_completed
|
65
|
+
unless verify_peer?
|
66
|
+
warn "[WARNING; em-http-request] TLS hostname validation is disabled (use 'tls: {verify_peer: true}'), see" +
|
67
|
+
" CVE-2020-13482 and https://github.com/igrigorik/em-http-request/issues/339 for details" unless parent.connopts.tls.has_key?(:verify_peer)
|
68
|
+
return true
|
69
|
+
end
|
70
|
+
|
71
|
+
unless OpenSSL::SSL.verify_certificate_identity(@last_seen_cert, host)
|
72
|
+
raise OpenSSL::SSL::SSLError.new(%(host "#{host}" does not match the server certificate))
|
73
|
+
else
|
74
|
+
true
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def verify_peer?
|
79
|
+
parent.connopts.tls[:verify_peer]
|
80
|
+
end
|
81
|
+
|
82
|
+
def host
|
83
|
+
parent.connopts.host
|
84
|
+
end
|
85
|
+
|
86
|
+
def certificate_store
|
87
|
+
@certificate_store ||= begin
|
88
|
+
store = OpenSSL::X509::Store.new
|
89
|
+
store.set_default_paths
|
90
|
+
ca_file = parent.connopts.tls[:cert_chain_file]
|
91
|
+
store.add_file(ca_file) if ca_file
|
92
|
+
store
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class HttpConnection
|
98
|
+
include HTTPMethods
|
99
|
+
include Socksify
|
100
|
+
include Connectify
|
101
|
+
|
102
|
+
attr_reader :deferred, :conn
|
103
|
+
attr_accessor :error, :connopts, :uri
|
104
|
+
|
105
|
+
def initialize
|
106
|
+
@deferred = true
|
107
|
+
@middleware = []
|
108
|
+
end
|
109
|
+
|
110
|
+
def conn=(c)
|
111
|
+
@conn = c
|
112
|
+
@deferred = false
|
113
|
+
end
|
114
|
+
|
115
|
+
def activate_connection(client)
|
116
|
+
begin
|
117
|
+
EventMachine.bind_connect(@connopts.bind, @connopts.bind_port,
|
118
|
+
@connopts.host, @connopts.port,
|
119
|
+
HttpStubConnection) do |conn|
|
120
|
+
post_init
|
121
|
+
|
122
|
+
@deferred = false
|
123
|
+
@conn = conn
|
124
|
+
|
125
|
+
conn.parent = self
|
126
|
+
conn.pending_connect_timeout = @connopts.connect_timeout
|
127
|
+
conn.comm_inactivity_timeout = @connopts.inactivity_timeout
|
128
|
+
end
|
129
|
+
|
130
|
+
finalize_request(client)
|
131
|
+
rescue EventMachine::ConnectionError => e
|
132
|
+
#
|
133
|
+
# Currently, this can only fire on initial connection setup
|
134
|
+
# since #connect is a synchronous method. Hence, rescue the exception,
|
135
|
+
# and return a failed deferred which fail any client request at next
|
136
|
+
# tick. We fail at next tick to keep a consistent API when the newly
|
137
|
+
# created HttpClient is failed. This approach has the advantage to
|
138
|
+
# remove a state check of @deferred_status after creating a new
|
139
|
+
# HttpRequest. The drawback is that users may setup a callback which we
|
140
|
+
# know won't be used.
|
141
|
+
#
|
142
|
+
# Once there is async-DNS, then we'll iterate over the outstanding
|
143
|
+
# client requests and fail them in order.
|
144
|
+
#
|
145
|
+
# Net outcome: failed connection will invoke the same ConnectionError
|
146
|
+
# message on the connection deferred, and on the client deferred.
|
147
|
+
#
|
148
|
+
EM.next_tick{client.close(e.message)}
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def setup_request(method, options = {}, c = nil)
|
153
|
+
c ||= HttpClient.new(self, HttpClientOptions.new(@uri, options, method))
|
154
|
+
@deferred ? activate_connection(c) : finalize_request(c)
|
155
|
+
c
|
156
|
+
end
|
157
|
+
|
158
|
+
def finalize_request(c)
|
159
|
+
@conn.callback { c.connection_completed }
|
160
|
+
|
161
|
+
middleware.each do |m|
|
162
|
+
c.callback(&m.method(:response)) if m.respond_to?(:response)
|
163
|
+
end
|
164
|
+
|
165
|
+
@clients.push c
|
166
|
+
end
|
167
|
+
|
168
|
+
def middleware
|
169
|
+
[HttpRequest.middleware, @middleware].flatten
|
170
|
+
end
|
171
|
+
|
172
|
+
def post_init
|
173
|
+
@clients = []
|
174
|
+
@pending = []
|
175
|
+
|
176
|
+
@p = Http::Parser.new
|
177
|
+
@p.header_value_type = :mixed
|
178
|
+
@p.on_headers_complete = proc do |h|
|
179
|
+
if client
|
180
|
+
if @p.status_code == 100
|
181
|
+
client.send_request_body
|
182
|
+
@p.reset!
|
183
|
+
else
|
184
|
+
client.parse_response_header(h, @p.http_version, @p.status_code)
|
185
|
+
:reset if client.req.no_body?
|
186
|
+
end
|
187
|
+
else
|
188
|
+
# if we receive unexpected data without a pending client request
|
189
|
+
# reset the parser to avoid firing any further callbacks and close
|
190
|
+
# the connection because we're processing invalid HTTP
|
191
|
+
@p.reset!
|
192
|
+
unbind
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
@p.on_body = proc do |b|
|
197
|
+
client.on_body_data(b)
|
198
|
+
end
|
199
|
+
|
200
|
+
@p.on_message_complete = proc do
|
201
|
+
if !client.continue?
|
202
|
+
c = @clients.shift
|
203
|
+
c.state = :finished
|
204
|
+
c.on_request_complete
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def use(klass, *args, &block)
|
210
|
+
@middleware << klass.new(*args, &block)
|
211
|
+
end
|
212
|
+
|
213
|
+
def peer
|
214
|
+
Socket.unpack_sockaddr_in(@peer)[1] rescue nil
|
215
|
+
end
|
216
|
+
|
217
|
+
def receive_data(data)
|
218
|
+
begin
|
219
|
+
@p << data
|
220
|
+
rescue HTTP::Parser::Error => e
|
221
|
+
c = @clients.shift
|
222
|
+
c.nil? ? unbind(e.message) : c.on_error(e.message)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def connection_completed
|
227
|
+
@peer = @conn.get_peername
|
228
|
+
|
229
|
+
if @connopts.socks_proxy?
|
230
|
+
socksify(client.req.uri.hostname, client.req.uri.inferred_port, *@connopts.proxy[:authorization]) { start }
|
231
|
+
elsif @connopts.connect_proxy?
|
232
|
+
connectify(client.req.uri.hostname, client.req.uri.inferred_port, *@connopts.proxy[:authorization]) { start }
|
233
|
+
else
|
234
|
+
start
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def start
|
239
|
+
@conn.start_tls(@connopts.tls) if client && client.req.ssl?
|
240
|
+
@conn.succeed
|
241
|
+
end
|
242
|
+
|
243
|
+
def redirect(client, new_location)
|
244
|
+
old_location = client.req.uri
|
245
|
+
new_location = client.req.set_uri(new_location)
|
246
|
+
|
247
|
+
if client.req.keepalive
|
248
|
+
# Application requested a keep-alive connection but one of the requests
|
249
|
+
# hits a cross-origin redirect. We need to open a new connection and
|
250
|
+
# let both connections proceed simultaneously.
|
251
|
+
if old_location.origin != new_location.origin
|
252
|
+
conn = HttpConnection.new
|
253
|
+
client.conn = conn
|
254
|
+
conn.connopts = @connopts
|
255
|
+
conn.connopts.https = new_location.scheme == "https"
|
256
|
+
conn.uri = client.req.uri
|
257
|
+
conn.activate_connection(client)
|
258
|
+
|
259
|
+
# If the redirect is a same-origin redirect on a keep-alive request
|
260
|
+
# then immidiately dispatch the request over existing connection.
|
261
|
+
else
|
262
|
+
@clients.push client
|
263
|
+
client.connection_completed
|
264
|
+
end
|
265
|
+
else
|
266
|
+
# If connection is not keep-alive the unbind will fire and we'll
|
267
|
+
# reconnect using the same connection object.
|
268
|
+
@pending.push client
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def unbind(reason = nil)
|
273
|
+
@clients.map { |c| c.unbind(reason) }
|
274
|
+
|
275
|
+
if r = @pending.shift
|
276
|
+
@clients.push r
|
277
|
+
|
278
|
+
r.reset!
|
279
|
+
@p.reset!
|
280
|
+
|
281
|
+
begin
|
282
|
+
@conn.set_deferred_status :unknown
|
283
|
+
|
284
|
+
if @connopts.proxy
|
285
|
+
@conn.reconnect(@connopts.host, @connopts.port)
|
286
|
+
else
|
287
|
+
@conn.reconnect(r.req.host, r.req.port)
|
288
|
+
end
|
289
|
+
|
290
|
+
@conn.pending_connect_timeout = @connopts.connect_timeout
|
291
|
+
@conn.comm_inactivity_timeout = @connopts.inactivity_timeout
|
292
|
+
@conn.callback { r.connection_completed }
|
293
|
+
rescue EventMachine::ConnectionError => e
|
294
|
+
@clients.pop.close(e.message)
|
295
|
+
end
|
296
|
+
else
|
297
|
+
@deferred = true
|
298
|
+
@conn.close_connection
|
299
|
+
end
|
300
|
+
end
|
301
|
+
alias :close :unbind
|
302
|
+
|
303
|
+
def send_data(data)
|
304
|
+
@conn.send_data data
|
305
|
+
end
|
306
|
+
|
307
|
+
def stream_file_data(filename, args = {})
|
308
|
+
@conn.stream_file_data filename, args
|
309
|
+
end
|
310
|
+
|
311
|
+
def stream_data(io, opts = {})
|
312
|
+
EventMachine::IOStreamer.new(self, io, opts)
|
313
|
+
end
|
314
|
+
|
315
|
+
private
|
316
|
+
|
317
|
+
def client
|
318
|
+
@clients.first
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|