aeden-jruby-http-reactor 0.0.1 → 0.1.0
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.
- data/.gitignore +2 -0
- data/VERSION +1 -1
- data/examples/opml.rb +30 -0
- data/jruby-http-reactor.gemspec +51 -0
- data/lib/http_reactor.rb +1 -0
- data/lib/http_reactor/client.rb +62 -18
- data/lib/http_reactor/request.rb +13 -0
- data/test/client_test.rb +13 -5
- metadata +8 -4
    
        data/.gitignore
    ADDED
    
    
    
        data/VERSION
    CHANGED
    
    | @@ -1 +1 @@ | |
| 1 | 
            -
            0.0 | 
| 1 | 
            +
            0.1.0
         | 
    
        data/examples/opml.rb
    ADDED
    
    | @@ -0,0 +1,30 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 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 | 
            +
              end
         | 
| 29 | 
            +
            #end
         | 
| 30 | 
            +
            puts "Processed #{requests.length} feeds"
         | 
| @@ -0,0 +1,51 @@ | |
| 1 | 
            +
            # -*- encoding: utf-8 -*-
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Gem::Specification.new do |s|
         | 
| 4 | 
            +
              s.name = %q{jruby-http-reactor}
         | 
| 5 | 
            +
              s.version = "0.1.0"
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
         | 
| 8 | 
            +
              s.authors = ["Anthony Eden"]
         | 
| 9 | 
            +
              s.date = %q{2009-09-12}
         | 
| 10 | 
            +
              s.description = %q{}
         | 
| 11 | 
            +
              s.email = %q{anthonyeden@gmail.com}
         | 
| 12 | 
            +
              s.extra_rdoc_files = [
         | 
| 13 | 
            +
                "README.rdoc"
         | 
| 14 | 
            +
              ]
         | 
| 15 | 
            +
              s.files = [
         | 
| 16 | 
            +
                ".gitignore",
         | 
| 17 | 
            +
                 "README.rdoc",
         | 
| 18 | 
            +
                 "Rakefile",
         | 
| 19 | 
            +
                 "VERSION",
         | 
| 20 | 
            +
                 "examples/opml.rb",
         | 
| 21 | 
            +
                 "jruby-http-reactor.gemspec",
         | 
| 22 | 
            +
                 "lib/http_reactor.rb",
         | 
| 23 | 
            +
                 "lib/http_reactor/client.rb",
         | 
| 24 | 
            +
                 "lib/http_reactor/request.rb",
         | 
| 25 | 
            +
                 "test/client_test.rb",
         | 
| 26 | 
            +
                 "test/test_helper.rb",
         | 
| 27 | 
            +
                 "vendor/httpcore-4.0.1.jar",
         | 
| 28 | 
            +
                 "vendor/httpcore-nio-4.0.1.jar"
         | 
| 29 | 
            +
              ]
         | 
| 30 | 
            +
              s.has_rdoc = true
         | 
