1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
|
# Writing custom adapters
!> A template for writing your own custom adapter is available in the [faraday-adapter-template](https://github.com/lostisland/faraday-adapter-template) repository.
Adapters have methods that can help you implement support for a new backend.
This example will use a fictional HTTP backend gem called `FlorpHttp`. It doesn't
exist. Its only function is to make this example more concrete.
## An Adapter _is_ a Middleware
When you subclass `Faraday::Adapter`, you get helpful methods defined and all you need to do is to
extend the `call` method (remember to call `super` inside it!):
```ruby
module Faraday
class Adapter
class FlorpHttp < Faraday::Adapter
def call(env)
super
# Perform the request and call `save_response`
end
end
end
end
```
Now, there are only two things which are actually mandatory for an adapter middleware to function:
- a `#call` implementation
- a call to `#save_response` inside `#call`, which will keep the Response around.
These are the only two things, the rest of this text is about methods which make the authoring easier.
Like any other middleware, the `env` parameter passed to `#call` is an instance of [Faraday::Env][env-object].
This object will contain all the information about the request, as well as the configuration of the connection.
Your adapter is expected to deal with SSL and Proxy settings, as well as any other configuration options.
## Connection options and configuration block
Users of your adapter have two main ways of configuring it:
* connection options: these can be passed to your adapter initializer and are automatically stored into an instance variable `@connection_options`.
* configuration block: this can also be provided to your adapter initializer and it's stored into an instance variable `@config_block`.
Both of these are automatically managed by `Faraday::Adapter#initialize`, so remember to call it with `super` if you create an `initialize` method in your adapter.
You can then use them in your adapter code as you wish, since they're pretty flexible.
Below is an example of how they can be used:
```ruby
# You can use @connection_options and @config_block in your adapter code
class FlorpHttp < Faraday::Adapter
def call(env)
# `connection` internally calls `build_connection` and yields the result
connection do |conn|
# perform the request using configured `conn`
end
end
def build_connection(env)
conn = FlorpHttp::Client.new(pool_size: @connection_options[:pool_size] || 10)
@config_block&.call(conn)
conn
end
end
# Then your users can provide them when initializing the connection
Faraday.new(...) do |f|
# ...
# in this example, { pool_size: 5 } will be provided as `connection_options`
f.adapter :florp_http, pool_size: 5 do |client|
# this block is useful to set properties unique to HTTP clients that are not
# manageable through the Faraday API
client.some_fancy_florp_http_property = 10
end
end
```
## Implementing `#close`
Just like middleware, adapters can implement a `#close` method. It will be called when the connection is closed.
In this method, you should close any resources that you opened in `#initialize` or `#call`, like sockets or files.
```ruby
class FlorpHttp < Faraday::Adapter
def close
@socket.close if @socket
end
end
```
## Helper methods
`Faraday::Adapter` provides some helper methods to make it easier to implement adapters.
### `#save_response`
The most important helper method and the only one you're expected to call from your `#call` method.
This method is responsible for, among other things, the following:
* Take the `env` object and save the response into it.
* Set the `:response` key in the `env` object.
* Parse headers using `Utils::Headers` and set the `:response_headers` key in the `env` object.
* Call `#finish` on the `Response` object, triggering the `#on_complete` callbacks in the middleware stack.
```ruby
class FlorpHttp < Faraday::Adapter
def call(env)
super
# Perform the request using FlorpHttp.
# Returns a FlorpHttp::Response object.
response = FlorpHttp.perform_request(...)
save_response(env, response.status, response.body, response.headers, response.reason_phrase)
end
end
```
`#save_response` also accepts a `finished` keyword argument, which defaults to `true`, but that you can set to false
if you don't want it to call `#finish` on the `Response` object.
### `#request_timeout`
Most HTTP libraries support different types of timeouts, like `:open_timeout`, `:read_timeout` and `:write_timeout`.
Faraday let you set individual values for each of these, as well as a more generic `:timeout` value on the request options.
This helper method knows about supported timeout types, and falls back to `:timeout` if they are not set.
You can use those when building the options you need for your backend's instantiation.
```ruby
class FlorpHttp < Faraday::Adapter
def call(env)
super
# Perform the request using FlorpHttp.
# Returns a FlorpHttp::Response object.
response = FlorpHttp.perform_request(
# ... other options ...,
open_timeout: request_timeout(:open, env[:request]),
read_timeout: request_timeout(:read, env[:request]),
write_timeout: request_timeout(:write, env[:request])
)
# Call save_response
end
end
```
## Register your adapter
Like middleware, you may register a nickname for your adapter.
People can then refer to your adapter with that name when initializing their connection.
You do that using `Faraday::Adapter.register_middleware`, like this:
```ruby
class FlorpHttp < Faraday::Adapter
# ...
end
Faraday::Adapter.register_middleware(florp_http: FlorpHttp)
```
[env-object]: /getting-started/env-object.md
|