manticore 0.1.0-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +3 -0
- data/APACHE-LICENSE-2.0.txt +202 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +28 -0
- data/README.md +120 -0
- data/Rakefile +8 -0
- data/lib/jar/commons-logging-1.1.3.jar +0 -0
- data/lib/jar/httpclient-4.3.2.jar +0 -0
- data/lib/jar/httpcore-4.3.1.jar +0 -0
- data/lib/manticore/async_response.rb +80 -0
- data/lib/manticore/client.rb +391 -0
- data/lib/manticore/facade.rb +40 -0
- data/lib/manticore/response.rb +76 -0
- data/lib/manticore/version.rb +3 -0
- data/lib/manticore.rb +27 -0
- data/manticore.gemspec +25 -0
- data/spec/manticore/client_spec.rb +178 -0
- data/spec/manticore/facade_spec.rb +41 -0
- data/spec/manticore/response_spec.rb +29 -0
- data/spec/spec_helper.rb +74 -0
- metadata +111 -0
@@ -0,0 +1,391 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Manticore
|
4
|
+
# General Timeout exception thrown for various Manticore timeouts
|
5
|
+
class Timeout < ManticoreException; end
|
6
|
+
|
7
|
+
# Core Manticore client, with a backing {http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/impl/conn/PoolingHttpClientConnectionManager.html PoolingHttpClientConnectionManager}
|
8
|
+
class Client
|
9
|
+
include_package "org.apache.http.client.methods"
|
10
|
+
include_package "org.apache.http.client.entity"
|
11
|
+
include_package "org.apache.http.client.config"
|
12
|
+
include_package "org.apache.http.config"
|
13
|
+
include_package "org.apache.http.impl"
|
14
|
+
include_package "org.apache.http.impl.client"
|
15
|
+
include_package "org.apache.http.impl.conn"
|
16
|
+
include_package "org.apache.http.entity"
|
17
|
+
include_package "org.apache.http.message"
|
18
|
+
include_package "org.apache.http.params"
|
19
|
+
include_package "org.apache.http.protocol"
|
20
|
+
include_package "java.util.concurrent"
|
21
|
+
java_import 'java.util.concurrent.TimeUnit'
|
22
|
+
java_import 'java.util.concurrent.CountDownLatch'
|
23
|
+
java_import 'java.util.concurrent.LinkedBlockingQueue'
|
24
|
+
|
25
|
+
# The default maximum pool size for requests
|
26
|
+
DEFAULT_MAX_POOL_SIZE = 50
|
27
|
+
|
28
|
+
# The default maximum number of threads per route that will be permitted
|
29
|
+
DEFAULT_MAX_PER_ROUTE = 2
|
30
|
+
|
31
|
+
DEFAULT_REQUEST_TIMEOUT = 60
|
32
|
+
DEFAULT_SOCKET_TIMEOUT = 10
|
33
|
+
DEFAULT_CONNECT_TIMEOUT = 10
|
34
|
+
DEFAULT_MAX_REDIRECTS = 5
|
35
|
+
DEFAULT_EXPECT_CONTINUE = false
|
36
|
+
DEFAULT_STALE_CHECK = false
|
37
|
+
|
38
|
+
# Create a new HTTP client with a backing request pool. if you pass a block to the initializer, the underlying
|
39
|
+
# {http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/impl/client/HttpClientBuilder.html HttpClientBuilder}
|
40
|
+
# and {http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/client/config/RequestConfig.Builder.html RequestConfig.Builder}
|
41
|
+
# will be yielded so that you can operate on them directly.
|
42
|
+
#
|
43
|
+
# @see http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/impl/client/HttpClientBuilder.html HttpClientBuilder
|
44
|
+
# @see http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/client/config/RequestConfig.Builder.html RequestConfig.Builder
|
45
|
+
# @example Simple instantiation and usage
|
46
|
+
# client = Manticore::Client.new
|
47
|
+
# client.get("http://www.google.com")
|
48
|
+
#
|
49
|
+
# @example Instantiation with a block
|
50
|
+
# client = Manticore::Client.new(socket_timeout: 5) do |http_client_builder, request_builder|
|
51
|
+
# http_client_builder.disable_redirect_handling
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# @param options [Hash] Client pool options
|
55
|
+
# @option options [String] user_agent The user agent used in requests.
|
56
|
+
# @option options [Integer] pool_max (50) The maximum number of active connections in the pool
|
57
|
+
# @option options [integer] pool_max_per_route (2) Sets the maximum number of active connections for a given target endpoint
|
58
|
+
# @option options [boolean] cookies (true) enable or disable automatic cookie management between requests
|
59
|
+
# @option options [boolean] compression (true) enable or disable transparent gzip/deflate support
|
60
|
+
# @option options [integer] request_timeout (60) Sets the timeout for requests. Raises Manticore::Timeout on failure.
|
61
|
+
# @option options [integer] connect_timeout (10) Sets the timeout for connections. Raises Manticore::Timeout on failure.
|
62
|
+
# @option options [integer] socket_timeout (10) Sets SO_TIMEOUT for open connections. A value of 0 is an infinite timeout. Raises Manticore::Timeout on failure.
|
63
|
+
# @option options [integer] request_timeout (60) Sets the timeout for a given request. Raises Manticore::Timeout on failure.
|
64
|
+
# @option options [integer] max_redirects (5) Sets the maximum number of redirects to follow.
|
65
|
+
# @option options [boolean] expect_continue (false) Enable support for HTTP 100
|
66
|
+
# @option options [boolean] stale_check (false) Enable support for stale connection checking. Adds overhead.
|
67
|
+
def initialize(options = {})
|
68
|
+
builder = client_builder
|
69
|
+
builder.set_user_agent options.fetch(:user_agent, "Manticore #{VERSION}")
|
70
|
+
builder.disable_cookie_management unless options.fetch(:cookies, false)
|
71
|
+
builder.disable_content_compression if options.fetch(:compression, true) == false
|
72
|
+
|
73
|
+
# This should make it easier to reuse connections
|
74
|
+
builder.disable_connection_state
|
75
|
+
builder.set_connection_reuse_strategy DefaultConnectionReuseStrategy.new
|
76
|
+
|
77
|
+
# socket_config = SocketConfig.custom.set_tcp_no_delay(true).build
|
78
|
+
builder.set_connection_manager pool(options)
|
79
|
+
|
80
|
+
request_config = RequestConfig.custom
|
81
|
+
request_config.set_connection_request_timeout options.fetch(:request_timeout, DEFAULT_REQUEST_TIMEOUT) * 1000
|
82
|
+
request_config.set_connect_timeout options.fetch(:connect_timeout, DEFAULT_CONNECT_TIMEOUT) * 1000
|
83
|
+
request_config.set_socket_timeout options.fetch(:socket_timeout, DEFAULT_SOCKET_TIMEOUT) * 1000
|
84
|
+
request_config.set_max_redirects options.fetch(:max_redirects, DEFAULT_MAX_REDIRECTS)
|
85
|
+
request_config.set_expect_continue_enabled options.fetch(:expect_continue, DEFAULT_EXPECT_CONTINUE)
|
86
|
+
request_config.set_stale_connection_check_enabled options.fetch(:stale_check, DEFAULT_STALE_CHECK)
|
87
|
+
# request_config.set_authentication_enabled options.fetch(:use_auth, false)
|
88
|
+
request_config.set_circular_redirects_allowed false
|
89
|
+
|
90
|
+
yield builder, request_config if block_given?
|
91
|
+
|
92
|
+
builder.set_default_request_config request_config.build
|
93
|
+
@client = builder.build
|
94
|
+
@options = options
|
95
|
+
@async_requests = []
|
96
|
+
end
|
97
|
+
|
98
|
+
### Sync methods
|
99
|
+
|
100
|
+
# Perform a HTTP GET request
|
101
|
+
# @param url [String] URL to request
|
102
|
+
# @param options [Hash]
|
103
|
+
# @option options [Hash] params Hash of options to pass as request parameters
|
104
|
+
# @option options [Hash] headers Hash of options to pass as additional request headers
|
105
|
+
#
|
106
|
+
# @return [Response]
|
107
|
+
def get(url, options = {}, &block)
|
108
|
+
request HttpGet, url, options, &block
|
109
|
+
end
|
110
|
+
|
111
|
+
# Perform a HTTP PUT request
|
112
|
+
# @param url [String] URL to request
|
113
|
+
# @param options [Hash]
|
114
|
+
# @option options [Hash] params Hash of options to pass as request parameters
|
115
|
+
# @option options [Hash] body Hash of options to pass as request body
|
116
|
+
# @option options [Hash] headers Hash of options to pass as additional request headers
|
117
|
+
#
|
118
|
+
# @return [Response]
|
119
|
+
def put(url, options = {}, &block)
|
120
|
+
request HttpPut, url, options, &block
|
121
|
+
end
|
122
|
+
|
123
|
+
# Perform a HTTP HEAD request
|
124
|
+
# @param url [String] URL to request
|
125
|
+
# @param options [Hash]
|
126
|
+
# @option options [Hash] params Hash of options to pass as request parameters
|
127
|
+
# @option options [Hash] headers Hash of options to pass as additional request headers
|
128
|
+
#
|
129
|
+
# @return [Response]
|
130
|
+
def head(url, options = {}, &block)
|
131
|
+
request HttpHead, url, options, &block
|
132
|
+
end
|
133
|
+
|
134
|
+
# Perform a HTTP POST request
|
135
|
+
# @param url [String] URL to request
|
136
|
+
# @param options [Hash]
|
137
|
+
# @option options [Hash] params Hash of options to pass as request parameters
|
138
|
+
# @option options [Hash] body Hash of options to pass as request body
|
139
|
+
# @option options [Hash] headers Hash of options to pass as additional request headers
|
140
|
+
#
|
141
|
+
# @return [Response]
|
142
|
+
def post(url, options = {}, &block)
|
143
|
+
request HttpPost, url, options, &block
|
144
|
+
end
|
145
|
+
|
146
|
+
# Perform a HTTP DELETE request
|
147
|
+
# @param url [String] URL to request
|
148
|
+
# @param options [Hash]
|
149
|
+
# @option options [Hash] params Hash of options to pass as request parameters
|
150
|
+
# @option options [Hash] headers Hash of options to pass as additional request headers
|
151
|
+
#
|
152
|
+
# @return [Response]
|
153
|
+
def delete(url, options = {}, &block)
|
154
|
+
request HttpDelete, url, options, &block
|
155
|
+
end
|
156
|
+
|
157
|
+
# Perform a HTTP OPTIONS request
|
158
|
+
# @param url [String] URL to request
|
159
|
+
# @param options [Hash]
|
160
|
+
# @option options [Hash] params Hash of options to pass as request parameters
|
161
|
+
# @option options [Hash] headers Hash of options to pass as additional request headers
|
162
|
+
#
|
163
|
+
# @return [Response]
|
164
|
+
def options(url, options = {}, &block)
|
165
|
+
request HttpOptions, url, options, &block
|
166
|
+
end
|
167
|
+
|
168
|
+
# Perform a HTTP PATCH request
|
169
|
+
# @param url [String] URL to request
|
170
|
+
# @param options [Hash]
|
171
|
+
# @option options [Hash] params Hash of options to pass as request parameters
|
172
|
+
# @option options [Hash] body Hash of options to pass as request body
|
173
|
+
# @option options [Hash] headers Hash of options to pass as additional request headers
|
174
|
+
#
|
175
|
+
# @return [Response]
|
176
|
+
def patch(url, options = {}, &block)
|
177
|
+
request HttpPatch, url, options, &block
|
178
|
+
end
|
179
|
+
|
180
|
+
### Async methods
|
181
|
+
|
182
|
+
# Queue an asynchronous HTTP GET request
|
183
|
+
# @param url [String] URL to request
|
184
|
+
# @param options [Hash]
|
185
|
+
# @option options [Hash] params Hash of options to pass as request parameters
|
186
|
+
# @option options [Hash] headers Hash of options to pass as additional request headers
|
187
|
+
#
|
188
|
+
# @return [Response]
|
189
|
+
def async_get(url, options = {}, &block)
|
190
|
+
get url, options.merge(async: true), &block
|
191
|
+
end
|
192
|
+
|
193
|
+
# Queue an asynchronous HTTP HEAD request
|
194
|
+
# @param url [String] URL to request
|
195
|
+
# @param options [Hash]
|
196
|
+
# @option options [Hash] params Hash of options to pass as request parameters
|
197
|
+
# @option options [Hash] headers Hash of options to pass as additional request headers
|
198
|
+
#
|
199
|
+
# @return [Response]
|
200
|
+
def async_head(url, options = {}, &block)
|
201
|
+
head url, options.merge(async: true), &block
|
202
|
+
end
|
203
|
+
|
204
|
+
# Queue an asynchronous HTTP PUT request
|
205
|
+
# @param url [String] URL to request
|
206
|
+
# @param options [Hash]
|
207
|
+
# @option options [Hash] params Hash of options to pass as request parameters
|
208
|
+
# @option options [Hash] body Hash of options to pass as request body
|
209
|
+
# @option options [Hash] headers Hash of options to pass as additional request headers
|
210
|
+
#
|
211
|
+
# @return [Response]
|
212
|
+
def async_put(url, options = {}, &block)
|
213
|
+
put url, options.merge(async: true), &block
|
214
|
+
end
|
215
|
+
|
216
|
+
# Queue an asynchronous HTTP POST request
|
217
|
+
# @param url [String] URL to request
|
218
|
+
# @param options [Hash]
|
219
|
+
# @option options [Hash] params Hash of options to pass as request parameters
|
220
|
+
# @option options [Hash] body Hash of options to pass as request body
|
221
|
+
# @option options [Hash] headers Hash of options to pass as additional request headers
|
222
|
+
#
|
223
|
+
# @return [Response]
|
224
|
+
def async_post(url, options = {}, &block)
|
225
|
+
post url, options.merge(async: true), &block
|
226
|
+
end
|
227
|
+
|
228
|
+
# Queue an asynchronous HTTP DELETE request
|
229
|
+
# @param url [String] URL to request
|
230
|
+
# @param options [Hash]
|
231
|
+
# @option options [Hash] params Hash of options to pass as request parameters
|
232
|
+
# @option options [Hash] headers Hash of options to pass as additional request headers
|
233
|
+
#
|
234
|
+
# @return [Response]
|
235
|
+
def async_delete(url, options = {}, &block)
|
236
|
+
delete url, options.merge(async: true), &block
|
237
|
+
end
|
238
|
+
|
239
|
+
# Queue an asynchronous HTTP OPTIONS request
|
240
|
+
# @param url [String] URL to request
|
241
|
+
# @param options [Hash]
|
242
|
+
# @option options [Hash] params Hash of options to pass as request parameters
|
243
|
+
# @option options [Hash] headers Hash of options to pass as additional request headers
|
244
|
+
#
|
245
|
+
# @return [Response]
|
246
|
+
def async_options(url, options = {}, &block)
|
247
|
+
options url, options.merge(async: true), &block
|
248
|
+
end
|
249
|
+
|
250
|
+
# Queue an asynchronous HTTP PATCH request
|
251
|
+
# @param url [String] URL to request
|
252
|
+
# @param options [Hash]
|
253
|
+
# @option options [Hash] params Hash of options to pass as request parameters
|
254
|
+
# @option options [Hash] body Hash of options to pass as request body
|
255
|
+
# @option options [Hash] headers Hash of options to pass as additional request headers
|
256
|
+
#
|
257
|
+
# @return [Response]
|
258
|
+
def async_patch(url, options = {}, &block)
|
259
|
+
patch url, options.merge(async: true), &block
|
260
|
+
end
|
261
|
+
|
262
|
+
# Remove all pending asynchronous requests.
|
263
|
+
#
|
264
|
+
# @return nil
|
265
|
+
def clear_pending
|
266
|
+
@async_requests.clear
|
267
|
+
nil
|
268
|
+
end
|
269
|
+
|
270
|
+
# Execute all queued async requests
|
271
|
+
#
|
272
|
+
# @return [Array] An array of the results of the on_success bodies for the requests executed.
|
273
|
+
def execute!
|
274
|
+
tasks = @async_requests.map do |request, response|
|
275
|
+
task = FutureTask.new(response)
|
276
|
+
@executor.submit task
|
277
|
+
task
|
278
|
+
end
|
279
|
+
@async_requests.clear
|
280
|
+
tasks.map do |task|
|
281
|
+
begin
|
282
|
+
response = task.get
|
283
|
+
rescue => e
|
284
|
+
raise e.getCause
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
protected
|
290
|
+
|
291
|
+
def client_builder
|
292
|
+
HttpClientBuilder.create
|
293
|
+
end
|
294
|
+
|
295
|
+
def pool_builder
|
296
|
+
PoolingHttpClientConnectionManager.new
|
297
|
+
end
|
298
|
+
|
299
|
+
def pool(options = {})
|
300
|
+
@pool ||= begin
|
301
|
+
@max_pool_size = options.fetch(:pool_max, DEFAULT_MAX_POOL_SIZE)
|
302
|
+
cm = pool_builder
|
303
|
+
cm.set_default_max_per_route options.fetch(:pool_max_per_route, DEFAULT_MAX_PER_ROUTE)
|
304
|
+
cm.set_max_total @max_pool_size
|
305
|
+
Thread.new {
|
306
|
+
loop {
|
307
|
+
cm.closeExpiredConnections
|
308
|
+
sleep 5000
|
309
|
+
}
|
310
|
+
}
|
311
|
+
cm
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
def create_executor_if_needed
|
316
|
+
return @executor if @executor
|
317
|
+
@executor = Executors.new_cached_thread_pool
|
318
|
+
at_exit { @executor.shutdown }
|
319
|
+
end
|
320
|
+
|
321
|
+
def request(klass, url, options, &block)
|
322
|
+
req = request_from_options(klass, url, options)
|
323
|
+
if options.delete(:async)
|
324
|
+
async_request req, &block
|
325
|
+
else
|
326
|
+
sync_request req, &block
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
def async_request(request, &block)
|
331
|
+
create_executor_if_needed
|
332
|
+
response = AsyncResponse.new(@client, request, BasicHttpContext.new, block)
|
333
|
+
@async_requests << [request, response]
|
334
|
+
response
|
335
|
+
end
|
336
|
+
|
337
|
+
def sync_request(request, &block)
|
338
|
+
response = Response.new(request, BasicHttpContext.new, block)
|
339
|
+
begin
|
340
|
+
@client.execute request, response, response.context
|
341
|
+
response
|
342
|
+
rescue Java::JavaNet::SocketTimeoutException, Java::OrgApacheHttpConn::ConnectTimeoutException, Java::OrgApacheHttp::NoHttpResponseException => e
|
343
|
+
raise Manticore::Timeout.new(e.get_cause)
|
344
|
+
rescue Java::OrgApacheHttpClient::ClientProtocolException => e
|
345
|
+
raise Manticore::ClientProtocolException.new(e.get_cause)
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
def uri_from_url_and_options(url, options)
|
350
|
+
uri = Addressable::URI.parse url
|
351
|
+
if options[:query]
|
352
|
+
uri.query_values ||= {}
|
353
|
+
case options[:query]
|
354
|
+
when Hash
|
355
|
+
uri.query_values.merge! options[:query]
|
356
|
+
when String
|
357
|
+
uri.query_values.merge! CGI.parse(options[:query])
|
358
|
+
else
|
359
|
+
raise "Queries must be hashes or strings"
|
360
|
+
end
|
361
|
+
end
|
362
|
+
uri
|
363
|
+
end
|
364
|
+
|
365
|
+
def request_from_options(klass, url, options)
|
366
|
+
req = klass.new uri_from_url_and_options(url, options).to_s
|
367
|
+
|
368
|
+
if ( options[:params] || options[:body] ) &&
|
369
|
+
( req.instance_of?(HttpPost) || req.instance_of?(HttpPatch) || req.instance_of?(HttpPut) )
|
370
|
+
if options[:params]
|
371
|
+
req.set_entity hash_to_entity(options[:params])
|
372
|
+
elsif options[:body]
|
373
|
+
req.set_entity StringEntity.new(options[:body])
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
if options[:headers]
|
378
|
+
options[:headers].each {|k, v| req.set_header k, v }
|
379
|
+
end
|
380
|
+
|
381
|
+
req
|
382
|
+
end
|
383
|
+
|
384
|
+
def hash_to_entity(hash)
|
385
|
+
pairs = hash.map do |key, val|
|
386
|
+
BasicNameValuePair.new(key, val)
|
387
|
+
end
|
388
|
+
UrlEncodedFormEntity.new(pairs)
|
389
|
+
end
|
390
|
+
end
|
391
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Manticore
|
4
|
+
# Mix-in that can be used to add Manticore functionality to arbitrary classes.
|
5
|
+
# Its primary purpose is to extend the Manticore module for one-shot usage.
|
6
|
+
#
|
7
|
+
# @example Simple mix-in usage
|
8
|
+
# class Foo
|
9
|
+
# include Manticore::Facade
|
10
|
+
# include_http_client pool_size: 5
|
11
|
+
#
|
12
|
+
# def latest_results
|
13
|
+
# Foo.get "http://some.com/url"
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
module Facade
|
17
|
+
# @private
|
18
|
+
def self.included(other)
|
19
|
+
other.send :extend, ClassMethods
|
20
|
+
end
|
21
|
+
|
22
|
+
module ClassMethods
|
23
|
+
# Adds basic synchronous Manticore::Client functionality to the receiver.
|
24
|
+
# @param options Hash Options to be passed to the underlying shared client, if it has not been created yet.
|
25
|
+
# @return nil
|
26
|
+
def include_http_client(options = {}, &block)
|
27
|
+
if shared_pool = options.delete(:shared_pool)
|
28
|
+
@manticore_facade = Manticore.instance_variable_get("@manticore_facade")
|
29
|
+
else
|
30
|
+
@manticore_facade = Manticore::Client.new(options, &block)
|
31
|
+
end
|
32
|
+
class << self
|
33
|
+
extend Forwardable
|
34
|
+
def_delegators "@manticore_facade", :get, :head, :put, :post, :delete, :options, :patch
|
35
|
+
end
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Manticore
|
2
|
+
# Implementation of {http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/client/ResponseHandler.html ResponseHandler} which serves
|
3
|
+
# as a Ruby proxy for HTTPClient responses.
|
4
|
+
#
|
5
|
+
# @!attribute [r] headers
|
6
|
+
# @return [Hash] Headers from this response
|
7
|
+
# @!attribute [r] code
|
8
|
+
# @return [Integer] Response code from this response
|
9
|
+
# @!attribute [r] context
|
10
|
+
# @return [HttpContext] Context associated with this request/response
|
11
|
+
class Response
|
12
|
+
include_package "org.apache.http.client"
|
13
|
+
include_package "org.apache.http.util"
|
14
|
+
include_package "org.apache.http.protocol"
|
15
|
+
include ResponseHandler
|
16
|
+
|
17
|
+
attr_reader :headers, :code, :context
|
18
|
+
|
19
|
+
# Creates a new Response
|
20
|
+
#
|
21
|
+
# @param request [HttpRequestBase] The underlying request object
|
22
|
+
# @param context [HttpContext] The underlying HttpContext
|
23
|
+
# @param body_handler_block [Proc] And optional block to by yielded to for handling this response
|
24
|
+
def initialize(request, context, body_handler_block)
|
25
|
+
@request = request
|
26
|
+
@context = context
|
27
|
+
@handler_block = body_handler_block
|
28
|
+
end
|
29
|
+
|
30
|
+
# Implementation of {http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/client/ResponseHandler.html#handleResponse(org.apache.http.HttpResponse) ResponseHandler#handleResponse}
|
31
|
+
# @param response [Response] The underlying Java Response object
|
32
|
+
def handle_response(response)
|
33
|
+
@response = response
|
34
|
+
@code = response.get_status_line.get_status_code
|
35
|
+
@headers = Hash[* response.get_all_headers.flat_map {|h| [h.get_name.downcase, h.get_value]} ]
|
36
|
+
if @handler_block
|
37
|
+
@handler_block.call(self)
|
38
|
+
else
|
39
|
+
read_body
|
40
|
+
end
|
41
|
+
# ensure
|
42
|
+
# @request.release_connection
|
43
|
+
end
|
44
|
+
|
45
|
+
# Fetch the final resolved URL for this response
|
46
|
+
#
|
47
|
+
# @return [String]
|
48
|
+
def final_url
|
49
|
+
last_request = context.get_attribute ExecutionContext.HTTP_REQUEST
|
50
|
+
last_host = context.get_attribute ExecutionContext.HTTP_TARGET_HOST
|
51
|
+
host = last_host.to_uri
|
52
|
+
url = last_request.get_uri
|
53
|
+
URI.join(host, url.to_s)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Fetch the body content of this response
|
57
|
+
#
|
58
|
+
# @return [String] Reponse body
|
59
|
+
def read_body
|
60
|
+
@body ||= begin
|
61
|
+
entity = @response.get_entity
|
62
|
+
entity && EntityUtils.to_string(entity)
|
63
|
+
rescue Java::JavaIo::IOException, Java::JavaNet::SocketException => e
|
64
|
+
raise StreamClosedException.new("Could not read from stream: #{e.message} (Did you forget to read #body from your block?)")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
alias_method :body, :read_body
|
68
|
+
|
69
|
+
# Returns the length of the response body. Returns -1 if content-length is not present in the response.
|
70
|
+
#
|
71
|
+
# @return [Integer]
|
72
|
+
def length
|
73
|
+
(@headers["content-length"] || -1).to_i
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/manticore.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'java'
|
2
|
+
require_relative "./jar/httpcore-4.3.1"
|
3
|
+
require_relative "./jar/httpclient-4.3.2"
|
4
|
+
require_relative "./jar/commons-logging-1.1.3"
|
5
|
+
require_relative "./manticore/version"
|
6
|
+
require "addressable/uri"
|
7
|
+
|
8
|
+
# HTTP client with the body of a lion and the head of a man. Or more simply, the power of Java
|
9
|
+
# with the beauty of Ruby.
|
10
|
+
module Manticore
|
11
|
+
# General base class for all Manticore exceptions
|
12
|
+
class ManticoreException < StandardError; end
|
13
|
+
|
14
|
+
# Exception thrown if you attempt to read from a closed Response stream
|
15
|
+
class StreamClosedException < ManticoreException; end
|
16
|
+
|
17
|
+
# Friendly wrapper for various Java ClientProtocolExceptions
|
18
|
+
class ClientProtocolException < ManticoreException; end
|
19
|
+
|
20
|
+
require_relative './manticore/client'
|
21
|
+
require_relative './manticore/response'
|
22
|
+
require_relative './manticore/async_response'
|
23
|
+
require_relative './manticore/facade'
|
24
|
+
|
25
|
+
include Facade
|
26
|
+
include_http_client
|
27
|
+
end
|
data/manticore.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'manticore/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "manticore"
|
8
|
+
spec.version = Manticore::VERSION
|
9
|
+
spec.authors = ["Chris Heald"]
|
10
|
+
spec.email = ["cheald@mashable.com"]
|
11
|
+
spec.description = %q{Manticore is an HTTP client built on the Apache HttpCore components}
|
12
|
+
spec.summary = %q{Manticore is an HTTP client built on the Apache HttpCore components}
|
13
|
+
spec.homepage = "https://github.com/cheald/manticore"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.platform = 'java'
|
16
|
+
|
17
|
+
spec.files = `git ls-files`.split($/)
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_dependency "addressable", "~> 2.3"
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
end
|