| 31 | 
            +
              s.homepage = %q{http://github.com/aeden/jruby-http-reactor}
         | 
| 32 | 
            +
              s.rdoc_options = ["--charset=UTF-8"]
         | 
| 33 | 
            +
              s.require_paths = ["lib"]
         | 
| 34 | 
            +
              s.rubygems_version = %q{1.3.1}
         | 
| 35 | 
            +
              s.summary = %q{JRuby NIO HTTP client.}
         | 
| 36 | 
            +
              s.test_files = [
         | 
| 37 | 
            +
                "test/client_test.rb",
         | 
| 38 | 
            +
                 "test/test_helper.rb",
         | 
| 39 | 
            +
                 "examples/opml.rb"
         | 
| 40 | 
            +
              ]
         | 
| 41 | 
            +
             | 
| 42 | 
            +
              if s.respond_to? :specification_version then
         | 
| 43 | 
            +
                current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
         | 
| 44 | 
            +
                s.specification_version = 2
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
         | 
| 47 | 
            +
                else
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
              else
         | 
| 50 | 
            +
              end
         | 
| 51 | 
            +
            end
         | 
    
        data/lib/http_reactor.rb
    CHANGED
    
    
    
        data/lib/http_reactor/client.rb
    CHANGED
    
    | @@ -8,6 +8,7 @@ module HttpReactor #:nodoc: | |
| 8 8 | 
             
                RESPONSE_RECEIVED  = "response-received"
         | 
| 9 9 |  | 
| 10 10 | 
             
                HTTP_TARGET_PATH = 'http_target_path'
         | 
| 11 | 
            +
                HTTP_TARGET_REQUEST = 'http_target_request'
         | 
| 11 12 |  | 
| 12 13 | 
             
                def initialize(request_count, handler_proc)
         | 
| 13 14 | 
             
                  @request_count = request_count
         | 
| @@ -15,8 +16,9 @@ module HttpReactor #:nodoc: | |
| 15 16 | 
             
                end
         | 
| 16 17 |  | 
| 17 18 | 
             
                def initalize_context(context, attachment)
         | 
| 18 | 
            -
                  context.set_attribute(ExecutionContext.HTTP_TARGET_HOST, attachment[:host]) | 
| 19 | 
            +
                  context.set_attribute(ExecutionContext.HTTP_TARGET_HOST, attachment[:host])
         | 
| 19 20 | 
             
                  context.set_attribute(HTTP_TARGET_PATH, attachment[:path])
         | 
| 21 | 
            +
                  context.set_attribute(HTTP_TARGET_REQUEST, attachment[:request])
         | 
| 20 22 | 
             
                end
         | 
| 21 23 |  | 
| 22 24 | 
             
                def finalize_context(context)
         | 
| @@ -27,6 +29,7 @@ module HttpReactor #:nodoc: | |
| 27 29 | 
             
                def submit_request(context)
         | 
| 28 30 | 
             
                  target_host = context.get_attribute(ExecutionContext.HTTP_TARGET_HOST);
         | 
| 29 31 | 
             
                  target_path = context.get_attribute(HTTP_TARGET_PATH)
         | 
| 32 | 
            +
                  target_request = context.get_attribute(HTTP_TARGET_REQUEST)
         | 
| 30 33 | 
             
                  flag = context.get_attribute(REQUEST_SENT);
         | 
| 31 34 | 
             
                  if flag.nil?
         | 
| 32 35 | 
             
                    # Stick some object into the context
         | 
| @@ -36,7 +39,9 @@ module HttpReactor #:nodoc: | |
| 36 39 | 
             
                    puts "Sending request to #{target_host}#{target_path}"
         | 
| 37 40 | 
             
                    puts "--------------"
         | 
| 38 41 |  | 
| 39 | 
            -
                    org.apache.http.message. | 
| 42 | 
            +
                    org.apache.http.message.BasicHttpEntityEnclosingRequest.new(
         | 
| 43 | 
            +
                      target_request.method, target_path
         | 
| 44 | 
            +
                    )
         | 
| 40 45 | 
             
                  else
         | 
| 41 46 | 
             
                    # No new request to submit
         | 
| 42 47 | 
             
                  end
         | 
| @@ -89,7 +94,7 @@ module HttpReactor #:nodoc: | |
| 89 94 | 
             
                def connection_closed(conn)
         | 
| 90 95 | 
             
                  puts "Connection closed: #{conn}"
         | 
| 91 96 | 
             
                end
         | 
| 92 | 
            -
                def  | 
| 97 | 
            +
                def fatal_i_o_exception(ex, conn)
         | 
| 93 98 | 
             
                  puts "Fatal I/O error: #{ex.message}"
         | 
| 94 99 | 
             
                end
         | 
| 95 100 | 
             
                def fatal_protocol_exception(ex, conn)
         | 
| @@ -113,16 +118,23 @@ module HttpReactor #:nodoc: | |
| 113 118 | 
             
                # * <tt>handler_proc</tt>: A Proc that will be called with the response and context
         | 
| 114 119 | 
             
                # * <tt>session_request_callback</tt>: A class that implements the session request
         | 
| 115 120 | 
             
                #   callback interface found in the HttpCore library.
         | 
| 116 | 
            -
                 | 
| 121 | 
            +
                # * <tt>options: A hash of configuration options. See below.
         | 
| 122 | 
            +
                # 
         | 
| 123 | 
            +
                # The options hash may include the following options
         | 
| 124 | 
            +
                # * <tt>:so_timeout</tt>: (default = 5 seconds)
         | 
| 125 | 
            +
                # * <tt>:connection_timeout</tt>: The HTTP connection timeout (default = 10 seconds)
         | 
| 126 | 
            +
                # * <tt>:socket_buffer_size</tt>: The buffer size (defaults to 8Kb)
         | 
| 127 | 
            +
                # * <tt>:stale_connection_check</tt>: (defaults to false)
         | 
| 128 | 
            +
                # * <tt>:tcp_nodelay</tt>: (defaults to true)
         | 
| 129 | 
            +
                # * <tt>:user_agent</tt>: The user agent string to send (defaults to "JRubyHttpReactor")
         | 
| 130 | 
            +
                # * <tt>:event_listener</tt>: A class that implements the org.apache.http.nio.protocol interface
         | 
| 131 | 
            +
                def initialize(requests=[], handler_proc=nil, options={})
         | 
| 117 132 | 
             
                  handler_proc ||= default_handler_proc
         | 
| 133 | 
            +
                  session_request_callback = SessionRequestCallback
         | 
| 118 134 |  | 
| 119 | 
            -
                   | 
| 120 | 
            -
                   | 
| 121 | 
            -
                  params | 
| 122 | 
            -
                  params.set_int_parameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024)
         | 
