manticore 0.3.0-java → 0.3.1-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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0078d294015d5ca40a8e4afadc48973fb9246de1
4
- data.tar.gz: bcff310d2a92c1e592cac51f63859a3ee483b63f
3
+ metadata.gz: 60f70fa96f93b6d9092b0455c139ae2aaf077fcf
4
+ data.tar.gz: ea9ab057b99806987c30ff3198cefd1d0d6ae455
5
5
  SHA512:
6
- metadata.gz: ef8bfdbaf46d56810e0f137fb4cf96425a46bc22ecf157f9451093bcf9acf721ff4cff86161593ec7c95112a64793856e5345c19454569d0b3ca457c3216d99f
7
- data.tar.gz: cd2bcc65d8d377d8622fae139e0f4c5e91816bcd0cea3926c8d1a5503eda2e68c28ef179cae5f153a389cd8c1474bb5ffbb3f789408e197f8d297b385b741e0a
6
+ metadata.gz: 24112c6208d50c27c68745340cf29c87f6cc37a8abeebb3bc051d2dc37ba883465f78e027e3dc4b5d77122c2bc9c2d9c2b6377607715bff5c953e38f0171ddb1
7
+ data.tar.gz: db3909740b474447d5467290992aad8b51fa34c39006714d14848650686f3ffaa095c3e8674df2f7ae0c834d7ac8fda4cc291485e790f9f3f44289a2041a647e
checksums.yaml.gz.sig CHANGED
Binary file
data.tar.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## v0.3
2
- ### v0.3.0 (pending)
2
+ ### v0.3.1
3
+ * Added `automatic_retries` (default 3) parameter to client. The client will automatically retry requests that failed
4
+ due to socket exceptions and empty responses up to this number of times. The most practical effect of this setting is
5
+ to automatically retry when the pool reuses a connection that a client unexpectedly closed.
6
+ * Added `request_timeout` to the RequestConfig used to construct requests.
7
+ * Fixed implementation of the `:query` parameter for GET, HEAD, and DELETE requests.
8
+
9
+ ### v0.3.0
3
10
 
4
11
  * Major refactor of `Response`/`AsyncResponse` to eliminate redundant code. `AsyncResponse` has been removed and
5
12
  its functionality has been rolled into `Response`.
data/Gemfile CHANGED
@@ -3,8 +3,8 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in manticore.gemspec
4
4
  gemspec
5
5
 
6
- gem "net-http-server"
7
- gem "rspec"
8
- gem "httpclient"
9
- gem "rack"
6
+ gem "net-http-server", "~> 0.2"
7
+ gem "rspec", "~> 2.14"
8
+ gem "httpclient", "~> 2.3"
9
+ gem "rack", "~> 1.5"
10
10
  gem "rake-compiler"
