manticore 0.1.0-java → 0.2.0-java

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3adc56dba6cc75385969e1b3654322a1c23c71f9
4
- data.tar.gz: d695c2db318e5eaec73f6cdbd5641df4fd51af07
3
+ metadata.gz: e0ee8e57988bc31f65a09326d862683aed668e2a
4
+ data.tar.gz: 7d7247a8d5087e9630b2ffed540abd16f4930bbd
5
5
  SHA512:
6
- metadata.gz: 9d09b50c6d313f09bd09cd0948905033079badb23f972430b29edfb6275312c750dc72d8274f76860d093747fd717de98436b7eddd4fb971df9998da4cc68907
7
- data.tar.gz: 4d290e644a3a9426608f378aef1deaf625c1fdba81c36d303e63211c884e55391a6aab445eaddc2618df3a261a28bc0cc819af09ac8b44ca9ee5f9d620f2806e
6
+ metadata.gz: 0728f990ebb06a7251267eb121d1be4bf108a3b13fb742a62289eea6d41f4d376125b79878d2ccb8bed83158783fba1170f826f14fd2b443f31d52c64cff5580
7
+ data.tar.gz: 3b32c8017107ce22d1da41b56faa3fab5e3a57749cd47814c86ed2632d771c7d923637e1c359b0f28cf0855089f3343ba921d47b0a0b79d8336c85f63b5999be
data/Gemfile CHANGED
@@ -6,4 +6,5 @@ gemspec
6
6
  gem "net-http-server"
7
7
  gem "rspec"
8
8
  gem "httpclient"
9
- gem "rack"
9
+ gem "rack"
10
+ gem "rake-compiler"
data/README.md CHANGED
@@ -22,6 +22,10 @@ Or install it yourself as:
22
22
 
