diff --git a/lib/httpx.rb b/lib/httpx.rb index bf5c8846..1bac3213 100644 --- a/lib/httpx.rb +++ b/lib/httpx.rb @@ -15,5 +15,29 @@ require "httpx/response" require "httpx/client" module HTTPX + # All plugins should be stored under this module/namespace. Can register and load + # plugins. + # + module Plugins + @plugins = {} + + # Loads a plugin based on a name. If the plugin hasn't been loaded, tries to load + # it from the load path under "httpx/plugins/" directory. + # + def self.load_plugin(name) + h = @plugins + unless (plugin = h[name]) + require "httpx/plugins/#{name}" + raise "Plugin #{name} hasn't been registered" unless (plugin = h[name]) + end + plugin + end + + # Registers a plugin (+mod+) in the central store indexed by +name+. + # + def self.register_plugin(name, mod) + @plugins[name] = mod + end + end end diff --git a/lib/httpx/channel.rb b/lib/httpx/channel.rb index fb6b1a04..4af85a7a 100644 --- a/lib/httpx/channel.rb +++ b/lib/httpx/channel.rb @@ -30,7 +30,7 @@ module HTTPX def initialize(io, options, &on_response) @io = io - @options = HTTPX::Options.new(options) + @options = Options.new(options) @read_buffer = +"" @write_buffer = +"" @pending = [] diff --git a/lib/httpx/channel/http1.rb b/lib/httpx/channel/http1.rb index 5d2913ad..a3740b3b 100644 --- a/lib/httpx/channel/http1.rb +++ b/lib/httpx/channel/http1.rb @@ -61,7 +61,8 @@ module HTTPX def on_headers_complete(h) log { "headers received" } - response = Response.new(@selector, @parser.status_code, h) + headers = @options.headers_class.new(h) + response = @options.response_class.new(@selector, @parser.status_code, headers) @responses << response request = @requests[@responses.size - 1] emit(:response, request, response) diff --git a/lib/httpx/channel/http2.rb b/lib/httpx/channel/http2.rb index c389bcff..65057f6c 100644 --- a/lib/httpx/channel/http2.rb +++ b/lib/httpx/channel/http2.rb @@ -45,9 +45,10 @@ module HTTPX end # stream.on(:half_close) # stream.on(:altsvc) - stream.on(:headers) do |headers| - _, status = headers.shift - response = Response.new(@selector, status, headers) + stream.on(:headers) do |h| + _, status = h.shift + headers = @options.headers_class.new(h) + response = @options.response_class.new(@selector, status, headers) @streams[stream.id] = response emit(:response, request, response) end diff --git a/lib/httpx/client.rb b/lib/httpx/client.rb index 73491df6..e3dd41b6 100644 --- a/lib/httpx/client.rb +++ b/lib/httpx/client.rb @@ -3,7 +3,7 @@ module HTTPX class Client def initialize(**options) - @default_options = Options.new(options) + @default_options = self.class.default_options.merge(options) @connection = Connection.new(@default_options) end @@ -12,7 +12,7 @@ module HTTPX end def request(verb, uri, **options) - Request.new(verb, uri, **@default_options.merge(options)) + @default_options.request_class.new(verb, uri, **@default_options.merge(options)) end def send(*requests) @@ -30,5 +30,32 @@ module HTTPX end requests.size == 1 ? responses.first : responses end + + @default_options = Options.new + + class << self + attr_reader :default_options + + def inherited(klass) + super + klass.instance_variable_set(:@default_options, @default_options.dup) + end + + def plugin(pl, *args, &block) + # raise Error, "Cannot add a plugin to a frozen config" if frozen? + pl = Plugins.load_plugin(pl) if pl.is_a?(Symbol) + pl.load_dependencies(self, *args, &block) if pl.respond_to?(:load_dependencies) + include(pl::InstanceMethods) if defined?(pl::InstanceMethods) + extend(pl::ClassMethods) if defined?(pl::ClassMethods) + default_options.request_class.send(:include, pl::RequestMethods) if defined?(pl::RequestMethods) + default_options.request_class.extend(pl::RequestClassMethods) if defined?(pl::RequestClassMethods) + default_options.response_class.send(:include, pl::ResponseMethods) if defined?(pl::ResponseMethods) + default_options.response_class.extend(pl::ResponseClassMethods) if defined?(pl::ResponseClassMethods) + default_options.headers_class.send(:include, pl::HeadersMethods) if defined?(pl::HeadersMethods) + default_options.headers_class.extend(pl::HeadersClassMethods) if defined?(pl::HeadersClassMethods) + pl.configure(self, *args, &block) if pl.respond_to?(:configure) + nil + end + end end end diff --git a/lib/httpx/options.rb b/lib/httpx/options.rb index b92dee34..1af28886 100644 --- a/lib/httpx/options.rb +++ b/lib/httpx/options.rb @@ -32,13 +32,16 @@ module HTTPX def initialize(options = {}) defaults = { - :proxy => {}, - :ssl => {}, - :timeout => Timeout.by(:null), - :headers => {}, - :cookies => {}, - :max_concurrent_requests => MAX_CONCURRENT_REQUESTS, - :max_retries => MAX_RETRIES, + :proxy => {}, + :ssl => {}, + :timeout => Timeout.by(:null), + :headers => {}, + :cookies => {}, + :max_concurrent_requests => MAX_CONCURRENT_REQUESTS, + :max_retries => MAX_RETRIES, + :request_class => Class.new(Request), + :response_class => Class.new(Response), + :headers_class => Class.new(Headers), } defaults.merge!(options) @@ -70,6 +73,7 @@ module HTTPX %w[ params form json body proxy follow ssl max_retries + request_class response_class headers_class ].each do |method_name| def_option(method_name) end diff --git a/lib/httpx/request.rb b/lib/httpx/request.rb index aae7e30a..28d4f2d9 100644 --- a/lib/httpx/request.rb +++ b/lib/httpx/request.rb @@ -36,7 +36,7 @@ module HTTPX raise(Error, "unknown method: #{verb}") unless METHODS.include?(@verb) - @headers = Headers.new(@options.headers) + @headers = @options.headers_class.new(@options.headers) @headers["user-agent"] ||= USER_AGENT @headers["accept"] ||= "*/*" @@ -55,7 +55,7 @@ module HTTPX end def <<(data) - (@body ||= +"") << data + @body << data end def authority