Compare commits

...

111 Commits
v2.7.7 ... main

Author SHA1 Message Date
dependabot[bot]
bc27144430
Bump actions/checkout from 4 to 5 (#1636)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-12 09:42:18 +02:00
Olle Jonsson
f0aab63319
Lint: use filter_map (#1637) 2025-08-12 09:38:38 +02:00
Matt
d099fafd65
Version bump to 2.13.4 2025-07-25 13:19:35 +01:00
Matt
cf32578f25
Improve error handling logic and add missing test coverage (#1633) 2025-07-25 13:18:53 +01:00
Matt
e76e60d3c0
Version bump to 2.13.3 2025-07-22 09:34:48 +01:00
Matt
674fc1583f
Fix type assumption in Faraday::Error (#1630)
Following #1627, we began to assume that the parameter passed to `Faraday::Error#new` could only be either and `Exception` or a `Hash`.

As demonstrated in #1629, it turns out in the real world we're also passing `Faraday::Env` instances when building errors, which also respond to `each_key` and `[]` too.
2025-07-22 09:34:19 +01:00
Matt
da86ebae9c
Version bump to 2.13.2 2025-07-04 14:14:39 +01:00
Niels Buus
ad8fe1e89a
Include HTTP method and URL in Faraday::Error messages for improved exception log transparency (#1628) 2025-07-04 14:09:46 +01:00
Olle Jonsson
1ddd281893 CONTRIBUTING: update socials links to Mastodon 2025-06-18 09:41:40 +02:00
Josef Šimánek
976369857e
Add migrating from rest-client docs section. (#1625) 2025-06-15 13:46:10 +03:00
Olle Jonsson
64e8a2bdb1
Lint rack_builder.rb: avoid naming a method (#1626)
Lint rack_builder.rb: avoid naming a method

The method name gave a complaint
2025-06-15 10:03:01 +03:00
Earlopain
bbaa093dbc
Only load what is required from cgi (#1623)
In Ruby 3.5 most of the `cgi` gem will be removed. Only the various escape/unescape methods will be retained by default.

On older versions, `require "cgi/util"` is needed because the unescape* methods don't work otherwise.

https://bugs.ruby-lang.org/issues/21258
2025-05-12 11:12:42 +01:00
Earlopain
fa9424b05a CI against Ruby 3.4 2025-05-11 15:10:53 +02:00
Matt
4018769a30
Version bump to 2.13.1 2025-04-25 15:37:39 +02:00
Matt
b5a02d7300
Fix Style/RedundantParentheses in options/env.rb (#1620) 2025-04-25 15:28:56 +02:00
Robert Keresnyei
b63eb9121f
Logger middleware default options (#1618) 2025-04-25 15:20:50 +02:00
Matt
77204cc7e8
Version bump to 2.13.0 2025-04-08 21:19:06 +01:00
Benjamin Fleischer
919dc8fdb3
feat(ssl options): support SNI hostname (#1615) 2025-04-08 21:16:16 +01:00
Matt
064a54be6c
Version bump to 2.12.3 2025-04-08 14:05:30 +01:00
Hiroaki Osawa
cd1c44a4aa
Fix thread safety issue by avoiding mutation of proxy options hash (#1617) 2025-04-08 13:53:16 +01:00
Sebastian Cohnen
1551c32371 removes ruby2_keywords usage 2025-03-03 06:54:32 +01:00
Matt
a9cf00425e
Version bump to 2.12.2 2024-12-09 10:50:18 +00:00
kodram
529b5b043e
Formatting the log using parameter progname for the logger (#1606) 2024-12-09 10:45:36 +00:00
Mamoru TASAKA
b7b2bc19e9 [TEST] fix compatibility with ruby 3.4.0dev
ruby 3.4 changes Hash#inspect formatting as:
https://bugs.ruby-lang.org/issues/20433

Closes #1602
2024-12-03 08:47:00 +01:00
chaymaeBZ
f9f4ce5bc1 Use generic argument forwarding + remove ruby2_keywords 2024-11-26 09:48:05 +01:00
Matt
93ef9e0ea9
Version bump to 2.12.1 2024-11-14 11:38:20 +00:00
Richard Marbach
fff02307ac
Allow faraday-net_http 3.4.x (#1599) 2024-11-14 11:37:28 +00:00
Matt
59c5003ceb
Version bump to 2.12.0 2024-09-18 10:03:36 +01:00
Clemens Kofler
98d5adf924
Make RaiseError middleware configurable to not raise error on certain status codes (e.g. 404) (#1590) 2024-09-13 15:08:50 +01:00
David Rodríguez
9e5c8a113f
Add json as an explicit dependency (#1589) 2024-09-11 08:31:10 +02:00
JC (Jonathan Chen)
9fcff671ff
docs: fix grammar (#1588) 2024-09-10 08:19:42 +02:00
Matt
3170e7df6f
Version bump to 2.11.0 2024-08-26 09:48:45 +01:00
Matt
f208ffcc9a
Allow faraday-net_http 3.3.x 2024-08-26 09:48:10 +01:00
womblep
9056eccea6
Add ciphers attribute to SSLOptions (#1582) 2024-08-24 11:25:57 +01:00
Geremia Taglialatela
99228e4743
Fix typos (#1585)
Found via `codespell`.
2024-08-24 09:23:20 +02:00
Geremia Taglialatela
9cdc025759
Opt-in for MFA requirement explicitly (#1580)
As a popular gem, `faraday` implicitly requires that all privileged
operations by any of the owners require OTP.

However, by explicitly setting `rubygems_mfa_required` metadata, the
gem will show "NEW VERSIONS REQUIRE MFA" and
"VERSION PUBLISHED WITH MFA" in the sidebar at
https://rubygems.org/gems/faraday

Ref:
- https://blog.rubygems.org/2022/08/15/requiring-mfa-on-popular-gems.html
- https://guides.rubygems.org/mfa-requirement-opt-in/
2024-08-22 17:17:20 +01:00
Matt
3835b48d80
Add support for a new ParallelManager#execute method. (#1584)
* Add support for a new `ParallelManager#execute` method.

The new interface passes the parallel block with all the requests to the ParallelManager, instead of running it beforehand.

This allows for better, stateless ParallelManager implementations.

Fixes https://github.com/lostisland/faraday/issues/1583

* Update docs/adapters/custom/parallel-requests.md

Co-authored-by: Olle Jonsson <olle.jonsson@gmail.com>

---------

Co-authored-by: Olle Jonsson <olle.jonsson@gmail.com>
2024-08-22 13:05:07 +01:00
dependabot[bot]
3efc0a8982
Update faraday-net_http requirement from >= 2.0, < 3.2 to >= 2.0, < 3.3 (#1579)
Updates the requirements on [faraday-net_http](https://github.com/lostisland/faraday-net_http) to permit the latest version.
- [Release notes](https://github.com/lostisland/faraday-net_http/releases)
- [Commits](https://github.com/lostisland/faraday-net_http/compare/v2.0.0...v3.2.0)

---
updated-dependencies:
- dependency-name: faraday-net_http
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-05 15:23:59 +02:00
Olle Jonsson
f27f7ab801 v2.10.1 2024-07-31 14:07:48 +02:00
Olle Jonsson
051a635f4b
fix: Avoid lazy-initialized lock (#1577) 2024-07-31 14:02:46 +02:00
Olle Jonsson
4860f75372 Update JS deps
We use docsify to render the documentation site, and so we have a package.json.

This updates its lockfile.

I used `npm audit fix` to update only the reported issues.
2024-07-22 13:05:29 +02:00
Matt
073faf7539
Remove rubygems-await version pinning
The commit being pinned has now been merged to master and released
2024-07-22 12:00:20 +01:00
Matt
18524c6e89
Version bump to 2.10.0 2024-07-08 10:36:36 +01:00
Matt
d51c392c2c
Use specific version of rubygems-await
See https://github.com/segiddins/rubygems-await/issues/47
2024-07-08 10:35:46 +01:00
Ryan McNeil
d83a2818e7
Introduce Middleware DEFAULT_OPTIONS with Application and Instance Configurability (#1572) 2024-07-05 19:30:43 +01:00
Fabian Winkler
1958cb1ce2
Add logger as explicit dependency (#1573) 2024-07-05 16:08:44 +01:00
Yutaka Kamei
d7d5a6a36f
Configure "npm" package-ecosystem for Dependabot (#1571) 2024-06-25 11:57:48 +01:00
Matt
5996054fd4
Version bump to 2.9.2 2024-06-18 09:52:10 +01:00
ykrods
d8bfca25fa
Merge relative url without escaping (#1569) 2024-06-18 09:50:41 +01:00
Mattia Giuffrida
4abafa5c66 Add Bundler::GemHelper tasks to Rakefile 2024-06-05 16:30:09 +01:00
Matt
89107f9889
Add missing bundle install to publish.yml 2024-06-05 15:57:43 +01:00
Matt
b5245081d9
Version bump to 2.9.1 2024-06-05 15:06:55 +01:00
Max Rozenoer
04515f38b3
Support default json decoder even when nil responds to :load (#1563)
In Rails (some versions at least), nil responds to `:load` (via ActiveSupport::Dependencies::Loadable mixin).
Enable default json decoder to work in this case
2024-06-05 15:06:16 +01:00
Tijmen Brommet
6933e9b70f
Add TooManyRequestsError (429) to error docs (#1565) 2024-06-04 11:34:03 +01:00
Masato Nakamura
6d82d716c2
Fix compatibility with Ruby 3.4.0-preview1 (#1560) 2024-05-24 15:50:52 +01:00
Mattia Giuffrida
7dc694150d Fix Rubocop errors 2024-05-24 16:41:19 +02:00
Vitali Semenyuk
c9cc1b30ec Make dig method case-insensitive in Faraday::Utils::Headers 2024-04-02 14:37:59 +02:00
Olle Jonsson
c0540b7ba3 Lint fix
Lint/RedundantCopDisableDirective: Unnecessary disabling of Performance/RedundantBlockCall
2024-04-02 12:54:46 +02:00
Matt
87e655f306
Use Rubygems Trusted Publishers to publish. (#1552) 2024-01-20 17:09:48 +00:00
dependabot[bot]
cd2cdfd446
Update rack requirement from ~> 2.2 to ~> 3.0 (#1549) 2024-01-20 16:53:12 +00:00
Matt
f56e9387c8
Remove unnecessary rubocop disable comments. (#1551)
These were added in #1550 because of an unsafe autocorrect, but have since been addressed in https://github.com/rubocop/rubocop/pull/12628
2024-01-20 16:33:29 +00:00
Gareth Jones
18154c8332
docs: update body param type for run_request (#1545) 2024-01-20 08:49:37 +00:00
Olle Jonsson
4b34b509fe Add RuboCop disables
The -a autoformatting failed. Putting these in, to pass.
2024-01-15 16:44:18 +01:00
geemus
d820a58314 add bundler config to dependabot 2024-01-15 16:07:29 +01:00
Matt
cc5d607766
Version bump to 2.9.0 2024-01-09 10:35:49 +00:00
Matt
ceb01e42e8
Bump faraday-net_http version to allow 3.1 (#1546)
v3.1 increases the minimum ruby version to 3.0
2024-01-09 10:33:43 +00:00
Mattia Giuffrida
074506e67c Use latest Ruby version to publish and run rubocop 2023-12-28 18:09:17 +01:00
Mattia Giuffrida
898f203584 Run rubocop in CI using Ruby 3.3 2023-12-28 18:09:17 +01:00
Mattia Giuffrida
f0f549d7ef Fix Rubocop offenses 2023-12-28 18:09:17 +01:00
Mattia Giuffrida
caa4ff42f8 Update GitHub workflows, add 3.3 to CI matrix 2023-12-28 18:09:17 +01:00
Mattia Giuffrida
13732f7ff2 Remove ruby2_keywords dependency 2023-12-28 18:09:17 +01:00
Mattia Giuffrida
8cbfd758c2 Make 3.0 the minimum supported Ruby version 2023-12-28 18:09:17 +01:00
Earlopain
9487833b42
Remove runtime dependency on base64 (#1541) 2023-12-27 10:07:35 +00:00
Olle Jonsson
7e12133b92
v2.8.1 2023-12-21 09:10:21 +01:00
Olle Jonsson
1206b3917c [ci skip] Add context-preserving comment 2023-12-21 09:07:45 +01:00
Olle Jonsson
d0e0436e24 Revert "Lint"
This require is necessary, in order to have access to the method Hash#pretty_inspect. Thanks segiddins for sharp eyes!

This reverts commit 53c7b499db87894c56d9a556ff9bddf5852b002d.
2023-12-21 09:07:45 +01:00
Matt
25da081f59
Version bump to 2.8.0 2023-12-20 09:36:53 +00:00
Igor Tikhomirov
1e81e2c71a
Configurable JSON encoders and decoders (#1539) 2023-12-20 09:35:08 +00:00
dependabot[bot]
b8e2e454df Bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-14 09:26:48 +01:00
Olle Jonsson
09a635f30e CI: tell dependabot to update GH Actions 2023-12-14 09:25:30 +01:00
Olle Jonsson
fa6075c0f9 lint: Style/HashEachMethods 2023-12-14 09:23:34 +01:00
Olle Jonsson
53c7b499db Lint 2023-12-14 09:23:34 +01:00
Olle Jonsson
639e1b52e7 Gemfile: add racc to development, lint group
In Ruby 3.3, this is not a stdlib.
2023-12-14 09:23:34 +01:00
Wesley Beary
342e7e65cd Update testing.md
fix `deflate` typo in description of compression feature
2023-12-13 22:13:34 +01:00
Matt
3e27447a80
Version bump to 2.7.12 2023-11-21 13:55:44 +00:00
Daniel Pepper
63ba2aaf0c 429 error 2023-10-21 01:45:30 +02:00
k0i
5fccc20854 Update parallel-requests.md 2023-10-03 06:51:57 +02:00
Matt
28a097f756
Version bump to 2.7.11 2023-09-12 16:41:46 +01:00
Clayton Passmore
5ccddaafef
Add option for omitting request data from Faraday exceptions (#1526) 2023-09-12 16:38:01 +01:00
Koichi ITO
ea30bd0b54 Add base64 to runtime dependency
This PR add `base64` to runtime dependency to suppress the following Ruby 3.3's warning:

```console
$ ruby -ve 'require "faraday"'
ruby 3.3.0dev (2023-08-14T15:48:39Z master 52837fcec2) [x86_64-darwin22]
/Users/koic/.rbenv/versions/3.3.0-dev/lib/ruby/gems/3.3.0+0/gems/faraday-2.7.10/lib/faraday/utils.rb:3:
warning: base64 which will be not part of the default gems since Ruby 3.4.0
```

cf: https://github.com/rails/rails/pull/48907
2023-08-15 10:47:52 +02:00
Olle Jonsson
230fa1b1f5 Format code with less indent 2023-08-15 08:58:34 +02:00
Koichi ITO
7ce686503a Fix a test failure using Ruby 3.3.0dev
This PR fixes the following test failure using Ruby 3.3.0dev:

```console
$ ruby -v
ruby 3.3.0dev (2023-08-14T15:48:39Z master 52837fcec2) [x86_64-darwin22]
$ bundle exec rspec spec/faraday_spec.rb

Randomized with seed 57031

Faraday
  has a version number
  proxies to default_connection
    uses method_missing on Faraday if there is no proxyable method (FAILED - 1)
    proxies methods that exist on the default_connection
    proxied methods can be accessed

Failures:

  1) Faraday proxies to default_connection uses method_missing on Faraday if there is no proxyable method
     Failure/Error:
       expect { Faraday.this_method_does_not_exist }.to raise_error(
         NoMethodError, expected_message
       )

       expected NoMethodError with "undefined method `this_method_does_not_exist' for Faraday:Module",
       got #<NoMethodError: undefined method `this_method_does_not_exist' for module Faraday> with backtrace:
         # ./lib/faraday.rb:147:in `method_missing'
         # ./spec/faraday_spec.rb:27:in `block (4 levels) in <top (required)>'
         # ./spec/faraday_spec.rb:27:in `block (3 levels) in <top (required)>'
     # ./spec/faraday_spec.rb:27:in `block (3 levels) in <top (required)>'
```

That error message has been changed by https://github.com/ruby/ruby/commit/e7b8d32e in Ruby 3.3.0dev.

cf. https://bugs.ruby-lang.org/issues/18285

So the test error message is changed:

Ruby 3.2 or lower:

```
undefined method `this_method_does_not_exist' for Faraday:Module
```

Ruby 3.3.0dev:

```
NoMethodError: undefined method `this_method_does_not_exist' for module Faraday
```
2023-08-15 08:52:00 +02:00
Edward Loveall
26168c4ce4 Fix included middleware links
The links were pointing to `/middleware/___` instead of
`/middleware/included/___`
2023-08-07 08:45:41 +02:00
shane pope
63c38f7716 Fix 404 link in UPGRADING documentation
https://lostisland.github.io/faraday/#/middleware/included/authentication is the corrected url
2023-08-05 07:19:54 +02:00
Fernando Briano
e88b55a3d4
Fix capitalization for Elasticsearch (#1520) 2023-08-02 15:11:56 +01:00
Matt
1eca6a987d
Remove dead links to "Faraday Team" page.
Fixes #1518
2023-07-25 10:35:05 +01:00
Mattia Giuffrida
0580f101ce Add new logo image 2023-07-22 18:06:09 +01:00
Matt
30c4205958
Add "why use faraday?" section to repository README.md 2023-07-22 08:57:06 +01:00
Matt
c88fa65e07
Support twitter meta tags for docs site link preview 2023-07-13 15:55:00 +01:00
Matt
228f660a92
Improve Docs site link preview 2023-07-13 15:50:12 +01:00
Matt
17d586cb2f
📄 New docs 🎉 (#1517) 2023-07-13 15:29:16 +01:00
Olle Jonsson
15788115f5
Refer to correct branch [ci skip] (#1516) 2023-07-10 12:52:09 +01:00
Matt
1518a984c0
Version bump to 2.7.10 2023-07-06 09:47:20 +01:00
Simon Perepelitsa
d1743e6476
Fix some logging inefficiencies (#1515) 2023-07-06 09:45:36 +01:00
Matt
3af5211900
Version bump to 2.7.9 2023-06-30 09:37:22 +01:00
Yutaka Kamei
1e3e4e056d
Include env[:headers] in Stubs::NotFound (#1514) 2023-06-30 09:36:05 +01:00
Sebastian Cohnen
83676df431 adds hint to docs/README.md how to compile eventmachine with Ruby >= 3.0 on macOS 2023-06-29 09:05:18 +02:00
Sebastian Cohnen
baa7b2724a updates raise error middleware docs 2023-06-29 09:05:18 +02:00
Sebastian Cohnen
b47e22bf5d adds Faraday::Response::RaiseError for HTTP 408 when using raise error middleware 2023-06-29 09:05:18 +02:00
Matt
2824eaf968
Version bump to 2.7.8 2023-06-28 10:18:00 +01:00
Eike Send
990799a850
Fix: Logging headers & errors fails when ConnectionFailed is raised (#1512) 2023-06-28 10:17:24 +01:00
112 changed files with 4724 additions and 1446 deletions

View File

@ -24,7 +24,7 @@ These resources can help:
This project attempts to improve in these areas. Join us in doing that important work.
If you want to privately raise any breach to this policy with the Faraday team, feel free to reach out to [@iMacTia](https://twitter.com/iMacTia) and [@olleolleolle](https://twitter.com/olleolleolle) on Twitter.
If you want to privately raise any breach to this policy with the Faraday team, feel free to reach out to [@iMacTia](https://ruby.social/@iMacTia) and [@olleolleolle](https://ruby.social/@olleolleolle) on the Mastodon instance ruby.social.
### Required Checks
@ -64,50 +64,16 @@ We encourage adapters that:
1. have features not present in included adapters.
### Changes to the Faraday Website
### Changes to the Faraday Docs
The [Faraday Website][website] is included in the Faraday repository, under the `/docs` folder.
The Faraday Docs are included in the Faraday repository, under the `/docs` folder and deployed to [GitHub Pages][website].
If you want to apply changes to it, please test it locally before opening your PR.
You can find more information in the [Faraday Docs README][docs], including how to preview changes locally.
#### Test website changes using Docker
Start by cloning the repository and navigate to the newly-cloned directory on your computer. Then run the following:
```bash
docker container run -p 80:4000 -v $(pwd)/docs:/site bretfisher/jekyll-serve
```
And that's it! Open your browser and navigate to `http://localhost` to see the website running.
Any change done to files in the `/docs` folder will be automatically picked up (with the exception of config changes).
#### Test website changes using Jekyll
You can test website changes locally, on your machine, too. Here's how:
Navigate into the /docs folder:
```bash
$ cd docs
```
Install Jekyll dependencies, this bundle is different from Faraday's one.
```bash
$ bundle install
```
Run the Jekyll server with the Faraday website
```bash
$ bundle exec jekyll serve
```
Now, navigate to http://127.0.0.1:4000/faraday/ to see the website running.
[semver]: https://semver.org/
[changelog]: https://github.com/lostisland/faraday/releases
[faraday_middleware]: https://github.com/lostisland/faraday_middleware
[website]: https://lostisland.github.io/faraday
[docs]: ../docs/README.md
[Code of Conduct]: ./CODE_OF_CONDUCT.md

14
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,14 @@
version: 2
updates:
- package-ecosystem: "bundler"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "npm"
directory: /
schedule:
interval: "weekly"

View File

@ -22,12 +22,12 @@ jobs:
BUNDLE_WITHOUT: development:test
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v5
- name: Set up Ruby 2.7
- name: Setup Ruby 3.x
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7
ruby-version: 3
bundler-cache: true
- name: Rubocop
@ -43,7 +43,7 @@ jobs:
strategy:
fail-fast: false
matrix:
ruby: [ '2.6', '2.7', '3.0', '3.1', '3.2' ]
ruby: [ '3.0', '3.1', '3.2', '3.3', '3.4' ]
experimental: [false]
include:
- ruby: head
@ -52,7 +52,7 @@ jobs:
experimental: true
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v5
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
@ -63,7 +63,6 @@ jobs:
run: bundle exec rake
- name: Test External Adapters
if: ${{ matrix.ruby != '2.6' }}
continue-on-error: ${{ matrix.experimental }}
run: bundle exec bake test:external

View File

@ -4,22 +4,23 @@ on:
release:
types: [published]
permissions:
contents: read # to checkout the code (actions/checkout)
jobs:
build:
name: Publish to Rubygems
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
permissions:
contents: write
id-token: write
- name: Set up Ruby 2.7
steps:
- uses: actions/checkout@v5
- name: Setup Ruby 3.x
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7
bundler-cache: true
ruby-version: 3
- name: Publish to RubyGems
uses: dawidd6/action-publish-gem@v1
with:
api_key: ${{secrets.RUBYGEMS_AUTH_TOKEN}}
uses: rubygems/release-gem@v1

4
.gitignore vendored
View File

@ -8,6 +8,7 @@ tmp
.rvmrc
.ruby-version
.yardoc
.DS_Store
## BUNDLER
*.gem
@ -16,6 +17,9 @@ Gemfile.lock
vendor/bundle
external
## NPM
node_modules
## PROJECT::SPECIFIC
.rbx

View File

@ -7,7 +7,7 @@ require:
AllCops:
DisplayCopNames: true
DisplayStyleGuide: true
TargetRubyVersion: 2.6
TargetRubyVersion: 3.0
# Custom config
Gemspec/RequireMFA: # we don't know if this works with auto-deployments yet

View File

@ -1,6 +1,6 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2022-08-08 14:26:32 UTC using RuboCop version 1.33.0.
# on 2023-12-27 11:12:52 UTC using RuboCop version 1.59.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
@ -23,23 +23,23 @@ Lint/EmptyBlock:
- 'spec/faraday/rack_builder_spec.rb'
- 'spec/faraday/response_spec.rb'
# Offense count: 12
# Configuration parameters: AllowedMethods, AllowedPatterns, IgnoredMethods, CountRepeatedAttributes.
# Offense count: 13
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
Metrics/AbcSize:
Max: 42
# Offense count: 4
# Offense count: 3
# Configuration parameters: CountComments, CountAsOne.
Metrics/ClassLength:
Max: 230
# Offense count: 9
# Configuration parameters: AllowedMethods, AllowedPatterns, IgnoredMethods.
# Configuration parameters: AllowedMethods, AllowedPatterns.
Metrics/CyclomaticComplexity:
Max: 13
# Offense count: 26
# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, AllowedMethods, AllowedPatterns, IgnoredMethods.
# Offense count: 27
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
Metrics/MethodLength:
Max: 33
@ -48,11 +48,22 @@ Metrics/MethodLength:
Metrics/ParameterLists:
Max: 6
# Offense count: 6
# Configuration parameters: AllowedMethods, AllowedPatterns, IgnoredMethods.
# Offense count: 7
# Configuration parameters: AllowedMethods, AllowedPatterns.
Metrics/PerceivedComplexity:
Max: 14
# Offense count: 19
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: AllowOnlyRestArgument, UseAnonymousForwarding, RedundantRestArgumentNames, RedundantKeywordRestArgumentNames, RedundantBlockArgumentNames.
# RedundantRestArgumentNames: args, arguments
# RedundantKeywordRestArgumentNames: kwargs, options, opts
# RedundantBlockArgumentNames: blk, block, proc
Style/ArgumentsForwarding:
Exclude:
- 'lib/faraday.rb'
- 'lib/faraday/rack_builder.rb'
# Offense count: 3
Style/DocumentDynamicEvalDefinition:
Exclude:

View File

@ -517,7 +517,7 @@ Breaking changes:
- Drop support for Ruby 1.8
Features:
- Include wrapped exception/reponse in ClientErrors
- Include wrapped exception/response in ClientErrors
- Add `response.reason_phrase`
- Provide option to selectively skip logging request/response headers
- Add regex support for pattern matching in `test` adapter

View File

@ -10,7 +10,7 @@ group :development, :test do
gem 'bake-test-external'
gem 'coveralls_reborn', require: false
gem 'pry'
gem 'rack', '~> 2.2'
gem 'rack', '~> 3.0'
gem 'rake'
gem 'rspec', '~> 3.7'
gem 'rspec_junit_formatter', '~> 0.4'
@ -19,6 +19,7 @@ group :development, :test do
end
group :development, :lint do
gem 'racc', '~> 1.7' # for RuboCop, on Ruby 3.3
gem 'rubocop'
gem 'rubocop-packaging', '~> 0.5'
gem 'rubocop-performance', '~> 1.0'

View File

@ -1,4 +1,4 @@
# [![Faraday](./docs/assets/img/repo-card-slim.png)][website]
# [![Faraday](./docs/_media/home-logo.svg)][website]
[![Gem Version](https://badge.fury.io/rb/faraday.svg)](https://rubygems.org/gems/faraday)
[![GitHub Actions CI](https://github.com/lostisland/faraday/workflows/CI/badge.svg)](https://github.com/lostisland/faraday/actions?query=workflow%3ACI)
@ -6,21 +6,36 @@
Faraday is an HTTP client library abstraction layer that provides a common interface over many
adapters (such as Net::HTTP) and embraces the concept of Rack middleware when processing the request/response cycle.
You probably don't want to use Faraday directly in your project, as it will lack an actual client library to perform
requests. Instead, you probably want to have a look at [Awesome Faraday][awesome] for a list of available adapters.
Take a look at [Awesome Faraday][awesome] for a list of available adapters and middleware.
## Why use Faraday?
Faraday gives you the power of Rack middleware for manipulating HTTP requests and responses,
making it easier to build sophisticated API clients or web service libraries that abstract away
the details of how HTTP requests are made.
Faraday comes with a lot of features out of the box, such as:
* Support for multiple adapters (Net::HTTP, Typhoeus, Patron, Excon, HTTPClient, and more)
* Persistent connections (keep-alive)
* Parallel requests
* Automatic response parsing (JSON, XML, YAML)
* Customization of the request/response cycle with middleware
* Support for streaming responses
* Support for uploading files
* And much more!
## Getting Started
The best starting point is the [Faraday Website][website], with its introduction and explanation.
Need more details? See the [Faraday API Documentation][apidoc] to see how it works internally, or take a look at [Advanced techniques for calling HTTP APIs in Ruby](https://mattbrictson.com/blog/advanced-http-techniques-in-ruby) blog post from @mattbrictson 🚀
Need more details? See the [Faraday API Documentation][apidoc] to see how it works internally, or take a look at [Advanced techniques for calling HTTP APIs in Ruby](https://mattbrictson.com/blog/advanced-http-techniques-in-ruby) blog post from [@mattbrictson](https://github.com/mattbrictson) 🚀
## Supported Ruby versions
This library aims to support and is [tested against][actions] the currently officially supported Ruby
implementations. This means that, even without a major release, we could add or drop support for Ruby versions,
following their [EOL](https://endoflife.date/ruby).
Currently that means we support Ruby 2.6+
Currently that means we support Ruby 3.0+
If something doesn't work on one of these Ruby versions, it's a bug.
@ -43,14 +58,10 @@ But before you start coding, please read our [Contributing Guide][contributing]
## Copyright
&copy; 2009 - 2023, the [Faraday Team][faraday_team]. Website and branding design by [Elena Lo Piccolo](https://elelopic.design).
&copy; 2009 - 2023, the Faraday Team. Website and branding design by [Elena Lo Piccolo](https://elelopic.design).
[awesome]: https://github.com/lostisland/awesome-faraday/#adapters
[website]: https://lostisland.github.io/faraday
[faraday_team]: https://lostisland.github.io/faraday/team
[contributing]: https://github.com/lostisland/faraday/blob/master/.github/CONTRIBUTING.md
[contributing]: https://github.com/lostisland/faraday/blob/main/.github/CONTRIBUTING.md
[apidoc]: https://www.rubydoc.info/github/lostisland/faraday
[actions]: https://github.com/lostisland/faraday/actions
[jruby]: http://jruby.org/
[rubinius]: http://rubini.us/
[license]: LICENSE.md

View File

@ -1,6 +1,9 @@
# frozen_string_literal: true
require 'rspec/core/rake_task'
require 'bundler'
Bundler::GemHelper.install_tasks
RSpec::Core::RakeTask.new(:spec) do |task|
task.ruby_opts = %w[-W]

View File

@ -19,7 +19,7 @@ We've taken this decision for the following technical reasons:
focused on the Faraday API more quickly, without having to push it on all adapters immediately.
* With the community creating more and more adapters, we wanted to avoid having first and second-class adapters
by having some of them included with the gem and others available externally.
* Moving adapters into separate gems allow to solve the dependency issues once and for all.
* Moving adapters into separate gems solve the dependency issues once and for all.
Faraday will remain a dependency-free gem, while adapter gems will be able to automatically pull
any necessary dependency, without having to rely on the developer to do so.
@ -112,7 +112,7 @@ but this has been removed from the v2.0 release to simplify the interface.
### Authentication helper methods in Connection have been removed
You were previously able to call `authorization`, `basic_auth` and `token_auth` against the `Connection` object, but this helper methods have now been dropped.
Instead, you should be using the equivalent middleware, as explained in [this page](https://lostisland.github.io/faraday/middleware/authentication).
Instead, you should be using the equivalent middleware, as explained in [this page](https://lostisland.github.io/faraday/#/middleware/included/authentication).
For more details, see https://github.com/lostisland/faraday/pull/1306

3
docs/.gitignore vendored
View File

@ -1,3 +0,0 @@
_site
.sass-cache
.jekyll-metadata

0
docs/.nojekyll Normal file
View File

View File

@ -1,24 +0,0 @@
---
layout: default
---
<style type="text/css" media="screen">
.container {
margin: 10px auto;
max-width: 600px;
text-align: center;
}
h1 {
margin: 30px 0;
font-size: 4em;
line-height: 1;
letter-spacing: -1px;
}
</style>
<div class="container">
<h1>404</h1>
<p><strong>Page not found :(</strong></p>
<p>The requested page could not be found.</p>
</div>

View File

@ -1,37 +0,0 @@
# frozen_string_literal: true
source 'https://rubygems.org'
# Hello! This is where you manage which Jekyll version is used to run.
# When you want to use a different version, change it below, save the
# file and run `bundle install`. Run Jekyll with `bundle exec`, like so:
#
# bundle exec jekyll serve
#
# This will help ensure the proper Jekyll version is running.
# Happy Jekylling!
# gem "jekyll", "~> 3.7.4"
# This is the default theme for new Jekyll sites.
# You may change this to anything you like.
# gem "minima", "~> 2.0"
# gem "jekyll-theme-type"
gem 'jekyll-remote-theme'
# If you want to use GitHub Pages, remove the "gem "jekyll"" above and
# uncomment the line below. To upgrade, run `bundle update github-pages`.
gem 'github-pages', group: :jekyll_plugins
# If you have any plugins, put them here!
group :jekyll_plugins do
gem 'jekyll-feed', '~> 0.6'
end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby]
# Performance-booster for watching directories on Windows
gem 'wdm', '~> 0.1.0' if Gem.win_platform?
# Ruby 3.X doesn't come with webrick by default anymore
gem 'webrick', '~> 1.7'

View File

@ -1,21 +1,21 @@
# Faraday Website
# Faraday Docs
This is the root directory of the [Faraday Website][website].
If you want to apply changes to it, please test it locally using `Jekyll`.
Faraday Docs are powered by [Docsify](https://docsify.js.org/#/).
Here is how:
## Development
### Setup
From the Faraday project root, run the following:
```bash
# Navigate into the /docs folder
$ cd docs
# Install Jekyll dependencies, this bundle is different from Faraday's one.
$ bundle install
# Run the Jekyll server with the Faraday website
$ bundle exec jekyll serve
# The site will now be reachable at http://127.0.0.1:4000/faraday/
npm install # this will install the necessary dependencies
```
[website]: https://lostisland.github.io/faraday
### Running the Docs Locally
To preview your changes locally, run the following:
```bash
npm run docs
```

View File

@ -1,55 +0,0 @@
# SITE CONFIGURATION
url: 'https://lostisland.github.io'
baseurl: '/faraday'
repository: 'lostisland/faraday'
# THEME-SPECIFIC CONFIGURATION
theme_settings:
title: Faraday
avatar: assets/img/logo.png
favicon: assets/img/favicon.png
# email: your-email@example.com
description: >-
Simple, but flexible HTTP client library, with support for multiple backends
footer_text: "&copy; 2009 - 2023, the <a class=\"body-link\" href=\"/faraday/team\">Faraday Team</a>. Website and branding design by <a href=\"https://elelopic.design\" target=\"_blank\">Elena Lo Piccolo</a>."
# Icons
github: 'lostisland/faraday'
gitter: 'lostisland/faraday'
# Post navigation
post_navigation: true
site_navigation_sort: 'order'
# BUILD SETTINGS
markdown: kramdown
remote_theme: rohanchandra/type-theme
plugins:
- jekyll-feed
- jekyll-remote-theme
# GitHub settings
lsi: false
safe: true
#source: [your repo's top level directory]
incremental: false
highlighter: rouge
gist:
noscript: false
kramdown:
math_engine: mathjax
syntax_highlighter: rouge
# Exclude from processing.
# The following items will not be processed, by default. Create a custom list
# to override the default setting.
exclude:
- Gemfile
- Gemfile.lock
- README.md
- node_modules
- vendor/bundle/
- vendor/cache/
- vendor/gems/
- vendor/ruby/

View File

@ -1,18 +0,0 @@
<div class="docs-nav">
<p class="docs-nav-item">
{% if page.prev_link %}
<a href="{{page.prev_link}}"><i class="fa fa-angle-left"></i> {{ page.prev_name }}</a>
{% endif %}
</p>
<p class="docs-nav-item">
{% if page.top_link %}
<a href="{{ page.top_link }}"><i class="fa fa-angle-up"></i> {{ page.top_name }}</a>
{% endif %}
</p>
<p class="docs-nav-item">
{% if page.next_link %}
<a href="{{ page.next_link }}">{{ page.next_name }} <i class="fa fa-angle-right"></i></a>
{% endif %}
</p>
</div>

View File

@ -1,21 +0,0 @@
<!-- Directly extracted from https://github.com/rohanchandra/type-theme/blob/c6ec5a69ff7dfe2df193be08515193c72bd4a55d/_includes/footer.html
for customization (DocSearch feature) -->
{% if site.theme_settings.katex and page.id %}
<script src="{{ "/assets/js/katex_init.js" | relative_url }}"></script>
{% endif %}
{% if site.theme_settings.footer_text %}
<footer class="site-footer">
<p class="text">{{ site.theme_settings.footer_text }}</p>
</footer>
{% endif %}
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.js"></script>
<script type="text/javascript"> docsearch({
apiKey: '4b20e432aa42a515b1a465b1494df8bf',
indexName: 'lostisland_faraday',
inputSelector: '#search-box',
debug: false // Set debug to true if you want to inspect the dropdown
});
</script>

View File

@ -1,59 +0,0 @@
<!-- This is extracted directly from https://github.com/rohanchandra/type-theme/blob/c6ec5a69ff7dfe2df193be08515193c72bd4a55d/_includes/header.html
for customization (DocSearch feature) -->
<header class="site-header">
<div class="branding">
{% if site.theme_settings.gravatar %}
<a href="{{ site.baseurl }}/">
<img class="avatar" src="https://secure.gravatar.com/avatar/{{ site.theme_settings.gravatar }}?s=100" alt=""/>
</a>
{% elsif site.theme_settings.avatar %}
<a href="{{ site.baseurl }}/">
<img class="avatar" src="{{ site.baseurl }}/{{ site.theme_settings.avatar }}" alt=""/>
</a>
{% endif %}
<h1 class="site-title">
<a href="{{ site.baseurl }}/">{{ site.theme_settings.title }}</a>
</h1>
</div>
<nav class="site-nav">
<ul>
{% if site.theme_settings.site_navigation_sort %}
{% assign site_pages = site.pages | sort: site.theme_settings.site_navigation_sort %}
{% else %}
{% assign site_pages = site.pages %}
{% endif %}
{% for page in site_pages %}
{% if page.title and page.hide != true %}
<li>
<a class="page-link" href="{{ page.url | relative_url }}">
{{ page.title }}
</a>
</li>
{% endif %}
{% endfor %}
<!-- Social icons from Font Awesome, if enabled -->
{% include icons.html %}
<!-- Search bar -->
{% if site.theme_settings.search %}
<li>
<form action="{{ site.baseurl }}/search.html" method="get">
<input type="text" id="search-box" name="query" placeholder="Search" class="">
<button type="submit" class="">
<i class="fa fa-fw fa-search"></i>
</button>
</form>
</li>
{% endif %}
<li>
<form id="search-form">
<input type="text" id="search-box" placeholder="Search">
<button type="submit">
<i class="fa fa-fw fa-search"></i>
</button>
</form>
</li>
</ul>
</nav>
</header>

View File

@ -1,7 +0,0 @@
---
layout: page
---
{{ content }}
{% include docs_nav.md %}

View File

Before

Width:  |  Height:  |  Size: 700 B

After

Width:  |  Height:  |  Size: 700 B

View File

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

BIN
docs/_media/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 225 KiB

After

Width:  |  Height:  |  Size: 225 KiB

BIN
docs/_media/overview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

View File

Before

Width:  |  Height:  |  Size: 186 KiB

After

Width:  |  Height:  |  Size: 186 KiB

View File

Before

Width:  |  Height:  |  Size: 358 KiB

After

Width:  |  Height:  |  Size: 358 KiB

View File

@ -1,25 +0,0 @@
---
layout: post
title: "Welcome to Jekyll!"
date: 2019-03-12 10:25:23 +0000
categories: jekyll update
---
Youll find this post in your `_posts` directory. Go ahead and edit it and re-build the site to see your changes. You can rebuild the site in many different ways, but the most common way is to run `jekyll serve`, which launches a web server and auto-regenerates your site when a file is updated.
To add new posts, simply add a file in the `_posts` directory that follows the convention `YYYY-MM-DD-name-of-post.ext` and includes the necessary front matter. Take a look at the source for this post to get an idea about how it works.
Jekyll also offers powerful support for code snippets:
{% highlight ruby %}
def print_hi(name)
puts "Hi, #{name}"
end
print_hi('Tom')
#=> prints 'Hi, Tom' to STDOUT.
{% endhighlight %}
Check out the [Jekyll docs][jekyll-docs] for more info on how to get the most out of Jekyll. File all bugs/feature requests at [Jekylls GitHub repo][jekyll-gh]. If you have questions, you can ask them on [Jekyll Talk][jekyll-talk].
[jekyll-docs]: https://jekyllrb.com/docs/home
[jekyll-gh]: https://github.com/jekyll/jekyll
[jekyll-talk]: https://talk.jekyllrb.com/

View File

@ -1,9 +0,0 @@
// Override theme variables.
@import url('https://fonts.googleapis.com/css?family=Raleway:700');
$link-color: #EE4266;
$text-color: #3C3C3C;
$font-family-main: 'KohinoorTelugu-Regular', Helvetica, Arial, sans-serif;
$font-family-headings: 'Raleway', Helvetica, Arial, sans-serif;
$search-color: #EE4266;

View File

@ -1,129 +0,0 @@
// Custom Styles added on top of the theme.
.btn
display: inline-block
background-color: $link-color
padding: 5px 10px
box-shadow: 0 4px 10px 5px rgba(238, 66, 102, 0.30)
border-radius: 20px
width: 200px
color: #FFFFFF
letter-spacing: -0.41px
text-align: center
margin: 0 10px
&:hover
background-color: darken($link-color, 10%)
color: white
text-decoration: none
.text-center
text-align: center
.mt-60
margin-top: 60px
.hidden
display: none
.docs-nav
display: flex
margin-top: 40px
.docs-nav-item
flex: 1 1 0
text-align: center
pre.highlight
padding: 20px
background-color: #F6F6F6
border-radius: 4px
code
word-wrap: normal
overflow: scroll
code.highlighter-rouge
background-color: #EEE
padding: 0 5px
border-radius: 3px
.site-header .site-nav li
margin-right: 1.2em
h1, h2, h3, h4, h5, h6
font-weight: bold
.feature-image header
@media (max-width: 1000px)
padding: 7% 12.5%
@media (max-width: 576px)
padding: 4% 5% 1% 5%
#team-content
h3
margin: 30px 0
#contributors-list
text-align: justify
.team-tile
width: 200px
display: inline-block
margin: 0 20px
img
width: 100%
border-radius: 50%
footer
background-color: #f1f3f4
#active-maintainers-list, #historical-team-list
text-align: center
#loader
margin-top: 20%
margin-bottom: 20%
text-align: center
.lds-ring
display: inline-block
position: relative
width: 200px
height: 200px
.lds-ring div
box-sizing: border-box
display: block
position: absolute
width: 187px
height: 187px
margin: 6px
border: 12px solid $link-color
border-radius: 50%
animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite
border-color: $link-color transparent transparent transparent
.lds-ring div:nth-child(1)
animation-delay: -0.45s
.lds-ring div:nth-child(2)
animation-delay: -0.3s
.lds-ring div:nth-child(3)
animation-delay: -0.15s
@keyframes lds-ring
0%
transform: rotate(0deg)
100%
transform: rotate(360deg)
#search-form
display: inline-block
// You need a minimal window size for the search to display well, should be ok, people are on desktop
// when needing search usually
@media screen and (max-width: 800px)
display: none

33
docs/_sidebar.md Normal file
View File

@ -0,0 +1,33 @@
* Getting Started
* [Quick Start](getting-started/quick-start.md)
* [The Env Object](getting-started/env-object.md)
* [Dealing with Errors](getting-started/errors.md)
* [Migrating from rest-client](getting-started/rest-client-migration.md)
* Customization
* [Configuration](customization/index.md)
* [Connection Options](customization/connection-options.md)
* [Request Options](customization/request-options.md)
* [SSL Options](customization/ssl-options.md)
* [Proxy Options](customization/proxy-options.md)
* Middleware
* [Overview](middleware/index.md)
* [Included](middleware/included/index.md)
* [Authentication](middleware/included/authentication.md)
* [URL Encoding](middleware/included/url-encoding.md)
* [JSON Encoding/Decoding](middleware/included/json.md)
* [Instrumentation](middleware/included/instrumentation.md)
* [Logging](middleware/included/logging.md)
* [Raising Errors](middleware/included/raising-errors.md)
* [Writing custom middleware](middleware/custom-middleware.md)
* Adapters
* [Overview](adapters/index.md)
* [Net::HTTP](adapters/net-http.md)
* [Test Adapter](adapters/test-adapter.md)
* Writing custom adapters
* [Overview](adapters/custom/index.md)
* [Parallel Requests](adapters/custom/parallel-requests.md)
* [Streaming Responses](adapters/custom/streaming.md)
* [Test your adapter](adapters/custom/testing.md)
* Advanced Features
* [Parallel Requests](advanced/parallel-requests.md)
* [Streaming Responses](advanced/streaming-responses.md)

View File

@ -0,0 +1,161 @@
# Writing custom adapters
!> A template for writing your own middleware 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

View File

@ -0,0 +1,75 @@
# Adding support for parallel requests
!> This is slightly more involved, and this section is not fully formed.
Vague example, excerpted from [the test suite about parallel requests](https://github.com/lostisland/faraday/blob/main/spec/support/shared_examples/request_method.rb#L179)
```ruby
response_1 = nil
response_2 = nil
conn.in_parallel do
response_1 = conn.get('/about')
response_2 = conn.get('/products')
end
puts response_1.status
puts response_2.status
```
First, in your class definition, you can tell Faraday that your backend supports parallel operation:
```ruby
class FlorpHttp < ::Faraday::Adapter
dependency do
require 'florp_http'
end
self.supports_parallel = true
end
```
Then, implement a method which returns a ParallelManager:
```ruby
class FlorpHttp < ::Faraday::Adapter
dependency do
require 'florp_http'
end
self.supports_parallel = true
def self.setup_parallel_manager(_options = nil)
FlorpParallelManager.new # NB: we will need to define this
end
def call(env)
# NB: you can call `in_parallel?` here to check if the current request
# is part of a parallel batch. Useful if you need to collect all requests
# into the ParallelManager before running them.
end
end
class FlorpParallelManager
# The execute method will be passed the same block as `in_parallel`,
# so you can either collect the requests or just wrap them into a wrapper,
# depending on how your adapter works.
def execute(&block)
run_async(&block)
end
end
```
### A note on the old, deprecated interface
Prior to the introduction of the `execute` method, the `ParallelManager` was expected to implement a `run` method
and the execution of the block was done by the Faraday connection BEFORE calling that method.
This approach made the `ParallelManager` implementation harder and forced you to keep state around.
The new `execute` implementation allows to avoid this shortfall and support different flows.
As of Faraday 2.0, `run` is still supported in case `execute` is not implemented by the `ParallelManager`,
but this method should be considered deprecated.
For reference, please see an example using `run` from [em-synchrony](https://github.com/lostisland/faraday-em_synchrony/blob/main/lib/faraday/adapter/em_synchrony.rb)
and its [ParallelManager implementation](https://github.com/lostisland/faraday-em_synchrony/blob/main/lib/faraday/adapter/em_synchrony/parallel_manager.rb).

View File

@ -0,0 +1,79 @@
# Adding support for streaming
Faraday supports streaming responses, which means that the response body is not loaded into memory all at once,
but instead it is read in chunks. This can be particularly useful when dealing with large responses.
Not all HTTP clients support this, so it is not required for adapters to support it.
However, if you do want to support it in your adapter, you can do so by leveraging helpers provided by the env object.
Let's see an example implementation first with some comments, and then we'll explain it in more detail:
```ruby
module Faraday
class Adapter
class FlorpHttp < Faraday::Adapter
def call(env)
super
if env.stream_response? # check if the user wants to stream the response
# start a streaming response.
# on_data is a block that will let users consume the response body
http_response = env.stream_response do |&on_data|
# perform the request using FlorpHttp
# the block will be called for each chunk of data
FlorpHttp.perform_request(...) do |chunk|
on_data.call(chunk)
end
end
# the body is already consumed by the block
# so it's good practice to set it to nil
http_response.body = nil
else
# perform the request normally, no streaming.
http_response = FlorpHttp.perform_request(...)
end
save_response(env, http_response.status, http_response.body, http_response.headers, http_response.reason_phrase)
end
end
end
end
```
## How it works
### `#stream_response?`
The first helper method we use is `#stream_response?`. This method is provided by the env object and it returns true
if the user wants to stream the response. This is controlled by the presence of an `on_data` callback in the request options.
### `#stream_response`
The second helper is `#stream_response`. This method is also provided by the env object and it takes a block.
The block will be called with a single argument, which is a callback that the user can use to consume the response body.
All your adapter needs to do, is to call this callback with each chunk of data that you receive from the server.
The `on_data` callback will internally call the callback provided by the user, so you don't need to worry about that.
It will also keep track of the number of bytes that have been read, and pass that information to the user's callback.
To see how this all works together, let's see an example of how a user would use this feature:
```ruby
# A buffer to store the streamed data
streamed = []
conn = Faraday.new('http://httpbingo.org')
conn.get('/stream/100') do |req|
# Set a callback which will receive tuples of chunk Strings,
# the sum of characters received so far, and the response environment.
# The latter will allow access to the response status, headers and reason, as well as the request info.
req.options.on_data = proc do |chunk, overall_received_bytes, env|
puts "Received #{overall_received_bytes} characters"
streamed << chunk
end
end
# Joins all response chunks together
streamed.join
```
For more details on the user experience, check the [Streaming Responses] page.
[Streaming Responses]: /advanced/streaming-responses.md

View File

@ -0,0 +1,60 @@
# Test your custom adapter
Faraday puts a lot of expectations on adapters, but it also provides you with useful tools to test your adapter
against those expectations. This guide will walk you through the process of testing your adapter.
## The adapter test suite
Faraday provides a test suite that you can use to test your adapter.
The test suite is located in the `spec/external_adapters/faraday_specs_setup.rb`.
All you need to do is to `require 'faraday_specs_setup'` in your adapter's `spec_helper.rb` file.
This will load the `an adapter` shared example group that you can use to test your adapter.
```ruby
require 'faraday_specs_setup'
RSpec.describe Faraday::Adapter::FlorpHttp do
it_behaves_like 'an adapter'
# You can then add any other test specific to this adapter here...
end
```
## Testing optional features
By default, `an adapter` will test your adapter against the required behaviour for an adapter.
However, there are some optional "features" that your adapter can implement, like parallel requests or streaming.
If your adapter implements any of those optional features, you can test it against those extra expectations
by calling the `features` method:
```ruby
RSpec.describe Faraday::Adapter::MyAdapter do
# Since not all adapters support all the features Faraday has to offer, you can use
# the `features` method to turn on only the ones you know you can support.
features :request_body_on_query_methods,
:compression,
:streaming
# Runs the tests provide by Faraday, according to the features specified above.
it_behaves_like 'an adapter'
# You can then add any other test specific to this adapter here...
end
```
### Available features
| Feature | Description |
|----------------------------------|----------------------------------------------------------------------------------------------------------|
| `:compression` | Tests that your adapter can handle `gzip` and `deflate` compressions. |
| `:local_socket_binding` | Tests that your adapter supports binding to a local socket via the `:bind` request option. |
| `:parallel` | Tests that your adapter supports parallel requests. See [Parallel requests][parallel] for more details. |
| `:reason_phrase_parse` | Tests that your adapter supports parsing the `reason_phrase` from the response. |
| `:request_body_on_query_methods` | Tests that your adapter supports sending a request body on `GET`, `HEAD`, `DELETE` and `TRACE` requests. |
| `:streaming` | Tests that your adapter supports streaming responses. See [Streaming][streaming] for more details. |
| `:trace_method` | Tests your adapter against the `TRACE` HTTP method. |
[streaming]: /adapters/custom/streaming.md
[parallel]: /adapters/custom/parallel-requests.md

View File

@ -1,9 +1,4 @@
---
layout: documentation
title: "Adapters"
permalink: /adapters/
order: 2
---
# Adapters
The Faraday Adapter interface determines how a Faraday request is turned into
a Faraday response object. Adapters are typically implemented with common Ruby
@ -32,11 +27,10 @@ conn = Faraday.new do |f|
end
```
{: .mt-60}
## Fantastic adapters and where to find them
With the only exception being the [Test Adapter][testing], which is for _test purposes only_,
adapters are distributed separately from Faraday.
Except for the default [Net::HTTP][net_http] adapter and the [Test Adapter][testing] adapter, which is for _test purposes only_,
adapters are distributed separately from Faraday and need to be manually installed.
They are usually available as gems, or bundled with HTTP clients.
While most adapters use a common Ruby HTTP client library, adapters can also
@ -45,13 +39,8 @@ have completely custom implementations.
If you're just getting started you can find a list of featured adapters in [Awesome Faraday][awesome].
Anyone can create a Faraday adapter and distribute it. If you're interested learning more, check how to [build your own][build_adapters]!
## Ad-hoc adapters customization
Faraday is intended to be a generic interface between your code and the adapter.
However, sometimes you need to access a feature specific to one of the adapters that is not covered in Faraday's interface.
When that happens, you can pass a block when specifying the adapter to customize it.
The block parameter will change based on the adapter you're using. See each adapter page for more details.
[testing]: ./testing
[testing]: /adapters/test-adapter.md
[net_http]: /adapters/net-http.md
[awesome]: https://github.com/lostisland/awesome-faraday/#adapters
[build_adapters]: ./write_your_adapter.md
[build_adapters]: /adapters/custom/index.md

12
docs/adapters/net-http.md Normal file
View File

@ -0,0 +1,12 @@
# Net::HTTP Adapter
Faraday's Net::HTTP adapter is the default adapter. It uses the `Net::HTTP`
library that ships with Ruby's standard library.
Unless you have a specific reason to use a different adapter, this is probably
the adapter you want to use.
With the release of Faraday 2.0, the Net::HTTP adapter has been moved into a [separate gem][faraday-net_http],
but it has also been added as a dependency of Faraday.
That means you can use it without having to install it or require it explicitly.
[faraday-net_http]: https://github.com/lostisland/faraday-net_http

View File

@ -1,11 +1,4 @@
---
layout: documentation
title: "Testing"
permalink: /adapters/testing
hide: true
top_name: Adapters
top_link: ./
---
# Test Adapter
The built-in Faraday Test adapter lets you define stubbed HTTP requests. This can
be used to mock out network services in an application's unit tests.
@ -120,5 +113,5 @@ Faraday.default_connection = nil
Working [RSpec] and [test/unit] examples for a fictional JSON API client are
available.
[RSpec]: https://github.com/lostisland/faraday/blob/master/examples/client_spec.rb
[test/unit]: https://github.com/lostisland/faraday/blob/master/examples/client_test.rb
[RSpec]: https://github.com/lostisland/faraday/blob/main/examples/client_spec.rb
[test/unit]: https://github.com/lostisland/faraday/blob/main/examples/client_test.rb

View File

@ -1,171 +0,0 @@
---
layout: documentation
title: "Write your own adapter"
permalink: /adapters/write_your_adapter
hide: true
order: 2
---
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:
```ruby
class FlorpHttp < ::Faraday::Adapter
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.
### Helpful method: `#build_connection`
Faraday abstracts all your backend's concrete stuff behind its user-facing API.
You take care of setting up the connection from the supplied parameters.
Example from the excon adapter: it gets an `Env` and reads its information
to instantiate an `Excon` object:
```ruby
class FlorpHttp < ::Faraday::Adapter
def build_connection(env)
opts = opts_from_env(env)
::Excon.new(env[:url].to_s, opts.merge(@connection_options))
end
end
```
The `env` contains stuff like:
- `env[:ssl]`
- `env[:request]`
There are helper methods to fetch timeouts: `#request_timeout(type, options)` 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.
So, use the information provided in `env` to instantiate your backend's connection class.
Return that instance. Now, Faraday knows how to create and reuse that connection.
### 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
```
### Nickname for your adapter: `.register_middleware`
You may register a nickname for your adapter. People can then refer to your adapter with that name.
You do that using `.register_middleware`, like this:
```ruby
class FlorpHttp < ::Faraday::Adapter
# ...
end
Faraday::Adapter.register_middleware(florp_http: FlorpHttp)
```
## Does your backend support parallel operation?
:warning: This is slightly more involved, and this section is not fully formed.
Vague example, excerpted from [the test suite about parallel requests](https://github.com/lostisland/faraday/blob/master/spec/support/shared_examples/request_method.rb#L179)
```ruby
response_1 = nil
response_2 = nil
conn.in_parallel do
response_1 = conn.get('/about')
response_2 = conn.get('/products')
end
puts response_1.status
puts response_2.status
```
First, in your class definition, you can tell Faraday that your backend supports parallel operation:
```ruby
class FlorpHttp < ::Faraday::Adapter
dependency do
require 'florp_http'
end
self.supports_parallel = true
end
```
Then, implement a method which returns a ParallelManager:
```ruby
class FlorpHttp < ::Faraday::Adapter
dependency do
require 'florp_http'
end
self.supports_parallel = true
def self.setup_parallel_manager(_options = nil)
FlorpParallelManager.new # NB: we will need to define this
end
end
class FlorpParallelManager
def add(request, method, *args, &block)
# Collect the requests
end
def run
# Process the requests
end
end
```
Compare to the finished example [em-synchrony](https://github.com/lostisland/faraday-em_synchrony/blob/main/lib/faraday/adapter/em_synchrony.rb)
and its [ParallelManager implementation](https://github.com/lostisland/faraday-em_synchrony/blob/main/lib/faraday/adapter/em_synchrony/parallel_manager.rb).

View File

@ -0,0 +1,58 @@
# Parallel Requests
Some adapters support running requests in parallel.
This can be achieved using the `#in_parallel` method on the connection object.
```ruby
# Install the Typhoeus adapter with `gem install faraday-typhoeus` first.
require 'faraday/typhoeus'
conn = Faraday.new('http://httpbingo.org') do |faraday|
faraday.adapter :typhoeus
end
now = Time.now
conn.in_parallel do
conn.get('/delay/3')
conn.get('/delay/3')
end
# This should take about 3 seconds, not 6.
puts "Time taken: #{Time.now - now}"
```
## A note on Async
You might have heard about [Async] and its native integration with Ruby 3.0.
The good news is that you can already use Async with Faraday (thanks to the [async-http-faraday] gem)
and this does not require the use of `#in_parallel` to run parallel requests.
Instead, you only need to wrap your Faraday code into an Async block:
```ruby
# Install the Async adapter with `gem install async-http-faraday` first.
require 'async/http/faraday'
conn = Faraday.new('http://httpbingo.org') do |faraday|
faraday.adapter :async_http
end
now = Time.now
# NOTE: This is not limited to a single connection anymore!
# You can run parallel requests spanning multiple connections.
Async do
Async { conn.get('/delay/3') }
Async { conn.get('/delay/3') }
end
# This should take about 3 seconds, not 6.
puts "Time taken: #{Time.now - now}"
```
The big advantage of using Async is that you can now run parallel requests *spanning multiple connections*,
whereas the `#in_parallel` method only works for requests that are made through the same connection.
[Async]: https://github.com/socketry/async
[async-http-faraday]: https://github.com/socketry/async-http-faraday

View File

@ -1,13 +1,4 @@
---
layout: documentation
title: "Streaming Responses"
permalink: /usage/streaming
hide: true
top_name: Usage
top_link: ./
prev_name: Customizing the Request
prev_link: ./customize
---
# Streaming Responses
Sometimes you might need to receive a streaming response.
You can do this with the `on_data` request option.
@ -21,7 +12,8 @@ This example implements such a callback:
# A buffer to store the streamed data
streamed = []
conn.get('/stream/10') do |req|
conn = Faraday.new('http://httpbingo.org')
conn.get('/stream/100') do |req|
# Set a callback which will receive tuples of chunk Strings,
# the sum of characters received so far, and the response environment.
# The latter will allow access to the response status, headers and reason, as well as the request info.
@ -41,4 +33,3 @@ Moreover, the `env` parameter was only recently added, which means some adapters
(i.e. only `chunk` and `overall_received_bytes` will be passed to your block).
[awesome]: https://github.com/lostisland/awesome-faraday/#adapters

View File

@ -1,7 +0,0 @@
---
---
@import "variables";
@import "type-theme";
@import "faraday";
@import "https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css"

View File

@ -1,52 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="1920px" height="1024px" viewBox="0 0 1920 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 55.1 (78136) - https://sketchapp.com -->
<title>Background and Stripes</title>
<desc>Created with Sketch.</desc>
<defs>
<linearGradient x1="0%" y1="50%" x2="113.817376%" y2="50%" id="linearGradient-1">
<stop stop-color="#F59D2A" offset="0%"></stop>
<stop stop-color="#F59A2C" offset="1%"></stop>
<stop stop-color="#EE6A4B" offset="21%"></stop>
<stop stop-color="#EA4D5F" offset="36%"></stop>
<stop stop-color="#E94266" offset="45%"></stop>
<stop stop-color="#D54F76" offset="54%"></stop>
<stop stop-color="#A0719F" offset="74%"></stop>
<stop stop-color="#52A3DB" offset="100%"></stop>
</linearGradient>
<path d="M1497.08184,1023 L1413.76949,1023 L1970,467.082708 L1970,550.348143 L1747.2718,772.950924 L1497.08184,1023 Z M1237.28591,1023 L1153.97357,1023 L1970,207.433075 L1970,290.698512 L1308.46073,951.865267 L1237.28591,1023 Z M977.1947,1023 L893.882349,1023 L1917.45873,-5.68434189e-13 L1970,-2.27373675e-13 L1970,30.7537554 L977.1947,1023 Z M716.514745,1023 L633.202393,1023 L1656.77878,-2.27373675e-13 L1740.09113,-2.01923225e-13 L716.514745,1023 Z M455.834791,1023 L372.522439,1023 L1396.09882,-2.27373675e-13 L1479.41117,-1.73066477e-13 L455.834791,1023 Z M198.100371,1023 L114.787781,1023 L1138.36416,-3.41060513e-13 L1221.67675,-1.44535795e-13 L198.100371,1023 Z" id="path-2"></path>
<filter x="-4.5%" y="-4.5%" width="109.0%" height="109.0%" filterUnits="objectBoundingBox" id="filter-4">
<feGaussianBlur stdDeviation="0" in="SourceGraphic"></feGaussianBlur>
</filter>
<filter x="-2.9%" y="-2.9%" width="105.8%" height="105.8%" filterUnits="objectBoundingBox" id="filter-5">
<feGaussianBlur stdDeviation="0" in="SourceGraphic"></feGaussianBlur>
</filter>
<filter x="-2.1%" y="-2.1%" width="104.3%" height="104.3%" filterUnits="objectBoundingBox" id="filter-6">
<feGaussianBlur stdDeviation="0" in="SourceGraphic"></feGaussianBlur>
</filter>
<filter x="-2.1%" y="-2.1%" width="104.0%" height="104.1%" filterUnits="objectBoundingBox" id="filter-7">
<feGaussianBlur stdDeviation="0" in="SourceGraphic"></feGaussianBlur>
</filter>
<filter x="-2.0%" y="-2.1%" width="104.0%" height="104.1%" filterUnits="objectBoundingBox" id="filter-8">
<feGaussianBlur stdDeviation="0" in="SourceGraphic"></feGaussianBlur>
</filter>
<filter x="-2.0%" y="-2.1%" width="104.0%" height="104.1%" filterUnits="objectBoundingBox" id="filter-9">
<feGaussianBlur stdDeviation="0" in="SourceGraphic"></feGaussianBlur>
</filter>
</defs>
<g id="Background-and-Stripes" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="Rectangle" fill="#EDEBEB" fill-rule="nonzero" x="0" y="0" width="1922" height="1023"></rect>
<mask id="mask-3" fill="white">
<use xlink:href="#path-2"></use>
</mask>
<use id="Combined-Shape" fill-opacity="0.5" fill="url(#linearGradient-1)" fill-rule="nonzero" xlink:href="#path-2"></use>
<g id="Line" transform="translate(148.000000, 0.000000)" stroke="#FFFFFF" stroke-linecap="square" stroke-width="30">
<path d="M1303.02503,1024 L1772,555.025033" filter="url(#filter-4)"></path>
<path d="M1045.02503,1024 L1772,297.025033" filter="url(#filter-5)"></path>
<path d="M786.025033,1024 L1772,38.0250327" filter="url(#filter-6)"></path>
<path d="M522.025037,1024 L1546.02504,0" filter="url(#filter-7)"></path>
<path d="M261.55292,1024 L1285.55292,0" filter="url(#filter-8)"></path>
<path d="M0.552920204,1024 L1024.55292,0" filter="url(#filter-9)"></path>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 876 B

View File

@ -1,49 +0,0 @@
function teamTile(member) {
console.log(member);
return '<div class="team-tile">' +
'<a href="' + member.author.html_url + '">' +
'<img src="' + member.author.avatar_url + '" />' +
'<p class="team-tile-login">' +
member.author.login +
'</p>' +
'</a>' +
'</div>';
}
function fetchTeam(json, team, div) {
let el = document.querySelector(div);
el.innerHTML = team.map(function (m) {
let index = json.findIndex(function(e) {
return e.author.login === m
});
return teamTile(json.splice(index, 1)[0]);
}).join('');
}
function fetchContributors(json) {
let el = document.querySelector('#contributors-list');
el.innerHTML = json.reverse().map(function (c) {
return '<span class="contributor"><a href="' + c.author.html_url + '">' + c.author.login + '</a></span>';
}).join('<b> &#183; </b>');
}
function hideLoader() {
let el = document.querySelector('#loader');
el.classList.add('hidden');
}
function showTeam() {
let el = document.querySelector('#team-content');
el.classList.remove('hidden');
}
fetch('https://api.github.com/repos/lostisland/faraday/stats/contributors')
.then(function (response) {
response.json().then(function (json) {
fetchTeam(json, ['technoweenie', 'iMacTia', 'olleolleolle'], '#active-maintainers-list');
fetchTeam(json, ['mislav', 'sferik'], '#historical-team-list');
fetchContributors(json);
hideLoader();
showTeam();
});
});

View File

@ -0,0 +1,48 @@
# Connection Options
When initializing a new Faraday connection with `Faraday.new`, you can pass a hash of options to customize the connection.
All these options are optional.
| Option | Type | Default | Description |
|---------------------|-------------------|-----------------|---------------------------------------------------------------------------------------------------------------|
| `:request` | Hash | nil | Hash of request options. Will be use to build [RequestOptions]. |
| `:proxy` | URI, String, Hash | nil | Proxy options, either as a URL or as a Hash of [ProxyOptions]. |
| `:ssl` | Hash | nil | Hash of SSL options. Will be use to build [SSLOptions]. |
| `:url` | URI, String | nil | URI or String base URL. This can also be passed as positional argument. |
| `:parallel_manager` | | nil | Default parallel manager to use. This is normally set by the adapter, but you have the option to override it. |
| `:params` | Hash | nil | URI query unencoded key/value pairs. |
| `:headers` | Hash | nil | Hash of unencoded HTTP header key/value pairs. |
| `:builder_class` | Class | RackBuilder | A custom class to use as the middleware stack builder. |
| `:builder` | Object | Rackbuilder.new | An instance of a custom class to use as the middleware stack builder. |
## Example
```ruby
options = {
request: {
open_timeout: 5,
timeout: 5
},
proxy: {
uri: 'https://proxy.com',
user: 'proxy_user',
password: 'proxy_password'
},
ssl: {
ca_file: '/path/to/ca_file',
ca_path: '/path/to/ca_path',
verify: true
},
url: 'https://example.com',
params: { foo: 'bar' },
headers: { 'X-Api-Key' => 'secret', 'X-Api-Version' => '2' }
}
conn = Faraday.new(options) do |faraday|
# ...
end
```
[RequestOptions]: /customization/request-options.md
[ProxyOptions]: /customization/proxy-options.md
[SSLOptions]: /customization/ssl-options.md

View File

@ -1,14 +1,9 @@
---
layout: documentation
title: "Customizing the Request"
permalink: /usage/customize
hide: true
top_name: Usage
top_link: ./
next_name: Streaming Responses
next_link: ./streaming
---
# Configuration
Faraday is highly configurable and allows you to customize the way requests are made.
This applies to both the connection and the request, but can also cover things like SSL and proxy settings.
Below are some examples of how to customize Faraday requests.
Configuration can be set up with the connection and/or adjusted per request.
As connection options:

View File

@ -0,0 +1,30 @@
# Proxy Options
Proxy options can be provided to the connection constructor or set on a per-request basis via [RequestOptions](/customization/request-options.md).
All these options are optional.
| Option | Type | Default | Description |
|-------------|-------------|---------|-----------------|
| `:uri` | URI, String | nil | Proxy URL. |
| `:user` | String | nil | Proxy user. |
| `:password` | String | nil | Proxy password. |
## Example
```ruby
# Proxy options can be passed to the connection constructor and will be applied to all requests.
proxy_options = {
uri: 'http://proxy.example.com:8080',
user: 'username',
password: 'password'
}
conn = Faraday.new(proxy: proxy_options) do |faraday|
# ...
end
# You can then override them on a per-request basis.
conn.get('/foo') do |req|
req.options.proxy.update(uri: 'http://proxy2.example.com:8080')
end
```

View File

@ -0,0 +1,39 @@
# Request Options
Request options can be provided to the connection constructor or set on a per-request basis.
All these options are optional.
| Option | Type | Default | Description |
|-------------------|-------------------|----------------------------------------------------------------|-------------------------------------------------------------------------|
| `:params_encoder` | Class | `Faraday::Utils.nested_params_encoder` (`NestedParamsEncoder`) | A custom class to use as the params encoder. |
| `:proxy` | URI, String, Hash | nil | Proxy options, either as a URL or as a Hash of [ProxyOptions]. |
| `:bind` | Hash | nil | Hash of bind options. Requires the `:host` and `:port` keys. |
| `:timeout` | Integer, Float | nil (adapter default) | The max number of seconds to wait for the request to complete. |
| `:open_timeout` | Integer, Float | nil (adapter default) | The max number of seconds to wait for the connection to be established. |
| `:read_timeout` | Integer, Float | nil (adapter default) | The max number of seconds to wait for one block to be read. |
| `:write_timeout` | Integer, Float | nil (adapter default) | The max number of seconds to wait for one block to be written. |
| `:boundary` | String | nil | The boundary string for multipart requests. |
| `:context` | Hash | nil | Arbitrary data that you can pass to your request. |
| `:on_data` | Proc | nil | A callback that will be called when data is received. See [Streaming] |
## Example
```ruby
# Request options can be passed to the connection constructor and will be applied to all requests.
request_options = {
params_encoder: Faraday::FlatParamsEncoder,
timeout: 5
}
conn = Faraday.new(request: request_options) do |faraday|
# ...
end
# You can then override them on a per-request basis.
conn.get('/foo') do |req|
req.options.timeout = 10
end
```
[ProxyOptions]: /customization/proxy-options.md
[SSLOptions]: /advanced/streaming-responses.md

View File

@ -0,0 +1,35 @@
# SSL Options
Faraday supports a number of SSL options, which can be provided while initializing the connection.
| Option | Type | Default | Description |
|--------------------|----------------------------------------|---------|------------------------------------------------------------------------------------------------------------------------------------|
| `:verify` | Boolean | true | Verify SSL certificate. Defaults to `true`. |
| `:verify_hostname` | Boolean | true | Verify SSL certificate hostname. Defaults to `true`. |
| `:hostname` | String | nil | Server hostname for SNI (see [SSL docs](https://ruby-doc.org/3.2.2/exts/openssl/OpenSSL/SSL/SSLSocket.html#method-i-hostname-3D)). |
| `:ca_file` | String | nil | Path to a CA file in PEM format. |
| `:ca_path` | String | nil | Path to a CA directory. |
| `:verify_mode` | Integer | nil | Any `OpenSSL::SSL::` constant (see [SSL docs](https://ruby-doc.org/3.2.2/exts/openssl/OpenSSL/SSL.html)). |
| `:cert_store` | OpenSSL::X509::Store | nil | OpenSSL certificate store. |
| `:client_cert` | OpenSSL::X509::Certificate | nil | Client certificate. |
| `:client_key` | OpenSSL::PKey::RSA, OpenSSL::PKey::DSA | nil | Client private key. |
| `:certificate` | OpenSSL::X509::Certificate | nil | Certificate (Excon only). |
| `:private_key` | OpenSSL::PKey::RSA | nil | Private key (Excon only). |
| `:verify_depth` | Integer | nil | Maximum depth for the certificate chain verification. |
| `:version` | Integer | nil | SSL version (see [SSL docs](https://ruby-doc.org/3.2.2/exts/openssl/OpenSSL/SSL/SSLContext.html#method-i-ssl_version-3D)). |
| `:min_version` | Integer | nil | Minimum SSL version (see [SSL docs](https://ruby-doc.org/3.2.2/exts/openssl/OpenSSL/SSL/SSLContext.html#method-i-min_version-3D)). |
| `:max_version` | Integer | nil | Maximum SSL version (see [SSL docs](https://ruby-doc.org/3.2.2/exts/openssl/OpenSSL/SSL/SSLContext.html#method-i-max_version-3D)). |
| `:ciphers` | String | nil | Ciphers supported (see [SSL docs](https://ruby-doc.org/3.2.2/exts/openssl/OpenSSL/SSL/SSLContext.html#method-i-ciphers-3D)). |
## Example
```ruby
ssl_options = {
ca_file: '/path/to/ca_file',
min_version: :TLS1_2
}
conn = Faraday.new(ssl: options) do |faraday|
# ...
end
```

View File

@ -0,0 +1,51 @@
# The Env Object
Inspired by Rack, Faraday uses an `env` object to pass data between middleware.
This object is initialized at the beginning of the request and passed down the middleware stack.
The adapter is then responsible to run the HTTP request and set the `response` property on the `env` object,
which is then passed back up the middleware stack.
You can read more about how the `env` object is used in the [Middleware - How it works](/middleware/index?id=how-it-works) section.
Because of its nature, the `env` object is a complex structure that holds a lot of information and can
therefore be a bit intimidating at first. This page will try to explain the different properties of the `env` object.
## Properties
Please also note that these properties are not all available at the same time: while configuration
and request properties are available at the beginning of the request, response properties are only
available after the request has been performed (i.e. in the `on_complete` callback of middleware).
| Property | Type | Request | Response | Description |
|---------------------|----------------------------|:------------------:|:------------------:|-----------------------------|
| `:method` | `Symbol` | :heavy_check_mark: | :heavy_check_mark: | The HTTP method to use. |
| `:request_body` | `String` | :heavy_check_mark: | :heavy_check_mark: | The request body. |
| `:url` | `URI` | :heavy_check_mark: | :heavy_check_mark: | The request URL. |
| `:request` | `Faraday::RequestOptions` | :heavy_check_mark: | :heavy_check_mark: | The request options. |
| `:request_headers` | `Faraday::Utils::Headers` | :heavy_check_mark: | :heavy_check_mark: | The request headers. |
| `:ssl` | `Faraday::SSLOptions` | :heavy_check_mark: | :heavy_check_mark: | The SSL options. |
| `:parallel_manager` | `Faraday::ParallelManager` | :heavy_check_mark: | :heavy_check_mark: | The parallel manager. |
| `:params` | `Hash` | :heavy_check_mark: | :heavy_check_mark: | The request params. |
| `:response` | `Faraday::Response` | :x: | :heavy_check_mark: | The response. |
| `:response_headers` | `Faraday::Utils::Headers` | :x: | :heavy_check_mark: | The response headers. |
| `:status` | `Integer` | :x: | :heavy_check_mark: | The response status code. |
| `:reason_phrase` | `String` | :x: | :heavy_check_mark: | The response reason phrase. |
| `:response_body` | `String` | :x: | :heavy_check_mark: | The response body. |
## Helpers
The `env` object also provides some helper methods to make it easier to work with the properties.
| Method | Description |
|-------------------------|--------------------------------------------------------------------------------------------------|
| `#body`/`#current_body` | Returns the request or response body, based on the presence of `#status`. |
| `#success?` | Returns `true` if the response status is in the 2xx range. |
| `#needs_body?` | Returns `true` if there's no body yet, and the method is in the set of `Env::MethodsWithBodies`. |
| `#clear_body` | Clears the body, if it's present. That includes resetting the `Content-Length` header. |
| `#parse_body?` | Returns `true` unless the status indicates otherwise (e.g. 204, 304). |
| `#parallel?` | Returns `true` if a parallel manager is available. |
| `#stream_response?` | Returns `true` if the `on_data` streaming callback has been provided. |
| `#stream_response` | Helper method to implement streaming in adapters. See [Support streaming in your adapter]. |
[Support streaming in your adapter]: /adapters/custom/streaming.md

View File

@ -0,0 +1,17 @@
# Dealing with Errors
As an abstraction layer between the user and the underlying HTTP library,
it's important that Faraday provides a consistent interface for dealing with errors.
This is especially important when dealing with multiple adapters, as each adapter may raise different errors.
Below is a list of errors that Faraday may raise, and that you should be prepared to handle.
| Error | Description |
|-----------------------------|--------------------------------------------------------------------------------|
| `Faraday::Error` | Base class for all Faraday errors, also used for generic or unexpected errors. |
| `Faraday::ConnectionFailed` | Raised when the connection to the remote server failed. |
| `Faraday::TimeoutError` | Raised when the connection to the remote server timed out. |
| `Faraday::SSLError` | Raised when the connection to the remote server failed due to an SSL error. |
If you add the `raise_error` middleware, Faraday will also raise additional errors for 4xx and 5xx responses.
You can find the full list of errors in the [raise_error middleware](/middleware/included/raising-errors) page.

View File

@ -0,0 +1,266 @@
# Quick Start
## Installation
Add this line to your applications `Gemfile`:
```ruby
gem 'faraday'
```
And then execute:
```bash
$ bundle
```
Or install it yourself as:
```bash
$ gem install faraday
```
## Usage
### Quick requests
Let's fetch the home page for the wonderful [httpbingo.org](https://httpbingo.org) service.
You can make a simple `GET` request using `Faraday.get`:
```ruby
response = Faraday.get('http://httpbingo.org')
```
This returns a `Faraday::Response` object with the response status, headers, and body.
```ruby
response.status
# => 200
response.headers
# => {"server"=>"Fly/c375678 (2021-04-23)", "content-type"=> ...
response.body
# => "<!DOCTYPE html><html> ...
```
### Faraday Connection
The recommended way to use Faraday, especially when integrating to 3rd party services and APIs, is to create
a `Faraday::Connection`. The connection initializer allows you to set:
- default request headers & query parameters
- network settings like proxy or timeout
- common URL base path
- Faraday adapter & middleware (see below)
Create a `Faraday::Connection` by calling `Faraday.new`. You can then call each HTTP verb
(`get`, `post`, ...) on your `Faraday::Connection` to perform a request:
```ruby
conn = Faraday.new(
url: 'http://httpbingo.org',
params: {param: '1'},
headers: {'Content-Type' => 'application/json'}
)
response = conn.post('/post') do |req|
req.params['limit'] = 100
req.body = {query: 'chunky bacon'}.to_json
end
# => POST http://httpbingo.org/post?param=1&limit=100
```
### GET, HEAD, DELETE, TRACE
Faraday supports the following HTTP verbs that typically don't include a request body:
- `get(url, params = nil, headers = nil)`
- `head(url, params = nil, headers = nil)`
- `delete(url, params = nil, headers = nil)`
- `trace(url, params = nil, headers = nil)`
You can specify URI query parameters and HTTP headers when making a request.
```ruby
response = conn.get('get', { boom: 'zap' }, { 'User-Agent' => 'myapp' })
# => GET http://httpbingo.org/get?boom=zap
```
### POST, PUT, PATCH
Faraday also supports HTTP verbs with bodies. Instead of query parameters, these
accept a request body:
- `post(url, body = nil, headers = nil)`
- `put(url, body = nil, headers = nil)`
- `patch(url, body = nil, headers = nil)`
```ruby
# POST 'application/x-www-form-urlencoded' content
response = conn.post('post', 'boom=zap')
# POST JSON content
response = conn.post('post', '{"boom": "zap"}',
"Content-Type" => "application/json")
```
#### Posting Forms
Faraday will automatically convert key/value hashes into proper form bodies
thanks to the `url_encoded` middleware included in the default connection.
```ruby
# POST 'application/x-www-form-urlencoded' content
response = conn.post('post', boom: 'zap')
# => POST 'boom=zap' to http://httpbingo.org/post
```
### Detailed HTTP Requests
Faraday supports a longer style for making requests. This is handy if you need
to change many of the defaults, or if the details of the HTTP request change
according to method arguments. Each of the HTTP verb helpers can yield a
`Faraday::Request` that can be modified before being sent.
This example shows a hypothetical search endpoint that accepts a JSON request
body as the actual search query.
```ruby
response = conn.post('post') do |req|
req.params['limit'] = 100
req.headers['Content-Type'] = 'application/json'
req.body = {query: 'chunky bacon'}.to_json
end
# => POST http://httpbingo.org/post?limit=100
```
### Using Middleware
Configuring your connection or request with predefined headers and parameters is a good start,
but the real power of Faraday comes from its middleware stack.
Middleware are classes that allow you to hook into the request/response cycle and modify the request.
They can help you with things like:
* adding authentication headers
* parsing JSON responses
* logging requests and responses
* raise errors on 4xx and 5xx responses
* and much more!
For example, let's say you want to call an API that:
* requires an authentication token in the `Authorization` header
* expects JSON request bodies
* returns JSON responses
and on top of that, you want to automatically raise errors on 4xx and 5xx responses,
as well as log all requests and responses.
You can easily achieve all of the above by adding the necessary middleware to your connection:
```ruby
conn = Faraday.new(url: 'http://httpbingo.org') do |builder|
# Calls MyAuthStorage.get_auth_token on each request to get the auth token
# and sets it in the Authorization header with Bearer scheme.
builder.request :authorization, 'Bearer', -> { MyAuthStorage.get_auth_token }
# Sets the Content-Type header to application/json on each request.
# Also, if the request body is a Hash, it will automatically be encoded as JSON.
builder.request :json
# Parses JSON response bodies.
# If the response body is not valid JSON, it will raise a Faraday::ParsingError.
builder.response :json
# Raises an error on 4xx and 5xx responses.
builder.response :raise_error
# Logs requests and responses.
# By default, it only logs the request method and URL, and the request/response headers.
builder.response :logger
end
# A simple example implementation for MyAuthStorage
class MyAuthStorage
def self.get_auth_token
rand(36 ** 8).to_s(36)
end
end
```
The connection can now be used to make requests.
```ruby
begin
response = conn.post('post', { payload: 'this ruby hash will become JSON' })
rescue Faraday::Error => e
# You can handle errors here (4xx/5xx responses, timeouts, etc.)
puts e.response[:status]
puts e.response[:body]
end
# At this point, you can assume the request was successful
puts response.body
# I, [2023-06-30T14:27:11.776511 #35368] INFO -- request: POST http://httpbingo.org/post
# I, [2023-06-30T14:27:11.776646 #35368] INFO -- request: User-Agent: "Faraday v2.7.8"
# Authorization: "Bearer wibzjgyh"
# Content-Type: "application/json"
# I, [2023-06-30T14:27:12.063897 #35368] INFO -- response: Status 200
# I, [2023-06-30T14:27:12.064260 #35368] INFO -- response: access-control-allow-credentials: "true"
# access-control-allow-origin: "*"
# content-type: "application/json; encoding=utf-8"
# date: "Fri, 30 Jun 2023 13:27:12 GMT"
# content-encoding: "gzip"
# transfer-encoding: "chunked"
# server: "Fly/a0b91024 (2023-06-13)"
# via: "1.1 fly.io"
# fly-request-id: "01H467RYRHA0YK4TQSZ7HS8ZFT-lhr"
# cf-team: "19ae1592b8000003bbaedcf400000001"
```
Faraday ships with a number of useful middleware, and you can also write your own.
To learn more about middleware, please check the [Middleware] section.
### Swapping Adapters
Faraday does not make HTTP requests itself, but instead relies on a Faraday adapter to do so.
By default, it will use the `Net::HTTP` adapter, which is part of the Ruby standard library.
Although `Net::HTTP` is the only adapter that ships with Faraday, there are [many other adapters
available as separate gems](https://github.com/lostisland/awesome-faraday#adapters).
Once you have installed an adapter, you can use it by passing the `adapter` option to `Faraday.new`:
```ruby
conn = Faraday.new(url: 'http://httpbingo.org') do |builder|
builder.adapter :async_http
end
```
To learn more about adapters, including how to write your own, please check the [Adapters] section.
### Default Connection, Default Adapter
Remember how we said that Faraday will automatically encode key/value hash
bodies into form bodies? Internally, the top level shortcut methods
`Faraday.get`, `post`, etc. use a simple default `Faraday::Connection`. The only
middleware used for the default connection is `:url_encoded`, which encodes
those form hashes, and the `default_adapter`.
You can change the default adapter or connection. Be careful because they're set globally.
```ruby
Faraday.default_adapter = :async_http # defaults to :net_http
# The default connection has only `:url_encoded` middleware.
# Note that if you create your own connection with middleware, it won't encode
# form bodies unless you too include the :url_encoded middleware!
Faraday.default_connection = Faraday.new do |conn|
conn.request :url_encoded
conn.response :logger
conn.adapter Faraday.default_adapter
end
```
[Adapters]: /adapters/index.md
[Middleware]: /middleware/index.md

View File

@ -0,0 +1,225 @@
# Migrating from `rest-client` to `Faraday`
The `rest-client` gem is in maintenance mode, and developers are encouraged to migrate to actively maintained alternatives like [`faraday`](https://github.com/lostisland/faraday). This guide highlights common usage patterns in `rest-client` and how to migrate them to `faraday`.
---
## Quick Comparison
| Task | rest-client example | faraday example |
| ----------------- | -------------------------------------------------------- | -------------------------------------------------------------------------- |
| Simple GET | `RestClient.get("https://httpbingo.org/get")` | `Faraday.get("https://httpbingo.org/get")` |
| GET with params | `RestClient.get(url, params: { id: 1 })` | `Faraday.get(url, { id: 1 })` |
| POST form data | `RestClient.post(url, { a: 1 })` | `Faraday.post(url, { a: 1 })` |
| POST JSON | `RestClient.post(url, obj.to_json, content_type: :json)` | `Faraday.post(url, obj.to_json, { 'Content-Type' => 'application/json' })` |
| Custom headers | `RestClient.get(url, { Authorization: 'Bearer token' })` | `Faraday.get(url, nil, { 'Authorization' => 'Bearer token' })` |
| Get response body | `response.body` | `response.body` |
| Get status code | `response.code` | `response.status` |
| Get headers | `response.headers` (returns `Hash<Symbol, String>`) | `response.headers` (returns `Hash<String, String>`) |
---
## Installation
In your `Gemfile`, replace `rest-client` with:
```ruby
gem "faraday"
```
Then run:
```sh
bundle install
```
---
## Basic HTTP Requests
### GET request
**rest-client:**
```ruby
RestClient.get("https://httpbingo.org/get")
```
**faraday:**
```ruby
Faraday.get("https://httpbingo.org/get")
```
---
### GET with Params
**rest-client:**
```ruby
RestClient.get("https://httpbingo.org/get", params: { id: 1, foo: "bar" })
```
**faraday:**
```ruby
Faraday.get("https://httpbingo.org/get", { id: 1, foo: "bar" })
```
---
### POST Requests
**rest-client:**
```ruby
RestClient.post("https://httpbingo.org/post", { foo: "bar" })
```
**faraday:**
```ruby
Faraday.post("https://httpbingo.org/post", { foo: "bar" })
```
---
### Sending JSON
**rest-client:**
```ruby
RestClient.post("https://httpbingo.org/post", { foo: "bar" }.to_json, content_type: :json)
```
**faraday (manual):**
```ruby
Faraday.post("https://httpbingo.org/post", { foo: "bar" }.to_json, { 'Content-Type' => 'application/json' })
```
**faraday (with middleware):**
```ruby
conn = Faraday.new(url: "https://httpbingo.org") do |f|
f.request :json # encode request body as JSON and set Content-Type
f.response :json # parse response body as JSON
end
conn.post("/post", { foo: "bar" })
```
---
## Handling Responses
**rest-client:**
```ruby
response = RestClient.get("https://httpbingo.org/headers")
response.code # => 200
response.body # => "..."
response.headers # => { content_type: "application/json", ... }
```
**faraday:**
> notice headers Hash keys are stringified, not symbolized like in rest-client
```ruby
response = Faraday.get("https://httpbingo.org/headers")
response.status # => 200
response.body # => "..."
response.headers # => { "content-type" => "application/json", ... }
```
---
## Error Handling
**rest-client:**
```ruby
begin
RestClient.get("https://httpbingo.org/status/404")
rescue RestClient::NotFound => e
puts e.response.code # 404
end
```
**faraday:**
> By default, Faraday does **not** raise exceptions for HTTP errors (like 404 or 500); it simply returns the response. If you want exceptions to be raised on HTTP error responses, include the `:raise_error` middleware.
>
> With `:raise_error`, Faraday will raise `Faraday::ResourceNotFound` for 404s and other exceptions for other 4xx/5xx responses.
>
> See also:
>
> * [Dealing with Errors](getting-started/errors.md)
> * [Raising Errors](middleware/included/raising-errors.md)
```ruby
conn = Faraday.new(url: "https://httpbingo.org") do |f|
f.response :raise_error
end
begin
conn.get("/status/404")
rescue Faraday::ResourceNotFound => e
puts e.response[:status] # 404
end
```
---
## Advanced Request Configuration
**rest-client:**
```ruby
RestClient::Request.execute(method: :get, url: "https://httpbingo.org/get", timeout: 10)
```
**faraday:**
```ruby
conn = Faraday.new(url: "https://httpbingo.org", request: { timeout: 10 })
conn.get("/get")
```
---
## Headers
**rest-client:**
```ruby
RestClient.get("https://httpbingo.org/headers", { Authorization: "Bearer token" })
```
**faraday:**
> Notice headers Hash expects stringified keys.
```ruby
Faraday.get("https://httpbingo.org/headers", nil, { "Authorization" => "Bearer token" })
```
---
## Redirects
**rest-client:**
Automatically follows GET/HEAD redirects by default.
**faraday:**
Use the `follow_redirects` middleware (not included by default):
```ruby
require "faraday/follow_redirects"
conn = Faraday.new(url: "https://httpbingo.org") do |f|
f.response :follow_redirects
end
```

121
docs/index.html Normal file
View File

@ -0,0 +1,121 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" type="image/x-icon" href="_media/favicon.png">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<!-- HTML Meta Tags -->
<title>Faraday Docs</title>
<meta name="description" content="Faraday is an HTTP client library abstraction layer that provides a common interface over many adapters (such as Net::HTTP) and embraces the concept of Rack middleware when processing the request/response cycle.">
<!-- OpenGraph Meta Tags -->
<meta property="og:url" content="https://lostisland.github.io/faraday/#/">
<meta property="og:type" content="website">
<meta property="og:title" content="Faraday Docs">
<meta property="og:description" content="Faraday is an HTTP client library abstraction layer that provides a common interface over many adapters (such as Net::HTTP) and embraces the concept of Rack middleware when processing the request/response cycle.">
<meta property="og:image" content="https://lostisland.github.io/faraday/_media/repo-card.png">
<!-- Twitter Meta Tags -->
<meta name="twitter:card" content="summary_large_image">
<meta property="twitter:domain" content="lostisland.github.io">
<meta property="twitter:url" content="https://lostisland.github.io/faraday/#/">
<meta name="twitter:title" content="Faraday Docs">
<meta name="twitter:description" content="Faraday is an HTTP client library abstraction layer that provides a common interface over many adapters (such as Net::HTTP) and embraces the concept of Rack middleware when processing the request/response cycle.">
<meta name="twitter:image" content="https://lostisland.github.io/faraday/_media/repo-card.png">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
<link
rel="stylesheet"
href="//cdn.jsdelivr.net/npm/docsify-darklight-theme@latest/dist/style.min.css"
title="docsify-darklight-theme"
type="text/css"
/>
<link
rel="stylesheet"
href="//cdn.jsdelivr.net/npm/prism-themes/themes/prism-material-light.min.css"
id="prism-theme"
type="text/css"
/>
</head>
<body>
<div id="app"></div>
<!-- Docsify plugins -->
<script src="//cdn.jsdelivr.net/npm/docsify-edit-on-github"></script>
<!-- Docsify config -->
<script>
window.$docsify = {
name: 'Faraday',
repo: 'lostisland/faraday',
logo: '_media/home-logo.svg',
homepage: 'index.md',
search: true,
loadSidebar: true,
subMaxLevel: 4,
auto2top: true,
darklightTheme: {
dark: {
accent: '#EE4266',
prismTheme: 'prism-material-dark'
},
light: {
accent: '#EE4266',
prismTheme: 'prism-material-light'
}
},
plugins: [
EditOnGithubPlugin.create(
'https://github.com/lostisland/faraday/blob/main/docs/',
null,
'Edit this page on GitHub'
),
function pageFooter(hook, _vm) {
var footer = [
'<hr/>',
'<footer>',
'<span>© 2009 - 2023, the Faraday Team. </span>',
'<span>Website and branding design by <a href="https://elelopic.design" target="_blank" rel="noopener">Elena Lo Piccolo</a>.</span>',
'</footer>',
].join('');
hook.afterEach(function (html) {
return html + footer;
});
},
function prismThemeSwitcher(hook, _vm) {
// Switch Prism theme based on docsify-darklight-theme setting
let lightTheme = '//cdn.jsdelivr.net/npm/prism-themes/themes/prism-one-light.min.css';
let darkTheme = '//cdn.jsdelivr.net/npm/prism-themes/themes/prism-one-dark.min.css';
let switchTheme = () => {
console.log('Theme changed');
let theme = localStorage.getItem('DARK_LIGHT_THEME')
let link = document.getElementById('prism-theme');
link.setAttribute('href', theme === 'dark' ? darkTheme : lightTheme);
}
hook.ready(() => {
document.getElementById('main').addEventListener('click', switchTheme);
switchTheme();
});
},
]
}
</script>
<!-- Docsify v4 -->
<script src="//cdn.jsdelivr.net/npm/docsify@4"></script>
<!-- Docsify Darklight Theme -->
<script
src="//cdn.jsdelivr.net/npm/docsify-darklight-theme@latest/dist/index.min.js"
type="text/javascript">
</script>
<!-- Prism Ruby highlight -->
<script src="//cdn.jsdelivr.net/npm/prismjs@v1.x/components/prism-ruby.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/prismjs@v1.x/plugins/autoloader/prism-autoloader.min.js"></script>
<!-- Other Plugins -->
<script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/search.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify-copy-code/dist/docsify-copy-code.min.js"></script>
</body>
</html>

View File

@ -1,47 +1,28 @@
---
# You don't need to edit this file, it's empty on purpose.
# Edit theme's home layout instead if you wanna make some changes
# See: https://jekyllrb.com/docs/themes/#overriding-theme-defaults
layout: page
title: Homepage
feature-title: <img src="assets/img/home-logo.svg">
feature-img: "assets/img/featured-bg.svg"
hide: true
---
# ![Faraday](_media/home-logo.svg)
Faraday is an HTTP client library that provides a common interface over many adapters (such as Net::HTTP)
and embraces the concept of Rack middleware when processing the request/response cycle.
Faraday is an HTTP client library abstraction layer that provides a common interface over many
adapters (such as Net::HTTP) and embraces the concept of Rack middleware when processing the request/response cycle.
{: .text-center}
[<i class="fab fa-fw fa-github"> </i> Fork on GitHub][github]{: .btn}
[<i class="fab fa-fw fa-gitter"> </i> Chat with us][gitter]{: .btn}
## Why use Faraday?
{: .mt-60}
## Installation
Faraday gives you the power of Rack middleware for manipulating HTTP requests and responses,
making it easier to build sophisticated API clients or web service libraries that abstract away
the details of how HTTP requests are made.
Add this line to your application's Gemfile:
Faraday comes with a lot of features out of the box, such as:
* Support for multiple adapters (Net::HTTP, Typhoeus, Patron, Excon, HTTPClient, and more)
* Persistent connections (keep-alive)
* Parallel requests
* Automatic response parsing (JSON, XML, YAML)
* Customization of the request/response cycle with middleware
* Support for streaming responses
* Support for uploading files
* And much more!
```ruby
gem 'faraday'
```
## Who uses Faraday?
And then execute:
```bash
$ bundle
```
Or install it yourself as:
```bash
$ gem install faraday
```
{: .mt-60}
{: .text-center}
[<i class="fas fa-fw fa-book-open"> </i> Read the docs][usage]{: .btn}
[github]: https://github.com/lostisland/faraday
[gitter]: https://gitter.im/lostisland/faraday
[usage]: ./usage
Faraday is used by many popular Ruby libraries, such as:
* [Signet](https://github.com/googleapis/signet)
* [Octokit](https://github.com/octokit/octokit.rb)
* [Oauth2](https://bestgems.org/gems/oauth2)
* [Elasticsearch](https://github.com/elastic/elasticsearch-ruby)

View File

@ -0,0 +1,84 @@
# Writing custom middleware
!> A template for writing your own middleware is available in the [faraday-middleware-template](https://github.com/lostisland/faraday-middleware-template) repository.
The recommended way to write middleware is to make your middleware subclass `Faraday::Middleware`.
`Faraday::Middleware` simply expects your subclass to implement two methods: `#on_request(env)` and `#on_complete(env)`.
* `#on_request` is called when the request is being built and is given the `env` representing the request.
* `#on_complete` is called after the response has been received (that's right, it already supports parallel mode!) and receives the `env` of the response.
For both `env` parameters, please refer to the [Env Object](getting-started/env-object.md) page.
```ruby
class MyMiddleware < Faraday::Middleware
def on_request(env)
# do something with the request
# env[:request_headers].merge!(...)
end
def on_complete(env)
# do something with the response
# env[:response_headers].merge!(...)
end
end
```
## Having more control
For the majority of middleware, it's not necessary to override the `#call` method. You can instead use `#on_request` and `#on_complete`.
However, in some cases you may need to wrap the call in a block, or work around it somehow (think of a begin-rescue, for example).
When that happens, then you can override `#call`. When you do so, remember to call either `app.call(env)` or `super` to avoid breaking the middleware stack call!
```ruby
def call(request_env)
# do something with the request
# request_env[:request_headers].merge!(...)
@app.call(request_env).on_complete do |response_env|
# do something with the response
# response_env[:response_headers].merge!(...)
end
end
```
It's important to do all processing of the response only in the `#on_complete`
block. This enables middleware to work in parallel mode where requests are
asynchronous.
The `request_env` and `response_env` are both [Env Objects](getting-started/env-object.md) but note the amount of
information available in each one will differ based on the request/response lifecycle.
## Accepting configuration options
`Faraday::Middleware` also allows your middleware to accept configuration options.
These are passed in when the middleware is added to the stack, and can be accessed via the `options` getter.
```ruby
class MyMiddleware < Faraday::Middleware
def on_request(_env)
# access the foo option
puts options[:foo]
end
end
conn = Faraday.new(url: 'http://httpbingo.org') do |faraday|
faraday.use MyMiddleware, foo: 'bar'
end
```
## Registering your middleware
Users can use your middleware using the class directly, but you can also register it with Faraday so that
it can be used with the `use`, `request` or `response` methods as well.
```ruby
# Register for `use`
Faraday::Middleware.register_middleware(my_middleware: MyMiddleware)
# Register for `request`
Faraday::Request.register_middleware(my_middleware: MyMiddleware)
# Register for `response`
Faraday::Response.register_middleware(my_middleware: MyMiddleware)
```

View File

@ -1,63 +0,0 @@
---
layout: documentation
title: "Writing Middleware"
permalink: /middleware/custom
hide: true
top_name: Middleware
top_link: ./
prev_name: Available Middleware
prev_link: ./list
---
Middleware are classes that implement a `#call` instance method. They hook into the request/response cycle.
```ruby
def call(request_env)
# do something with the request
# request_env[:request_headers].merge!(...)
@app.call(request_env).on_complete do |response_env|
# do something with the response
# response_env[:response_headers].merge!(...)
end
end
```
It's important to do all processing of the response only in the `#on_complete`
block. This enables middleware to work in parallel mode where requests are
asynchronous.
The `env` is a hash with symbol keys that contains info about the request and,
later, response. Some keys are:
```
# request phase
:method - :get, :post, ...
:url - URI for the current request; also contains GET parameters
:body - POST parameters for :post/:put requests
:request_headers
# response phase
:status - HTTP response status code, such as 200
:body - the response body
:response_headers
```
### Faraday::Middleware
There's an easier way to write middleware, and it's also the recommended one: make your middleware subclass `Faraday::Middleware`.
`Faraday::Middleware` already implements the `#call` method for you and looks for two methods in your subclass: `#on_request(env)` and `#on_complete(env)`.
`#on_request` is called when the request is being built and is given the `env` representing the request.
`#on_complete` is called after the response has been received (that's right, it already supports parallel mode!) and receives the `env` of the response.
### Do I need to override `#call`?
For the majority of middleware, it's not necessary to override the `#call` method. You can instead use `#on_request` and `#on_complete`.
However, in some cases you may need to wrap the call in a block, or work around it somehow (think of a begin-rescue, for example).
When that happens, then you can override `#call`. When you do so, remember to call either `app.call(env)` or `super` to avoid breaking the middleware stack call!
### Can I find a middleware template somewhere?
Yes, you can! Look at the [`faraday-middleware-template`](https://github.com/lostisland/faraday-middleware-template) repository.

View File

@ -1,13 +1,4 @@
---
layout: documentation
title: "Authentication Middleware"
permalink: /middleware/authentication
hide: true
next_name: UrlEncoded Middleware
next_link: ./url-encoded
top_name: Back to Middleware
top_link: ./list
---
# Authentication
The `Faraday::Request::Authorization` middleware allows you to automatically add an `Authorization` header
to your requests. It also features a handy helper to manage Basic authentication.
@ -30,7 +21,7 @@ Faraday.new(...) do |conn|
end
```
If the proc takes an argument, it will receive the forwarded `env`
If the proc takes an argument, it will receive the forwarded `env` (see [The Env Object](getting-started/env-object.md)):
```ruby
Faraday.new(...) do |conn|

View File

@ -1,13 +1,4 @@
---
layout: documentation
title: "Available Middleware"
permalink: /middleware/list
hide: true
top_name: Middleware
top_link: ./
next_name: Writing Middleware
next_link: ./custom
---
# Included middleware
Faraday ships with some useful middleware that you can use to customize your request/response lifecycle.
Middleware are separated into two macro-categories: **Request Middleware** and **Response Middleware**.
@ -21,9 +12,7 @@ parsing the response body, logging useful info or checking the response status.
middleware set Header values or transform the request body based on the
content type.
* [`BasicAuthentication`][authentication] sets the `Authorization` header to the `user:password`
base64 representation.
* [`TokenAuthentication`][authentication] sets the `Authorization` header to the specified token.
* [`Authorization`][authentication] allows you to automatically add an Authorization header to your requests.
* [`UrlEncoded`][url_encoded] converts a `Faraday::Request#body` hash of key/value pairs into a url-encoded request body.
* [`Json Request`][json-request] converts a `Faraday::Request#body` hash of key/value pairs into a JSON request body.
* [`Instrumentation`][instrumentation] allows to instrument requests using different tools.
@ -39,10 +28,10 @@ before returning it.
* [`RaiseError`][raise_error] checks the response HTTP code and raises an exception if it is a 4xx or 5xx code.
[authentication]: ./authentication
[url_encoded]: ./url-encoded
[json-request]: ./json-request
[instrumentation]: ./instrumentation
[json-response]: ./json-response
[logger]: ./logger
[raise_error]: ./raise-error
[authentication]: middleware/included/authentication.md
[url_encoded]: middleware/included/url-encoding
[json-request]: middleware/included/json#json-requests
[instrumentation]: middleware/included/instrumentation
[json-response]: middleware/included/json#json-responses
[logger]: middleware/included/logging
[raise_error]: middleware/included/raising-errors

View File

@ -1,15 +1,4 @@
---
layout: documentation
title: "Instrumentation Middleware"
permalink: /middleware/instrumentation
hide: true
prev_name: JSON Request Middleware
prev_link: ./json-request
next_name: JSON Response Middleware
next_link: ./json-response
top_name: Back to Middleware
top_link: ./list
---
# Instrumentation
The `Instrumentation` middleware allows to instrument requests using different tools.
Options for this middleware include the instrumentation `name` and the `instrumenter` you want to use.

View File

@ -0,0 +1,81 @@
# JSON Encoding/Decoding
## JSON Requests
The `JSON` request middleware converts a `Faraday::Request#body` hash of key/value pairs into a JSON request body.
The middleware also automatically sets the `Content-Type` header to `application/json`,
processes only requests with matching Content-Type or those without a type and
doesn't try to encode bodies that already are in string form.
### Example Usage
```ruby
conn = Faraday.new(...) do |f|
f.request :json
...
end
conn.post('/', { a: 1, b: 2 })
# POST with
# Content-Type: application/json
# Body: {"a":1,"b":2}
```
### Using custom JSON encoders
By default, middleware utilizes Ruby's `json` to generate JSON strings.
Other encoders can be used by specifying `encoder` option for the middleware:
* a module/class which implements `dump`
* a module/class-method pair to be used
```ruby
require 'oj'
Faraday.new(...) do |f|
f.request :json, encoder: Oj
end
Faraday.new(...) do |f|
f.request :json, encoder: [Oj, :dump]
end
```
## JSON Responses
The `JSON` response middleware parses response body into a hash of key/value pairs.
The behaviour can be customized with the following options:
* **parser_options:** options that will be sent to the JSON.parse method. Defaults to {}.
* **content_type:** Single value or Array of response content-types that should be processed. Can be either strings or Regex. Defaults to `/\bjson$/`.
* **preserve_raw:** If set to true, the original un-parsed response will be stored in the `response.env[:raw_body]` property. Defaults to `false`.
### Example Usage
```ruby
conn = Faraday.new('http://httpbingo.org') do |f|
f.response :json, **options
end
conn.get('json').body
# => {"slideshow"=>{"author"=>"Yours Truly", "date"=>"date of publication", "slides"=>[{"title"=>"Wake up to WonderWidgets!", "type"=>"all"}, {"items"=>["Why <em>WonderWidgets</em> are great", "Who <em>buys</em> WonderWidgets"], "title"=>"Overview", "type"=>"all"}], "title"=>"Sample Slide Show"}}
```
### Using custom JSON decoders
By default, middleware utilizes Ruby's `json` to parse JSON strings.
Other decoders can be used by specifying `decoder` parser option for the middleware:
* a module/class which implements `load`
* a module/class-method pair to be used
```ruby
require 'oj'
Faraday.new(...) do |f|
f.response :json, parser_options: { decoder: Oj }
end
Faraday.new(...) do |f|
f.response :json, parser_options: { decoder: [Oj, :load] }
end
```

View File

@ -1,15 +1,4 @@
---
layout: documentation
title: "Logger Middleware"
permalink: /middleware/logger
hide: true
prev_name: JSON Response Middleware
prev_link: ./json-response
next_name: RaiseError Middleware
next_link: ./raise-error
top_name: Back to Middleware
top_link: ./list
---
# Logging
The `Logger` middleware logs both the request and the response body and headers.
It is highly customizable and allows to mask confidential information if necessary.

View File

@ -0,0 +1,90 @@
# Raising Errors
The `RaiseError` middleware raises a `Faraday::Error` exception if an HTTP
response returns with a 4xx or 5xx status code.
This greatly increases the ease of use of Faraday, as you don't have to check
the response status code manually.
These errors add to the list of default errors [raised by Faraday](getting-started/errors.md).
All exceptions are initialized with a hash containing the response `status`, `headers`, and `body`.
```ruby
conn = Faraday.new(url: 'http://httpbingo.org') do |faraday|
faraday.response :raise_error # raise Faraday::Error on status code 4xx or 5xx
end
begin
conn.get('/wrong-url') # => Assume this raises a 404 response
rescue Faraday::ResourceNotFound => e
e.response_status #=> 404
e.response_headers #=> { ... }
e.response_body #=> "..."
end
```
Specific exceptions are raised based on the HTTP Status code of the response.
## 4xx Errors
An HTTP status in the 400-499 range typically represents an error
by the client. They raise error classes inheriting from `Faraday::ClientError`.
| Status Code | Exception Class |
|---------------------------------------------------------------------|-------------------------------------|
| [400](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400) | `Faraday::BadRequestError` |
| [401](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401) | `Faraday::UnauthorizedError` |
| [403](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403) | `Faraday::ForbiddenError` |
| [404](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404) | `Faraday::ResourceNotFound` |
| [407](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/407) | `Faraday::ProxyAuthError` |
| [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) | `Faraday::RequestTimeoutError` |
| [409](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409) | `Faraday::ConflictError` |
| [422](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422) | `Faraday::UnprocessableEntityError` |
| [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) | `Faraday::TooManyRequestsError` |
| 4xx (any other) | `Faraday::ClientError` |
## 5xx Errors
An HTTP status in the 500-599 range represents a server error, and raises a
`Faraday::ServerError` exception.
It's important to note that this exception is only returned if we receive a response and the
HTTP status in such response is in the 500-599 range.
Other kind of errors normally attributed to errors in the 5xx range (such as timeouts, failure to connect, etc...)
are raised as specific exceptions inheriting from `Faraday::Error`.
See [Faraday Errors](getting-started/errors.md) for more information on these.
### Missing HTTP status
The HTTP response status may be nil due to a malformed HTTP response from the
server, or a bug in the underlying HTTP library. This is considered a server error
and raised as `Faraday::NilStatusError`, which inherits from `Faraday::ServerError`.
## Middleware Options
The behavior of this middleware can be customized with the following options:
| Option | Default | Description |
|----------------------|---------|-------------|
| **include_request** | true | When true, exceptions are initialized with request information including `method`, `url`, `url_path`, `params`, `headers`, and `body`. |
| **allowed_statuses** | [] | An array of status codes that should not raise an error. |
### Example Usage
```ruby
conn = Faraday.new(url: 'http://httpbingo.org') do |faraday|
faraday.response :raise_error, include_request: true, allowed_statuses: [404]
end
begin
conn.get('/wrong-url') # => Assume this raises a 404 response
conn.get('/protected-url') # => Assume this raises a 401 response
rescue Faraday::UnauthorizedError => e
e.response[:status] # => 401
e.response[:headers] # => { ... }
e.response[:body] # => "..."
e.response[:request][:url_path] # => "/protected-url"
end
```
In this example, a `Faraday::UnauthorizedError` exception is raised for the `/protected-url` request, while the
`/wrong-url` request does not raise an error because the status code `404` is in the `allowed_statuses` array.

View File

@ -1,19 +1,8 @@
---
layout: documentation
title: "UrlEncoded Middleware"
permalink: /middleware/url-encoded
hide: true
prev_name: Authentication Middleware
prev_link: ./authentication
next_name: JSON Request Middleware
next_link: ./json-request
top_name: Back to Middleware
top_link: ./list
---
# URL Encoding
The `UrlEncoded` middleware converts a `Faraday::Request#body` hash of key/value pairs into a url-encoded request body.
The middleware also automatically sets the `Content-Type` header to `application/x-www-form-urlencoded`.
The way parameters are serialized can be [customized][customize].
The way parameters are serialized can be customized in the [Request Options](customization/request-options.md).
### Example Usage

View File

@ -1,11 +1,4 @@
---
layout: documentation
title: "Middleware"
permalink: /middleware/
next_name: Available Middleware
next_link: ./list
order: 3
---
# Middleware
Under the hood, Faraday uses a Rack-inspired middleware stack for making
requests. Much of Faraday's power is unlocked with custom middleware. Some
@ -44,10 +37,10 @@ run, Faraday will return a `Faraday::Response` to the end user.
The order in which middleware is stacked is important. Like with Rack, the first
middleware on the list wraps all others, while the last middleware is the
innermost one. If you want to use a custom [adapter](../adapters), it must
innermost one. If you want to use a custom [adapter](adapters/index.md), it must
therefore be last.
![Middleware](../assets/img/middleware.png)
![Middleware](../_media/middleware.png)
This is what makes things like the "retry middleware" possible.
It doesn't really matter if the middleware was registered as a request or a response one, the only thing that matter is how they're added to the stack.
@ -121,16 +114,64 @@ conn = Faraday.new do |f|
end
```
### DEFAULT_OPTIONS
`DEFAULT_OPTIONS` improve the flexibility and customizability of new and existing middleware. Class-level `DEFAULT_OPTIONS` and the ability to set these defaults at the application level compliment existing functionality in which options can be passed into middleware on a per-instance basis.
#### Using DEFAULT_OPTIONS
Using `RaiseError` as an example, you can see that `DEFAULT_OPTIONS` have been defined at the top of the class:
```ruby
DEFAULT_OPTIONS = { include_request: true }.freeze
```
These options will be set at the class level upon instantiation and referenced as needed within the class. From our same example:
```ruby
def response_values(env)
...
return response unless options[:include_request]
...
```
If the default value provides the desired functionality, no further consideration is needed.
#### Setting Alternative Options per Application
In the case where it is desirable to change the default option for all instances within an application, it can be done by configuring the options in a `/config/initializers` file. For example:
```ruby
# config/initializers/faraday_config.rb
Faraday::Response::RaiseError.default_options = { include_request: false }
```
After app initialization, all instances of the middleware will have the newly configured option(s). They can still be overridden on a per-instance bases (if handled in the middleware), like this:
```ruby
Faraday.new do |f|
...
f.response :raise_error, include_request: true
...
end
```
### Available Middleware
The following pages provide detailed configuration for the middleware that ships with Faraday:
* [Authentication](middleware/included/authentication.md)
* [URL Encoding](middleware/included/url-encoding.md)
* [JSON Encoding/Decoding](middleware/included/json.md)
* [Instrumentation](middleware/included/instrumentation.md)
* [Logging](middleware/included/logging.md)
* [Raising Errors](middleware/included/raising-errors.md)
The [Awesome Faraday](https://github.com/lostisland/awesome-faraday/) project
has a complete list of useful, well-maintained Faraday middleware. Middleware is
often provided by external gems, like the
[faraday-retry](https://github.com/lostisland/faraday-retry) gem.
We also have [great documentation](list) for the middleware that ships with
Faraday.
### Detailed Example
Here's a more realistic example:
@ -151,7 +192,7 @@ end
This request middleware setup affects POST/PUT requests in the following way:
1. `Request::UrlEncoded` encodes as "application/x-www-form-urlencoded" if not
already encoded or of another type.
already encoded or of another type.
2. `Response::Logger` logs request and response headers, can be configured to log bodies as well.
Swapping middleware means giving the other priority. Specifying the

View File

@ -1,31 +0,0 @@
---
layout: documentation
title: "JSON Request Middleware"
permalink: /middleware/json-request
hide: true
prev_name: UrlEncoded Middleware
prev_link: ./url-encoded
next_name: Instrumentation Middleware
next_link: ./instrumentation
top_name: Back to Middleware
top_link: ./list
---
The `JSON` request middleware converts a `Faraday::Request#body` hash of key/value pairs into a JSON request body.
The middleware also automatically sets the `Content-Type` header to `application/json`,
processes only requests with matching Content-Type or those without a type and
doesn't try to encode bodies that already are in string form.
### Example Usage
```ruby
conn = Faraday.new(...) do |f|
f.request :json
...
end
conn.post('/', { a: 1, b: 2 })
# POST with
# Content-Type: application/json
# Body: {"a":1,"b":2}
```

View File

@ -1,29 +0,0 @@
---
layout: documentation
title: "JSON Response Middleware"
permalink: /middleware/json-response
hide: true
prev_name: Instrumentation Middleware
prev_link: ./instrumentation
next_name: Logger Middleware
next_link: ./logger
top_name: Back to Middleware
top_link: ./list
---
The `JSON` response middleware parses response body into a hash of key/value pairs.
The behaviour can be customized with the following options:
* **parser_options:** options that will be sent to the JSON.parse method. Defaults to {}.
* **content_type:** Single value or Array of response content-types that should be processed. Can be either strings or Regex. Defaults to `/\bjson$/`.
* **preserve_raw:** If set to true, the original un-parsed response will be stored in the `response.env[:raw_body]` property. Defaults to `false`.
### Example Usage
```ruby
conn = Faraday.new('http://httpbingo.org') do |f|
f.response :json, **options
end
conn.get('json').body
# => {"slideshow"=>{"author"=>"Yours Truly", "date"=>"date of publication", "slides"=>[{"title"=>"Wake up to WonderWidgets!", "type"=>"all"}, {"items"=>["Why <em>WonderWidgets</em> are great", "Who <em>buys</em> WonderWidgets"], "title"=>"Overview", "type"=>"all"}], "title"=>"Sample Slide Show"}}
```

View File

@ -1,53 +0,0 @@
---
layout: documentation
title: "Raise Error Middleware"
permalink: /middleware/raise-error
hide: true
prev_name: Logger Middleware
prev_link: ./logger
top_name: Back to Middleware
top_link: ./list
---
The `RaiseError` middleware raises a `Faraday::Error` exception if an HTTP
response returns with a 4xx or 5xx status code. All exceptions are initialized
providing the response `status`, `headers`, and `body`.
```ruby
conn = Faraday.new(url: 'http://httpbingo.org') do |faraday|
faraday.response :raise_error # raise Faraday::Error on status code 4xx or 5xx
end
begin
conn.get('/wrong-url') # => Assume this raises a 404 response
rescue Faraday::ResourceNotFound => e
e.response[:status] #=> 404
e.response[:headers] #=> { ... }
e.response[:body] #=> "..."
end
```
Specific exceptions are raised based on the HTTP Status code, according to the list below:
An HTTP status in the 400-499 range typically represents an error
by the client. They raise error classes inheriting from `Faraday::ClientError`.
* 400 => `Faraday::BadRequestError`
* 401 => `Faraday::UnauthorizedError`
* 403 => `Faraday::ForbiddenError`
* 404 => `Faraday::ResourceNotFound`
* 407 => `Faraday::ProxyAuthError`
* 409 => `Faraday::ConflictError`
* 422 => `Faraday::UnprocessableEntityError`
* 4xx => `Faraday::ClientError`
An HTTP status in the 500-599 range represents a server error, and raises a
`Faraday::ServerError` exception.
* 5xx => `Faraday::ServerError`
The HTTP response status may be nil due to a malformed HTTP response from the
server, or a bug in the underlying HTTP library. It inherits from
`Faraday::ServerError`.
* nil => `Faraday::NilStatusError`

View File

@ -1,26 +0,0 @@
---
layout: page
title: Team
permalink: /team/
order: 4
---
<div id="loader">
<div class="lds-ring"><div></div><div></div><div></div><div></div></div>
</div>
<div id="team-content" class="hidden">
<h3 class="text-center">Active Maintainers</h3>
<div id="active-maintainers-list"></div>
<h3 class="text-center">Historical team</h3>
<div id="historical-team-list"></div>
<h3 class="text-center">All the contributors</h3>
<div id="contributors-list"></div>
<h3 class="text-center">And some extra help</h3>
<p class="text-center">Website and branding design: <a href="https://elelopic.design" target="_blank">Elena Lo Piccolo</a></p>
</div>
<script src="{{ site.baseurl }}/assets/js/team.js"></script>

View File

@ -1,179 +0,0 @@
---
layout: documentation
title: "Usage"
permalink: /usage/
next_name: Customizing the Request
next_link: ./customize
order: 1
---
Let's fetch the home page for the wonderful
[httpbingo.org](https://httpbingo.org) service.
You can make a simple `GET` request using `Faraday.get`:
```ruby
response = Faraday.get('http://httpbingo.org')
```
This returns a `Faraday::Response` object with the response status, headers, and body.
```ruby
response.status
# => 200
response.headers
# => {"server"=>"Fly/c375678 (2021-04-23)", "content-type"=> ...
response.body
# => "<!DOCTYPE html><html> ...
```
### Faraday Connection
The recommended way to use Faraday, especially when integrating to 3rd party services and API, is to create
a `Faraday::Connection`. The connection object can be configured with things like:
- default request headers & query parameters
- network settings like proxy or timeout
- common URL base path
- Faraday adapter & middleware (see below)
Create a `Faraday::Connection` by calling `Faraday.new`. You can then call each HTTP verb
(`get`, `post`, ...) on your `Faraday::Connection` to perform a request:
```ruby
conn = Faraday.new(
url: 'http://httpbingo.org',
params: {param: '1'},
headers: {'Content-Type' => 'application/json'}
)
response = conn.post('/post') do |req|
req.params['limit'] = 100
req.body = {query: 'chunky bacon'}.to_json
end
# => POST http://httpbingo.org/post?param=1&limit=100
```
### GET, HEAD, DELETE, TRACE
Faraday supports the following HTTP verbs that typically don't include a request body:
- `get(url, params = nil, headers = nil)`
- `head(url, params = nil, headers = nil)`
- `delete(url, params = nil, headers = nil)`
- `trace(url, params = nil, headers = nil)`
You can specify URI query parameters and HTTP headers when making a request.
```ruby
response = conn.get('get', { boom: 'zap' }, { 'User-Agent' => 'myapp' })
# => GET http://httpbingo.org/get?boom=zap
```
### POST, PUT, PATCH
Faraday also supports HTTP verbs with bodies. Instead of query parameters, these
accept a request body:
- `post(url, body = nil, headers = nil)`
- `put(url, body = nil, headers = nil)`
- `patch(url, body = nil, headers = nil)`
```ruby
# POST 'application/x-www-form-urlencoded' content
response = conn.post('post', 'boom=zap')
# POST JSON content
response = conn.post('post', '{"boom": "zap"}',
"Content-Type" => "application/json")
```
#### Posting Forms
Faraday will automatically convert key/value hashes into proper form bodies
thanks to the `url_encoded` middleware included in the default connection.
```ruby
# POST 'application/x-www-form-urlencoded' content
response = conn.post('post', boom: 'zap')
# => POST 'boom=zap' to http://httpbingo.org/post
```
### Detailed HTTP Requests
Faraday supports a longer style for making requests. This is handy if you need
to change many of the defaults, or if the details of the HTTP request change
according to method arguments. Each of the HTTP verb helpers can yield a
`Faraday::Request` that can be modified before being sent.
This example shows a hypothetical search endpoint that accepts a JSON request
body as the actual search query.
```ruby
response = conn.post('post') do |req|
req.params['limit'] = 100
req.headers['Content-Type'] = 'application/json'
req.body = {query: 'chunky bacon'}.to_json
end
# => POST http://httpbingo.org/post?limit=100
```
### Adapters
Adapters are responsible for actually executing HTTP requests. The default
adapter uses Ruby's `Net::HTTP`, but there are many different adapters
available. You might want to use Faraday with the Typhoeus adapter, for example.
[Learn more about Adapters](../adapters).
### Middleware
Under the hood, Faraday uses a Rack-inspired middleware stack for making
requests. Much of Faraday's power is unlocked with custom middleware. Some
middleware is included with Faraday, and others are in external gems. [Learn
more about Middleware](../middleware).
Here are some of the features that middleware can provide:
- authentication
- caching responses on disk or in memory
- cookies
- following redirects
- JSON encoding/decoding
- logging
- retrying
To use these great features, create a `Faraday::Connection` with `Faraday.new`
and add the correct middleware in a block. For example:
```ruby
require 'faraday'
require 'faraday/retry'
conn = Faraday.new('http://httpbingo.org') do |f|
f.request :json # encode req bodies as JSON and automatically set the Content-Type header
f.request :retry # retry transient failures
f.response :json # decode response bodies as JSON
f.adapter :net_http # adds the adapter to the connection, defaults to `Faraday.default_adapter`
end
# Sends a GET request with JSON body that will automatically retry in case of failure.
response = conn.get('get', boom: 'zap')
# response body is automatically decoded from JSON to a Ruby hash
response.body['args'] #=> {"boom"=>["zap"]}
```
#### Default Connection, Default Middleware
Remember how we said that Faraday will automatically encode key/value hash
bodies into form bodies? Internally, the top level shortcut methods
`Faraday.get`, `post`, etc. use a simple default `Faraday::Connection`. The only
middleware used for the default connection is `:url_encoded`, which encodes
those form hashes, and the `default_adapter`.
Note that if you create your own connection with middleware, it won't encode
form bodies unless you too include the [`:url_encoded`][encoding] middleware!
[encoding]: ../middleware/url-encoded

View File

@ -13,7 +13,7 @@ Gem::Specification.new do |spec|
spec.homepage = 'https://lostisland.github.io/faraday'
spec.licenses = ['MIT']
spec.required_ruby_version = '>= 2.6'
spec.required_ruby_version = '>= 3.0'
# faraday-net_http is the "default adapter", but being a Faraday dependency it can't
# control which version of faraday it will be pulled from.
@ -21,8 +21,9 @@ Gem::Specification.new do |spec|
# always fix its required version to the next MINOR version.
# This way, we can release minor versions of the adapter with "breaking" changes for older versions of Faraday
# and then bump the version requirement on the next compatible version of faraday.
spec.add_dependency 'faraday-net_http', '>= 2.0', '< 3.1'
spec.add_dependency 'ruby2_keywords', '>= 0.0.4'
spec.add_dependency 'faraday-net_http', '>= 2.0', '< 3.5'
spec.add_dependency 'json'
spec.add_dependency 'logger'
# Includes `examples` and `spec` to allow external adapter gems to run Faraday unit and integration tests
spec.files = Dir['CHANGELOG.md', '{examples,lib,spec}/**/*', 'LICENSE.md', 'Rakefile', 'README.md']
@ -32,6 +33,7 @@ Gem::Specification.new do |spec|
'changelog_uri' =>
"https://github.com/lostisland/faraday/releases/tag/v#{spec.version}",
'source_code_uri' => 'https://github.com/lostisland/faraday',
'bug_tracker_uri' => 'https://github.com/lostisland/faraday/issues'
'bug_tracker_uri' => 'https://github.com/lostisland/faraday/issues',
'rubygems_mfa_required' => 'true'
}
end

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true
require 'cgi'
require 'cgi/escape'
require 'cgi/util' if RUBY_VERSION < '3.5'
require 'date'
require 'set'
require 'forwardable'

View File

@ -26,7 +26,7 @@ module Faraday
self.supports_parallel = false
def initialize(_app = nil, opts = {}, &block)
@app = ->(env) { env.response }
@app = lambda(&:response)
@connection_options = opts
@config_block = block
end

View File

@ -146,7 +146,7 @@ module Faraday
# which means that all of a path, parameters, and headers must be the same as an actual request.
def strict_mode=(value)
@strict_mode = value
@stack.each do |_method, stubs|
@stack.each_value do |stubs|
stubs.each do |stub|
stub.strict_mode = value
end
@ -275,7 +275,7 @@ module Faraday
unless stub
raise Stubs::NotFound, "no stubbed request for #{env[:method]} " \
"#{env[:url]} #{env[:body]}"
"#{env[:url]} #{env[:body]} #{env[:headers]}"
end
block_arity = stub.block.arity

View File

@ -15,7 +15,7 @@ module Faraday
class Connection
# A Set of allowed HTTP verbs.
METHODS = Set.new %i[get post put delete head patch options trace]
USER_AGENT = "Faraday v#{VERSION}"
USER_AGENT = "Faraday v#{VERSION}".freeze
# @return [Hash] URI query unencoded key/value pairs.
attr_reader :params
@ -314,15 +314,23 @@ module Faraday
#
# @yield a block to execute multiple requests.
# @return [void]
def in_parallel(manager = nil)
def in_parallel(manager = nil, &block)
@parallel_manager = manager || default_parallel_manager do
warn 'Warning: `in_parallel` called but no parallel-capable adapter ' \
'on Faraday stack'
warn caller[2, 10].join("\n")
nil
end
yield
@parallel_manager&.run
return yield unless @parallel_manager
if @parallel_manager.respond_to?(:execute)
# Execute is the new method that is responsible for executing the block.
@parallel_manager.execute(&block)
else
# TODO: Old behaviour, deprecate and remove in 3.0
yield
@parallel_manager.run
end
ensure
@parallel_manager = nil
end
@ -423,8 +431,8 @@ module Faraday
#
# @param method [Symbol] HTTP method.
# @param url [String, URI, nil] String or URI to access.
# @param body [String, nil] The request body that will eventually be converted to
# a string.
# @param body [String, Hash, Array, nil] The request body that will eventually be converted to
# a string; middlewares can be used to support more complex types.
# @param headers [Hash, nil] unencoded HTTP header key/value pairs.
#
# @return [Faraday::Response]
@ -473,7 +481,8 @@ module Faraday
if url && !base.path.end_with?('/')
base.path = "#{base.path}/" # ensure trailing slash
end
url = url.to_s.gsub(':', '%3A') if URI.parse(url.to_s).opaque
# Ensure relative url will be parsed correctly (such as `service:search` )
url = "./#{url}" if url.respond_to?(:start_with?) && !url.start_with?('http://', 'https://', '/', './', '../')
uri = url ? base + url : base
if params
uri.query = params.to_query(params_encoder || options.params_encoder)

View File

@ -76,9 +76,9 @@ module Faraday
empty_accumulator = {}
split_query = (query.split('&').map do |pair|
split_query = query.split('&').filter_map do |pair|
pair.split('=', 2) if pair && !pair.empty?
end).compact
end
split_query.each_with_object(empty_accumulator.dup) do |pair, accu|
pair[0] = unescape(pair[0])
pair[1] = true if pair[1].nil?

View File

@ -102,7 +102,7 @@ module Faraday
protected
SUBKEYS_REGEX = /[^\[\]]+(?:\]?\[\])?/.freeze
SUBKEYS_REGEX = /[^\[\]]+(?:\]?\[\])?/
def decode_pair(key, value, context)
subkeys = key.scan(SUBKEYS_REGEX)

View File

@ -79,12 +79,46 @@ module Faraday
# Pulls out potential parent exception and response hash.
def exc_msg_and_response(exc, response = nil)
return [exc, exc.message, response] if exc.respond_to?(:backtrace)
case exc
when Exception
[exc, exc.message, response]
when Hash
[nil, build_error_message_from_hash(exc), exc]
when Faraday::Env
[nil, build_error_message_from_env(exc), exc]
else
[nil, exc.to_s, response]
end
end
return [nil, "the server responded with status #{exc[:status]}", exc] \
if exc.respond_to?(:each_key)
private
[nil, exc.to_s, response]
def build_error_message_from_hash(hash)
# Be defensive with external Hash objects - they might be missing keys
status = hash.fetch(:status, nil)
request = hash.fetch(:request, nil)
return fallback_error_message(status) if request.nil?
method = request.fetch(:method, nil)
url = request.fetch(:url, nil)
build_status_error_message(status, method, url)
end
def build_error_message_from_env(env)
# Faraday::Env is internal - we can make reasonable assumptions about its structure
build_status_error_message(env.status, env.method, env.url)
end
def build_status_error_message(status, method, url)
method_str = method ? method.to_s.upcase : ''
url_str = url ? url.to_s : ''
"the server responded with status #{status} for #{method_str} #{url_str}"
end
def fallback_error_message(status)
"the server responded with status #{status} - method and url are not available " \
'due to include_request: false on Faraday::Response::RaiseError middleware'
end
end
@ -112,6 +146,10 @@ module Faraday
class ProxyAuthError < ClientError
end
# Raised by Faraday::Response::RaiseError in case of a 408 response.
class RequestTimeoutError < ClientError
end
# Raised by Faraday::Response::RaiseError in case of a 409 response.
class ConflictError < ClientError
end
@ -120,6 +158,10 @@ module Faraday
class UnprocessableEntityError < ClientError
end
# Raised by Faraday::Response::RaiseError in case of a 429 response.
class TooManyRequestsError < ClientError
end
# Faraday server error class. Represents 5xx status responses.
class ServerError < Error
end
@ -150,4 +192,8 @@ module Faraday
# Raised by middlewares that parse the response, like the JSON response middleware.
class ParsingError < Error
end
# Raised by Faraday::Middleware and subclasses when invalid default_options are used
class InitializationError < Error
end
end

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'pp'
require 'pp' # This require is necessary for Hash#pretty_inspect to work, do not remove it, people rely on it.
module Faraday
module Logging
@ -13,25 +13,26 @@ module Faraday
def initialize(logger:, options:)
@logger = logger
@filter = []
@options = DEFAULT_OPTIONS.merge(options)
unless %i[debug info warn error fatal].include?(@options[:log_level])
@options[:log_level] = :info
end
@filter = []
end
def_delegators :@logger, :debug, :info, :warn, :error, :fatal
def request(env)
request_log = proc do
"#{env.method.upcase} #{apply_filters(env.url.to_s)}"
public_send(log_level) do
"request: #{env.method.upcase} #{apply_filters(env.url.to_s)}"
end
public_send(log_level, 'request', &request_log)
log_headers('request', env.request_headers) if log_headers?(:request)
log_body('request', env[:body]) if env[:body] && log_body?(:request)
end
def response(env)
status = proc { "Status #{env.status}" }
public_send(log_level, 'response', &status)
public_send(log_level) { "response: Status #{env.status}" }
log_headers('response', env.response_headers) if log_headers?(:response)
log_body('response', env[:body]) if env[:body] && log_body?(:response)
@ -40,8 +41,7 @@ module Faraday
def exception(exc)
return unless log_errors?
error_log = proc { exc.full_message }
public_send(log_level, 'error', &error_log)
public_send(log_level) { "error: #{exc.full_message}" }
log_headers('error', exc.response_headers) if exc.respond_to?(:response_headers) && log_headers?(:error)
return unless exc.respond_to?(:response_body) && exc.response_body && log_body?(:error)
@ -56,6 +56,8 @@ module Faraday
private
def dump_headers(headers)
return if headers.nil?
headers.map { |k, v| "#{k}: #{v.inspect}" }.join("\n")
end
@ -101,21 +103,15 @@ module Faraday
end
def log_level
unless %i[debug info warn error fatal].include?(@options[:log_level])
return :info
end
@options[:log_level]
end
def log_headers(type, headers)
headers_log = proc { apply_filters(dump_headers(headers)) }
public_send(log_level, type, &headers_log)
public_send(log_level) { "#{type}: #{apply_filters(dump_headers(headers))}" }
end
def log_body(type, body)
body_log = proc { apply_filters(dump_body(body)) }
public_send(log_level, type, &body_log)
public_send(log_level) { "#{type}: #{apply_filters(dump_body(body))}" }
end
end
end

View File

@ -1,5 +1,7 @@
# frozen_string_literal: true
require 'monitor'
module Faraday
# Middleware is the basic base class of any Faraday middleware.
class Middleware
@ -7,9 +9,46 @@ module Faraday
attr_reader :app, :options
DEFAULT_OPTIONS = {}.freeze
LOCK = Mutex.new
def initialize(app = nil, options = {})
@app = app
@options = options
@options = self.class.default_options.merge(options)
end
class << self
# Faraday::Middleware::default_options= allows user to set default options at the Faraday::Middleware
# class level.
#
# @example Set the Faraday::Response::RaiseError option, `include_request` to `false`
# my_app/config/initializers/my_faraday_middleware.rb
#
# Faraday::Response::RaiseError.default_options = { include_request: false }
#
def default_options=(options = {})
validate_default_options(options)
LOCK.synchronize do
@default_options = default_options.merge(options)
end
end
# default_options attr_reader that initializes class instance variable
# with the values of any Faraday::Middleware defaults, and merges with
# subclass defaults
def default_options
@default_options ||= DEFAULT_OPTIONS.merge(self::DEFAULT_OPTIONS)
end
private
def validate_default_options(options)
invalid_keys = options.keys.reject { |opt| self::DEFAULT_OPTIONS.key?(opt) }
return unless invalid_keys.any?
raise(Faraday::InitializationError,
"Invalid options provided. Keys not found in #{self}::DEFAULT_OPTIONS: #{invalid_keys.join(', ')}")
end
end
def call(env)

View File

@ -30,7 +30,7 @@ module Faraday
new_value = value
end
send("#{key}=", new_value) unless new_value.nil?
send(:"#{key}=", new_value) unless new_value.nil?
end
self
end
@ -38,7 +38,7 @@ module Faraday
# Public
def delete(key)
value = send(key)
send("#{key}=", nil)
send(:"#{key}=", nil)
value
end
@ -57,7 +57,7 @@ module Faraday
else
other_value
end
send("#{key}=", new_value) unless new_value.nil?
send(:"#{key}=", new_value) unless new_value.nil?
end
self
end

View File

@ -60,7 +60,7 @@ module Faraday
:reason_phrase, :response_body) do
const_set(:ContentLength, 'Content-Length')
const_set(:StatusesWithoutBody, Set.new([204, 304]))
const_set(:SuccessfulStatuses, (200..299).freeze)
const_set(:SuccessfulStatuses, 200..299)
# A Set of HTTP verbs that typically send a body. If no body is set for
# these requests, the Content-Length header is set to 0.
@ -169,7 +169,7 @@ module Faraday
def stream_response(&block)
size = 0
yielded = false
block_result = block.call do |chunk| # rubocop:disable Performance/RedundantBlockCall
block_result = block.call do |chunk|
if chunk.bytesize.positive? || size.positive?
yielded = true
size += chunk.bytesize

View File

@ -22,8 +22,10 @@ module Faraday
when URI
value = { uri: value }
when Hash, Options
if (uri = value.delete(:uri))
value[:uri] = Utils.URI(uri)
if value[:uri]
value = value.dup.tap do |duped|
duped[:uri] = Utils.URI(duped[:uri])
end
end
end

View File

@ -11,6 +11,9 @@ module Faraday
# # @return [Boolean] whether to enable hostname verification on server certificates
# # during the handshake or not (see https://github.com/ruby/openssl/pull/60)
# #
# # @!attribute hostname
# # @return [String] Server hostname used for SNI (see https://ruby-doc.org/stdlib-2.5.1/libdoc/openssl/rdoc/OpenSSL/SSL/SSLSocket.html#method-i-hostname-3D)
# #
# # @!attribute ca_file
# # @return [String] CA file
# #
@ -46,12 +49,15 @@ module Faraday
# #
# # @!attribute max_version
# # @return [String, Symbol] maximum SSL version (see https://ruby-doc.org/stdlib-2.5.1/libdoc/openssl/rdoc/OpenSSL/SSL/SSLContext.html#method-i-max_version-3D)
# #
# # @!attribute ciphers
# # @return [String] cipher list in OpenSSL format (see https://ruby-doc.org/stdlib-2.5.1/libdoc/openssl/rdoc/OpenSSL/SSL/SSLContext.html#method-i-ciphers-3D)
# class SSLOptions < Options; end
SSLOptions = Options.new(:verify, :verify_hostname,
SSLOptions = Options.new(:verify, :verify_hostname, :hostname,
:ca_file, :ca_path, :verify_mode,
:cert_store, :client_cert, :client_key,
:certificate, :private_key, :verify_depth,
:version, :min_version, :max_version) do
:version, :min_version, :max_version, :ciphers) do
# @return [Boolean] true if should verify
def verify?
verify != false

View File

@ -1,6 +1,5 @@
# frozen_string_literal: true
require 'ruby2_keywords'
require 'faraday/adapter_registry'
module Faraday
@ -28,10 +27,11 @@ module Faraday
attr_reader :name
ruby2_keywords def initialize(klass, *args, &block)
def initialize(klass, *args, **kwargs, &block)
@name = klass.to_s
REGISTRY.set(klass) if klass.respond_to?(:name)
@args = args
@kwargs = kwargs
@block = block
end
@ -54,7 +54,7 @@ module Faraday
end
def build(app = nil)
klass.new(app, *@args, &@block)
klass.new(app, *@args, **@kwargs, &@block)
end
end
@ -89,52 +89,52 @@ module Faraday
@handlers.frozen?
end
ruby2_keywords def use(klass, *args, &block)
def use(klass, ...)
if klass.is_a? Symbol
use_symbol(Faraday::Middleware, klass, *args, &block)
use_symbol(Faraday::Middleware, klass, ...)
else
raise_if_locked
raise_if_adapter(klass)
@handlers << self.class::Handler.new(klass, *args, &block)
@handlers << self.class::Handler.new(klass, ...)
end
end
ruby2_keywords def request(key, *args, &block)
use_symbol(Faraday::Request, key, *args, &block)
def request(key, ...)
use_symbol(Faraday::Request, key, ...)
end
ruby2_keywords def response(key, *args, &block)
use_symbol(Faraday::Response, key, *args, &block)
def response(...)
use_symbol(Faraday::Response, ...)
end
ruby2_keywords def adapter(klass = NO_ARGUMENT, *args, &block)
def adapter(klass = NO_ARGUMENT, *args, **kwargs, &block)
return @adapter if klass == NO_ARGUMENT || klass.nil?
klass = Faraday::Adapter.lookup_middleware(klass) if klass.is_a?(Symbol)
@adapter = self.class::Handler.new(klass, *args, &block)
@adapter = self.class::Handler.new(klass, *args, **kwargs, &block)
end
## methods to push onto the various positions in the stack:
ruby2_keywords def insert(index, *args, &block)
def insert(index, ...)
raise_if_locked
index = assert_index(index)
handler = self.class::Handler.new(*args, &block)
handler = self.class::Handler.new(...)
@handlers.insert(index, handler)
end
alias insert_before insert
ruby2_keywords def insert_after(index, *args, &block)
def insert_after(index, ...)
index = assert_index(index)
insert(index + 1, *args, &block)
insert(index + 1, ...)
end
ruby2_keywords def swap(index, *args, &block)
def swap(index, ...)
raise_if_locked
index = assert_index(index)
@handlers.delete_at(index)
insert(index, *args, &block)
insert(index, ...)
end
def delete(handler)
@ -221,7 +221,7 @@ module Faraday
end
def raise_if_adapter(klass)
return unless is_adapter?(klass)
return unless klass <= Faraday::Adapter
raise 'Adapter should be set using the `adapter` method, not `use`'
end
@ -234,12 +234,8 @@ module Faraday
!@adapter.nil?
end
def is_adapter?(klass) # rubocop:disable Naming/PredicateName
klass <= Faraday::Adapter
end
ruby2_keywords def use_symbol(mod, key, *args, &block)
use(mod.lookup_middleware(key), *args, &block)
def use_symbol(mod, key, ...)
use(mod.lookup_middleware(key), ...)
end
def assert_index(index)

View File

@ -13,7 +13,7 @@ module Faraday
# Doesn't try to encode bodies that already are in string form.
class Json < Middleware
MIME_TYPE = 'application/json'
MIME_TYPE_REGEX = %r{^application/(vnd\..+\+)?json$}.freeze
MIME_TYPE_REGEX = %r{^application/(vnd\..+\+)?json$}
def on_request(env)
match_content_type(env) do |data|
@ -24,7 +24,13 @@ module Faraday
private
def encode(data)
::JSON.generate(data)
if options[:encoder].is_a?(Array) && options[:encoder].size >= 2
options[:encoder][0].public_send(options[:encoder][1], data)
elsif options[:encoder].respond_to?(:dump)
options[:encoder].dump(data)
else
::JSON.generate(data)
end
end
def match_content_type(env)

View File

@ -11,6 +11,8 @@ module Faraday
@parser_options = parser_options
@content_types = Array(content_type)
@preserve_raw = preserve_raw
process_parser_options
end
def on_complete(env)
@ -27,7 +29,11 @@ module Faraday
end
def parse(body)
::JSON.parse(body, @parser_options || {}) unless body.strip.empty?
return if body.strip.empty?
decoder, method_name = @decoder_options
decoder.public_send(method_name, body, @parser_options || {})
end
def parse_response?(env)
@ -47,6 +53,20 @@ module Faraday
type = type.split(';', 2).first if type.index(';')
type
end
def process_parser_options
@decoder_options = @parser_options&.delete(:decoder)
@decoder_options =
if @decoder_options.is_a?(Array) && @decoder_options.size >= 2
@decoder_options.slice(0, 2)
elsif @decoder_options&.respond_to?(:load) # rubocop:disable Lint/RedundantSafeNavigation
# In some versions of Rails, `nil` responds to `load` - hence the safe navigation check above
[@decoder_options, :load]
else
[::JSON, :parse]
end
end
end
end
end

View File

@ -10,11 +10,13 @@ module Faraday
# lifecycle to a given Logger object. By default, this logs to STDOUT. See
# Faraday::Logging::Formatter to see specifically what is logged.
class Logger < Middleware
DEFAULT_OPTIONS = { formatter: Logging::Formatter }.merge(Logging::Formatter::DEFAULT_OPTIONS).freeze
def initialize(app, logger = nil, options = {})
super(app)
super(app, options)
logger ||= ::Logger.new($stdout)
formatter_class = options.delete(:formatter) || Logging::Formatter
@formatter = formatter_class.new(logger: logger, options: options)
formatter_class = @options.delete(:formatter)
@formatter = formatter_class.new(logger: logger, options: @options)
yield @formatter if block_given?
end

View File

@ -6,28 +6,32 @@ module Faraday
# client or server error responses.
class RaiseError < Middleware
# rubocop:disable Naming/ConstantName
ClientErrorStatuses = (400...500).freeze
ServerErrorStatuses = (500...600).freeze
ClientErrorStatuses = (400...500)
ServerErrorStatuses = (500...600)
ClientErrorStatusesWithCustomExceptions = {
400 => Faraday::BadRequestError,
401 => Faraday::UnauthorizedError,
403 => Faraday::ForbiddenError,
404 => Faraday::ResourceNotFound,
408 => Faraday::RequestTimeoutError,
409 => Faraday::ConflictError,
422 => Faraday::UnprocessableEntityError,
429 => Faraday::TooManyRequestsError
}.freeze
# rubocop:enable Naming/ConstantName
DEFAULT_OPTIONS = { include_request: true, allowed_statuses: [] }.freeze
def on_complete(env)
return if Array(options[:allowed_statuses]).include?(env[:status])
case env[:status]
when 400
raise Faraday::BadRequestError, response_values(env)
when 401
raise Faraday::UnauthorizedError, response_values(env)
when 403
raise Faraday::ForbiddenError, response_values(env)
when 404
raise Faraday::ResourceNotFound, response_values(env)
when *ClientErrorStatusesWithCustomExceptions.keys
raise ClientErrorStatusesWithCustomExceptions[env[:status]], response_values(env)
when 407
# mimic the behavior that we get with proxy requests with HTTPS
msg = %(407 "Proxy Authentication Required")
raise Faraday::ProxyAuthError.new(msg, response_values(env))
when 409
raise Faraday::ConflictError, response_values(env)
when 422
raise Faraday::UnprocessableEntityError, response_values(env)
when ClientErrorStatuses
raise Faraday::ClientError, response_values(env)
when ServerErrorStatuses
@ -37,11 +41,26 @@ module Faraday
end
end
# Returns a hash of response data with the following keys:
# - status
# - headers
# - body
# - request
#
# The `request` key is omitted when the middleware is explicitly
# configured with the option `include_request: false`.
def response_values(env)
{
response = {
status: env.status,
headers: env.response_headers,
body: env.body,
body: env.body
}
# Include the request data by default. If the middleware was explicitly
# configured to _not_ include request data, then omit it.
return response unless options[:include_request]
response.merge(
request: {
method: env.method,
url: env.url,
@ -50,7 +69,7 @@ module Faraday
headers: env.request_headers,
body: env.request_body
}
}
)
end
def query_params(env)

View File

@ -1,6 +1,5 @@
# frozen_string_literal: true
require 'base64'
require 'uri'
require 'faraday/utils/headers'
require 'faraday/utils/params_hash'
@ -26,7 +25,7 @@ module Faraday
attr_writer :default_space_encoding
end
ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/.freeze
ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/
def escape(str)
str.to_s.gsub(ESCAPE_RE) do |match|
@ -38,7 +37,7 @@ module Faraday
CGI.unescape str.to_s
end
DEFAULT_SEP = /[&;] */n.freeze
DEFAULT_SEP = /[&;] */n
# Adapted from Rack
def parse_query(query)
@ -54,7 +53,7 @@ module Faraday
end
def basic_header_from(login, pass)
value = Base64.encode64("#{login}:#{pass}")
value = ["#{login}:#{pass}"].pack('m') # Base64 encoding
value.delete!("\n")
"Basic #{value}"
end

View File

@ -62,10 +62,10 @@ module Faraday
super(key, val)
end
def fetch(key, *args, &block)
def fetch(key, ...)
key = KeyMap[key]
key = @names.fetch(key.downcase, key)
super(key, *args, &block)
super(key, ...)
end
def delete(key)
@ -77,6 +77,12 @@ module Faraday
super(key)
end
def dig(key, *rest)
key = KeyMap[key]
key = @names.fetch(key.downcase, key)
super(key, *rest)
end
def include?(key)
@names.include? key.downcase
end

View File

@ -1,5 +1,5 @@
# frozen_string_literal: true
module Faraday
VERSION = '2.7.7'
VERSION = '2.13.4'
end

2035
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

31
package.json Normal file
View File

@ -0,0 +1,31 @@
{
"name": "faraday-docs",
"version": "0.1.0",
"description": "Faraday Docs",
"main": "index.js",
"directories": {
"doc": "docs",
"example": "examples",
"lib": "lib"
},
"scripts": {
"docs": "docsify serve ./docs"
},
"repository": {
"type": "git",
"url": "git+https://github.com/lostisland/faraday.git"
},
"keywords": [
"docs",
"faraday"
],
"author": "Mattia Giuffrida",
"license": "MIT",
"bugs": {
"url": "https://github.com/lostisland/faraday/issues"
},
"homepage": "https://github.com/lostisland/faraday#readme",
"dependencies": {
"docsify-cli": "^4.4.4"
}
}

View File

@ -300,14 +300,14 @@ RSpec.describe Faraday::Connection do
it 'joins url to base when used relative path' do
conn = Faraday.new(url: url)
uri = conn.build_exclusive_url('service:search?limit=400')
expect(uri.to_s).to eq('http://service.com/service%3Asearch?limit=400')
expect(uri.to_s).to eq('http://service.com/service:search?limit=400')
end
it 'joins url to base when used with path prefix' do
conn = Faraday.new(url: url)
conn.path_prefix = '/api'
uri = conn.build_exclusive_url('service:search?limit=400')
expect(uri.to_s).to eq('http://service.com/api/service%3Asearch?limit=400')
expect(uri.to_s).to eq('http://service.com/api/service:search?limit=400')
end
end

View File

@ -23,8 +23,12 @@ RSpec.describe Faraday::Error do
it { expect(subject.wrapped_exception).to be_nil }
it { expect(subject.response).to eq(exception) }
it { expect(subject.message).to eq('the server responded with status 400') }
it { expect(subject.inspect).to eq('#<Faraday::Error response={:status=>400}>') }
it { expect(subject.message).to eq('the server responded with status 400 - method and url are not available due to include_request: false on Faraday::Response::RaiseError middleware') }
if RUBY_VERSION >= '3.4'
it { expect(subject.inspect).to eq('#<Faraday::Error response={status: 400}>') }
else
it { expect(subject.inspect).to eq('#<Faraday::Error response={:status=>400}>') }
end
it { expect(subject.response_status).to eq(400) }
it { expect(subject.response_headers).to be_nil }
it { expect(subject.response_body).to be_nil }
@ -61,7 +65,11 @@ RSpec.describe Faraday::Error do
it { expect(subject.wrapped_exception).to be_nil }
it { expect(subject.response).to eq(response) }
it { expect(subject.message).to eq('custom message') }
it { expect(subject.inspect).to eq('#<Faraday::Error response={:status=>400}>') }
if RUBY_VERSION >= '3.4'
it { expect(subject.inspect).to eq('#<Faraday::Error response={status: 400}>') }
else
it { expect(subject.inspect).to eq('#<Faraday::Error response={:status=>400}>') }
end
it { expect(subject.response_status).to eq(400) }
it { expect(subject.response_headers).to be_nil }
it { expect(subject.response_body).to be_nil }
@ -81,5 +89,87 @@ RSpec.describe Faraday::Error do
it { expect(subject.response_headers).to eq(headers) }
it { expect(subject.response_body).to eq(body) }
end
context 'with hash missing status key' do
let(:exception) { { body: 'error body' } }
it { expect(subject.wrapped_exception).to be_nil }
it { expect(subject.response).to eq(exception) }
it { expect(subject.message).to eq('the server responded with status - method and url are not available due to include_request: false on Faraday::Response::RaiseError middleware') }
end
context 'with hash with status but missing request data' do
let(:exception) { { status: 404, body: 'not found' } } # missing request key
it { expect(subject.wrapped_exception).to be_nil }
it { expect(subject.response).to eq(exception) }
it { expect(subject.message).to eq('the server responded with status 404 - method and url are not available due to include_request: false on Faraday::Response::RaiseError middleware') }
end
context 'with hash with status and request but missing method in request' do
let(:exception) { { status: 404, body: 'not found', request: { url: 'http://example.com/test' } } } # missing method
it { expect(subject.wrapped_exception).to be_nil }
it { expect(subject.response).to eq(exception) }
it { expect(subject.message).to eq('the server responded with status 404 for http://example.com/test') }
end
context 'with hash with status and request but missing url in request' do
let(:exception) { { status: 404, body: 'not found', request: { method: :get } } } # missing url
it { expect(subject.wrapped_exception).to be_nil }
it { expect(subject.response).to eq(exception) }
it { expect(subject.message).to eq('the server responded with status 404 for GET ') }
end
context 'with properly formed Faraday::Env' do
# This represents the normal case - a well-formed Faraday::Env object
# with all the standard properties populated as they would be during
# a typical HTTP request/response cycle
let(:exception) { Faraday::Env.new }
before do
exception.status = 500
exception.method = :post
exception.url = URI('https://api.example.com/users')
exception.request = Faraday::RequestOptions.new
exception.response_headers = { 'content-type' => 'application/json' }
exception.response_body = '{"error": "Internal server error"}'
exception.request_headers = { 'authorization' => 'Bearer token123' }
exception.request_body = '{"name": "John"}'
end
it { expect(subject.wrapped_exception).to be_nil }
it { expect(subject.response).to eq(exception) }
it { expect(subject.message).to eq('the server responded with status 500 for POST https://api.example.com/users') }
end
context 'with Faraday::Env missing status key' do
let(:exception) { Faraday::Env.new }
before do
exception[:body] = 'error body'
# Intentionally not setting status
end
it { expect(subject.wrapped_exception).to be_nil }
it { expect(subject.response).to eq(exception) }
it { expect(subject.message).to eq('the server responded with status for ') }
end
context 'with Faraday::Env with direct method and url properties' do
let(:exception) { Faraday::Env.new }
before do
exception.status = 404
exception.method = :get
exception.url = URI('http://example.com/test')
exception[:body] = 'not found'
end
it { expect(subject.wrapped_exception).to be_nil }
it { expect(subject.response).to eq(exception) }
it { expect(subject.message).to eq('the server responded with status 404 for GET http://example.com/test') }
end
end
end

Some files were not shown because too many files have changed in this diff Show More