@@ -77,7 +77,7 @@ public class Manticore implements Library {
77
77
  byte[] tmp = new byte[4096];
78
78
  int l;
79
79
  while((l = instream.read(tmp)) != -1) {
80
- block.call( context, RubyString.newString(context.getRuntime(), new ByteList(tmp, true), encoding) );
80
+ block.call( context, RubyString.newString(context.getRuntime(), new ByteList(tmp, 0, l, true), encoding) );
81
81
  }
82
82
  } finally {
83
83
  instream.close();
Binary file
Binary file
Binary file
data/lib/manticore.rb CHANGED
@@ -3,7 +3,7 @@ require 'uri'
3
3
  require 'cgi'
4
4
  require 'cgi/cookie'
5
5
 
6
- jars = ["httpcore-4.3.1", "httpclient-4.3.2-patched", "commons-logging-1.1.3", "commons-codec-1.6.jar"]
6
+ jars = ["httpcore-4.3.1", "httpclient-4.3.2-patched", "commons-logging-1.1.3", "commons-codec-1.6.jar", "httpmime-4.3.2.jar"]
7
7
  jars.each do |jar|
8
8
  begin
9
9
  require_relative "./jar/#{jar}"
@@ -70,7 +70,9 @@ module Manticore
70
70
  include_package "org.apache.http.auth"
71
71
  include_package "java.util.concurrent"
72
72
  include_package "org.apache.http.client.protocol"
73
+ include_package 'org.apache.http.conn.ssl'
73
74
  java_import "org.apache.http.HttpHost"
75
+
74
76
  include ProxiesInterface
75
77
 
76
78
  # The default maximum pool size for requests
@@ -108,8 +110,10 @@ module Manticore
108
110
  # @option options [integer] request_timeout (60) Sets the timeout for requests. Raises {Manticore::Timeout} on failure.
109
111
  # @option options [integer] connect_timeout (10) Sets the timeout for connections. Raises Manticore::Timeout on failure.
110
112
  # @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.
113
+ # @option options [boolean] tcp_no_delay (true) Enable or disable Nagle's algorithm
111
114
  # @option options [integer] request_timeout (60) Sets the timeout for a given request. Raises Manticore::Timeout on failure.
112
115
  # @option options [integer] max_redirects (5) Sets the maximum number of redirects to follow.
116
+ # @option options [integer] automatic_retries (3) Sets the number of times the client will automatically retry failed requests.
113
117
  # @option options [boolean] expect_continue (false) Enable support for HTTP 100
114
118
  # @option options [boolean] stale_check (false) Enable support for stale connection checking. Adds overhead.
115
119
  # @option options [String] proxy Proxy host in form: http://proxy.org:1234
@@ -126,6 +130,20 @@ module Manticore
126
130
  builder.disable_content_compression if options.fetch(:compression, true) == false
127
131
  builder.set_proxy get_proxy_host(options[:proxy]) if options.key?(:proxy)
128
132
 
133
+ builder.set_retry_handler do |exception, executionCount, context|
134
+ if (executionCount > options.fetch(:automatic_retries, 3))
135
+ false
136
+ else
137
+ case exception
138
+ when Java::OrgApacheHttp::NoHttpResponseException, Java::JavaNet::SocketException
139
+ context.setAttribute "retryCount", executionCount
140
+ true
141
+ else
142
+ false
143
+ end
144
+ end
145
+ end
146
+
129
147
  # This should make it easier to reuse connections
130
148
  # TODO: Determine what this actually does!
131
149
  # builder.disable_connection_state
@@ -138,7 +156,8 @@ module Manticore
138
156
  end
139
157
 
140
158
  socket_config_builder = SocketConfig.custom
141
- socket_config_builder.setSoTimeout( options.fetch(:socket_timeout, DEFAULT_SOCKET_TIMEOUT) * 1000 )
159
+ socket_config_builder.set_so_timeout( options.fetch(:socket_timeout, DEFAULT_SOCKET_TIMEOUT) * 1000 )
160
+ socket_config_builder.set_tcp_no_delay( options.fetch(:tcp_no_delay, true) )
142
161
  builder.set_default_socket_config socket_config_builder.build
143
162
 
144
163
  builder.set_connection_manager pool(options)
@@ -269,16 +288,24 @@ module Manticore
269
288
  HttpClientBuilder.create
270
289
  end
271
290
 
272
- def pool_builder
273
- PoolingHttpClientConnectionManager.new
291
+ def pool_builder(options)
292
+ if options.fetch(:ignore_ssl_validation, false)
293
+ context = SSLContexts.custom.load_trust_material(nil, TrustSelfSignedStrategy.new).build
294
+ sslsf = SSLConnectionSocketFactory.new(context, SSLConnectionSocketFactory::ALLOW_ALL_HOSTNAME_VERIFIER)
295
+ registry = RegistryBuilder.create.register("https", sslsf).build
296
+ PoolingHttpClientConnectionManager.new(registry)
297
+ else
298
+ PoolingHttpClientConnectionManager.new
299
+ end
274
300
  end
275
301
 
276
302
  def pool(options = {})
277
303
  @pool ||= begin
278
304
  @max_pool_size = options.fetch(:pool_max, DEFAULT_MAX_POOL_SIZE)
279
- cm = pool_builder
305
+ cm = pool_builder options
280
306
  cm.set_default_max_per_route options.fetch(:pool_max_per_route, @max_pool_size)
281
307
  cm.set_max_total @max_pool_size
308
+
282
309
  Thread.new {
283
310
  loop {
284
311
  cm.closeExpiredConnections
@@ -332,12 +359,12 @@ module Manticore
332
359
  def uri_from_url_and_options(url, options)
333
360
  uri = Addressable::URI.parse url
334
361
  if options[:query]
335
- uri.query_values ||= {}
362
+ v = uri.query_values || {}
336
363
  case options[:query]
337
364
  when Hash
338
- uri.query_values.merge! options[:query]
365
+ uri.query_values = v.merge options[:query]
339
366
  when String
340
- uri.query_values.merge! CGI.parse(options[:query])
367
+ uri.query_values = v.merge CGI.parse(options[:query])
341
368
  else
342
369
  raise "Queries must be hashes or strings"
343
370
  end
@@ -348,22 +375,25 @@ module Manticore
348
375
  def request_from_options(klass, url, options)
349
376
  req = klass.new uri_from_url_and_options(url, options).to_s
350
377
 
351
- if ( options[:params] || options[:body] ) &&
378
+ if ( options[:params] || options[:body] || options[:entity]) &&
352
379
  ( req.instance_of?(HttpPost) || req.instance_of?(HttpPatch) || req.instance_of?(HttpPut) )
353
380
  if options[:params]
354
381
  req.set_entity hash_to_entity(options[:params])
355
382
  elsif options[:body]
356
383
  req.set_entity StringEntity.new(options[:body])
384
+ elsif options[:entity]
385
+ req.set_entity options[:entity]
357
386
  end
358
387
  end
359
388
 
360
389
  if options.key?(:proxy) || options.key?(:connect_timeout) || options.key?(:socket_timeout) || options.key?(:max_redirects) || options.key?(:follow_redirects)
361
390
  config = RequestConfig.custom()
362
- config.set_proxy get_proxy_host(options[:proxy]) if options[:proxy]
363
- config.set_connect_timeout options[:connect_timeout] if options[:connect_timeout]
364
- config.set_socket_timeout options[:socket_timeout] if options[:socket_timeout]
365
- config.set_max_redirects options[:max_redirects] if options[:max_redirects]
366
- config.set_redirects_enabled !!options[:follow_redirects] if options.fetch(:follow_redirects, nil) != nil
391
+ config.set_proxy get_proxy_host(options[:proxy]) if options[:proxy]
392
+ config.set_connect_timeout options[:connect_timeout] if options[:connect_timeout]
393
+ config.set_socket_timeout options[:socket_timeout] if options[:socket_timeout]
394
+ config.set_max_redirects options[:max_redirects] if options[:max_redirects]
395
+ config.set_redirects_enabled !!options[:follow_redirects] if options.fetch(:follow_redirects, nil) != nil
396
+ config.set_connection_request_timeout options[:request_timeout] if options[:request_timeout]
367
397
  req.set_config config.build
368
398
  end
369
399
 
@@ -47,11 +47,12 @@ module Manticore
47
47
  @client.execute @request, self, @context
48
48
  execute_complete
49
49
  return self
50
- rescue Java::JavaNet::SocketTimeoutException, Java::OrgApacheHttpConn::ConnectTimeoutException, Java::OrgApacheHttp::NoHttpResponseException => e
50
+ rescue Java::JavaNet::SocketTimeoutException, Java::OrgApacheHttpConn::ConnectTimeoutException => e
51
51
  ex = Manticore::Timeout.new(e.get_cause)
52
52
  rescue Java::JavaNet::SocketException => e
53
53
  ex = Manticore::SocketException.new(e.get_cause)
54
- rescue Java::OrgApacheHttpClient::ClientProtocolException, Java::JavaxNetSsl::SSLHandshakeException, Java::OrgApacheHttpConn::HttpHostConnectException => e
54
+ rescue Java::OrgApacheHttpClient::ClientProtocolException, Java::JavaxNetSsl::SSLHandshakeException, Java::OrgApacheHttpConn::HttpHostConnectException,
55
+ Java::OrgApacheHttp::NoHttpResponseException, Java::OrgApacheHttp::ConnectionClosedException => e
55
56
  ex = Manticore::ClientProtocolException.new(e.get_cause)
56
57
  rescue Java::JavaNet::UnknownHostException => e
57
58
  ex = Manticore::ResolutionFailure.new(e.get_cause)
@@ -194,6 +195,10 @@ module Manticore
194
195
  alias_method :completed, :on_complete
195
196
  alias_method :on_completed, :on_complete
196
197
 
198
+ def times_retried
199
+ @context.get_attribute("retryCount") || 0
200
+ end
201
+
197
202
  private
198
203
 
199
204
  # Implementation of {http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/client/ResponseHandler.html#handleResponse(org.apache.http.HttpResponse) ResponseHandler#handleResponse}
@@ -1,3 +1,3 @@
1
1
  module Manticore
2
- VERSION = "0.3.0"
2
+ VERSION = "0.3.1"
3
3
  end
@@ -1,6 +1,10 @@
1
1
  require 'spec_helper'
2
2
 
3
+ java_import 'org.apache.http.entity.mime.MultipartEntityBuilder'
4
+ java_import 'org.apache.http.entity.ContentType'
5
+
3
6
  describe Manticore::Client do
7
+
4
8
  let(:client) { Manticore::Client.new }
5
9
 
6
10
  it "should fetch a URL and return a response" do
@@ -37,6 +41,24 @@ describe Manticore::Client do
37
41
  j["uri"]["port"].should == 55441
38
42
  end
39
43
 
44
+ describe "ignore_ssl_validation" do
45
+ context "when on" do
46
+ let(:client) { Manticore::Client.new ignore_ssl_validation: true }
47
+
48
+ it "should not break on SSL validation errors" do
49
+ expect { client.get("https://localhost:55444/").body }.to_not raise_exception
50
+ end
51
+ end
52
+
53
+ context "when off" do
54
+ let(:client) { Manticore::Client.new ignore_ssl_validation: false }
55
+
56
+ it "should break on SSL validation errors" do
57
+ expect { client.get("https://localhost:55444/").call }.to raise_exception(Manticore::ClientProtocolException)
58
+ end
59
+ end
60
+ end
61
+
40
62
  describe "lazy evaluation" do
41
63
  it "should not call synchronous requests by default" do
42
64
  req = client.get(local_server)
@@ -189,6 +211,13 @@ describe Manticore::Client do
189
211
  response = client.get(local_server)
190
212
  JSON.load(response.body)["method"].should == "GET"
191
213
  end
214
+
215
+ context "with a query" do
216
+ it "should work" do
217
+ response = client.get local_server, query: {foo: "bar"}
218
+ CGI.parse(JSON.load(response.body)["uri"]["query"])["foo"].should == ["bar"]
219
+ end
220
+ end
192
221
  end
193
222
 
194
223
  describe "#post" do
@@ -206,6 +235,13 @@ describe Manticore::Client do
206
235
  response = client.post(local_server, params: {key: "value"})
207
236
  JSON.load(response.body)["body"].should == "key=value"
208
237
  end
238
+
239
+ it "should send an arbitrary entity" do
240
+ f = open(__FILE__, "r").to_inputstream
241
+ multipart_entity = MultipartEntityBuilder.create.add_text_body("foo", "bar").add_binary_body("whatever", f , ContentType::TEXT_PLAIN, __FILE__)
242
+ response = client.post(local_server, entity: multipart_entity.build)
243
+ response.body.should match("should send an arbitrary entity")
244
+ end
209
245
  end
210
246
 
211
247
  describe "#put" do
@@ -258,14 +294,16 @@ describe Manticore::Client do
258
294
 
259
295
  describe "#execute!" do
260
296
  it "should perform multiple concurrent requests" do
261
- @times = []
262
- [55441, 55442].each do |port|
297
+ futures = [55441, 55442].map do |port|
263
298
  client.async.get("http://localhost:#{port}/?sleep=1").
264
- on_success {|response| @times << Time.now.to_f }
299
+ on_success do |response|
300
+ Time.now.to_f
301
+ end
265
302
  end
266
303
 
267
304
  client.execute!
268
- @times[0].should be_within(0.5).of(@times[1])
305
+ values = futures.map(&:callback_result)
306
+ (values[0] - values[1]).abs.should < 0.25
269
307
  end
270
308
 
271
309
  it "should return the results of the handler blocks" do
@@ -349,6 +387,74 @@ describe Manticore::Client do
349
387
  end
350
388
  end
351
389
 
390
+ context "with a misbehaving endpoint" do
391
+ before do
392
+ @socket = TCPServer.new 4567
393
+ @server = Thread.new do
394
+ puts "Accepting"
395
+ loop do
396
+ client = @socket.accept
397
+ client.puts([
398
+ "HTTP/1.1 200 OK",
399
+ "Keep-Alive: timeout=3000",
400
+ "Connection: Keep-Alive",
401
+ "Content-Length: 6",
402
+ "",
403
+ "Hello!"
404
+ ].join("\n"))
405
+ client.close
406
+ end
407
+ end
408
+ end
409
+
410
+ let(:client) { Manticore::Client.new keepalive: true, pool_max: 1 }
411
+
412
+ it "should retry 3 times by default" do
413
+ # The first time, reply with keepalive, then close the connection
414
+ # The second connection should succeed
415
+
416
+ request1 = client.get("http://localhost:4567/")
417
+ request2 = client.get("http://localhost:4567/")
418
+ expect { request1.call }.to_not raise_exception
419
+ expect { request2.call }.to_not raise_exception
420
+
421
+ request1.times_retried.should == 0
422
+ request2.times_retried.should == 1
423
+ end
424
+
425
+ context "when the max retry is restrictive" do
426
+ let(:client) { Manticore::Client.new keepalive: true, pool_max: 1, automatic_retries: 0 }
427
+
428
+ it "should retry 0 times and fail on the second request" do
429
+ # The first time, reply with keepalive, then close the connection
430
+ # The second connection should succeed
431
+ expect { client.get("http://localhost:4567/").call }.to_not raise_exception
432
+ expect { client.get("http://localhost:4567/").call }.to raise_exception(Manticore::SocketException)
433
+ end
434
+ end
435
+
436
+ context "when keepalive is off" do
437
+ let(:client) { Manticore::Client.new keepalive: false, pool_max: 1 }
438
+
439
+ it "should succeed without any retries" do
440
+ # The first time, reply with keepalive, then close the connection
441
+ # The second connection should succeed
442
+ request1 = client.get("http://localhost:4567/")
443
+ request2 = client.get("http://localhost:4567/")
444
+ expect { request1.call }.to_not raise_exception
445
+ expect { request2.call }.to_not raise_exception
446
+
447
+ request1.times_retried.should == 0
448
+ request2.times_retried.should == 0
449
+ end
450
+ end
451
+
452
+ after do
453
+ Thread.kill @server
454
+ @socket.close
455
+ end
456
+ end
457
+
352
458
  def get_connection(client, uri, &block)
353
459
  java_import "java.util.concurrent.TimeUnit"
354
460
  host = URI.parse(uri).host
data/spec/spec_helper.rb CHANGED
@@ -4,6 +4,8 @@ require 'manticore'
4
4
  require 'zlib'
5
5
  require 'json'
6
6
  require 'rack'
7
+ require 'webrick'
8
+ require 'webrick/https'
7
9
 
8
10
  PORT = 55441
9
11
 
@@ -34,7 +36,7 @@ def start_server(port = PORT)
34
36
  sleep(query["sleep"].to_f)
35
37
  end
36
38
 
37
- if cl = request[:headers]["Content-Length"]
39
+ if cl = request[:headers]["Content-Length"] || request[:headers]["Transfer-Encoding"] == "chunked"
38
40
  request[:body] = read_nonblock stream.socket
39
41
  end
40
42
 
@@ -46,6 +48,9 @@ def start_server(port = PORT)
46
48
  else
47
49
  [401, {'WWW-Authenticate' => 'Basic realm="test"'}, [""]]
48
50
  end
51
+ elsif request[:uri][:path] == "/failearly"
52
+ # Return an invalid HTTP response
53
+ []
49
54
  elsif match = request[:uri][:path].match(/\/cookies\/(\d)\/(\d)/)
50
55
  cookie_value = (request[:headers]["Cookie"] || "x=0").split("=").last.to_i
51
56
  if match[1].to_i == match[2].to_i
@@ -82,6 +87,21 @@ def stop_servers
82
87
  @servers.values.each(&:kill) if @servers
83
88
  end
84
89
 
90
+ def start_ssl_server(port)
91
+ cert_name = [
92
+ %w[CN localhost],
93
+ ]
94
+ @servers[port] = Thread.new {
95
+ server = WEBrick::HTTPServer.new(:Port => port, :SSLEnable => true, :SSLCertName => cert_name, :Logger => WEBrick::Log.new("/dev/null"))
96
+ server.mount_proc "/" do |req, res|
97
+ res.body = "hello!"
98
+ end
99
+
100
+ server.start
101
+ puts "Server started?"
102
+ }
103
+ end
104
+
85
105
  RSpec.configure do |c|
86
106
  require 'net/http/server'
87
107
 
@@ -89,6 +109,7 @@ RSpec.configure do |c|
89
109
  @server = {}
90
110
  start_server 55441
91
111
  start_server 55442
112
+ start_ssl_server 55444
92
113
  }
93
114
 
94
115
  c.after(:suite) { stop_servers }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: manticore
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: java
6
6
  authors:
7
7
  - Chris Heald
@@ -30,7 +30,7 @@ cert_chain:
30
30
  E7PWS50D9moUJ6xWcemf0qKYC87qBFh0ng73awjG9uf+13lMslqJRMtek8C92cvh
31
31
  +R9zgQlbeNjy9O1i
32
32
  -----END CERTIFICATE-----
33
- date: 2014-03-28 00:00:00.000000000 Z
33
+ date: 2014-08-08 00:00:00.000000000 Z
34
34
  dependencies:
35
35
  - !ruby/object:Gem::Dependency
36
36
  name: addressable
@@ -95,6 +95,7 @@ files:
95
95
  - lib/jar/commons-logging-1.1.3.jar
96
96
  - lib/jar/httpclient-4.3.2-patched.jar
97
97
  - lib/jar/httpcore-4.3.1.jar
98
+ - lib/jar/httpmime-4.3.2.jar
98
99
  - lib/jar/lazy_decompressing_stream.patch
99
100
  - lib/jar/manticore-ext.jar
100
101
  - lib/manticore.rb
@@ -133,7 +134,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
133
134
  version: '0'
134
135
  requirements: []
135
136
  rubyforge_project:
136
- rubygems_version: 2.2.1
137
+ rubygems_version: 2.2.2
137
138
  signing_key:
138
139
  specification_version: 4
139
140
  summary: Manticore is an HTTP client built on the Apache HttpCore components
metadata.gz.sig CHANGED
Binary file