manticore 0.1.0-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.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
|