23
23
  Documentation is available [at rubydoc.info](http://rubydoc.info/github/cheald/manticore/master/frames).
24
24
 
25
+ ## Performance
26
+
27
+ Manticore is [very fast](https://github.com/cheald/manticore/wiki/Performance).
28
+
25
29
  ## Major Features
26
30
 
27
31
  As it's built on the Apache Commons HTTP components, Manticore is very rich. It includes support for:
@@ -89,7 +93,7 @@ Manticore can perform multiple concurrent execution of requests.
89
93
  # These aren't actually executed until #execute! is called.
90
94
  # You can define response handlers in a block when you queue the request:
91
95
  client.async_get("http://www.google.com") {|req|
92
- req.on_success do |response, request|
96
+ req.on_success do |response|
93
97
  puts response.body
94
98
  end
95
99
 
@@ -100,13 +104,13 @@ Manticore can perform multiple concurrent execution of requests.
100
104
 
101
105
  # ...or by invoking the method on the queued response returned:
102
106
  response = client.async_get("http://www.yahoo.com")
103
- response.on_success do |response, request|
107
+ response.on_success do |response|
104
108
  puts "The length of the Yahoo! homepage is #{response.body.length}"
105
109
  end
106
110
 
107
111
  # ...or even by chaining them onto the call
108
112
  client.async_get("http://bing.com").
109
- on_success {|r, _| puts r.code }.
113
+ on_success {|r| puts r.code }.
110
114
  on_failure {|e| puts "on noes!"}
111
115
 
112
116
  client.execute!
data/Rakefile CHANGED
@@ -5,4 +5,17 @@ RSpec::Core::RakeTask.new(:spec) do |spec|
5
5
  spec.pattern = 'spec/**/*_spec.rb'
6
6
  spec.rspec_opts = ['--tty --color --format documentation']
7
7
  end
8
- task :default => :spec
8
+ task :default => :spec
9
+
10
+ require 'rake/javaextensiontask'
11
+
12
+ # Dependency jars for the Kerrigan ext build
13
+ jars = [
14
+ "#{ENV['MY_RUBY_HOME']}/lib/jruby.jar",
15
+ "lib/jar/httpcore-4.3.1.jar"
16
+ ]
17
+ Rake::JavaExtensionTask.new do |ext|
18
+ ext.name = "manticore-ext"
19
+ ext.lib_dir = "lib/jar"
20
+ ext.classpath = jars.map {|x| File.expand_path x}.join ':'
21
+ end
@@ -0,0 +1,96 @@
1
+ package org.manticore;
2
+
3
+ import java.io.IOException;
4
+ import java.io.InputStream;
5
+ import org.jruby.Ruby;
6
+ import org.jruby.RubyClass;
7
+ import org.jruby.RubyString;
8
+ import org.jruby.RubyObject;
9
+ import org.jruby.RubyModule;
10
+ import org.jruby.util.ByteList;
11
+ import org.jruby.anno.JRubyMethod;
12
+ import org.jruby.runtime.Block;
13
+ import org.jruby.runtime.ThreadContext;
14
+ import org.jruby.runtime.load.Library;
15
+ import org.jruby.runtime.ObjectAllocator;
16
+ import org.jruby.runtime.builtin.IRubyObject;
17
+ import org.apache.http.HttpEntity;
18
+ import org.apache.http.util.EntityUtils;
19
+ import org.apache.http.protocol.HTTP;
20
+ import org.jcodings.Encoding;
21
+
22
+ public class Manticore implements Library {
23
+
24
+ public void load(final Ruby ruby, boolean _) {
25
+ RubyModule manticore = ruby.defineModule("Manticore");
26
+ RubyClass converter = ruby.defineClassUnder("EntityConverter", ruby.getObject(), new ObjectAllocator() {
27
+ public IRubyObject allocate(Ruby ruby, RubyClass rc) {
28
+ return new EntityConverter(ruby, rc);
29
+ }
30
+ }, manticore);
31
+ converter.defineAnnotatedMethods(EntityConverter.class);
32
+ }
33
+
34
+ public class EntityConverter extends RubyObject {
35
+ public EntityConverter(Ruby ruby, RubyClass rubyClass) {
36
+ super(ruby, rubyClass);
37
+ }
38
+
39
+ // @JRubyMethod
40
+ // public IRubyObject initialize(ThreadContext context) {
41
+ // return context.nil;
42
+ // }
43
+
44
+ @JRubyMethod(name = "read_entity")
45
+ public IRubyObject readEntity(ThreadContext context, IRubyObject rEntity, Block block) throws IOException {
46
+ HttpEntity entity = (HttpEntity)rEntity.toJava(HttpEntity.class);
47
+ String charset = EntityUtils.getContentCharSet(entity);
48
+ if(charset == null) { charset = HTTP.DEFAULT_CONTENT_CHARSET; }
49
+ Encoding encoding;
50
+ try {
51
+ encoding = context.getRuntime().getEncodingService().getEncodingFromString(charset);
52
+ } catch(Throwable e) {
53
+ encoding = context.getRuntime().getEncodingService().getEncodingFromString(HTTP.DEFAULT_CONTENT_CHARSET);
54
+ }
55
+
56
+ if(block.isGiven()) {
57
+ return streamEntity(context, entity, encoding, block);
58
+ } else {
59
+ return readWholeEntity(context, entity, encoding);
60
+ }
61
+ }
62
+
63
+ // @JRubyMethod(name = "read_entity")
64
+ private IRubyObject readWholeEntity(ThreadContext context, HttpEntity entity, Encoding encoding) throws IOException {
65
+ ByteList bl = new ByteList(EntityUtils.toByteArray(entity), false);
66
+ return RubyString.newStringShared(context.getRuntime(), bl, encoding);
67
+ }
68
+
69
+ // @JRubyMethod(name = "stream_entity")
70
+ private IRubyObject streamEntity(ThreadContext context, HttpEntity entity, Encoding encoding, Block block) throws IOException {
71
+ InputStream instream = entity.getContent();
72
+ if (instream == null) { return null; }
73
+ String charset = EntityUtils.getContentCharSet(entity);
74
+ if(charset == null) { charset = HTTP.DEFAULT_CONTENT_CHARSET; }
75
+
76
+ int i = (int)entity.getContentLength();
77
+ if (i < 0) { i = 4096; }
78
+
79
+ if (charset == null) {
80
+ charset = HTTP.DEFAULT_CONTENT_CHARSET;
81
+ }
82
+
83
+ try {
84
+ byte[] tmp = new byte[4096];
85
+ int l;
86
+ while((l = instream.read(tmp)) != -1) {
87
+ // String str = new String(tmp, charset);
88
+ block.call( context, RubyString.newString(context.getRuntime(), new ByteList(tmp, true), encoding) );
89
+ }
90
+ } finally {
91
+ instream.close();
92
+ }
93
+ return context.nil;
94
+ }
95
+ }
96
+ }
@@ -0,0 +1,17 @@
1
+ diff --git a/httpclient/src/main/java/org/apache/http/client/entity/LazyDecompressingInputStream.java b/httpclient/src/main/java/org/apache/http/client/entity/LazyDecompressingInputStream.java
2
+ index 26e4981..ef07caf 100644
3
+ --- a/httpclient/src/main/java/org/apache/http/client/entity/LazyDecompressingInputStream.java
4
+ +++ b/httpclient/src/main/java/org/apache/http/client/entity/LazyDecompressingInputStream.java
5
+ @@ -57,6 +57,12 @@ class LazyDecompressingInputStream extends InputStream {
6
+ }
7
+
8
+ @Override
9
+ + public int read(final byte[] b, final int off, final int len) throws IOException {
10
+ + initWrapper();
11
+ + return wrapperStream.read(b, off, len);
12
+ + }
13
+ +
14
+ + @Override
15
+ public int available() throws IOException {
16
+ initWrapper();
17
+ return wrapperStream.available();
Binary file
data/lib/manticore.rb CHANGED
@@ -1,7 +1,12 @@
1
1
  require 'java'
2
+ # 4.3.x
2
3
  require_relative "./jar/httpcore-4.3.1"
3
- require_relative "./jar/httpclient-4.3.2"
4
+ require_relative "./jar/httpclient-4.3.2-patched"
4
5
  require_relative "./jar/commons-logging-1.1.3"
6
+ require_relative "./jar/manticore-ext"
7
+
8
+ org.manticore.Manticore.new.load(JRuby.runtime, false)
9
+
5
10
  require_relative "./manticore/version"
6
11
  require "addressable/uri"
7
12
 
@@ -10,9 +10,9 @@ module Manticore
10
10
  def initialize(client, request, context, body_handler_block)
11
11
  @client = client
12
12
  @handlers = {
13
- success: ->(request){},
14
- failure: ->(ex){},
15
- cancelled: ->{}
13
+ success: Proc.new {|resp| resp.body },
14
+ failure: Proc.new {},
15
+ cancelled: Proc.new {}
16
16
  }
17
17
  body_handler_block.call(self) if body_handler_block
18
18
  super request, context, nil
@@ -23,6 +23,7 @@ module Manticore
23
23
  def call
24
24
  begin
25
25
  @client.execute @request, self, @context
26
+ self
26
27
  rescue Java::JavaNet::SocketTimeoutException, Java::OrgApacheHttpConn::ConnectTimeoutException, Java::OrgApacheHttp::NoHttpResponseException => e
27
28
  @handlers[:failure].call( Manticore::Timeout.new(e.get_cause) )
28
29
  rescue Java::OrgApacheHttpClient::ClientProtocolException => e
@@ -71,7 +72,8 @@ module Manticore
71
72
  @response = response
72
73
  @code = response.get_status_line.get_status_code
73
74
  @headers = Hash[* response.get_all_headers.flat_map {|h| [h.get_name.downcase, h.get_value]} ]
74
- @handlers[:success].call(self, @request)
75
+ @callback_result = @handlers[:success].call(self)
76
+ nil
75
77
  rescue => e
76
78
  @exception = e
77
79
  end
@@ -23,17 +23,17 @@ module Manticore
23
23
  java_import 'java.util.concurrent.LinkedBlockingQueue'
24
24
 
25
25
  # The default maximum pool size for requests
26
- DEFAULT_MAX_POOL_SIZE = 50
26
+ DEFAULT_MAX_POOL_SIZE = 50
27
27
 
28
28
  # The default maximum number of threads per route that will be permitted
29
- DEFAULT_MAX_PER_ROUTE = 2
29
+ DEFAULT_MAX_PER_ROUTE = 10
30
30
 
31
31
  DEFAULT_REQUEST_TIMEOUT = 60
32
- DEFAULT_SOCKET_TIMEOUT = 10
32
+ DEFAULT_SOCKET_TIMEOUT = 10
33
33
  DEFAULT_CONNECT_TIMEOUT = 10
34
- DEFAULT_MAX_REDIRECTS = 5
34
+ DEFAULT_MAX_REDIRECTS = 5
35
35
  DEFAULT_EXPECT_CONTINUE = false
36
- DEFAULT_STALE_CHECK = false
36
+ DEFAULT_STALE_CHECK = false
37
37
 
38
38
  # Create a new HTTP client with a backing request pool. if you pass a block to the initializer, the underlying
39
39
  # {http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/impl/client/HttpClientBuilder.html HttpClientBuilder}
@@ -269,21 +269,11 @@ module Manticore
269
269
 
270
270
  # Execute all queued async requests
271
271
  #
272
- # @return [Array] An array of the results of the on_success bodies for the requests executed.
272
+ # @return [Array] An array of the responses from the requests executed.
273
273
  def execute!
274
- tasks = @async_requests.map do |request, response|
275
- task = FutureTask.new(response)
276
- @executor.submit task
277
- task
278
- end
274
+ result = @executor.invoke_all(@async_requests).map(&:get)
279
275
  @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
276
+ result
287
277
  end
288
278
 
289
279
  protected
@@ -330,7 +320,7 @@ module Manticore
330
320
  def async_request(request, &block)
331
321
  create_executor_if_needed
332
322
  response = AsyncResponse.new(@client, request, BasicHttpContext.new, block)
333
- @async_requests << [request, response]
323
+ @async_requests << response
334
324
  response
335
325
  end
336
326
 
@@ -338,7 +328,6 @@ module Manticore
338
328
  response = Response.new(request, BasicHttpContext.new, block)
339
329
  begin
340
330
  @client.execute request, response, response.context
341
- response
342
331
  rescue Java::JavaNet::SocketTimeoutException, Java::OrgApacheHttpConn::ConnectTimeoutException, Java::OrgApacheHttp::NoHttpResponseException => e
343
332
  raise Manticore::Timeout.new(e.get_cause)
344
333
  rescue Java::OrgApacheHttpClient::ClientProtocolException => e
@@ -8,13 +8,16 @@ module Manticore
8
8
  # @return [Integer] Response code from this response
9
9
  # @!attribute [r] context
10
10
  # @return [HttpContext] Context associated with this request/response
11
+ # @!attribute [r] callback_result
12
+ # @return Value returned from any given on_success/response block
11
13
  class Response
12
14
  include_package "org.apache.http.client"
13
15
  include_package "org.apache.http.util"
14
16
  include_package "org.apache.http.protocol"
17
+ # java_import "org.manticore.EntityConverter"
15
18
  include ResponseHandler
16
19
 
17
- attr_reader :headers, :code, :context
20
+ attr_reader :headers, :code, :context, :request, :callback_result
18
21
 
19
22
  # Creates a new Response
20
23
  #
@@ -34,12 +37,11 @@ module Manticore
34
37
  @code = response.get_status_line.get_status_code
35
38
  @headers = Hash[* response.get_all_headers.flat_map {|h| [h.get_name.downcase, h.get_value]} ]
36
39
  if @handler_block
37
- @handler_block.call(self)
40
+ @callback_result = @handler_block.call(self)
38
41
  else
39
42
  read_body
40
43
  end
41
- # ensure
42
- # @request.release_connection
44
+ self
43
45
  end
44
46
 
45
47
  # Fetch the final resolved URL for this response
@@ -53,14 +55,27 @@ module Manticore
53
55
  URI.join(host, url.to_s)
54
56
  end
55
57
 
56
- # Fetch the body content of this response
58
+ # Fetch the body content of this response.
59
+ # This fetches the input stream in Ruby; this isn't optimal, but it's faster than
60
+ # fetching the whole thing in Java then UTF-8 encoding it all into a giant Ruby string.
61
+ #
62
+ # This permits for streaming response bodies, as well.
63
+ #
64
+ # @example Streaming response
65
+ #
66
+ # client.get("http://example.com/resource").on_success do |response|
67
+ # response.body do |chunk|
68
+ # # Do something with chunk, which is a parsed portion of the returned body
69
+ # end
70
+ # end
57
71
  #
58
72
  # @return [String] Reponse body
59
- def read_body
73
+ def read_body(&block)
60
74
  @body ||= begin
61
- entity = @response.get_entity
62
- entity && EntityUtils.to_string(entity)
63
- rescue Java::JavaIo::IOException, Java::JavaNet::SocketException => e
75
+ if entity = @response.get_entity
76
+ EntityConverter.new.read_entity(entity, &block)
77
+ end
78
+ rescue Java::JavaIo::IOException, Java::JavaNet::SocketException, IOError => e
64
79
  raise StreamClosedException.new("Could not read from stream: #{e.message} (Did you forget to read #body from your block?)")
65
80
  end
66
81
  end
@@ -72,5 +87,18 @@ module Manticore
72
87
  def length
73
88
  (@headers["content-length"] || -1).to_i
74
89
  end
90
+
91
+ private
92
+
93
+ def encode(string, charset)
94
+ return string if charset.nil?
95
+ begin
96
+ string.encode(charset)
97
+ rescue Encoding::ConverterNotFoundError
98
+ string.encode("utf-8")
99
+ rescue
100
+ string
101
+ end
102
+ end
75
103
  end
76
104
  end
@@ -1,3 +1,3 @@
1
1
  module Manticore
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -40,6 +40,30 @@ describe Manticore::Client do
40
40
  end
41
41
  end
42
42
 
43
+ context "when no response charset is specified" do
44
+ let(:content_type) { "text/plain" }
45
+
46
+ it "should decode response bodies according to the content-type header" do
47
+ client.get(local_server, headers: {"X-Content-Type" => content_type}).body.encoding.name.should == "ISO-8859-1"
48
+ end
49
+ end
50
+
51
+ context "when an invalid response charset is specified" do
52
+ let(:content_type) { "text/plain; charset=bogus" }
53
+
54
+ it "should decode the content as UTF-8" do
55
+ client.get(local_server, headers: {"X-Content-Type" => content_type}).body.encoding.name.should == "ISO-8859-1"
56
+ end
57
+ end
58
+
59
+ context "when the response charset is UTF-8" do
60
+ let(:content_type) { "text/plain; charset=utf-8" }
61
+
62
+ it "should decode response bodies according to the content-type header" do
63
+ client.get(local_server, headers: {"X-Content-Type" => content_type}).body.encoding.name.should == "UTF-8"
64
+ end
65
+ end
66
+
43
67
  describe "#get" do
44
68
  it "should work" do
45
69
  response = client.get(local_server)
@@ -136,7 +160,7 @@ describe Manticore::Client do
136
160
 
137
161
  it "can chain handlers" do
138
162
  client.async_get("http://localhost:55441/").on_success {|r| r.code }
139
- client.execute!.should == [200]
163
+ client.execute!.map(&:callback_result).should == [200]
140
164
  end
141
165
  end
142
166
 
@@ -162,7 +186,7 @@ describe Manticore::Client do
162
186
  end
163
187
  end
164
188
 
165
- client.execute!.should == ["Result", "Result"]
189
+ client.execute!.map(&:callback_result).should == ["Result", "Result"]
166
190
  end
167
191
  end
168
192
 
data/spec/spec_helper.rb CHANGED
@@ -41,16 +41,17 @@ def start_server(port = PORT)
41
41
  if request[:headers]["X-Redirect"] && request[:uri][:path] != request[:headers]["X-Redirect"]
42
42
  [301, {"Location" => local_server( request[:headers]["X-Redirect"] )}, [""]]
43
43
  else
44
+ content_type = request[:headers]["X-Content-Type"] || "text/plain"
44
45
  if request[:headers]["Accept-Encoding"] && request[:headers]["Accept-Encoding"].match("gzip")
45
46
  out = StringIO.new('', "w")
46
47
  io = Zlib::GzipWriter.new(out, 2)
47
48
  io.write JSON.dump(request)
48
49
  io.close
49
50
  payload = out.string
50
- [200, {'Content-Type' => "text/plain", 'Content-Encoding' => "gzip", "Content-Length" => payload.length}, [payload]]
51
+ [200, {'Content-Type' => content_type, 'Content-Encoding' => "gzip", "Content-Length" => payload.length}, [payload]]
51
52
  else
52
53
  payload = JSON.dump(request)
53
- [200, {'Content-Type' => "text/plain", "Content-Length" => payload.length}, [payload]]
54
+ [200, {'Content-Type' => content_type, "Content-Length" => payload.length}, [payload]]
54
55
  end
55
56
  end
56
57
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: manticore
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: java
6
6
  authors:
7
7
  - Chris Heald
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-02-17 00:00:00.000000000 Z
11
+ date: 2014-02-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -66,9 +66,12 @@ files:
66
66
  - LICENSE.txt
67
67
  - README.md
68
68
  - Rakefile
69
+ - ext/manticore/org/manticore/Manticore.java
69
70
  - lib/jar/commons-logging-1.1.3.jar
70
- - lib/jar/httpclient-4.3.2.jar
71
+ - lib/jar/httpclient-4.3.2-patched.jar
71
72
  - lib/jar/httpcore-4.3.1.jar
73
+ - lib/jar/lazy_decompressing_stream.patch
74
+ - lib/jar/manticore-ext.jar
72
75
  - lib/manticore.rb
73
76
  - lib/manticore/async_response.rb
74
77
  - lib/manticore/client.rb