em-http-request 1.0.0 → 1.0.1
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/.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
@@ -28,6 +28,7 @@ class HttpClientOptions
|
|
28
28
|
def follow_redirect?; @followed < @redirects; end
|
29
29
|
def http_proxy?; @proxy && [nil, :http].include?(@proxy[:type]); end
|
30
30
|
def ssl?; @uri.scheme == "https" || @uri.port == 443; end
|
31
|
+
def no_body?; @method == "HEAD"; end
|
31
32
|
|
32
33
|
def set_uri(uri)
|
33
34
|
uri = uri.kind_of?(Addressable::URI) ? uri : Addressable::URI::parse(uri.to_s)
|
@@ -1,194 +1,199 @@
|
|
1
|
-
module EventMachine
|
2
|
-
|
3
|
-
module HTTPMethods
|
4
|
-
def get options = {}, &blk; setup_request(:get, options, &blk); end
|
5
|
-
def head options = {}, &blk; setup_request(:head, options, &blk); end
|
6
|
-
def delete options = {}, &blk; setup_request(:delete,options, &blk); end
|
7
|
-
def put options = {}, &blk; setup_request(:put, options, &blk); end
|
8
|
-
def post options = {}, &blk; setup_request(:post, options, &blk); end
|
9
|
-
end
|
10
|
-
|
11
|
-
class HttpStubConnection < Connection
|
12
|
-
include Deferrable
|
13
|
-
attr_reader :parent
|
14
|
-
|
15
|
-
def parent=(p)
|
16
|
-
@parent = p
|
17
|
-
@parent.conn = self
|
18
|
-
end
|
19
|
-
|
20
|
-
def receive_data(data)
|
21
|
-
@parent.receive_data data
|
22
|
-
end
|
23
|
-
|
24
|
-
def connection_completed
|
25
|
-
@parent.connection_completed
|
26
|
-
end
|
27
|
-
|
28
|
-
def unbind(reason=nil)
|
29
|
-
@parent.unbind(reason)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
class HttpConnection
|
34
|
-
include HTTPMethods
|
35
|
-
include Socksify
|
36
|
-
|
37
|
-
attr_reader :deferred
|
38
|
-
attr_accessor :error, :connopts, :uri, :conn
|
39
|
-
|
40
|
-
def initialize
|
41
|
-
@deferred = true
|
42
|
-
@middleware = []
|
43
|
-
end
|
44
|
-
|
45
|
-
def conn=(c)
|
46
|
-
@conn = c
|
47
|
-
@deferred = false
|
48
|
-
end
|
49
|
-
|
50
|
-
def activate_connection(client)
|
51
|
-
begin
|
52
|
-
EventMachine.
|
53
|
-
post_init
|
54
|
-
|
55
|
-
@deferred = false
|
56
|
-
@conn = conn
|
57
|
-
|
58
|
-
conn.parent = self
|
59
|
-
conn.pending_connect_timeout = @connopts.connect_timeout
|
60
|
-
conn.comm_inactivity_timeout = @connopts.inactivity_timeout
|
61
|
-
end
|
62
|
-
|
63
|
-
finalize_request(client)
|
64
|
-
rescue EventMachine::ConnectionError => e
|
65
|
-
#
|
66
|
-
# Currently, this can only fire on initial connection setup
|
67
|
-
# since #connect is a synchronous method. Hence, rescue the
|
68
|
-
#
|
69
|
-
# fail
|
70
|
-
#
|
71
|
-
#
|
72
|
-
#
|
73
|
-
#
|
74
|
-
#
|
75
|
-
#
|
76
|
-
#
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
end
|
100
|
-
|
101
|
-
def
|
102
|
-
@
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
@
|
107
|
-
@
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
@p.
|
112
|
-
client.
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
end
|
155
|
-
|
156
|
-
def
|
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
|
-
end
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
1
|
+
module EventMachine
|
2
|
+
|
3
|
+
module HTTPMethods
|
4
|
+
def get options = {}, &blk; setup_request(:get, options, &blk); end
|
5
|
+
def head options = {}, &blk; setup_request(:head, options, &blk); end
|
6
|
+
def delete options = {}, &blk; setup_request(:delete,options, &blk); end
|
7
|
+
def put options = {}, &blk; setup_request(:put, options, &blk); end
|
8
|
+
def post options = {}, &blk; setup_request(:post, options, &blk); end
|
9
|
+
end
|
10
|
+
|
11
|
+
class HttpStubConnection < Connection
|
12
|
+
include Deferrable
|
13
|
+
attr_reader :parent
|
14
|
+
|
15
|
+
def parent=(p)
|
16
|
+
@parent = p
|
17
|
+
@parent.conn = self
|
18
|
+
end
|
19
|
+
|
20
|
+
def receive_data(data)
|
21
|
+
@parent.receive_data data
|
22
|
+
end
|
23
|
+
|
24
|
+
def connection_completed
|
25
|
+
@parent.connection_completed
|
26
|
+
end
|
27
|
+
|
28
|
+
def unbind(reason=nil)
|
29
|
+
@parent.unbind(reason)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class HttpConnection
|
34
|
+
include HTTPMethods
|
35
|
+
include Socksify
|
36
|
+
|
37
|
+
attr_reader :deferred
|
38
|
+
attr_accessor :error, :connopts, :uri, :conn
|
39
|
+
|
40
|
+
def initialize
|
41
|
+
@deferred = true
|
42
|
+
@middleware = []
|
43
|
+
end
|
44
|
+
|
45
|
+
def conn=(c)
|
46
|
+
@conn = c
|
47
|
+
@deferred = false
|
48
|
+
end
|
49
|
+
|
50
|
+
def activate_connection(client)
|
51
|
+
begin
|
52
|
+
EventMachine.bind_connect(@connopts.bind, @connopts.bind_port, @connopts.host, @connopts.port, HttpStubConnection) do |conn|
|
53
|
+
post_init
|
54
|
+
|
55
|
+
@deferred = false
|
56
|
+
@conn = conn
|
57
|
+
|
58
|
+
conn.parent = self
|
59
|
+
conn.pending_connect_timeout = @connopts.connect_timeout
|
60
|
+
conn.comm_inactivity_timeout = @connopts.inactivity_timeout
|
61
|
+
end
|
62
|
+
|
63
|
+
finalize_request(client)
|
64
|
+
rescue EventMachine::ConnectionError => e
|
65
|
+
#
|
66
|
+
# Currently, this can only fire on initial connection setup
|
67
|
+
# since #connect is a synchronous method. Hence, rescue the exception,
|
68
|
+
# and return a failed deferred which fail any client request at next
|
69
|
+
# tick. We fail at next tick to keep a consistent API when the newly
|
70
|
+
# created HttpClient is failed. This approach has the advantage to
|
71
|
+
# remove a state check of @deferred_status after creating a new
|
72
|
+
# HttpRequest. The drawback is that users may setup a callback which we
|
73
|
+
# know won't be used.
|
74
|
+
#
|
75
|
+
# Once there is async-DNS, then we'll iterate over the outstanding
|
76
|
+
# client requests and fail them in order.
|
77
|
+
#
|
78
|
+
# Net outcome: failed connection will invoke the same ConnectionError
|
79
|
+
# message on the connection deferred, and on the client deferred.
|
80
|
+
#
|
81
|
+
EM.next_tick{client.close(e.message)}
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def setup_request(method, options = {}, c = nil)
|
86
|
+
c ||= HttpClient.new(self, HttpClientOptions.new(@uri, options, method))
|
87
|
+
@deferred ? activate_connection(c) : finalize_request(c)
|
88
|
+
c
|
89
|
+
end
|
90
|
+
|
91
|
+
def finalize_request(c)
|
92
|
+
@conn.callback { c.connection_completed }
|
93
|
+
|
94
|
+
middleware.each do |m|
|
95
|
+
c.callback &m.method(:response) if m.respond_to?(:response)
|
96
|
+
end
|
97
|
+
|
98
|
+
@clients.push c
|
99
|
+
end
|
100
|
+
|
101
|
+
def middleware
|
102
|
+
[HttpRequest.middleware, @middleware].flatten
|
103
|
+
end
|
104
|
+
|
105
|
+
def post_init
|
106
|
+
@clients = []
|
107
|
+
@pending = []
|
108
|
+
|
109
|
+
@p = Http::Parser.new
|
110
|
+
@p.header_value_type = :mixed
|
111
|
+
@p.on_headers_complete = proc do |h|
|
112
|
+
client.parse_response_header(h, @p.http_version, @p.status_code)
|
113
|
+
:reset if client.req.no_body?
|
114
|
+
end
|
115
|
+
|
116
|
+
@p.on_body = proc do |b|
|
117
|
+
client.on_body_data(b)
|
118
|
+
end
|
119
|
+
|
120
|
+
@p.on_message_complete = proc do
|
121
|
+
if not client.continue?
|
122
|
+
c = @clients.shift
|
123
|
+
c.state = :finished
|
124
|
+
c.on_request_complete
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def use(klass, *args, &block)
|
130
|
+
@middleware << klass.new(*args, &block)
|
131
|
+
end
|
132
|
+
|
133
|
+
def peer
|
134
|
+
Socket.unpack_sockaddr_in(@peer)[1] rescue nil
|
135
|
+
end
|
136
|
+
|
137
|
+
def receive_data(data)
|
138
|
+
begin
|
139
|
+
@p << data
|
140
|
+
rescue HTTP::Parser::Error => e
|
141
|
+
c = @clients.shift
|
142
|
+
c.nil? ? unbind(e.message) : c.on_error(e.message)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def connection_completed
|
147
|
+
@peer = @conn.get_peername
|
148
|
+
|
149
|
+
if @connopts.proxy && @connopts.proxy[:type] == :socks5
|
150
|
+
socksify(client.req.uri.host, client.req.uri.port, *@connopts.proxy[:authorization]) { start }
|
151
|
+
else
|
152
|
+
start
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def start
|
157
|
+
@conn.start_tls(@connopts.tls) if client && client.req.ssl?
|
158
|
+
@conn.succeed
|
159
|
+
end
|
160
|
+
|
161
|
+
def redirect(client)
|
162
|
+
@pending.push client
|
163
|
+
end
|
164
|
+
|
165
|
+
def unbind(reason)
|
166
|
+
@clients.map { |c| c.unbind(reason) }
|
167
|
+
|
168
|
+
if r = @pending.shift
|
169
|
+
@clients.push r
|
170
|
+
|
171
|
+
r.reset!
|
172
|
+
@p.reset!
|
173
|
+
|
174
|
+
begin
|
175
|
+
@conn.set_deferred_status :unknown
|
176
|
+
@conn.reconnect(r.req.host, r.req.port)
|
177
|
+
@conn.callback { r.connection_completed }
|
178
|
+
rescue EventMachine::ConnectionError => e
|
179
|
+
@clients.pop.close(e.message)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
alias :close :unbind
|
184
|
+
|
185
|
+
def send_data(data)
|
186
|
+
@conn.send_data data
|
187
|
+
end
|
188
|
+
|
189
|
+
def stream_file_data(filename, args = {})
|
190
|
+
@conn.stream_file_data filename, args
|
191
|
+
end
|
192
|
+
|
193
|
+
private
|
194
|
+
|
195
|
+
def client
|
196
|
+
@clients.first
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
class HttpConnectionOptions
|
2
|
-
attr_reader :host, :port, :tls, :proxy
|
2
|
+
attr_reader :host, :port, :tls, :proxy, :bind, :bind_port
|
3
3
|
attr_reader :connect_timeout, :inactivity_timeout
|
4
4
|
|
5
5
|
def initialize(uri, options)
|
@@ -9,6 +9,11 @@ class HttpConnectionOptions
|
|
9
9
|
@tls = options[:tls] || options[:ssl] || {}
|
10
10
|
@proxy = options[:proxy]
|
11
11
|
|
12
|
+
if bind = options[:bind]
|
13
|
+
@bind = bind[:host] || '0.0.0.0'
|
14
|
+
@bind_port = bind[:port] || 0
|
15
|
+
end
|
16
|
+
|
12
17
|
uri = uri.kind_of?(Addressable::URI) ? uri : Addressable::URI::parse(uri.to_s)
|
13
18
|
uri.port = (uri.scheme == "https" ? (uri.port || 443) : (uri.port || 80))
|
14
19
|
|
@@ -7,7 +7,7 @@ module EventMachine
|
|
7
7
|
if defined?(EscapeUtils)
|
8
8
|
EscapeUtils.escape_url(s.to_s)
|
9
9
|
else
|
10
|
-
s.to_s.gsub(/([^a-zA-Z0-9_.-]+)/
|
10
|
+
s.to_s.gsub(/([^a-zA-Z0-9_.-]+)/) {
|
11
11
|
'%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
|
12
12
|
}
|
13
13
|
end
|
@@ -17,7 +17,7 @@ module EventMachine
|
|
17
17
|
if defined?(EscapeUtils)
|
18
18
|
EscapeUtils.unescape_url(s.to_s)
|
19
19
|
else
|
20
|
-
s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/
|
20
|
+
s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/) {
|
21
21
|
[$1.delete('%')].pack('H*')
|
22
22
|
}
|
23
23
|
end
|
data/lib/em-http/http_header.rb
CHANGED
@@ -1,43 +1,57 @@
|
|
1
1
|
module EventMachine
|
2
2
|
module HttpStatus
|
3
3
|
CODE = {
|
4
|
-
100
|
5
|
-
101
|
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
|
-
|
4
|
+
100 => 'Continue',
|
5
|
+
101 => 'Switching Protocols',
|
6
|
+
102 => 'Processing',
|
7
|
+
200 => 'OK',
|
8
|
+
201 => 'Created',
|
9
|
+
202 => 'Accepted',
|
10
|
+
203 => 'Non-Authoritative Information',
|
11
|
+
204 => 'No Content',
|
12
|
+
205 => 'Reset Content',
|
13
|
+
206 => 'Partial Content',
|
14
|
+
207 => 'Multi-Status',
|
15
|
+
226 => 'IM Used',
|
16
|
+
300 => 'Multiple Choices',
|
17
|
+
301 => 'Moved Permanently',
|
18
|
+
302 => 'Found',
|
19
|
+
303 => 'See Other',
|
20
|
+
304 => 'Not Modified',
|
21
|
+
305 => 'Use Proxy',
|
22
|
+
306 => 'Reserved',
|
23
|
+
307 => 'Temporary Redirect',
|
24
|
+
400 => 'Bad Request',
|
25
|
+
401 => 'Unauthorized',
|
26
|
+
402 => 'Payment Required',
|
27
|
+
403 => 'Forbidden',
|
28
|
+
404 => 'Not Found',
|
29
|
+
405 => 'Method Not Allowed',
|
30
|
+
406 => 'Not Acceptable',
|
31
|
+
407 => 'Proxy Authentication Required',
|
32
|
+
408 => 'Request Timeout',
|
33
|
+
409 => 'Conflict',
|
34
|
+
410 => 'Gone',
|
35
|
+
411 => 'Length Required',
|
36
|
+
412 => 'Precondition Failed',
|
37
|
+
413 => 'Request Entity Too Large',
|
38
|
+
414 => 'Request-URI Too Long',
|
39
|
+
415 => 'Unsupported Media Type',
|
40
|
+
416 => 'Requested Range Not Satisfiable',
|
41
|
+
417 => 'Expectation Failed',
|
42
|
+
422 => 'Unprocessable Entity',
|
43
|
+
423 => 'Locked',
|
44
|
+
424 => 'Failed Dependency',
|
45
|
+
426 => 'Upgrade Required',
|
46
|
+
500 => 'Internal Server Error',
|
47
|
+
501 => 'Not Implemented',
|
48
|
+
502 => 'Bad Gateway',
|
49
|
+
503 => 'Service Unavailable',
|
50
|
+
504 => 'Gateway Timeout',
|
51
|
+
505 => 'HTTP Version Not Supported',
|
52
|
+
506 => 'Variant Also Negotiates',
|
53
|
+
507 => 'Insufficient Storage',
|
54
|
+
510 => 'Not Extended'
|
41
55
|
}
|
42
56
|
end
|
43
57
|
end
|