excon 0.25.3 → 0.26.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of excon might be problematic. Click here for more details.
- data/Gemfile.lock +4 -4
- data/README.md +175 -138
- data/changelog.txt +13 -0
- data/excon.gemspec +7 -2
- data/lib/excon.rb +17 -2
- data/lib/excon/connection.rb +33 -33
- data/lib/excon/constants.rb +1 -1
- data/lib/excon/middlewares/decompress.rb +18 -0
- data/lib/excon/middlewares/idempotent.rb +0 -1
- data/lib/excon/middlewares/mock.rb +4 -4
- data/lib/excon/middlewares/redirect_follower.rb +45 -0
- data/lib/excon/socket.rb +19 -31
- data/lib/excon/ssl_socket.rb +12 -2
- data/tests/basic_tests.rb +2 -1
- data/tests/middlewares/decompress_tests.rb +24 -0
- data/tests/middlewares/mock_tests.rb +26 -0
- data/tests/middlewares/redirect_follower.rb +32 -0
- data/tests/rackups/deflater.ru +4 -0
- data/tests/request_method_tests.rb +6 -0
- data/tests/test_helper.rb +32 -2
- metadata +8 -3
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
excon (0.
|
4
|
+
excon (0.26.0)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: http://rubygems.org/
|
@@ -9,7 +9,7 @@ GEM
|
|
9
9
|
activesupport (3.2.6)
|
10
10
|
i18n (~> 0.6)
|
11
11
|
multi_json (~> 1.0)
|
12
|
-
bouncy-castle-java (1.5.
|
12
|
+
bouncy-castle-java (1.5.0147)
|
13
13
|
chronic (0.6.7)
|
14
14
|
delorean (2.0.0)
|
15
15
|
chronic
|
@@ -17,8 +17,8 @@ GEM
|
|
17
17
|
eventmachine (1.0.0-java)
|
18
18
|
formatador (0.2.3)
|
19
19
|
i18n (0.6.0)
|
20
|
-
jruby-openssl (0.
|
21
|
-
bouncy-castle-java (>= 1.5.
|
20
|
+
jruby-openssl (0.8.8)
|
21
|
+
bouncy-castle-java (>= 1.5.0147)
|
22
22
|
json (1.7.3)
|
23
23
|
json (1.7.3-java)
|
24
24
|
multi_json (1.3.6)
|
data/README.md
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
excon
|
2
2
|
=====
|
3
3
|
|
4
|
-
Usable, fast, simple Ruby HTTP 1.
|
4
|
+
Usable, fast, simple Ruby HTTP 1.1
|
5
|
+
|
6
|
+
Excon was designed to be simple, fast and performant. It works great as a general HTTP(s) client and is particularly well suited to usage in API clients.
|
5
7
|
|
6
8
|
[![Build Status](https://secure.travis-ci.org/geemus/excon.png)](http://travis-ci.org/geemus/excon)
|
7
9
|
[![Dependency Status](https://gemnasium.com/geemus/excon.png)](https://gemnasium.com/geemus/excon)
|
@@ -12,130 +14,142 @@ Getting Started
|
|
12
14
|
|
13
15
|
Install the gem.
|
14
16
|
|
15
|
-
|
17
|
+
```
|
18
|
+
$ sudo gem install excon
|
19
|
+
```
|
16
20
|
|
17
21
|
Require with rubygems.
|
18
22
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
response = Excon.get('http://geemus.com')
|
25
|
-
|
26
|
-
Supported one-off request methods are #connect, #delete, #get, #head, #options, #post, #put, and #trace.
|
27
|
-
|
28
|
-
The returned response object has #body, #headers and #status attributes.
|
29
|
-
|
30
|
-
Alternately you can create a connection object which is reusable across multiple requests (more performant!).
|
23
|
+
```ruby
|
24
|
+
require 'rubygems'
|
25
|
+
require 'excon'
|
26
|
+
```
|
31
27
|
|
32
|
-
|
33
|
-
response_one = connection.get
|
34
|
-
response_two = connection.post(:path => '/foo')
|
35
|
-
response_three = connection.delete(:path => '/bar')
|
28
|
+
The easiest way to get started is by using one-off requests. Supported one-off request methods are `connect`, `delete`, `get`, `head`, `options`, `post`, `put`, and `trace`. Requests return a response object which has `body`, `headers`, `remote_ip` and `status` attributes.
|
36
29
|
|
37
|
-
|
30
|
+
```ruby
|
31
|
+
response = Excon.get('http://geemus.com')
|
32
|
+
response.body # => "..."
|
33
|
+
response.headers # => {...}
|
34
|
+
response.remote_ip # => "..."
|
35
|
+
response.status # => 200
|
36
|
+
```
|
38
37
|
|
39
|
-
|
38
|
+
For API clients or other ongoing usage, reuse a connection across multiple requests to share options and improve performance.
|
40
39
|
|
41
|
-
|
40
|
+
```ruby
|
41
|
+
connection = Excon.new('http://geemus.com')
|
42
|
+
get_response = connection.get
|
43
|
+
post_response = connection.post(:path => '/foo')
|
44
|
+
delete_response = connection.delete(:path => '/bar')
|
45
|
+
```
|
42
46
|
|
43
|
-
|
44
|
-
|
45
|
-
connection.get(:headers => {'Authorization' => 'Basic 0123456789ABCDEF'})
|
47
|
+
Options
|
48
|
+
-------
|
46
49
|
|
47
|
-
|
48
|
-
connection = Excon.new('http://geemus.com/')
|
49
|
-
connection.get(:query => {:foo => 'bar'})
|
50
|
+
Both one-off and persistent connections support many other options. The final options for a request are built up by starting with `Excon.defaults`, then merging in options from the connection and finally merging in any request options. In this way you have plenty of options on where and how to set options and can easily setup connections or defaults to match common options for a particular endpoint.
|
50
51
|
|
51
|
-
|
52
|
-
Excon.post('http://geemus.com',
|
53
|
-
:body => 'language=ruby&class=fog',
|
54
|
-
:headers => { "Content-Type" => "application/x-www-form-urlencoded" })
|
52
|
+
Here are a few common examples:
|
55
53
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
54
|
+
```ruby
|
55
|
+
# Custom headers
|
56
|
+
Excon.get('http://geemus.com', :headers => {'Authorization' => 'Basic 0123456789ABCDEF'})
|
57
|
+
connection.get(:headers => {'Authorization' => 'Basic 0123456789ABCDEF'})
|
60
58
|
|
61
|
-
|
62
|
-
|
63
|
-
|
59
|
+
# Changing query strings
|
60
|
+
connection = Excon.new('http://geemus.com/')
|
61
|
+
connection.get(:query => {:foo => 'bar'})
|
64
62
|
|
65
|
-
|
66
|
-
|
63
|
+
# POST body encoded with application/x-www-form-urlencoded
|
64
|
+
Excon.post('http://geemus.com',
|
65
|
+
:body => 'language=ruby&class=fog',
|
66
|
+
:headers => { "Content-Type" => "application/x-www-form-urlencoded" })
|
67
67
|
|
68
|
-
|
69
|
-
|
68
|
+
# same again, but using URI to build the body of parameters
|
69
|
+
Excon.post('http://geemus.com',
|
70
|
+
:body => URI.encode_www_form(:language => 'ruby', :class => 'fog'),
|
71
|
+
:headers => { "Content-Type" => "application/x-www-form-urlencoded" })
|
70
72
|
|
71
|
-
|
72
|
-
|
73
|
+
# request takes a method option, accepting either a symbol or string
|
74
|
+
connection.request(:method => :get)
|
75
|
+
connection.request(:method => 'GET')
|
73
76
|
|
74
|
-
|
75
|
-
|
77
|
+
# expect one or more status codes, or raise an error
|
78
|
+
connection.request(:expects => [200, 201], :method => :get)
|
76
79
|
|
77
|
-
|
78
|
-
|
80
|
+
# this request can be repeated safely, so retry on errors up to 3 times
|
81
|
+
connection.request(:idempotent => true)
|
79
82
|
|
80
|
-
|
81
|
-
|
83
|
+
# this request can be repeated safely, retry up to 6 times
|
84
|
+
connection.request(:idempotent => true, :retry_limit => 6)
|
82
85
|
|
83
|
-
|
84
|
-
|
86
|
+
# opt-out of nonblocking operations for performance and/or as a workaround
|
87
|
+
connection.request(:nonblock => false)
|
85
88
|
|
86
|
-
|
87
|
-
|
88
|
-
# This can improve response time when sending frequent short
|
89
|
-
# requests in time-sensitive scenarios.
|
90
|
-
#
|
91
|
-
connection = Excon.new('http://geemus.com/', :tcp_nodelay => true)
|
89
|
+
# opt-in to omitting port from http:80 and https:443
|
90
|
+
connection.request(:omit_default_port => true)
|
92
91
|
|
93
|
-
|
92
|
+
# set longer connect_timeout (default is 60 seconds)
|
93
|
+
connection.request(:connect_timeout => 360)
|
94
94
|
|
95
|
-
|
95
|
+
# set longer read_timeout (default is 60 seconds)
|
96
|
+
connection.request(:read_timeout => 360)
|
96
97
|
|
97
|
-
|
98
|
+
# set longer write_timeout (default is 60 seconds)
|
99
|
+
connection.request(:write_timeout => 360)
|
98
100
|
|
99
|
-
|
101
|
+
# Enable the socket option TCP_NODELAY on the underlying socket.
|
102
|
+
#
|
103
|
+
# This can improve response time when sending frequent short
|
104
|
+
# requests in time-sensitive scenarios.
|
105
|
+
#
|
106
|
+
connection = Excon.new('http://geemus.com/', :tcp_nodelay => true)
|
107
|
+
```
|
100
108
|
|
101
109
|
Chunked Requests
|
102
110
|
----------------
|
103
111
|
|
104
112
|
You can make `Transfer-Encoding: chunked` requests by passing a block that will deliver chunks, delivering an empty chunk to signal completion.
|
105
113
|
|
106
|
-
|
114
|
+
```ruby
|
115
|
+
file = File.open('data')
|
107
116
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
117
|
+
chunker = lambda do
|
118
|
+
# Excon.defaults[:chunk_size] defaults to 1048576, ie 1MB
|
119
|
+
# to_s will convert the nil receieved after everything is read to the final empty chunk
|
120
|
+
file.read(Excon.defaults[:chunk_size]).to_s
|
121
|
+
end
|
113
122
|
|
114
|
-
|
123
|
+
Excon.post('http://geemus.com', :request_block => chunker)
|
115
124
|
|
116
|
-
|
125
|
+
file.close
|
126
|
+
```
|
117
127
|
|
118
128
|
Iterating in this way allows you to have more granular control over writes and to write things where you can not calculate the overall length up front.
|
119
129
|
|
120
130
|
Pipelining Requests
|
121
131
|
------------------
|
122
132
|
|
123
|
-
You can make use of HTTP pipelining to improve performance.
|
133
|
+
You can make use of HTTP pipelining to improve performance. Instead of the normal request/response cyle, pipelining sends a series of requests and then receives a series of responses. You can take advantage of this using the `requests` method, which takes an array of params where each is a hash like request would receive and returns an array of responses.
|
124
134
|
|
125
|
-
|
126
|
-
|
135
|
+
```ruby
|
136
|
+
connection = Excon.new('http://geemus.com/')
|
137
|
+
connection.requests([{:method => :get}, {:method => :get}])
|
138
|
+
```
|
127
139
|
|
128
140
|
Streaming Responses
|
129
141
|
-------------------
|
130
142
|
|
131
143
|
You can stream responses by passing a block that will receive each chunk.
|
132
144
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
145
|
+
```ruby
|
146
|
+
streamer = lambda do |chunk, remaining_bytes, total_bytes|
|
147
|
+
puts chunk
|
148
|
+
puts "Remaining: #{remaining_bytes.to_f / total_bytes}%"
|
149
|
+
end
|
137
150
|
|
138
|
-
|
151
|
+
Excon.get('http://geemus.com', :response_block => streamer)
|
152
|
+
```
|
139
153
|
|
140
154
|
Iterating over each chunk will allow you to do work on the response incrementally without buffering the entire response first. For very large responses this can lead to significant memory savings.
|
141
155
|
|
@@ -144,8 +158,10 @@ Proxy Support
|
|
144
158
|
|
145
159
|
You can specify a proxy URL that Excon will use with both HTTP and HTTPS connections:
|
146
160
|
|
147
|
-
|
148
|
-
|
161
|
+
```ruby
|
162
|
+
connection = Excon.new('http://geemus.com', :proxy => 'http://my.proxy:3128')
|
163
|
+
connection.request(:method => 'GET')
|
164
|
+
```
|
149
165
|
|
150
166
|
The proxy URL must be fully specified, including scheme (e.g. "http://") and port.
|
151
167
|
|
@@ -158,94 +174,96 @@ Stubs
|
|
158
174
|
|
159
175
|
You can stub out requests for testing purposes by enabling mock mode on a connection.
|
160
176
|
|
161
|
-
|
177
|
+
```ruby
|
178
|
+
connection = Excon.new('http://example.com', :mock => true)
|
179
|
+
```
|
162
180
|
|
163
181
|
Or by enabling mock mode for a request.
|
164
182
|
|
165
|
-
|
166
|
-
|
167
|
-
|
183
|
+
```ruby
|
184
|
+
connection.request(:method => :get, :path => 'example', :mock => true)
|
185
|
+
```
|
168
186
|
|
169
|
-
|
170
|
-
Excon.stub({:method => :get}, {:body => 'body', :status => 200})
|
187
|
+
Add stubs by providing the request_attributes to match and response attributes to return. Response params can be specified as either a hash or block which will yield with response_params.
|
171
188
|
|
172
|
-
|
189
|
+
```ruby
|
190
|
+
Excon.stub({}, {:body => 'body', :status => 200})
|
191
|
+
Excon.stub({}, lambda {|request_params| :body => request_params[:body], :status => 200})
|
192
|
+
```
|
173
193
|
|
174
|
-
|
175
|
-
|
176
|
-
# Excon.stub(request_attributes, &response_block)
|
177
|
-
Excon.stub({:method => :put}) do |params|
|
178
|
-
{:body => params[:body], :status => 200}
|
179
|
-
end
|
194
|
+
Omitted attributes are assumed to match, so this stub will match *any* request and return an Excon::Response with a body of 'body' and status of 200. You can add whatever stubs you might like this way and they will be checked against in the order they were added, if none of them match then excon will raise an `Excon::Errors::StubNotFound` error to let you know.
|
180
195
|
|
181
|
-
|
196
|
+
To remove a previously defined stub, or all stubs:
|
182
197
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
Excon.stubs.shift
|
198
|
+
```ruby
|
199
|
+
Excon.unstub({}) # remove first/oldest stub matching {}
|
200
|
+
Excon.stubs.clear # remove all stubs
|
201
|
+
```
|
188
202
|
|
189
203
|
For example, if using RSpec for your test suite you can clear stubs after running each example:
|
190
204
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
before(:all) { Excon.defaults[:mock] = true }
|
198
|
-
|
199
|
-
For additional information on stubbing, read the pull request notes [here](https://github.com/geemus/excon/issues/29)
|
200
|
-
|
201
|
-
HTTPS/SSL Issues
|
202
|
-
----------------
|
203
|
-
|
204
|
-
By default excon will try to verify peer certificates when using SSL for HTTPS. Unfortunately on some operating systems the defaults will not work. This will likely manifest itself as something like `Excon::Errors::SocketError: SSL_connect returned=1 ...`
|
205
|
-
|
206
|
-
If you have the misfortune of running into this problem you have a couple options. If you have certificates but they aren't being auto-discovered, you can specify the path to your certificates:
|
205
|
+
```ruby
|
206
|
+
config.after(:each) do
|
207
|
+
Excon.stubs.clear
|
208
|
+
end
|
209
|
+
```
|
207
210
|
|
208
|
-
|
211
|
+
You can also modify 'Excon.defaults` to set a stub for all requests, so for a test suite you might do this:
|
209
212
|
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
213
|
+
```ruby
|
214
|
+
# Mock by default and stub any request as success
|
215
|
+
config.before(:all) do
|
216
|
+
Excon.defaults[:mock] = true
|
217
|
+
Excon.stub({}, {:body => 'Fallback', :status => 200})
|
218
|
+
# Add your own stubs here or in specific tests...
|
219
|
+
end
|
220
|
+
```
|
215
221
|
|
216
222
|
Instrumentation
|
217
223
|
---------------
|
218
224
|
|
219
225
|
Excon calls can be timed using the [ActiveSupport::Notifications](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html) API.
|
220
226
|
|
221
|
-
|
222
|
-
|
227
|
+
```ruby
|
228
|
+
connection = Excon.new(
|
229
|
+
'http://geemus.com',
|
230
|
+
:instrumentor => ActiveSupport::Notifications
|
231
|
+
)
|
232
|
+
```
|
223
233
|
|
224
234
|
Excon will then instrument each request, retry, and error. The corresponding events are named excon.request, excon.retry, and excon.error respectively.
|
225
235
|
|
226
|
-
|
227
|
-
|
228
|
-
|
236
|
+
```ruby
|
237
|
+
ActiveSupport::Notifications.subscribe(/excon/) do |*args|
|
238
|
+
puts "Excon did stuff!"
|
239
|
+
end
|
240
|
+
```
|
229
241
|
|
230
242
|
If you prefer to label each event with something other than "excon," you may specify
|
231
243
|
an alternate name in the constructor:
|
232
244
|
|
233
|
-
|
234
|
-
|
235
|
-
|
245
|
+
```ruby
|
246
|
+
connection = Excon.new(
|
247
|
+
'http://geemus.com',
|
248
|
+
:instrumentor => ActiveSupport::Notifications,
|
249
|
+
:instrumentor_name => 'my_app'
|
250
|
+
)
|
251
|
+
```
|
236
252
|
|
237
253
|
If you don't want to add activesupport to your application, simply define a class which implements the same #instrument method like so:
|
238
254
|
|
239
|
-
|
240
|
-
|
241
|
-
|
255
|
+
```ruby
|
256
|
+
class SimpleInstrumentor
|
257
|
+
class << self
|
258
|
+
attr_accessor :events
|
242
259
|
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
end
|
247
|
-
end
|
260
|
+
def instrument(name, params = {}, &block)
|
261
|
+
puts "#{name} just happened."
|
262
|
+
yield if block_given?
|
248
263
|
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
```
|
249
267
|
|
250
268
|
The #instrument method will be called for each HTTP request, response, retry, and error.
|
251
269
|
|
@@ -253,12 +271,31 @@ For debugging purposes you can also use Excon::StandardInstrumentor to output al
|
|
253
271
|
|
254
272
|
See [the documentation for ActiveSupport::Notifications](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html) for more detail on using the subscription interface. See excon's instrumentation_test.rb for more examples of instrumenting excon.
|
255
273
|
|
274
|
+
HTTPS/SSL Issues
|
275
|
+
----------------
|
276
|
+
|
277
|
+
By default excon will try to verify peer certificates when using SSL for HTTPS. Unfortunately on some operating systems the defaults will not work. This will likely manifest itself as something like `Excon::Errors::SocketError: SSL_connect returned=1 ...`
|
278
|
+
|
279
|
+
If you have the misfortune of running into this problem you have a couple options. If you have certificates but they aren't being auto-discovered, you can specify the path to your certificates:
|
280
|
+
|
281
|
+
```ruby
|
282
|
+
Excon.defaults[:ssl_ca_path] = '/path/to/certs'
|
283
|
+
```
|
284
|
+
|
285
|
+
Failing that, you can turn off peer verification (less secure):
|
286
|
+
|
287
|
+
```ruby
|
288
|
+
Excon.defaults[:ssl_verify_peer] = false
|
289
|
+
```
|
290
|
+
|
291
|
+
Either of these should allow you to work around the socket error and continue with your work.
|
292
|
+
|
256
293
|
Copyright
|
257
294
|
---------
|
258
295
|
|
259
296
|
(The MIT License)
|
260
297
|
|
261
|
-
Copyright (c) 2010-2013
|
298
|
+
Copyright (c) 2010-2013 [geemus (Wesley Beary)](http://github.com/geemus)
|
262
299
|
|
263
300
|
Permission is hereby granted, free of charge, to any person obtaining
|
264
301
|
a copy of this software and associated documentation files (the
|
data/changelog.txt
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
0.26.0 09/24/2013
|
2
|
+
=================
|
3
|
+
|
4
|
+
add basic decompress middleware
|
5
|
+
update readme mocking+stubbing info
|
6
|
+
add unstub functionality
|
7
|
+
avoid modifying original options in request
|
8
|
+
jruby fixes
|
9
|
+
misc cleanup/fixes
|
10
|
+
encoding/compatibility fixes
|
11
|
+
close sockets on error
|
12
|
+
warn when both request_block and idempotent are set
|
13
|
+
|
1
14
|
0.25.3 07/18/2013
|
2
15
|
=================
|
3
16
|
|
data/excon.gemspec
CHANGED
@@ -13,8 +13,8 @@ Gem::Specification.new do |s|
|
|
13
13
|
## If your rubyforge_project name is different, then edit it and comment out
|
14
14
|
## the sub! line in the Rakefile
|
15
15
|
s.name = 'excon'
|
16
|
-
s.version = '0.
|
17
|
-
s.date = '2013-
|
16
|
+
s.version = '0.26.0'
|
17
|
+
s.date = '2013-09-24'
|
18
18
|
s.rubyforge_project = 'excon'
|
19
19
|
|
20
20
|
## Make sure your summary is short. The description may be as long
|
@@ -98,10 +98,12 @@ Gem::Specification.new do |s|
|
|
98
98
|
lib/excon/constants.rb
|
99
99
|
lib/excon/errors.rb
|
100
100
|
lib/excon/middlewares/base.rb
|
101
|
+
lib/excon/middlewares/decompress.rb
|
101
102
|
lib/excon/middlewares/expects.rb
|
102
103
|
lib/excon/middlewares/idempotent.rb
|
103
104
|
lib/excon/middlewares/instrumentor.rb
|
104
105
|
lib/excon/middlewares/mock.rb
|
106
|
+
lib/excon/middlewares/redirect_follower.rb
|
105
107
|
lib/excon/middlewares/response_parser.rb
|
106
108
|
lib/excon/response.rb
|
107
109
|
lib/excon/socket.rb
|
@@ -115,14 +117,17 @@ Gem::Specification.new do |s|
|
|
115
117
|
tests/data/xs
|
116
118
|
tests/errors_tests.rb
|
117
119
|
tests/header_tests.rb
|
120
|
+
tests/middlewares/decompress_tests.rb
|
118
121
|
tests/middlewares/idempotent_tests.rb
|
119
122
|
tests/middlewares/instrumentation_tests.rb
|
120
123
|
tests/middlewares/mock_tests.rb
|
124
|
+
tests/middlewares/redirect_follower.rb
|
121
125
|
tests/proxy_tests.rb
|
122
126
|
tests/query_string_tests.rb
|
123
127
|
tests/rackups/basic.rb
|
124
128
|
tests/rackups/basic.ru
|
125
129
|
tests/rackups/basic_auth.ru
|
130
|
+
tests/rackups/deflater.ru
|
126
131
|
tests/rackups/proxy.ru
|
127
132
|
tests/rackups/query_string.ru
|
128
133
|
tests/rackups/request_headers.ru
|
data/lib/excon.rb
CHANGED
@@ -8,6 +8,8 @@ require 'rbconfig'
|
|
8
8
|
require 'socket'
|
9
9
|
require 'timeout'
|
10
10
|
require 'uri'
|
11
|
+
require 'zlib'
|
12
|
+
require 'stringio'
|
11
13
|
|
12
14
|
# Define defaults first so they will be available to other files
|
13
15
|
module Excon
|
@@ -17,6 +19,7 @@ module Excon
|
|
17
19
|
def defaults
|
18
20
|
@defaults ||= {
|
19
21
|
:chunk_size => CHUNK_SIZE || DEFAULT_CHUNK_SIZE,
|
22
|
+
:ciphers => 'HIGH:!SSLv2:!aNULL:!eNULL:!3DES',
|
20
23
|
:connect_timeout => 60,
|
21
24
|
:debug_request => false,
|
22
25
|
:debug_response => false,
|
@@ -57,10 +60,12 @@ require 'excon/constants'
|
|
57
60
|
require 'excon/connection'
|
58
61
|
require 'excon/errors'
|
59
62
|
require 'excon/middlewares/base'
|
63
|
+
require 'excon/middlewares/decompress'
|
60
64
|
require 'excon/middlewares/expects'
|
61
65
|
require 'excon/middlewares/idempotent'
|
62
66
|
require 'excon/middlewares/instrumentor'
|
63
67
|
require 'excon/middlewares/mock'
|
68
|
+
require 'excon/middlewares/redirect_follower'
|
64
69
|
require 'excon/middlewares/response_parser'
|
65
70
|
require 'excon/response'
|
66
71
|
require 'excon/socket'
|
@@ -173,11 +178,13 @@ module Excon
|
|
173
178
|
end
|
174
179
|
|
175
180
|
# get a stub matching params or nil
|
181
|
+
# @param [Hash<Symbol, >] request params to match against, omitted params match all
|
182
|
+
# @return [Hash<Symbol, >] response params to return from matched request or block to call with params
|
176
183
|
def stub_for(request_params={})
|
177
184
|
if method = request_params.delete(:method)
|
178
185
|
request_params[:method] = method.to_s.downcase.to_sym
|
179
186
|
end
|
180
|
-
Excon.stubs.each do |stub,
|
187
|
+
Excon.stubs.each do |stub, response_params|
|
181
188
|
captures = { :headers => {} }
|
182
189
|
headers_match = !stub.has_key?(:headers) || stub[:headers].keys.all? do |key|
|
183
190
|
case value = stub[:headers][key]
|
@@ -203,7 +210,7 @@ module Excon
|
|
203
210
|
end
|
204
211
|
if headers_match && non_headers_match
|
205
212
|
request_params[:captures] = captures
|
206
|
-
return
|
213
|
+
return [stub, response_params]
|
207
214
|
end
|
208
215
|
end
|
209
216
|
nil
|
@@ -214,6 +221,14 @@ module Excon
|
|
214
221
|
@stubs ||= []
|
215
222
|
end
|
216
223
|
|
224
|
+
# remove first/oldest stub matching request_params
|
225
|
+
# @param [Hash<Symbol, >] request params to match against, omitted params match all
|
226
|
+
# @return [Hash<Symbol, >] response params from deleted stub
|
227
|
+
def unstub(request_params = {})
|
228
|
+
stub = stub_for(request_params)
|
229
|
+
Excon.stubs.delete_at(Excon.stubs.index(stub))
|
230
|
+
end
|
231
|
+
|
217
232
|
# Generic non-persistent HTTP methods
|
218
233
|
HTTP_VERBS.each do |method|
|
219
234
|
module_eval <<-DEF, __FILE__, __LINE__ + 1
|
data/lib/excon/connection.rb
CHANGED
@@ -39,6 +39,7 @@ module Excon
|
|
39
39
|
# @option params [Fixnum] :port The port on which to connect, to the destination host
|
40
40
|
# @option params [Hash] :query Default query; appended to the 'scheme://host:port/path/' in the form of '?key=value'. Will only be used if params[:query] is not supplied to Connection#request
|
41
41
|
# @option params [String] :scheme The protocol; 'https' causes OpenSSL to be used
|
42
|
+
# @option params [String] :ciphers Only use the specified SSL/TLS cipher suites; use OpenSSL cipher spec format e.g. 'HIGH:!aNULL:!3DES' or 'AES256-SHA:DES-CBC3-SHA'
|
42
43
|
# @option params [String] :proxy Proxy server; e.g. 'http://myproxy.com:8888'
|
43
44
|
# @option params [Fixnum] :retry_limit Set how many times we'll retry a failed request. (Default 4)
|
44
45
|
# @option params [Class] :instrumentor Responds to #instrument as in ActiveSupport::Notifications
|
@@ -133,10 +134,16 @@ module Excon
|
|
133
134
|
|
134
135
|
if datum.has_key?(:request_block)
|
135
136
|
datum[:headers]['Transfer-Encoding'] = 'chunked'
|
136
|
-
|
137
|
+
else
|
138
|
+
body = datum[:body].is_a?(String) ? StringIO.new(datum[:body]) : datum[:body]
|
139
|
+
|
137
140
|
# The HTTP spec isn't clear on it, but specifically, GET requests don't usually send bodies;
|
138
141
|
# if they don't, sending Content-Length:0 can cause issues.
|
139
|
-
datum[:
|
142
|
+
unless datum[:method].to_s.casecmp('GET') == 0 && body.nil?
|
143
|
+
unless datum[:headers].has_key?('Content-Length')
|
144
|
+
datum[:headers]['Content-Length'] = detect_content_length(body)
|
145
|
+
end
|
146
|
+
end
|
140
147
|
end
|
141
148
|
|
142
149
|
# add headers to request
|
@@ -148,9 +155,9 @@ module Excon
|
|
148
155
|
|
149
156
|
# add additional "\r\n" to indicate end of headers
|
150
157
|
request << CR_NL
|
158
|
+
socket.write(request) # write out request + headers
|
151
159
|
|
152
160
|
if datum.has_key?(:request_block)
|
153
|
-
socket.write(request) # write out request + headers
|
154
161
|
while true # write out body with chunked encoding
|
155
162
|
chunk = datum[:request_block].call
|
156
163
|
if FORCE_ENC
|
@@ -163,23 +170,16 @@ module Excon
|
|
163
170
|
break
|
164
171
|
end
|
165
172
|
end
|
166
|
-
elsif !
|
167
|
-
if
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
datum[:body].pos = 0
|
176
|
-
end
|
177
|
-
while chunk = datum[:body].read(datum[:chunk_size])
|
178
|
-
socket.write(chunk)
|
179
|
-
end
|
173
|
+
elsif !body.nil? # write out body
|
174
|
+
if body.respond_to?(:binmode)
|
175
|
+
body.binmode
|
176
|
+
end
|
177
|
+
if body.respond_to?(:pos=)
|
178
|
+
body.pos = 0
|
179
|
+
end
|
180
|
+
while chunk = body.read(datum[:chunk_size])
|
181
|
+
socket.write(chunk)
|
180
182
|
end
|
181
|
-
else # write out nil body
|
182
|
-
socket.write(request) # write out request + headers
|
183
183
|
end
|
184
184
|
end
|
185
185
|
rescue => error
|
@@ -215,7 +215,7 @@ module Excon
|
|
215
215
|
# @option params [Fixnum] :port The port on which to connect, to the destination host
|
216
216
|
# @option params [Hash] :query appended to the 'scheme://host:port/path/' in the form of '?key=value'
|
217
217
|
# @option params [String] :scheme The protocol; 'https' causes OpenSSL to be used
|
218
|
-
def request(params, &block)
|
218
|
+
def request(params={}, &block)
|
219
219
|
# @data has defaults, merge in new params to override
|
220
220
|
datum = @data.merge(params)
|
221
221
|
invalid_keys_warning(params, VALID_CONNECTION_KEYS)
|
@@ -226,7 +226,7 @@ module Excon
|
|
226
226
|
|
227
227
|
# if path is empty or doesn't start with '/', insert one
|
228
228
|
unless datum[:path][0, 1] == '/'
|
229
|
-
datum[:path].insert(0, '/')
|
229
|
+
datum[:path] = datum[:path].dup.insert(0, '/')
|
230
230
|
end
|
231
231
|
|
232
232
|
if block_given?
|
@@ -234,6 +234,11 @@ module Excon
|
|
234
234
|
datum[:response_block] = Proc.new
|
235
235
|
end
|
236
236
|
|
237
|
+
if datum[:request_block] && datum[:idempotent]
|
238
|
+
Excon.display_warning("Excon requests with a :request_block can not be :idempotent (#{caller.first})")
|
239
|
+
datum[:idempotent] = false
|
240
|
+
end
|
241
|
+
|
237
242
|
datum[:connection] = self
|
238
243
|
|
239
244
|
datum[:stack] = datum[:middlewares].map do |middleware|
|
@@ -255,6 +260,7 @@ module Excon
|
|
255
260
|
datum
|
256
261
|
end
|
257
262
|
rescue => error
|
263
|
+
reset
|
258
264
|
datum[:error] = error
|
259
265
|
if datum[:stack]
|
260
266
|
datum[:stack].error_call(datum)
|
@@ -321,20 +327,14 @@ module Excon
|
|
321
327
|
private
|
322
328
|
|
323
329
|
def detect_content_length(body)
|
324
|
-
if body.
|
325
|
-
|
326
|
-
body.force_encoding('BINARY')
|
327
|
-
end
|
328
|
-
body.length
|
329
|
-
elsif body.respond_to?(:size)
|
330
|
-
# IO object: File, Tempfile, etc.
|
330
|
+
if body.respond_to?(:size)
|
331
|
+
# IO object: File, Tempfile, StringIO, etc.
|
331
332
|
body.size
|
333
|
+
elsif body.respond_to?(:stat)
|
334
|
+
# for 1.8.7 where file does not have size
|
335
|
+
body.stat.size
|
332
336
|
else
|
333
|
-
|
334
|
-
File.size(body) # for 1.8.7 where file does not have size
|
335
|
-
rescue
|
336
|
-
0
|
337
|
-
end
|
337
|
+
0
|
338
338
|
end
|
339
339
|
end
|
340
340
|
|
data/lib/excon/constants.rb
CHANGED
@@ -0,0 +1,18 @@
|
|
1
|
+
module Excon
|
2
|
+
module Middleware
|
3
|
+
class Decompress < Excon::Middleware::Base
|
4
|
+
def response_call(datum)
|
5
|
+
unless datum.has_key?(:response_block)
|
6
|
+
case datum[:response][:headers]['Content-Encoding']
|
7
|
+
when 'deflate'
|
8
|
+
# assume inflate omits header
|
9
|
+
datum[:response][:body] = Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(datum[:response][:body])
|
10
|
+
when 'gzip'
|
11
|
+
datum[:response][:body] = Zlib::GzipReader.new(StringIO.new(datum[:response][:body])).read
|
12
|
+
end
|
13
|
+
end
|
14
|
+
@stack.response_call(datum)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -6,7 +6,6 @@ module Excon
|
|
6
6
|
Excon::Errors::HTTPStatusError].any? {|ex| datum[:error].kind_of?(ex) } && datum[:retries_remaining] > 1
|
7
7
|
# reduces remaining retries, reset connection, and restart request_call
|
8
8
|
datum[:retries_remaining] -= 1
|
9
|
-
datum[:connection].reset
|
10
9
|
datum.delete(:response)
|
11
10
|
datum.delete(:error)
|
12
11
|
datum[:connection].request(datum)
|
@@ -14,7 +14,7 @@ module Excon
|
|
14
14
|
datum[:body] = datum[:body].read
|
15
15
|
end
|
16
16
|
|
17
|
-
if
|
17
|
+
if stub = Excon.stub_for(datum)
|
18
18
|
datum[:response] = {
|
19
19
|
:body => '',
|
20
20
|
:headers => {},
|
@@ -22,11 +22,11 @@ module Excon
|
|
22
22
|
:remote_ip => '127.0.0.1'
|
23
23
|
}
|
24
24
|
|
25
|
-
stub_datum = case
|
25
|
+
stub_datum = case stub.last
|
26
26
|
when Proc
|
27
|
-
|
27
|
+
stub.last.call(datum)
|
28
28
|
else
|
29
|
-
|
29
|
+
stub.last
|
30
30
|
end
|
31
31
|
|
32
32
|
datum[:response].merge!(stub_datum.reject {|key,value| key == :headers})
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Excon
|
2
|
+
module Middleware
|
3
|
+
class RedirectFollower < Excon::Middleware::Base
|
4
|
+
def response_call(datum)
|
5
|
+
if datum.has_key?(:response) && [:get, :head].include?(datum[:method].to_s.downcase.to_sym)
|
6
|
+
case datum[:response][:status]
|
7
|
+
when 301, 302, 303, 307
|
8
|
+
uri_parser = datum[:uri_parser] || Excon.defaults[:uri_parser]
|
9
|
+
_, location = datum[:response][:headers].detect do |key, value|
|
10
|
+
key.casecmp('Location') == 0
|
11
|
+
end
|
12
|
+
uri = uri_parser.parse(location)
|
13
|
+
|
14
|
+
port_string = if datum[:omit_default_port] && ((uri.scheme.casecmp('http') == 0 && uri.port.to_i == 80) || (uri.scheme.casecmp('https') == 0 && uri.port.to_i == 443))
|
15
|
+
''
|
16
|
+
else
|
17
|
+
':' << uri.port.to_s
|
18
|
+
end
|
19
|
+
|
20
|
+
# delete old/redirect response
|
21
|
+
datum.delete(:response)
|
22
|
+
|
23
|
+
response = datum[:connection].request(
|
24
|
+
datum.merge!(
|
25
|
+
:headers => (datum[:headers] || {}).merge({'Host' => '' << uri.host << port_string}),
|
26
|
+
:host => uri.host,
|
27
|
+
:path => uri.path,
|
28
|
+
:port => uri.port,
|
29
|
+
:query => uri.query,
|
30
|
+
:scheme => uri.scheme,
|
31
|
+
:user => (URI.decode(uri.user) if uri.user),
|
32
|
+
:password => (URI.decode(uri.password) if uri.password)
|
33
|
+
)
|
34
|
+
)
|
35
|
+
datum.merge!({:response => response.data})
|
36
|
+
else
|
37
|
+
@stack.response_call(datum)
|
38
|
+
end
|
39
|
+
else
|
40
|
+
@stack.response_call(datum)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/excon/socket.rb
CHANGED
@@ -84,47 +84,35 @@ module Excon
|
|
84
84
|
|
85
85
|
def write(data)
|
86
86
|
if @data[:nonblock]
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
87
|
+
if FORCE_ENC
|
88
|
+
data.force_encoding('BINARY')
|
89
|
+
end
|
90
|
+
while true
|
91
|
+
written = nil
|
92
92
|
begin
|
93
93
|
# I wish that this API accepted a start position, then we wouldn't
|
94
94
|
# have to slice data when there is a short write.
|
95
95
|
written = @socket.write_nonblock(data)
|
96
|
-
rescue OpenSSL::SSL::SSLError => error
|
97
|
-
if error.message
|
96
|
+
rescue OpenSSL::SSL::SSLError, Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable => error
|
97
|
+
if error.is_a?(OpenSSL::SSL::SSLError) && error.message != 'write would block'
|
98
|
+
raise error
|
99
|
+
else
|
98
100
|
if IO.select(nil, [@socket], nil, @data[:write_timeout])
|
99
101
|
retry
|
100
102
|
else
|
101
|
-
raise
|
103
|
+
raise Excon::Errors::Timeout.new('write timeout reached')
|
102
104
|
end
|
103
|
-
else
|
104
|
-
raise(error)
|
105
105
|
end
|
106
|
-
rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable
|
107
|
-
if IO.select(nil, [@socket], nil, @data[:write_timeout])
|
108
|
-
retry
|
109
|
-
else
|
110
|
-
raise(Excon::Errors::Timeout.new("write timeout reached"))
|
111
|
-
end
|
112
|
-
else
|
113
|
-
# Fast, common case.
|
114
|
-
# The >= seems weird, why would it have written MORE than we
|
115
|
-
# requested. But we're getting some weird behavior when @socket
|
116
|
-
# is an OpenSSL socket, where it seems like it's saying it wrote
|
117
|
-
# more (perhaps due to SSL packet overhead?).
|
118
|
-
#
|
119
|
-
# Pretty weird, but this is a simple defensive mechanism.
|
120
|
-
return if written >= data.size
|
121
|
-
|
122
|
-
# This takes advantage of the fact that most ruby implementations
|
123
|
-
# have Copy-On-Write strings. Thusly why requesting a subrange
|
124
|
-
# of data, we actually don't copy data because the new string
|
125
|
-
# simply references a subrange of the original.
|
126
|
-
data = data[written, data.size]
|
127
106
|
end
|
107
|
+
|
108
|
+
# Fast, common case.
|
109
|
+
break if written == data.size
|
110
|
+
|
111
|
+
# This takes advantage of the fact that most ruby implementations
|
112
|
+
# have Copy-On-Write strings. Thusly why requesting a subrange
|
113
|
+
# of data, we actually don't copy data because the new string
|
114
|
+
# simply references a subrange of the original.
|
115
|
+
data = data[written, data.size]
|
128
116
|
end
|
129
117
|
else
|
130
118
|
begin
|
data/lib/excon/ssl_socket.rb
CHANGED
@@ -9,7 +9,8 @@ module Excon
|
|
9
9
|
|
10
10
|
# create ssl context
|
11
11
|
ssl_context = OpenSSL::SSL::SSLContext.new
|
12
|
-
|
12
|
+
ssl_context.ciphers = @data[:ciphers]
|
13
|
+
|
13
14
|
if @data[:ssl_verify_peer]
|
14
15
|
# turn verification on
|
15
16
|
ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
@@ -21,7 +22,16 @@ module Excon
|
|
21
22
|
else # attempt default, fallback to bundled
|
22
23
|
ssl_context.cert_store = OpenSSL::X509::Store.new
|
23
24
|
ssl_context.cert_store.set_default_paths
|
24
|
-
|
25
|
+
|
26
|
+
# workaround issue #257 (JRUBY-6970)
|
27
|
+
ca_file = DEFAULT_CA_FILE
|
28
|
+
ca_file.gsub!(/^jar:/, "") if ca_file =~ /^jar:file:\//
|
29
|
+
|
30
|
+
begin
|
31
|
+
ssl_context.cert_store.add_file(ca_file)
|
32
|
+
rescue => e
|
33
|
+
Excon.display_warning("Excon unable to add file to cert store, ignoring: #{ca_file}\n[#{e.class}] #{e.message}\n#{e.backtrace.join("\n")}")
|
34
|
+
end
|
25
35
|
end
|
26
36
|
else
|
27
37
|
# turn verification off
|
data/tests/basic_tests.rb
CHANGED
@@ -67,7 +67,8 @@ with_rackup('ssl_verify_peer.ru') do
|
|
67
67
|
|
68
68
|
basic_tests('https://127.0.0.1:8443',
|
69
69
|
:client_key => File.join(File.dirname(__FILE__), 'data', 'excon.cert.key'),
|
70
|
-
:client_cert => File.join(File.dirname(__FILE__), 'data', 'excon.cert.crt')
|
70
|
+
:client_cert => File.join(File.dirname(__FILE__), 'data', 'excon.cert.crt'),
|
71
|
+
:reset_connection => RUBY_VERSION == '1.9.2'
|
71
72
|
)
|
72
73
|
|
73
74
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
Shindo.tests('Excon decompression support') do
|
2
|
+
env_init
|
3
|
+
|
4
|
+
with_rackup('deflater.ru') do
|
5
|
+
connection = Excon.new(
|
6
|
+
'http://127.0.0.1:9292/echo',
|
7
|
+
:body => 'x' * 100,
|
8
|
+
:method => :post,
|
9
|
+
:middlewares => Excon.defaults[:middlewares] + [Excon::Middleware::Decompress]
|
10
|
+
)
|
11
|
+
|
12
|
+
tests('deflate').returns('x' * 100) do
|
13
|
+
response = connection.request(:headers => { 'Accept-Encoding' => 'deflate' })
|
14
|
+
response.body
|
15
|
+
end
|
16
|
+
|
17
|
+
tests('gzip').returns('x' * 100) do
|
18
|
+
response = connection.request(:headers => { 'Accept-Encoding' => 'gzip' })
|
19
|
+
response.body
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
env_restore
|
24
|
+
end
|
@@ -193,6 +193,32 @@ Shindo.tests('Excon stubs') do
|
|
193
193
|
|
194
194
|
end
|
195
195
|
|
196
|
+
tests("stub_for({})") do
|
197
|
+
connection = Excon.new('http://127.0.0.1:9292', :mock => true)
|
198
|
+
Excon.stub({}, {})
|
199
|
+
|
200
|
+
tests("stub_for({})").returns([{}, {}]) do
|
201
|
+
Excon.stub_for({})
|
202
|
+
end
|
203
|
+
|
204
|
+
Excon.stubs.clear
|
205
|
+
end
|
206
|
+
|
207
|
+
tests("unstub({})") do
|
208
|
+
connection = Excon.new('http://127.0.0.1:9292', :mock => true)
|
209
|
+
Excon.stub({}, {})
|
210
|
+
|
211
|
+
tests("unstub({})").returns([{}, {}]) do
|
212
|
+
Excon.unstub({})
|
213
|
+
end
|
214
|
+
|
215
|
+
tests("request(:method => :get)").raises(Excon::Errors::StubNotFound) do
|
216
|
+
connection.request(:method => :get)
|
217
|
+
end
|
218
|
+
|
219
|
+
Excon.stubs.clear
|
220
|
+
end
|
221
|
+
|
196
222
|
tests('mock = false') do
|
197
223
|
with_rackup('basic.ru') do
|
198
224
|
basic_tests
|
@@ -0,0 +1,32 @@
|
|
1
|
+
Shindo.tests('Excon redirector support') do
|
2
|
+
env_init
|
3
|
+
|
4
|
+
connection = Excon.new(
|
5
|
+
'http://127.0.0.1:9292',
|
6
|
+
:middlewares => Excon.defaults[:middlewares] + [Excon::Middleware::RedirectFollower],
|
7
|
+
:mock => true
|
8
|
+
)
|
9
|
+
|
10
|
+
Excon.stub(
|
11
|
+
{ :path => '/old' },
|
12
|
+
{
|
13
|
+
:headers => { 'Location' => 'http://127.0.0.1:9292/new' },
|
14
|
+
:body => 'old',
|
15
|
+
:status => 301
|
16
|
+
}
|
17
|
+
)
|
18
|
+
|
19
|
+
Excon.stub(
|
20
|
+
{ :path => '/new' },
|
21
|
+
{
|
22
|
+
:body => 'new',
|
23
|
+
:status => 200
|
24
|
+
}
|
25
|
+
)
|
26
|
+
|
27
|
+
tests("request(:method => :get, :path => '/old').body").returns('new') do
|
28
|
+
connection.request(:method => :get, :path => '/old').body
|
29
|
+
end
|
30
|
+
|
31
|
+
env_restore
|
32
|
+
end
|
data/tests/test_helper.rb
CHANGED
@@ -3,9 +3,8 @@ require 'bundler'
|
|
3
3
|
|
4
4
|
Bundler.require(:default, :development)
|
5
5
|
|
6
|
-
require 'stringio'
|
7
|
-
|
8
6
|
def basic_tests(url = 'http://127.0.0.1:9292', options = {})
|
7
|
+
reset_connection = !!options.delete(:reset_connection)
|
9
8
|
[false, true].each do |nonblock|
|
10
9
|
options = options.merge({:ssl_verify_peer => false, :nonblock => nonblock })
|
11
10
|
connection = Excon.new(url, options)
|
@@ -77,6 +76,9 @@ def basic_tests(url = 'http://127.0.0.1:9292', options = {})
|
|
77
76
|
tests('POST /body-sink') do
|
78
77
|
|
79
78
|
tests('response.body').returns("5000000") do
|
79
|
+
if reset_connection && !nonblock
|
80
|
+
connection.reset
|
81
|
+
end
|
80
82
|
response = connection.request(:method => :post, :path => '/body-sink', :headers => { 'Content-Type' => 'text/plain' }, :body => 'x' * 5_000_000)
|
81
83
|
response.body
|
82
84
|
end
|
@@ -110,6 +112,20 @@ def basic_tests(url = 'http://127.0.0.1:9292', options = {})
|
|
110
112
|
response.body
|
111
113
|
end
|
112
114
|
|
115
|
+
tests('with multi-byte strings') do
|
116
|
+
body = "\xC3\xBC" * 100
|
117
|
+
headers = { 'Custom' => body.dup }
|
118
|
+
if RUBY_VERSION >= '1.9'
|
119
|
+
body.force_encoding('BINARY')
|
120
|
+
headers['Custom'].force_encoding('UTF-8')
|
121
|
+
end
|
122
|
+
|
123
|
+
returns(body, 'properly concatenates request+headers and body') do
|
124
|
+
response = connection.request(:method => :post, :path => '/echo', :headers => headers, :body => body)
|
125
|
+
response.body
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
113
129
|
end
|
114
130
|
|
115
131
|
tests('PUT /echo') do
|
@@ -134,6 +150,20 @@ def basic_tests(url = 'http://127.0.0.1:9292', options = {})
|
|
134
150
|
response.body
|
135
151
|
end
|
136
152
|
|
153
|
+
tests('with multi-byte strings') do
|
154
|
+
body = "\xC3\xBC" * 100
|
155
|
+
headers = { 'Custom' => body.dup }
|
156
|
+
if RUBY_VERSION >= '1.9'
|
157
|
+
body.force_encoding('BINARY')
|
158
|
+
headers['Custom'].force_encoding('UTF-8')
|
159
|
+
end
|
160
|
+
|
161
|
+
returns(body, 'properly concatenates request+headers and body') do
|
162
|
+
response = connection.request(:method => :put, :path => '/echo', :headers => headers, :body => body)
|
163
|
+
response.body
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
137
167
|
end
|
138
168
|
|
139
169
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: excon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.26.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2013-
|
14
|
+
date: 2013-09-24 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: activesupport
|
@@ -178,10 +178,12 @@ files:
|
|
178
178
|
- lib/excon/constants.rb
|
179
179
|
- lib/excon/errors.rb
|
180
180
|
- lib/excon/middlewares/base.rb
|
181
|
+
- lib/excon/middlewares/decompress.rb
|
181
182
|
- lib/excon/middlewares/expects.rb
|
182
183
|
- lib/excon/middlewares/idempotent.rb
|
183
184
|
- lib/excon/middlewares/instrumentor.rb
|
184
185
|
- lib/excon/middlewares/mock.rb
|
186
|
+
- lib/excon/middlewares/redirect_follower.rb
|
185
187
|
- lib/excon/middlewares/response_parser.rb
|
186
188
|
- lib/excon/response.rb
|
187
189
|
- lib/excon/socket.rb
|
@@ -195,14 +197,17 @@ files:
|
|
195
197
|
- tests/data/xs
|
196
198
|
- tests/errors_tests.rb
|
197
199
|
- tests/header_tests.rb
|
200
|
+
- tests/middlewares/decompress_tests.rb
|
198
201
|
- tests/middlewares/idempotent_tests.rb
|
199
202
|
- tests/middlewares/instrumentation_tests.rb
|
200
203
|
- tests/middlewares/mock_tests.rb
|
204
|
+
- tests/middlewares/redirect_follower.rb
|
201
205
|
- tests/proxy_tests.rb
|
202
206
|
- tests/query_string_tests.rb
|
203
207
|
- tests/rackups/basic.rb
|
204
208
|
- tests/rackups/basic.ru
|
205
209
|
- tests/rackups/basic_auth.ru
|
210
|
+
- tests/rackups/deflater.ru
|
206
211
|
- tests/rackups/proxy.ru
|
207
212
|
- tests/rackups/query_string.ru
|
208
213
|
- tests/rackups/request_headers.ru
|
@@ -237,7 +242,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
237
242
|
version: '0'
|
238
243
|
segments:
|
239
244
|
- 0
|
240
|
-
hash: -
|
245
|
+
hash: -2550839664096284654
|
241
246
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
242
247
|
none: false
|
243
248
|
requirements:
|