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.
@@ -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
@@ -0,0 +1,3 @@
1
+ module Manticore
2
+ VERSION = "0.1.0"
3
+ 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