| 123 | 
            -
                  params.set_boolean_parameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, false)
         | 
| 124 | 
            -
                  params.set_boolean_parameter(CoreConnectionPNames.TCP_NODELAY, true)
         | 
| 125 | 
            -
                  params.set_parameter(CoreProtocolPNames.USER_AGENT, "JRubyHttpReactor/0.0.1")
         | 
| 135 | 
            +
                  initialize_options(options)
         | 
| 136 | 
            +
                  
         | 
| 137 | 
            +
                  params = build_params(options)
         | 
| 126 138 |  | 
| 127 139 | 
             
                  io_reactor = DefaultConnectingIOReactor.new(2, params);
         | 
| 128 140 |  | 
| @@ -135,7 +147,7 @@ module HttpReactor #:nodoc: | |
| 135 147 |  | 
| 136 148 | 
             
                  # We are going to use this object to synchronize between the 
         | 
| 137 149 | 
             
                  # I/O event and main threads
         | 
| 138 | 
            -
                  request_count = java.util.concurrent.CountDownLatch.new( | 
| 150 | 
            +
                  request_count = java.util.concurrent.CountDownLatch.new(requests.length);
         | 
| 139 151 |  | 
| 140 152 | 
             
                  handler = BufferingHttpClientHandler.new(
         | 
| 141 153 | 
             
                    httpproc,
         | 
| @@ -143,8 +155,8 @@ module HttpReactor #:nodoc: | |
| 143 155 | 
             
                    org.apache.http.impl.DefaultConnectionReuseStrategy.new,
         | 
| 144 156 | 
             
                    params
         | 
| 145 157 | 
             
                  )
         | 
| 146 | 
            -
             | 
| 147 | 
            -
                  handler.event_listener =  | 
| 158 | 
            +
                  
         | 
| 159 | 
            +
                  handler.event_listener = options[:event_listener].new if options[:event_listener]
         | 
| 148 160 |  | 
| 149 161 | 
             
                  io_event_dispatch = DefaultClientIOEventDispatch.new(handler, params)
         | 
| 150 162 |  | 
| @@ -161,11 +173,17 @@ module HttpReactor #:nodoc: | |
| 161 173 | 
             
                    puts "Shutdown"
         | 
| 162 174 | 
             
                  end
         | 
| 163 175 |  | 
| 164 | 
            -
                   | 
| 176 | 
            +
                  requests.each do |request|
         | 
| 177 | 
            +
                    uri = request.uri
         | 
| 178 | 
            +
                    attachment = {
         | 
| 179 | 
            +
                      :host => HttpHost.new(uri.host), 
         | 
| 180 | 
            +
                      :path => uri.path,
         | 
| 181 | 
            +
                      :request => request
         | 
| 182 | 
            +
                    }
         | 
| 165 183 | 
             
                    io_reactor.connect(
         | 
| 166 184 | 
             
                      java.net.InetSocketAddress.new(uri.host, uri.port), 
         | 
| 167 185 | 
             
                      nil, 
         | 
| 168 | 
            -
                       | 
| 186 | 
            +
                      attachment,
         | 
| 169 187 | 
             
                      session_request_callback.new(request_count)
         | 
| 170 188 | 
             
                    )
         | 
| 171 189 | 
             
                  end
         | 
| @@ -182,6 +200,34 @@ module HttpReactor #:nodoc: | |
| 182 200 | 
             
                end
         | 
| 183 201 |  | 
| 184 202 | 
             
                private
         | 
| 203 | 
            +
                
         | 
| 204 | 
            +
                def initialize_options(options)
         | 
| 205 | 
            +
                  options[:so_timeout] ||= 5000
         | 
| 206 | 
            +
                  options[:connection_timeout] ||= 10000
         | 
| 207 | 
            +
                  options[:socket_buffer_size] ||= 8 * 1024
         | 
| 208 | 
            +
                  options[:stale_connection_check] ||= false
         | 
| 209 | 
            +
                  options[:tcp_nodelay] ||= true
         | 
| 210 | 
            +
                  options[:user_agent] ||= "JRubyHttpReactor"
         | 
| 211 | 
            +
                  #options[:event_listener] ||= EventLogger
         | 
| 212 | 
            +
                end
         | 
| 213 | 
            +
                
         | 
| 214 | 
            +
                def build_params(options)
         | 
| 215 | 
            +
                  params = BasicHttpParams.new
         | 
| 216 | 
            +
                  params.set_int_parameter(
         | 
| 217 | 
            +
                    CoreConnectionPNames.SO_TIMEOUT, options[:so_timeout])
         | 
| 218 | 
            +
                  params.set_int_parameter(
         | 
| 219 | 
            +
                    CoreConnectionPNames.CONNECTION_TIMEOUT, options[:connection_timeout])
         | 
| 220 | 
            +
                  params.set_int_parameter(
         | 
| 221 | 
            +
                    CoreConnectionPNames.SOCKET_BUFFER_SIZE, options[:socket_buffer_size])
         | 
| 222 | 
            +
                  params.set_boolean_parameter(
         | 
| 223 | 
            +
                    CoreConnectionPNames.STALE_CONNECTION_CHECK, options[:stale_connection_check])
         | 
| 224 | 
            +
                  params.set_boolean_parameter(
         | 
| 225 | 
            +
                    CoreConnectionPNames.TCP_NODELAY, options[:tcp_nodelay])
         | 
| 226 | 
            +
                  params.set_parameter(
         | 
| 227 | 
            +
                    CoreProtocolPNames.USER_AGENT, options[:user_agent])
         | 
| 228 | 
            +
                  params
         | 
| 229 | 
            +
                end
         | 
| 230 | 
            +
                
         | 
| 185 231 | 
             
                def default_handler_proc
         | 
| 186 232 | 
             
                  Proc.new { |response, context|
         | 
| 187 233 | 
             
                    target_host = context.get_attribute(ExecutionContext.HTTP_TARGET_HOST);
         | 
| @@ -193,9 +239,7 @@ module HttpReactor #:nodoc: | |
| 193 239 |  | 
| 194 240 | 
             
                      puts "--------------"
         | 
| 195 241 | 
             
                      puts "Response from #{target_host}#{target_path}"
         | 
| 196 | 
            -
                      puts "--------------"
         | 
| 197 242 | 
             
                      puts response.status_line
         | 
| 198 | 
            -
                      puts "--------------"
         | 
| 199 243 | 
             
                      puts "Document length: #{content.length}"
         | 
| 200 244 | 
             
                      puts "--------------"
         | 
| 201 245 | 
             
                    rescue java.io.IOException => ex
         | 
| @@ -0,0 +1,13 @@ | |
| 1 | 
            +
            module HttpReactor
         | 
| 2 | 
            +
              # A class that represents an HTTP request.
         | 
| 3 | 
            +
              class Request
         | 
| 4 | 
            +
                attr_reader :uri, :method, :payload
         | 
| 5 | 
            +
                
         | 
| 6 | 
            +
                # Initialize the request object.
         | 
| 7 | 
            +
                def initialize(uri, method='GET', payload=nil)
         | 
| 8 | 
            +
                  @uri = uri
         | 
| 9 | 
            +
                  @method = method
         | 
| 10 | 
            +
                  @payload = payload
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
            end
         | 
    
        data/test/client_test.rb
    CHANGED
    
    | @@ -1,18 +1,19 @@ | |
| 1 1 | 
             
            require File.dirname(__FILE__) + '/test_helper'
         | 
| 2 | 
            +
            require 'uri'
         | 
| 2 3 |  | 
| 3 4 | 
             
            class ClientTest < Test::Unit::TestCase
         | 
| 4 | 
            -
              def  | 
| 5 | 
            -
                @ | 
| 5 | 
            +
              def requests
         | 
| 6 | 
            +
                @requests ||= [
         | 
| 6 7 | 
             
                  'http://www.yahoo.com/', 
         | 
| 7 8 | 
             
                  'http://www.google.com/', 
         | 
| 8 9 | 
             
                  'http://www.apache.org/',
         | 
| 9 10 | 
             
                  'http://anthony.mp/about_me'
         | 
| 10 | 
            -
                ].map { |url_string| URI.parse(url_string) }
         | 
| 11 | 
            +
                ].map { |url_string| HttpReactor::Request.new(URI.parse(url_string)) }
         | 
| 11 12 | 
             
              end
         | 
| 12 13 |  | 
| 13 14 | 
             
              def test_new
         | 
| 14 15 | 
             
                assert_nothing_raised do
         | 
| 15 | 
            -
                  HttpReactor::Client.new( | 
| 16 | 
            +
                  HttpReactor::Client.new(requests)
         | 
| 16 17 | 
             
                end
         | 
| 17 18 | 
             
              end
         | 
| 18 19 |  | 
| @@ -20,6 +21,13 @@ class ClientTest < Test::Unit::TestCase | |
| 20 21 | 
             
                handler = Proc.new { |response, context|
         | 
| 21 22 | 
             
                  puts "Response: #{response.status_line.status_code}"
         | 
| 22 23 | 
             
                }
         | 
| 23 | 
            -
                HttpReactor::Client.new( | 
| 24 | 
            +
                HttpReactor::Client.new(requests, handler)
         | 
| 24 25 | 
             
              end
         | 
| 26 | 
            +
              
         | 
| 27 | 
            +
              def test_block
         | 
| 28 | 
            +
                HttpReactor::Client.new(requests) do |response, context|
         | 
| 29 | 
            +
                  puts "Response: #{response.status_line.status_code}"
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
              
         | 
| 25 33 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification 
         | 
| 2 2 | 
             
            name: aeden-jruby-http-reactor
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version 
         | 
| 4 | 
            -
              version: 0.0 | 
| 4 | 
            +
              version: 0.1.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors: 
         | 
| 7 7 | 
             
            - Anthony Eden
         | 
| @@ -9,7 +9,7 @@ autorequire: | |
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 11 |  | 
| 12 | 
            -
            date: 2009- | 
| 12 | 
            +
            date: 2009-09-12 00:00:00 -07:00
         | 
| 13 13 | 
             
            default_executable: 
         | 
| 14 14 | 
             
            dependencies: []
         | 
| 15 15 |  | 
| @@ -22,18 +22,21 @@ extensions: [] | |
| 22 22 | 
             
            extra_rdoc_files: 
         | 
| 23 23 | 
             
            - README.rdoc
         | 
| 24 24 | 
             
            files: 
         | 
| 25 | 
            +
            - .gitignore
         | 
| 25 26 | 
             
            - README.rdoc
         | 
| 26 27 | 
             
            - Rakefile
         | 
| 27 28 | 
             
            - VERSION
         | 
| 29 | 
            +
            - examples/opml.rb
         | 
| 30 | 
            +
            - jruby-http-reactor.gemspec
         | 
| 28 31 | 
             
            - lib/http_reactor.rb
         | 
| 29 32 | 
             
            - lib/http_reactor/client.rb
         | 
| 33 | 
            +
            - lib/http_reactor/request.rb
         | 
| 30 34 | 
             
            - test/client_test.rb
         | 
| 31 35 | 
             
            - test/test_helper.rb
         | 
| 32 36 | 
             
            - vendor/httpcore-4.0.1.jar
         | 
| 33 37 | 
             
            - vendor/httpcore-nio-4.0.1.jar
         | 
| 34 38 | 
             
            has_rdoc: true
         | 
| 35 39 | 
             
            homepage: http://github.com/aeden/jruby-http-reactor
         | 
| 36 | 
            -
            licenses: 
         | 
| 37 40 | 
             
            post_install_message: 
         | 
| 38 41 | 
             
            rdoc_options: 
         | 
| 39 42 | 
             
            - --charset=UTF-8
         | 
| @@ -54,10 +57,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 54 57 | 
             
            requirements: []
         | 
| 55 58 |  | 
| 56 59 | 
             
            rubyforge_project: 
         | 
| 57 | 
            -
            rubygems_version: 1. | 
| 60 | 
            +
            rubygems_version: 1.2.0
         | 
| 58 61 | 
             
            signing_key: 
         | 
| 59 62 | 
             
            specification_version: 2
         | 
| 60 63 | 
             
            summary: JRuby NIO HTTP client.
         | 
| 61 64 | 
             
            test_files: 
         | 
| 62 65 | 
             
            - test/client_test.rb
         | 
| 63 66 | 
             
            - test/test_helper.rb
         | 
| 67 | 
            +
            - examples/opml.rb
         |