2 # A Builder that processes requests into responses by passing through an inner
3 # middleware stack (heavily inspired by Rack).
5 # Faraday::Connection.new(:url => 'http://sushi.com') do |builder|
6 # builder.request :url_encoded # Faraday::Request::UrlEncoded
7 # builder.adapter :net_http # Faraday::Adapter::NetHttp
10 attr_accessor :handlers
12 # Error raised when trying to modify the stack after calling `lock!`
13 class StackLocked < RuntimeError; end
15 # borrowed from ActiveSupport::Dependencies::Reference &
16 # ActionDispatch::MiddlewareStack::Middleware
18 @@constants_mutex = Mutex.new
19 @@constants = Hash.new { |h, k|
20 value = k.respond_to?(:constantize) ? k.constantize : Object.const_get(k)
21 @@constants_mutex.synchronize { h[k] = value }
26 def initialize(klass, *args, &block)
28 if klass.respond_to?(:name)
29 @@constants_mutex.synchronize { @@constants[@name] = klass }
31 @args, @block = args, block
34 def klass() @@constants[@name] end
35 def inspect() @name end
38 if other.is_a? Handler
39 self.name == other.name
40 elsif other.respond_to? :name
48 klass.new(app, *@args, &@block)
52 def initialize(handlers = [])
56 elsif @handlers.empty?
57 # default stack, if nothing else is configured
58 self.request :url_encoded
59 self.adapter Faraday.default_adapter
63 def build(options = {})
65 @handlers.clear unless options[:keep]
66 yield(self) if block_given?
73 # Locks the middleware stack to ensure no further modifications are possible.
82 def use(klass, *args, &block)
84 use_symbol(Faraday::Middleware, klass, *args, &block)
87 @handlers << self.class::Handler.new(klass, *args, &block)
91 def request(key, *args, &block)
92 use_symbol(Faraday::Request, key, *args, &block)
95 def response(key, *args, &block)
96 use_symbol(Faraday::Response, key, *args, &block)
99 def adapter(key, *args, &block)
100 use_symbol(Faraday::Adapter, key, *args, &block)
103 ## methods to push onto the various positions in the stack:
105 def insert(index, *args, &block)
107 index = assert_index(index)
108 handler = self.class::Handler.new(*args, &block)
109 @handlers.insert(index, handler)
112 alias_method :insert_before, :insert
114 def insert_after(index, *args, &block)
115 index = assert_index(index)
116 insert(index + 1, *args, &block)
119 def swap(index, *args, &block)
121 index = assert_index(index)
122 @handlers.delete_at(index)
123 insert(index, *args, &block)
128 @handlers.delete(handler)
131 # Processes a Request into a Response by passing it through this Builder's
134 # connection - Faraday::Connection
135 # request - Faraday::Request
137 # Returns a Faraday::Response.
138 def build_response(connection, request)
139 app.call(build_env(connection, request))
142 # The "rack app" wrapped in middleware. All requests are sent here.
144 # The builder is responsible for creating the app object. After this,
145 # the builder gets locked to ensure no further modifications are made
146 # to the middleware stack.
148 # Returns an object that responds to `call` and returns a Response.
152 to_app(lambda { |env|
153 response = Response.new
154 response.finish(env) unless env.parallel?
155 env.response = response
160 def to_app(inner_app)
161 # last added handler is the deepest and thus closest to the inner app
162 @handlers.reverse.inject(inner_app) { |app, handler| handler.build(app) }
166 other.is_a?(self.class) && @handlers == other.handlers
170 self.class.new(@handlers.dup)
174 # :method - a symbolized request method (:get, :post)
175 # :body - the request body that will eventually be converted to a string.
176 # :url - URI instance for the current request.
177 # :status - HTTP response status code
178 # :request_headers - hash of HTTP Headers to be sent to the server
179 # :response_headers - Hash of HTTP headers from the server
180 # :parallel_manager - sent if the connection is in parallel mode
181 # :request - Hash of options for configuring the request.
182 # :timeout - open/read timeout Integer in seconds
183 # :open_timeout - read timeout Integer in seconds
184 # :proxy - Hash of proxy options
185 # :uri - Proxy Server URI
186 # :user - Proxy server username
187 # :password - Proxy server password
188 # :ssl - Hash of options for configuring SSL requests.
189 def build_env(connection, request)
190 Env.new(request.method, request.body,
191 connection.build_exclusive_url(request.path, request.params),
192 request.options, request.headers, connection.ssl,
193 connection.parallel_manager)
199 raise StackLocked, "can't modify middleware stack after making a request" if locked?
202 def use_symbol(mod, key, *args, &block)
203 use(mod.lookup_middleware(key), *args, &block)
206 def assert_index(index)
207 idx = index.is_a?(Integer) ? index : @handlers.index(index)
208 raise "No such handler: #{index.inspect}" unless idx