em-http-request-samesite 1.1.7
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.
- 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
|