jruby-http-reactor 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc ADDED
@@ -0,0 +1,13 @@
1
+ A client that uses the Apache HttpCore NIO library to do HTTP requests.
2
+
3
+ <pre>
4
+ require 'http_reactor'
5
+
6
+ uris = ['http://www.yahoo.com/','http://www.google.com/']
7
+ requests = uris.map { |uri| HttpReactor::Request.new(uri) }
8
+
9
+ HttpReactor::Client.new(requests) do |response, context|
10
+ puts "Response code: #{response.code}"
11
+ puts "Response body: #{response.body}"
12
+ end
13
+ </pre>
data/README.textile ADDED
@@ -0,0 +1,13 @@
1
+ A client that uses the Apache HttpCore NIO library to do HTTP requests.
2
+
3
+ <pre>
4
+ require 'http_reactor'
5
+
6
+ uris = ['http://www.yahoo.com/','http://www.google.com/']
7
+ requests = uris.map { |uri| HttpReactor::Request.new(uri) }
8
+
9
+ HttpReactor::Client.new(requests) do |response, context|
10
+ puts "Response code: #{response.code}"
11
+ puts "Response body: #{response.body}"
12
+ end
13
+ </pre>
data/Rakefile ADDED
@@ -0,0 +1,36 @@
1
+ require 'rake'
2
+ require 'rake/rdoctask'
3
+
4
+ desc 'Default: run tests.'
5
+ task :default => [:test]
6
+
7
+ desc 'Run tests.'
8
+ task :test do
9
+ require File.dirname(__FILE__) + '/test/client_test'
10
+ end
11
+
12
+ desc 'Generate documentation.'
13
+ Rake::RDocTask.new(:rdoc) do |rdoc|
14
+ rdoc.rdoc_dir = 'rdoc'
15
+ rdoc.title = 'JRuby HTTP Reactor'
16
+ rdoc.options << '--line-numbers' << '--inline-source'
17
+ rdoc.rdoc_files.include('README.rdoc')
18
+ rdoc.rdoc_files.include('lib/*.rb')
19
+ rdoc.rdoc_files.include('lib/**/*.rb')
20
+ end
21
+
22
+ begin
23
+ require 'jeweler'
24
+ Jeweler::Tasks.new do |gemspec|
25
+ gemspec.name = "jruby-http-reactor"
26
+ gemspec.summary = "JRuby NIO HTTP client."
27
+ gemspec.email = "anthonyeden@gmail.com"
28
+ gemspec.homepage = "http://github.com/aeden/jruby-http-reactor"
29
+ gemspec.description = ""
30
+ gemspec.authors = ["Anthony Eden"]
31
+ gemspec.files.exclude 'docs/**/*'
32
+ gemspec.files.exclude '.gitignore'
33
+ end
34
+ rescue LoadError
35
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
36
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.5.2
data/examples/opml.rb ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env jruby
2
+ #
3
+ # Usage: jruby examples/opml.rb opml.xml
4
+ #
5
+ # Dependencies:
6
+ #
7
+ # hpricot (0.6.164)
8
+ # threadify (1.1.0) (optional, uncomment in code)
9
+
10
+ require 'uri'
11
+ require 'rubygems'
12
+ require 'hpricot'
13
+ #require 'threadify'
14
+ require File.dirname(__FILE__) + '/../lib/http_reactor'
15
+
16
+ def requests
17
+ @requests ||= begin
18
+ xml = File.read(ARGV.pop)
19
+ doc = Hpricot::XML(xml)
20
+ urls = (doc/'outline').map { |outline| outline['xmlUrl'] }
21
+ urls.map { |url_string| HttpReactor::Request.new(URI.parse(url_string)) }
22
+ end
23
+ end
24
+
25
+ #uris.threadify(:each_slice, 1) do |slice|
26
+ HttpReactor::Client.new(requests) do |response, context|
27
+ puts "Response: #{response.status_line.status_code}"
28
+ puts "Content length: #{response.body.length}"
29
+ end
30
+ #end
31
+ puts "Processed #{requests.length} feeds"
@@ -0,0 +1,55 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{jruby-http-reactor}
8
+ s.version = "0.5.2"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Anthony Eden"]
12
+ s.date = %q{2009-10-22}
13
+ s.description = %q{}
14
+ s.email = %q{anthonyeden@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "README.rdoc",
17
+ "README.textile"
18
+ ]
19
+ s.files = [
20
+ "README.rdoc",
21
+ "README.textile",
22
+ "Rakefile",
23
+ "VERSION",
24
+ "examples/opml.rb",
25
+ "jruby-http-reactor.gemspec",
26
+ "lib/http_reactor.rb",
27
+ "lib/http_reactor/client.rb",
28
+ "lib/http_reactor/request.rb",
29
+ "lib/http_reactor/response.rb",
30
+ "test/client_test.rb",
31
+ "test/test_helper.rb",
32
+ "vendor/httpcore-4.0.1.jar",
33
+ "vendor/httpcore-nio-4.0.1.jar"
34
+ ]
35
+ s.homepage = %q{http://github.com/aeden/jruby-http-reactor}
36
+ s.rdoc_options = ["--charset=UTF-8"]
37
+ s.require_paths = ["lib"]
38
+ s.rubygems_version = %q{1.3.5}
39
+ s.summary = %q{JRuby NIO HTTP client.}
40
+ s.test_files = [
41
+ "test/client_test.rb",
42
+ "test/test_helper.rb",
43
+ "examples/opml.rb"
44
+ ]
45
+
46
+ if s.respond_to? :specification_version then
47
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
48
+ s.specification_version = 3
49
+
50
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
51
+ else
52
+ end
53
+ else
54
+ end
55
+ end
@@ -0,0 +1,287 @@
1
+ module HttpReactor #:nodoc:
2
+ class RequestExecutionHandler #:nodoc:
3
+ import org.apache.http.protocol
4
+ import org.apache.http.nio.protocol
5
+ include HttpRequestExecutionHandler
6
+
7
+ REQUEST_SENT = "request-sent"
8
+ RESPONSE_RECEIVED = "response-received"
9
+
10
+ HTTP_TARGET_PATH = 'http_target_path'
11
+ HTTP_TARGET_REQUEST = 'http_target_request'
12
+ IO_REACTOR = "io_reactor"
13
+ REDIRECT_HISTORY = "redirect_history"
14
+
15
+ def initialize(request_count, handler_proc)
16
+ @request_count = request_count
17
+ @handler_proc = handler_proc
18
+ end
19
+
20
+ def initalize_context(context, attachment)
21
+ context.set_attribute(ExecutionContext.HTTP_TARGET_HOST, attachment[:host])
22
+ context.set_attribute(HTTP_TARGET_PATH, attachment[:path])
23
+ context.set_attribute(HTTP_TARGET_REQUEST, attachment[:request])
24
+ context.set_attribute(IO_REACTOR, attachment[:io_reactor])
25
+ context.set_attribute(REDIRECT_HISTORY, attachment[:redirect_history] || [])
26
+ end
27
+
28
+ def finalize_context(context)
29
+ flag = context.get_attribute(RESPONSE_RECEIVED)
30
+ @request_count.count_down() unless flag
31
+ end
32
+
33
+ def submit_request(context)
34
+ target_host = context.get_attribute(ExecutionContext.HTTP_TARGET_HOST);
35
+ target_path = context.get_attribute(HTTP_TARGET_PATH)
36
+ target_request = context.get_attribute(HTTP_TARGET_REQUEST)
37
+ flag = context.get_attribute(REQUEST_SENT);
38
+ if flag.nil?
39
+ # Stick some object into the context
40
+ context.set_attribute(REQUEST_SENT, true);
41
+
42
+ #puts "--------------"
43
+ #puts "Sending request to #{target_host}#{target_path}"
44
+ #puts "--------------"
45
+
46
+ org.apache.http.message.BasicHttpEntityEnclosingRequest.new(
47
+ target_request.method, target_path
48
+ )
49
+ else
50
+ # No new request to submit
51
+ end
52
+ end
53
+
54
+ def handle_response(response, context)
55
+ begin
56
+ res = HttpReactor::Response.new(response)
57
+ case res.code
58
+ when 301, 302
59
+ redirect_to = res.headers['Location']
60
+ redirect_history = context.getAttribute(REDIRECT_HISTORY)
61
+ if redirect_history.include?(redirect_to)
62
+ #puts "Too many redirects"
63
+ context.setAttribute(RESPONSE_RECEIVED, true)
64
+ @request_count.count_down()
65
+ else
66
+ #puts "Redirecting to #{redirect_to}"
67
+ redirect_history << redirect_to
68
+ context.setAttribute(REDIRECT_HISTORY, redirect_history)
69
+ request = context.getAttribute(HTTP_TARGET_REQUEST)
70
+ request.uri = URI.parse(redirect_to)
71
+ io_reactor = context.getAttribute(IO_REACTOR)
72
+ HttpReactor::Client.process_requests([request], io_reactor, @request_count)
73
+ end
74
+ else
75
+ @handler_proc.call(res, context)
76
+ context.setAttribute(RESPONSE_RECEIVED, true)
77
+ # Signal completion of the request execution
78
+ @request_count.count_down()
79
+ end
80
+ rescue => e
81
+ puts "Error handling response: #{e.message}"
82
+ end
83
+ end
84
+ end
85
+
86
+ class SessionRequestCallback #:nodoc:
87
+ include org.apache.http.nio.reactor.SessionRequestCallback
88
+
89
+ def initialize(request_count)
90
+ @request_count = request_count
91
+ end
92
+
93
+ def cancelled(request)
94
+ puts "Connect request cancelled: #{request.remote_address}"
95
+ @request_count.count_down()
96
+ end
97
+
98
+ def completed(request); end
99
+
100
+ def failed(request)
101
+ puts "Connect request failed: #{request.remote_address}"
102
+ @request_count.count_down()
103
+ end
104
+
105
+ def timeout(request)
106
+ puts "Connect request timed out: #{request.remote_address}"
107
+ @request_count.count_down()
108
+ end
109
+ end
110
+
111
+ class EventLogger #:nodoc:
112
+ import org.apache.http.nio.protocol
113
+ include EventListener
114
+ def connection_open(conn)
115
+ puts "Connection open: #{conn}"
116
+ end
117
+ def connection_timeout(conn)
118
+ puts "Connection timed out: #{conn}"
119
+ end
120
+ def connection_closed(conn)
121
+ puts "Connection closed: #{conn}"
122
+ end
123
+ def fatal_i_o_exception(ex, conn)
124
+ puts "Fatal I/O error: #{ex.message}"
125
+ end
126
+ def fatal_protocol_exception(ex, conn)
127
+ puts "HTTP error: #{ex.message}"
128
+ end
129
+ end
130
+
131
+ # An HTTP client that uses the Reactor pattern.
132
+ class Client
133
+ import org.apache.http
134
+ import org.apache.http.params
135
+ import org.apache.http.protocol
136
+ import org.apache.http.nio.protocol
137
+ import org.apache.http.impl.nio
138
+ import org.apache.http.impl.nio.reactor
139
+
140
+ # Create a new HttpReactor client that will request the given URIs.
141
+ #
142
+ # Parameters:
143
+ # * <tt>uris</tt>: An array of URI objects.
144
+ # * <tt>handler_proc</tt>: A Proc that will be called with the response and context
145
+ # * <tt>session_request_callback</tt>: A class that implements the session request
146
+ # callback interface found in the HttpCore library.
147
+ # * <tt>options: A hash of configuration options. See below.
148
+ #
149
+ # The options hash may include the following options
150
+ # * <tt>:so_timeout</tt>: (default = 5 seconds)
151
+ # * <tt>:connection_timeout</tt>: The HTTP connection timeout (default = 10 seconds)
152
+ # * <tt>:socket_buffer_size</tt>: The buffer size (defaults to 8Kb)
153
+ # * <tt>:stale_connection_check</tt>: (defaults to false)
154
+ # * <tt>:tcp_nodelay</tt>: (defaults to true)
155
+ # * <tt>:user_agent</tt>: The user agent string to send (defaults to "JRubyHttpReactor")
156
+ # * <tt>:event_listener</tt>: A class that implements the org.apache.http.nio.protocol interface
157
+ def initialize(requests=[], handler_proc=nil, options={}, &block)
158
+ handler_proc = block if block_given?
159
+ handler_proc ||= default_handler_proc
160
+
161
+ initialize_options(options)
162
+
163
+ params = build_params(options)
164
+
165
+ io_reactor = DefaultConnectingIOReactor.new(2, params);
166
+
167
+ httpproc = BasicHttpProcessor.new;
168
+ httpproc.add_interceptor(RequestContent.new);
169
+ httpproc.add_interceptor(RequestTargetHost.new);
170
+ httpproc.add_interceptor(RequestConnControl.new);
171
+ httpproc.add_interceptor(RequestUserAgent.new);
172
+ httpproc.add_interceptor(RequestExpectContinue.new);
173
+
174
+ # We are going to use this object to synchronize between the
175
+ # I/O event and main threads
176
+ request_counter = java.util.concurrent.CountDownLatch.new(requests.length);
177
+
178
+ handler = BufferingHttpClientHandler.new(
179
+ httpproc,
180
+ RequestExecutionHandler.new(request_counter, handler_proc),
181
+ org.apache.http.impl.DefaultConnectionReuseStrategy.new,
182
+ params
183
+ )
184
+
185
+ handler.event_listener = options[:event_listener].new if options[:event_listener]
186
+
187
+ io_event_dispatch = DefaultClientIOEventDispatch.new(handler, params)
188
+
189
+ Thread.abort_on_exception = true
190
+ t = Thread.new do
191
+ begin
192
+ #puts "Executing IO reactor"
193
+ io_reactor.execute(io_event_dispatch)
194
+ rescue java.io.InterruptedIOException => e
195
+ puts "Interrupted"
196
+ rescue java.io.IOException => e
197
+ puts "I/O error in reactor execution thread: #{e.message}"
198
+ end
199
+ #puts "Shutdown"
200
+ end
201
+
202
+ process_requests(requests, io_reactor, request_counter)
203
+
204
+ # Block until all connections signal
205
+ # completion of the request execution
206
+ request_counter.await()
207
+
208
+ #puts "Shutting down I/O reactor"
209
+
210
+ io_reactor.shutdown()
211
+
212
+ #puts "Done"
213
+ end
214
+
215
+ def process_requests(requests, io_reactor, request_counter)
216
+ HttpReactor::Client.process_requests(requests, io_reactor, request_counter)
217
+ end
218
+
219
+ def self.process_requests(requests, io_reactor, request_counter)
220
+ requests.each do |request|
221
+ uri = request.uri
222
+ attachment = {
223
+ :host => HttpHost.new(uri.host),
224
+ :path => uri.request_uri,
225
+ :request => request,
226
+ :io_reactor => io_reactor
227
+ }
228
+ io_reactor.connect(
229
+ java.net.InetSocketAddress.new(uri.host, uri.port),
230
+ nil,
231
+ attachment,
232
+ SessionRequestCallback.new(request_counter)
233
+ )
234
+ end
235
+ end
236
+
237
+ private
238
+
239
+ def initialize_options(options)
240
+ options[:so_timeout] ||= 5000
241
+ options[:connection_timeout] ||= 10000
242
+ options[:socket_buffer_size] ||= 8 * 1024
243
+ options[:stale_connection_check] ||= false
244
+ options[:tcp_nodelay] ||= true
245
+ options[:user_agent] ||= "JRubyHttpReactor"
246
+ #options[:event_listener] ||= EventLogger
247
+ end
248
+
249
+ def build_params(options)
250
+ params = BasicHttpParams.new
251
+ params.set_int_parameter(
252
+ CoreConnectionPNames.SO_TIMEOUT, options[:so_timeout])
253
+ params.set_int_parameter(
254
+ CoreConnectionPNames.CONNECTION_TIMEOUT, options[:connection_timeout])
255
+ params.set_int_parameter(
256
+ CoreConnectionPNames.SOCKET_BUFFER_SIZE, options[:socket_buffer_size])
257
+ params.set_boolean_parameter(
258
+ CoreConnectionPNames.STALE_CONNECTION_CHECK, options[:stale_connection_check])
259
+ params.set_boolean_parameter(
260
+ CoreConnectionPNames.TCP_NODELAY, options[:tcp_nodelay])
261
+ params.set_parameter(
262
+ CoreProtocolPNames.USER_AGENT, options[:user_agent])
263
+ params
264
+ end
265
+
266
+ def default_handler_proc
267
+ Proc.new { |response, context|
268
+ target_host = context.get_attribute(ExecutionContext.HTTP_TARGET_HOST);
269
+ target_path = context.get_attribute(RequestExecutionHandler::HTTP_TARGET_PATH)
270
+
271
+ entity = response.entity
272
+ begin
273
+ content = org.apache.http.util.EntityUtils.toString(entity)
274
+
275
+ puts "--------------"
276
+ puts "Response from #{target_host}#{target_path}"
277
+ puts response.status_line
278
+ puts "Document length: #{content.length}"
279
+ puts "--------------"
280
+ rescue java.io.IOException => ex
281
+ puts "I/O error in handle_response: #{ex.message}"
282
+ end
283
+ }
284
+ end
285
+
286
+ end
287
+ end
@@ -0,0 +1,16 @@
1
+ module HttpReactor
2
+ # A class that represents an HTTP request.
3
+ class Request
4
+ attr_accessor :uri
5
+ attr_reader :method, :payload
6
+ attr_accessor :extra
7
+
8
+ # Initialize the request object.
9
+ def initialize(uri, method='GET', payload=nil, extra={})
10
+ @uri = uri
11
+ @method = method
12
+ @payload = payload
13
+ @extra = extra
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,67 @@
1
+ module HttpReactor #:nodoc:
2
+ # A class that represents an HTTP response which wraps the
3
+ # Java HTTP NIO response object and provides methods for accessing
4
+ # the data using ruby idioms.
5
+ class Response
6
+ def initialize(response_impl)
7
+ @response_impl = response_impl
8
+ end
9
+
10
+ # Delegates to the HTTP NIO response
11
+ def status_line
12
+ @response_impl.status_line
13
+ end
14
+
15
+ # Delegates to the HTTP NIO response
16
+ def entity
17
+ @response_impl.entity
18
+ end
19
+
20
+ def code
21
+ status_line.status_code
22
+ end
23
+
24
+ # Get the response content type
25
+ def content_type
26
+ @content_type ||= @response_impl.entity.content_type.value
27
+ end
28
+
29
+ # Get the response content length
30
+ def content_length
31
+ @content_length ||= @response_impl.entity.content_length
32
+ end
33
+
34
+ # Access the headers
35
+ def [](name)
36
+ headers[name]
37
+ end
38
+
39
+ def headers
40
+ @headers ||= begin
41
+ h = Hash.new
42
+ @response_impl.all_headers.each do |header|
43
+ if h[header.name]
44
+ h[header.name] = [h[header.name], header.value]
45
+ else
46
+ h[header.name] = header.value
47
+ end
48
+ end
49
+ h
50
+ end
51
+ end
52
+
53
+ # Get the body text
54
+ def body
55
+ @body ||= begin
56
+ begin
57
+ io = Java.java_to_ruby(
58
+ org.jruby.RubyIO.new(JRuby.runtime, entity.content).java_object
59
+ )
60
+ io.read
61
+ rescue Exception => e
62
+ puts "Error in Response#body: #{e.message}"
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,14 @@
1
+ require 'java'
2
+ require File.dirname(__FILE__) + '/../vendor/httpcore-4.0.1.jar'
3
+ require File.dirname(__FILE__) + '/../vendor/httpcore-nio-4.0.1.jar'
4
+
5
+ # The Ruby module that contains wrappers for the the Apache
6
+ # HTTP NIO implementation.
7
+ module HttpReactor
8
+ end
9
+
10
+ $:.unshift(File.dirname(__FILE__))
11
+
12
+ require 'http_reactor/request'
13
+ require 'http_reactor/response'
14
+ require 'http_reactor/client'
@@ -0,0 +1,49 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+ require 'uri'
3
+ require 'mime/types'
4
+
5
+ class ClientTest < Test::Unit::TestCase
6
+ def requests
7
+ @requests ||= [
8
+ 'http://yahoo.com/',
9
+ 'http://www.google.com/',
10
+ 'http://www.apache.org/',
11
+ 'http://anthony.mp/about_me',
12
+ 'http://search.twitter.com/search?q=jruby'
13
+ ].map { |url_string| HttpReactor::Request.new(URI.parse(url_string)) }
14
+ end
15
+
16
+ def test_new
17
+ assert_nothing_raised do
18
+ HttpReactor::Client.new(requests)
19
+ end
20
+ end
21
+
22
+ def test_proc
23
+ handler = Proc.new do |response, context|
24
+ assert_equal 200, response.code
25
+ assert response.body.length > 0
26
+ mime_type = MIME::Types[response.content_type].first
27
+ assert_equal "text/html", mime_type.content_type
28
+ end
29
+ HttpReactor::Client.new(requests, handler)
30
+ end
31
+
32
+ def test_block
33
+ HttpReactor::Client.new(requests) do |response, context|
34
+ assert_equal 200, response.status_line.status_code
35
+ assert_equal 200, response.code
36
+ mime_type = MIME::Types[response.content_type].first
37
+ assert_equal "text/html", mime_type.content_type
38
+ puts "request ur: #{context.getAttribute('http_target_request').uri}"
39
+ puts "content-length: #{response.content_length}"
40
+ assert response.body.length > 0
41
+ puts "=== HEADERS ==="
42
+ puts response.headers.inspect
43
+ # puts "===== BODY ===="
44
+ # puts response.body
45
+ # puts "==============="
46
+ end
47
+ end
48
+
49
+ end
@@ -0,0 +1,2 @@
1
+ require 'test/unit'
2
+ require File.dirname(__FILE__) + '/../lib/http_reactor'
Binary file
Binary file
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jruby-http-reactor
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.2
5
+ platform: ruby
6
+ authors:
7
+ - Anthony Eden
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-22 00:00:00 -10:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: ""
17
+ email: anthonyeden@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ - README.textile
25
+ files:
26
+ - README.rdoc
27
+ - README.textile
28
+ - Rakefile
29
+ - VERSION
30
+ - examples/opml.rb
31
+ - jruby-http-reactor.gemspec
32
+ - lib/http_reactor.rb
33
+ - lib/http_reactor/client.rb
34
+ - lib/http_reactor/request.rb
35
+ - lib/http_reactor/response.rb
36
+ - test/client_test.rb
37
+ - test/test_helper.rb
38
+ - vendor/httpcore-4.0.1.jar
39
+ - vendor/httpcore-nio-4.0.1.jar
40
+ has_rdoc: true
41
+ homepage: http://github.com/aeden/jruby-http-reactor
42
+ licenses: []
43
+
44
+ post_install_message:
45
+ rdoc_options:
46
+ - --charset=UTF-8
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ requirements: []
62
+
63
+ rubyforge_project:
64
+ rubygems_version: 1.3.5
65
+ signing_key:
66
+ specification_version: 3
67
+ summary: JRuby NIO HTTP client.
68
+ test_files:
69
+ - test/client_test.rb
70
+ - test/test_helper.rb
71
+ - examples/opml.rb