manticore 0.1.0-java → 0.2.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 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