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
@@ -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
|